Merge "Pass --write-reference-baseline to lint"
diff --git a/Android.bp b/Android.bp
index d6260b4..0e8d86d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -62,6 +62,7 @@
         },
     },
     notice: ":mingw-libwinpthread-notice",
+    licenses: ["winpthreads_license"],
 }
 
 kernel_headers {
@@ -126,3 +127,7 @@
     srcs: ["cc/config/global.go"],
     out: ["clang-prebuilts-version.txt"],
 }
+
+dexpreopt_systemserver_check {
+    name: "dexpreopt_systemserver_check",
+}
diff --git a/README.md b/README.md
index e92349e..b820fd1 100644
--- a/README.md
+++ b/README.md
@@ -33,8 +33,9 @@
 Every module must have a `name` property, and the value must be unique across
 all Android.bp files.
 
-For a list of valid module types and their properties see
-[$OUT_DIR/soong/docs/soong_build.html](https://ci.android.com/builds/latest/branches/aosp-build-tools/targets/linux/view/soong_build.html).
+The list of valid module types and their properties can be generated by calling
+`m soong_docs`. It will be written to `$OUT_DIR/soong/docs/soong_build.html`.
+This list for the current version of Soong can be found [here](https://ci.android.com/builds/latest/branches/aosp-build-tools/targets/linux/view/soong_build.html).
 
 ### File lists
 
@@ -560,27 +561,41 @@
 
 ## Developing for Soong
 
-To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
-```bash
-apt install bindfs
-export GOPATH=<path to the directory you created>
-build/soong/scripts/setup_go_workspace_for_soong.sh
-```
+To load the code of Soong in IntelliJ:
 
-This will bind mount the Soong source directories into the directory in the layout expected by
-the IDE.
-
+* File -> Open, open the `build/soong` directory. It will be opened as a new
+  project.
+* File -> Settings, then Languages & Frameworks -> Go -> GOROOT, then set it to
+  `prebuilts/go/linux-x86`
+* File -> Project Structure, then, Project Settings -> Modules, then Add
+  Content Root, then add the `build/blueprint` directory.
+* Optional: also add the `external/golang-protobuf` directory. In practice,
+  IntelliJ seems to work well enough without this, too.
 ### Running Soong in a debugger
 
-To run the soong_build process in a debugger, install `dlv` and then start the build with
-`SOONG_DELVE=<listen addr>` in the environment.
+To make `soong_build` wait for a debugger connection, install `dlv` and then
+start the build with `SOONG_DELVE=<listen addr>` in the environment.
 For example:
 ```bash
-SOONG_DELVE=:1234 m nothing
+SOONG_DELVE=:5006 m nothing
 ```
-and then in another terminal:
+
+To make `soong_ui` wait for a debugger connection, use the `SOONG_UI_DELVE`
+variable:
+
 ```
-dlv connect :1234
+SOONG_UI_DELVE=:5006 m nothing
+```
+
+
+setting or unsetting `SOONG_DELVE` causes a recompilation of `soong_build`. This
+is because in order to debug the binary, it needs to be built with debug
+symbols.
+
+To test the debugger connection, run this command:
+
+```
+dlv connect :5006
 ```
 
 If you see an error:
@@ -595,6 +610,21 @@
 sudo sysctl -w kernel.yama.ptrace_scope=0
 ```
 
+To connect to the process using IntelliJ:
+
+* Run -> Edit Configurations...
+* Choose "Go Remote" on the left
+* Click on the "+" buttion on the top-left
+* Give it a nice name and set "Host" to localhost and "Port" to the port in the
+  environment variable
+
+Debugging works far worse than debugging Java, but is sometimes useful.
+
+Sometimes the `dlv` process hangs on connection. A symptom of this is `dlv`
+spinning a core or two. In that case, `kill -9` `dlv` and try again.
+Anecdotally, it _feels_ like waiting a minute after the start of `soong_build`
+helps.
+
 ## Contact
 
 Email android-building@googlegroups.com (external) for any questions, or see
diff --git a/android/Android.bp b/android/Android.bp
index 5901ed9..da36959 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -19,6 +19,9 @@
         "soong-ui-metrics_proto",
         "golang-protobuf-proto",
         "golang-protobuf-encoding-prototext",
+
+        // Only used for tests.
+        "androidmk-parser",
     ],
     srcs: [
         "androidmk.go",
@@ -44,6 +47,7 @@
         "image.go",
         "license.go",
         "license_kind.go",
+        "license_metadata.go",
         "license_sdk_member.go",
         "licenses.go",
         "makefile_goal.go",
@@ -80,7 +84,6 @@
         "util.go",
         "variable.go",
         "visibility.go",
-        "writedocs.go",
     ],
     testSrcs: [
         "android_test.go",
@@ -111,6 +114,8 @@
         "paths_test.go",
         "prebuilt_test.go",
         "rule_builder_test.go",
+        "sdk_version_test.go",
+        "sdk_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
         "util_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 9853d2c..72b6584 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -25,15 +25,16 @@
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
+	"runtime"
 	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
+	"github.com/google/blueprint/pathtools"
 )
 
 func init() {
@@ -113,7 +114,7 @@
 	// If true, the module is skipped and does not appear on the final Android-<product name>.mk
 	// file. Useful when a module needs to be skipped conditionally.
 	Disabled bool
-	// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_prebuilt.mk
+	// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk
 	// If not set, $(BUILD_SYSTEM)/prebuilt.mk is used.
 	Include string
 	// Required modules that need to be built and included in the final build output when building
@@ -473,12 +474,14 @@
 	ModuleDir(module blueprint.Module) string
 	Config() Config
 	ModuleProvider(module blueprint.Module, provider blueprint.ProviderKey) interface{}
+	ModuleHasProvider(module blueprint.Module, provider blueprint.ProviderKey) bool
 }
 
 func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
 	a.EntryMap = make(map[string][]string)
-	amod := mod.(Module).base()
-	name := amod.BaseModuleName()
+	amod := mod.(Module)
+	base := amod.base()
+	name := base.BaseModuleName()
 	if a.OverrideName != "" {
 		name = a.OverrideName
 	}
@@ -486,9 +489,9 @@
 	if a.Include == "" {
 		a.Include = "$(BUILD_PREBUILT)"
 	}
-	a.Required = append(a.Required, mod.(Module).RequiredModuleNames()...)
-	a.Host_required = append(a.Host_required, mod.(Module).HostRequiredModuleNames()...)
-	a.Target_required = append(a.Target_required, mod.(Module).TargetRequiredModuleNames()...)
+	a.Required = append(a.Required, amod.RequiredModuleNames()...)
+	a.Host_required = append(a.Host_required, amod.HostRequiredModuleNames()...)
+	a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
 
 	for _, distString := range a.GetDistForGoals(mod) {
 		fmt.Fprintf(&a.header, distString)
@@ -499,14 +502,14 @@
 	// Collect make variable assignment entries.
 	a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
 	a.SetString("LOCAL_MODULE", name+a.SubName)
-	a.AddStrings("LOCAL_LICENSE_KINDS", amod.commonProperties.Effective_license_kinds...)
-	a.AddStrings("LOCAL_LICENSE_CONDITIONS", amod.commonProperties.Effective_license_conditions...)
-	a.AddStrings("LOCAL_NOTICE_FILE", amod.commonProperties.Effective_license_text.Strings()...)
+	a.AddStrings("LOCAL_LICENSE_KINDS", base.commonProperties.Effective_license_kinds...)
+	a.AddStrings("LOCAL_LICENSE_CONDITIONS", base.commonProperties.Effective_license_conditions...)
+	a.AddStrings("LOCAL_NOTICE_FILE", base.commonProperties.Effective_license_text.Strings()...)
 	// TODO(b/151177513): Does this code need to set LOCAL_MODULE_IS_CONTAINER ?
-	if amod.commonProperties.Effective_package_name != nil {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *amod.commonProperties.Effective_package_name)
-	} else if len(amod.commonProperties.Effective_licenses) > 0 {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(amod.commonProperties.Effective_licenses, " "))
+	if base.commonProperties.Effective_package_name != nil {
+		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *base.commonProperties.Effective_package_name)
+	} else if len(base.commonProperties.Effective_licenses) > 0 {
+		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(base.commonProperties.Effective_licenses, " "))
 	}
 	a.SetString("LOCAL_MODULE_CLASS", a.Class)
 	a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
@@ -514,31 +517,41 @@
 	a.AddStrings("LOCAL_HOST_REQUIRED_MODULES", a.Host_required...)
 	a.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", a.Target_required...)
 
+	// If the install rule was generated by Soong tell Make about it.
+	if len(base.katiInstalls) > 0 {
+		// Assume the primary install file is last since it probably needs to depend on any other
+		// installed files.  If that is not the case we can add a method to specify the primary
+		// installed file.
+		a.SetPath("LOCAL_SOONG_INSTALLED_MODULE", base.katiInstalls[len(base.katiInstalls)-1].to)
+		a.SetString("LOCAL_SOONG_INSTALL_PAIRS", base.katiInstalls.BuiltInstalled())
+		a.SetPaths("LOCAL_SOONG_INSTALL_SYMLINKS", base.katiSymlinks.InstallPaths().Paths())
+	}
+
 	if am, ok := mod.(ApexModule); ok {
 		a.SetBoolIfTrue("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", am.NotAvailableForPlatform())
 	}
 
-	archStr := amod.Arch().ArchType.String()
+	archStr := base.Arch().ArchType.String()
 	host := false
-	switch amod.Os().Class {
+	switch base.Os().Class {
 	case Host:
-		if amod.Target().HostCross {
+		if base.Target().HostCross {
 			// Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common.
-			if amod.Arch().ArchType != Common {
+			if base.Arch().ArchType != Common {
 				a.SetString("LOCAL_MODULE_HOST_CROSS_ARCH", archStr)
 			}
 		} else {
 			// Make cannot identify LOCAL_MODULE_HOST_ARCH:= common.
-			if amod.Arch().ArchType != Common {
+			if base.Arch().ArchType != Common {
 				a.SetString("LOCAL_MODULE_HOST_ARCH", archStr)
 			}
 		}
 		host = true
 	case Device:
 		// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
-		if amod.Arch().ArchType != Common {
-			if amod.Target().NativeBridge {
-				hostArchStr := amod.Target().NativeBridgeHostArchName
+		if base.Arch().ArchType != Common {
+			if base.Target().NativeBridge {
+				hostArchStr := base.Target().NativeBridgeHostArchName
 				if hostArchStr != "" {
 					a.SetString("LOCAL_MODULE_TARGET_ARCH", hostArchStr)
 				}
@@ -547,31 +560,31 @@
 			}
 		}
 
-		if !amod.InRamdisk() && !amod.InVendorRamdisk() {
-			a.AddPaths("LOCAL_FULL_INIT_RC", amod.initRcPaths)
+		if !base.InRamdisk() && !base.InVendorRamdisk() {
+			a.AddPaths("LOCAL_FULL_INIT_RC", base.initRcPaths)
 		}
-		if len(amod.vintfFragmentsPaths) > 0 {
-			a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", amod.vintfFragmentsPaths)
+		if len(base.vintfFragmentsPaths) > 0 {
+			a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", base.vintfFragmentsPaths)
 		}
-		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary))
-		if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
+		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(base.commonProperties.Proprietary))
+		if Bool(base.commonProperties.Vendor) || Bool(base.commonProperties.Soc_specific) {
 			a.SetString("LOCAL_VENDOR_MODULE", "true")
 		}
-		a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(amod.commonProperties.Device_specific))
-		a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_specific))
-		a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(amod.commonProperties.System_ext_specific))
-		if amod.commonProperties.Owner != nil {
-			a.SetString("LOCAL_MODULE_OWNER", *amod.commonProperties.Owner)
+		a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(base.commonProperties.Device_specific))
+		a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(base.commonProperties.Product_specific))
+		a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(base.commonProperties.System_ext_specific))
+		if base.commonProperties.Owner != nil {
+			a.SetString("LOCAL_MODULE_OWNER", *base.commonProperties.Owner)
 		}
 	}
 
-	if len(amod.noticeFiles) > 0 {
-		a.SetString("LOCAL_NOTICE_FILE", strings.Join(amod.noticeFiles.Strings(), " "))
+	if len(base.noticeFiles) > 0 {
+		a.SetString("LOCAL_NOTICE_FILE", strings.Join(base.noticeFiles.Strings(), " "))
 	}
 
 	if host {
-		makeOs := amod.Os().String()
-		if amod.Os() == Linux || amod.Os() == LinuxBionic || amod.Os() == LinuxMusl {
+		makeOs := base.Os().String()
+		if base.Os() == Linux || base.Os() == LinuxBionic || base.Os() == LinuxMusl {
 			makeOs = "linux"
 		}
 		a.SetString("LOCAL_MODULE_HOST_OS", makeOs)
@@ -579,10 +592,10 @@
 	}
 
 	prefix := ""
-	if amod.ArchSpecific() {
-		switch amod.Os().Class {
+	if base.ArchSpecific() {
+		switch base.Os().Class {
 		case Host:
-			if amod.Target().HostCross {
+			if base.Target().HostCross {
 				prefix = "HOST_CROSS_"
 			} else {
 				prefix = "HOST_"
@@ -592,11 +605,16 @@
 
 		}
 
-		if amod.Arch().ArchType != ctx.Config().Targets[amod.Os()][0].Arch.ArchType {
+		if base.Arch().ArchType != ctx.Config().Targets[base.Os()][0].Arch.ArchType {
 			prefix = "2ND_" + prefix
 		}
 	}
 
+	if ctx.ModuleHasProvider(mod, LicenseMetadataProvider) {
+		licenseMetadata := ctx.ModuleProvider(mod, LicenseMetadataProvider).(*LicenseMetadataInfo)
+		a.SetPath("LOCAL_SOONG_LICENSE_METADATA", licenseMetadata.LicenseMetadataPath)
+	}
+
 	extraCtx := &androidMkExtraEntriesContext{
 		ctx: ctx,
 		mod: mod,
@@ -678,7 +696,7 @@
 	})
 }
 
-func translateAndroidMk(ctx SingletonContext, mkFile string, mods []blueprint.Module) error {
+func translateAndroidMk(ctx SingletonContext, absMkFile string, mods []blueprint.Module) error {
 	buf := &bytes.Buffer{}
 
 	fmt.Fprintln(buf, "LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))")
@@ -687,7 +705,7 @@
 	for _, mod := range mods {
 		err := translateAndroidMkModule(ctx, buf, mod)
 		if err != nil {
-			os.Remove(mkFile)
+			os.Remove(absMkFile)
 			return err
 		}
 
@@ -707,27 +725,7 @@
 		fmt.Fprintf(buf, "STATS.SOONG_MODULE_TYPE.%s := %d\n", mod_type, typeStats[mod_type])
 	}
 
-	// Don't write to the file if it hasn't changed
-	if _, err := os.Stat(absolutePath(mkFile)); !os.IsNotExist(err) {
-		if data, err := ioutil.ReadFile(absolutePath(mkFile)); err == nil {
-			matches := buf.Len() == len(data)
-
-			if matches {
-				for i, value := range buf.Bytes() {
-					if value != data[i] {
-						matches = false
-						break
-					}
-				}
-			}
-
-			if matches {
-				return nil
-			}
-		}
-	}
-
-	return ioutil.WriteFile(absolutePath(mkFile), buf.Bytes(), 0666)
+	return pathtools.WriteFileIfChanged(absMkFile, buf.Bytes(), 0666)
 }
 
 func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.Module) error {
@@ -836,6 +834,7 @@
 		case "*aidl.aidlApi": // writes non-custom before adding .phony
 		case "*aidl.aidlMapping": // writes non-custom before adding .phony
 		case "*android.customModule": // appears in tests only
+		case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
 		case "*apex.apexBundle": // license properties written
 		case "*bpf.bpf": // license properties written (both for module and objs)
 		case "*genrule.Module": // writes non-custom before adding .phony
@@ -845,7 +844,7 @@
 		case "*selinux.selinuxContextsModule": // license properties written
 		case "*sysprop.syspropLibrary": // license properties written
 		default:
-			if ctx.Config().IsEnvTrue("ANDROID_REQUIRE_LICENSES") {
+			if !ctx.Config().IsEnvFalse("ANDROID_REQUIRE_LICENSES") {
 				return fmt.Errorf("custom make rules not allowed for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), ctx.ModuleName(mod))
 			}
 		}
@@ -894,6 +893,10 @@
 	return nil
 }
 
+func ShouldSkipAndroidMkProcessing(module Module) bool {
+	return shouldSkipAndroidMkProcessing(module.base())
+}
+
 func shouldSkipAndroidMkProcessing(module *ModuleBase) bool {
 	if !module.commonProperties.NamespaceExportedToMake {
 		// TODO(jeffrygaston) do we want to validate that there are no modules being
@@ -901,6 +904,18 @@
 		return true
 	}
 
+	// On Mac, only expose host darwin modules to Make, as that's all we claim to support.
+	// In reality, some of them depend on device-built (Java) modules, so we can't disable all
+	// device modules in Soong, but we can hide them from Make (and thus the build user interface)
+	if runtime.GOOS == "darwin" && module.Os() != Darwin {
+		return true
+	}
+
+	// Only expose the primary Darwin target, as Make does not understand Darwin+Arm64
+	if module.Os() == Darwin && module.Target().HostCross {
+		return true
+	}
+
 	return !module.Enabled() ||
 		module.commonProperties.HideFromMake ||
 		// Make does not understand LinuxBionic
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 8eda9b2..ecfb008 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"io"
 	"reflect"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -155,6 +156,11 @@
 }
 
 func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
+	if runtime.GOOS == "darwin" {
+		// Device modules are not exported on Mac, so this test doesn't work.
+		t.SkipNow()
+	}
+
 	bp := `
 	custom {
 		name: "foo",
diff --git a/android/apex.go b/android/apex.go
index b9efe4e..cf1bcfe 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -854,7 +854,6 @@
 	}
 	return list
 }(map[string]int{
-	"adbd":                                                     30,
 	"android.net.ipsec.ike":                                    30,
 	"androidx.annotation_annotation-nodeps":                    29,
 	"androidx.arch.core_core-common-nodeps":                    29,
@@ -879,24 +878,8 @@
 	"kotlinx-coroutines-android-nodeps":                        30,
 	"kotlinx-coroutines-core":                                  28,
 	"kotlinx-coroutines-core-nodeps":                           30,
-	"libadb_crypto":                                            30,
-	"libadb_pairing_auth":                                      30,
-	"libadb_pairing_connection":                                30,
-	"libadb_pairing_server":                                    30,
-	"libadb_protos":                                            30,
-	"libadb_tls_connection":                                    30,
-	"libadbconnection_client":                                  30,
-	"libadbconnection_server":                                  30,
-	"libadbd_core":                                             30,
-	"libadbd_services":                                         30,
-	"libadbd":                                                  30,
-	"libapp_processes_protos_lite":                             30,
-	"libasyncio":                                               30,
 	"libbrotli":                                                30,
-	"libbuildversion":                                          30,
 	"libcrypto_static":                                         30,
-	"libcrypto_utils":                                          30,
-	"libdiagnose_usb":                                          30,
 	"libeigen":                                                 30,
 	"liblz4":                                                   30,
 	"libmdnssd":                                                30,
@@ -906,7 +889,6 @@
 	"libprocpartition":                                         30,
 	"libprotobuf-java-lite":                                    30,
 	"libprotoutil":                                             30,
-	"libsync":                                                  30,
 	"libtextclassifier_hash_headers":                           30,
 	"libtextclassifier_hash_static":                            30,
 	"libtflite_kernel_utils":                                   30,
@@ -926,16 +908,18 @@
 //
 // Return true if the `to` module should be visited, false otherwise.
 type PayloadDepsCallback func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool
+type WalkPayloadDepsFunc func(ctx ModuleContext, do PayloadDepsCallback)
 
-// UpdatableModule represents updatable APEX/APK
-type UpdatableModule interface {
+// ModuleWithMinSdkVersionCheck represents a module that implements min_sdk_version checks
+type ModuleWithMinSdkVersionCheck interface {
 	Module
-	WalkPayloadDeps(ctx ModuleContext, do PayloadDepsCallback)
+	MinSdkVersion(ctx EarlyModuleContext) SdkSpec
+	CheckMinSdkVersion(ctx ModuleContext)
 }
 
 // CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version
 // accordingly
-func CheckMinSdkVersion(m UpdatableModule, ctx ModuleContext, minSdkVersion ApiLevel) {
+func CheckMinSdkVersion(ctx ModuleContext, minSdkVersion ApiLevel, walk WalkPayloadDepsFunc) {
 	// do not enforce min_sdk_version for host
 	if ctx.Host() {
 		return
@@ -951,7 +935,7 @@
 		return
 	}
 
-	m.WalkPayloadDeps(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
+	walk(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
 		if externalDep {
 			// external deps are outside the payload boundary, which is "stable"
 			// interface. We don't have to check min_sdk_version for external
@@ -961,6 +945,14 @@
 		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
 			return false
 		}
+		if m, ok := to.(ModuleWithMinSdkVersionCheck); ok {
+			// This dependency performs its own min_sdk_version check, just make sure it sets min_sdk_version
+			// to trigger the check.
+			if !m.MinSdkVersion(ctx).Specified() {
+				ctx.OtherModuleErrorf(m, "must set min_sdk_version")
+			}
+			return false
+		}
 		if err := to.ShouldSupportSdkVersion(ctx, minSdkVersion); err != nil {
 			toName := ctx.OtherModuleName(to)
 			if ver, ok := minSdkVersionAllowlist[toName]; !ok || ver.GreaterThan(minSdkVersion) {
diff --git a/android/api_levels.go b/android/api_levels.go
index 84ab27c..1fbbc15 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -177,6 +177,10 @@
 // libandroid_support.
 var FirstNonLibAndroidSupportVersion = uncheckedFinalApiLevel(21)
 
+// LastWithoutModuleLibCoreSystemModules is the last API level where prebuilts/sdk does not contain
+// a core-for-system-modules.jar for the module-lib API scope.
+var LastWithoutModuleLibCoreSystemModules = uncheckedFinalApiLevel(31)
+
 // If the `raw` input is the codename of an API level has been finalized, this
 // function returns the API level number associated with that API level. If the
 // input is *not* a finalized codename, the input is returned unmodified.
@@ -188,8 +192,8 @@
 // * "30" -> "30"
 // * "R" -> "30"
 // * "S" -> "S"
-func ReplaceFinalizedCodenames(ctx PathContext, raw string) string {
-	num, ok := getFinalCodenamesMap(ctx.Config())[raw]
+func ReplaceFinalizedCodenames(config Config, raw string) string {
+	num, ok := getFinalCodenamesMap(config)[raw]
 	if !ok {
 		return raw
 	}
@@ -197,7 +201,7 @@
 	return strconv.Itoa(num)
 }
 
-// Converts the given string `raw` to an ApiLevel, possibly returning an error.
+// ApiLevelFromUser converts the given string `raw` to an ApiLevel, possibly returning an error.
 //
 // `raw` must be non-empty. Passing an empty string results in a panic.
 //
@@ -212,6 +216,12 @@
 // Inputs that are not "current", known previews, or convertible to an integer
 // will return an error.
 func ApiLevelFromUser(ctx PathContext, raw string) (ApiLevel, error) {
+	return ApiLevelFromUserWithConfig(ctx.Config(), raw)
+}
+
+// ApiLevelFromUserWithConfig implements ApiLevelFromUser, see comments for
+// ApiLevelFromUser for more details.
+func ApiLevelFromUserWithConfig(config Config, raw string) (ApiLevel, error) {
 	if raw == "" {
 		panic("API level string must be non-empty")
 	}
@@ -220,13 +230,13 @@
 		return FutureApiLevel, nil
 	}
 
-	for _, preview := range ctx.Config().PreviewApiLevels() {
+	for _, preview := range config.PreviewApiLevels() {
 		if raw == preview.String() {
 			return preview, nil
 		}
 	}
 
-	canonical := ReplaceFinalizedCodenames(ctx, raw)
+	canonical := ReplaceFinalizedCodenames(config, raw)
 	asInt, err := strconv.Atoi(canonical)
 	if err != nil {
 		return NoneApiLevel, fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", canonical)
@@ -236,6 +246,27 @@
 	return apiLevel, nil
 }
 
+// ApiLevelForTest returns an ApiLevel constructed from the supplied raw string.
+//
+// This only supports "current" and numeric levels, code names are not supported.
+func ApiLevelForTest(raw string) ApiLevel {
+	if raw == "" {
+		panic("API level string must be non-empty")
+	}
+
+	if raw == "current" {
+		return FutureApiLevel
+	}
+
+	asInt, err := strconv.Atoi(raw)
+	if err != nil {
+		panic(fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", raw))
+	}
+
+	apiLevel := uncheckedFinalApiLevel(asInt)
+	return apiLevel
+}
+
 // Converts an API level string `raw` into an ApiLevel in the same method as
 // `ApiLevelFromUser`, but the input is assumed to have no errors and any errors
 // will panic instead of returning an error.
@@ -289,6 +320,7 @@
 			"P":     28,
 			"Q":     29,
 			"R":     30,
+			"S":     31,
 		}
 
 		// TODO: Differentiate "current" and "future".
@@ -331,6 +363,7 @@
 			"P":     28,
 			"Q":     29,
 			"R":     30,
+			"S":     31,
 		}
 		for i, codename := range config.PlatformVersionActiveCodenames() {
 			apiLevelsMap[codename] = previewAPILevelBase + i
diff --git a/android/arch.go b/android/arch.go
index f7eb963..e08fd5c 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -168,7 +168,7 @@
 	return archType
 }
 
-// ArchTypeList returns the a slice copy of the 4 supported ArchTypes for arm,
+// ArchTypeList returns a slice copy of the 4 supported ArchTypes for arm,
 // arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
@@ -308,7 +308,7 @@
 	// LinuxMusl is the OS for the Linux kernel plus the musl runtime.
 	LinuxMusl = newOsType("linux_musl", Host, false, X86, X86_64)
 	// Darwin is the OS for MacOS/Darwin host machines.
-	Darwin = newOsType("darwin", Host, false, X86_64)
+	Darwin = newOsType("darwin", Host, false, Arm64, X86_64)
 	// LinuxBionic is the OS for the Linux kernel plus the Bionic libc runtime, but without the
 	// rest of Android.
 	LinuxBionic = newOsType("linux_bionic", Host, false, Arm64, X86_64)
@@ -408,7 +408,7 @@
 
 	// addPathDepsForProps does not descend into sub structs, so we need to descend into the
 	// arch-specific properties ourselves
-	properties := []interface{}{}
+	var properties []interface{}
 	for _, archProperties := range m.archProperties {
 		for _, archProps := range archProperties {
 			archPropValues := reflect.ValueOf(archProps).Elem()
@@ -566,6 +566,8 @@
 	return variants
 }
 
+var DarwinUniversalVariantTag = archDepTag{name: "darwin universal binary"}
+
 // archMutator splits a module into a variant for each Target requested by the module.  Target selection
 // for a module is in three levels, OsClass, multilib, and then Target.
 // OsClass selection is determined by:
@@ -631,8 +633,7 @@
 	image := base.commonProperties.ImageVariation
 	// Filter NativeBridge targets unless they are explicitly supported.
 	// Skip creating native bridge variants for non-core modules.
-	if os == Android &&
-		!(Bool(base.commonProperties.Native_bridge_supported) && image == CoreVariation) {
+	if os == Android && !(base.IsNativeBridgeSupported() && image == CoreVariation) {
 
 		var targets []Target
 		for _, t := range osTargets {
@@ -653,7 +654,7 @@
 	prefer32 := os == Windows
 
 	// Determine the multilib selection for this module.
-	multilib, extraMultilib := decodeMultilib(base, os.Class)
+	multilib, extraMultilib := decodeMultilib(base, os)
 
 	// Convert the multilib selection into a list of Targets.
 	targets, err := decodeMultilibTargets(multilib, osTargets, prefer32)
@@ -697,6 +698,21 @@
 	for i, m := range modules {
 		addTargetProperties(m, targets[i], multiTargets, i == 0)
 		m.base().setArchProperties(mctx)
+
+		// Install support doesn't understand Darwin+Arm64
+		if os == Darwin && targets[i].HostCross {
+			m.base().commonProperties.SkipInstall = true
+		}
+	}
+
+	// Create a dependency for Darwin Universal binaries from the primary to secondary
+	// architecture. The module itself will be responsible for calling lipo to merge the outputs.
+	if os == Darwin {
+		if multilib == "darwin_universal" && len(modules) == 2 {
+			mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[1], modules[0])
+		} else if multilib == "darwin_universal_common_first" && len(modules) == 3 {
+			mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[2], modules[1])
+		}
 	}
 }
 
@@ -713,9 +729,9 @@
 // multilib from the factory's call to InitAndroidArchModule if none was set.  For modules that
 // called InitAndroidMultiTargetsArchModule it always returns "common" for multilib, and returns
 // the actual multilib in extraMultilib.
-func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib string) {
+func decodeMultilib(base *ModuleBase, os OsType) (multilib, extraMultilib string) {
 	// First check the "android.compile_multilib" or "host.compile_multilib" properties.
-	switch class {
+	switch os.Class {
 	case Device:
 		multilib = String(base.commonProperties.Target.Android.Compile_multilib)
 	case Host:
@@ -733,6 +749,26 @@
 	}
 
 	if base.commonProperties.UseTargetVariants {
+		// Darwin has the concept of "universal binaries" which is implemented in Soong by
+		// building both x86_64 and arm64 variants, and having select module types know how to
+		// merge the outputs of their corresponding variants together into a final binary. Most
+		// module types don't need to understand this logic, as we only build a small portion
+		// of the tree for Darwin, and only module types writing macho files need to do the
+		// merging.
+		//
+		// This logic is not enabled for:
+		//  "common", as it's not an arch-specific variant
+		//  "32", as Darwin never has a 32-bit variant
+		//  !UseTargetVariants, as the module has opted into handling the arch-specific logic on
+		//    its own.
+		if os == Darwin && multilib != "common" && multilib != "32" {
+			if multilib == "common_first" {
+				multilib = "darwin_universal_common_first"
+			} else {
+				multilib = "darwin_universal"
+			}
+		}
+
 		return multilib, ""
 	} else {
 		// For app modules a single arch variant will be created per OS class which is expected to handle all the
@@ -935,6 +971,8 @@
 		if len(values) > 0 && values[0] != "path" {
 			panic(fmt.Errorf("unknown tags %q in field %q", values, prefix+field.Name))
 		} else if len(values) == 1 {
+			// FIXME(b/200678898): This assumes that the only tag type when there's
+			// `android:"arch_variant"` is `android` itself and thus clobbers others
 			field.Tag = reflect.StructTag(`android:"` + strings.Join(values, ",") + `"`)
 		} else {
 			field.Tag = ``
@@ -957,8 +995,11 @@
 
 	// Store the original list of top level property structs
 	base.generalProperties = m.GetProperties()
+	if len(base.archProperties) != 0 {
+		panic(fmt.Errorf("module %s already has archProperties", m.Name()))
+	}
 
-	for _, properties := range base.generalProperties {
+	getStructType := func(properties interface{}) reflect.Type {
 		propertiesValue := reflect.ValueOf(properties)
 		t := propertiesValue.Type()
 		if propertiesValue.Kind() != reflect.Ptr {
@@ -968,10 +1009,14 @@
 
 		propertiesValue = propertiesValue.Elem()
 		if propertiesValue.Kind() != reflect.Struct {
-			panic(fmt.Errorf("properties must be a pointer to a struct, got %T",
+			panic(fmt.Errorf("properties must be a pointer to a struct, got a pointer to %T",
 				propertiesValue.Interface()))
 		}
+		return t
+	}
 
+	for _, properties := range base.generalProperties {
+		t := getStructType(properties)
 		// Get or create the arch-specific property struct types for this property struct type.
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
 			return createArchPropTypeDesc(t)
@@ -1586,10 +1631,12 @@
 	return PrefixInList(arch.Abi, "arm")
 }
 
-// hasArmArch returns true if targets has at least non-native_bridge arm Android arch
+// hasArmAndroidArch returns true if targets has at least
+// one arm Android arch (possibly native bridged)
 func hasArmAndroidArch(targets []Target) bool {
 	for _, target := range targets {
-		if target.Os == Android && target.Arch.ArchType == Arm {
+		if target.Os == Android &&
+			(target.Arch.ArchType == Arm || target.Arch.ArchType == Arm64) {
 			return true
 		}
 	}
@@ -1785,6 +1832,15 @@
 		if len(buildTargets) == 0 {
 			buildTargets = filterMultilibTargets(targets, "lib64")
 		}
+	case "darwin_universal":
+		buildTargets = filterMultilibTargets(targets, "lib64")
+		// Reverse the targets so that the first architecture can depend on the second
+		// architecture module in order to merge the outputs.
+		reverseSliceInPlace(buildTargets)
+	case "darwin_universal_common_first":
+		archTargets := filterMultilibTargets(targets, "lib64")
+		reverseSliceInPlace(archTargets)
+		buildTargets = append(getCommonTargets(targets), archTargets...)
 	default:
 		return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", "prefer32" or "first_prefer32" found %q`,
 			multilib)
@@ -1998,38 +2054,80 @@
 		// Create a new instance of the requested property set
 		value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
 
-		// Merge all the structs together
-		for _, propertyStruct := range propertyStructs {
-			mergePropertyStruct(ctx, value, propertyStruct)
-		}
-
-		archToProp[arch.Name] = value
+		archToProp[arch.Name] = mergeStructs(ctx, propertyStructs, value)
 	}
 	axisToProps[bazel.ArchConfigurationAxis] = archToProp
 
 	osToProp := ArchVariantProperties{}
 	archOsToProp := ArchVariantProperties{}
+
+	linuxStructs := getTargetStructs(ctx, archProperties, "Linux")
+	bionicStructs := getTargetStructs(ctx, archProperties, "Bionic")
+	hostStructs := getTargetStructs(ctx, archProperties, "Host")
+	hostNotWindowsStructs := getTargetStructs(ctx, archProperties, "Not_windows")
+
 	// For android, linux, ...
 	for _, os := range osTypeList {
 		if os == CommonOS {
 			// It looks like this OS value is not used in Blueprint files
 			continue
 		}
-		osToProp[os.Name] = getTargetStruct(ctx, propertySet, archProperties, os.Field)
+		osStructs := make([]reflect.Value, 0)
+
+		osSpecificStructs := getTargetStructs(ctx, archProperties, os.Field)
+		if os.Class == Host {
+			osStructs = append(osStructs, hostStructs...)
+		}
+		if os.Linux() {
+			osStructs = append(osStructs, linuxStructs...)
+		}
+		if os.Bionic() {
+			osStructs = append(osStructs, bionicStructs...)
+		}
+
+		if os == LinuxMusl {
+			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Musl")...)
+		}
+		if os == Linux {
+			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Glibc")...)
+		}
+
+		osStructs = append(osStructs, osSpecificStructs...)
+
+		if os.Class == Host && os != Windows {
+			osStructs = append(osStructs, hostNotWindowsStructs...)
+		}
+		osToProp[os.Name] = mergeStructs(ctx, osStructs, propertySet)
+
 		// For arm, x86, ...
 		for _, arch := range osArchTypeMap[os] {
+			osArchStructs := make([]reflect.Value, 0)
+
+			// Auto-combine with Linux_ and Bionic_ targets. This potentially results in
+			// repetition and select() bloat, but use of Linux_* and Bionic_* targets is rare.
+			// TODO(b/201423152): Look into cleanup.
+			if os.Linux() {
+				targetField := "Linux_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
+			}
+			if os.Bionic() {
+				targetField := "Bionic_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
+			}
+
 			targetField := GetCompoundTargetField(os, arch)
 			targetName := fmt.Sprintf("%s_%s", os.Name, arch.Name)
-			archOsToProp[targetName] = getTargetStruct(ctx, propertySet, archProperties, targetField)
+			targetStructs := getTargetStructs(ctx, archProperties, targetField)
+			osArchStructs = append(osArchStructs, targetStructs...)
+
+			archOsToProp[targetName] = mergeStructs(ctx, osArchStructs, propertySet)
 		}
 	}
+
 	axisToProps[bazel.OsConfigurationAxis] = osToProp
 	axisToProps[bazel.OsArchConfigurationAxis] = archOsToProp
-
-	axisToProps[bazel.BionicConfigurationAxis] = map[string]interface{}{
-		"bionic": getTargetStruct(ctx, propertySet, archProperties, "Bionic"),
-	}
-
 	return axisToProps
 }
 
@@ -2047,17 +2145,23 @@
 //      }
 //    }
 // This would return a BaseCompilerProperties with BaseCompilerProperties.Srcs = ["foo.c"]
-func getTargetStruct(ctx ArchVariantContext, propertySet interface{}, archProperties []interface{}, targetName string) interface{} {
-	propertyStructs := make([]reflect.Value, 0)
+func getTargetStructs(ctx ArchVariantContext, archProperties []interface{}, targetName string) []reflect.Value {
+	var propertyStructs []reflect.Value
 	for _, archProperty := range archProperties {
 		archPropValues := reflect.ValueOf(archProperty).Elem()
 		targetProp := archPropValues.FieldByName("Target").Elem()
 		targetStruct, ok := getChildPropertyStruct(ctx, targetProp, targetName, targetName)
 		if ok {
 			propertyStructs = append(propertyStructs, targetStruct)
+		} else {
+			return []reflect.Value{}
 		}
 	}
 
+	return propertyStructs
+}
+
+func mergeStructs(ctx ArchVariantContext, propertyStructs []reflect.Value, propertySet interface{}) interface{} {
 	// Create a new instance of the requested property set
 	value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
 
diff --git a/android/bazel.go b/android/bazel.go
index 692fbcd..9a966b6 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"bufio"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
@@ -39,6 +41,10 @@
 	// To opt-out a module, set bazel_module: { bp2build_available: false }
 	// To defer the default setting for the directory, do not set the value.
 	Bp2build_available *bool
+
+	// CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
+	// Bazel with Bp2build.
+	CanConvertToBazel bool `blueprint:"mutated"`
 }
 
 // Properties contains common module properties for Bazel migration purposes.
@@ -48,10 +54,30 @@
 	Bazel_module bazelModuleProperties
 }
 
+// namespacedVariableProperties is a map from a string representing a Soong
+// config variable namespace, like "android" or "vendor_name" to a slice of
+// pointer to a struct containing a single field called Soong_config_variables
+// whose value mirrors the structure in the Blueprint file.
+type namespacedVariableProperties map[string][]interface{}
+
 // BazelModuleBase contains the property structs with metadata for modules which can be converted to
 // Bazel.
 type BazelModuleBase struct {
 	bazelProperties properties
+
+	// namespacedVariableProperties is used for soong_config_module_type support
+	// in bp2build. Soong config modules allow users to set module properties
+	// based on custom product variables defined in Android.bp files. These
+	// variables are namespaced to prevent clobbering, especially when set from
+	// Makefiles.
+	namespacedVariableProperties namespacedVariableProperties
+
+	// baseModuleType is set when this module was created from a module type
+	// defined by a soong_config_module_type. Every soong_config_module_type
+	// "wraps" another module type, e.g. a soong_config_module_type can wrap a
+	// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
+	// This baseModuleType is set to the wrapped module type.
+	baseModuleType string
 }
 
 // Bazelable is specifies the interface for modules that can be converted to Bazel.
@@ -60,9 +86,24 @@
 	HasHandcraftedLabel() bool
 	HandcraftedLabel() string
 	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
-	ConvertWithBp2build(ctx BazelConversionPathContext) bool
+	ShouldConvertWithBp2build(ctx BazelConversionContext) bool
+	shouldConvertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool
 	GetBazelBuildFileContents(c Config, path, name string) (string, error)
-	ConvertedToBazel(ctx BazelConversionPathContext) bool
+	ConvertWithBp2build(ctx TopDownMutatorContext)
+
+	// namespacedVariableProps is a map from a soong config variable namespace
+	// (e.g. acme, android) to a map of interfaces{}, which are really
+	// reflect.Struct pointers, representing the value of the
+	// soong_config_variables property of a module. The struct pointer is the
+	// one with the single member called Soong_config_variables, which itself is
+	// a struct containing fields for each supported feature in that namespace.
+	//
+	// The reason for using an slice of interface{} is to support defaults
+	// propagation of the struct pointers.
+	namespacedVariableProps() namespacedVariableProperties
+	setNamespacedVariableProps(props namespacedVariableProperties)
+	BaseModuleType() string
+	SetBaseModuleType(baseModuleType string)
 }
 
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@@ -75,6 +116,7 @@
 // properties.
 func InitBazelModule(module BazelModule) {
 	module.AddProperties(module.bazelProps())
+	module.bazelProps().Bazel_module.CanConvertToBazel = true
 }
 
 // bazelProps returns the Bazel properties for the given BazelModuleBase.
@@ -82,6 +124,22 @@
 	return &b.bazelProperties
 }
 
+func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
+	return b.namespacedVariableProperties
+}
+
+func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
+	b.namespacedVariableProperties = props
+}
+
+func (b *BazelModuleBase) BaseModuleType() string {
+	return b.baseModuleType
+}
+
+func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
+	b.baseModuleType = baseModuleType
+}
+
 // HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
 func (b *BazelModuleBase) HasHandcraftedLabel() bool {
 	return b.bazelProperties.Bazel_module.Label != nil
@@ -97,7 +155,7 @@
 	if b.HasHandcraftedLabel() {
 		return b.HandcraftedLabel()
 	}
-	if b.ConvertWithBp2build(ctx) {
+	if b.ShouldConvertWithBp2build(ctx) {
 		return bp2buildModuleLabel(ctx, module)
 	}
 	return "" // no label for unconverted module
@@ -150,113 +208,263 @@
 		"build/bazel/platforms":/* recursive = */ true,
 		"build/bazel/product_variables":/* recursive = */ true,
 		"build/bazel_common_rules":/* recursive = */ true,
+		// build/make/tools/signapk BUILD file is generated, so build/make/tools is not recursive.
+		"build/make/tools":/* recursive = */ false,
 		"build/pesto":/* recursive = */ true,
 
 		// external/bazelbuild-rules_android/... is needed by mixed builds, otherwise mixed builds analysis fails
 		// e.g. ERROR: Analysis of target '@soong_injection//mixed_builds:buildroot' failed
 		"external/bazelbuild-rules_android":/* recursive = */ true,
 		"external/bazel-skylib":/* recursive = */ true,
+		"external/guava":/* recursive = */ true,
+		"external/error_prone":/* recursive = */ true,
+		"external/jsr305":/* recursive = */ true,
+		"frameworks/ex/common":/* recursive = */ true,
 
+		"packages/apps/Music":/* recursive = */ true,
+		"packages/apps/QuickSearchBox":/* recursive = */ true,
+		"packages/apps/WallpaperPicker":/* recursive = */ false,
+
+		"prebuilts/gcc":/* recursive = */ true,
 		"prebuilts/sdk":/* recursive = */ false,
+		"prebuilts/sdk/current/extras/app-toolkit":/* recursive = */ false,
+		"prebuilts/sdk/current/support":/* recursive = */ false,
 		"prebuilts/sdk/tools":/* recursive = */ false,
 		"prebuilts/r8":/* recursive = */ false,
-		"packages/apps/Music":/* recursive = */ true,
 	}
 
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
 	bp2buildDefaultConfig = Bp2BuildConfig{
-		"bionic":                            Bp2BuildDefaultTrueRecursively,
-		"build/bazel/examples/apex/minimal": Bp2BuildDefaultTrueRecursively,
-		"development/sdk":                   Bp2BuildDefaultTrueRecursively,
-		"external/gwp_asan":                 Bp2BuildDefaultTrueRecursively,
-		"external/brotli":                   Bp2BuildDefaultTrue,
-		"system/core/libcutils":             Bp2BuildDefaultTrueRecursively,
-		"system/core/libprocessgroup":       Bp2BuildDefaultTrue,
+		"art/libdexfile":                        Bp2BuildDefaultTrueRecursively,
+		"bionic":                                Bp2BuildDefaultTrueRecursively,
+		"bootable/recovery/tools/recovery_l10n": Bp2BuildDefaultTrue,
+		"build/bazel/examples/soong_config_variables":        Bp2BuildDefaultTrueRecursively,
+		"build/bazel/examples/apex/minimal":                  Bp2BuildDefaultTrueRecursively,
+		"build/make/tools/signapk":                           Bp2BuildDefaultTrue,
+		"build/soong":                                        Bp2BuildDefaultTrue,
+		"build/soong/cc/libbuildversion":                     Bp2BuildDefaultTrue, // Skip tests subdir
+		"build/soong/cc/ndkstubgen":                          Bp2BuildDefaultTrue,
+		"build/soong/cc/symbolfile":                          Bp2BuildDefaultTrue,
+		"build/soong/scripts":                                Bp2BuildDefaultTrueRecursively,
+		"cts/common/device-side/nativetesthelper/jni":        Bp2BuildDefaultTrueRecursively,
+		"development/apps/DevelopmentSettings":               Bp2BuildDefaultTrue,
+		"development/apps/Fallback":                          Bp2BuildDefaultTrue,
+		"development/apps/WidgetPreview":                     Bp2BuildDefaultTrue,
+		"development/samples/BasicGLSurfaceView":             Bp2BuildDefaultTrue,
+		"development/samples/BluetoothChat":                  Bp2BuildDefaultTrue,
+		"development/samples/BrokenKeyDerivation":            Bp2BuildDefaultTrue,
+		"development/samples/Compass":                        Bp2BuildDefaultTrue,
+		"development/samples/ContactManager":                 Bp2BuildDefaultTrue,
+		"development/samples/FixedGridLayout":                Bp2BuildDefaultTrue,
+		"development/samples/HelloEffects":                   Bp2BuildDefaultTrue,
+		"development/samples/Home":                           Bp2BuildDefaultTrue,
+		"development/samples/HoneycombGallery":               Bp2BuildDefaultTrue,
+		"development/samples/JetBoy":                         Bp2BuildDefaultTrue,
+		"development/samples/KeyChainDemo":                   Bp2BuildDefaultTrue,
+		"development/samples/LceDemo":                        Bp2BuildDefaultTrue,
+		"development/samples/LunarLander":                    Bp2BuildDefaultTrue,
+		"development/samples/MultiResolution":                Bp2BuildDefaultTrue,
+		"development/samples/MultiWindow":                    Bp2BuildDefaultTrue,
+		"development/samples/NotePad":                        Bp2BuildDefaultTrue,
+		"development/samples/Obb":                            Bp2BuildDefaultTrue,
+		"development/samples/RSSReader":                      Bp2BuildDefaultTrue,
+		"development/samples/ReceiveShareDemo":               Bp2BuildDefaultTrue,
+		"development/samples/SearchableDictionary":           Bp2BuildDefaultTrue,
+		"development/samples/SipDemo":                        Bp2BuildDefaultTrue,
+		"development/samples/SkeletonApp":                    Bp2BuildDefaultTrue,
+		"development/samples/Snake":                          Bp2BuildDefaultTrue,
+		"development/samples/SpellChecker/":                  Bp2BuildDefaultTrueRecursively,
+		"development/samples/ThemedNavBarKeyboard":           Bp2BuildDefaultTrue,
+		"development/samples/ToyVpn":                         Bp2BuildDefaultTrue,
+		"development/samples/TtsEngine":                      Bp2BuildDefaultTrue,
+		"development/samples/USB/AdbTest":                    Bp2BuildDefaultTrue,
+		"development/samples/USB/MissileLauncher":            Bp2BuildDefaultTrue,
+		"development/samples/VoiceRecognitionService":        Bp2BuildDefaultTrue,
+		"development/samples/VoicemailProviderDemo":          Bp2BuildDefaultTrue,
+		"development/sdk":                                    Bp2BuildDefaultTrueRecursively,
+		"external/arm-optimized-routines":                    Bp2BuildDefaultTrueRecursively,
+		"external/boringssl":                                 Bp2BuildDefaultTrueRecursively,
+		"external/bouncycastle":                              Bp2BuildDefaultTrue,
+		"external/brotli":                                    Bp2BuildDefaultTrue,
+		"external/conscrypt":                                 Bp2BuildDefaultTrue,
+		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
+		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
+		"external/googletest":                                Bp2BuildDefaultTrueRecursively,
+		"external/gwp_asan":                                  Bp2BuildDefaultTrueRecursively,
+		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
+		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
+		"external/libcap":                                    Bp2BuildDefaultTrueRecursively,
+		"external/libcxx":                                    Bp2BuildDefaultTrueRecursively,
+		"external/libcxxabi":                                 Bp2BuildDefaultTrueRecursively,
+		"external/libevent":                                  Bp2BuildDefaultTrueRecursively,
+		"external/libpng":                                    Bp2BuildDefaultTrueRecursively,
+		"external/lz4/lib":                                   Bp2BuildDefaultTrue,
+		"external/lzma/C":                                    Bp2BuildDefaultTrueRecursively,
+		"external/mdnsresponder":                             Bp2BuildDefaultTrueRecursively,
+		"external/minijail":                                  Bp2BuildDefaultTrueRecursively,
+		"external/pcre":                                      Bp2BuildDefaultTrueRecursively,
+		"external/protobuf":                                  Bp2BuildDefaultTrueRecursively,
+		"external/python/six":                                Bp2BuildDefaultTrueRecursively,
+		"external/scudo":                                     Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libselinux":                        Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
+		"external/zlib":                                      Bp2BuildDefaultTrueRecursively,
+		"external/zstd":                                      Bp2BuildDefaultTrueRecursively,
+		"frameworks/base/media/tests/MediaDump":              Bp2BuildDefaultTrue,
+		"frameworks/base/startop/apps/test":                  Bp2BuildDefaultTrue,
+		"frameworks/native/libs/adbd_auth":                   Bp2BuildDefaultTrueRecursively,
+		"frameworks/native/opengl/tests/gl2_cameraeye":       Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/gl2_java":            Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testLatency":         Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testPauseResume":     Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testViewport":        Bp2BuildDefaultTrue,
+		"frameworks/proto_logging/stats/stats_log_api_gen":   Bp2BuildDefaultTrueRecursively,
+		"libnativehelper":                                    Bp2BuildDefaultTrueRecursively,
+		"packages/apps/DevCamera":                            Bp2BuildDefaultTrue,
+		"packages/apps/HTMLViewer":                           Bp2BuildDefaultTrue,
+		"packages/apps/Protips":                              Bp2BuildDefaultTrue,
+		"packages/modules/adb":                               Bp2BuildDefaultTrue,
+		"packages/modules/adb/apex":                          Bp2BuildDefaultTrue,
+		"packages/modules/adb/crypto":                        Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/libs":                          Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/pairing_auth":                  Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/pairing_connection":            Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/proto":                         Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/tls":                           Bp2BuildDefaultTrueRecursively,
+		"packages/providers/MediaProvider/tools/dialogs":     Bp2BuildDefaultTrue,
+		"packages/screensavers/Basic":                        Bp2BuildDefaultTrue,
+		"packages/services/Car/tests/SampleRearViewCamera":   Bp2BuildDefaultTrue,
+		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
+		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
+		"system/core/debuggerd":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/diagnose_usb":                           Bp2BuildDefaultTrueRecursively,
+		"system/core/libasyncio":                             Bp2BuildDefaultTrue,
+		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
+		"system/core/libcutils":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/libpackagelistparser":                   Bp2BuildDefaultTrueRecursively,
+		"system/core/libprocessgroup":                        Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc":               Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc_format":        Bp2BuildDefaultTrue,
+		"system/core/libsystem":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/libutils":                               Bp2BuildDefaultTrueRecursively,
+		"system/core/libvndksupport":                         Bp2BuildDefaultTrueRecursively,
 		"system/core/property_service/libpropertyinfoparser": Bp2BuildDefaultTrueRecursively,
-		"system/libbase":                  Bp2BuildDefaultTrueRecursively,
-		"system/logging/liblog":           Bp2BuildDefaultTrueRecursively,
-		"system/timezone/apex":            Bp2BuildDefaultTrueRecursively,
-		"system/timezone/output_data":     Bp2BuildDefaultTrueRecursively,
-		"external/arm-optimized-routines": Bp2BuildDefaultTrueRecursively,
-		"external/fmtlib":                 Bp2BuildDefaultTrueRecursively,
-		"external/jemalloc_new":           Bp2BuildDefaultTrueRecursively,
-		"external/libcxx":                 Bp2BuildDefaultTrueRecursively,
-		"external/libcxxabi":              Bp2BuildDefaultTrueRecursively,
-		"external/scudo":                  Bp2BuildDefaultTrueRecursively,
-		"prebuilts/clang/host/linux-x86":  Bp2BuildDefaultTrueRecursively,
+		"system/libbase":                                     Bp2BuildDefaultTrueRecursively,
+		"system/libprocinfo":                                 Bp2BuildDefaultTrue,
+		"system/libziparchive":                               Bp2BuildDefaultTrueRecursively,
+		"system/logging/liblog":                              Bp2BuildDefaultTrueRecursively,
+		"system/sepolicy/apex":                               Bp2BuildDefaultTrueRecursively,
+		"system/timezone/apex":                               Bp2BuildDefaultTrueRecursively,
+		"system/timezone/output_data":                        Bp2BuildDefaultTrueRecursively,
+		"system/unwinding/libbacktrace":                      Bp2BuildDefaultTrueRecursively,
+		"system/unwinding/libunwindstack":                    Bp2BuildDefaultTrueRecursively,
+		"tools/apksig":                                       Bp2BuildDefaultTrue,
+		"tools/platform-compat/java/android/compat":          Bp2BuildDefaultTrueRecursively,
 	}
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
-		// Things that transitively depend on unconverted libc_* modules.
-		"libbionic_spawn_benchmark", // http://b/186824595, cc_library_static, depends on //external/google-benchmark (http://b/186822740)
-		//                                                                also depends on //system/logging/liblog:liblog (http://b/186822772)
+		"libnativehelper_compat_libc", // Broken compile: implicit declaration of function 'strerror_r' is invalid in C99
 
-		"libc_malloc_debug",           // http://b/186824339, cc_library_static, depends on //system/libbase:libbase (http://b/186823646)
-		"libc_malloc_debug_backtrace", // http://b/186824112, cc_library_static, depends on //external/libcxxabi:libc++demangle (http://b/186823773)
+		"libandroid_runtime_lazy", // depends on unconverted modules: libbinder_headers
+		"libcmd",                  // depends on unconverted modules: libbinder
 
-		"libcutils",         // http://b/186827426, cc_library, depends on //system/core/libprocessgroup:libprocessgroup_headers (http://b/186826841)
-		"libcutils_sockets", // http://b/186826853, cc_library, depends on //system/libbase:libbase (http://b/186826479)
+		"chkcon", "sefcontext_compile", // depends on unconverted modules: libsepol
 
-		"liblinker_debuggerd_stub", // http://b/186824327, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                               also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                               also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_main", // http://b/186825989, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                     also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                     also depends on//system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_malloc", // http://b/186826466, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                       also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                       also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"libc_ndk",          // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
-		"libc_malloc_hooks", // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
+		"libsepol", // TODO(b/207408632): Unsupported case of .l sources in cc library rules
 
-		// http://b/186823769: Needs C++ STL support, includes from unconverted standard libraries in //external/libcxx
-		// c++_static
-		"libbase_ndk", // http://b/186826477, cc_library, no such target '//build/bazel/platforms/os:darwin' when --platforms //build/bazel/platforms:android_x86 is added
-		// libcxx
-		"libBionicBenchmarksUtils", // cc_library_static, fatal error: 'map' file not found, from libcxx
-		"fmtlib",                   // cc_library_static, fatal error: 'cassert' file not found, from libcxx
-		"fmtlib_ndk",               // cc_library_static, fatal error: 'cassert' file not found
-		"liblog",                   // http://b/186822772: cc_library, 'sys/cdefs.h' file not found
-		"libbase",                  // Requires liblog. http://b/186826479, cc_library, fatal error: 'memory' file not found, from libcxx.
-		// Also depends on fmtlib.
+		"gen-kotlin-build-file.py", // module has same name as source
 
-		"libseccomp_policy", // depends on libbase
+		"libbinder",               // TODO(b/188503688): Disabled for some archs,
+		"libactivitymanager_aidl", // TODO(b/207426160): Depends on activity_manager_procstate_aidl, which is an aidl filegroup.
 
-		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
+		"libnativehelper_lazy_mts_jni", "libnativehelper_mts_jni", // depends on unconverted modules: libgmock_ndk
+		"libnativetesthelper_jni", "libgmock_main_ndk", "libgmock_ndk", // depends on unconverted module: libgtest_ndk_c++
 
-		//system/core/libprocessgroup/...
-		"libprocessgroup", // depends on //system/core/libprocessgroup/cgrouprc:libcgrouprc
+		"statslog-framework-java-gen", "statslog.cpp", "statslog.h", "statslog.rs", "statslog_header.rs", // depends on unconverted modules: stats-log-api-gen
 
-		//external/brotli/...
-		"brotli-fuzzer-corpus", // "declared output 'external/brotli/c/fuzz/73231c6592f195ffd41100b8706d1138ff6893b9' was not created by genrule"
+		"stats-log-api-gen", // depends on unconverted modules: libstats_proto_host, libprotobuf-cpp-full
 
-		// Tests. Handle later.
-		"libbionic_tests_headers_posix", // http://b/186024507, cc_library_static, sched.h, time.h not found
-		"libjemalloc5_integrationtest",
-		"libjemalloc5_stresstestlib",
-		"libjemalloc5_unittest",
+		"libstatslog", // depends on unconverted modules: statslog.cpp, statslog.h, ...
+
+		"cmd",                                                        // depends on unconverted module packagemanager_aidl-cpp, of unsupported type aidl_interface
+		"servicedispatcher",                                          // depends on unconverted module android.debug_aidl, of unsupported type aidl_interface
+		"libutilscallstack",                                          // depends on unconverted module libbacktrace
+		"libbacktrace",                                               // depends on unconverted module libunwindstack
+		"libdebuggerd_handler",                                       // depends on unconverted module libdebuggerd_handler_core
+		"libdebuggerd_handler_core", "libdebuggerd_handler_fallback", // depends on unconverted module libdebuggerd
+		"unwind_for_offline", // depends on unconverted module libunwindstack_utils
+		"libdebuggerd",       // depends on unconverted modules libdexfile_support, libunwindstack, gwp_asan_crash_handler, libtombstone_proto, libprotobuf-cpp-lite
+		"libdexfile_static",  // depends on libartpalette, libartbase, libdexfile, which are of unsupported type: art_cc_library.
+
+		"host_bionic_linker_asm",    // depends on extract_linker, a go binary.
+		"host_bionic_linker_script", // depends on extract_linker, a go binary.
+		"static_crasher",            // depends on unconverted modules: libdebuggerd_handler
+
+		"pbtombstone", "crash_dump", // depends on libdebuggerd, libunwindstack
+
+		"libbase_ndk", // http://b/186826477, fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
+
+		"libprotobuf-python",               // contains .proto sources
+		"libprotobuf-internal-protos",      // b/210751803, we don't handle path property for filegroups
+		"libprotobuf-internal-python-srcs", // b/210751803, we don't handle path property for filegroups
+		"libprotobuf-java-full",            // b/210751803, we don't handle path property for filegroups
+		"libprotobuf-java-util-full",       // b/210751803, we don't handle path property for filegroups
+		"conscrypt",                        // b/210751803, we don't handle path property for filegroups
+
+		"conv_linker_config", // depends on linker_config_proto, a python lib with proto sources
+
+		"brotli-fuzzer-corpus", // b/202015218: outputs are in location incompatible with bazel genrule handling.
+
+		// b/203369847: multiple genrules in the same package creating the same file
+		// //development/sdk/...
+		"platform_tools_properties",
+		"build_tools_source_properties",
 
 		// APEX support
-		"com.android.runtime", // http://b/194746715, apex, depends on 'libc_malloc_debug' and 'libc_malloc_hooks'
+		"com.android.runtime", // depends on unconverted modules: bionic-linker-config, linkerconfig
+
+		"libgtest_ndk_c++",      // b/201816222: Requires sdk_version support.
+		"libgtest_main_ndk_c++", // b/201816222: Requires sdk_version support.
+
+		"abb",                     // depends on unconverted modules: libcmd, libbinder
+		"adb",                     // depends on unconverted modules: AdbWinApi, libadb_host, libandroidfw, libapp_processes_protos_full, libfastdeploy_host, libopenscreen-discovery, libopenscreen-platform-impl, libusb, bin2c_fastdeployagent, AdbWinUsbApi
+		"linker",                  // depends on unconverted modules: libdebuggerd_handler_fallback
+		"linker_reloc_bench_main", // depends on unconverted modules: liblinker_reloc_bench_*
+		"versioner",               // depends on unconverted modules: libclang_cxx_host, libLLVM_host, of unsupported type llvm_host_prebuilt_library_shared
+
+		"linkerconfig", // http://b/202876379 has arch-variant static_executable
+		"mdnsd",        // http://b/202876379 has arch-variant static_executable
+
+		"acvp_modulewrapper", // disabled for android x86/x86_64
+		"CarHTMLViewer",      // depends on unconverted modules android.car-stubs, car-ui-lib
+
+		"libdexfile",  // depends on unconverted modules: dexfile_operator_srcs, libartbase, libartpalette,
+		"libdexfiled", // depends on unconverted modules: dexfile_operator_srcs, libartbased, libartpalette
 	}
 
 	// Per-module denylist of cc_library modules to only generate the static
 	// variant if their shared variant isn't ready or buildable by Bazel.
 	bp2buildCcLibraryStaticOnlyList = []string{
-		"libstdc++",    // http://b/186822597, cc_library, ld.lld: error: undefined symbol: __errno
 		"libjemalloc5", // http://b/188503688, cc_library, `target: { android: { enabled: false } }` for android targets.
 	}
 
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
 	mixedBuildsDisabledList = []string{
-		"libbrotli",           // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
-		"libc++fs",            // http://b/198403271, Missing symbols/members in the global namespace when referenced from headers in //external/libcxx/includes
-		"libc++_experimental", // http://b/198403271, Missing symbols/members in the global namespace when referenced from headers in //external/libcxx/includes
-		"libc++_static",       // http://b/198403271, Missing symbols/members in the global namespace when referenced from headers in //external/libcxx/includes
-		"libc++abi",           // http://b/195970501, cc_library_static, duplicate symbols because it propagates libc objects.
-		"libc++demangle",      // http://b/195970501, cc_library_static, duplicate symbols because it propagates libc objects.
+		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+
+		"libbrotli",               // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
+		"minijail_constants_json", // http://b/200899432, bazel-built cc_genrule does not work in mixed build when it is a dependency of another soong module.
+
+		"cap_names.h",                                  // TODO(b/204913827) runfiles need to be handled in mixed builds
+		"libcap",                                       // TODO(b/204913827) runfiles need to be handled in mixed builds
+		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
+
+		// Depends on libprotobuf-cpp-*
+		"libadb_pairing_connection",
+		"libadb_pairing_connection_static",
+		"libadb_pairing_server", "libadb_pairing_server_static",
 	}
 
 	// Used for quicker lookups
@@ -279,8 +487,8 @@
 	}
 }
 
-func GenerateCcLibraryStaticOnly(ctx BazelConversionPathContext) bool {
-	return bp2buildCcLibraryStaticOnly[ctx.Module().Name()]
+func GenerateCcLibraryStaticOnly(moduleName string) bool {
+	return bp2buildCcLibraryStaticOnly[moduleName]
 }
 
 func ShouldKeepExistingBuildFileForDir(dir string) bool {
@@ -302,14 +510,19 @@
 
 // MixedBuildsEnabled checks that a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
-func (b *BazelModuleBase) MixedBuildsEnabled(ctx BazelConversionPathContext) bool {
+func (b *BazelModuleBase) MixedBuildsEnabled(ctx ModuleContext) bool {
+	if ctx.Os() == Windows {
+		// Windows toolchains are not currently supported.
+		return false
+	}
 	if !ctx.Config().BazelContext.BazelEnabled() {
 		return false
 	}
-	if len(b.GetBazelLabel(ctx, ctx.Module())) == 0 {
+	if !convertedToBazel(ctx, ctx.Module()) {
 		return false
 	}
-	if GenerateCcLibraryStaticOnly(ctx) {
+
+	if GenerateCcLibraryStaticOnly(ctx.Module().Name()) {
 		// Don't use partially-converted cc_library targets in mixed builds,
 		// since mixed builds would generally rely on both static and shared
 		// variants of a cc_library.
@@ -318,20 +531,30 @@
 	return !mixedBuildsDisabled[ctx.Module().Name()]
 }
 
-// ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
-func (b *BazelModuleBase) ConvertWithBp2build(ctx BazelConversionPathContext) bool {
-	if bp2buildModuleDoNotConvert[ctx.Module().Name()] {
+// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
+func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
+	b, ok := module.(Bazelable)
+	if !ok {
+		return false
+	}
+	return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
+}
+
+// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
+func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
+	return b.shouldConvertWithBp2build(ctx, ctx.Module())
+}
+
+func (b *BazelModuleBase) shouldConvertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool {
+	if bp2buildModuleDoNotConvert[module.Name()] {
 		return false
 	}
 
-	// Ensure that the module type of this module has a bp2build converter. This
-	// prevents mixed builds from using auto-converted modules just by matching
-	// the package dir; it also has to have a bp2build mutator as well.
-	if ctx.Config().bp2buildModuleTypeConfig[ctx.ModuleType()] == false {
+	if !b.bazelProps().Bazel_module.CanConvertToBazel {
 		return false
 	}
 
-	packagePath := ctx.ModuleDir()
+	packagePath := ctx.OtherModuleDir(module)
 	config := ctx.Config().bp2buildPackageConfig
 
 	// This is a tristate value: true, false, or unset.
@@ -403,8 +626,34 @@
 	return string(data[:]), nil
 }
 
-// ConvertedToBazel returns whether this module has been converted to Bazel, whether automatically
-// or manually
-func (b *BazelModuleBase) ConvertedToBazel(ctx BazelConversionPathContext) bool {
-	return b.ConvertWithBp2build(ctx) || b.HasHandcraftedLabel()
+func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
+	ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel()
+}
+
+func convertWithBp2build(ctx TopDownMutatorContext) {
+	bModule, ok := ctx.Module().(Bazelable)
+	if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) {
+		return
+	}
+
+	bModule.ConvertWithBp2build(ctx)
+}
+
+// GetMainClassInManifest scans the manifest file specified in filepath and returns
+// the value of attribute Main-Class in the manifest file if it exists, or returns error.
+// WARNING: this is for bp2build converters of java_* modules only.
+func GetMainClassInManifest(c Config, filepath string) (string, error) {
+	file, err := c.fs.Open(filepath)
+	if err != nil {
+		return "", err
+	}
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.HasPrefix(line, "Main-Class:") {
+			return strings.TrimSpace(line[len("Main-Class:"):]), nil
+		}
+	}
+
+	return "", errors.New("Main-Class is not found.")
 }
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 9c922dd..0052551 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -27,9 +27,9 @@
 	"sync"
 
 	"android/soong/bazel/cquery"
+	"android/soong/shared"
 
 	"android/soong/bazel"
-	"android/soong/shared"
 )
 
 type cqueryRequest interface {
@@ -48,11 +48,17 @@
 	StarlarkFunctionBody() string
 }
 
+// Portion of cquery map key to describe target configuration.
+type configKey struct {
+	archType ArchType
+	osType   OsType
+}
+
 // Map key to describe bazel cquery requests.
 type cqueryKey struct {
 	label       string
 	requestType cqueryRequest
-	archType    ArchType
+	configKey   configKey
 }
 
 // bazelHandler is the interface for a helper object related to deferring to Bazel for
@@ -72,14 +78,14 @@
 	// has been queued to be run later.
 
 	// Returns result files built by building the given bazel target label.
-	GetOutputFiles(label string, archType ArchType) ([]string, bool)
+	GetOutputFiles(label string, cfgKey configKey) ([]string, bool)
 
 	// TODO(cparsons): Other cquery-related methods should be added here.
 	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
-	GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error)
+	GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error)
 
 	// Returns the executable binary resultant from building together the python sources
-	GetPythonBinary(label string, archType ArchType) (string, bool)
+	GetPythonBinary(label string, cfgKey configKey) (string, bool)
 
 	// ** End cquery methods
 
@@ -140,17 +146,17 @@
 	LabelToPythonBinary map[string]string
 }
 
-func (m MockBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
+func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
 	result, ok := m.LabelToOutputFiles[label]
 	return result, ok
 }
 
-func (m MockBazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
+func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
 	result, ok := m.LabelToCcInfo[label]
 	return result, ok, nil
 }
 
-func (m MockBazelContext) GetPythonBinary(label string, archType ArchType) (string, bool) {
+func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
 	result, ok := m.LabelToPythonBinary[label]
 	return result, ok
 }
@@ -171,8 +177,8 @@
 
 var _ BazelContext = MockBazelContext{}
 
-func (bazelCtx *bazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, archType)
+func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
+	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, cfgKey)
 	var ret []string
 	if ok {
 		bazelOutput := strings.TrimSpace(rawString)
@@ -181,8 +187,8 @@
 	return ret, ok
 }
 
-func (bazelCtx *bazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
-	result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, archType)
+func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
+	result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, cfgKey)
 	if !ok {
 		return cquery.CcInfo{}, ok, nil
 	}
@@ -192,8 +198,8 @@
 	return ret, ok, err
 }
 
-func (bazelCtx *bazelContext) GetPythonBinary(label string, archType ArchType) (string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetPythonBinary, archType)
+func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
+	rawString, ok := bazelCtx.cquery(label, cquery.GetPythonBinary, cfgKey)
 	var ret string
 	if ok {
 		bazelOutput := strings.TrimSpace(rawString)
@@ -202,19 +208,15 @@
 	return ret, ok
 }
 
-func (n noopBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
+func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
+func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetPythonBinary(label string, archType ArchType) (string, bool) {
-	panic("unimplemented")
-}
-
-func (n noopBazelContext) GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool) {
+func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
 	panic("unimplemented")
 }
 
@@ -303,8 +305,8 @@
 // returns (result, true). If the request is queued but no results are available,
 // then returns ("", false).
 func (context *bazelContext) cquery(label string, requestType cqueryRequest,
-	archType ArchType) (string, bool) {
-	key := cqueryKey{label, requestType, archType}
+	cfgKey configKey) (string, bool) {
+	key := cqueryKey{label, requestType, cfgKey}
 	if result, ok := context.results[key]; ok {
 		return result, true
 	} else {
@@ -419,7 +421,7 @@
 
 def _config_node_transition_impl(settings, attr):
     return {
-        "//command_line_option:platforms": "@//build/bazel/platforms:android_%s" % attr.arch,
+        "//command_line_option:platforms": "@//build/bazel/platforms:%s_%s" % (attr.os, attr.arch),
     }
 
 _config_node_transition = transition(
@@ -437,7 +439,8 @@
     implementation = _passthrough_rule_impl,
     attrs = {
         "arch" : attr.string(mandatory = True),
-        "deps" : attr.label_list(cfg = _config_node_transition),
+        "os"   : attr.string(mandatory = True),
+        "deps" : attr.label_list(cfg = _config_node_transition, allow_files = True),
         "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
     },
 )
@@ -488,27 +491,35 @@
 	configNodeFormatString := `
 config_node(name = "%s",
     arch = "%s",
+    os = "%s",
     deps = [%s],
 )
 `
 
 	configNodesSection := ""
 
-	labelsByArch := map[string][]string{}
+	labelsByConfig := map[string][]string{}
 	for val, _ := range context.requests {
 		labelString := fmt.Sprintf("\"@%s\"", val.label)
-		archString := getArchString(val)
-		labelsByArch[archString] = append(labelsByArch[archString], labelString)
+		configString := getConfigString(val)
+		labelsByConfig[configString] = append(labelsByConfig[configString], labelString)
 	}
 
-	configNodeLabels := []string{}
-	for archString, labels := range labelsByArch {
-		configNodeLabels = append(configNodeLabels, fmt.Sprintf("\":%s\"", archString))
+	allLabels := []string{}
+	for configString, labels := range labelsByConfig {
+		configTokens := strings.Split(configString, "|")
+		if len(configTokens) != 2 {
+			panic(fmt.Errorf("Unexpected config string format: %s", configString))
+		}
+		archString := configTokens[0]
+		osString := configTokens[1]
+		targetString := fmt.Sprintf("%s_%s", osString, archString)
+		allLabels = append(allLabels, fmt.Sprintf("\":%s\"", targetString))
 		labelsString := strings.Join(labels, ",\n            ")
-		configNodesSection += fmt.Sprintf(configNodeFormatString, archString, archString, labelsString)
+		configNodesSection += fmt.Sprintf(configNodeFormatString, targetString, archString, osString, labelsString)
 	}
 
-	return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(configNodeLabels, ",\n            ")))
+	return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(allLabels, ",\n            ")))
 }
 
 func indent(original string) string {
@@ -573,7 +584,15 @@
 %s
 
 def get_arch(target):
+  # TODO(b/199363072): filegroups and file targets aren't associated with any
+  # specific platform architecture in mixed builds. This is consistent with how
+  # Soong treats filegroups, but it may not be the case with manually-written
+  # filegroup BUILD targets.
   buildoptions = build_options(target)
+  if buildoptions == None:
+    # File targets do not have buildoptions. File targets aren't associated with
+    #  any specific platform architecture in mixed builds, so use the host.
+    return "x86_64|linux"
   platforms = build_options(target)["//command_line_option:platforms"]
   if len(platforms) != 1:
     # An individual configured target should have only one platform architecture.
@@ -584,9 +603,9 @@
   if platform_name == "host":
     return "HOST"
   elif platform_name.startswith("android_"):
-    return platform_name[len("android_"):]
+    return platform_name[len("android_"):] + "|" + platform_name[:len("android_")-1]
   elif platform_name.startswith("linux_"):
-    return platform_name[len("linux_"):]
+    return platform_name[len("linux_"):] + "|" + platform_name[:len("linux_")-1]
   else:
     fail("expected platform name of the form 'android_<arch>' or 'linux_<arch>', but was " + str(platforms))
     return "UNKNOWN"
@@ -671,11 +690,12 @@
 	if err != nil {
 		return err
 	}
+
 	buildrootLabel := "@soong_injection//mixed_builds:buildroot"
 	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
 		context.paths,
 		bazel.CqueryBuildRootRunName,
-		bazelCommand{"cquery", fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
+		bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)},
 		"--output=starlark",
 		"--starlark:file="+absolutePath(cqueryFileRelpath))
 	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"),
@@ -788,7 +808,16 @@
 		cmd := rule.Command()
 
 		// cd into Bazel's execution root, which is the action cwd.
-		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ && ", ctx.Config().BazelContext.OutputBase()))
+		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ &&", ctx.Config().BazelContext.OutputBase()))
+
+		// Remove old outputs, as some actions might not rerun if the outputs are detected.
+		if len(buildStatement.OutputPaths) > 0 {
+			cmd.Text("rm -f")
+			for _, outputPath := range buildStatement.OutputPaths {
+				cmd.Text(outputPath)
+			}
+			cmd.Text("&&")
+		}
 
 		for _, pair := range buildStatement.Env {
 			// Set per-action env variables, if any.
@@ -824,14 +853,23 @@
 }
 
 func getCqueryId(key cqueryKey) string {
-	return key.label + "|" + getArchString(key)
+	return key.label + "|" + getConfigString(key)
 }
 
-func getArchString(key cqueryKey) string {
-	arch := key.archType.Name
-	if len(arch) > 0 {
-		return arch
-	} else {
-		return "x86_64"
+func getConfigString(key cqueryKey) string {
+	arch := key.configKey.archType.Name
+	if len(arch) == 0 || arch == "common" {
+		// Use host platform, which is currently hardcoded to be x86_64.
+		arch = "x86_64"
 	}
+	os := key.configKey.osType.Name
+	if len(os) == 0 || os == "common_os" || os == "linux_glibc" {
+		// Use host OS, which is currently hardcoded to be linux.
+		os = "linux"
+	}
+	return arch + "|" + os
+}
+
+func GetConfigKey(ctx ModuleContext) configKey {
+	return configKey{archType: ctx.Arch().ArchType, osType: ctx.Os()}
 }
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index 557faea..ad5b63b 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -9,11 +9,11 @@
 
 func TestRequestResultsAfterInvokeBazel(t *testing.T) {
 	label := "//foo:bar"
-	arch := Arm64
+	cfg := configKey{Arm64, Android}
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "cquery", expression: "kind(rule, deps(@soong_injection//mixed_builds:buildroot))"}: `//foo:bar|arm64>>out/foo/bar.txt`,
+		bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64|android>>out/foo/bar.txt`,
 	})
-	g, ok := bazelContext.GetOutputFiles(label, arch)
+	g, ok := bazelContext.GetOutputFiles(label, cfg)
 	if ok {
 		t.Errorf("Did not expect cquery results prior to running InvokeBazel(), but got %s", g)
 	}
@@ -21,7 +21,7 @@
 	if err != nil {
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
-	g, ok = bazelContext.GetOutputFiles(label, arch)
+	g, ok = bazelContext.GetOutputFiles(label, cfg)
 	if !ok {
 		t.Errorf("Expected cquery results after running InvokeBazel(), but got none")
 	} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index a4bd2ef..62e6156 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -15,11 +15,12 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
 	"path/filepath"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/pathtools"
 )
@@ -67,40 +68,37 @@
 //   cannot be resolved,the function will panic. This is often due to the dependency not being added
 //   via an AddDependency* method.
 
+// A minimal context interface to check if a module should be converted by bp2build,
+// with functions containing information to match against allowlists and denylists.
+// If a module is deemed to be convertible by bp2build, then it should rely on a
+// BazelConversionPathContext for more functions for dep/path features.
+type BazelConversionContext interface {
+	Config() Config
+
+	Module() Module
+	OtherModuleType(m blueprint.Module) string
+	OtherModuleName(m blueprint.Module) string
+	OtherModuleDir(m blueprint.Module) string
+}
+
 // A subset of the ModuleContext methods which are sufficient to resolve references to paths/deps in
 // order to form a Bazel-compatible label for conversion.
 type BazelConversionPathContext interface {
 	EarlyModulePathContext
+	BazelConversionContext
 
+	ModuleErrorf(fmt string, args ...interface{})
+	PropertyErrorf(property, fmt string, args ...interface{})
 	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
 	ModuleFromName(name string) (blueprint.Module, bool)
-	Module() Module
-	ModuleType() string
-	OtherModuleName(m blueprint.Module) string
-	OtherModuleDir(m blueprint.Module) string
+	AddUnconvertedBp2buildDep(string)
 }
 
 // BazelLabelForModuleDeps expects a list of reference to other modules, ("<module>"
 // or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
 // module within the given ctx.
 func BazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
-	return bazelLabelForModuleDeps(ctx, modules, false)
-}
-
-// BazelLabelForModuleWholeDeps expects a list of references to other modules, ("<module>"
-// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
-// module within the given ctx, where prebuilt dependencies will be appended with _alwayslink so
-// they can be handled as whole static libraries.
-func BazelLabelForModuleWholeDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
-	return bazelLabelForModuleDeps(ctx, modules, true)
-}
-
-// BazelLabelForModuleDepsExcludes expects two lists: modules (containing modules to include in the
-// list), and excludes (modules to exclude from the list). Both of these should contain references
-// to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label list which
-// corresponds to dependencies on the module within the given ctx, and the excluded dependencies.
-func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
-	return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, false)
+	return BazelLabelForModuleDepsWithFn(ctx, modules, BazelModuleLabel)
 }
 
 // BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in
@@ -109,11 +107,15 @@
 // list which corresponds to dependencies on the module within the given ctx, and the excluded
 // dependencies.  Prebuilt dependencies will be appended with _alwayslink so they can be handled as
 // whole static libraries.
-func BazelLabelForModuleWholeDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
-	return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, true)
+func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, BazelModuleLabel)
 }
 
-func bazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string, isWholeLibs bool) bazel.LabelList {
+// BazelLabelForModuleDepsWithFn expects a list of reference to other modules, ("<module>"
+// or ":<module>") and applies moduleToLabelFn to determine and return a Bazel-compatible label
+// which corresponds to dependencies on the module within the given ctx.
+func BazelLabelForModuleDepsWithFn(ctx BazelConversionPathContext, modules []string,
+	moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList {
 	var labels bazel.LabelList
 	// In some cases, a nil string list is different than an explicitly empty list.
 	if len(modules) == 0 && modules != nil {
@@ -126,7 +128,7 @@
 			module = ":" + module
 		}
 		if m, t := SrcIsModuleWithTag(module); m != "" {
-			l := getOtherModuleLabel(ctx, m, t, isWholeLibs)
+			l := getOtherModuleLabel(ctx, m, t, moduleToLabelFn)
 			l.OriginalModuleName = bpText
 			labels.Includes = append(labels.Includes, l)
 		} else {
@@ -136,12 +138,18 @@
 	return labels
 }
 
-func bazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string, isWholeLibs bool) bazel.LabelList {
-	moduleLabels := bazelLabelForModuleDeps(ctx, RemoveListFromList(modules, excludes), isWholeLibs)
+// BazelLabelForModuleDepsExcludesWithFn expects two lists: modules (containing modules to include in the
+// list), and excludes (modules to exclude from the list). Both of these should contain references
+// to other modules, ("<module>" or ":<module>"). It applies moduleToLabelFn to determine and return a
+// Bazel-compatible label list which corresponds to dependencies on the module within the given ctx, and
+// the excluded dependencies.
+func BazelLabelForModuleDepsExcludesWithFn(ctx BazelConversionPathContext, modules, excludes []string,
+	moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList {
+	moduleLabels := BazelLabelForModuleDepsWithFn(ctx, RemoveListFromList(modules, excludes), moduleToLabelFn)
 	if len(excludes) == 0 {
 		return moduleLabels
 	}
-	excludeLabels := bazelLabelForModuleDeps(ctx, excludes, isWholeLibs)
+	excludeLabels := BazelLabelForModuleDepsWithFn(ctx, excludes, moduleToLabelFn)
 	return bazel.LabelList{
 		Includes: moduleLabels.Includes,
 		Excludes: excludeLabels.Includes,
@@ -271,6 +279,16 @@
 	return newPaths
 }
 
+// Converts root-relative Paths to a list of bazel.Label relative to the module in ctx.
+func RootToModuleRelativePaths(ctx BazelConversionPathContext, paths Paths) []bazel.Label {
+	var newPaths []bazel.Label
+	for _, path := range PathsWithModuleSrcSubDir(ctx, paths, "") {
+		s := path.Rel()
+		newPaths = append(newPaths, bazel.Label{Label: s})
+	}
+	return newPaths
+}
+
 // expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source
 // directory and Bazel target labels, excluding those included in the excludes argument (which
 // should already be expanded to resolve references to Soong-modules). Valid elements of paths
@@ -309,7 +327,7 @@
 
 	for _, p := range paths {
 		if m, tag := SrcIsModuleWithTag(p); m != "" {
-			l := getOtherModuleLabel(ctx, m, tag, false)
+			l := getOtherModuleLabel(ctx, m, tag, BazelModuleLabel)
 			if !InList(l.Label, expandedExcludes) {
 				l.OriginalModuleName = fmt.Sprintf(":%s", m)
 				labels.Includes = append(labels.Includes, l)
@@ -320,12 +338,7 @@
 				// e.g. turn "math/*.c" in
 				// external/arm-optimized-routines to external/arm-optimized-routines/math/*.c
 				rootRelativeGlobPath := pathForModuleSrc(ctx, p).String()
-				globbedPaths := GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes)
-				globbedPaths = PathsWithModuleSrcSubDir(ctx, globbedPaths, "")
-				for _, path := range globbedPaths {
-					s := path.Rel()
-					expandedPaths = append(expandedPaths, bazel.Label{Label: s})
-				}
+				expandedPaths = RootToModuleRelativePaths(ctx, GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes))
 			} else {
 				if !InList(p, expandedExcludes) {
 					expandedPaths = append(expandedPaths, bazel.Label{Label: p})
@@ -340,18 +353,20 @@
 // getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
 // module. The label will be relative to the current directory if appropriate. The dependency must
 // already be resolved by either deps mutator or path deps mutator.
-func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string, isWholeLibs bool) bazel.Label {
+func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string,
+	labelFromModule func(BazelConversionPathContext, blueprint.Module) string) bazel.Label {
 	m, _ := ctx.ModuleFromName(dep)
 	if m == nil {
 		panic(fmt.Errorf("No module named %q found, but was a direct dep of %q", dep, ctx.Module().Name()))
 	}
-	otherLabel := bazelModuleLabel(ctx, m, tag)
-	label := bazelModuleLabel(ctx, ctx.Module(), "")
-	if isWholeLibs {
-		if m, ok := m.(Module); ok && IsModulePrebuilt(m) {
-			otherLabel += "_alwayslink"
-		}
+	if !convertedToBazel(ctx, m) {
+		ctx.AddUnconvertedBp2buildDep(dep)
 	}
+	label := BazelModuleLabel(ctx, ctx.Module())
+	otherLabel := labelFromModule(ctx, m)
+
+	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
+
 	if samePackage(label, otherLabel) {
 		otherLabel = bazelShortLabel(otherLabel)
 	}
@@ -361,23 +376,28 @@
 	}
 }
 
-func bazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module, tag string) string {
+func BazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
 	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
-	b, ok := module.(Bazelable)
-	// TODO(b/181155349): perhaps return an error here if the module can't be/isn't being converted
-	if !ok || !b.ConvertedToBazel(ctx) {
+	if !convertedToBazel(ctx, module) {
 		return bp2buildModuleLabel(ctx, module)
 	}
+	b, _ := module.(Bazelable)
 	return b.GetBazelLabel(ctx, module)
 }
 
 func bazelShortLabel(label string) string {
 	i := strings.Index(label, ":")
+	if i == -1 {
+		panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
+	}
 	return label[i:]
 }
 
 func bazelPackage(label string) string {
 	i := strings.Index(label, ":")
+	if i == -1 {
+		panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
+	}
 	return label[0:i]
 }
 
@@ -385,7 +405,7 @@
 	return bazelPackage(label1) == bazelPackage(label2)
 }
 
-func bp2buildModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
+func bp2buildModuleLabel(ctx BazelConversionContext, module blueprint.Module) string {
 	moduleName := ctx.OtherModuleName(module)
 	moduleDir := ctx.OtherModuleDir(module)
 	return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
@@ -396,9 +416,19 @@
 	OutputPath
 }
 
+// ensure BazelOutPath implements Path
 var _ Path = BazelOutPath{}
+
+// ensure BazelOutPath implements genPathProvider
+var _ genPathProvider = BazelOutPath{}
+
+// ensure BazelOutPath implements objPathProvider
 var _ objPathProvider = BazelOutPath{}
 
+func (p BazelOutPath) genPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleGenPath {
+	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
+}
+
 func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
diff --git a/android/config.go b/android/config.go
index e0fc266..5c0e5ae 100644
--- a/android/config.go
+++ b/android/config.go
@@ -79,10 +79,6 @@
 	return c.runGoTests
 }
 
-func (c Config) UseValidationsForGoTests() bool {
-	return c.useValidationsForGoTests
-}
-
 func (c Config) DebugCompilation() bool {
 	return false // Never compile Go code in the main build for debugging
 }
@@ -142,8 +138,7 @@
 	soongOutDir    string
 	moduleListFile string // the path to the file which lists blueprint files to parse.
 
-	runGoTests               bool
-	useValidationsForGoTests bool
+	runGoTests bool
 
 	env       map[string]string
 	envLock   sync.Mutex
@@ -157,13 +152,12 @@
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
 
-	stopBefore bootstrap.StopBefore
-
 	fs         pathtools.FileSystem
 	mockBpList string
 
-	bp2buildPackageConfig    Bp2BuildConfig
-	bp2buildModuleTypeConfig map[string]bool
+	runningAsBp2Build              bool
+	bp2buildPackageConfig          Bp2BuildConfig
+	Bp2buildSoongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions
 
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
@@ -329,7 +323,7 @@
 			DeviceName:                        stringPtr("test_device"),
 			Platform_sdk_version:              intPtr(30),
 			Platform_sdk_codename:             stringPtr("S"),
-			Platform_version_active_codenames: []string{"S"},
+			Platform_version_active_codenames: []string{"S", "Tiramisu"},
 			DeviceSystemSdkVersions:           []string{"14", "15"},
 			Platform_systemsdk_versions:       []string{"29", "30"},
 			AAPTConfig:                        []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"},
@@ -340,10 +334,8 @@
 			ShippingApiLevel:                  stringPtr("30"),
 		},
 
-		outDir: buildDir,
-		// soongOutDir is inconsistent with production (it should be buildDir + "/soong")
-		// but a lot of tests assume this :(
-		soongOutDir:  buildDir,
+		outDir:       buildDir,
+		soongOutDir:  filepath.Join(buildDir, "soong"),
 		captureBuild: true,
 		env:          envCopy,
 
@@ -360,7 +352,7 @@
 
 	config.mockFileSystem(bp, fs)
 
-	config.bp2buildModuleTypeConfig = map[string]bool{}
+	determineBuildOS(config)
 
 	return Config{config}
 }
@@ -368,8 +360,6 @@
 func modifyTestConfigToSupportArchMutator(testConfig Config) {
 	config := testConfig.config
 
-	determineBuildOS(config)
-
 	config.Targets = map[OsType][]Target{
 		Android: []Target{
 			{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
@@ -420,8 +410,8 @@
 // bootstrap run. Only per-run data is reset. Data which needs to persist across
 // multiple runs in the same program execution is carried over (such as Bazel
 // context or environment deps).
-func ConfigForAdditionalRun(cmdlineArgs bootstrap.Args, c Config) (Config, error) {
-	newConfig, err := NewConfig(cmdlineArgs, c.soongOutDir, c.env)
+func ConfigForAdditionalRun(c Config) (Config, error) {
+	newConfig, err := NewConfig(c.moduleListFile, c.runGoTests, c.outDir, c.soongOutDir, c.env)
 	if err != nil {
 		return Config{}, err
 	}
@@ -432,20 +422,19 @@
 
 // NewConfig creates a new Config object. The srcDir argument specifies the path
 // to the root source directory. It also loads the config file, if found.
-func NewConfig(cmdlineArgs bootstrap.Args, soongOutDir string, availableEnv map[string]string) (Config, error) {
+func NewConfig(moduleListFile string, runGoTests bool, outDir, soongOutDir string, availableEnv map[string]string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
 		ProductVariablesFileName: filepath.Join(soongOutDir, productVariablesFileName),
 
 		env: availableEnv,
 
-		outDir:                   cmdlineArgs.OutDir,
-		soongOutDir:              soongOutDir,
-		runGoTests:               cmdlineArgs.RunGoTests,
-		useValidationsForGoTests: cmdlineArgs.UseValidations,
-		multilibConflicts:        make(map[ArchType]bool),
+		outDir:            outDir,
+		soongOutDir:       soongOutDir,
+		runGoTests:        runGoTests,
+		multilibConflicts: make(map[ArchType]bool),
 
-		moduleListFile: cmdlineArgs.ModuleListFile,
+		moduleListFile: moduleListFile,
 		fs:             pathtools.NewOsFs(absSrcDir),
 	}
 
@@ -530,7 +519,6 @@
 
 	config.BazelContext, err = NewBazelContext(config)
 	config.bp2buildPackageConfig = bp2buildDefaultConfig
-	config.bp2buildModuleTypeConfig = make(map[string]bool)
 
 	return Config{config}, err
 }
@@ -565,41 +553,37 @@
 	c.mockBpList = blueprint.MockModuleListFile
 }
 
-func (c *config) StopBefore() bootstrap.StopBefore {
-	return c.stopBefore
-}
-
-// SetStopBefore configures soong_build to exit earlier at a specific point.
-func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) {
-	c.stopBefore = stopBefore
-}
-
 func (c *config) SetAllowMissingDependencies() {
 	c.productVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
 }
 
-var _ bootstrap.ConfigStopBefore = (*config)(nil)
-
 // BlueprintToolLocation returns the directory containing build system tools
 // from Blueprint, like soong_zip and merge_zips.
 func (c *config) HostToolDir() string {
-	return filepath.Join(c.soongOutDir, "host", c.PrebuiltOS(), "bin")
+	if c.KatiEnabled() {
+		return filepath.Join(c.outDir, "host", c.PrebuiltOS(), "bin")
+	} else {
+		return filepath.Join(c.soongOutDir, "host", c.PrebuiltOS(), "bin")
+	}
 }
 
 func (c *config) HostToolPath(ctx PathContext, tool string) Path {
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "bin", tool)
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false, tool)
+	return path
 }
 
-func (c *config) HostJNIToolPath(ctx PathContext, path string) Path {
+func (c *config) HostJNIToolPath(ctx PathContext, lib string) Path {
 	ext := ".so"
 	if runtime.GOOS == "darwin" {
 		ext = ".dylib"
 	}
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "lib64", path+ext)
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "lib64", false, lib+ext)
+	return path
 }
 
-func (c *config) HostJavaToolPath(ctx PathContext, path string) Path {
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "framework", path)
+func (c *config) HostJavaToolPath(ctx PathContext, tool string) Path {
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "framework", false, tool)
+	return path
 }
 
 // PrebuiltOS returns the name of the host OS used in prebuilts directories.
@@ -674,6 +658,10 @@
 	return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
 }
 
+func (c *config) TargetsJava11() bool {
+	return c.IsEnvTrue("EXPERIMENTAL_TARGET_JAVA_VERSION_11")
+}
+
 // 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 {
@@ -768,6 +756,16 @@
 	return levels
 }
 
+func (c *config) LatestPreviewApiLevel() ApiLevel {
+	level := NoneApiLevel
+	for _, l := range c.PreviewApiLevels() {
+		if l.GreaterThan(level) {
+			level = l
+		}
+	}
+	return level
+}
+
 func (c *config) AllSupportedApiLevels() []ApiLevel {
 	var levels []ApiLevel
 	levels = append(levels, c.FinalApiLevels()...)
@@ -859,7 +857,7 @@
 // Returns true if building apps that aren't bundled with the platform.
 // UnbundledBuild() is always true when this is true.
 func (c *config) UnbundledBuildApps() bool {
-	return Bool(c.productVariables.Unbundled_build_apps)
+	return len(c.productVariables.Unbundled_build_apps) > 0
 }
 
 // Returns true if building image that aren't bundled with the platform.
@@ -873,11 +871,6 @@
 	return Bool(c.productVariables.Always_use_prebuilt_sdks)
 }
 
-// Returns true if the boot jars check should be skipped.
-func (c *config) SkipBootJarsCheck() bool {
-	return Bool(c.productVariables.Skip_boot_jars_check)
-}
-
 func (c *config) MinimizeJavaDebugInfo() bool {
 	return Bool(c.productVariables.MinimizeJavaDebugInfo) && !Bool(c.productVariables.Eng)
 }
@@ -890,8 +883,13 @@
 	return Bool(c.productVariables.Eng)
 }
 
+// DevicePrimaryArchType returns the ArchType for the first configured device architecture, or
+// Common if there are no device architectures.
 func (c *config) DevicePrimaryArchType() ArchType {
-	return c.Targets[Android][0].Arch.ArchType
+	if androidTargets := c.Targets[Android]; len(androidTargets) > 0 {
+		return androidTargets[0].Arch.ArchType
+	}
+	return Common
 }
 
 func (c *config) SanitizeHost() []string {
@@ -1192,10 +1190,6 @@
 	return c.config.productVariables.DeviceKernelHeaders
 }
 
-func (c *deviceConfig) SamplingPGO() bool {
-	return Bool(c.config.productVariables.SamplingPGO)
-}
-
 // JavaCoverageEnabledForPath returns whether Java code coverage is enabled for
 // path. Coverage is enabled by default when the product variable
 // JavaCoveragePaths is empty. If JavaCoveragePaths is not empty, coverage is
@@ -1467,6 +1461,10 @@
 	return String(c.config.productVariables.PlatformSepolicyVersion)
 }
 
+func (c *deviceConfig) TotSepolicyVersion() string {
+	return String(c.config.productVariables.TotSepolicyVersion)
+}
+
 func (c *deviceConfig) BoardSepolicyVers() string {
 	if ver := String(c.config.productVariables.BoardSepolicyVers); ver != "" {
 		return ver
@@ -1474,10 +1472,30 @@
 	return c.PlatformSepolicyVersion()
 }
 
+func (c *deviceConfig) BoardPlatVendorPolicy() []string {
+	return c.config.productVariables.BoardPlatVendorPolicy
+}
+
 func (c *deviceConfig) BoardReqdMaskPolicy() []string {
 	return c.config.productVariables.BoardReqdMaskPolicy
 }
 
+func (c *deviceConfig) BoardSystemExtPublicPrebuiltDirs() []string {
+	return c.config.productVariables.BoardSystemExtPublicPrebuiltDirs
+}
+
+func (c *deviceConfig) BoardSystemExtPrivatePrebuiltDirs() []string {
+	return c.config.productVariables.BoardSystemExtPrivatePrebuiltDirs
+}
+
+func (c *deviceConfig) BoardProductPublicPrebuiltDirs() []string {
+	return c.config.productVariables.BoardProductPublicPrebuiltDirs
+}
+
+func (c *deviceConfig) BoardProductPrivatePrebuiltDirs() []string {
+	return c.config.productVariables.BoardProductPrivatePrebuiltDirs
+}
+
 func (c *deviceConfig) DirectedVendorSnapshot() bool {
 	return c.config.productVariables.DirectedVendorSnapshot
 }
@@ -1550,6 +1568,10 @@
 		c.config.productVariables.RecoverySnapshotDirsIncluded)
 }
 
+func (c *deviceConfig) HostFakeSnapshotEnabled() bool {
+	return c.config.productVariables.HostFakeSnapshotEnabled
+}
+
 func (c *deviceConfig) ShippingApiLevel() ApiLevel {
 	if c.config.productVariables.ShippingApiLevel == nil {
 		return NoneApiLevel
@@ -1586,6 +1608,18 @@
 	return c.config.productVariables.SepolicySplit
 }
 
+func (c *deviceConfig) SepolicyFreezeTestExtraDirs() []string {
+	return c.config.productVariables.SepolicyFreezeTestExtraDirs
+}
+
+func (c *deviceConfig) SepolicyFreezeTestExtraPrebuiltDirs() []string {
+	return c.config.productVariables.SepolicyFreezeTestExtraPrebuiltDirs
+}
+
+func (c *deviceConfig) GenerateAidlNdkPlatformBackend() bool {
+	return c.config.productVariables.GenerateAidlNdkPlatformBackend
+}
+
 // The ConfiguredJarList struct provides methods for handling a list of (apex, jar) pairs.
 // Such lists are used in the build system for things like bootclasspath jars or system server jars.
 // The apex part is either an apex name, or a special names "platform" or "system_ext". Jar is a
@@ -1674,6 +1708,20 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// Append a list of (apex, jar) pairs to the list.
+func (l *ConfiguredJarList) AppendList(other *ConfiguredJarList) ConfiguredJarList {
+	apexes := make([]string, 0, l.Len()+other.Len())
+	jars := make([]string, 0, l.Len()+other.Len())
+
+	apexes = append(apexes, l.apexes...)
+	jars = append(jars, l.jars...)
+
+	apexes = append(apexes, other.apexes...)
+	jars = append(jars, other.jars...)
+
+	return ConfiguredJarList{apexes, jars}
+}
+
 // RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
 func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
diff --git a/android/deapexer.go b/android/deapexer.go
index de3f635..265f531 100644
--- a/android/deapexer.go
+++ b/android/deapexer.go
@@ -69,11 +69,18 @@
 
 // The information exported by the `deapexer` module, access it using `DeapxerInfoProvider`.
 type DeapexerInfo struct {
+	apexModuleName string
+
 	// map from the name of an exported file from a prebuilt_apex to the path to that file. The
 	// exported file name is the apex relative path, e.g. javalib/core-libart.jar.
 	//
 	// See Prebuilt.ApexInfoMutator for more information.
-	exports map[string]Path
+	exports map[string]WritablePath
+}
+
+// ApexModuleName returns the name of the APEX module that provided the info.
+func (i DeapexerInfo) ApexModuleName() string {
+	return i.apexModuleName
 }
 
 // PrebuiltExportPath provides the path, or nil if not available, of a file exported from the
@@ -82,7 +89,7 @@
 // The exported file is identified by the apex relative path, e.g. "javalib/core-libart.jar".
 //
 // See apex/deapexer.go for more information.
-func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) Path {
+func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) WritablePath {
 	path := i.exports[apexRelativePath]
 	return path
 }
@@ -95,9 +102,10 @@
 // for use with a prebuilt_apex module.
 //
 // See apex/deapexer.go for more information.
-func NewDeapexerInfo(exports map[string]Path) DeapexerInfo {
+func NewDeapexerInfo(apexModuleName string, exports map[string]WritablePath) DeapexerInfo {
 	return DeapexerInfo{
-		exports: exports,
+		apexModuleName: apexModuleName,
+		exports:        exports,
 	}
 }
 
@@ -133,3 +141,24 @@
 	// Method that differentiates this interface from others.
 	RequiresFilesFromPrebuiltApex()
 }
+
+// FindDeapexerProviderForModule searches through the direct dependencies of the current context
+// module for a DeapexerTag dependency and returns its DeapexerInfo. If a single nonambiguous
+// deapexer module isn't found then errors are reported with ctx.ModuleErrorf and nil is returned.
+func FindDeapexerProviderForModule(ctx ModuleContext) *DeapexerInfo {
+	var di *DeapexerInfo
+	ctx.VisitDirectDepsWithTag(DeapexerTag, func(m Module) {
+		p := ctx.OtherModuleProvider(m, DeapexerProvider).(DeapexerInfo)
+		if di != nil {
+			ctx.ModuleErrorf("Multiple installable prebuilt APEXes provide ambiguous deapexers: %s and %s",
+				di.ApexModuleName(), p.ApexModuleName())
+		}
+		di = &p
+	})
+	if di != nil {
+		return di
+	}
+	ai := ctx.Provider(ApexInfoProvider).(ApexInfo)
+	ctx.ModuleErrorf("No prebuilt APEX provides a deapexer module for APEX variant %s", ai.ApexVariationName)
+	return nil
+}
diff --git a/android/defaults.go b/android/defaults.go
index be80cf1..5677638 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -89,10 +89,11 @@
 var _ Defaultable = (*DefaultableModuleBase)(nil)
 
 func InitDefaultableModule(module DefaultableModule) {
-	if module.(Module).base().module == nil {
+	if module.base().module == nil {
 		panic("InitAndroidModule must be called before InitDefaultableModule")
 	}
-	module.setProperties(module.(Module).GetProperties(), module.(Module).base().variableProperties)
+
+	module.setProperties(module.GetProperties(), module.base().variableProperties)
 
 	module.AddProperties(module.defaults())
 
@@ -118,6 +119,11 @@
 
 type DefaultsModuleBase struct {
 	DefaultableModuleBase
+
+	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
+	// target. This is primarily useful for modules that were architecture specific and instead are
+	// handled in Bazel as a select().
+	BazelModuleBase
 }
 
 // The common pattern for defaults modules is to register separate instances of
@@ -160,6 +166,7 @@
 type DefaultsModule interface {
 	Module
 	Defaults
+	Bazelable
 }
 
 func (d *DefaultsModuleBase) properties() []interface{} {
@@ -170,8 +177,11 @@
 	return d.defaultableVariableProperties
 }
 
-func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {
-}
+func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+// ConvertWithBp2build to fulfill Bazelable interface; however, at this time defaults module are
+// *NOT* converted with bp2build
+func (defaultable *DefaultsModuleBase) ConvertWithBp2build(ctx TopDownMutatorContext) {}
 
 func InitDefaultsModule(module DefaultsModule) {
 	commonProperties := &commonProperties{}
@@ -182,6 +192,8 @@
 		&ApexProperties{},
 		&distProperties{})
 
+	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
+	InitBazelModule(module)
 	initAndroidModuleBase(module)
 	initProductVariableModule(module)
 	initArchModule(module)
@@ -208,15 +220,64 @@
 	// The applicable licenses property for defaults is 'licenses'.
 	setPrimaryLicensesProperty(module, "licenses", &commonProperties.Licenses)
 
-	base.module = module
 }
 
 var _ Defaults = (*DefaultsModuleBase)(nil)
 
+// applyNamespacedVariableDefaults only runs in bp2build mode for
+// defaultable/defaults modules. Its purpose is to merge namespaced product
+// variable props from defaults deps, even if those defaults are custom module
+// types created from soong_config_module_type, e.g. one that's wrapping a
+// cc_defaults or java_defaults.
+func applyNamespacedVariableDefaults(defaultDep Defaults, ctx TopDownMutatorContext) {
+	var dep, b Bazelable
+
+	dep, ok := defaultDep.(Bazelable)
+	if !ok {
+		if depMod, ok := defaultDep.(Module); ok {
+			// Track that this dependency hasn't been converted to bp2build yet.
+			ctx.AddUnconvertedBp2buildDep(depMod.Name())
+			return
+		} else {
+			panic("Expected default dep to be a Module.")
+		}
+	}
+
+	b, ok = ctx.Module().(Bazelable)
+	if !ok {
+		return
+	}
+
+	// namespacedVariableProps is a map from namespaces (e.g. acme, android,
+	// vendor_foo) to a slice of soong_config_variable struct pointers,
+	// containing properties for that particular module.
+	src := dep.namespacedVariableProps()
+	dst := b.namespacedVariableProps()
+	if dst == nil {
+		dst = make(namespacedVariableProperties)
+	}
+
+	// Propagate all soong_config_variable structs from the dep. We'll merge the
+	// actual property values later in variable.go.
+	for namespace := range src {
+		if dst[namespace] == nil {
+			dst[namespace] = []interface{}{}
+		}
+		for _, i := range src[namespace] {
+			dst[namespace] = append(dst[namespace], i)
+		}
+	}
+
+	b.setNamespacedVariableProps(dst)
+}
+
 func (defaultable *DefaultableModuleBase) applyDefaults(ctx TopDownMutatorContext,
 	defaultsList []Defaults) {
 
 	for _, defaults := range defaultsList {
+		if ctx.Config().runningAsBp2Build {
+			applyNamespacedVariableDefaults(defaults, ctx)
+		}
 		for _, prop := range defaultable.defaultableProperties {
 			if prop == defaultable.defaultableVariableProperties {
 				defaultable.applyDefaultVariableProperties(ctx, defaults, prop)
diff --git a/android/defs.go b/android/defs.go
index b3ff376..362b382 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -52,10 +52,10 @@
 	// A copy rule.
 	Cp = pctx.AndroidStaticRule("Cp",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out",
+			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out$extraCmds",
 			Description: "cp $out",
 		},
-		"cpFlags")
+		"cpFlags", "extraCmds")
 
 	// A copy rule that only updates the output if it changed.
 	CpIfChanged = pctx.AndroidStaticRule("CpIfChanged",
@@ -68,10 +68,10 @@
 
 	CpExecutable = pctx.AndroidStaticRule("CpExecutable",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out && chmod +x $out",
+			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out && chmod +x $out$extraCmds",
 			Description: "cp $out",
 		},
-		"cpFlags")
+		"cpFlags", "extraCmds")
 
 	// A timestamp touch rule.
 	Touch = pctx.AndroidStaticRule("Touch",
@@ -188,6 +188,15 @@
 	buildWriteFileRule(ctx, outputFile, content)
 }
 
+func CatFileRule(ctx BuilderContext, paths Paths, outputFile WritablePath) {
+	ctx.Build(pctx, BuildParams{
+		Rule:        Cat,
+		Inputs:      paths,
+		Output:      outputFile,
+		Description: "combine files to " + outputFile.Base(),
+	})
+}
+
 // shellUnescape reverses proptools.ShellEscape
 func shellUnescape(s string) string {
 	// Remove leading and trailing quotes if present
diff --git a/android/filegroup.go b/android/filegroup.go
index 54d01d3..c932ffa 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -22,7 +22,6 @@
 
 func init() {
 	RegisterModuleType("filegroup", FileGroupFactory)
-	RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
 }
 
 var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
@@ -34,14 +33,31 @@
 	Srcs bazel.LabelListAttribute
 }
 
-func FilegroupBp2Build(ctx TopDownMutatorContext) {
-	fg, ok := ctx.Module().(*fileGroup)
-	if !ok || !fg.ConvertWithBp2build(ctx) {
-		return
-	}
-
+// ConvertWithBp2build performs bp2build conversion of filegroup
+func (fg *fileGroup) ConvertWithBp2build(ctx TopDownMutatorContext) {
 	srcs := bazel.MakeLabelListAttribute(
 		BazelLabelForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs))
+
+	// For Bazel compatibility, don't generate the filegroup if there is only 1
+	// source file, and that the source file is named the same as the module
+	// itself. In Bazel, eponymous filegroups like this would be an error.
+	//
+	// Instead, dependents on this single-file filegroup can just depend
+	// on the file target, instead of rule target, directly.
+	//
+	// You may ask: what if a filegroup has multiple files, and one of them
+	// shares the name? The answer: we haven't seen that in the wild, and
+	// should lock Soong itself down to prevent the behavior. For now,
+	// we raise an error if bp2build sees this problem.
+	for _, f := range srcs.Value.Includes {
+		if f.Label == fg.Name() {
+			if len(srcs.Value.Includes) > 1 {
+				ctx.ModuleErrorf("filegroup '%s' cannot contain a file with the same name", fg.Name())
+			}
+			return
+		}
+	}
+
 	attrs := &bazelFilegroupAttributes{
 		Srcs: srcs,
 	}
@@ -51,7 +67,7 @@
 		Bzl_load_location: "//build/bazel/rules:filegroup.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(fg.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, CommonAttributes{Name: fg.Name()}, attrs)
 }
 
 type fileGroupProperties struct {
@@ -91,15 +107,24 @@
 	return module
 }
 
-func (fg *fileGroup) GenerateBazelBuildActions(ctx ModuleContext) bool {
+func (fg *fileGroup) maybeGenerateBazelBuildActions(ctx ModuleContext) {
 	if !fg.MixedBuildsEnabled(ctx) {
-		return false
+		return
+	}
+
+	archVariant := ctx.Arch().ArchType
+	osVariant := ctx.Os()
+	if len(fg.Srcs()) == 1 && fg.Srcs()[0].Base() == fg.Name() {
+		// This will be a regular file target, not filegroup, in Bazel.
+		// See FilegroupBp2Build for more information.
+		archVariant = Common
+		osVariant = CommonOS
 	}
 
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), ctx.Arch().ArchType)
+	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{archVariant, osVariant})
 	if !ok {
-		return false
+		return
 	}
 
 	bazelOuts := make(Paths, 0, len(filePaths))
@@ -109,19 +134,15 @@
 	}
 
 	fg.srcs = bazelOuts
-
-	return true
 }
 
 func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
-	if fg.GenerateBazelBuildActions(ctx) {
-		return
-	}
-
 	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
 	if fg.properties.Path != nil {
 		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
 	}
+
+	fg.maybeGenerateBazelBuildActions(ctx)
 }
 
 func (fg *fileGroup) Srcs() Paths {
diff --git a/android/hooks.go b/android/hooks.go
index 85fc081..9eaa1ac 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -65,10 +65,10 @@
 	return l.bp.ModuleFactories()
 }
 
-func (l *loadHookContext) AppendProperties(props ...interface{}) {
+func (l *loadHookContext) appendPrependHelper(props []interface{},
+	extendFn func([]interface{}, interface{}, proptools.ExtendPropertyFilterFunc) error) {
 	for _, p := range props {
-		err := proptools.AppendMatchingProperties(l.Module().base().customizableProperties,
-			p, nil)
+		err := extendFn(l.Module().base().customizableProperties, p, nil)
 		if err != nil {
 			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
 				l.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
@@ -78,19 +78,12 @@
 		}
 	}
 }
+func (l *loadHookContext) AppendProperties(props ...interface{}) {
+	l.appendPrependHelper(props, proptools.AppendMatchingProperties)
+}
 
 func (l *loadHookContext) PrependProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.PrependMatchingProperties(l.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				l.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
-	}
+	l.appendPrependHelper(props, proptools.PrependMatchingProperties)
 }
 
 func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
diff --git a/android/license_metadata.go b/android/license_metadata.go
new file mode 100644
index 0000000..3bc53d6
--- /dev/null
+++ b/android/license_metadata.go
@@ -0,0 +1,231 @@
+// Copyright 2021 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 android
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	_ = pctx.HostBinToolVariable("licenseMetadataCmd", "build_license_metadata")
+
+	licenseMetadataRule = pctx.AndroidStaticRule("licenseMetadataRule", blueprint.RuleParams{
+		Command:        "${licenseMetadataCmd} -o $out @${out}.rsp",
+		CommandDeps:    []string{"${licenseMetadataCmd}"},
+		Rspfile:        "${out}.rsp",
+		RspfileContent: "${args}",
+	}, "args")
+)
+
+func buildLicenseMetadata(ctx ModuleContext) {
+	base := ctx.Module().base()
+
+	if !base.Enabled() {
+		return
+	}
+
+	if exemptFromRequiredApplicableLicensesProperty(ctx.Module()) {
+		return
+	}
+
+	var allDepMetadataFiles Paths
+	var allDepMetadataArgs []string
+	var allDepOutputFiles Paths
+
+	ctx.VisitDirectDepsBlueprint(func(bpdep blueprint.Module) {
+		dep, _ := bpdep.(Module)
+		if dep == nil {
+			return
+		}
+		if !dep.Enabled() {
+			return
+		}
+
+		if ctx.OtherModuleHasProvider(dep, LicenseMetadataProvider) {
+			info := ctx.OtherModuleProvider(dep, LicenseMetadataProvider).(*LicenseMetadataInfo)
+			allDepMetadataFiles = append(allDepMetadataFiles, info.LicenseMetadataPath)
+
+			depAnnotations := licenseAnnotationsFromTag(ctx.OtherModuleDependencyTag(dep))
+
+			allDepMetadataArgs = append(allDepMetadataArgs, info.LicenseMetadataPath.String()+depAnnotations)
+
+			if depInstallFiles := dep.base().installFiles; len(depInstallFiles) > 0 {
+				allDepOutputFiles = append(allDepOutputFiles, depInstallFiles.Paths()...)
+			} else if depOutputFiles, err := outputFilesForModule(ctx, dep, ""); err == nil {
+				depOutputFiles = PathsIfNonNil(depOutputFiles...)
+				allDepOutputFiles = append(allDepOutputFiles, depOutputFiles...)
+			}
+		}
+	})
+
+	allDepMetadataFiles = SortedUniquePaths(allDepMetadataFiles)
+	sort.Strings(allDepMetadataArgs)
+	allDepOutputFiles = SortedUniquePaths(allDepOutputFiles)
+
+	var orderOnlyDeps Paths
+	var args []string
+
+	if t := ctx.ModuleType(); t != "" {
+		args = append(args,
+			"-mt "+proptools.NinjaAndShellEscape(t))
+	}
+
+	args = append(args,
+		"-r "+proptools.NinjaAndShellEscape(ctx.ModuleDir()),
+		"-mc UNKNOWN")
+
+	if p := base.commonProperties.Effective_package_name; p != nil {
+		args = append(args,
+			"-p "+proptools.NinjaAndShellEscape(*p))
+	}
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_kinds), "-k "))
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_conditions), "-c "))
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_text.Strings()), "-n "))
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(allDepMetadataArgs), "-d "))
+	orderOnlyDeps = append(orderOnlyDeps, allDepMetadataFiles...)
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(allDepOutputFiles.Strings()), "-s "))
+
+	// Install map
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.licenseInstallMap), "-m "))
+
+	// Built files
+	var outputFiles Paths
+	if outputFileProducer, ok := ctx.Module().(OutputFileProducer); ok {
+		outputFiles, _ = outputFileProducer.OutputFiles("")
+		outputFiles = PathsIfNonNil(outputFiles...)
+	}
+
+	if len(outputFiles) > 0 {
+		args = append(args,
+			JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(outputFiles.Strings()), "-t "))
+	} else {
+		args = append(args, fmt.Sprintf("-t //%s:%s", ctx.ModuleDir(), ctx.ModuleName()))
+	}
+
+	// Installed files
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.installFiles.Strings()), "-i "))
+
+	isContainer := isContainerFromFileExtensions(base.installFiles, outputFiles)
+	if isContainer {
+		args = append(args, "--is_container")
+	}
+
+	licenseMetadataFile := PathForModuleOut(ctx, "meta_lic")
+
+	ctx.Build(pctx, BuildParams{
+		Rule:        licenseMetadataRule,
+		Output:      licenseMetadataFile,
+		OrderOnly:   orderOnlyDeps,
+		Description: "license metadata",
+		Args: map[string]string{
+			"args": strings.Join(args, " "),
+		},
+	})
+
+	ctx.SetProvider(LicenseMetadataProvider, &LicenseMetadataInfo{
+		LicenseMetadataPath: licenseMetadataFile,
+	})
+}
+
+func isContainerFromFileExtensions(installPaths InstallPaths, builtPaths Paths) bool {
+	var paths Paths
+	if len(installPaths) > 0 {
+		paths = installPaths.Paths()
+	} else {
+		paths = builtPaths
+	}
+
+	for _, path := range paths {
+		switch path.Ext() {
+		case ".zip", ".tar", ".tgz", ".tar.gz", ".img", ".srcszip", ".apex":
+			return true
+		}
+	}
+
+	return false
+}
+
+// LicenseMetadataProvider is used to propagate license metadata paths between modules.
+var LicenseMetadataProvider = blueprint.NewProvider(&LicenseMetadataInfo{})
+
+// LicenseMetadataInfo stores the license metadata path for a module.
+type LicenseMetadataInfo struct {
+	LicenseMetadataPath Path
+}
+
+// licenseAnnotationsFromTag returns the LicenseAnnotations for a tag (if any) converted into
+// a string, or an empty string if there are none.
+func licenseAnnotationsFromTag(tag blueprint.DependencyTag) string {
+	if annoTag, ok := tag.(LicenseAnnotationsDependencyTag); ok {
+		annos := annoTag.LicenseAnnotations()
+		if len(annos) > 0 {
+			annoStrings := make([]string, len(annos))
+			for i, s := range annos {
+				annoStrings[i] = string(s)
+			}
+			return ":" + strings.Join(annoStrings, ",")
+		}
+	}
+	return ""
+}
+
+// LicenseAnnotationsDependencyTag is implemented by dependency tags in order to provide a
+// list of license dependency annotations.
+type LicenseAnnotationsDependencyTag interface {
+	LicenseAnnotations() []LicenseAnnotation
+}
+
+// LicenseAnnotation is an enum of annotations that can be applied to dependencies for propagating
+// license information.
+type LicenseAnnotation string
+
+const (
+	// LicenseAnnotationSharedDependency should be returned by LicenseAnnotations implementations
+	// of dependency tags when the usage of the dependency is dynamic, for example a shared library
+	// linkage for native modules or as a classpath library for java modules.
+	LicenseAnnotationSharedDependency LicenseAnnotation = "dynamic"
+
+	// LicenseAnnotationToolchain should be returned by LicenseAnnotations implementations of
+	// dependency tags when the dependency is used as a toolchain.
+	//
+	// Dependency tags that need to always return LicenseAnnotationToolchain
+	// can embed LicenseAnnotationToolchainDependencyTag to implement LicenseAnnotations.
+	LicenseAnnotationToolchain LicenseAnnotation = "toolchain"
+)
+
+// LicenseAnnotationToolchainDependencyTag can be embedded in a dependency tag to implement
+// LicenseAnnotations that always returns LicenseAnnotationToolchain.
+type LicenseAnnotationToolchainDependencyTag struct{}
+
+func (LicenseAnnotationToolchainDependencyTag) LicenseAnnotations() []LicenseAnnotation {
+	return []LicenseAnnotation{LicenseAnnotationToolchain}
+}
diff --git a/android/licenses.go b/android/licenses.go
index d54f8f4..e9e271b 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -48,7 +48,7 @@
 
 	// License modules, i.e. modules depended upon via a licensesTag, must be automatically added to
 	// any sdk/module_exports to which their referencing module is a member.
-	_ SdkMemberTypeDependencyTag = licensesTag
+	_ SdkMemberDependencyTag = licensesTag
 )
 
 // Describes the property provided by a module to reference applicable licenses.
@@ -253,7 +253,7 @@
 
 	primaryProperty := module.base().primaryLicensesProperty
 	if primaryProperty == nil {
-		if ctx.Config().IsEnvTrue("ANDROID_REQUIRE_LICENSES") {
+		if !ctx.Config().IsEnvFalse("ANDROID_REQUIRE_LICENSES") {
 			ctx.ModuleErrorf("module type %q must have an applicable licenses property", ctx.OtherModuleType(module))
 		}
 		return nil
@@ -308,3 +308,12 @@
 }
 
 var LicenseInfoProvider = blueprint.NewProvider(LicenseInfo{})
+
+func init() {
+	RegisterMakeVarsProvider(pctx, licensesMakeVarsProvider)
+}
+
+func licensesMakeVarsProvider(ctx MakeVarsContext) {
+	ctx.Strict("BUILD_LICENSE_METADATA",
+		ctx.Config().HostToolPath(ctx, "build_license_metadata").String())
+}
diff --git a/android/makevars.go b/android/makevars.go
index 40c0ccd..ece7091 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -17,6 +17,8 @@
 import (
 	"bytes"
 	"fmt"
+	"path/filepath"
+	"runtime"
 	"sort"
 	"strings"
 
@@ -140,15 +142,19 @@
 
 var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
 
+func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider {
+	return config.Once(singletonMakeVarsProvidersKey, func() interface{} {
+		return &[]makeVarsProvider{}
+	}).(*[]makeVarsProvider)
+}
+
 // registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
 // the list of MakeVarsProviders to run.
 func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
 	// Singletons are registered on the Context and may be different between different Contexts,
 	// for example when running multiple tests.  Store the SingletonMakeVarsProviders in the
 	// Config so they are attached to the Context.
-	singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} {
-		return &[]makeVarsProvider{}
-	}).(*[]makeVarsProvider)
+	singletonMakeVarsProviders := getSingletonMakevarsProviders(config)
 
 	*singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
 		makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
@@ -173,7 +179,9 @@
 	return &makeVarsSingleton{}
 }
 
-type makeVarsSingleton struct{}
+type makeVarsSingleton struct {
+	installsForTesting []byte
+}
 
 type makeVarsProvider struct {
 	pctx PackageContext
@@ -222,6 +230,9 @@
 	lateOutFile := absolutePath(PathForOutput(ctx,
 		"late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
 
+	installsFile := absolutePath(PathForOutput(ctx,
+		"installs"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
+
 	if ctx.Failed() {
 		return
 	}
@@ -229,9 +240,11 @@
 	var vars []makeVarsVariable
 	var dists []dist
 	var phonies []phony
+	var katiInstalls []katiInstall
+	var katiSymlinks []katiInstall
 
 	providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
-	providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
+	providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...)
 
 	for _, provider := range providers {
 		mctx := &makeVarsContext{
@@ -258,6 +271,11 @@
 			phonies = append(phonies, mctx.phonies...)
 			dists = append(dists, mctx.dists...)
 		}
+
+		if m.ExportedToMake() {
+			katiInstalls = append(katiInstalls, m.base().katiInstalls...)
+			katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...)
+		}
 	})
 
 	if ctx.Failed() {
@@ -297,6 +315,12 @@
 		ctx.Errorf(err.Error())
 	}
 
+	installsBytes := s.writeInstalls(katiInstalls, katiSymlinks)
+	if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil {
+		ctx.Errorf(err.Error())
+	}
+
+	s.installsForTesting = installsBytes
 }
 
 func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
@@ -398,6 +422,7 @@
 	fmt.Fprintln(buf)
 
 	for _, dist := range dists {
+		fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " "))
 		fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
 			strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
 	}
@@ -405,6 +430,94 @@
 	return buf.Bytes()
 }
 
+// writeInstalls writes the list of install rules generated by Soong to a makefile.  The rules
+// are exported to Make instead of written directly to the ninja file so that main.mk can add
+// the dependencies from the `required` property that are hard to resolve in Soong.
+func (s *makeVarsSingleton) writeInstalls(installs, symlinks []katiInstall) []byte {
+	buf := &bytes.Buffer{}
+
+	fmt.Fprint(buf, `# Autogenerated file
+
+# Values written by Soong to generate install rules that can be amended by Kati.
+
+
+`)
+
+	preserveSymlinksFlag := "-d"
+	if runtime.GOOS == "darwin" {
+		preserveSymlinksFlag = "-R"
+	}
+
+	for _, install := range installs {
+		// Write a rule for each install request in the form:
+		//  to: from [ deps ] [ | order only deps ]
+		//       cp -f -d $< $@ [ && chmod +x $@ ]
+		fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String())
+		for _, dep := range install.implicitDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		if extraFiles := install.extraFiles; extraFiles != nil {
+			fmt.Fprintf(buf, " %s", extraFiles.zip.String())
+		}
+		if len(install.orderOnlyDeps) > 0 {
+			fmt.Fprintf(buf, " |")
+		}
+		for _, dep := range install.orderOnlyDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		fmt.Fprintln(buf)
+		fmt.Fprintln(buf, "\t@echo \"Install $@\"")
+		fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag)
+		if install.executable {
+			fmt.Fprintf(buf, "\tchmod +x $@\n")
+		}
+		if extraFiles := install.extraFiles; extraFiles != nil {
+			fmt.Fprintf(buf, "\tunzip -qDD -d '%s' '%s'\n", extraFiles.dir.String(), extraFiles.zip.String())
+		}
+		fmt.Fprintln(buf)
+	}
+
+	for _, symlink := range symlinks {
+		fmt.Fprintf(buf, "%s:", symlink.to.String())
+		if symlink.from != nil {
+			// The symlink doesn't need updating when the target is modified, but we sometimes
+			// have a dependency on a symlink to a binary instead of to the binary directly, and
+			// the mtime of the symlink must be updated when the binary is modified, so use a
+			// normal dependency here instead of an order-only dependency.
+			fmt.Fprintf(buf, " %s", symlink.from.String())
+		}
+		for _, dep := range symlink.implicitDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		if len(symlink.orderOnlyDeps) > 0 {
+			fmt.Fprintf(buf, " |")
+		}
+		for _, dep := range symlink.orderOnlyDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		fmt.Fprintln(buf)
+
+		fromStr := ""
+		if symlink.from != nil {
+			rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String())
+			if err != nil {
+				panic(fmt.Errorf("failed to find relative path for symlink from %q to %q: %w",
+					symlink.from.String(), symlink.to.String(), err))
+			}
+			fromStr = rel
+		} else {
+			fromStr = symlink.absFrom
+		}
+
+		fmt.Fprintln(buf, "\t@echo \"Symlink $@\"")
+		fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr)
+		fmt.Fprintln(buf)
+		fmt.Fprintln(buf)
+	}
+
+	return buf.Bytes()
+}
+
 func (c *makeVarsContext) DeviceConfig() DeviceConfig {
 	return DeviceConfig{c.Config().deviceConfig}
 }
diff --git a/android/module.go b/android/module.go
index cc03418..6de4165 100644
--- a/android/module.go
+++ b/android/module.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
 	"os"
 	"path"
@@ -24,6 +23,8 @@
 	"strings"
 	"text/scanner"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -316,6 +317,9 @@
 
 	AddMissingDependencies(missingDeps []string)
 
+	// AddUnconvertedBp2buildDep stores module name of a direct dependency that was not converted via bp2build
+	AddUnconvertedBp2buildDep(dep string)
+
 	Target() Target
 	TargetPrimary() bool
 
@@ -377,6 +381,16 @@
 	// for which IsInstallDepNeeded returns true.
 	InstallFile(installPath InstallPath, name string, srcPath Path, deps ...Path) InstallPath
 
+	// InstallFileWithExtraFilesZip creates a rule to copy srcPath to name in the installPath
+	// directory, and also unzip a zip file containing extra files to install into the same
+	// directory.
+	//
+	// The installed file will be returned by FilesToInstall(), and the PackagingSpec for the
+	// installed file will be returned by PackagingSpecs() on this module or by
+	// TransitivePackagingSpecs() on modules that depend on this module through dependency tags
+	// for which IsInstallDepNeeded returns true.
+	InstallFileWithExtraFilesZip(installPath InstallPath, name string, srcPath Path, extraZip Path, deps ...Path) InstallPath
+
 	// InstallSymlink creates a rule to create a symlink from src srcPath to name in the installPath
 	// directory.
 	//
@@ -415,7 +429,6 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallInVendor() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 
 	RequiredModuleNames() []string
@@ -464,6 +477,14 @@
 	Enabled() bool
 	Target() Target
 	MultiTargets() []Target
+
+	// ImageVariation returns the image variation of this module.
+	//
+	// The returned structure has its Mutator field set to "image" and its Variation field set to the
+	// image variation, e.g. recovery, ramdisk, etc.. The Variation field is "" for host modules and
+	// device modules that have no image variation.
+	ImageVariation() blueprint.Variation
+
 	Owner() string
 	InstallInData() bool
 	InstallInTestcases() bool
@@ -474,7 +495,6 @@
 	InstallInRecovery() bool
 	InstallInRoot() bool
 	InstallInVendor() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 	HideFromMake()
 	IsHideFromMake() bool
@@ -495,6 +515,7 @@
 	IsConvertedByBp2build() bool
 	// Bp2buildTargets returns the target(s) generated for Bazel via bp2build for this module
 	Bp2buildTargets() []bp2buildInfo
+	GetUnconvertedBp2buildDeps() []string
 
 	BuildParamsForTests() []BuildParams
 	RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
@@ -521,62 +542,6 @@
 	TransitivePackagingSpecs() []PackagingSpec
 }
 
-// BazelTargetModule is a lightweight wrapper interface around Module for
-// bp2build conversion purposes.
-//
-// In bp2build's bootstrap.Main execution, Soong runs an alternate pipeline of
-// mutators that creates BazelTargetModules from regular Module objects,
-// performing the mapping from Soong properties to Bazel rule attributes in the
-// process. This process may optionally create additional BazelTargetModules,
-// resulting in a 1:many mapping.
-//
-// bp2build.Codegen is then responsible for visiting all modules in the graph,
-// filtering for BazelTargetModules, and code-generating BUILD targets from
-// them.
-type BazelTargetModule interface {
-	Module
-
-	bazelTargetModuleProperties() *bazel.BazelTargetModuleProperties
-	SetBazelTargetModuleProperties(props bazel.BazelTargetModuleProperties)
-
-	RuleClass() string
-	BzlLoadLocation() string
-}
-
-// InitBazelTargetModule is a wrapper function that decorates BazelTargetModule
-// with property structs containing metadata for bp2build conversion.
-func InitBazelTargetModule(module BazelTargetModule) {
-	module.AddProperties(module.bazelTargetModuleProperties())
-	InitAndroidModule(module)
-}
-
-// BazelTargetModuleBase contains the property structs with metadata for
-// bp2build conversion.
-type BazelTargetModuleBase struct {
-	ModuleBase
-	Properties bazel.BazelTargetModuleProperties
-}
-
-// bazelTargetModuleProperties getter.
-func (btmb *BazelTargetModuleBase) bazelTargetModuleProperties() *bazel.BazelTargetModuleProperties {
-	return &btmb.Properties
-}
-
-// SetBazelTargetModuleProperties setter for BazelTargetModuleProperties
-func (btmb *BazelTargetModuleBase) SetBazelTargetModuleProperties(props bazel.BazelTargetModuleProperties) {
-	btmb.Properties = props
-}
-
-// RuleClass returns the rule class for this Bazel target
-func (b *BazelTargetModuleBase) RuleClass() string {
-	return b.bazelTargetModuleProperties().Rule_class
-}
-
-// BzlLoadLocation returns the rule class for this Bazel target
-func (b *BazelTargetModuleBase) BzlLoadLocation() string {
-	return b.bazelTargetModuleProperties().Bzl_load_location
-}
-
 // Qualified id for a module
 type qualifiedModuleName struct {
 	// The package (i.e. directory) in which the module is defined, without trailing /
@@ -888,6 +853,20 @@
 	// supported as Soong handles some things within a single target that we may choose to split into
 	// multiple targets, e.g. renderscript, protos, yacc within a cc module.
 	Bp2buildInfo []bp2buildInfo `blueprint:"mutated"`
+
+	// UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to
+	// Bazel
+	UnconvertedBp2buildDeps []string `blueprint:"mutated"`
+}
+
+// CommonAttributes represents the common Bazel attributes from which properties
+// in `commonProperties` are translated/mapped; such properties are annotated in
+// a list their corresponding attribute. It is embedded within `bp2buildInfo`.
+type CommonAttributes struct {
+	// Soong nameProperties -> Bazel name
+	Name string
+	// Data mapped from: Required
+	Data bazel.LabelListAttribute
 }
 
 type distProperties struct {
@@ -987,12 +966,12 @@
 	DeviceSupported = deviceSupported | deviceDefault
 
 	// By default, _only_ device variant is built. Device variant can be disabled with `device_supported: false`
-    // Host and HostCross are disabled by default and can be enabled with `host_supported: true`
+	// Host and HostCross are disabled by default and can be enabled with `host_supported: true`
 	HostAndDeviceSupported = hostSupported | hostCrossSupported | deviceSupported | deviceDefault
 
 	// Host, HostCross, and Device are built by default.
-    // Building Device can be disabled with `device_supported: false`
-    // Building Host and HostCross can be disabled with `host_supported: false`
+	// Building Device can be disabled with `device_supported: false`
+	// Building Host and HostCross can be disabled with `host_supported: false`
 	HostAndDeviceDefault = hostSupported | hostCrossSupported | hostDefault |
 		deviceSupported | deviceDefault
 
@@ -1110,6 +1089,34 @@
 	m.base().commonProperties.CreateCommonOSVariant = true
 }
 
+func (attrs *CommonAttributes) fillCommonBp2BuildModuleAttrs(ctx *topDownMutatorContext) {
+	// Assert passed-in attributes include Name
+	name := attrs.Name
+	if len(name) == 0 {
+		ctx.ModuleErrorf("CommonAttributes in fillCommonBp2BuildModuleAttrs expects a `.Name`!")
+	}
+
+	mod := ctx.Module().base()
+	props := &mod.commonProperties
+
+	depsToLabelList := func(deps []string) bazel.LabelListAttribute {
+		return bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, deps))
+	}
+
+	data := &attrs.Data
+
+	required := depsToLabelList(props.Required)
+	archVariantProps := mod.GetArchVariantProperties(ctx, &commonProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*commonProperties); ok {
+				required.SetSelectValue(axis, config, depsToLabelList(archProps.Required).Value)
+			}
+		}
+	}
+	data.Append(required)
+}
+
 // A ModuleBase object contains the properties that are common to all Android
 // modules.  It should be included as an anonymous field in every module
 // struct definition.  InitAndroidModule should then be called from the module's
@@ -1138,7 +1145,7 @@
 //         }
 //     }
 //
-//     func NewMyModule() android.Module) {
+//     func NewMyModule() android.Module {
 //         m := &myModule{}
 //         m.AddProperties(&m.properties)
 //         android.InitAndroidModule(m)
@@ -1193,7 +1200,10 @@
 	packagingSpecs       []PackagingSpec
 	packagingSpecsDepSet *packagingSpecsDepSet
 	noticeFiles          Paths
-	phonies              map[string]Paths
+	// katiInstalls tracks the install rules that were created by Soong but are being exported
+	// to Make to convert to ninja rules so that Make can add additional dependencies.
+	katiInstalls katiInstalls
+	katiSymlinks katiInstalls
 
 	// The files to copy to the dist as explicitly specified in the .bp file.
 	distFiles TaggedDistFiles
@@ -1215,19 +1225,23 @@
 
 	initRcPaths         Paths
 	vintfFragmentsPaths Paths
+
+	// set of dependency module:location mappings used to populate the license metadata for
+	// apex containers.
+	licenseInstallMap []string
 }
 
 // A struct containing all relevant information about a Bazel target converted via bp2build.
 type bp2buildInfo struct {
-	Name       string
-	Dir        string
-	BazelProps bazel.BazelTargetModuleProperties
-	Attrs      interface{}
+	Dir         string
+	BazelProps  bazel.BazelTargetModuleProperties
+	CommonAttrs CommonAttributes
+	Attrs       interface{}
 }
 
 // TargetName returns the Bazel target name of a bp2build converted target.
 func (b bp2buildInfo) TargetName() string {
-	return b.Name
+	return b.CommonAttrs.Name
 }
 
 // TargetPackage returns the Bazel package of a bp2build converted target.
@@ -1247,8 +1261,8 @@
 }
 
 // BazelAttributes returns the Bazel attributes of a bp2build converted target.
-func (b bp2buildInfo) BazelAttributes() interface{} {
-	return b.Attrs
+func (b bp2buildInfo) BazelAttributes() []interface{} {
+	return []interface{}{&b.CommonAttrs, b.Attrs}
 }
 
 func (m *ModuleBase) addBp2buildInfo(info bp2buildInfo) {
@@ -1265,6 +1279,18 @@
 	return m.commonProperties.Bp2buildInfo
 }
 
+// AddUnconvertedBp2buildDep stores module name of a dependency that was not converted to Bazel.
+func (b *baseModuleContext) AddUnconvertedBp2buildDep(dep string) {
+	unconvertedDeps := &b.Module().base().commonProperties.UnconvertedBp2buildDeps
+	*unconvertedDeps = append(*unconvertedDeps, dep)
+}
+
+// GetUnconvertedBp2buildDeps returns the list of module names of this module's direct dependencies that
+// were not converted to Bazel.
+func (m *ModuleBase) GetUnconvertedBp2buildDeps() []string {
+	return FirstUniqueStrings(m.commonProperties.UnconvertedBp2buildDeps)
+}
+
 func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
 	(*d)["Android"] = map[string]interface{}{}
 }
@@ -1273,6 +1299,8 @@
 
 func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {}
 
+// AddProperties "registers" the provided props
+// each value in props MUST be a pointer to a struct
 func (m *ModuleBase) AddProperties(props ...interface{}) {
 	m.registerProps = append(m.registerProps, props...)
 }
@@ -1282,7 +1310,30 @@
 }
 
 func (m *ModuleBase) BuildParamsForTests() []BuildParams {
-	return m.buildParams
+	// Expand the references to module variables like $flags[0-9]*,
+	// so we do not need to change many existing unit tests.
+	// This looks like undoing the shareFlags optimization in cc's
+	// transformSourceToObj, and should only affects unit tests.
+	vars := m.VariablesForTests()
+	buildParams := append([]BuildParams(nil), m.buildParams...)
+	for i, _ := range buildParams {
+		newArgs := make(map[string]string)
+		for k, v := range buildParams[i].Args {
+			newArgs[k] = v
+			// Replaces both ${flags1} and $flags1 syntax.
+			if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
+				if value, found := vars[v[2:len(v)-1]]; found {
+					newArgs[k] = value
+				}
+			} else if strings.HasPrefix(v, "$") {
+				if value, found := vars[v[1:]]; found {
+					newArgs[k] = value
+				}
+			}
+		}
+		buildParams[i].Args = newArgs
+	}
+	return buildParams
 }
 
 func (m *ModuleBase) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams {
@@ -1651,10 +1702,6 @@
 	return false
 }
 
-func (m *ModuleBase) InstallBypassMake() bool {
-	return false
-}
-
 func (m *ModuleBase) InstallForceOS() (*OsType, *ArchType) {
 	return nil, nil
 }
@@ -1724,13 +1771,25 @@
 	return append(Paths{}, m.vintfFragmentsPaths...)
 }
 
+// SetLicenseInstallMap stores the set of dependency module:location mappings for files in an
+// apex container for use when generation the license metadata file.
+func (m *ModuleBase) SetLicenseInstallMap(installMap []string) {
+	m.licenseInstallMap = append(m.licenseInstallMap, installMap...)
+}
+
 func (m *ModuleBase) generateModuleTarget(ctx ModuleContext) {
 	var allInstalledFiles InstallPaths
 	var allCheckbuildFiles Paths
 	ctx.VisitAllModuleVariants(func(module Module) {
 		a := module.base()
 		allInstalledFiles = append(allInstalledFiles, a.installFiles...)
-		allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...)
+		// A module's -checkbuild phony targets should
+		// not be created if the module is not exported to make.
+		// Those could depend on the build target and fail to compile
+		// for the current build target.
+		if !ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(a) {
+			allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...)
+		}
 	})
 
 	var deps Paths
@@ -1963,9 +2022,8 @@
 		m.installFiles = append(m.installFiles, ctx.installFiles...)
 		m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
 		m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
-		for k, v := range ctx.phonies {
-			m.phonies[k] = append(m.phonies[k], v...)
-		}
+		m.katiInstalls = append(m.katiInstalls, ctx.katiInstalls...)
+		m.katiSymlinks = append(m.katiSymlinks, ctx.katiSymlinks...)
 	} else if ctx.Config().AllowMissingDependencies() {
 		// If the module is not enabled it will not create any build rules, nothing will call
 		// ctx.GetMissingDependencies(), and blueprint will consider the missing dependencies to be unhandled
@@ -1984,6 +2042,8 @@
 	m.installFilesDepSet = newInstallPathsDepSet(m.installFiles, dependencyInstallFiles)
 	m.packagingSpecsDepSet = newPackagingSpecsDepSet(m.packagingSpecs, dependencyPackagingSpecs)
 
+	buildLicenseMetadata(ctx)
+
 	m.buildParams = ctx.buildParams
 	m.ruleParams = ctx.ruleParams
 	m.variables = ctx.variables
@@ -2030,18 +2090,18 @@
 	return GlobFiles(e, globPattern, excludes)
 }
 
-func (b *earlyModuleContext) IsSymlink(path Path) bool {
-	fileInfo, err := b.config.fs.Lstat(path.String())
+func (e *earlyModuleContext) IsSymlink(path Path) bool {
+	fileInfo, err := e.config.fs.Lstat(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
 	}
 	return fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
 }
 
-func (b *earlyModuleContext) Readlink(path Path) string {
-	dest, err := b.config.fs.Readlink(path.String())
+func (e *earlyModuleContext) Readlink(path Path) string {
+	dest, err := e.config.fs.Readlink(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
 	}
 	return dest
 }
@@ -2163,12 +2223,58 @@
 	module          Module
 	phonies         map[string]Paths
 
+	katiInstalls []katiInstall
+	katiSymlinks []katiInstall
+
 	// For tests
 	buildParams []BuildParams
 	ruleParams  map[blueprint.Rule]blueprint.RuleParams
 	variables   map[string]string
 }
 
+// katiInstall stores a request from Soong to Make to create an install rule.
+type katiInstall struct {
+	from          Path
+	to            InstallPath
+	implicitDeps  Paths
+	orderOnlyDeps Paths
+	executable    bool
+	extraFiles    *extraFilesZip
+
+	absFrom string
+}
+
+type extraFilesZip struct {
+	zip Path
+	dir InstallPath
+}
+
+type katiInstalls []katiInstall
+
+// BuiltInstalled returns the katiInstalls in the form used by $(call copy-many-files) in Make, a
+// space separated list of from:to tuples.
+func (installs katiInstalls) BuiltInstalled() string {
+	sb := strings.Builder{}
+	for i, install := range installs {
+		if i != 0 {
+			sb.WriteRune(' ')
+		}
+		sb.WriteString(install.from.String())
+		sb.WriteRune(':')
+		sb.WriteString(install.to.String())
+	}
+	return sb.String()
+}
+
+// InstallPaths returns the install path of each entry.
+func (installs katiInstalls) InstallPaths() InstallPaths {
+	paths := make(InstallPaths, 0, len(installs))
+	for _, install := range installs {
+		paths = append(paths, install.to)
+	}
+	return paths
+}
+
 func (m *moduleContext) ninjaError(params BuildParams, err error) (PackageContext, BuildParams) {
 	return pctx, BuildParams{
 		Rule:            ErrorRule,
@@ -2717,10 +2823,6 @@
 	return m.module.InstallInRoot()
 }
 
-func (m *moduleContext) InstallBypassMake() bool {
-	return m.module.InstallBypassMake()
-}
-
 func (m *moduleContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.module.InstallForceOS()
 }
@@ -2745,23 +2847,25 @@
 		return true
 	}
 
-	if m.Device() {
-		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
-			return true
-		}
-	}
-
 	return false
 }
 
 func (m *moduleContext) InstallFile(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, deps, false)
+	return m.installFile(installPath, name, srcPath, deps, false, nil)
 }
 
 func (m *moduleContext) InstallExecutable(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, deps, true)
+	return m.installFile(installPath, name, srcPath, deps, true, nil)
+}
+
+func (m *moduleContext) InstallFileWithExtraFilesZip(installPath InstallPath, name string, srcPath Path,
+	extraZip Path, deps ...Path) InstallPath {
+	return m.installFile(installPath, name, srcPath, deps, false, &extraFilesZip{
+		zip: extraZip,
+		dir: installPath,
+	})
 }
 
 func (m *moduleContext) PackageFile(installPath InstallPath, name string, srcPath Path) PackagingSpec {
@@ -2770,17 +2874,20 @@
 }
 
 func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
+	licenseFiles := m.Module().EffectiveLicenseFiles()
 	spec := PackagingSpec{
-		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
-		srcPath:          srcPath,
-		symlinkTarget:    "",
-		executable:       executable,
+		relPathInPackage:      Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:               srcPath,
+		symlinkTarget:         "",
+		executable:            executable,
+		effectiveLicenseFiles: &licenseFiles,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
 }
 
-func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path, executable bool) InstallPath {
+func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path,
+	executable bool, extraZip *extraFilesZip) InstallPath {
 
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
@@ -2798,20 +2905,44 @@
 			orderOnlyDeps = deps
 		}
 
-		rule := Cp
-		if executable {
-			rule = CpExecutable
-		}
+		if m.Config().KatiEnabled() {
+			// When creating the install rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiInstalls = append(m.katiInstalls, katiInstall{
+				from:          srcPath,
+				to:            fullInstallPath,
+				implicitDeps:  implicitDeps,
+				orderOnlyDeps: orderOnlyDeps,
+				executable:    executable,
+				extraFiles:    extraZip,
+			})
+		} else {
+			rule := Cp
+			if executable {
+				rule = CpExecutable
+			}
 
-		m.Build(pctx, BuildParams{
-			Rule:        rule,
-			Description: "install " + fullInstallPath.Base(),
-			Output:      fullInstallPath,
-			Input:       srcPath,
-			Implicits:   implicitDeps,
-			OrderOnly:   orderOnlyDeps,
-			Default:     !m.Config().KatiEnabled(),
-		})
+			extraCmds := ""
+			if extraZip != nil {
+				extraCmds += fmt.Sprintf(" && unzip -qDD -d '%s' '%s'",
+					extraZip.dir.String(), extraZip.zip.String())
+				implicitDeps = append(implicitDeps, extraZip.zip)
+			}
+
+			m.Build(pctx, BuildParams{
+				Rule:        rule,
+				Description: "install " + fullInstallPath.Base(),
+				Output:      fullInstallPath,
+				Input:       srcPath,
+				Implicits:   implicitDeps,
+				OrderOnly:   orderOnlyDeps,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"extraCmds": extraCmds,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
@@ -2833,16 +2964,30 @@
 	}
 	if !m.skipInstall() {
 
-		m.Build(pctx, BuildParams{
-			Rule:        Symlink,
-			Description: "install symlink " + fullInstallPath.Base(),
-			Output:      fullInstallPath,
-			Input:       srcPath,
-			Default:     !m.Config().KatiEnabled(),
-			Args: map[string]string{
-				"fromPath": relPath,
-			},
-		})
+		if m.Config().KatiEnabled() {
+			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+				from: srcPath,
+				to:   fullInstallPath,
+			})
+		} else {
+			// The symlink doesn't need updating when the target is modified, but we sometimes
+			// have a dependency on a symlink to a binary instead of to the binary directly, and
+			// the mtime of the symlink must be updated when the binary is modified, so use a
+			// normal dependency here instead of an order-only dependency.
+			m.Build(pctx, BuildParams{
+				Rule:        Symlink,
+				Description: "install symlink " + fullInstallPath.Base(),
+				Output:      fullInstallPath,
+				Input:       srcPath,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"fromPath": relPath,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
@@ -2865,15 +3010,25 @@
 	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
 	if !m.skipInstall() {
-		m.Build(pctx, BuildParams{
-			Rule:        Symlink,
-			Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
-			Output:      fullInstallPath,
-			Default:     !m.Config().KatiEnabled(),
-			Args: map[string]string{
-				"fromPath": absPath,
-			},
-		})
+		if m.Config().KatiEnabled() {
+			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+				absFrom: absPath,
+				to:      fullInstallPath,
+			})
+		} else {
+			m.Build(pctx, BuildParams{
+				Rule:        Symlink,
+				Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
+				Output:      fullInstallPath,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"fromPath": absPath,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
@@ -3150,6 +3305,34 @@
 
 type buildTargetSingleton struct{}
 
+func AddAncestors(ctx SingletonContext, dirMap map[string]Paths, mmName func(string) string) ([]string, []string) {
+	// Ensure ancestor directories are in dirMap
+	// Make directories build their direct subdirectories
+	// Returns a slice of all directories and a slice of top-level directories.
+	dirs := SortedStringKeys(dirMap)
+	for _, dir := range dirs {
+		dir := parentDir(dir)
+		for dir != "." && dir != "/" {
+			if _, exists := dirMap[dir]; exists {
+				break
+			}
+			dirMap[dir] = nil
+			dir = parentDir(dir)
+		}
+	}
+	dirs = SortedStringKeys(dirMap)
+	var topDirs []string
+	for _, dir := range dirs {
+		p := parentDir(dir)
+		if p != "." && p != "/" {
+			dirMap[p] = append(dirMap[p], PathForPhony(ctx, mmName(dir)))
+		} else if dir != "." && dir != "/" && dir != "" {
+			topDirs = append(topDirs, dir)
+		}
+	}
+	return SortedStringKeys(dirMap), topDirs
+}
+
 func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) {
 	var checkbuildDeps Paths
 
@@ -3187,26 +3370,7 @@
 		return
 	}
 
-	// Ensure ancestor directories are in modulesInDir
-	dirs := SortedStringKeys(modulesInDir)
-	for _, dir := range dirs {
-		dir := parentDir(dir)
-		for dir != "." && dir != "/" {
-			if _, exists := modulesInDir[dir]; exists {
-				break
-			}
-			modulesInDir[dir] = nil
-			dir = parentDir(dir)
-		}
-	}
-
-	// Make directories build their direct subdirectories
-	for _, dir := range dirs {
-		p := parentDir(dir)
-		if p != "." && p != "/" {
-			modulesInDir[p] = append(modulesInDir[p], PathForPhony(ctx, mmTarget(dir)))
-		}
-	}
+	dirs, _ := AddAncestors(ctx, modulesInDir, mmTarget)
 
 	// Create a MODULES-IN-<directory> target that depends on all modules in a directory, and
 	// depends on the MODULES-IN-* targets of all of its subdirectories that contain Android.bp
diff --git a/android/module_test.go b/android/module_test.go
index 9e2b0ca..d9e2c87 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -15,7 +15,12 @@
 package android
 
 import (
+	"bytes"
+	"path/filepath"
+	"runtime"
 	"testing"
+
+	mkparser "android/soong/androidmk/parser"
 )
 
 func TestSrcIsModule(t *testing.T) {
@@ -200,16 +205,23 @@
 }
 
 func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	outputFile := PathForModuleOut(ctx, ctx.ModuleName())
+	ctx.Build(pctx, BuildParams{
+		Rule:   Touch,
+		Output: outputFile,
+	})
+	installFile := ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+	ctx.InstallSymlink(PathForModuleInstall(ctx, "symlinks"), ctx.ModuleName(), installFile)
 }
 
 func (m *depsModule) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
 }
 
 func depsModuleFactory() Module {
 	m := &depsModule{}
 	m.AddProperties(&m.props)
-	InitAndroidModule(m)
+	InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon)
 	return m
 }
 
@@ -320,3 +332,286 @@
 		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)).
 		RunTestWithBp(t, bp)
 }
+
+func TestInstall(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+	).RunTestWithBp(t, bp)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	installRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name))
+	}
+
+	assertInputs := func(params TestingBuildParams, inputs ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", Paths(inputs).Strings(),
+			append(PathsIfNonNil(params.Input), params.Inputs...).Strings())
+	}
+
+	assertImplicits := func(params TestingBuildParams, implicits ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected implicit dependencies", Paths(implicits).Strings(),
+			append(PathsIfNonNil(params.Implicit), params.Implicits...).Strings())
+	}
+
+	assertOrderOnlys := func(params TestingBuildParams, orderonlys ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", Paths(orderonlys).Strings(),
+			params.OrderOnly.Strings())
+	}
+
+	// Check host install rule dependencies
+	assertInputs(hostInstallRule("foo"), hostOutputRule("foo").Output)
+	assertImplicits(hostInstallRule("foo"),
+		hostInstallRule("bar").Output,
+		hostSymlinkRule("bar").Output,
+		hostInstallRule("baz").Output,
+		hostSymlinkRule("baz").Output,
+		hostInstallRule("qux").Output,
+		hostSymlinkRule("qux").Output,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an
+	// order-only dependency, so that the tool gets updated when the symlink is depended on.
+	assertInputs(hostSymlinkRule("foo"), hostInstallRule("foo").Output)
+	assertImplicits(hostSymlinkRule("foo"))
+	assertOrderOnlys(hostSymlinkRule("foo"))
+
+	// Check device install rule dependencies
+	assertInputs(installRule("foo"), outputRule("foo").Output)
+	assertImplicits(installRule("foo"))
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").Output,
+		symlinkRule("bar").Output,
+		installRule("baz").Output,
+		symlinkRule("baz").Output,
+		installRule("qux").Output,
+		symlinkRule("qux").Output,
+	)
+
+	// Check device symlink rule dependencies.  Device symlinks could use an order-only dependency,
+	// but the current implementation uses a normal dependency.
+	assertInputs(symlinkRule("foo"), installRule("foo").Output)
+	assertImplicits(symlinkRule("foo"))
+	assertOrderOnlys(symlinkRule("foo"))
+}
+
+func TestInstallKatiEnabled(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+		FixtureModifyConfig(SetKatiEnabledForTests),
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
+		}),
+	).RunTestWithBp(t, bp)
+
+	installs := result.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting
+	buf := bytes.NewBuffer(append([]byte(nil), installs...))
+	parser := mkparser.NewParser("makevars", buf)
+
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		t.Fatalf("error parsing install rules: %s", errs[0])
+	}
+
+	rules := parseMkRules(t, result.Config, nodes)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	ruleForOutput := func(output string) installMakeRule {
+		for _, rule := range rules {
+			if rule.target == output {
+				return rule
+			}
+		}
+		t.Fatalf("no make install rule for %s", output)
+		return installMakeRule{}
+	}
+
+	installRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) installMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86/symlinks", name))
+	}
+
+	assertDeps := func(rule installMakeRule, deps ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", deps, rule.deps)
+	}
+
+	assertOrderOnlys := func(rule installMakeRule, orderonlys ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", orderonlys, rule.orderOnlyDeps)
+	}
+
+	// Check host install rule dependencies
+	assertDeps(hostInstallRule("foo"),
+		hostOutputRule("foo").Output.String(),
+		hostInstallRule("bar").target,
+		hostSymlinkRule("bar").target,
+		hostInstallRule("baz").target,
+		hostSymlinkRule("baz").target,
+		hostInstallRule("qux").target,
+		hostSymlinkRule("qux").target,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an
+	// order-only dependency, so that the tool gets updated when the symlink is depended on.
+	assertDeps(hostSymlinkRule("foo"), hostInstallRule("foo").target)
+	assertOrderOnlys(hostSymlinkRule("foo"))
+
+	// Check device install rule dependencies
+	assertDeps(installRule("foo"), outputRule("foo").Output.String())
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").target,
+		symlinkRule("bar").target,
+		installRule("baz").target,
+		symlinkRule("baz").target,
+		installRule("qux").target,
+		symlinkRule("qux").target,
+	)
+
+	// Check device symlink rule dependencies.  Device symlinks could use an order-only dependency,
+	// but the current implementation uses a normal dependency.
+	assertDeps(symlinkRule("foo"), installRule("foo").target)
+	assertOrderOnlys(symlinkRule("foo"))
+}
+
+type installMakeRule struct {
+	target        string
+	deps          []string
+	orderOnlyDeps []string
+}
+
+func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []installMakeRule {
+	var rules []installMakeRule
+	for _, node := range nodes {
+		if mkParserRule, ok := node.(*mkparser.Rule); ok {
+			var rule installMakeRule
+
+			if targets := mkParserRule.Target.Words(); len(targets) == 0 {
+				t.Fatalf("no targets for rule %s", mkParserRule.Dump())
+			} else if len(targets) > 1 {
+				t.Fatalf("unsupported multiple targets for rule %s", mkParserRule.Dump())
+			} else if !targets[0].Const() {
+				t.Fatalf("unsupported non-const target for rule %s", mkParserRule.Dump())
+			} else {
+				rule.target = normalizeStringRelativeToTop(config, targets[0].Value(nil))
+			}
+
+			prereqList := &rule.deps
+			for _, prereq := range mkParserRule.Prerequisites.Words() {
+				if !prereq.Const() {
+					t.Fatalf("unsupported non-const prerequisite for rule %s", mkParserRule.Dump())
+				}
+
+				if prereq.Value(nil) == "|" {
+					prereqList = &rule.orderOnlyDeps
+					continue
+				}
+
+				*prereqList = append(*prereqList, normalizeStringRelativeToTop(config, prereq.Value(nil)))
+			}
+
+			rules = append(rules, rule)
+		}
+	}
+
+	return rules
+}
diff --git a/android/mutator.go b/android/mutator.go
index 20ec621..dbd8c04 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -15,11 +15,9 @@
 package android
 
 import (
-	"android/soong/bazel"
-	"fmt"
 	"reflect"
-	"strings"
-	"sync"
+
+	"android/soong/bazel"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -35,12 +33,12 @@
 //   continue on to GenerateAndroidBuildActions
 
 // RegisterMutatorsForBazelConversion is a alternate registration pipeline for bp2build. Exported for testing.
-func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators, bp2buildMutators []RegisterMutatorFunc) {
+func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{
 		bazelConversionMode: true,
 	}
 
-	bp2buildPreArchMutators = append([]RegisterMutatorFunc{
+	bp2buildMutators := append([]RegisterMutatorFunc{
 		RegisterNamespaceMutator,
 		RegisterDefaultsPreArchMutators,
 		// TODO(b/165114590): this is required to resolve deps that are only prebuilts, but we should
@@ -48,10 +46,7 @@
 		RegisterPrebuiltsPreArchMutators,
 	},
 		preArchMutators...)
-
-	for _, f := range bp2buildPreArchMutators {
-		f(mctx)
-	}
+	bp2buildMutators = append(bp2buildMutators, registerBp2buildConversionMutator)
 
 	// Register bp2build mutators
 	for _, f := range bp2buildMutators {
@@ -217,24 +212,12 @@
 }
 
 var bp2buildPreArchMutators = []RegisterMutatorFunc{}
-var bp2buildMutators = map[string]RegisterMutatorFunc{}
 
-// See http://b/192523357
-var bp2buildLock sync.Mutex
+// A minimal context for Bp2build conversion
+type Bp2buildMutatorContext interface {
+	BazelConversionPathContext
 
-// RegisterBp2BuildMutator registers specially crafted mutators for
-// converting Blueprint/Android modules into special modules that can
-// be code-generated into Bazel BUILD targets.
-//
-// TODO(b/178068862): bring this into TestContext.
-func RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
-	f := func(ctx RegisterMutatorsContext) {
-		ctx.TopDown(moduleType, m)
-	}
-	// Use a lock to avoid a concurrent map write if RegisterBp2BuildMutator is called in parallel
-	bp2buildLock.Lock()
-	defer bp2buildLock.Unlock()
-	bp2buildMutators[moduleType] = f
+	CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
 }
 
 // PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
@@ -270,7 +253,7 @@
 	// factory method, just like in CreateModule, but also requires
 	// BazelTargetModuleProperties containing additional metadata for the
 	// bp2build codegenerator.
-	CreateBazelTargetModule(string, bazel.BazelTargetModuleProperties, interface{})
+	CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
 }
 
 type topDownMutatorContext struct {
@@ -516,52 +499,18 @@
 }
 
 func (t *topDownMutatorContext) CreateBazelTargetModule(
-	name string,
 	bazelProps bazel.BazelTargetModuleProperties,
+	commonAttrs CommonAttributes,
 	attrs interface{}) {
-	if strings.HasPrefix(name, bazel.BazelTargetModuleNamePrefix) {
-		panic(fmt.Errorf(
-			"The %s name prefix is added automatically, do not set it manually: %s",
-			bazel.BazelTargetModuleNamePrefix,
-			name))
-	}
-
+	commonAttrs.fillCommonBp2BuildModuleAttrs(t)
+	mod := t.Module()
 	info := bp2buildInfo{
-		Name:       name,
-		Dir:        t.OtherModuleDir(t.Module()),
-		BazelProps: bazelProps,
-		Attrs:      attrs,
+		Dir:         t.OtherModuleDir(mod),
+		BazelProps:  bazelProps,
+		CommonAttrs: commonAttrs,
+		Attrs:       attrs,
 	}
-
-	t.Module().base().addBp2buildInfo(info)
-}
-
-func (t *topDownMutatorContext) AppendProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.AppendMatchingProperties(t.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
-	}
-}
-
-func (t *topDownMutatorContext) PrependProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.PrependMatchingProperties(t.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
-	}
+	mod.base().addBp2buildInfo(info)
 }
 
 // android.topDownMutatorContext either has to embed blueprint.TopDownMutatorContext, in which case every method that
diff --git a/android/neverallow.go b/android/neverallow.go
index 19b58a7..0348619 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -27,7 +27,7 @@
 // "neverallow" rules for the build system.
 //
 // This allows things which aren't related to the build system and are enforced
-// for sanity, in progress code refactors, or policy to be expressed in a
+// against assumptions, in progress code refactors, or policy to be expressed in a
 // straightforward away disjoint from implementations and tests which should
 // work regardless of these restrictions.
 //
@@ -150,9 +150,10 @@
 
 func createJavaDeviceForHostRules() []Rule {
 	javaDeviceForHostProjectsAllowedList := []string{
+		"development/build",
 		"external/guava",
 		"external/robolectric-shadows",
-		"framework/layoutlib",
+		"frameworks/layoutlib",
 	}
 
 	return []Rule{
@@ -175,6 +176,7 @@
 		"tools/test/graphicsbenchmark/apps/sample_app",
 		"tools/test/graphicsbenchmark/functional_tests/java",
 		"vendor/xts/gts-tests/hostsidetests/gamedevicecert/apps/javatests",
+		"external/libtextclassifier/native",
 	}
 
 	platformVariantPropertiesAllowedList := []string{
@@ -212,8 +214,11 @@
 	return []Rule{
 		NeverAllow().
 			ModuleType("makefile_goal").
+			// TODO(b/33691272): remove this after migrating seapp to Soong
+			Without("product_out_path", "obj/ETC/plat_seapp_contexts_intermediates/plat_seapp_contexts").
+			Without("product_out_path", "obj/ETC/plat_seapp_neverallows_intermediates/plat_seapp_neverallows").
 			WithoutMatcher("product_out_path", Regexp("^boot[0-9a-zA-Z.-]*[.]img$")).
-			Because("Only boot images may be imported as a makefile goal."),
+			Because("Only boot images and seapp contexts may be imported as a makefile goal."),
 	}
 }
 
@@ -247,7 +252,7 @@
 			continue
 		}
 
-		if !n.appliesToProperties(properties) {
+		if !n.appliesToProperties(ctx, properties) {
 			continue
 		}
 
@@ -267,8 +272,12 @@
 	}
 }
 
+type ValueMatcherContext interface {
+	Config() Config
+}
+
 type ValueMatcher interface {
-	Test(string) bool
+	Test(ValueMatcherContext, string) bool
 	String() string
 }
 
@@ -276,7 +285,7 @@
 	expected string
 }
 
-func (m *equalMatcher) Test(value string) bool {
+func (m *equalMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return m.expected == value
 }
 
@@ -287,7 +296,7 @@
 type anyMatcher struct {
 }
 
-func (m *anyMatcher) Test(value string) bool {
+func (m *anyMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return true
 }
 
@@ -301,7 +310,7 @@
 	prefix string
 }
 
-func (m *startsWithMatcher) Test(value string) bool {
+func (m *startsWithMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return strings.HasPrefix(value, m.prefix)
 }
 
@@ -313,7 +322,7 @@
 	re *regexp.Regexp
 }
 
-func (m *regexMatcher) Test(value string) bool {
+func (m *regexMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return m.re.MatchString(value)
 }
 
@@ -325,7 +334,7 @@
 	allowed []string
 }
 
-func (m *notInListMatcher) Test(value string) bool {
+func (m *notInListMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return !InList(value, m.allowed)
 }
 
@@ -335,7 +344,7 @@
 
 type isSetMatcher struct{}
 
-func (m *isSetMatcher) Test(value string) bool {
+func (m *isSetMatcher) Test(ctx ValueMatcherContext, value string) bool {
 	return value != ""
 }
 
@@ -345,6 +354,19 @@
 
 var isSetMatcherInstance = &isSetMatcher{}
 
+type sdkVersionMatcher struct {
+	condition   func(ctx ValueMatcherContext, spec SdkSpec) bool
+	description string
+}
+
+func (m *sdkVersionMatcher) Test(ctx ValueMatcherContext, value string) bool {
+	return m.condition(ctx, SdkSpecFromWithConfig(ctx.Config(), value))
+}
+
+func (m *sdkVersionMatcher) String() string {
+	return ".sdk-version(" + m.description + ")"
+}
+
 type ruleProperty struct {
 	fields  []string // e.x.: Vndk.Enabled
 	matcher ValueMatcher
@@ -558,9 +580,10 @@
 	return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
 }
 
-func (r *rule) appliesToProperties(properties []interface{}) bool {
-	includeProps := hasAllProperties(properties, r.props)
-	excludeProps := hasAnyProperty(properties, r.unlessProps)
+func (r *rule) appliesToProperties(ctx ValueMatcherContext,
+	properties []interface{}) bool {
+	includeProps := hasAllProperties(ctx, properties, r.props)
+	excludeProps := hasAnyProperty(ctx, properties, r.unlessProps)
 	return includeProps && !excludeProps
 }
 
@@ -580,6 +603,16 @@
 	return &notInListMatcher{allowed}
 }
 
+func LessThanSdkVersion(sdk string) ValueMatcher {
+	return &sdkVersionMatcher{
+		condition: func(ctx ValueMatcherContext, spec SdkSpec) bool {
+			return spec.ApiLevel.LessThan(
+				SdkSpecFromWithConfig(ctx.Config(), sdk).ApiLevel)
+		},
+		description: "lessThan=" + sdk,
+	}
+}
+
 // assorted utils
 
 func cleanPaths(paths []string) []string {
@@ -598,25 +631,28 @@
 	return names
 }
 
-func hasAnyProperty(properties []interface{}, props []ruleProperty) bool {
+func hasAnyProperty(ctx ValueMatcherContext, properties []interface{},
+	props []ruleProperty) bool {
 	for _, v := range props {
-		if hasProperty(properties, v) {
+		if hasProperty(ctx, properties, v) {
 			return true
 		}
 	}
 	return false
 }
 
-func hasAllProperties(properties []interface{}, props []ruleProperty) bool {
+func hasAllProperties(ctx ValueMatcherContext, properties []interface{},
+	props []ruleProperty) bool {
 	for _, v := range props {
-		if !hasProperty(properties, v) {
+		if !hasProperty(ctx, properties, v) {
 			return false
 		}
 	}
 	return true
 }
 
-func hasProperty(properties []interface{}, prop ruleProperty) bool {
+func hasProperty(ctx ValueMatcherContext, properties []interface{},
+	prop ruleProperty) bool {
 	for _, propertyStruct := range properties {
 		propertiesValue := reflect.ValueOf(propertyStruct).Elem()
 		for _, v := range prop.fields {
@@ -630,7 +666,7 @@
 		}
 
 		check := func(value string) bool {
-			return prop.matcher.Test(value)
+			return prop.matcher.Test(ctx, value)
 		}
 
 		if matchValue(propertiesValue, check) {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 35aadd8..18a8705 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -293,7 +293,49 @@
 			`),
 		},
 		expectedErrors: []string{
-			"Only boot images may be imported as a makefile goal.",
+			"Only boot images and seapp contexts may be imported as a makefile goal.",
+		},
+	},
+	{
+		name: "min_sdk too low",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "min_sdk_too_low",
+					min_sdk_version: "30",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+		},
+		expectedErrors: []string{
+			"module \"min_sdk_too_low\": violates neverallow",
+		},
+	},
+	{
+		name: "min_sdk high enough",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "min_sdk_high_enough",
+					min_sdk_version: "31",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
+		},
+	},
+	{
+		name: "current min_sdk high enough",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "current_min_sdk_high_enough",
+					min_sdk_version: "current",
+				}`),
+		},
+		rules: []Rule{
+			NeverAllow().WithMatcher("min_sdk_version", LessThanSdkVersion("31")),
 		},
 	},
 }
@@ -379,9 +421,10 @@
 }
 
 type mockJavaLibraryProperties struct {
-	Libs           []string
-	Sdk_version    *string
-	Uncompress_dex *bool
+	Libs            []string
+	Min_sdk_version *string
+	Sdk_version     *string
+	Uncompress_dex  *bool
 }
 
 type mockJavaLibraryModule struct {
diff --git a/android/notices.go b/android/notices.go
index 07cf3e4..d8cfaf2 100644
--- a/android/notices.go
+++ b/android/notices.go
@@ -100,3 +100,56 @@
 		HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
 	}
 }
+
+// BuildNotices merges the supplied NOTICE files into a single file that lists notices
+// for every key in noticeMap (which would normally be installed files).
+func BuildNotices(ctx ModuleContext, noticeMap map[string]Paths) NoticeOutputs {
+	// TODO(jungjw): We should just produce a well-formatted NOTICE.html file in a single pass.
+	//
+	// generate-notice-files.py, which processes the merged NOTICE file, has somewhat strict rules
+	// about input NOTICE file paths.
+	// 1. Their relative paths to the src root become their NOTICE index titles. We want to use
+	// on-device paths as titles, and so output the merged NOTICE file the corresponding location.
+	// 2. They must end with .txt extension. Otherwise, they're ignored.
+
+	mergeTool := PathForSource(ctx, "build/soong/scripts/mergenotice.py")
+	generateNoticeTool := PathForSource(ctx, "build/soong/scripts/generate-notice-files.py")
+
+	outputDir := PathForModuleOut(ctx, "notices")
+	builder := NewRuleBuilder(pctx, ctx).
+		Sbox(outputDir, PathForModuleOut(ctx, "notices.sbox.textproto"))
+	for _, installPath := range SortedStringKeys(noticeMap) {
+		noticePath := outputDir.Join(ctx, installPath+".txt")
+		// It would be nice if sbox created directories for temporaries, but until then
+		// this is simple enough.
+		builder.Command().
+			Text("(cd").OutputDir().Text("&&").
+			Text("mkdir -p").Text(filepath.Dir(installPath)).Text(")")
+		builder.Temporary(noticePath)
+		builder.Command().
+			Tool(mergeTool).
+			Flag("--output").Output(noticePath).
+			Inputs(noticeMap[installPath])
+	}
+
+	// Transform the merged NOTICE file into a gzipped HTML file.
+	txtOutput := outputDir.Join(ctx, "NOTICE.txt")
+	htmlOutput := outputDir.Join(ctx, "NOTICE.html")
+	htmlGzOutput := outputDir.Join(ctx, "NOTICE.html.gz")
+	title := "\"Notices for " + ctx.ModuleName() + "\""
+	builder.Command().Tool(generateNoticeTool).
+		FlagWithOutput("--text-output ", txtOutput).
+		FlagWithOutput("--html-output ", htmlOutput).
+		FlagWithArg("-t ", title).
+		Flag("-s").OutputDir()
+	builder.Command().BuiltTool("minigzip").
+		FlagWithInput("-c ", htmlOutput).
+		FlagWithOutput("> ", htmlGzOutput)
+	builder.Build("build_notices", "generate notice output")
+
+	return NoticeOutputs{
+		TxtOutput:    OptionalPathForPath(txtOutput),
+		HtmlOutput:   OptionalPathForPath(htmlOutput),
+		HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
+	}
+}
diff --git a/android/package_ctx.go b/android/package_ctx.go
index c19debb..f354db8 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/remoteexec"
 )
@@ -173,7 +174,7 @@
 // package-scoped variable's initialization.
 func (p PackageContext) HostBinToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostToolPath(ctx, path).String())
 	})
 }
 
@@ -183,7 +184,7 @@
 // package-scoped variable's initialization.
 func (p PackageContext) HostJNIToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostJNIToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostJNIToolPath(ctx, path).String())
 	})
 }
 
@@ -193,7 +194,7 @@
 // part of a package-scoped variable's initialization.
 func (p PackageContext) HostJavaToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostJavaToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostJavaToolPath(ctx, path).String())
 	})
 }
 
diff --git a/android/package_test.go b/android/package_test.go
index 7ea10a4..65c4240 100644
--- a/android/package_test.go
+++ b/android/package_test.go
@@ -11,7 +11,7 @@
 }{
 	// Package default_visibility handling is tested in visibility_test.go
 	{
-		name: "package must not accept visibility and name properties",
+		name: "package must not accept visibility, name or licenses properties",
 		fs: map[string][]byte{
 			"top/Android.bp": []byte(`
 				package {
@@ -48,8 +48,7 @@
 					default_visibility: ["//visibility:private"],
 					default_applicable_licenses: ["license"],
 				}
-
-			        package {
+				package {
 				}`),
 		},
 		expectedErrors: []string{
diff --git a/android/packaging.go b/android/packaging.go
index 9065826..e3a0b54 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -38,6 +38,8 @@
 
 	// Whether relPathInPackage should be marked as executable or not
 	executable bool
+
+	effectiveLicenseFiles *Paths
 }
 
 // Get file name of installed package
@@ -54,6 +56,17 @@
 	return p.relPathInPackage
 }
 
+func (p *PackagingSpec) SetRelPathInPackage(relPathInPackage string) {
+	p.relPathInPackage = relPathInPackage
+}
+
+func (p *PackagingSpec) EffectiveLicenseFiles() Paths {
+	if p.effectiveLicenseFiles == nil {
+		return Paths{}
+	}
+	return *p.effectiveLicenseFiles
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
@@ -214,15 +227,9 @@
 	return m
 }
 
-// See PackageModule.CopyDepsToZip
-func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
-	m := p.GatherPackagingSpecs(ctx)
-	builder := NewRuleBuilder(pctx, ctx)
-
-	dir := PathForModuleOut(ctx, ".zip")
-	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
-	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
-
+// CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
+// entries into the specified directory.
+func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, m map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
 	seenDir := make(map[string]bool)
 	for _, k := range SortedStringKeys(m) {
 		ps := m[k]
@@ -243,6 +250,19 @@
 		}
 	}
 
+	return entries
+}
+
+// See PackageModule.CopyDepsToZip
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
+	m := p.GatherPackagingSpecs(ctx)
+	builder := NewRuleBuilder(pctx, ctx)
+
+	dir := PathForModuleOut(ctx, ".zip")
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
+	entries = p.CopySpecsToDir(ctx, builder, m, dir)
+
 	builder.Command().
 		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", zipOut).
diff --git a/android/paths.go b/android/paths.go
index 763cd7c..70e427b 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -112,7 +112,6 @@
 	InstallInDebugRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 }
 
@@ -263,38 +262,56 @@
 
 // OptionalPath is a container that may or may not contain a valid Path.
 type OptionalPath struct {
-	valid bool
-	path  Path
+	path          Path   // nil if invalid.
+	invalidReason string // Not applicable if path != nil. "" if the reason is unknown.
 }
 
 // OptionalPathForPath returns an OptionalPath containing the path.
 func OptionalPathForPath(path Path) OptionalPath {
-	if path == nil {
-		return OptionalPath{}
-	}
-	return OptionalPath{valid: true, path: path}
+	return OptionalPath{path: path}
+}
+
+// InvalidOptionalPath returns an OptionalPath that is invalid with the given reason.
+func InvalidOptionalPath(reason string) OptionalPath {
+
+	return OptionalPath{invalidReason: reason}
 }
 
 // Valid returns whether there is a valid path
 func (p OptionalPath) Valid() bool {
-	return p.valid
+	return p.path != nil
 }
 
 // Path returns the Path embedded in this OptionalPath. You must be sure that
 // there is a valid path, since this method will panic if there is not.
 func (p OptionalPath) Path() Path {
-	if !p.valid {
-		panic("Requesting an invalid path")
+	if p.path == nil {
+		msg := "Requesting an invalid path"
+		if p.invalidReason != "" {
+			msg += ": " + p.invalidReason
+		}
+		panic(msg)
 	}
 	return p.path
 }
 
+// InvalidReason returns the reason that the optional path is invalid, or "" if it is valid.
+func (p OptionalPath) InvalidReason() string {
+	if p.path != nil {
+		return ""
+	}
+	if p.invalidReason == "" {
+		return "unknown"
+	}
+	return p.invalidReason
+}
+
 // AsPaths converts the OptionalPath into Paths.
 //
 // It returns nil if this is not valid, or a single length slice containing the Path embedded in
 // this OptionalPath.
 func (p OptionalPath) AsPaths() Paths {
-	if !p.valid {
+	if p.path == nil {
 		return nil
 	}
 	return Paths{p.path}
@@ -303,7 +320,7 @@
 // RelativeToTop returns an OptionalPath with the path that was embedded having been replaced by the
 // result of calling Path.RelativeToTop on it.
 func (p OptionalPath) RelativeToTop() OptionalPath {
-	if !p.valid {
+	if p.path == nil {
 		return p
 	}
 	p.path = p.path.RelativeToTop()
@@ -312,7 +329,7 @@
 
 // String returns the string version of the Path, or "" if it isn't valid.
 func (p OptionalPath) String() string {
-	if p.valid {
+	if p.path != nil {
 		return p.path.String()
 	} else {
 		return ""
@@ -444,6 +461,13 @@
 	return ret
 }
 
+// PathForGoBinary returns the path to the installed location of a bootstrap_go_binary module.
+func PathForGoBinary(ctx PathContext, goBinary bootstrap.GoBinaryTool) Path {
+	goBinaryInstallDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false)
+	rel := Rel(ctx, goBinaryInstallDir.String(), goBinary.InstallPath())
+	return goBinaryInstallDir.Join(ctx, rel)
+}
+
 // Expands Paths to a SourceFileProducer or OutputFileProducer module dependency referenced via ":name" or ":name{.tag}" syntax.
 // If the dependency is not found, a missingErrorDependency is returned.
 // If the module dependency is not a SourceFileProducer or OutputFileProducer, appropriate errors will be returned.
@@ -464,11 +488,8 @@
 	} else if tag != "" {
 		return nil, fmt.Errorf("path dependency %q is not an output file producing module", path)
 	} else if goBinary, ok := module.(bootstrap.GoBinaryTool); ok {
-		if rel, err := filepath.Rel(PathForOutput(ctx).String(), goBinary.InstallPath()); err == nil {
-			return Paths{PathForOutput(ctx, rel).WithoutRel()}, nil
-		} else {
-			return nil, fmt.Errorf("cannot find output path for %q: %w", goBinary.InstallPath(), err)
-		}
+		goBinaryPath := PathForGoBinary(ctx, goBinary)
+		return Paths{goBinaryPath}, nil
 	} else if srcProducer, ok := module.(SourceFileProducer); ok {
 		return srcProducer.Srcs(), nil
 	} else {
@@ -1077,6 +1098,7 @@
 	path, err := pathForSource(ctx, pathComponents...)
 	if err != nil {
 		reportPathError(ctx, err)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 
@@ -1091,7 +1113,7 @@
 		return OptionalPath{}
 	}
 	if !exists {
-		return OptionalPath{}
+		return InvalidOptionalPath(path.String() + " does not exist")
 	}
 	return OptionalPathForPath(path)
 }
@@ -1127,6 +1149,7 @@
 		relDir = srcPath.path
 	} else {
 		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 	dir := filepath.Join(p.srcDir, p.path, relDir)
@@ -1140,7 +1163,7 @@
 		return OptionalPath{}
 	}
 	if len(paths) == 0 {
-		return OptionalPath{}
+		return InvalidOptionalPath(dir + " does not exist")
 	}
 	relPath := Rel(ctx, p.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
@@ -1551,6 +1574,8 @@
 	// For example, it is host/<os>-<arch> for host modules, and target/product/<device>/<partition> for device modules.
 	partitionDir string
 
+	partition string
+
 	// makePath indicates whether this path is for Soong (false) or Make (true).
 	makePath bool
 }
@@ -1617,8 +1642,8 @@
 	return p
 }
 
-// ToMakePath returns a new InstallPath that points to Make's install directory instead of Soong's,
-// i.e. out/ instead of out/soong/.
+// Deprecated: ToMakePath is a noop, PathForModuleInstall always returns Make paths when building
+// embedded in Make.
 func (p InstallPath) ToMakePath() InstallPath {
 	p.makePath = true
 	return p
@@ -1632,6 +1657,12 @@
 	return makePathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
 }
 
+// PathForHostDexInstall returns an InstallPath representing the install path for the
+// module appended with paths...
+func PathForHostDexInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
+	return makePathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "", ctx.Debug(), pathComponents...)
+}
+
 // PathForModuleInPartitionInstall is similar to PathForModuleInstall but partition is provided by the caller
 func PathForModuleInPartitionInstall(ctx ModuleInstallPathContext, partition string, pathComponents ...string) InstallPath {
 	os, arch := osAndArch(ctx)
@@ -1653,9 +1684,6 @@
 
 func makePathForInstall(ctx ModuleInstallPathContext, os OsType, arch ArchType, partition string, debug bool, pathComponents ...string) InstallPath {
 	ret := pathForInstall(ctx, os, arch, partition, debug, pathComponents...)
-	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
-		ret = ret.ToMakePath()
-	}
 	return ret
 }
 
@@ -1696,7 +1724,11 @@
 		basePath:     basePath{partionPath, ""},
 		soongOutDir:  ctx.Config().soongOutDir,
 		partitionDir: partionPath,
-		makePath:     false,
+		partition:    partition,
+	}
+
+	if ctx.Config().KatiEnabled() {
+		base.makePath = true
 	}
 
 	return base.Join(ctx, pathComponents...)
@@ -1721,8 +1753,7 @@
 }
 
 func InstallPathToOnDevicePath(ctx PathContext, path InstallPath) string {
-	rel := Rel(ctx, PathForOutput(ctx, "target", "product", ctx.Config().DeviceName()).String(), path.String())
-
+	rel := Rel(ctx, strings.TrimSuffix(path.PartitionDir(), path.partition), path.String())
 	return "/" + rel
 }
 
@@ -1973,10 +2004,6 @@
 	return m.inRoot
 }
 
-func (m testModuleInstallPathContext) InstallBypassMake() bool {
-	return false
-}
-
 func (m testModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.forceOS, m.forceArch
 }
@@ -2029,7 +2056,12 @@
 // 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.
 func WriteFileToOutputDir(path WritablePath, data []byte, perm os.FileMode) error {
-	return ioutil.WriteFile(absolutePath(path.String()), data, perm)
+	absPath := absolutePath(path.String())
+	err := os.MkdirAll(filepath.Dir(absPath), 0777)
+	if err != nil {
+		return err
+	}
+	return ioutil.WriteFile(absPath, data, perm)
 }
 
 func RemoveAllOutputDir(path WritablePath) error {
diff --git a/android/paths_test.go b/android/paths_test.go
index f4e4ce1..2f87977 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -137,26 +137,35 @@
 
 func TestOptionalPath(t *testing.T) {
 	var path OptionalPath
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(nil)
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
+
+	path = InvalidOptionalPath("foo")
+	checkInvalidOptionalPath(t, path, "foo")
+
+	path = InvalidOptionalPath("")
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(PathForTesting("path"))
 	checkValidOptionalPath(t, path, "path")
 }
 
-func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
+func checkInvalidOptionalPath(t *testing.T, path OptionalPath, expectedInvalidReason string) {
 	t.Helper()
 	if path.Valid() {
-		t.Errorf("Uninitialized OptionalPath should not be valid")
+		t.Errorf("Invalid OptionalPath should not be valid")
+	}
+	if path.InvalidReason() != expectedInvalidReason {
+		t.Errorf("Wrong invalid reason: expected %q, got %q", expectedInvalidReason, path.InvalidReason())
 	}
 	if path.String() != "" {
-		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
+		t.Errorf("Invalid OptionalPath String() should return \"\", not %q", path.String())
 	}
 	paths := path.AsPaths()
 	if len(paths) != 0 {
-		t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths)
+		t.Errorf("Invalid OptionalPath AsPaths() should return empty Paths, not %q", paths)
 	}
 	defer func() {
 		if r := recover(); r == nil {
@@ -171,6 +180,9 @@
 	if !path.Valid() {
 		t.Errorf("Initialized OptionalPath should not be invalid")
 	}
+	if path.InvalidReason() != "" {
+		t.Errorf("Initialized OptionalPath should not have an invalid reason, got: %q", path.InvalidReason())
+	}
 	if path.String() != expectedString {
 		t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
 	}
@@ -981,7 +993,7 @@
 		{
 			name:     "in out dir",
 			buildDir: "out",
-			src:      "out/a/b/c",
+			src:      "out/soong/a/b/c",
 			err:      "is in output",
 		},
 	}
@@ -1474,7 +1486,8 @@
 		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
 	})
 	t.Run("install for make", func(t *testing.T) {
-		p := PathForModuleInstall(ctx, "install/path").ToMakePath()
+		p := PathForModuleInstall(ctx, "install/path")
+		p.makePath = true
 		AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
 	})
 	t.Run("output", func(t *testing.T) {
@@ -1488,14 +1501,12 @@
 	t.Run("mixture", func(t *testing.T) {
 		paths := Paths{
 			PathForModuleInstall(ctx, "install/path"),
-			PathForModuleInstall(ctx, "install/path").ToMakePath(),
 			PathForOutput(ctx, "output/path"),
 			PathForSource(ctx, "source/path"),
 		}
 
 		expected := []string{
 			"out/soong/target/product/test_device/system/install/path",
-			"out/target/product/test_device/system/install/path",
 			"out/soong/output/path",
 			"source/path",
 		}
@@ -1513,7 +1524,7 @@
 	fmt.Println(p.Rel(), p2.Rel())
 
 	// Output:
-	// out/system/framework/boot.art out/system/framework/boot.oat
+	// out/soong/system/framework/boot.art out/soong/system/framework/boot.oat
 	// boot.art boot.oat
 }
 
@@ -1527,7 +1538,7 @@
 	fmt.Println(p.Rel(), p2.Rel())
 
 	// Output:
-	// out/system/framework/boot.art out/system/framework/oat/arm/boot.vdex
+	// out/soong/system/framework/boot.art out/soong/system/framework/oat/arm/boot.vdex
 	// boot.art oat/arm/boot.vdex
 }
 
diff --git a/android/prebuilt.go b/android/prebuilt.go
index e611502..b0a4f43 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -97,7 +97,10 @@
 type Prebuilt struct {
 	properties PrebuiltProperties
 
-	srcsSupplier     PrebuiltSrcsSupplier
+	// nil if the prebuilt has no srcs property at all. See InitPrebuiltModuleWithoutSrcs.
+	srcsSupplier PrebuiltSrcsSupplier
+
+	// "-" if the prebuilt has no srcs property at all. See InitPrebuiltModuleWithoutSrcs.
 	srcsPropertyName string
 }
 
@@ -177,6 +180,23 @@
 // Return the src value or nil if it is not available.
 type PrebuiltSrcsSupplier func(ctx BaseModuleContext, prebuilt Module) []string
 
+func initPrebuiltModuleCommon(module PrebuiltInterface) *Prebuilt {
+	p := module.Prebuilt()
+	module.AddProperties(&p.properties)
+	module.base().customizableProperties = module.GetProperties()
+	return p
+}
+
+// Initialize the module as a prebuilt module that has no dedicated property that lists its
+// sources. SingleSourcePathFromSupplier should not be called for this module.
+//
+// This is the case e.g. for header modules, which provides the headers in source form
+// regardless whether they are prebuilt or not.
+func InitPrebuiltModuleWithoutSrcs(module PrebuiltInterface) {
+	p := initPrebuiltModuleCommon(module)
+	p.srcsPropertyName = "-"
+}
+
 // Initialize the module as a prebuilt module that uses the provided supplier to access the
 // prebuilt sources of the module.
 //
@@ -190,10 +210,6 @@
 // The provided property name is used to provide helpful error messages in the event that
 // a problem arises, e.g. calling SingleSourcePath() when more than one source is provided.
 func InitPrebuiltModuleWithSrcSupplier(module PrebuiltInterface, srcsSupplier PrebuiltSrcsSupplier, srcsPropertyName string) {
-	p := module.Prebuilt()
-	module.AddProperties(&p.properties)
-	module.base().customizableProperties = module.GetProperties()
-
 	if srcsSupplier == nil {
 		panic(fmt.Errorf("srcsSupplier must not be nil"))
 	}
@@ -201,6 +217,7 @@
 		panic(fmt.Errorf("srcsPropertyName must not be empty"))
 	}
 
+	p := initPrebuiltModuleCommon(module)
 	p.srcsSupplier = srcsSupplier
 	p.srcsPropertyName = srcsPropertyName
 }
@@ -240,7 +257,7 @@
 			value = value.Elem()
 		}
 		if value.Kind() != reflect.String {
-			panic(fmt.Errorf("prebuilt src field %q should be a string or a pointer to one but was %d %q", srcPropertyName, value.Kind(), value))
+			panic(fmt.Errorf("prebuilt src field %q in %T in module %s should be a string or a pointer to one but was %v", srcField, srcProps, module, value))
 		}
 		src := value.String()
 		if src == "" {
@@ -336,7 +353,7 @@
 func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) {
 	m := ctx.Module()
 	if p := GetEmbeddedPrebuilt(m); p != nil {
-		if p.srcsSupplier == nil {
+		if p.srcsSupplier == nil && p.srcsPropertyName == "" {
 			panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it"))
 		}
 		if !p.properties.SourceExists {
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index a1f8e63..fa40d1f 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -510,9 +510,9 @@
 	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
 	ctx.RegisterModuleType("source", newSourceModule)
 	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
-	ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-	ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-	ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+	ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 }
 
 type prebuiltModule struct {
diff --git a/android/proto.go b/android/proto.go
index 0be7893..64d4d05 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -15,12 +15,17 @@
 package android
 
 import (
+	"android/soong/bazel"
 	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
+const (
+	canonicalPathFromRootDefault = true
+)
+
 // TODO(ccross): protos are often used to communicate between multiple modules.  If the only
 // way to convert a proto to source is to reference it as a source file, and external modules cannot
 // reference source files in other modules, then every module that owns a proto file will need to
@@ -90,7 +95,7 @@
 		Flags:                 flags,
 		Deps:                  deps,
 		OutTypeFlag:           protoOutFlag,
-		CanonicalPathFromRoot: proptools.BoolDefault(p.Proto.Canonical_path_from_root, true),
+		CanonicalPathFromRoot: proptools.BoolDefault(p.Proto.Canonical_path_from_root, canonicalPathFromRootDefault),
 		Dir:                   PathForModuleGen(ctx, "proto"),
 		SubDir:                PathForModuleGen(ctx, "proto", ctx.ModuleDir()),
 	}
@@ -146,3 +151,57 @@
 	rule.Command().
 		BuiltTool("dep_fixer").Flag(depFile.String())
 }
+
+// Bp2buildProtoInfo contains information necessary to pass on to language specific conversion.
+type Bp2buildProtoInfo struct {
+	Type *string
+	Name string
+}
+
+type protoAttrs struct {
+	Srcs                bazel.LabelListAttribute
+	Strip_import_prefix *string
+}
+
+// Bp2buildProtoProperties converts proto properties, creating a proto_library and returning the
+// information necessary for language-specific handling.
+func Bp2buildProtoProperties(ctx Bp2buildMutatorContext, module Module, srcs bazel.LabelListAttribute) (Bp2buildProtoInfo, bool) {
+	var info Bp2buildProtoInfo
+	if srcs.IsEmpty() {
+		return info, false
+	}
+	m := module.base()
+
+	info.Name = m.Name() + "_proto"
+	attrs := protoAttrs{
+		Srcs: srcs,
+	}
+
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &ProtoProperties{}) {
+		for _, rawProps := range configToProps {
+			var props *ProtoProperties
+			var ok bool
+			if props, ok = rawProps.(*ProtoProperties); !ok {
+				ctx.ModuleErrorf("Could not cast ProtoProperties to expected type")
+			}
+			if axis == bazel.NoConfigAxis {
+				info.Type = props.Proto.Type
+
+				if proptools.BoolDefault(props.Proto.Canonical_path_from_root, canonicalPathFromRootDefault) {
+					// an empty string indicates to strips the package path
+					path := ""
+					attrs.Strip_import_prefix = &path
+				}
+			} else if props.Proto.Type != info.Type && props.Proto.Type != nil {
+				ctx.ModuleErrorf("Cannot handle arch-variant types for protos at this time.")
+			}
+		}
+	}
+
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
+		CommonAttributes{Name: info.Name},
+		&attrs)
+
+	return info, true
+}
diff --git a/android/register.go b/android/register.go
index 5984862..1ac4440 100644
--- a/android/register.go
+++ b/android/register.go
@@ -161,6 +161,10 @@
 	return ctx
 }
 
+func (ctx *Context) SetRunningAsBp2build() {
+	ctx.config.runningAsBp2Build = true
+}
+
 // RegisterForBazelConversion registers an alternate shadow pipeline of
 // singletons, module types and mutators to register for converting Blueprint
 // files to semantically equivalent BUILD files.
@@ -174,13 +178,7 @@
 		t.register(ctx)
 	}
 
-	bp2buildMutatorList := []RegisterMutatorFunc{}
-	for t, f := range bp2buildMutators {
-		ctx.config.bp2buildModuleTypeConfig[t] = true
-		bp2buildMutatorList = append(bp2buildMutatorList, f)
-	}
-
-	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators, bp2buildMutatorList)
+	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
 // Register the pipeline of singletons, module types, and mutators for
@@ -192,15 +190,6 @@
 		t.register(ctx)
 	}
 
-	if ctx.config.BazelContext.BazelEnabled() {
-		// Hydrate the configuration of bp2build-enabled module types. This is
-		// required as a signal to identify which modules should be deferred to
-		// Bazel in mixed builds, if it is enabled.
-		for t, _ := range bp2buildMutators {
-			ctx.config.bp2buildModuleTypeConfig[t] = true
-		}
-	}
-
 	mutators := collateGloballyRegisteredMutators()
 	mutators.registerAll(ctx)
 
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 6605869..1c6b1c0 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -769,16 +769,25 @@
 	paths Paths
 }
 
+func checkPathNotNil(path Path) {
+	if path == nil {
+		panic("rule_builder paths cannot be nil")
+	}
+}
+
 func (c *RuleBuilderCommand) addInput(path Path) string {
+	checkPathNotNil(path)
 	c.inputs = append(c.inputs, path)
 	return c.PathForInput(path)
 }
 
 func (c *RuleBuilderCommand) addImplicit(path Path) {
+	checkPathNotNil(path)
 	c.implicits = append(c.implicits, path)
 }
 
 func (c *RuleBuilderCommand) addOrderOnly(path Path) {
+	checkPathNotNil(path)
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
@@ -824,10 +833,11 @@
 
 func sboxPathForToolRel(ctx BuilderContext, path Path) string {
 	// Errors will be handled in RuleBuilder.Build where we have a context to report them
-	relOut, isRelOut, _ := maybeRelErr(PathForOutput(ctx, "host", ctx.Config().PrebuiltOS()).String(), path.String())
-	if isRelOut {
-		// The tool is in the output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
-		return filepath.Join(sboxToolsSubDir, "out", relOut)
+	toolDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "", false)
+	relOutSoong, isRelOutSoong, _ := maybeRelErr(toolDir.String(), path.String())
+	if isRelOutSoong {
+		// The tool is in the Soong output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
+		return filepath.Join(sboxToolsSubDir, "out", relOutSoong)
 	}
 	// The tool is in the source directory, it will be copied to __SBOX_OUT_DIR__/tools/src
 	return filepath.Join(sboxToolsSubDir, "src", path.String())
@@ -1004,19 +1014,23 @@
 // Tool adds the specified tool path to the command line.  The path will be also added to the dependencies returned by
 // RuleBuilder.Tools.
 func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c.Text(c.PathForTool(path))
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTools(paths Paths) *RuleBuilderCommand {
-	c.tools = append(c.tools, paths...)
+	for _, path := range paths {
+		c.ImplicitTool(path)
+	}
 	return c
 }
 
@@ -1093,6 +1107,7 @@
 // Validation adds the specified input path to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validation(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.validations = append(c.validations, path)
 	return c
 }
@@ -1100,13 +1115,16 @@
 // Validations adds the specified input paths to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validations(paths Paths) *RuleBuilderCommand {
-	c.validations = append(c.validations, paths...)
+	for _, path := range paths {
+		c.Validation(path)
+	}
 	return c
 }
 
 // Output adds the specified output path to the command line.  The path will also be added to the outputs returned by
 // RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.outputs = append(c.outputs, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1133,6 +1151,7 @@
 // line, and causes RuleBuilder.Build file to set the depfile flag for ninja.  If multiple depfiles are added to
 // commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
 func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.depFiles = append(c.depFiles, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1155,6 +1174,7 @@
 // will be a symlink instead of a regular file. Does not modify the command
 // line.
 func (c *RuleBuilderCommand) ImplicitSymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.ImplicitOutput(path)
 }
@@ -1172,6 +1192,7 @@
 // SymlinkOutput declares the specified path as an output that will be a symlink
 // instead of a regular file. Modifies the command line.
 func (c *RuleBuilderCommand) SymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.Output(path)
 }
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index feee90f..3766bb0 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -64,10 +64,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "ld a.o b.o -o out/linked && echo success"
+	// commands: "ld a.o b.o -o out/soong/linked && echo success"
 	// tools: ["ld"]
 	// inputs: ["a.o" "b.o"]
-	// outputs: ["out/linked"]
+	// outputs: ["out/soong/linked"]
 }
 
 func ExampleRuleBuilder_SymlinkOutputs() {
@@ -79,7 +79,7 @@
 		Tool(PathForSource(ctx, "ln")).
 		FlagWithInput("-s ", PathForTesting("a.o")).
 		SymlinkOutput(PathForOutput(ctx, "a"))
-	rule.Command().Text("cp out/a out/b").
+	rule.Command().Text("cp out/soong/a out/soong/b").
 		ImplicitSymlinkOutput(PathForOutput(ctx, "b"))
 
 	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
@@ -89,11 +89,11 @@
 	fmt.Printf("symlink_outputs: %q\n", rule.SymlinkOutputs())
 
 	// Output:
-	// commands: "ln -s a.o out/a && cp out/a out/b"
+	// commands: "ln -s a.o out/soong/a && cp out/soong/a out/soong/b"
 	// tools: ["ln"]
 	// inputs: ["a.o"]
-	// outputs: ["out/a" "out/b"]
-	// symlink_outputs: ["out/a" "out/b"]
+	// outputs: ["out/soong/a" "out/soong/b"]
+	// symlink_outputs: ["out/soong/a" "out/soong/b"]
 }
 
 func ExampleRuleBuilder_Temporary() {
@@ -117,10 +117,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "cp a out/b && cp out/b out/c"
+	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c"
 	// tools: ["cp"]
 	// inputs: ["a"]
-	// outputs: ["out/c"]
+	// outputs: ["out/soong/c"]
 }
 
 func ExampleRuleBuilder_DeleteTemporaryFiles() {
@@ -145,10 +145,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "cp a out/b && cp out/b out/c && rm -f out/b"
+	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c && rm -f out/soong/b"
 	// tools: ["cp"]
 	// inputs: ["a"]
-	// outputs: ["out/c"]
+	// outputs: ["out/soong/c"]
 }
 
 func ExampleRuleBuilder_Installs() {
@@ -168,7 +168,7 @@
 	fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String())
 
 	// Output:
-	// rule.Installs().String() = "out/linked:/bin/linked out/linked:/sbin/linked"
+	// rule.Installs().String() = "out/soong/linked:/bin/linked out/soong/linked:/sbin/linked"
 }
 
 func ExampleRuleBuilderCommand() {
@@ -271,7 +271,7 @@
 		FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")).
 		String())
 	// Output:
-	// javac @out/foo.rsp
+	// javac @out/soong/foo.rsp
 }
 
 func ExampleRuleBuilderCommand_String() {
@@ -371,15 +371,15 @@
 		addCommands(rule)
 
 		wantCommands := []string{
-			"out_local/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/module/depfile " +
-				"FlagWithInput=input FlagWithOutput=out_local/module/output FlagWithRspFileInputList=out_local/rsp " +
-				"Input out_local/module/Output out_local/module/SymlinkOutput Text Tool after command2 old cmd",
-			"command2 out_local/module/depfile2 input2 out_local/module/output2 tool2",
-			"command3 input3 out_local/module/output2 out_local/module/output3 input3 out_local/module/output2",
+			"out_local/soong/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/soong/module/depfile " +
+				"FlagWithInput=input FlagWithOutput=out_local/soong/module/output FlagWithRspFileInputList=out_local/soong/rsp " +
+				"Input out_local/soong/module/Output out_local/soong/module/SymlinkOutput Text Tool after command2 old cmd",
+			"command2 out_local/soong/module/depfile2 input2 out_local/soong/module/output2 tool2",
+			"command3 input3 out_local/soong/module/output2 out_local/soong/module/output3 input3 out_local/soong/module/output2",
 		}
 
-		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
-			"out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2"
+		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
+			"out_local/soong/module/DepFile out_local/soong/module/depfile out_local/soong/module/ImplicitDepFile out_local/soong/module/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -403,13 +403,13 @@
 		wantCommands := []string{
 			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
 				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
-				"FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
 				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
 			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
 			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
 		}
 
-		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
+		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -433,7 +433,7 @@
 		wantCommands := []string{
 			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
 				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
-				"FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
 				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
 			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
 			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
diff --git a/android/sdk.go b/android/sdk.go
index b8f76c1..1d63d7a 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"sort"
 	"strings"
 
@@ -22,6 +23,8 @@
 	"github.com/google/blueprint/proptools"
 )
 
+// RequiredSdks provides access to the set of SDKs required by an APEX and its contents.
+//
 // Extracted from SdkAware to make it easier to define custom subsets of the
 // SdkAware interface and improve code navigation within the IDE.
 //
@@ -30,11 +33,11 @@
 // is expected to implement RequiredSdks() by reading its own properties like
 // `uses_sdks`.
 type RequiredSdks interface {
-	// The set of SDKs required by an APEX and its contents.
+	// RequiredSdks returns the set of SDKs required by an APEX and its contents.
 	RequiredSdks() SdkRefs
 }
 
-// Provided to improve code navigation with the IDE.
+// sdkAwareWithoutModule is provided simply to improve code navigation with the IDE.
 type sdkAwareWithoutModule interface {
 	RequiredSdks
 
@@ -233,75 +236,85 @@
 	return false
 }
 
-// Provide support for generating the build rules which will build the snapshot.
+// SnapshotBuilder provides support for generating the build rules which will build the snapshot.
 type SnapshotBuilder interface {
-	// Copy src to the dest (which is a snapshot relative path) and add the dest
-	// to the zip
+	// CopyToSnapshot generates a rule that will copy the src to the dest (which is a snapshot
+	// relative path) and add the dest to the zip.
 	CopyToSnapshot(src Path, dest string)
 
-	// Return the path to an empty file.
+	// EmptyFile returns the path to an empty file.
 	//
 	// This can be used by sdk member types that need to create an empty file in the snapshot, simply
 	// pass the value returned from this to the CopyToSnapshot() method.
 	EmptyFile() Path
 
-	// Unzip the supplied zip into the snapshot relative directory destDir.
+	// UnzipToSnapshot generates a rule that will unzip the supplied zip into the snapshot relative
+	// directory destDir.
 	UnzipToSnapshot(zipPath Path, destDir string)
 
-	// Add a new prebuilt module to the snapshot. The returned module
-	// must be populated with the module type specific properties. The following
-	// properties will be automatically populated.
+	// AddPrebuiltModule adds a new prebuilt module to the snapshot.
+	//
+	// It is intended to be called from SdkMemberType.AddPrebuiltModule which can add module type
+	// specific properties that are not variant specific. The following properties will be
+	// automatically populated before returning.
 	//
 	// * name
 	// * sdk_member_name
 	// * prefer
 	//
-	// This will result in two Soong modules being generated in the Android. One
-	// that is versioned, coupled to the snapshot version and marked as
-	// prefer=true. And one that is not versioned, not marked as prefer=true and
-	// will only be used if the equivalently named non-prebuilt module is not
-	// present.
+	// Properties that are variant specific will be handled by SdkMemberProperties structure.
+	//
+	// Each module created by this method can be output to the generated Android.bp file in two
+	// different forms, depending on the setting of the SOONG_SDK_SNAPSHOT_VERSION build property.
+	// The two forms are:
+	// 1. A versioned Soong module that is referenced from a corresponding similarly versioned
+	//    snapshot module.
+	// 2. An unversioned Soong module that.
+	//
+	// See sdk/update.go for more information.
 	AddPrebuiltModule(member SdkMember, moduleType string) BpModule
 
-	// The property tag to use when adding a property to a BpModule that contains
-	// references to other sdk members. Using this will ensure that the reference
-	// is correctly output for both versioned and unversioned prebuilts in the
-	// snapshot.
+	// SdkMemberReferencePropertyTag returns a property tag to use when adding a property to a
+	// BpModule that contains references to other sdk members.
 	//
-	// "required: true" means that the property must only contain references
-	// to other members of the sdk. Passing a reference to a module that is not a
-	// member of the sdk will result in a build error.
+	// Using this will ensure that the reference is correctly output for both versioned and
+	// unversioned prebuilts in the snapshot.
 	//
-	// "required: false" means that the property can contain references to modules
-	// that are either members or not members of the sdk. If a reference is to a
-	// module that is a non member then the reference is left unchanged, i.e. it
-	// is not transformed as references to members are.
+	// "required: true" means that the property must only contain references to other members of the
+	// sdk. Passing a reference to a module that is not a member of the sdk will result in a build
+	// error.
 	//
-	// The handling of the member names is dependent on whether it is an internal or
-	// exported member. An exported member is one whose name is specified in one of
-	// the member type specific properties. An internal member is one that is added
-	// due to being a part of an exported (or other internal) member and is not itself
-	// an exported member.
+	// "required: false" means that the property can contain references to modules that are either
+	// members or not members of the sdk. If a reference is to a module that is a non member then the
+	// reference is left unchanged, i.e. it is not transformed as references to members are.
+	//
+	// The handling of the member names is dependent on whether it is an internal or exported member.
+	// An exported member is one whose name is specified in one of the member type specific
+	// properties. An internal member is one that is added due to being a part of an exported (or
+	// other internal) member and is not itself an exported member.
 	//
 	// Member names are handled as follows:
-	// * When creating the unversioned form of the module the name is left unchecked
-	//   unless the member is internal in which case it is transformed into an sdk
-	//   specific name, i.e. by prefixing with the sdk name.
+	// * When creating the unversioned form of the module the name is left unchecked unless the member
+	//   is internal in which case it is transformed into an sdk specific name, i.e. by prefixing with
+	//   the sdk name.
 	//
-	// * When creating the versioned form of the module the name is transformed into
-	//   a versioned sdk specific name, i.e. by prefixing with the sdk name and
-	//   suffixing with the version.
+	// * When creating the versioned form of the module the name is transformed into a versioned sdk
+	//   specific name, i.e. by prefixing with the sdk name and suffixing with the version.
 	//
 	// e.g.
 	// bpPropertySet.AddPropertyWithTag("libs", []string{"member1", "member2"}, builder.SdkMemberReferencePropertyTag(true))
 	SdkMemberReferencePropertyTag(required bool) BpPropertyTag
 }
 
+// BpPropertyTag is a marker interface that can be associated with properties in a BpPropertySet to
+// provide additional information which can be used to customize their behavior.
 type BpPropertyTag interface{}
 
-// A set of properties for use in a .bp file.
+// BpPropertySet is a set of properties for use in a .bp file.
 type BpPropertySet interface {
-	// Add a property, the value can be one of the following types:
+	// AddProperty adds a property.
+	//
+	// The value can be one of the following types:
 	// * string
 	// * array of the above
 	// * bool
@@ -326,18 +339,18 @@
 	// * Otherwise, if a property exists with the name then it is an error.
 	AddProperty(name string, value interface{})
 
-	// Add a property with an associated tag
+	// AddPropertyWithTag adds a property with an associated property tag.
 	AddPropertyWithTag(name string, value interface{}, tag BpPropertyTag)
 
-	// Add a property set with the specified name and return so that additional
-	// properties can be added.
+	// AddPropertySet adds a property set with the specified name and returns it so that additional
+	// properties can be added to it.
 	AddPropertySet(name string) BpPropertySet
 
-	// Add comment for property (or property set).
+	// AddCommentForProperty adds a comment for the named property (or property set).
 	AddCommentForProperty(name, text string)
 }
 
-// A .bp module definition.
+// BpModule represents a module definition in a .bp file.
 type BpModule interface {
 	BpPropertySet
 
@@ -364,19 +377,238 @@
 
 var _ BpPrintable = BpPrintableBase{}
 
-// An individual member of the SDK, includes all of the variants that the SDK
-// requires.
+// sdkRegisterable defines the interface that must be implemented by objects that can be registered
+// in an sdkRegistry.
+type sdkRegisterable interface {
+	// SdkPropertyName returns the name of the corresponding property on an sdk module.
+	SdkPropertyName() string
+}
+
+// sdkRegistry provides support for registering and retrieving objects that define properties for
+// use by sdk and module_exports module types.
+type sdkRegistry struct {
+	// The list of registered objects sorted by property name.
+	list []sdkRegisterable
+}
+
+// copyAndAppend creates a new sdkRegistry that includes all the traits registered in
+// this registry plus the supplied trait.
+func (r *sdkRegistry) copyAndAppend(registerable sdkRegisterable) *sdkRegistry {
+	oldList := r.list
+
+	// Make sure that list does not already contain the property. Uses a simple linear search instead
+	// of a binary search even though the list is sorted. That is because the number of items in the
+	// list is small and so not worth the overhead of a binary search.
+	found := false
+	newPropertyName := registerable.SdkPropertyName()
+	for _, r := range oldList {
+		if r.SdkPropertyName() == newPropertyName {
+			found = true
+			break
+		}
+	}
+	if found {
+		names := []string{}
+		for _, r := range oldList {
+			names = append(names, r.SdkPropertyName())
+		}
+		panic(fmt.Errorf("duplicate properties found, %q already exists in %q", newPropertyName, names))
+	}
+
+	// Copy the slice just in case this is being read while being modified, e.g. when testing.
+	list := make([]sdkRegisterable, 0, len(oldList)+1)
+	list = append(list, oldList...)
+	list = append(list, registerable)
+
+	// Sort the registered objects by their property name to ensure that registry order has no effect
+	// on behavior.
+	sort.Slice(list, func(i1, i2 int) bool {
+		t1 := list[i1]
+		t2 := list[i2]
+
+		return t1.SdkPropertyName() < t2.SdkPropertyName()
+	})
+
+	// Create a new registry so the pointer uniquely identifies the set of registered types.
+	return &sdkRegistry{
+		list: list,
+	}
+}
+
+// registeredObjects returns the list of registered instances.
+func (r *sdkRegistry) registeredObjects() []sdkRegisterable {
+	return r.list
+}
+
+// uniqueOnceKey returns a key that uniquely identifies this instance and can be used with
+// OncePer.Once
+func (r *sdkRegistry) uniqueOnceKey() OnceKey {
+	// Use the pointer to the registry as the unique key. The pointer is used because it is guaranteed
+	// to uniquely identify the contained list. The list itself cannot be used as slices are not
+	// comparable. Using the pointer does mean that two separate registries with identical lists would
+	// have different keys and so cause whatever information is cached to be created multiple times.
+	// However, that is not an issue in practice as it should not occur outside tests. Constructing a
+	// string representation of the list to use instead would avoid that but is an unnecessary
+	// complication that provides no significant benefit.
+	return NewCustomOnceKey(r)
+}
+
+// SdkMemberTrait represents a trait that members of an sdk module can contribute to the sdk
+// snapshot.
+//
+// A trait is simply a characteristic of sdk member that is not required by default which may be
+// required for some members but not others. Traits can cause additional information to be output
+// to the sdk snapshot or replace the default information exported for a member with something else.
+// e.g.
+// * By default cc libraries only export the default image variants to the SDK. However, for some
+//   members it may be necessary to export specific image variants, e.g. vendor, or recovery.
+// * By default cc libraries export all the configured architecture variants except for the native
+//   bridge architecture variants. However, for some members it may be necessary to export the
+//   native bridge architecture variants as well.
+// * By default cc libraries export the platform variant (i.e. sdk:). However, for some members it
+//   may be necessary to export the sdk variant (i.e. sdk:sdk).
+//
+// A sdk can request a module to provide no traits, one trait or a collection of traits. The exact
+// behavior of a trait is determined by how SdkMemberType implementations handle the traits. A trait
+// could be specific to one SdkMemberType or many. Some trait combinations could be incompatible.
+//
+// The sdk module type will create a special traits structure that contains a property for each
+// trait registered with RegisterSdkMemberTrait(). The property names are those returned from
+// SdkPropertyName(). Each property contains a list of modules that are required to have that trait.
+// e.g. something like this:
+//
+//   sdk {
+//     name: "sdk",
+//     ...
+//     traits: {
+//       recovery_image: ["module1", "module4", "module5"],
+//       native_bridge: ["module1", "module2"],
+//       native_sdk: ["module1", "module3"],
+//       ...
+//     },
+//     ...
+//   }
+type SdkMemberTrait interface {
+	// SdkPropertyName returns the name of the traits property on an sdk module.
+	SdkPropertyName() string
+}
+
+var _ sdkRegisterable = (SdkMemberTrait)(nil)
+
+// SdkMemberTraitBase is the base struct that must be embedded within any type that implements
+// SdkMemberTrait.
+type SdkMemberTraitBase struct {
+	// PropertyName is the name of the property
+	PropertyName string
+}
+
+func (b *SdkMemberTraitBase) SdkPropertyName() string {
+	return b.PropertyName
+}
+
+// SdkMemberTraitSet is a set of SdkMemberTrait instances.
+type SdkMemberTraitSet interface {
+	// Empty returns true if this set is empty.
+	Empty() bool
+
+	// Contains returns true if this set contains the specified trait.
+	Contains(trait SdkMemberTrait) bool
+
+	// Subtract returns a new set containing all elements of this set except for those in the
+	// other set.
+	Subtract(other SdkMemberTraitSet) SdkMemberTraitSet
+
+	// String returns a string representation of the set and its contents.
+	String() string
+}
+
+func NewSdkMemberTraitSet(traits []SdkMemberTrait) SdkMemberTraitSet {
+	if len(traits) == 0 {
+		return EmptySdkMemberTraitSet()
+	}
+
+	m := sdkMemberTraitSet{}
+	for _, trait := range traits {
+		m[trait] = true
+	}
+	return m
+}
+
+func EmptySdkMemberTraitSet() SdkMemberTraitSet {
+	return (sdkMemberTraitSet)(nil)
+}
+
+type sdkMemberTraitSet map[SdkMemberTrait]bool
+
+var _ SdkMemberTraitSet = (sdkMemberTraitSet{})
+
+func (s sdkMemberTraitSet) Empty() bool {
+	return len(s) == 0
+}
+
+func (s sdkMemberTraitSet) Contains(trait SdkMemberTrait) bool {
+	return s[trait]
+}
+
+func (s sdkMemberTraitSet) Subtract(other SdkMemberTraitSet) SdkMemberTraitSet {
+	if other.Empty() {
+		return s
+	}
+
+	var remainder []SdkMemberTrait
+	for trait, _ := range s {
+		if !other.Contains(trait) {
+			remainder = append(remainder, trait)
+		}
+	}
+
+	return NewSdkMemberTraitSet(remainder)
+}
+
+func (s sdkMemberTraitSet) String() string {
+	list := []string{}
+	for trait, _ := range s {
+		list = append(list, trait.SdkPropertyName())
+	}
+	sort.Strings(list)
+	return fmt.Sprintf("[%s]", strings.Join(list, ","))
+}
+
+var registeredSdkMemberTraits = &sdkRegistry{}
+
+// RegisteredSdkMemberTraits returns a OnceKey and a sorted list of registered traits.
+//
+// The key uniquely identifies the array of traits and can be used with OncePer.Once() to cache
+// information derived from the array of traits.
+func RegisteredSdkMemberTraits() (OnceKey, []SdkMemberTrait) {
+	registerables := registeredSdkMemberTraits.registeredObjects()
+	traits := make([]SdkMemberTrait, len(registerables))
+	for i, registerable := range registerables {
+		traits[i] = registerable.(SdkMemberTrait)
+	}
+	return registeredSdkMemberTraits.uniqueOnceKey(), traits
+}
+
+// RegisterSdkMemberTrait registers an SdkMemberTrait object to allow them to be used in the
+// module_exports, module_exports_snapshot, sdk and sdk_snapshot module types.
+func RegisterSdkMemberTrait(trait SdkMemberTrait) {
+	registeredSdkMemberTraits = registeredSdkMemberTraits.copyAndAppend(trait)
+}
+
+// SdkMember is an individual member of the SDK.
+//
+// It includes all of the variants that the SDK depends upon.
 type SdkMember interface {
-	// The name of the member.
+	// Name returns the name of the member.
 	Name() string
 
-	// All the variants required by the SDK.
+	// Variants returns all the variants of this module depended upon by the SDK.
 	Variants() []SdkAware
 }
 
-// SdkMemberTypeDependencyTag is the interface that a tag must implement in order to allow the
+// SdkMemberDependencyTag is the interface that a tag must implement in order to allow the
 // dependent module to be automatically added to the sdk.
-type SdkMemberTypeDependencyTag interface {
+type SdkMemberDependencyTag interface {
 	blueprint.DependencyTag
 
 	// SdkMemberType returns the SdkMemberType that will be used to automatically add the child module
@@ -401,37 +633,37 @@
 	ExportMember() bool
 }
 
-var _ SdkMemberTypeDependencyTag = (*sdkMemberTypeDependencyTag)(nil)
-var _ ReplaceSourceWithPrebuilt = (*sdkMemberTypeDependencyTag)(nil)
+var _ SdkMemberDependencyTag = (*sdkMemberDependencyTag)(nil)
+var _ ReplaceSourceWithPrebuilt = (*sdkMemberDependencyTag)(nil)
 
-type sdkMemberTypeDependencyTag struct {
+type sdkMemberDependencyTag struct {
 	blueprint.BaseDependencyTag
 	memberType SdkMemberType
 	export     bool
 }
 
-func (t *sdkMemberTypeDependencyTag) SdkMemberType(_ Module) SdkMemberType {
+func (t *sdkMemberDependencyTag) SdkMemberType(_ Module) SdkMemberType {
 	return t.memberType
 }
 
-func (t *sdkMemberTypeDependencyTag) ExportMember() bool {
+func (t *sdkMemberDependencyTag) ExportMember() bool {
 	return t.export
 }
 
-// Prevent dependencies from the sdk/module_exports onto their members from being
-// replaced with a preferred prebuilt.
-func (t *sdkMemberTypeDependencyTag) ReplaceSourceWithPrebuilt() bool {
+// ReplaceSourceWithPrebuilt prevents dependencies from the sdk/module_exports onto their members
+// from being replaced with a preferred prebuilt.
+func (t *sdkMemberDependencyTag) ReplaceSourceWithPrebuilt() bool {
 	return false
 }
 
-// DependencyTagForSdkMemberType creates an SdkMemberTypeDependencyTag that will cause any
+// DependencyTagForSdkMemberType creates an SdkMemberDependencyTag that will cause any
 // dependencies added by the tag to be added to the sdk as the specified SdkMemberType and exported
 // (or not) as specified by the export parameter.
-func DependencyTagForSdkMemberType(memberType SdkMemberType, export bool) SdkMemberTypeDependencyTag {
-	return &sdkMemberTypeDependencyTag{memberType: memberType, export: export}
+func DependencyTagForSdkMemberType(memberType SdkMemberType, export bool) SdkMemberDependencyTag {
+	return &sdkMemberDependencyTag{memberType: memberType, export: export}
 }
 
-// Interface that must be implemented for every type that can be a member of an
+// SdkMemberType is the interface that must be implemented for every type that can be a member of an
 // sdk.
 //
 // The basic implementation should look something like this, where ModuleType is
@@ -452,43 +684,43 @@
 //    ...methods...
 //
 type SdkMemberType interface {
-	// The name of the member type property on an sdk module.
+	// SdkPropertyName returns the name of the member type property on an sdk module.
 	SdkPropertyName() string
 
 	// RequiresBpProperty returns true if this member type requires its property to be usable within
 	// an Android.bp file.
 	RequiresBpProperty() bool
 
-	// True if the member type supports the sdk/sdk_snapshot, false otherwise.
+	// UsableWithSdkAndSdkSnapshot returns true if the member type supports the sdk/sdk_snapshot,
+	// false otherwise.
 	UsableWithSdkAndSdkSnapshot() bool
 
-	// Return true if prebuilt host artifacts may be specific to the host OS. Only
-	// applicable to modules where HostSupported() is true. If this is true,
-	// snapshots will list each host OS variant explicitly and disable all other
-	// host OS'es.
+	// IsHostOsDependent returns true if prebuilt host artifacts may be specific to the host OS. Only
+	// applicable to modules where HostSupported() is true. If this is true, snapshots will list each
+	// host OS variant explicitly and disable all other host OS'es.
 	IsHostOsDependent() bool
 
-	// Add dependencies from the SDK module to all the module variants the member
-	// type contributes to the SDK. `names` is the list of module names given in
-	// the member type property (as returned by SdkPropertyName()) in the SDK
-	// module. The exact set of variants required is determined by the SDK and its
-	// properties. The dependencies must be added with the supplied tag.
+	// AddDependencies adds dependencies from the SDK module to all the module variants the member
+	// type contributes to the SDK. `names` is the list of module names given in the member type
+	// property (as returned by SdkPropertyName()) in the SDK module. The exact set of variants
+	// required is determined by the SDK and its properties. The dependencies must be added with the
+	// supplied tag.
 	//
 	// The BottomUpMutatorContext provided is for the SDK module.
 	AddDependencies(ctx SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string)
 
-	// Return true if the supplied module is an instance of this member type.
+	// IsInstance returns true if the supplied module is an instance of this member type.
 	//
-	// This is used to check the type of each variant before added to the
-	// SdkMember. Returning false will cause an error to be logged expaining that
-	// the module is not allowed in whichever sdk property it was added.
+	// This is used to check the type of each variant before added to the SdkMember. Returning false
+	// will cause an error to be logged explaining that the module is not allowed in whichever sdk
+	// property it was added.
 	IsInstance(module Module) bool
 
 	// UsesSourceModuleTypeInSnapshot returns true when the AddPrebuiltModule() method returns a
 	// source module type.
 	UsesSourceModuleTypeInSnapshot() bool
 
-	// Add a prebuilt module that the sdk will populate.
+	// AddPrebuiltModule is called to add a prebuilt module that the sdk will populate.
 	//
 	// The sdk module code generates the snapshot as follows:
 	//
@@ -525,17 +757,32 @@
 	//
 	AddPrebuiltModule(ctx SdkMemberContext, member SdkMember) BpModule
 
-	// Create a structure into which variant specific properties can be added.
+	// CreateVariantPropertiesStruct creates a structure into which variant specific properties can be
+	// added.
 	CreateVariantPropertiesStruct() SdkMemberProperties
+
+	// SupportedTraits returns the set of traits supported by this member type.
+	SupportedTraits() SdkMemberTraitSet
 }
 
+var _ sdkRegisterable = (SdkMemberType)(nil)
+
 // SdkDependencyContext provides access to information needed by the SdkMemberType.AddDependencies()
 // implementations.
 type SdkDependencyContext interface {
 	BottomUpMutatorContext
+
+	// RequiredTraits returns the set of SdkMemberTrait instances that the sdk requires the named
+	// member to provide.
+	RequiredTraits(name string) SdkMemberTraitSet
+
+	// RequiresTrait returns true if the sdk requires the member with the supplied name to provide the
+	// supplied trait.
+	RequiresTrait(name string, trait SdkMemberTrait) bool
 }
 
-// Base type for SdkMemberType implementations.
+// SdkMemberTypeBase is the base type for SdkMemberType implementations and must be embedded in any
+// struct that implements SdkMemberType.
 type SdkMemberTypeBase struct {
 	PropertyName string
 
@@ -550,6 +797,9 @@
 	// module type in its SdkMemberType.AddPrebuiltModule() method. That prevents the sdk snapshot
 	// code from automatically adding a prefer: true flag.
 	UseSourceModuleTypeInSnapshot bool
+
+	// The list of supported traits.
+	Traits []SdkMemberTrait
 }
 
 func (b *SdkMemberTypeBase) SdkPropertyName() string {
@@ -572,63 +822,57 @@
 	return b.UseSourceModuleTypeInSnapshot
 }
 
-// Encapsulates the information about registered SdkMemberTypes.
-type SdkMemberTypesRegistry struct {
-	// The list of types sorted by property name.
-	list []SdkMemberType
+func (b *SdkMemberTypeBase) SupportedTraits() SdkMemberTraitSet {
+	return NewSdkMemberTraitSet(b.Traits)
 }
 
-func (r *SdkMemberTypesRegistry) copyAndAppend(memberType SdkMemberType) *SdkMemberTypesRegistry {
-	oldList := r.list
+// registeredModuleExportsMemberTypes is the set of registered SdkMemberTypes for module_exports
+// modules.
+var registeredModuleExportsMemberTypes = &sdkRegistry{}
 
-	// Copy the slice just in case this is being read while being modified, e.g. when testing.
-	list := make([]SdkMemberType, 0, len(oldList)+1)
-	list = append(list, oldList...)
-	list = append(list, memberType)
+// registeredSdkMemberTypes is the set of registered registeredSdkMemberTypes for sdk modules.
+var registeredSdkMemberTypes = &sdkRegistry{}
 
-	// Sort the member types by their property name to ensure that registry order has no effect
-	// on behavior.
-	sort.Slice(list, func(i1, i2 int) bool {
-		t1 := list[i1]
-		t2 := list[i2]
-
-		return t1.SdkPropertyName() < t2.SdkPropertyName()
-	})
-
-	// Create a new registry so the pointer uniquely identifies the set of registered types.
-	return &SdkMemberTypesRegistry{
-		list: list,
-	}
-}
-
-func (r *SdkMemberTypesRegistry) RegisteredTypes() []SdkMemberType {
-	return r.list
-}
-
-func (r *SdkMemberTypesRegistry) UniqueOnceKey() OnceKey {
-	// Use the pointer to the registry as the unique key.
-	return NewCustomOnceKey(r)
-}
-
-// The set of registered SdkMemberTypes for module_exports modules.
-var ModuleExportsMemberTypes = &SdkMemberTypesRegistry{}
-
-// The set of registered SdkMemberTypes for sdk modules.
-var SdkMemberTypes = &SdkMemberTypesRegistry{}
-
-// Register an SdkMemberType object to allow them to be used in the sdk and sdk_snapshot module
+// RegisteredSdkMemberTypes returns a OnceKey and a sorted list of registered types.
+//
+// If moduleExports is true then the slice of types includes all registered types that can be used
+// with the module_exports and module_exports_snapshot module types. Otherwise, the slice of types
+// only includes those registered types that can be used with the sdk and sdk_snapshot module
 // types.
+//
+// The key uniquely identifies the array of types and can be used with OncePer.Once() to cache
+// information derived from the array of types.
+func RegisteredSdkMemberTypes(moduleExports bool) (OnceKey, []SdkMemberType) {
+	var registry *sdkRegistry
+	if moduleExports {
+		registry = registeredModuleExportsMemberTypes
+	} else {
+		registry = registeredSdkMemberTypes
+	}
+
+	registerables := registry.registeredObjects()
+	types := make([]SdkMemberType, len(registerables))
+	for i, registerable := range registerables {
+		types[i] = registerable.(SdkMemberType)
+	}
+	return registry.uniqueOnceKey(), types
+}
+
+// RegisterSdkMemberType registers an SdkMemberType object to allow them to be used in the
+// module_exports, module_exports_snapshot and (depending on the value returned from
+// SdkMemberType.UsableWithSdkAndSdkSnapshot) the sdk and sdk_snapshot module types.
 func RegisterSdkMemberType(memberType SdkMemberType) {
 	// All member types are usable with module_exports.
-	ModuleExportsMemberTypes = ModuleExportsMemberTypes.copyAndAppend(memberType)
+	registeredModuleExportsMemberTypes = registeredModuleExportsMemberTypes.copyAndAppend(memberType)
 
 	// Only those that explicitly indicate it are usable with sdk.
 	if memberType.UsableWithSdkAndSdkSnapshot() {
-		SdkMemberTypes = SdkMemberTypes.copyAndAppend(memberType)
+		registeredSdkMemberTypes = registeredSdkMemberTypes.copyAndAppend(memberType)
 	}
 }
 
-// Base structure for all implementations of SdkMemberProperties.
+// SdkMemberPropertiesBase is the base structure for all implementations of SdkMemberProperties and
+// must be embedded in any struct that implements SdkMemberProperties.
 //
 // Contains common properties that apply across many different member types.
 type SdkMemberPropertiesBase struct {
@@ -655,7 +899,7 @@
 	Compile_multilib string `android:"arch_variant"`
 }
 
-// The os prefix to use for any file paths in the sdk.
+// OsPrefix returns the os prefix to use for any file paths in the sdk.
 //
 // Is an empty string if the member only provides variants for a single os type, otherwise
 // is the OsType.Name.
@@ -671,39 +915,54 @@
 	return b
 }
 
-// Interface to be implemented on top of a structure that contains variant specific
-// information.
+// SdkMemberProperties is the interface to be implemented on top of a structure that contains
+// variant specific information.
 //
-// Struct fields that are capitalized are examined for common values to extract. Fields
-// that are not capitalized are assumed to be arch specific.
+// Struct fields that are capitalized are examined for common values to extract. Fields that are not
+// capitalized are assumed to be arch specific.
 type SdkMemberProperties interface {
-	// Access the base structure.
+	// Base returns the base structure.
 	Base() *SdkMemberPropertiesBase
 
-	// Populate this structure with information from the variant.
+	// PopulateFromVariant populates this structure with information from a module variant.
+	//
+	// It will typically be called once for each variant of a member module that the SDK depends upon.
 	PopulateFromVariant(ctx SdkMemberContext, variant Module)
 
-	// Add the information from this structure to the property set.
+	// AddToPropertySet adds the information from this structure to the property set.
+	//
+	// This will be called for each instance of this structure on which the PopulateFromVariant method
+	// was called and also on a number of different instances of this structure into which properties
+	// common to one or more variants have been copied. Therefore, implementations of this must handle
+	// the case when this structure is only partially populated.
 	AddToPropertySet(ctx SdkMemberContext, propertySet BpPropertySet)
 }
 
-// Provides access to information common to a specific member.
+// SdkMemberContext provides access to information common to a specific member.
 type SdkMemberContext interface {
 
-	// The module context of the sdk common os variant which is creating the snapshot.
+	// SdkModuleContext returns the module context of the sdk common os variant which is creating the
+	// snapshot.
+	//
+	// This is common to all members of the sdk and is not specific to the member being processed.
+	// If information about the member being processed needs to be obtained from this ModuleContext it
+	// must be obtained using one of the OtherModule... methods not the Module... methods.
 	SdkModuleContext() ModuleContext
 
-	// The builder of the snapshot.
+	// SnapshotBuilder the builder of the snapshot.
 	SnapshotBuilder() SnapshotBuilder
 
-	// The type of the member.
+	// MemberType returns the type of the member currently being processed.
 	MemberType() SdkMemberType
 
-	// The name of the member.
+	// Name returns the name of the member currently being processed.
 	//
 	// Provided for use by sdk members to create a member specific location within the snapshot
 	// into which to copy the prebuilt files.
 	Name() string
+
+	// RequiresTrait returns true if this member is expected to provide the specified trait.
+	RequiresTrait(trait SdkMemberTrait) bool
 }
 
 // ExportedComponentsInfo contains information about the components that this module exports to an
diff --git a/android/sdk_test.go b/android/sdk_test.go
new file mode 100644
index 0000000..51aeb31
--- /dev/null
+++ b/android/sdk_test.go
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 android
+
+import "testing"
+
+type testSdkRegisterable struct {
+	name string
+}
+
+func (t *testSdkRegisterable) SdkPropertyName() string {
+	return t.name
+}
+
+var _ sdkRegisterable = &testSdkRegisterable{}
+
+func TestSdkRegistry(t *testing.T) {
+	alpha := &testSdkRegisterable{"alpha"}
+	beta := &testSdkRegisterable{"beta"}
+	betaDup := &testSdkRegisterable{"beta"}
+
+	// Make sure that an empty registry is empty.
+	emptyRegistry := &sdkRegistry{}
+	AssertDeepEquals(t, "emptyRegistry should be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects())
+
+	// Add beta to the empty registry to create another registry, check that it contains beta and make
+	// sure that it does not affect the creating registry.
+	registry1 := emptyRegistry.copyAndAppend(beta)
+	AssertDeepEquals(t, "emptyRegistry should still be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects())
+	AssertDeepEquals(t, "registry1 should contain beta", []sdkRegisterable{beta}, registry1.registeredObjects())
+
+	// Add alpha to the registry containing beta to create another registry, check that it contains
+	// alpha,beta (in order) and make sure that it does not affect the creating registry.
+	registry2 := registry1.copyAndAppend(alpha)
+	AssertDeepEquals(t, "registry1 should still contain beta", []sdkRegisterable{beta}, registry1.registeredObjects())
+	AssertDeepEquals(t, "registry2 should contain alpha,beta", []sdkRegisterable{alpha, beta}, registry2.registeredObjects())
+
+	AssertPanicMessageContains(t, "duplicate beta should be detected", `"beta" already exists in ["alpha" "beta"]`, func() {
+		registry2.copyAndAppend(betaDup)
+	})
+}
diff --git a/android/sdk_version.go b/android/sdk_version.go
index c6c75a3..2004c92 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -117,7 +117,7 @@
 	return false
 }
 
-// PrebuiltSdkAvailableForUnbundledBuilt tells whether this SdkSpec can have a prebuilt SDK
+// PrebuiltSdkAvailableForUnbundledBuild tells whether this SdkSpec can have a prebuilt SDK
 // that can be used for unbundled builds.
 func (s SdkSpec) PrebuiltSdkAvailableForUnbundledBuild() bool {
 	// "", "none", and "core_platform" are not available for unbundled build
@@ -157,7 +157,7 @@
 		return ctx.Config().AlwaysUsePrebuiltSdks()
 	} else if !s.ApiLevel.IsPreview() {
 		// validation check
-		if s.Kind != SdkPublic && s.Kind != SdkSystem && s.Kind != SdkTest && s.Kind != SdkModule {
+		if s.Kind != SdkPublic && s.Kind != SdkSystem && s.Kind != SdkTest && s.Kind != SdkModule && s.Kind != SdkSystemServer {
 			panic(fmt.Errorf("prebuilt SDK is not not available for SdkKind=%q", s.Kind))
 			return false
 		}
@@ -212,6 +212,10 @@
 )
 
 func SdkSpecFrom(ctx EarlyModuleContext, str string) SdkSpec {
+	return SdkSpecFromWithConfig(ctx.Config(), str)
+}
+
+func SdkSpecFromWithConfig(config Config, str string) SdkSpec {
 	switch str {
 	// special cases first
 	case "":
@@ -252,7 +256,7 @@
 			return SdkSpec{SdkInvalid, NoneApiLevel, str}
 		}
 
-		apiLevel, err := ApiLevelFromUser(ctx, versionString)
+		apiLevel, err := ApiLevelFromUserWithConfig(config, versionString)
 		if err != nil {
 			return SdkSpec{SdkInvalid, apiLevel, str}
 		}
diff --git a/android/sdk_version_test.go b/android/sdk_version_test.go
new file mode 100644
index 0000000..ec81782
--- /dev/null
+++ b/android/sdk_version_test.go
@@ -0,0 +1,89 @@
+// Copyright 2015 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 android
+
+import (
+	"testing"
+)
+
+func TestSdkSpecFrom(t *testing.T) {
+	testCases := []struct {
+		input    string
+		expected string
+	}{
+		{
+			input:    "",
+			expected: "private_current",
+		},
+		{
+			input:    "none",
+			expected: "none_(no version)",
+		},
+		{
+			input:    "core_platform",
+			expected: "core_platform_current",
+		},
+		{
+			input:    "_",
+			expected: "invalid_(no version)",
+		},
+		{
+			input:    "_31",
+			expected: "invalid_(no version)",
+		},
+		{
+			input:    "system_R",
+			expected: "system_30",
+		},
+		{
+			input:    "test_31",
+			expected: "test_31",
+		},
+		{
+			input:    "module_current",
+			expected: "module-lib_current",
+		},
+		{
+			input:    "31",
+			expected: "public_31",
+		},
+		{
+			input:    "S",
+			expected: "public_31",
+		},
+		{
+			input:    "current",
+			expected: "public_current",
+		},
+		{
+			input:    "Tiramisu",
+			expected: "public_Tiramisu",
+		},
+	}
+
+	config := NullConfig("", "")
+
+	config.productVariables = productVariables{
+		Platform_sdk_version:              intPtr(31),
+		Platform_sdk_codename:             stringPtr("Tiramisu"),
+		Platform_version_active_codenames: []string{"Tiramisu"},
+	}
+
+	for _, tc := range testCases {
+		if got := SdkSpecFromWithConfig(config, tc.input).String(); tc.expected != got {
+			t.Errorf("Expected %v, got %v", tc.expected, got)
+		}
+	}
+}
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 17f6d66..91bbce6 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -31,10 +31,10 @@
 )
 
 func init() {
-	RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-	RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-	RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-	RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+	RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+	RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+	RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+	RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 }
 
 type soongConfigModuleTypeImport struct {
@@ -153,7 +153,7 @@
 // Then libacme_foo would build with cflags:
 //   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
 
-func soongConfigModuleTypeImportFactory() Module {
+func SoongConfigModuleTypeImportFactory() Module {
 	module := &soongConfigModuleTypeImport{}
 
 	module.AddProperties(&module.properties)
@@ -179,6 +179,7 @@
 
 type soongConfigModuleTypeModule struct {
 	ModuleBase
+	BazelModuleBase
 	properties soongconfig.ModuleTypeProperties
 }
 
@@ -262,7 +263,7 @@
 //     SOONG_CONFIG_acme_width := 200
 //
 // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
-func soongConfigModuleTypeFactory() Module {
+func SoongConfigModuleTypeFactory() Module {
 	module := &soongConfigModuleTypeModule{}
 
 	module.AddProperties(&module.properties)
@@ -296,7 +297,7 @@
 
 // soong_config_string_variable defines a variable and a set of possible string values for use
 // in a soong_config_module_type definition.
-func soongConfigStringVariableDummyFactory() Module {
+func SoongConfigStringVariableDummyFactory() Module {
 	module := &soongConfigStringVariableDummyModule{}
 	module.AddProperties(&module.properties, &module.stringProperties)
 	initAndroidModuleBase(module)
@@ -305,7 +306,7 @@
 
 // soong_config_string_variable defines a variable with true or false values for use
 // in a soong_config_module_type definition.
-func soongConfigBoolVariableDummyFactory() Module {
+func SoongConfigBoolVariableDummyFactory() Module {
 	module := &soongConfigBoolVariableDummyModule{}
 	module.AddProperties(&module.properties)
 	initAndroidModuleBase(module)
@@ -324,6 +325,9 @@
 func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
 func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
 
+// importModuleTypes registers the module factories for a list of module types defined
+// in an Android.bp file. These module factories are scoped for the current Android.bp
+// file only.
 func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
 	from = filepath.Clean(from)
 	if filepath.Ext(from) != ".bp" {
@@ -376,6 +380,9 @@
 		}
 
 		mtDef, errs := soongconfig.Parse(r, from)
+		if ctx.Config().runningAsBp2Build {
+			ctx.Config().Bp2buildSoongConfigDefinitions.AddVars(*mtDef)
+		}
 
 		if len(errs) > 0 {
 			reportErrors(ctx, from, errs...)
@@ -389,7 +396,7 @@
 		for name, moduleType := range mtDef.ModuleTypes {
 			factory := globalModuleTypes[moduleType.BaseModuleType]
 			if factory != nil {
-				factories[name] = soongConfigModuleFactory(factory, moduleType)
+				factories[name] = configModuleFactory(factory, moduleType, ctx.Config().runningAsBp2Build)
 			} else {
 				reportErrors(ctx, from,
 					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
@@ -404,20 +411,39 @@
 	}).(map[string]blueprint.ModuleFactory)
 }
 
-// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
-// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
-// variables.
-func soongConfigModuleFactory(factory blueprint.ModuleFactory,
-	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
-
+// configModuleFactory takes an existing soongConfigModuleFactory and a
+// ModuleType to create a new ModuleFactory that uses a custom loadhook.
+func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType, bp2build bool) blueprint.ModuleFactory {
 	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
-	if conditionalFactoryProps.IsValid() {
-		return func() (blueprint.Module, []interface{}) {
-			module, props := factory()
+	if !conditionalFactoryProps.IsValid() {
+		return factory
+	}
 
-			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
-			props = append(props, conditionalProps.Interface())
+	return func() (blueprint.Module, []interface{}) {
+		module, props := factory()
+		conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
+		props = append(props, conditionalProps.Interface())
 
+		if bp2build {
+			// The loadhook is different for bp2build, since we don't want to set a specific
+			// set of property values based on a vendor var -- we want __all of them__ to
+			// generate select statements, so we put the entire soong_config_variables
+			// struct, together with the namespace representing those variables, while
+			// creating the custom module with the factory.
+			AddLoadHook(module, func(ctx LoadHookContext) {
+				if m, ok := module.(Bazelable); ok {
+					m.SetBaseModuleType(moduleType.BaseModuleType)
+					// Instead of applying all properties, keep the entire conditionalProps struct as
+					// part of the custom module so dependent modules can create the selects accordingly
+					m.setNamespacedVariableProps(namespacedVariableProperties{
+						moduleType.ConfigNamespace: []interface{}{conditionalProps.Interface()},
+					})
+				}
+			})
+		} else {
+			// Regular Soong operation wraps the existing module factory with a
+			// conditional on Soong config variables by reading the product
+			// config variables from Make.
 			AddLoadHook(module, func(ctx LoadHookContext) {
 				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
 				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
@@ -429,10 +455,7 @@
 					ctx.AppendProperties(ps)
 				}
 			})
-
-			return module, props
 		}
-	} else {
-		return factory
+		return module, props
 	}
 }
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index b2f8eaa..acb9d18 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -60,7 +60,7 @@
 			module_type: "test",
 			config_namespace: "acme",
 			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
-			bool_variables: ["feature2", "unused_feature"],
+			bool_variables: ["feature2", "unused_feature", "always_true"],
 			value_variables: ["size", "unused_size"],
 			properties: ["cflags", "srcs", "defaults"],
 		}
@@ -148,6 +148,11 @@
 			cflags: ["DEFAULT_B"],
 		}
 
+		test_defaults {
+			name: "foo_defaults_always_true",
+			cflags: ["DEFAULT_ALWAYS_TRUE"],
+		}
+
 		acme_test {
 			name: "foo_with_defaults",
 			cflags: ["-DGENERIC"],
@@ -176,6 +181,15 @@
 				FEATURE3: {
 					cflags: ["-DFEATURE3"],
 				},
+				always_true: {
+					defaults: ["foo_defaults_always_true"],
+					conditions_default: {
+						// verify that conditions_default is skipped if the
+						// soong config variable is true by specifying a
+						// non-existent module in conditions_default
+						defaults: ["//nonexistent:defaults"],
+					}
+				},
 			},
 		}
     `
@@ -205,6 +219,7 @@
 						"unused_feature":    "true", // unused
 						"unused_size":       "1",    // unused
 						"unused_string_var": "a",    // unused
+						"always_true":       "true",
 					},
 				}),
 				fooExpectedFlags: []string{
@@ -217,6 +232,7 @@
 				},
 				fooDefaultsExpectedFlags: []string{
 					"DEFAULT_A",
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 					"-DSIZE=42",
@@ -227,7 +243,10 @@
 			{
 				name: "empty_prop_for_string_var",
 				preparer: fixtureForVendorVars(map[string]map[string]string{
-					"acme": {"board": "soc_c"}}),
+					"acme": {
+						"board":       "soc_c",
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -236,6 +255,7 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
@@ -243,7 +263,10 @@
 			{
 				name: "unused_string_var",
 				preparer: fixtureForVendorVars(map[string]map[string]string{
-					"acme": {"board": "soc_d"}}),
+					"acme": {
+						"board":       "soc_d",
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -253,14 +276,18 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
 			},
 
 			{
-				name:     "conditions_default",
-				preparer: fixtureForVendorVars(map[string]map[string]string{}),
+				name: "conditions_default",
+				preparer: fixtureForVendorVars(map[string]map[string]string{
+					"acme": {
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -270,6 +297,7 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
@@ -282,10 +310,10 @@
 					tc.preparer,
 					PrepareForTestWithDefaults,
 					FixtureRegisterWithContext(func(ctx RegistrationContext) {
-						ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-						ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-						ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-						ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+						ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+						ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+						ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+						ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 						ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
 						ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
 					}),
@@ -344,10 +372,10 @@
 		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
 		PrepareForTestWithDefaults,
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-			ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-			ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-			ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+			ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+			ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+			ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+			ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 			ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
 			ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
 		}),
diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp
index e7fa5a0..9bf3344 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "blueprint-parser",
         "blueprint-proptools",
+        "soong-bazel",
     ],
     srcs: [
         "config.go",
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 34b180d..09a5057 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -20,6 +20,7 @@
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/parser"
@@ -28,7 +29,7 @@
 
 const conditionsDefault = "conditions_default"
 
-var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
+var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
 
 // loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
 // result so each file is only parsed once.
@@ -230,6 +231,104 @@
 	variables map[string]soongConfigVariable
 }
 
+// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
+// string vars, bool vars and value vars created by every
+// soong_config_module_type in this build.
+type Bp2BuildSoongConfigDefinitions struct {
+	StringVars map[string]map[string]bool
+	BoolVars   map[string]bool
+	ValueVars  map[string]bool
+}
+
+var bp2buildSoongConfigVarsLock sync.Mutex
+
+// SoongConfigVariablesForBp2build extracts information from a
+// SoongConfigDefinition that bp2build needs to generate constraint settings and
+// values for, in order to migrate soong_config_module_type usages to Bazel.
+func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) {
+	// In bp2build mode, this method is called concurrently in goroutines from
+	// loadhooks while parsing soong_config_module_type, so add a mutex to
+	// prevent concurrent map writes. See b/207572723
+	bp2buildSoongConfigVarsLock.Lock()
+	defer bp2buildSoongConfigVarsLock.Unlock()
+
+	if defs.StringVars == nil {
+		defs.StringVars = make(map[string]map[string]bool)
+	}
+	if defs.BoolVars == nil {
+		defs.BoolVars = make(map[string]bool)
+	}
+	if defs.ValueVars == nil {
+		defs.ValueVars = make(map[string]bool)
+	}
+	for _, moduleType := range mtDef.ModuleTypes {
+		for _, v := range moduleType.Variables {
+			key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
+			if strVar, ok := v.(*stringVariable); ok {
+				if _, ok := defs.StringVars[key]; !ok {
+					defs.StringVars[key] = make(map[string]bool, 0)
+				}
+				for _, value := range strVar.values {
+					defs.StringVars[key][value] = true
+				}
+			} else if _, ok := v.(*boolVariable); ok {
+				defs.BoolVars[key] = true
+			} else if _, ok := v.(*valueVariable); ok {
+				defs.ValueVars[key] = true
+			} else {
+				panic(fmt.Errorf("Unsupported variable type: %+v", v))
+			}
+		}
+	}
+}
+
+// This is a copy of the one available in soong/android/util.go, but depending
+// on the android package causes a cyclic dependency. A refactoring here is to
+// extract common utils out from android/utils.go for other packages like this.
+func sortedStringKeys(m interface{}) []string {
+	v := reflect.ValueOf(m)
+	if v.Kind() != reflect.Map {
+		panic(fmt.Sprintf("%#v is not a map", m))
+	}
+	keys := v.MapKeys()
+	s := make([]string, 0, len(keys))
+	for _, key := range keys {
+		s = append(s, key.String())
+	}
+	sort.Strings(s)
+	return s
+}
+
+// String emits the Soong config variable definitions as Starlark dictionaries.
+func (defs Bp2BuildSoongConfigDefinitions) String() string {
+	ret := ""
+	ret += "soong_config_bool_variables = {\n"
+	for _, boolVar := range sortedStringKeys(defs.BoolVars) {
+		ret += fmt.Sprintf("    \"%s\": True,\n", boolVar)
+	}
+	ret += "}\n"
+	ret += "\n"
+
+	ret += "soong_config_value_variables = {\n"
+	for _, valueVar := range sortedStringKeys(defs.ValueVars) {
+		ret += fmt.Sprintf("    \"%s\": True,\n", valueVar)
+	}
+	ret += "}\n"
+	ret += "\n"
+
+	ret += "soong_config_string_variables = {\n"
+	for _, stringVar := range sortedStringKeys(defs.StringVars) {
+		ret += fmt.Sprintf("    \"%s\": [\n", stringVar)
+		for _, choice := range sortedStringKeys(defs.StringVars[stringVar]) {
+			ret += fmt.Sprintf("        \"%s\",\n", choice)
+		}
+		ret += fmt.Sprintf("    ],\n")
+	}
+	ret += "}"
+
+	return ret
+}
+
 // CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
 // property layout for the Soong config variables, with each possible value an interface{} that
 // contains a nil pointer to another newly constructed type that contains the affectable properties.
@@ -271,12 +370,12 @@
 	}
 
 	typ := reflect.StructOf([]reflect.StructField{{
-		Name: soongConfigProperty,
+		Name: SoongConfigProperty,
 		Type: reflect.StructOf(fields),
 	}})
 
 	props := reflect.New(typ)
-	structConditions := props.Elem().FieldByName(soongConfigProperty)
+	structConditions := props.Elem().FieldByName(SoongConfigProperty)
 
 	for i, c := range moduleType.Variables {
 		c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
@@ -415,7 +514,7 @@
 // soong_config_variables are expected to be in the same order as moduleType.Variables.
 func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
 	var ret []interface{}
-	props = props.Elem().FieldByName(soongConfigProperty)
+	props = props.Elem().FieldByName(SoongConfigProperty)
 	for i, c := range moduleType.Variables {
 		if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
 			return nil, err
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
index 48cdfe7..b14f8b4 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -364,3 +364,117 @@
 		}
 	}
 }
+
+func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) {
+	testCases := []struct {
+		defs     Bp2BuildSoongConfigDefinitions
+		expected string
+	}{
+		{
+			defs: Bp2BuildSoongConfigDefinitions{},
+			expected: `soong_config_bool_variables = {
+}
+
+soong_config_value_variables = {
+}
+
+soong_config_string_variables = {
+}`}, {
+			defs: Bp2BuildSoongConfigDefinitions{
+				BoolVars: map[string]bool{
+					"bool_var": true,
+				},
+			},
+			expected: `soong_config_bool_variables = {
+    "bool_var": True,
+}
+
+soong_config_value_variables = {
+}
+
+soong_config_string_variables = {
+}`}, {
+			defs: Bp2BuildSoongConfigDefinitions{
+				ValueVars: map[string]bool{
+					"value_var": true,
+				},
+			},
+			expected: `soong_config_bool_variables = {
+}
+
+soong_config_value_variables = {
+    "value_var": True,
+}
+
+soong_config_string_variables = {
+}`}, {
+			defs: Bp2BuildSoongConfigDefinitions{
+				StringVars: map[string]map[string]bool{
+					"string_var": map[string]bool{
+						"choice1": true,
+						"choice2": true,
+						"choice3": true,
+					},
+				},
+			},
+			expected: `soong_config_bool_variables = {
+}
+
+soong_config_value_variables = {
+}
+
+soong_config_string_variables = {
+    "string_var": [
+        "choice1",
+        "choice2",
+        "choice3",
+    ],
+}`}, {
+			defs: Bp2BuildSoongConfigDefinitions{
+				BoolVars: map[string]bool{
+					"bool_var_one": true,
+				},
+				ValueVars: map[string]bool{
+					"value_var_one": true,
+					"value_var_two": true,
+				},
+				StringVars: map[string]map[string]bool{
+					"string_var_one": map[string]bool{
+						"choice1": true,
+						"choice2": true,
+						"choice3": true,
+					},
+					"string_var_two": map[string]bool{
+						"foo": true,
+						"bar": true,
+					},
+				},
+			},
+			expected: `soong_config_bool_variables = {
+    "bool_var_one": True,
+}
+
+soong_config_value_variables = {
+    "value_var_one": True,
+    "value_var_two": True,
+}
+
+soong_config_string_variables = {
+    "string_var_one": [
+        "choice1",
+        "choice2",
+        "choice3",
+    ],
+    "string_var_two": [
+        "bar",
+        "foo",
+    ],
+}`},
+	}
+	for _, test := range testCases {
+		actual := test.defs.String()
+		if actual != test.expected {
+			t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual)
+		}
+	}
+}
diff --git a/android/test_suites.go b/android/test_suites.go
index 22f6cf2..55e1da7 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -60,7 +60,7 @@
 	for _, module := range SortedStringKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases", false).ToMakePath()
+	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases", false)
 
 	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
diff --git a/android/testing.go b/android/testing.go
index e25e5c5..8daf6b7 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -208,16 +208,6 @@
 	ctx.config.bp2buildPackageConfig = config
 }
 
-// RegisterBp2BuildMutator registers a BazelTargetModule mutator for converting a module
-// type to the equivalent Bazel target.
-func (ctx *TestContext) RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
-	f := func(ctx RegisterMutatorsContext) {
-		ctx.TopDown(moduleType, m)
-	}
-	ctx.config.bp2buildModuleTypeConfig[moduleType] = true
-	ctx.bp2buildMutators = append(ctx.bp2buildMutators, f)
-}
-
 // PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
 // into Bazel BUILD targets that should run prior to deps and conversion.
 func (ctx *TestContext) PreArchBp2BuildMutators(f RegisterMutatorFunc) {
@@ -458,7 +448,8 @@
 
 // RegisterForBazelConversion prepares a test context for bp2build conversion.
 func (ctx *TestContext) RegisterForBazelConversion() {
-	RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch, ctx.bp2buildMutators)
+	ctx.SetRunningAsBp2build()
+	RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch)
 }
 
 func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
@@ -491,6 +482,66 @@
 	ctx.preSingletons = append(ctx.preSingletons, newPreSingleton(name, factory))
 }
 
+// ModuleVariantForTests selects a specific variant of the module with the given
+// name by matching the variations map against the variations of each module
+// variant. A module variant matches the map if every variation that exists in
+// both have the same value. Both the module and the map are allowed to have
+// extra variations that the other doesn't have. Panics if not exactly one
+// module variant matches.
+func (ctx *TestContext) ModuleVariantForTests(name string, matchVariations map[string]string) TestingModule {
+	modules := []Module{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if ctx.ModuleName(m) == name {
+			am := m.(Module)
+			amMut := am.base().commonProperties.DebugMutators
+			amVar := am.base().commonProperties.DebugVariations
+			matched := true
+			for i, mut := range amMut {
+				if wantedVar, found := matchVariations[mut]; found && amVar[i] != wantedVar {
+					matched = false
+					break
+				}
+			}
+			if matched {
+				modules = append(modules, am)
+			}
+		}
+	})
+
+	if len(modules) == 0 {
+		// Show all the modules or module variants that do exist.
+		var allModuleNames []string
+		var allVariants []string
+		ctx.VisitAllModules(func(m blueprint.Module) {
+			allModuleNames = append(allModuleNames, ctx.ModuleName(m))
+			if ctx.ModuleName(m) == name {
+				allVariants = append(allVariants, m.(Module).String())
+			}
+		})
+
+		if len(allVariants) == 0 {
+			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
+		} else {
+			sort.Strings(allVariants)
+			panic(fmt.Errorf("failed to find module %q matching %v. All variants:\n  %s",
+				name, matchVariations, strings.Join(allVariants, "\n  ")))
+		}
+	}
+
+	if len(modules) > 1 {
+		moduleStrings := []string{}
+		for _, m := range modules {
+			moduleStrings = append(moduleStrings, m.String())
+		}
+		sort.Strings(moduleStrings)
+		panic(fmt.Errorf("module %q has more than one variant that match %v:\n  %s",
+			name, matchVariations, strings.Join(moduleStrings, "\n  ")))
+	}
+
+	return newTestingModule(ctx.config, modules[0])
+}
+
 func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
 	var module Module
 	ctx.VisitAllModules(func(m blueprint.Module) {
@@ -509,12 +560,11 @@
 				allVariants = append(allVariants, ctx.ModuleSubDir(m))
 			}
 		})
-		sort.Strings(allModuleNames)
 		sort.Strings(allVariants)
 
 		if len(allVariants) == 0 {
 			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
-				name, strings.Join(allModuleNames, "\n  ")))
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
 		} else {
 			panic(fmt.Errorf("failed to find module %q variant %q. All variants:\n  %s",
 				name, variant, strings.Join(allVariants, "\n  ")))
@@ -749,7 +799,7 @@
 }
 
 func (b baseTestingComponent) maybeBuildParamsFromOutput(file string) (TestingBuildParams, []string) {
-	var searchedOutputs []string
+	searchedOutputs := WritablePaths(nil)
 	for _, p := range b.provider.BuildParamsForTests() {
 		outputs := append(WritablePaths(nil), p.Outputs...)
 		outputs = append(outputs, p.ImplicitOutputs...)
@@ -760,10 +810,17 @@
 			if f.String() == file || f.Rel() == file || PathRelativeToTop(f) == file {
 				return b.newTestingBuildParams(p), nil
 			}
-			searchedOutputs = append(searchedOutputs, f.Rel())
+			searchedOutputs = append(searchedOutputs, f)
 		}
 	}
-	return TestingBuildParams{}, searchedOutputs
+
+	formattedOutputs := []string{}
+	for _, f := range searchedOutputs {
+		formattedOutputs = append(formattedOutputs,
+			fmt.Sprintf("%s (rel=%s)", PathRelativeToTop(f), f.Rel()))
+	}
+
+	return TestingBuildParams{}, formattedOutputs
 }
 
 func (b baseTestingComponent) buildParamsFromOutput(file string) TestingBuildParams {
diff --git a/android/util.go b/android/util.go
index a0394f6..0ee253e 100644
--- a/android/util.go
+++ b/android/util.go
@@ -65,22 +65,6 @@
 	return buf.String()
 }
 
-// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
-// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
-func SortedIntKeys(m interface{}) []int {
-	v := reflect.ValueOf(m)
-	if v.Kind() != reflect.Map {
-		panic(fmt.Sprintf("%#v is not a map", m))
-	}
-	keys := v.MapKeys()
-	s := make([]int, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, int(key.Int()))
-	}
-	sort.Ints(s)
-	return s
-}
-
 // SorterStringKeys returns the keys of the given string-keyed map in the ascending order
 func SortedStringKeys(m interface{}) []string {
 	v := reflect.ValueOf(m)
@@ -96,21 +80,6 @@
 	return s
 }
 
-// SortedStringMapValues returns the values of the string-values map in the ascending order
-func SortedStringMapValues(m interface{}) []string {
-	v := reflect.ValueOf(m)
-	if v.Kind() != reflect.Map {
-		panic(fmt.Sprintf("%#v is not a map", m))
-	}
-	keys := v.MapKeys()
-	s := make([]string, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, v.MapIndex(key).String())
-	}
-	sort.Strings(s)
-	return s
-}
-
 // IndexList returns the index of the first occurrence of the given string in the list or -1
 func IndexList(s string, list []string) int {
 	for i, l := range list {
diff --git a/android/variable.go b/android/variable.go
index a1af527..bc93835 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -20,6 +20,9 @@
 	"runtime"
 	"strings"
 
+	"android/soong/android/soongconfig"
+	"android/soong/bazel"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -40,6 +43,7 @@
 		Platform_sdk_version struct {
 			Asflags []string
 			Cflags  []string
+			Cmd     *string
 		}
 
 		Platform_sdk_version_or_codename struct {
@@ -50,6 +54,10 @@
 			Cmd *string
 		}
 
+		Platform_version_name struct {
+			Base_dir *string
+		}
+
 		// unbundled_build is a catch-all property to annotate modules that don't build in one or
 		// more unbundled branches, usually due to dependencies missing from the manifest.
 		Unbundled_build struct {
@@ -61,6 +69,8 @@
 			Shared_libs         []string `android:"arch_variant"`
 			Whole_static_libs   []string `android:"arch_variant"`
 			Exclude_static_libs []string `android:"arch_variant"`
+			Srcs                []string `android:"arch_variant"`
+			Header_libs         []string `android:"arch_variant"`
 		} `android:"arch_variant"`
 
 		Malloc_zero_contents struct {
@@ -143,6 +153,7 @@
 		Arc struct {
 			Cflags            []string `android:"arch_variant"`
 			Exclude_srcs      []string `android:"arch_variant"`
+			Header_libs       []string `android:"arch_variant"`
 			Include_dirs      []string `android:"arch_variant"`
 			Shared_libs       []string `android:"arch_variant"`
 			Static_libs       []string `android:"arch_variant"`
@@ -235,30 +246,30 @@
 
 	AppsDefaultVersionName *string `json:",omitempty"`
 
-	Allow_missing_dependencies   *bool `json:",omitempty"`
-	Unbundled_build              *bool `json:",omitempty"`
-	Unbundled_build_apps         *bool `json:",omitempty"`
-	Unbundled_build_image        *bool `json:",omitempty"`
-	Always_use_prebuilt_sdks     *bool `json:",omitempty"`
-	Skip_boot_jars_check         *bool `json:",omitempty"`
-	Malloc_not_svelte            *bool `json:",omitempty"`
-	Malloc_zero_contents         *bool `json:",omitempty"`
-	Malloc_pattern_fill_contents *bool `json:",omitempty"`
-	Safestack                    *bool `json:",omitempty"`
-	HostStaticBinaries           *bool `json:",omitempty"`
-	Binder32bit                  *bool `json:",omitempty"`
-	UseGoma                      *bool `json:",omitempty"`
-	UseRBE                       *bool `json:",omitempty"`
-	UseRBEJAVAC                  *bool `json:",omitempty"`
-	UseRBER8                     *bool `json:",omitempty"`
-	UseRBED8                     *bool `json:",omitempty"`
-	Debuggable                   *bool `json:",omitempty"`
-	Eng                          *bool `json:",omitempty"`
-	Treble_linker_namespaces     *bool `json:",omitempty"`
-	Enforce_vintf_manifest       *bool `json:",omitempty"`
-	Uml                          *bool `json:",omitempty"`
-	Arc                          *bool `json:",omitempty"`
-	MinimizeJavaDebugInfo        *bool `json:",omitempty"`
+	Allow_missing_dependencies   *bool    `json:",omitempty"`
+	Unbundled_build              *bool    `json:",omitempty"`
+	Unbundled_build_apps         []string `json:",omitempty"`
+	Unbundled_build_image        *bool    `json:",omitempty"`
+	Always_use_prebuilt_sdks     *bool    `json:",omitempty"`
+	Skip_boot_jars_check         *bool    `json:",omitempty"`
+	Malloc_not_svelte            *bool    `json:",omitempty"`
+	Malloc_zero_contents         *bool    `json:",omitempty"`
+	Malloc_pattern_fill_contents *bool    `json:",omitempty"`
+	Safestack                    *bool    `json:",omitempty"`
+	HostStaticBinaries           *bool    `json:",omitempty"`
+	Binder32bit                  *bool    `json:",omitempty"`
+	UseGoma                      *bool    `json:",omitempty"`
+	UseRBE                       *bool    `json:",omitempty"`
+	UseRBEJAVAC                  *bool    `json:",omitempty"`
+	UseRBER8                     *bool    `json:",omitempty"`
+	UseRBED8                     *bool    `json:",omitempty"`
+	Debuggable                   *bool    `json:",omitempty"`
+	Eng                          *bool    `json:",omitempty"`
+	Treble_linker_namespaces     *bool    `json:",omitempty"`
+	Enforce_vintf_manifest       *bool    `json:",omitempty"`
+	Uml                          *bool    `json:",omitempty"`
+	Arc                          *bool    `json:",omitempty"`
+	MinimizeJavaDebugInfo        *bool    `json:",omitempty"`
 
 	Check_elf_files *bool `json:",omitempty"`
 
@@ -288,8 +299,6 @@
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
 
-	SamplingPGO *bool `json:",omitempty"`
-
 	JavaCoveragePaths        []string `json:",omitempty"`
 	JavaCoverageExcludePaths []string `json:",omitempty"`
 
@@ -333,16 +342,23 @@
 	VendorSnapshotDirsExcluded   []string `json:",omitempty"`
 	RecoverySnapshotDirsExcluded []string `json:",omitempty"`
 	RecoverySnapshotDirsIncluded []string `json:",omitempty"`
+	HostFakeSnapshotEnabled      bool     `json:",omitempty"`
 
-	BoardVendorSepolicyDirs      []string `json:",omitempty"`
-	BoardOdmSepolicyDirs         []string `json:",omitempty"`
-	BoardReqdMaskPolicy          []string `json:",omitempty"`
-	SystemExtPublicSepolicyDirs  []string `json:",omitempty"`
-	SystemExtPrivateSepolicyDirs []string `json:",omitempty"`
-	BoardSepolicyM4Defs          []string `json:",omitempty"`
+	BoardVendorSepolicyDirs           []string `json:",omitempty"`
+	BoardOdmSepolicyDirs              []string `json:",omitempty"`
+	BoardReqdMaskPolicy               []string `json:",omitempty"`
+	BoardPlatVendorPolicy             []string `json:",omitempty"`
+	BoardSystemExtPublicPrebuiltDirs  []string `json:",omitempty"`
+	BoardSystemExtPrivatePrebuiltDirs []string `json:",omitempty"`
+	BoardProductPublicPrebuiltDirs    []string `json:",omitempty"`
+	BoardProductPrivatePrebuiltDirs   []string `json:",omitempty"`
+	SystemExtPublicSepolicyDirs       []string `json:",omitempty"`
+	SystemExtPrivateSepolicyDirs      []string `json:",omitempty"`
+	BoardSepolicyM4Defs               []string `json:",omitempty"`
 
 	BoardSepolicyVers       *string `json:",omitempty"`
 	PlatformSepolicyVersion *string `json:",omitempty"`
+	TotSepolicyVersion      *string `json:",omitempty"`
 
 	VendorVars map[string]map[string]string `json:",omitempty"`
 
@@ -406,6 +422,11 @@
 	SelinuxIgnoreNeverallows bool `json:",omitempty"`
 
 	SepolicySplit bool `json:",omitempty"`
+
+	SepolicyFreezeTestExtraDirs         []string `json:",omitempty"`
+	SepolicyFreezeTestExtraPrebuiltDirs []string `json:",omitempty"`
+
+	GenerateAidlNdkPlatformBackend bool `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
@@ -472,53 +493,412 @@
 // ProductConfigProperty contains the information for a single property (may be a struct) paired
 // with the appropriate ProductConfigVariable.
 type ProductConfigProperty struct {
-	ProductConfigVariable string
-	FullConfig            string
-	Property              interface{}
+	// The name of the product variable, e.g. "safestack", "malloc_not_svelte",
+	// "board"
+	Name string
+
+	// Namespace of the variable, if this is a soong_config_module_type variable
+	// e.g. "acme", "ANDROID", "vendor_name"
+	Namespace string
+
+	// Unique configuration to identify this product config property (i.e. a
+	// primary key), as just using the product variable name is not sufficient.
+	//
+	// For product variables, this is the product variable name + optional
+	// archvariant information. e.g.
+	//
+	// product_variables: {
+	//     foo: {
+	//         cflags: ["-Dfoo"],
+	//     },
+	// },
+	//
+	// FullConfig would be "foo".
+	//
+	// target: {
+	//     android: {
+	//         product_variables: {
+	//             foo: {
+	//                 cflags: ["-Dfoo-android"],
+	//             },
+	//         },
+	//     },
+	// },
+	//
+	// FullConfig would be "foo-android".
+	//
+	// For soong config variables, this is the namespace + product variable name
+	// + value of the variable, if applicable. The value can also be
+	// conditions_default.
+	//
+	// e.g.
+	//
+	// soong_config_variables: {
+	//     feature1: {
+	//         conditions_default: {
+	//             cflags: ["-DDEFAULT1"],
+	//         },
+	//         cflags: ["-DFEATURE1"],
+	//     },
+	// }
+	//
+	// where feature1 is created in the "acme" namespace, so FullConfig would be
+	// "acme__feature1" and "acme__feature1__conditions_default".
+	//
+	// e.g.
+	//
+	// soong_config_variables: {
+	//     board: {
+	//         soc_a: {
+	//             cflags: ["-DSOC_A"],
+	//         },
+	//         soc_b: {
+	//             cflags: ["-DSOC_B"],
+	//         },
+	//         soc_c: {},
+	//         conditions_default: {
+	//             cflags: ["-DSOC_DEFAULT"]
+	//         },
+	//     },
+	// }
+	//
+	// where board is created in the "acme" namespace, so FullConfig would be
+	// "acme__board__soc_a", "acme__board__soc_b", and
+	// "acme__board__conditions_default"
+	FullConfig string
 }
 
-// ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that
-// all it all product variable-specific versions of a property are easily accessed together
-type ProductConfigProperties map[string]map[string]ProductConfigProperty
+func (p *ProductConfigProperty) AlwaysEmit() bool {
+	return p.Namespace != ""
+}
+
+func (p *ProductConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
+	if p.Namespace == "" {
+		return bazel.ProductVariableConfigurationAxis(p.FullConfig)
+	} else {
+		// Soong config variables can be uniquely identified by the namespace
+		// (e.g. acme, android) and the product variable name (e.g. board, size)
+		return bazel.ProductVariableConfigurationAxis(p.Namespace + "__" + p.Name)
+	}
+}
+
+// SelectKey returns the literal string that represents this variable in a BUILD
+// select statement.
+func (p *ProductConfigProperty) SelectKey() string {
+	if p.Namespace == "" {
+		return strings.ToLower(p.FullConfig)
+	}
+
+	if p.FullConfig == bazel.ConditionsDefaultConfigKey {
+		return bazel.ConditionsDefaultConfigKey
+	}
+
+	value := p.FullConfig
+	if value == p.Name {
+		value = "enabled"
+	}
+	// e.g. acme__feature1__enabled, android__board__soc_a
+	return strings.ToLower(strings.Join([]string{p.Namespace, p.Name, value}, "__"))
+}
+
+// ProductConfigProperties is a map of maps to group property values according
+// their property name and the product config variable they're set under.
+//
+// The outer map key is the name of the property, like "cflags".
+//
+// The inner map key is a ProductConfigProperty, which is a struct of product
+// variable name, namespace, and the "full configuration" of the product
+// variable.
+//
+// e.g. product variable name: board, namespace: acme, full config: vendor_chip_foo
+//
+// The value of the map is the interface{} representing the value of the
+// property, like ["-DDEFINES"] for cflags.
+type ProductConfigProperties map[string]map[ProductConfigProperty]interface{}
 
 // ProductVariableProperties returns a ProductConfigProperties containing only the properties which
 // have been set for the module in the given context.
-func ProductVariableProperties(ctx BaseMutatorContext) ProductConfigProperties {
+func ProductVariableProperties(ctx BazelConversionPathContext) ProductConfigProperties {
 	module := ctx.Module()
 	moduleBase := module.base()
 
 	productConfigProperties := ProductConfigProperties{}
 
-	if moduleBase.variableProperties == nil {
-		return productConfigProperties
-	}
+	if moduleBase.variableProperties != nil {
+		productVariablesProperty := proptools.FieldNameForProperty("product_variables")
+		productVariableValues(
+			productVariablesProperty,
+			moduleBase.variableProperties,
+			"",
+			"",
+			&productConfigProperties)
 
-	productVariableValues(moduleBase.variableProperties, "", &productConfigProperties)
-
-	for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
-		for config, props := range configToProps {
-			// GetArchVariantProperties is creating an instance of the requested type
-			// and productVariablesValues expects an interface, so no need to cast
-			productVariableValues(props, config, &productConfigProperties)
+		for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
+			for config, props := range configToProps {
+				// GetArchVariantProperties is creating an instance of the requested type
+				// and productVariablesValues expects an interface, so no need to cast
+				productVariableValues(
+					productVariablesProperty,
+					props,
+					"",
+					config,
+					&productConfigProperties)
+			}
 		}
 	}
 
+	if m, ok := module.(Bazelable); ok && m.namespacedVariableProps() != nil {
+		for namespace, namespacedVariableProps := range m.namespacedVariableProps() {
+			for _, namespacedVariableProp := range namespacedVariableProps {
+				productVariableValues(
+					soongconfig.SoongConfigProperty,
+					namespacedVariableProp,
+					namespace,
+					"",
+					&productConfigProperties)
+			}
+		}
+	}
+
+	productConfigProperties.zeroValuesForNamespacedVariables()
+
 	return productConfigProperties
 }
 
-func productVariableValues(variableProps interface{}, suffix string, productConfigProperties *ProductConfigProperties) {
-	if suffix != "" {
-		suffix = "-" + suffix
+// zeroValuesForNamespacedVariables ensures that selects that contain __only__
+// conditions default values have zero values set for the other non-default
+// values for that select statement.
+//
+// If the ProductConfigProperties map contains these items, as parsed from the .bp file:
+//
+// library_linking_strategy: {
+//     prefer_static: {
+//         static_libs: [
+//             "lib_a",
+//             "lib_b",
+//         ],
+//     },
+//     conditions_default: {
+//         shared_libs: [
+//             "lib_a",
+//             "lib_b",
+//         ],
+//     },
+// },
+//
+// Static_libs {Library_linking_strategy ANDROID prefer_static} [lib_a lib_b]
+// Shared_libs {Library_linking_strategy ANDROID conditions_default} [lib_a lib_b]
+//
+// We need to add this:
+//
+// Shared_libs {Library_linking_strategy ANDROID prefer_static} []
+//
+// so that the following gets generated for the "dynamic_deps" attribute,
+// instead of putting lib_a and lib_b directly into dynamic_deps without a
+// select:
+//
+// dynamic_deps = select({
+//     "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+//     "//conditions:default": [
+//         "//foo/bar:lib_a",
+//         "//foo/bar:lib_b",
+//     ],
+// }),
+func (props *ProductConfigProperties) zeroValuesForNamespacedVariables() {
+	// A map of product config properties to the zero values of their respective
+	// property value.
+	zeroValues := make(map[ProductConfigProperty]interface{})
+
+	// A map of prop names (e.g. cflags) to product config properties where the
+	// (prop name, ProductConfigProperty) tuple contains a non-conditions_default key.
+	//
+	// e.g.
+	//
+	// prefer_static: {
+	//     static_libs: [
+	//         "lib_a",
+	//         "lib_b",
+	//     ],
+	// },
+	// conditions_default: {
+	//     shared_libs: [
+	//         "lib_a",
+	//         "lib_b",
+	//     ],
+	// },
+	//
+	// The tuple of ("static_libs", prefer_static) would be in this map.
+	hasNonDefaultValue := make(map[string]map[ProductConfigProperty]bool)
+
+	// Iterate over all added soong config variables.
+	for propName, v := range *props {
+		for p, intf := range v {
+			if p.Namespace == "" {
+				// If there's no namespace, this isn't a soong config variable,
+				// i.e. this is a product variable. product variables have no
+				// conditions_defaults, so skip them.
+				continue
+			}
+			if p.FullConfig == bazel.ConditionsDefaultConfigKey {
+				// Skip conditions_defaults.
+				continue
+			}
+			if hasNonDefaultValue[propName] == nil {
+				hasNonDefaultValue[propName] = make(map[ProductConfigProperty]bool)
+				hasNonDefaultValue[propName][p] = false
+			}
+			// Create the zero value of the variable.
+			if _, exists := zeroValues[p]; !exists {
+				zeroValue := reflect.Zero(reflect.ValueOf(intf).Type()).Interface()
+				if zeroValue == nil {
+					panic(fmt.Errorf("Expected non-nil zero value for product/config variable %+v\n", intf))
+				}
+				zeroValues[p] = zeroValue
+			}
+			hasNonDefaultValue[propName][p] = true
+		}
 	}
-	variableValues := reflect.ValueOf(variableProps).Elem().FieldByName("Product_variables")
+
+	for propName := range *props {
+		for p, zeroValue := range zeroValues {
+			// Ignore variables that already have a non-default value for that axis
+			if exists, _ := hasNonDefaultValue[propName][p]; !exists {
+				// fmt.Println(propName, p.Namespace, p.Name, p.FullConfig, zeroValue)
+				// Insert the zero value for this propname + product config value.
+				props.AddProductConfigProperty(
+					propName,
+					p.Namespace,
+					p.Name,
+					p.FullConfig,
+					zeroValue,
+				)
+			}
+		}
+	}
+}
+
+func (p *ProductConfigProperties) AddProductConfigProperty(
+	propertyName, namespace, productVariableName, config string, property interface{}) {
+	if (*p)[propertyName] == nil {
+		(*p)[propertyName] = make(map[ProductConfigProperty]interface{})
+	}
+
+	productConfigProp := ProductConfigProperty{
+		Namespace:  namespace,           // e.g. acme, android
+		Name:       productVariableName, // e.g. size, feature1, feature2, FEATURE3, board
+		FullConfig: config,              // e.g. size, feature1-x86, size__conditions_default
+	}
+
+	if existing, ok := (*p)[propertyName][productConfigProp]; ok && namespace != "" {
+		switch dst := existing.(type) {
+		case []string:
+			if src, ok := property.([]string); ok {
+				dst = append(dst, src...)
+				(*p)[propertyName][productConfigProp] = dst
+			}
+		default:
+			panic(fmt.Errorf("TODO: handle merging value %s", existing))
+		}
+	} else {
+		(*p)[propertyName][productConfigProp] = property
+	}
+}
+
+var (
+	conditionsDefaultField string = proptools.FieldNameForProperty(bazel.ConditionsDefaultConfigKey)
+)
+
+// maybeExtractConfigVarProp attempts to read this value as a config var struct
+// wrapped by interfaces and ptrs. If it's not the right type, the second return
+// value is false.
+func maybeExtractConfigVarProp(v reflect.Value) (reflect.Value, bool) {
+	if v.Kind() == reflect.Interface {
+		// The conditions_default value can be either
+		// 1) an ptr to an interface of a struct (bool config variables and product variables)
+		// 2) an interface of 1) (config variables with nested structs, like string vars)
+		v = v.Elem()
+	}
+	if v.Kind() != reflect.Ptr {
+		return v, false
+	}
+	v = reflect.Indirect(v)
+	if v.Kind() == reflect.Interface {
+		// Extract the struct from the interface
+		v = v.Elem()
+	}
+
+	if !v.IsValid() {
+		return v, false
+	}
+
+	if v.Kind() != reflect.Struct {
+		return v, false
+	}
+	return v, true
+}
+
+func (productConfigProperties *ProductConfigProperties) AddProductConfigProperties(namespace, suffix string, variableValues reflect.Value) {
+	// variableValues can either be a product_variables or
+	// soong_config_variables struct.
+	//
+	// Example of product_variables:
+	//
+	// product_variables: {
+	//     malloc_not_svelte: {
+	//         shared_libs: ["malloc_not_svelte_shared_lib"],
+	//         whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
+	//         exclude_static_libs: [
+	//             "malloc_not_svelte_static_lib_excludes",
+	//             "malloc_not_svelte_whole_static_lib_excludes",
+	//         ],
+	//     },
+	// },
+	//
+	// Example of soong_config_variables:
+	//
+	// soong_config_variables: {
+	//      feature1: {
+	//        	conditions_default: {
+	//               ...
+	//          },
+	//          cflags: ...
+	//      },
+	//      feature2: {
+	//          cflags: ...
+	//        	conditions_default: {
+	//               ...
+	//          },
+	//      },
+	//      board: {
+	//         soc_a: {
+	//             ...
+	//         },
+	//         soc_a: {
+	//             ...
+	//         },
+	//         soc_c: {},
+	//         conditions_default: {
+	//              ...
+	//         },
+	//      },
+	// }
 	for i := 0; i < variableValues.NumField(); i++ {
+		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
+		productVariableName := variableValues.Type().Field(i).Name
+
 		variableValue := variableValues.Field(i)
 		// Check if any properties were set for the module
 		if variableValue.IsZero() {
+			// e.g. feature1: {}, malloc_not_svelte: {}
 			continue
 		}
-		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
-		productVariableName := variableValues.Type().Field(i).Name
+
+		// Unlike product variables, config variables require a few more
+		// indirections to extract the struct from the reflect.Value.
+		if v, ok := maybeExtractConfigVarProp(variableValue); ok {
+			variableValue = v
+		}
+
 		for j := 0; j < variableValue.NumField(); j++ {
 			property := variableValue.Field(j)
 			// If the property wasn't set, no need to pass it along
@@ -528,19 +908,74 @@
 
 			// e.g. Asflags, Cflags, Enabled, etc.
 			propertyName := variableValue.Type().Field(j).Name
-			if (*productConfigProperties)[propertyName] == nil {
-				(*productConfigProperties)[propertyName] = make(map[string]ProductConfigProperty)
-			}
-			config := productVariableName + suffix
-			(*productConfigProperties)[propertyName][config] = ProductConfigProperty{
-				ProductConfigVariable: productVariableName,
-				FullConfig:            config,
-				Property:              property.Interface(),
+
+			if v, ok := maybeExtractConfigVarProp(property); ok {
+				// The field is a struct, which is used by:
+				// 1) soong_config_string_variables
+				//
+				// soc_a: {
+				//     cflags: ...,
+				// }
+				//
+				// soc_b: {
+				//     cflags: ...,
+				// }
+				//
+				// 2) conditions_default structs for all soong config variable types.
+				//
+				// conditions_default: {
+				//     cflags: ...,
+				//     static_libs: ...
+				// }
+				field := v
+				for k := 0; k < field.NumField(); k++ {
+					// Iterate over fields of this struct prop.
+					if field.Field(k).IsZero() {
+						continue
+					}
+					// config can also be "conditions_default".
+					config := proptools.PropertyNameForField(propertyName)
+					actualPropertyName := field.Type().Field(k).Name
+
+					productConfigProperties.AddProductConfigProperty(
+						actualPropertyName,  // e.g. cflags, static_libs
+						namespace,           // e.g. acme, android
+						productVariableName, // e.g. size, feature1, FEATURE2, board
+						config,
+						field.Field(k).Interface(), // e.g. ["-DDEFAULT"], ["foo", "bar"]
+					)
+				}
+			} else if property.Kind() != reflect.Interface {
+				// If not an interface, then this is not a conditions_default or
+				// a struct prop. That is, this is a regular product variable,
+				// or a bool/value config variable.
+				config := productVariableName + suffix
+				productConfigProperties.AddProductConfigProperty(
+					propertyName,
+					namespace,
+					productVariableName,
+					config,
+					property.Interface(),
+				)
 			}
 		}
 	}
 }
 
+// productVariableValues uses reflection to convert a property struct for
+// product_variables and soong_config_variables to structs that can be generated
+// as select statements.
+func productVariableValues(
+	fieldName string, variableProps interface{}, namespace, suffix string, productConfigProperties *ProductConfigProperties) {
+	if suffix != "" {
+		suffix = "-" + suffix
+	}
+
+	// variableValues represent the product_variables or soong_config_variables struct.
+	variableValues := reflect.ValueOf(variableProps).Elem().FieldByName(fieldName)
+	productConfigProperties.AddProductConfigProperties(namespace, suffix, variableValues)
+}
+
 func VariableMutator(mctx BottomUpMutatorContext) {
 	var module Module
 	var ok bool
diff --git a/android/writedocs.go b/android/writedocs.go
deleted file mode 100644
index c380a3d..0000000
--- a/android/writedocs.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2015 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 android
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/google/blueprint"
-)
-
-func init() {
-	RegisterSingletonType("writedocs", DocsSingleton)
-}
-
-func DocsSingleton() Singleton {
-	return &docsSingleton{}
-}
-
-type docsSingleton struct{}
-
-func primaryBuilderPath(ctx SingletonContext) Path {
-	soongOutDir := absolutePath(ctx.Config().SoongOutDir())
-	binary := absolutePath(os.Args[0])
-	primaryBuilder, err := filepath.Rel(soongOutDir, binary)
-	if err != nil {
-		ctx.Errorf("path to primary builder %q is not in build dir %q (%q)",
-			os.Args[0], ctx.Config().SoongOutDir(), err)
-	}
-
-	return PathForOutput(ctx, primaryBuilder)
-}
-
-func (c *docsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	var deps Paths
-	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().moduleListFile))
-	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().ProductVariablesFileName))
-
-	// The dexpreopt configuration may not exist, but if it does, it's a dependency
-	// of soong_build.
-	dexpreoptConfigPath := ctx.Config().DexpreoptGlobalConfigPath(ctx)
-	if dexpreoptConfigPath.Valid() {
-		deps = append(deps, dexpreoptConfigPath.Path())
-	}
-
-	// Generate build system docs for the primary builder.  Generating docs reads the source
-	// files used to build the primary builder, but that dependency will be picked up through
-	// the dependency on the primary builder itself.  There are no dependencies on the
-	// Blueprints files, as any relevant changes to the Blueprints files would have caused
-	// a rebuild of the primary builder.
-	docsFile := PathForOutput(ctx, "docs", "soong_build.html")
-	primaryBuilder := primaryBuilderPath(ctx)
-	soongDocs := ctx.Rule(pctx, "soongDocs",
-		blueprint.RuleParams{
-			Command: fmt.Sprintf("rm -f ${outDir}/* && %s --soong_docs %s %s",
-				primaryBuilder.String(),
-				docsFile.String(),
-				"\""+strings.Join(os.Args[1:], "\" \"")+"\""),
-			CommandDeps: []string{primaryBuilder.String()},
-			Description: fmt.Sprintf("%s docs $out", primaryBuilder.Base()),
-		},
-		"outDir")
-
-	ctx.Build(pctx, BuildParams{
-		Rule:   soongDocs,
-		Output: docsFile,
-		Inputs: deps,
-		Args: map[string]string{
-			"outDir": PathForOutput(ctx, "docs").String(),
-		},
-	})
-
-	// Add a phony target for building the documentation
-	ctx.Phony("soong_docs", docsFile)
-}
diff --git a/android_sdk/Android.bp b/android_sdk/Android.bp
new file mode 100644
index 0000000..e686d59
--- /dev/null
+++ b/android_sdk/Android.bp
@@ -0,0 +1,22 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-android-sdk",
+    pkgPath: "android/soong/android_sdk",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+        "soong-cc",
+        "soong-cc-config",
+    ],
+    srcs: [
+        "sdk_repo_host.go",
+    ],
+    testSrcs: [
+        "sdk_repo_host_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/android_sdk/sdk_repo_host.go b/android_sdk/sdk_repo_host.go
new file mode 100644
index 0000000..d64eb7a
--- /dev/null
+++ b/android_sdk/sdk_repo_host.go
@@ -0,0 +1,294 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 android_sdk
+
+import (
+	"fmt"
+	"io"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/cc/config"
+)
+
+var pctx = android.NewPackageContext("android/soong/android_sdk")
+
+func init() {
+	registerBuildComponents(android.InitRegistrationContext)
+}
+
+func registerBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_sdk_repo_host", SdkRepoHostFactory)
+}
+
+type sdkRepoHost struct {
+	android.ModuleBase
+	android.PackagingBase
+
+	properties sdkRepoHostProperties
+
+	outputBaseName string
+	outputFile     android.OptionalPath
+}
+
+type remapProperties struct {
+	From string
+	To   string
+}
+
+type sdkRepoHostProperties struct {
+	// The top level directory to use for the SDK repo.
+	Base_dir *string
+
+	// List of src:dst mappings to rename files from `deps`.
+	Deps_remap []remapProperties `android:"arch_variant"`
+
+	// List of zip files to merge into the SDK repo.
+	Merge_zips []string `android:"arch_variant,path"`
+
+	// List of sources to include into the SDK repo. These are usually raw files, filegroups,
+	// or genrules, as most built modules should be referenced via `deps`.
+	Srcs []string `android:"arch_variant,path"`
+
+	// List of files to strip. This should be a list of files, not modules. This happens after
+	// `deps_remap` and `merge_zips` are applied, but before the `base_dir` is added.
+	Strip_files []string `android:"arch_variant"`
+}
+
+// android_sdk_repo_host defines an Android SDK repo containing host tools.
+//
+// This implementation is trying to be a faithful reproduction of how these sdk-repos were produced
+// in the Make system, which may explain some of the oddities (like `strip_files` not being
+// automatic)
+func SdkRepoHostFactory() android.Module {
+	return newSdkRepoHostModule()
+}
+
+func newSdkRepoHostModule() *sdkRepoHost {
+	s := &sdkRepoHost{}
+	s.AddProperties(&s.properties)
+	android.InitPackageModule(s)
+	android.InitAndroidMultiTargetsArchModule(s, android.HostSupported, android.MultilibCommon)
+	return s
+}
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.PackagingItemAlwaysDepTag
+}
+
+// TODO(b/201696252): Evaluate whether licenses should be propagated through this dependency.
+func (d dependencyTag) PropagateLicenses() bool {
+	return false
+}
+
+var depTag = dependencyTag{}
+
+func (s *sdkRepoHost) DepsMutator(ctx android.BottomUpMutatorContext) {
+	s.AddDeps(ctx, depTag)
+}
+
+func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	dir := android.PathForModuleOut(ctx, "zip")
+	builder := android.NewRuleBuilder(pctx, ctx).
+		Sbox(dir, android.PathForModuleOut(ctx, "out.sbox.textproto")).
+		SandboxInputs()
+
+	// Get files from modules listed in `deps`
+	packageSpecs := s.GatherPackagingSpecs(ctx)
+
+	// Handle `deps_remap` renames
+	err := remapPackageSpecs(packageSpecs, s.properties.Deps_remap)
+	if err != nil {
+		ctx.PropertyErrorf("deps_remap", "%s", err.Error())
+	}
+
+	s.CopySpecsToDir(ctx, builder, packageSpecs, dir)
+
+	// Collect licenses to write into NOTICE.txt
+	noticeMap := map[string]android.Paths{}
+	for path, pkgSpec := range packageSpecs {
+		licenseFiles := pkgSpec.EffectiveLicenseFiles()
+		if len(licenseFiles) > 0 {
+			noticeMap[path] = pkgSpec.EffectiveLicenseFiles()
+		}
+	}
+	notices := android.BuildNotices(ctx, noticeMap)
+	builder.Command().Text("cp").
+		Input(notices.TxtOutput.Path()).
+		Text(filepath.Join(dir.String(), "NOTICE.txt"))
+
+	// Handle `merge_zips` by extracting their contents into our tmpdir
+	for _, zip := range android.PathsForModuleSrc(ctx, s.properties.Merge_zips) {
+		builder.Command().
+			Text("unzip").
+			Flag("-DD").
+			Flag("-q").
+			FlagWithArg("-d ", dir.String()).
+			Input(zip)
+	}
+
+	// Copy files from `srcs` into our tmpdir
+	for _, src := range android.PathsForModuleSrc(ctx, s.properties.Srcs) {
+		builder.Command().
+			Text("cp").Input(src).Flag(dir.Join(ctx, src.Rel()).String())
+	}
+
+	// Handle `strip_files` by calling the necessary strip commands
+	//
+	// Note: this stripping logic was copied over from the old Make implementation
+	// It's not using the same flags as the regular stripping support, nor does it
+	// support the array of per-module stripping options. It would be nice if we
+	// pulled the stripped versions from the CC modules, but that doesn't exist
+	// for host tools today. (And not all the things we strip are CC modules today)
+	if ctx.Darwin() {
+		macStrip := config.MacStripPath(ctx)
+		for _, strip := range s.properties.Strip_files {
+			builder.Command().
+				Text(macStrip).Flag("-x").
+				Flag(dir.Join(ctx, strip).String())
+		}
+	} else {
+		llvmStrip := config.ClangPath(ctx, "bin/llvm-strip")
+		llvmLib := config.ClangPath(ctx, "lib64/libc++.so.1")
+		for _, strip := range s.properties.Strip_files {
+			cmd := builder.Command().Tool(llvmStrip).ImplicitTool(llvmLib)
+			if !ctx.Windows() {
+				cmd.Flag("-x")
+			}
+			cmd.Flag(dir.Join(ctx, strip).String())
+		}
+	}
+
+	// Fix up the line endings of all text files. This also removes executable permissions.
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("-name '*.aidl' -o -name '*.css' -o -name '*.html' -o -name '*.java'").
+		Flag("-o -name '*.js' -o -name '*.prop' -o -name '*.template'").
+		Flag("-o -name '*.txt' -o -name '*.windows' -o -name '*.xml' -print0").
+		// Using -n 500 for xargs to limit the max number of arguments per call to line_endings
+		// to 500. This avoids line_endings failing with "arguments too long".
+		Text("| xargs -0 -n 500 ").
+		BuiltTool("line_endings").
+		Flag("unix")
+
+	// Exclude some file types (roughly matching sdk.exclude.atree)
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("'('").
+		Flag("-name '.*' -o -name '*~' -o -name 'Makefile' -o -name 'Android.mk' -o").
+		Flag("-name '.*.swp' -o -name '.DS_Store' -o -name '*.pyc' -o -name 'OWNERS' -o").
+		Flag("-name 'MODULE_LICENSE_*' -o -name '*.ezt' -o -name 'Android.bp'").
+		Flag("')' -print0").
+		Text("| xargs -0 -r rm -rf")
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("-name '_*' ! -name '__*' -print0").
+		Text("| xargs -0 -r rm -rf")
+
+	if ctx.Windows() {
+		// Fix EOL chars to make window users happy
+		builder.Command().
+			Text("find").
+			Flag(dir.String()).
+			Flag("-maxdepth 2 -name '*.bat' -type f -print0").
+			Text("| xargs -0 -r unix2dos")
+	}
+
+	// Zip up our temporary directory as the sdk-repo
+	outputZipFile := dir.Join(ctx, "output.zip")
+	builder.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", outputZipFile).
+		FlagWithArg("-P ", proptools.StringDefault(s.properties.Base_dir, ".")).
+		FlagWithArg("-C ", dir.String()).
+		FlagWithArg("-D ", dir.String())
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+
+	builder.Build("build_sdk_repo", "Creating sdk-repo-"+s.BaseModuleName())
+
+	osName := ctx.Os().String()
+	if osName == "linux_glibc" {
+		osName = "linux"
+	}
+	name := fmt.Sprintf("sdk-repo-%s-%s", osName, s.BaseModuleName())
+
+	s.outputBaseName = name
+	s.outputFile = android.OptionalPathForPath(outputZipFile)
+	ctx.InstallFile(android.PathForModuleInstall(ctx, "sdk-repo"), name+".zip", outputZipFile)
+}
+
+func (s *sdkRepoHost) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			fmt.Fprintln(w, ".PHONY:", name, "sdk_repo", "sdk-repo-"+name)
+			fmt.Fprintln(w, "sdk_repo", "sdk-repo-"+name+":", strings.Join(s.FilesToInstall().Strings(), " "))
+
+			fmt.Fprintf(w, "$(call dist-for-goals,sdk_repo sdk-repo-%s,%s:%s-$(FILE_NAME_TAG).zip)\n\n", s.BaseModuleName(), s.outputFile.String(), s.outputBaseName)
+		},
+	}
+}
+
+func remapPackageSpecs(specs map[string]android.PackagingSpec, remaps []remapProperties) error {
+	for _, remap := range remaps {
+		for path, spec := range specs {
+			if match, err := pathtools.Match(remap.From, path); err != nil {
+				return fmt.Errorf("Error parsing %q: %v", remap.From, err)
+			} else if match {
+				newPath := remap.To
+				if pathtools.IsGlob(remap.From) {
+					rel, err := filepath.Rel(constantPartOfPattern(remap.From), path)
+					if err != nil {
+						return fmt.Errorf("Error handling %q", path)
+					}
+					newPath = filepath.Join(remap.To, rel)
+				}
+				delete(specs, path)
+				spec.SetRelPathInPackage(newPath)
+				specs[newPath] = spec
+			}
+		}
+	}
+	return nil
+}
+
+func constantPartOfPattern(pattern string) string {
+	ret := ""
+	for pattern != "" {
+		var first string
+		first, pattern = splitFirst(pattern)
+		if pathtools.IsGlob(first) {
+			return ret
+		}
+		ret = filepath.Join(ret, first)
+	}
+	return ret
+}
+
+func splitFirst(path string) (string, string) {
+	i := strings.IndexRune(path, filepath.Separator)
+	if i < 0 {
+		return path, ""
+	}
+	return path[:i], path[i+1:]
+}
diff --git a/android_sdk/sdk_repo_host_test.go b/android_sdk/sdk_repo_host_test.go
new file mode 100644
index 0000000..0688921
--- /dev/null
+++ b/android_sdk/sdk_repo_host_test.go
@@ -0,0 +1,124 @@
+// Copyright 2021 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 android_sdk
+
+import (
+	"fmt"
+	"runtime"
+	"sort"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+
+	"github.com/google/blueprint/pathtools"
+)
+
+var fixture = android.GroupFixturePreparers(
+	android.PrepareForIntegrationTestWithAndroid,
+	cc.PrepareForIntegrationTestWithCc,
+	android.FixtureRegisterWithContext(registerBuildComponents),
+)
+
+func TestSdkRepoHostDeps(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skipf("Skipping sdk_repo_host testing that is only supported on linux not %s", runtime.GOOS)
+	}
+
+	result := fixture.RunTestWithBp(t, `
+		android_sdk_repo_host {
+			name: "platform-tools",
+		}
+	`)
+
+	// produces "sdk-repo-{OS}-platform-tools.zip"
+	result.ModuleForTests("platform-tools", "linux_glibc_common").Output("sdk-repo-linux-platform-tools.zip")
+}
+
+func TestRemapPackageSpecs(t *testing.T) {
+	testcases := []struct {
+		name   string
+		input  []string
+		remaps []remapProperties
+		output []string
+		err    string
+	}{
+		{
+			name:  "basic remap",
+			input: []string{"a", "c"},
+			remaps: []remapProperties{
+				{From: "a", To: "b"},
+			},
+			output: []string{"b", "c"},
+		},
+		{
+			name:  "non-matching remap",
+			input: []string{"a"},
+			remaps: []remapProperties{
+				{From: "b", To: "c"},
+			},
+			output: []string{"a"},
+		},
+		{
+			name:  "glob",
+			input: []string{"bin/d", "liba.so", "libb.so", "lib/c.so"},
+			remaps: []remapProperties{
+				{From: "lib*.so", To: "lib/"},
+			},
+			output: []string{"bin/d", "lib/c.so", "lib/liba.so", "lib/libb.so"},
+		},
+		{
+			name:  "bad glob",
+			input: []string{"a"},
+			remaps: []remapProperties{
+				{From: "**", To: "./"},
+			},
+			err: fmt.Sprintf("Error parsing \"**\": %v", pathtools.GlobLastRecursiveErr.Error()),
+		},
+		{
+			name:  "globbed dirs",
+			input: []string{"a/b/c"},
+			remaps: []remapProperties{
+				{From: "a/*/c", To: "./"},
+			},
+			output: []string{"b/c"},
+		},
+	}
+
+	for _, test := range testcases {
+		t.Run(test.name, func(t *testing.T) {
+			specs := map[string]android.PackagingSpec{}
+			for _, input := range test.input {
+				spec := android.PackagingSpec{}
+				spec.SetRelPathInPackage(input)
+				specs[input] = spec
+			}
+
+			err := remapPackageSpecs(specs, test.remaps)
+
+			if test.err != "" {
+				android.AssertErrorMessageEquals(t, "", test.err, err)
+			} else {
+				outputs := []string{}
+				for path, spec := range specs {
+					android.AssertStringEquals(t, "path does not match rel path", path, spec.RelPathInPackage())
+					outputs = append(outputs, path)
+				}
+				sort.Strings(outputs)
+				android.AssertArrayString(t, "outputs mismatch", test.output, outputs)
+			}
+		})
+	}
+}
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 08616a9..ae52688 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -15,11 +15,12 @@
 package androidmk
 
 import (
-	mkparser "android/soong/androidmk/parser"
 	"fmt"
 	"sort"
 	"strings"
 
+	mkparser "android/soong/androidmk/parser"
+
 	bpparser "github.com/google/blueprint/parser"
 )
 
@@ -57,6 +58,7 @@
 	"LOCAL_MODULE_STEM":                    stem,
 	"LOCAL_MODULE_HOST_OS":                 hostOs,
 	"LOCAL_RESOURCE_DIR":                   localizePathList("resource_dirs"),
+	"LOCAL_NOTICE_FILE":                    localizePathList("android_license_files"),
 	"LOCAL_SANITIZE":                       sanitize(""),
 	"LOCAL_SANITIZE_DIAG":                  sanitize("diag."),
 	"LOCAL_STRIP_MODULE":                   strip(),
@@ -110,7 +112,6 @@
 			"LOCAL_PROTOC_OPTIMIZE_TYPE":    "proto.type",
 			"LOCAL_MODULE_OWNER":            "owner",
 			"LOCAL_RENDERSCRIPT_TARGET_API": "renderscript.target_api",
-			"LOCAL_NOTICE_FILE":             "notice",
 			"LOCAL_JAVA_LANGUAGE_VERSION":   "java_version",
 			"LOCAL_INSTRUMENTATION_FOR":     "instrumentation_for",
 			"LOCAL_MANIFEST_FILE":           "manifest",
@@ -128,6 +129,8 @@
 			"LOCAL_STATIC_LIBRARIES":              "static_libs",
 			"LOCAL_WHOLE_STATIC_LIBRARIES":        "whole_static_libs",
 			"LOCAL_SYSTEM_SHARED_LIBRARIES":       "system_shared_libs",
+			"LOCAL_USES_LIBRARIES":                "uses_libs",
+			"LOCAL_OPTIONAL_USES_LIBRARIES":       "optional_uses_libs",
 			"LOCAL_ASFLAGS":                       "asflags",
 			"LOCAL_CLANG_ASFLAGS":                 "clang_asflags",
 			"LOCAL_COMPATIBILITY_SUPPORT_FILES":   "data",
@@ -182,6 +185,12 @@
 			"LOCAL_JACK_COVERAGE_EXCLUDE_FILTER": "jacoco.exclude_filter",
 
 			"LOCAL_FULL_LIBS_MANIFEST_FILES": "additional_manifests",
+
+			// will be rewrite later to "license_kinds:" by byfix
+			"LOCAL_LICENSE_KINDS": "android_license_kinds",
+			// will be removed later by byfix
+			// TODO: does this property matter in the license module?
+			"LOCAL_LICENSE_CONDITIONS": "android_license_conditions",
 		})
 
 	addStandardProperties(bpparser.BoolType,
@@ -201,6 +210,7 @@
 			"LOCAL_VENDOR_MODULE":              "vendor",
 			"LOCAL_ODM_MODULE":                 "device_specific",
 			"LOCAL_PRODUCT_MODULE":             "product_specific",
+			"LOCAL_PRODUCT_SERVICES_MODULE":    "product_specific",
 			"LOCAL_SYSTEM_EXT_MODULE":          "system_ext_specific",
 			"LOCAL_EXPORT_PACKAGE_RESOURCES":   "export_package_resources",
 			"LOCAL_PRIVILEGED_MODULE":          "privileged",
@@ -219,6 +229,8 @@
 			"LOCAL_IS_UNIT_TEST": "unit_test",
 
 			"LOCAL_ENFORCE_USES_LIBRARIES": "enforce_uses_libs",
+
+			"LOCAL_CHECK_ELF_FILES": "check_elf_files",
 		})
 }
 
@@ -635,6 +647,12 @@
 	if len(val.Variables) == 1 && varLiteralName(val.Variables[0]) != "" && len(val.Strings) == 2 && val.Strings[0] == "" {
 		fixed = val.Strings[1]
 		varname = val.Variables[0].Name.Strings[0]
+		// TARGET_OUT_OPTIONAL_EXECUTABLES puts the artifact in xbin, which is
+		// deprecated. TARGET_OUT_DATA_APPS install location will be handled
+		// automatically by Soong
+		if varname == "TARGET_OUT_OPTIONAL_EXECUTABLES" || varname == "TARGET_OUT_DATA_APPS" {
+			return nil
+		}
 	} else if len(val.Variables) == 2 && varLiteralName(val.Variables[0]) == "PRODUCT_OUT" && varLiteralName(val.Variables[1]) == "TARGET_COPY_OUT_VENDOR" &&
 		len(val.Strings) == 3 && val.Strings[0] == "" && val.Strings[1] == "/" {
 		fixed = val.Strings[2]
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 02ab89d..ea53705 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -1479,6 +1479,112 @@
 }
 `,
 	},
+	{
+		desc: "LOCAL_USES_LIBRARIES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_USES_LIBRARIES := foo.test bar.test baz.test
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+    name: "foo",
+    uses_libs: [
+        "foo.test",
+        "bar.test",
+        "baz.test",
+    ],
+}
+`,
+	},
+	{
+		desc: "LOCAL_OPTIONAL_USES_LIBRARIES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_OPTIONAL_USES_LIBRARIES := foo.test bar.test baz.test
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+    name: "foo",
+    optional_uses_libs: [
+        "foo.test",
+        "bar.test",
+        "baz.test",
+    ],
+}
+`,
+	},
+	{
+		desc: "Obsolete LOCAL_MODULE_PATH",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_CTS_TEST_PACKAGE := bar
+LOCAL_USE_AAPT2 := blah
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+  name: "foo",
+
+}
+`,
+	},
+	{
+		desc: "LOCAL_LICENSE_KINDS, LOCAL_LICENSE_CONDITIONS, LOCAL_NOTICE_FILE",
+		// When "android_license_files" is valid, the test requires an Android.mk file
+		// outside the current (and an Android.bp file is required as well if the license
+		// files locates directory), thus a mock file system is needed. The integration
+		// test cases for these scenarios have been added in
+		// $(ANDROID_BUILD_TOP)/build/soong/tests/androidmk_test.sh.
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := license_notice
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+	"Android-Apache-2.0",
+    ],
+}
+
+android_app {
+    name: "foo",
+    // ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
+    // LOCAL_NOTICE_FILE := license_notice
+
+}
+`,
+	},
+	{
+		desc: "LOCAL_CHECK_ELF_FILES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_SRC_FILES := test.c
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+LOCAL_CHECK_ELF_FILES := false
+include $(BUILD_PREBUILT)
+		`,
+		expected: `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["test.c"],
+
+	check_elf_files: false,
+}
+`,
+	},
 }
 
 func TestEndToEnd(t *testing.T) {
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 2f10904..8cca137 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -103,16 +103,9 @@
 		return moduleNames
 	}
 
-	var postInstallCommands []string
-	for _, fi := range a.filesInfo {
-		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
-			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			linkTarget := filepath.Join("/system", fi.path())
-			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.path())
-			mkdirCmd := "mkdir -p " + filepath.Dir(linkPath)
-			linkCmd := "ln -sfn " + linkTarget + " " + linkPath
-			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
-		}
+	// 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)
@@ -156,7 +149,7 @@
 		var modulePath string
 		if apexType == flattenedApex {
 			// /system/apex/<name>/{lib|framework|...}
-			modulePath = filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.installDir)
+			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)
@@ -188,6 +181,8 @@
 			// we will have duplicated notice entries.
 			fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
 		}
+		fmt.Fprintln(w, "LOCAL_SOONG_INSTALLED_MODULE :=", filepath.Join(modulePath, fi.stem()))
+		fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS :=", fi.builtFile.String()+":"+filepath.Join(modulePath, fi.stem()))
 		fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", fi.builtFile.String())
 		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.nameInMake())
 		if fi.module != nil {
@@ -258,7 +253,7 @@
 			if !ok {
 				panic(fmt.Sprintf("Expected %s to be AndroidAppSet", fi.module))
 			}
-			fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.InstallFile())
+			fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.PackedAdditionalOutputs().String())
 			fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String())
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk")
 		case nativeSharedLib, nativeExecutable, nativeTest:
@@ -272,7 +267,7 @@
 					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", ccMod.CoverageOutputFile().String())
 				}
 			}
-			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_prebuilt.mk")
+			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk")
 		default:
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if fi.builtFile == a.manifestPbOut && apexType == flattenedApex {
@@ -297,18 +292,10 @@
 					if len(patterns) > 0 {
 						fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(patterns, " "))
 					}
-					if len(a.compatSymlinks) > 0 {
-						// For flattened apexes, compat symlinks are attached to apex_manifest.json which is guaranteed for every apex
-						postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
-					}
 				}
 
 				// File_contexts of flattened APEXes should be merged into file_contexts.bin
 				fmt.Fprintln(w, "LOCAL_FILE_CONTEXTS :=", a.fileContexts)
-
-				if len(postInstallCommands) > 0 {
-					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(postInstallCommands, " && "))
-				}
 			}
 			fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 		}
@@ -322,16 +309,17 @@
 	return moduleNames
 }
 
-func (a *apexBundle) writeRequiredModules(w io.Writer, apexBundleName string) {
+func (a *apexBundle) writeRequiredModules(w io.Writer) {
 	var required []string
 	var targetRequired []string
 	var hostRequired []string
-	installMapSet := make(map[string]bool) // set of dependency module:location mappings
+	required = append(required, a.RequiredModuleNames()...)
+	targetRequired = append(targetRequired, a.TargetRequiredModuleNames()...)
+	hostRequired = append(hostRequired, a.HostRequiredModuleNames()...)
 	for _, fi := range a.filesInfo {
 		required = append(required, fi.requiredModuleNames...)
 		targetRequired = append(targetRequired, fi.targetRequiredModuleNames...)
 		hostRequired = append(hostRequired, fi.hostRequiredModuleNames...)
-		installMapSet[a.fullModuleName(apexBundleName, &fi)+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
 	}
 
 	if len(required) > 0 {
@@ -343,11 +331,6 @@
 	if len(hostRequired) > 0 {
 		fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES +=", strings.Join(hostRequired, " "))
 	}
-	if len(installMapSet) > 0 {
-		var installs []string
-		installs = append(installs, android.SortedStringKeys(installMapSet)...)
-		fmt.Fprintln(w, "LOCAL_LICENSE_INSTALL_MAP +=", strings.Join(installs, " "))
-	}
 }
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
@@ -369,7 +352,7 @@
 				if len(moduleNames) > 0 {
 					fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(moduleNames, " "))
 				}
-				a.writeRequiredModules(w, name)
+				a.writeRequiredModules(w)
 				fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
 
 			} else {
@@ -379,13 +362,15 @@
 				data.Entries.WriteLicenseVariables(w)
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
-				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
+				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.String())
 				stemSuffix := apexType.suffix()
 				if a.isCompressed {
-					stemSuffix = ".capex"
+					stemSuffix = imageCapexSuffix
 				}
 				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+stemSuffix)
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
+				fmt.Fprintln(w, "LOCAL_SOONG_INSTALLED_MODULE :=", a.installedFile.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS :=", a.outputFile.String()+":"+a.installedFile.String())
 
 				// Because apex writes .mk with Custom(), we need to write manually some common properties
 				// which are available via data.Entries
@@ -409,17 +394,7 @@
 				if len(a.requiredDeps) > 0 {
 					fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(a.requiredDeps, " "))
 				}
-				a.writeRequiredModules(w, name)
-				var postInstallCommands []string
-				if a.prebuiltFileToDelete != "" {
-					postInstallCommands = append(postInstallCommands, "rm -rf "+
-						filepath.Join(a.installDir.ToMakePath().String(), a.prebuiltFileToDelete))
-				}
-				// For unflattened apexes, compat symlinks are attached to apex package itself as LOCAL_POST_INSTALL_CMD
-				postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
-				if len(postInstallCommands) > 0 {
-					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(postInstallCommands, " && "))
-				}
+				a.writeRequiredModules(w)
 
 				if a.mergedNotices.Merged.Valid() {
 					fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNotices.Merged.Path().String())
@@ -446,23 +421,18 @@
 					fmt.Fprintf(w, dist)
 				}
 
-				if a.apisUsedByModuleFile.String() != "" {
-					goal := "apps_only"
-					distFile := a.apisUsedByModuleFile.String()
-					fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
-						" $(call dist-for-goals,%s,%s:ndk_apis_usedby_apex/$(notdir %s))\n"+
-						"endif\n",
-						goal, distFile, distFile)
-				}
-
-				if a.apisBackedByModuleFile.String() != "" {
-					goal := "apps_only"
-					distFile := a.apisBackedByModuleFile.String()
-					fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
-						" $(call dist-for-goals,%s,%s:ndk_apis_backedby_apex/$(notdir %s))\n"+
-						"endif\n",
-						goal, distFile, distFile)
-				}
+				distCoverageFiles(w, "ndk_apis_usedby_apex", a.nativeApisUsedByModuleFile.String())
+				distCoverageFiles(w, "ndk_apis_backedby_apex", a.nativeApisBackedByModuleFile.String())
+				distCoverageFiles(w, "java_apis_used_by_apex", a.javaApisUsedByModuleFile.String())
 			}
 		}}
 }
+
+func distCoverageFiles(w io.Writer, dir string, distfile string) {
+	if distfile != "" {
+		goal := "apps_only"
+		fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
+			" $(call dist-for-goals,%s,%s:%s/$(notdir %s))\n"+
+			"endif\n", goal, distfile, dir, distfile)
+	}
+}
diff --git a/apex/apex.go b/apex/apex.go
index fbf6a6f..635ff30 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -54,8 +54,6 @@
 	ctx.PreArchMutators(registerPreArchMutators)
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPostDepsMutators)
-
-	android.RegisterBp2BuildMutator("apex", ApexBundleBp2Build)
 }
 
 func registerPreArchMutators(ctx android.RegisterMutatorsContext) {
@@ -98,6 +96,14 @@
 	// /system/sepolicy/apex/<module_name>_file_contexts.
 	File_contexts *string `android:"path"`
 
+	// Path to the canned fs config file for customizing file's uid/gid/mod/capabilities. The
+	// format is /<path_or_glob> <uid> <gid> <mode> [capabilities=0x<cap>], where path_or_glob is a
+	// path or glob pattern for a file or set of files, uid/gid are numerial values of user ID
+	// and group ID, mode is octal value for the file mode, and cap is hexadecimal value for the
+	// capability. If this property is not set, or a file is missing in the file, default config
+	// is used.
+	Canned_fs_config *string `android:"path"`
+
 	ApexNativeDependencies
 
 	Multilib apexMultilibProperties
@@ -111,6 +117,9 @@
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
 
+	// List of sh binaries that are embedded inside this APEX bundle.
+	Sh_binaries []string
+
 	// List of platform_compat_config files that are embedded inside this APEX bundle.
 	Compat_configs []string
 
@@ -127,6 +136,13 @@
 	// symlinking to the system libs. Default is true.
 	Updatable *bool
 
+	// Marks that this APEX is designed to be updatable in the future, although it's not
+	// updatable yet. This is used to mimic some of the build behaviors that are applied only to
+	// updatable APEXes. Currently, this disables the size optimization, so that the size of
+	// APEX will not increase when the APEX is actually marked as truly updatable. Default is
+	// false.
+	Future_updatable *bool
+
 	// Whether this APEX can use platform APIs or not. Can be set to true only when `updatable:
 	// false`. Default is false.
 	Platform_apis *bool
@@ -141,15 +157,20 @@
 	// Default: true.
 	Compressible *bool
 
-	// For native libraries and binaries, use the vendor variant instead of the core (platform)
-	// variant. Default is false. DO NOT use this for APEXes that are installed to the system or
-	// system_ext partition.
-	Use_vendor *bool
-
 	// If set true, VNDK libs are considered as stable libs and are not included in this APEX.
 	// 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
+
 	// List of SDKs that are used to build this APEX. A reference to an SDK should be either
 	// `name#version` or `name` which is an alias for `name#current`. If left empty,
 	// `platform#current` is implied. This value affects all modules included in this APEX. In
@@ -163,8 +184,8 @@
 	// is 'image'.
 	Payload_type *string
 
-	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4' or 'f2fs'.
-	// Default 'ext4'.
+	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4', 'f2fs'
+	// or 'erofs'. Default 'ext4'.
 	Payload_fs_type *string
 
 	// For telling the APEX to ignore special handling for system libraries such as bionic.
@@ -183,6 +204,10 @@
 	// used in tests.
 	Test_only_force_compression *bool
 
+	// Put extra tags (signer=<value>) to apexkeys.txt, so that release tools can sign this apex
+	// with the tool to sign payload contents.
+	Custom_sign_tool *string
+
 	// Canonical name of this APEX bundle. Used to determine the path to the
 	// activated APEX on device (i.e. /apex/<apexVariationName>), and used for the
 	// apex mutator variations. For override_apex modules, this is the name of the
@@ -353,7 +378,6 @@
 	// Flags for special variants of APEX
 	testApex bool
 	vndkApex bool
-	artApex  bool
 
 	// Tells whether this variant of the APEX bundle is the primary one or not. Only the primary
 	// one gets installed to the device.
@@ -401,14 +425,14 @@
 	// vendor/google/build/build_unbundled_mainline_module.sh for more detail.
 	bundleModuleFile android.WritablePath
 
-	// Target path to install this APEX. Usually out/target/product/<device>/<partition>/apex.
+	// Target directory to install this APEX. Usually out/target/product/<device>/<partition>/apex.
 	installDir android.InstallPath
 
-	// List of commands to create symlinks for backward compatibility. These commands will be
-	// attached as LOCAL_POST_INSTALL_CMD to apex package itself (for unflattened build) or
-	// apex_manifest (for flattened build) so that compat symlinks are always installed
-	// regardless of TARGET_FLATTEN_APEX setting.
-	compatSymlinks []string
+	// Path where this APEX was installed.
+	installedFile android.InstallPath
+
+	// Installed locations of symlinks for backward compatibility.
+	compatSymlinks android.InstallPaths
 
 	// Text file having the list of individual files that are included in this APEX. Used for
 	// debugging purpose.
@@ -426,8 +450,9 @@
 	isCompressed bool
 
 	// Path of API coverage generate file
-	apisUsedByModuleFile   android.ModuleOutPath
-	apisBackedByModuleFile android.ModuleOutPath
+	nativeApisUsedByModuleFile   android.ModuleOutPath
+	nativeApisBackedByModuleFile android.ModuleOutPath
+	javaApisUsedByModuleFile     android.ModuleOutPath
 
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
@@ -605,6 +630,7 @@
 	sharedLibTag    = dependencyTag{name: "sharedLib", payload: true}
 	testForTag      = dependencyTag{name: "test for"}
 	testTag         = dependencyTag{name: "test", payload: true}
+	shBinaryTag     = dependencyTag{name: "shBinary", payload: true}
 )
 
 // TODO(jiyong): shorten this function signature
@@ -654,10 +680,7 @@
 	var prefix string
 	var vndkVersion string
 	if deviceConfig.VndkVersion() != "" {
-		if proptools.Bool(a.properties.Use_vendor) {
-			prefix = cc.VendorVariationPrefix
-			vndkVersion = deviceConfig.PlatformVndkVersion()
-		} else if a.SocSpecific() || a.DeviceSpecific() {
+		if a.SocSpecific() || a.DeviceSpecific() {
 			prefix = cc.VendorVariationPrefix
 			vndkVersion = deviceConfig.VndkVersion()
 		} else if a.ProductSpecific() {
@@ -676,9 +699,6 @@
 }
 
 func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
-	// TODO(jiyong): move this kind of checks to GenerateAndroidBuildActions?
-	checkUseVendorProperty(ctx, a)
-
 	// apexBundle is a multi-arch targets module. Arch variant of apexBundle is set to 'common'.
 	// arch-specific targets are enabled by the compile_multilib setting of the apex bundle. For
 	// each target os/architectures, appropriate dependencies are selected by their
@@ -755,6 +775,10 @@
 		for _, d := range depsList {
 			addDependenciesForNativeModules(ctx, d, target, imageVariation)
 		}
+		ctx.AddFarVariationDependencies([]blueprint.Variation{
+			{Mutator: "os", Variation: target.OsVariation()},
+			{Mutator: "arch", Variation: target.ArchVariation()},
+		}, shBinaryTag, a.properties.Sh_binaries...)
 	}
 
 	// Common-arch dependencies come next
@@ -765,13 +789,6 @@
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
 
-	if a.artApex {
-		// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
-		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-			ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
-		}
-	}
-
 	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
 	// This field currently isn't used.
 	// TODO(jiyong): consider dropping this feature
@@ -1152,17 +1169,19 @@
 
 const (
 	// File extensions of an APEX for different packaging methods
-	imageApexSuffix = ".apex"
-	zipApexSuffix   = ".zipapex"
-	flattenedSuffix = ".flattened"
+	imageApexSuffix  = ".apex"
+	imageCapexSuffix = ".capex"
+	zipApexSuffix    = ".zipapex"
+	flattenedSuffix  = ".flattened"
 
 	// variant names each of which is for a packaging method
 	imageApexType     = "image"
 	zipApexType       = "zip"
 	flattenedApexType = "flattened"
 
-	ext4FsType = "ext4"
-	f2fsFsType = "f2fs"
+	ext4FsType  = "ext4"
+	f2fsFsType  = "f2fs"
+	erofsFsType = "erofs"
 )
 
 // The suffix for the output "file", not the module
@@ -1239,42 +1258,6 @@
 	}
 }
 
-// checkUseVendorProperty checks if the use of `use_vendor` property is allowed for the given APEX.
-// When use_vendor is used, native modules are built with __ANDROID_VNDK__ and __ANDROID_APEX__,
-// which may cause compatibility issues. (e.g. libbinder) Even though libbinder restricts its
-// availability via 'apex_available' property and relies on yet another macro
-// __ANDROID_APEX_<NAME>__, we restrict usage of "use_vendor:" from other APEX modules to avoid
-// similar problems.
-func checkUseVendorProperty(ctx android.BottomUpMutatorContext, a *apexBundle) {
-	if proptools.Bool(a.properties.Use_vendor) && !android.InList(a.Name(), useVendorAllowList(ctx.Config())) {
-		ctx.PropertyErrorf("use_vendor", "not allowed to set use_vendor: true")
-	}
-}
-
-var (
-	useVendorAllowListKey = android.NewOnceKey("useVendorAllowList")
-)
-
-func useVendorAllowList(config android.Config) []string {
-	return config.Once(useVendorAllowListKey, func() interface{} {
-		return []string{
-			// swcodec uses "vendor" variants for smaller size
-			"com.android.media.swcodec",
-			"test_com.android.media.swcodec",
-		}
-	}).([]string)
-}
-
-// setUseVendorAllowListForTest returns a FixturePreparer that overrides useVendorAllowList and
-// must be called before the first call to useVendorAllowList()
-func setUseVendorAllowListForTest(allowList []string) android.FixturePreparer {
-	return android.FixtureModifyConfig(func(config android.Config) {
-		config.Once(useVendorAllowListKey, func() interface{} {
-			return allowList
-		})
-	})
-}
-
 var _ android.DepIsInSameApex = (*apexBundle)(nil)
 
 // Implements android.DepInInSameApex
@@ -1332,6 +1315,10 @@
 	return proptools.BoolDefault(a.properties.Updatable, true)
 }
 
+func (a *apexBundle) FutureUpdatable() bool {
+	return proptools.BoolDefault(a.properties.Future_updatable, false)
+}
+
 func (a *apexBundle) UsePlatformApis() bool {
 	return proptools.BoolDefault(a.properties.Platform_apis, false)
 }
@@ -1507,12 +1494,7 @@
 
 func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
 	dirInApex := "bin"
-	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
-	if err != nil {
-		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
-		return apexFile{}
-	}
-	fileToCopy := android.PathForOutput(ctx, s)
+	fileToCopy := android.PathForGoBinary(ctx, gb)
 	// NB: Since go binaries are static we don't need the module for anything here, which is
 	// good since the go tool is a blueprint.Module not an android.Module like we would
 	// normally use.
@@ -1521,6 +1503,9 @@
 
 func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
 	dirInApex := filepath.Join("bin", sh.SubDir())
+	if sh.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, sh.Target().NativeBridgeRelativePath)
+	}
 	fileToCopy := sh.OutputFile()
 	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
 	af.symlinks = sh.Symlinks()
@@ -1544,7 +1529,7 @@
 type javaModule interface {
 	android.Module
 	BaseModuleName() string
-	DexJarBuildPath() android.Path
+	DexJarBuildPath() java.OptionalDexJarPath
 	JacocoReportClassesFile() android.Path
 	LintDepSets() java.LintDepSets
 	Stem() string
@@ -1558,7 +1543,7 @@
 
 // apexFileForJavaModule creates an apexFile for a java module's dex implementation jar.
 func apexFileForJavaModule(ctx android.BaseModuleContext, module javaModule) apexFile {
-	return apexFileForJavaModuleWithFile(ctx, module, module.DexJarBuildPath())
+	return apexFileForJavaModuleWithFile(ctx, module, module.DexJarBuildPath().PathOrNil())
 }
 
 // apexFileForJavaModuleWithFile creates an apexFile for a java module with the supplied file.
@@ -1568,6 +1553,11 @@
 	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
 	af.lintDepSets = module.LintDepSets()
 	af.customStem = module.Stem() + ".jar"
+	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+		for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() {
+			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+		}
+	}
 	return af
 }
 
@@ -1666,6 +1656,7 @@
 const (
 	ext4 fsType = iota
 	f2fs
+	erofs
 )
 
 func (f fsType) string() string {
@@ -1674,6 +1665,8 @@
 		return ext4FsType
 	case f2fs:
 		return f2fsFsType
+	case erofs:
+		return erofsFsType
 	default:
 		panic(fmt.Errorf("unknown APEX payload type %d", f))
 	}
@@ -1690,7 +1683,7 @@
 	// 1) do some validity checks such as apex_available, min_sdk_version, etc.
 	a.checkApexAvailability(ctx)
 	a.checkUpdatable(ctx)
-	a.checkMinSdkVersion(ctx)
+	a.CheckMinSdkVersion(ctx)
 	a.checkStaticLinkingToStubLibraries(ctx)
 	a.checkStaticExecutables(ctx)
 	if len(a.properties.Tests) > 0 && !a.testApex {
@@ -1720,6 +1713,9 @@
 		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
 			return false
 		}
+		if mod, ok := child.(android.Module); ok && !mod.Enabled() {
+			return false
+		}
 		depName := ctx.OtherModuleName(child)
 		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
 			switch depTag {
@@ -1738,6 +1734,7 @@
 					return true // track transitive dependencies
 				} else if r, ok := child.(*rust.Module); ok {
 					fi := apexFileForRustLibrary(ctx, r)
+					fi.isJniLib = isJniLib
 					filesInfo = append(filesInfo, fi)
 				} else {
 					propertyName := "native_shared_libs"
@@ -1750,8 +1747,6 @@
 				if cc, ok := child.(*cc.Module); ok {
 					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
 					return true // track transitive dependencies
-				} else if sh, ok := child.(*sh.ShBinary); ok {
-					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
 				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
 					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
 				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
@@ -1760,7 +1755,13 @@
 					filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
 					return true // track transitive dependencies
 				} else {
-					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, nor (host) bootstrap_go_binary", depName)
+				}
+			case shBinaryTag:
+				if sh, ok := child.(*sh.ShBinary); ok {
+					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
+				} else {
+					ctx.PropertyErrorf("sh_binaries", "%q is not a sh_binary module", depName)
 				}
 			case bcpfTag:
 				{
@@ -1922,13 +1923,7 @@
 							// system libraries.
 							if !am.DirectlyInAnyApex() {
 								// we need a module name for Make
-								name := cc.ImplementationModuleNameForMake(ctx)
-
-								if !proptools.Bool(a.properties.Use_vendor) {
-									// we don't use subName(.vendor) for a "use_vendor: true" apex
-									// which is supposed to be installed in /system
-									name += cc.Properties.SubName
-								}
+								name := cc.ImplementationModuleNameForMake(ctx) + cc.Properties.SubName
 								if !android.InList(name, a.requiredDeps) {
 									a.requiredDeps = append(a.requiredDeps, name)
 								}
@@ -2022,6 +2017,8 @@
 					}
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
 					// nothing
+				} else if depTag == android.DarwinUniversalVariantTag {
+					// nothing
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
 					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
 				}
@@ -2103,8 +2100,10 @@
 		a.payloadFsType = ext4
 	case f2fsFsType:
 		a.payloadFsType = f2fs
+	case erofsFsType:
+		a.payloadFsType = erofs
 	default:
-		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
+		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs, erofs]", *a.properties.Payload_fs_type)
 	}
 
 	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
@@ -2112,7 +2111,7 @@
 	// the same library in the system partition, thus effectively sharing the same libraries
 	// across the APEX boundary. For unbundled APEX, all the gathered files are actually placed
 	// in the APEX.
-	a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable() && !proptools.Bool(a.properties.Use_vendor)
+	a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable()
 
 	// APEXes targeting other than system/system_ext partitions use vendor/product variants.
 	// So we can't link them to /system/lib libs which are core variants.
@@ -2121,10 +2120,11 @@
 	}
 
 	forced := ctx.Config().ForceApexSymlinkOptimization()
+	updatable := a.Updatable() || a.FutureUpdatable()
 
 	// We don't need the optimization for updatable APEXes, as it might give false signal
 	// to the system health when the APEXes are still bundled (b/149805758).
-	if !forced && a.Updatable() && a.properties.ApexType == imageApex {
+	if !forced && updatable && a.properties.ApexType == imageApex {
 		a.linkToSystemLib = false
 	}
 
@@ -2133,7 +2133,9 @@
 		a.linkToSystemLib = false
 	}
 
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+	if a.properties.ApexType != zipApex {
+		a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, a.primaryApexType)
+	}
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 4) generate the build rules to create the APEX. This is done in builder.go.
@@ -2188,6 +2190,40 @@
 		filesToAdd = append(filesToAdd, *af)
 	}
 
+	if pathInApex := bootclasspathFragmentInfo.ProfileInstallPathInApex(); pathInApex != "" {
+		pathOnHost := bootclasspathFragmentInfo.ProfilePathOnHost()
+		tempPath := android.PathForModuleOut(ctx, "boot_image_profile", pathInApex)
+
+		if pathOnHost != nil {
+			// We need to copy the profile to a temporary path with the right filename because the apexer
+			// will take the filename as is.
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  pathOnHost,
+				Output: tempPath,
+			})
+		} else {
+			// At this point, the boot image profile cannot be generated. It is probably because the boot
+			// image profile source file does not exist on the branch, or it is not available for the
+			// current build target.
+			// However, we cannot enforce the boot image profile to be generated because some build
+			// targets (such as module SDK) do not need it. It is only needed when the APEX is being
+			// built. Therefore, we create an error rule so that an error will occur at the ninja phase
+			// only if the APEX is being built.
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.ErrorRule,
+				Output: tempPath,
+				Args: map[string]string{
+					"error": "Boot image profile cannot be generated",
+				},
+			})
+		}
+
+		androidMkModuleName := filepath.Base(pathInApex)
+		af := newApexFile(ctx, tempPath, androidMkModuleName, filepath.Dir(pathInApex), etc, nil)
+		filesToAdd = append(filesToAdd, af)
+	}
+
 	return filesToAdd
 }
 
@@ -2241,10 +2277,9 @@
 	return module
 }
 
-func ApexBundleFactory(testApex bool, artApex bool) android.Module {
+func ApexBundleFactory(testApex bool) android.Module {
 	bundle := newApexBundle()
 	bundle.testApex = testApex
-	bundle.artApex = artApex
 	return bundle
 }
 
@@ -2315,22 +2350,28 @@
 //
 // TODO(jiyong): move these checks to a separate go file.
 
+var _ android.ModuleWithMinSdkVersionCheck = (*apexBundle)(nil)
+
 // Entures that min_sdk_version of the included modules are equal or less than the min_sdk_version
 // of this apexBundle.
-func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
+func (a *apexBundle) CheckMinSdkVersion(ctx android.ModuleContext) {
 	if a.testApex || a.vndkApex {
 		return
 	}
-	// Meaningless to check min_sdk_version when building use_vendor modules against non-Trebleized targets
-	if proptools.Bool(a.properties.Use_vendor) && ctx.DeviceConfig().VndkVersion() == "" {
-		return
-	}
 	// apexBundle::minSdkVersion reports its own errors.
 	minSdkVersion := a.minSdkVersion(ctx)
-	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+	android.CheckMinSdkVersion(ctx, minSdkVersion, a.WalkPayloadDeps)
 }
 
-func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
+func (a *apexBundle) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	return android.SdkSpec{
+		Kind:     android.SdkNone,
+		ApiLevel: a.minSdkVersion(ctx),
+		Raw:      String(a.properties.Min_sdk_version),
+	}
+}
+
+func (a *apexBundle) minSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
 	ver := proptools.String(a.properties.Min_sdk_version)
 	if ver == "" {
 		return android.NoneApiLevel
@@ -2396,6 +2437,12 @@
 		if a.UsePlatformApis() {
 			ctx.PropertyErrorf("updatable", "updatable APEXes can't use platform APIs")
 		}
+		if a.SocSpecific() || a.DeviceSpecific() {
+			ctx.PropertyErrorf("updatable", "vendor APEXes are not updatable")
+		}
+		if a.FutureUpdatable() {
+			ctx.PropertyErrorf("future_updatable", "Already updatable. Remove `future_updatable: true:`")
+		}
 		a.checkJavaStableSdkVersion(ctx)
 		a.checkClasspathFragments(ctx)
 	}
@@ -2596,7 +2643,7 @@
 	//
 	// Module separator
 	//
-	m["com.android.bluetooth.updatable"] = []string{
+	m["com.android.bluetooth"] = []string{
 		"android.hardware.audio.common@5.0",
 		"android.hardware.bluetooth.a2dp@1.0",
 		"android.hardware.bluetooth.audio@2.0",
@@ -2703,7 +2750,6 @@
 		"libbuildversion",
 		"libmath",
 		"libprocpartition",
-		"libsync",
 	}
 	//
 	// Module separator
@@ -2811,7 +2857,6 @@
 		"libstagefright_metadatautils",
 		"libstagefright_mpeg2extractor",
 		"libstagefright_mpeg2support",
-		"libsync",
 		"libui",
 		"libui_headers",
 		"libunwindstack",
@@ -2944,7 +2989,6 @@
 		"libstagefright_amrwbdec",
 		"libstagefright_amrwbenc",
 		"libstagefright_bufferpool@2.0.1",
-		"libstagefright_bufferqueue_helper",
 		"libstagefright_enc_common",
 		"libstagefright_flacdec",
 		"libstagefright_foundation",
@@ -2953,7 +2997,6 @@
 		"libstagefright_m4vh263dec",
 		"libstagefright_m4vh263enc",
 		"libstagefright_mp3dec",
-		"libsync",
 		"libui",
 		"libui_headers",
 		"libunwindstack",
@@ -3159,15 +3202,16 @@
 			BootclasspathJar().
 			With("apex_available", module_name).
 			WithMatcher("permitted_packages", android.NotInList(module_packages)).
+			WithMatcher("min_sdk_version", android.LessThanSdkVersion("Tiramisu")).
 			Because("jars that are part of the " + module_name +
 				" module may only allow these packages: " + strings.Join(module_packages, ",") +
-				". Please jarjar or move code around.")
+				" with min_sdk < T. Please jarjar or move code around.")
 		rules = append(rules, permittedPackagesRule)
 	}
 	return rules
 }
 
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on Q/R/S.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func qModulesPackages() map[string][]string {
 	return map[string][]string{
@@ -3181,7 +3225,7 @@
 	}
 }
 
-// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
+// DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART on R/S.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
 func rModulesPackages() map[string][]string {
 	return map[string][]string{
@@ -3224,80 +3268,70 @@
 	File_contexts      bazel.LabelAttribute
 	Key                bazel.LabelAttribute
 	Certificate        bazel.LabelAttribute
-	Min_sdk_version    string
+	Min_sdk_version    *string
 	Updatable          bazel.BoolAttribute
 	Installable        bazel.BoolAttribute
 	Native_shared_libs bazel.LabelListAttribute
-	Binaries           bazel.StringListAttribute
+	Binaries           bazel.LabelListAttribute
 	Prebuilts          bazel.LabelListAttribute
 }
 
-func ApexBundleBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*apexBundle)
-	if !ok {
-		// Not an APEX bundle
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
+// ConvertWithBp2build performs bp2build conversion of an apex
+func (a *apexBundle) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	// We do not convert apex_test modules at this time
 	if ctx.ModuleType() != "apex" {
 		return
 	}
 
-	apexBundleBp2BuildInternal(ctx, module)
-}
-
-func apexBundleBp2BuildInternal(ctx android.TopDownMutatorContext, module *apexBundle) {
 	var manifestLabelAttribute bazel.LabelAttribute
-	if module.properties.Manifest != nil {
-		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.Manifest))
+	if a.properties.Manifest != nil {
+		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.Manifest))
 	}
 
 	var androidManifestLabelAttribute bazel.LabelAttribute
-	if module.properties.AndroidManifest != nil {
-		androidManifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.AndroidManifest))
+	if a.properties.AndroidManifest != nil {
+		androidManifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.AndroidManifest))
 	}
 
 	var fileContextsLabelAttribute bazel.LabelAttribute
-	if module.properties.File_contexts != nil {
-		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *module.properties.File_contexts))
+	if a.properties.File_contexts != nil {
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.properties.File_contexts))
 	}
 
-	var minSdkVersion string
-	if module.properties.Min_sdk_version != nil {
-		minSdkVersion = *module.properties.Min_sdk_version
+	var minSdkVersion *string
+	if a.properties.Min_sdk_version != nil {
+		minSdkVersion = a.properties.Min_sdk_version
 	}
 
 	var keyLabelAttribute bazel.LabelAttribute
-	if module.overridableProperties.Key != nil {
-		keyLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *module.overridableProperties.Key))
+	if a.overridableProperties.Key != nil {
+		keyLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Key))
 	}
 
 	var certificateLabelAttribute bazel.LabelAttribute
-	if module.overridableProperties.Certificate != nil {
-		certificateLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *module.overridableProperties.Certificate))
+	if a.overridableProperties.Certificate != nil {
+		certificateLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Certificate))
 	}
 
-	nativeSharedLibs := module.properties.ApexNativeDependencies.Native_shared_libs
+	nativeSharedLibs := a.properties.ApexNativeDependencies.Native_shared_libs
 	nativeSharedLibsLabelList := android.BazelLabelForModuleDeps(ctx, nativeSharedLibs)
 	nativeSharedLibsLabelListAttribute := bazel.MakeLabelListAttribute(nativeSharedLibsLabelList)
 
-	prebuilts := module.overridableProperties.Prebuilts
+	prebuilts := a.overridableProperties.Prebuilts
 	prebuiltsLabelList := android.BazelLabelForModuleDeps(ctx, prebuilts)
 	prebuiltsLabelListAttribute := bazel.MakeLabelListAttribute(prebuiltsLabelList)
 
-	binaries := module.properties.ApexNativeDependencies.Binaries
-	binariesStringListAttribute := bazel.MakeStringListAttribute(binaries)
+	binaries := android.BazelLabelForModuleDeps(ctx, a.properties.ApexNativeDependencies.Binaries)
+	binariesLabelListAttribute := bazel.MakeLabelListAttribute(binaries)
 
 	var updatableAttribute bazel.BoolAttribute
-	if module.properties.Updatable != nil {
-		updatableAttribute.Value = module.properties.Updatable
+	if a.properties.Updatable != nil {
+		updatableAttribute.Value = a.properties.Updatable
 	}
 
 	var installableAttribute bazel.BoolAttribute
-	if module.properties.Installable != nil {
-		installableAttribute.Value = module.properties.Installable
+	if a.properties.Installable != nil {
+		installableAttribute.Value = a.properties.Installable
 	}
 
 	attrs := &bazelApexBundleAttributes{
@@ -3310,7 +3344,7 @@
 		Updatable:          updatableAttribute,
 		Installable:        installableAttribute,
 		Native_shared_libs: nativeSharedLibsLabelListAttribute,
-		Binaries:           binariesStringListAttribute,
+		Binaries:           binariesLabelListAttribute,
 		Prebuilts:          prebuiltsLabelListAttribute,
 	}
 
@@ -3319,5 +3353,5 @@
 		Bzl_load_location: "//build/bazel/rules:apex.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
 }
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index 9178431..6faed70 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -17,9 +17,9 @@
 package apex
 
 import (
-	"android/soong/android"
-
 	"github.com/google/blueprint"
+
+	"android/soong/android"
 )
 
 func init() {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 5c7c90b..9ab8ca1 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -215,7 +215,9 @@
 		variables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
 		variables.Platform_sdk_codename = proptools.StringPtr("Q")
 		variables.Platform_sdk_final = proptools.BoolPtr(false)
-		variables.Platform_version_active_codenames = []string{"Q"}
+		// "Tiramisu" needs to be in the next line for compatibility with soong code,
+		// not because of these tests specifically (it's not used by the tests)
+		variables.Platform_version_active_codenames = []string{"Q", "Tiramisu"}
 		variables.Platform_vndk_version = proptools.StringPtr("29")
 	}),
 )
@@ -932,9 +934,17 @@
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12/mylib3.so")
 
+	// Comment out this test. Now it fails after the optimization of sharing "cflags" in cc/cc.go
+	// is replaced by sharing of "cFlags" in cc/builder.go.
+	// The "cflags" contains "-include mylib.h", but cFlags contained only a reference to the
+	// module variable representing "cflags". So it was not detected by ensureNotContains.
+	// Now "cFlags" is a reference to a module variable like $flags1, which includes all previous
+	// content of "cflags". ModuleForTests...Args["cFlags"] returns the full string of $flags1,
+	// including the original cflags's "-include mylib.h".
+	//
 	// Ensure that stubs libs are built without -include flags
-	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
-	ensureNotContains(t, mylib2Cflags, "-include ")
+	// mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	// ensureNotContains(t, mylib2Cflags, "-include ")
 
 	// Ensure that genstub is invoked with --apex
 	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"])
@@ -1501,7 +1511,6 @@
 			apex {
 				name: "myapex",
 				key: "myapex.key",
-				use_vendor: true,
 				native_shared_libs: ["mylib"],
 				updatable: false,
 				`+tc.minSdkVersion+`
@@ -1535,7 +1544,6 @@
 				}
 			}
 			`,
-				setUseVendorAllowListForTest([]string{"myapex"}),
 				withUnbundledBuild,
 			)
 
@@ -1549,13 +1557,13 @@
 			ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 			ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libbar.so")
 
-			mylibLdFlags := ctx.ModuleForTests("mylib", "android_vendor.29_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
-			ensureContains(t, mylibLdFlags, "libbar/android_vendor.29_arm64_armv8-a_shared_"+tc.shouldLink+"/libbar.so")
+			mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
+			ensureContains(t, mylibLdFlags, "libbar/android_arm64_armv8-a_shared_"+tc.shouldLink+"/libbar.so")
 			for _, ver := range tc.shouldNotLink {
-				ensureNotContains(t, mylibLdFlags, "libbar/android_vendor.29_arm64_armv8-a_shared_"+ver+"/libbar.so")
+				ensureNotContains(t, mylibLdFlags, "libbar/android_arm64_armv8-a_shared_"+ver+"/libbar.so")
 			}
 
-			mylibCFlags := ctx.ModuleForTests("mylib", "android_vendor.29_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
+			mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
 			ver := tc.shouldLink
 			if tc.shouldLink == "current" {
 				ver = strconv.Itoa(android.FutureApiLevelInt)
@@ -2179,6 +2187,7 @@
 		name          string
 		expectedError string
 		bp            string
+		preparer      android.FixturePreparer
 	}{
 		{
 			name: "Non-updatable apex with non-stable dep",
@@ -2250,6 +2259,30 @@
 			`,
 		},
 		{
+			name:          "Updatable apex with non-stable legacy core platform dep",
+			expectedError: `\Qcannot depend on "myjar-uses-legacy": non stable SDK core_platform_current - uses legacy core platform\E`,
+			bp: `
+				apex {
+					name: "myapex",
+					java_libs: ["myjar-uses-legacy"],
+					key: "myapex.key",
+					updatable: true,
+				}
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+				java_library {
+					name: "myjar-uses-legacy",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "core_platform",
+					apex_available: ["myapex"],
+				}
+			`,
+			preparer: java.FixtureUseLegacyCorePlatformApi("myjar-uses-legacy"),
+		},
+		{
 			name: "Updatable apex with non-stable transitive dep",
 			// This is not actually detecting that the transitive dependency is unstable, rather it is
 			// detecting that the transitive dependency is building against a wider API surface than the
@@ -2285,12 +2318,22 @@
 	}
 
 	for _, test := range testCases {
+		if test.name != "Updatable apex with non-stable legacy core platform dep" {
+			continue
+		}
 		t.Run(test.name, func(t *testing.T) {
-			if test.expectedError == "" {
-				testApex(t, test.bp)
-			} else {
-				testApexError(t, test.expectedError, test.bp)
+			errorHandler := android.FixtureExpectsNoErrors
+			if test.expectedError != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
 			}
+			android.GroupFixturePreparers(
+				java.PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				android.OptionalFixturePreparer(test.preparer),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, test.bp)
 		})
 	}
 }
@@ -2538,22 +2581,21 @@
 	`)
 
 	generateFsRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("generateFsConfig")
-	dirs := strings.Split(generateFsRule.Args["exec_paths"], " ")
+	cmd := generateFsRule.RuleParams.Command
 
 	// Ensure that the subdirectories are all listed
-	ensureListContains(t, dirs, "etc")
-	ensureListContains(t, dirs, "etc/foo")
-	ensureListContains(t, dirs, "etc/foo/bar")
-	ensureListContains(t, dirs, "lib64")
-	ensureListContains(t, dirs, "lib64/foo")
-	ensureListContains(t, dirs, "lib64/foo/bar")
-	ensureListContains(t, dirs, "lib")
-	ensureListContains(t, dirs, "lib/foo")
-	ensureListContains(t, dirs, "lib/foo/bar")
-
-	ensureListContains(t, dirs, "bin")
-	ensureListContains(t, dirs, "bin/foo")
-	ensureListContains(t, dirs, "bin/foo/bar")
+	ensureContains(t, cmd, "/etc ")
+	ensureContains(t, cmd, "/etc/foo ")
+	ensureContains(t, cmd, "/etc/foo/bar ")
+	ensureContains(t, cmd, "/lib64 ")
+	ensureContains(t, cmd, "/lib64/foo ")
+	ensureContains(t, cmd, "/lib64/foo/bar ")
+	ensureContains(t, cmd, "/lib ")
+	ensureContains(t, cmd, "/lib/foo ")
+	ensureContains(t, cmd, "/lib/foo/bar ")
+	ensureContains(t, cmd, "/bin ")
+	ensureContains(t, cmd, "/bin/foo ")
+	ensureContains(t, cmd, "/bin/foo/bar ")
 }
 
 func TestFilesInSubDirWhenNativeBridgeEnabled(t *testing.T) {
@@ -2614,121 +2656,11 @@
 	})
 }
 
-func TestUseVendor(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib"],
-			use_vendor: true,
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2"],
-			system_shared_libs: [],
-			vendor_available: true,
-			stl: "none",
-			apex_available: [ "myapex" ],
-		}
-
-		cc_library {
-			name: "mylib2",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			vendor_available: true,
-			stl: "none",
-			apex_available: [ "myapex" ],
-		}
-	`,
-		setUseVendorAllowListForTest([]string{"myapex"}),
-	)
-
-	inputsList := []string{}
-	for _, i := range ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().BuildParamsForTests() {
-		for _, implicit := range i.Implicits {
-			inputsList = append(inputsList, implicit.String())
-		}
-	}
-	inputsString := strings.Join(inputsList, " ")
-
-	// ensure that the apex includes vendor variants of the direct and indirect deps
-	ensureContains(t, inputsString, "android_vendor.29_arm64_armv8-a_shared_apex10000/mylib.so")
-	ensureContains(t, inputsString, "android_vendor.29_arm64_armv8-a_shared_apex10000/mylib2.so")
-
-	// ensure that the apex does not include core variants
-	ensureNotContains(t, inputsString, "android_arm64_armv8-a_shared_apex10000/mylib.so")
-	ensureNotContains(t, inputsString, "android_arm64_armv8-a_shared_apex10000/mylib2.so")
-}
-
-func TestUseVendorNotAllowedForSystemApexes(t *testing.T) {
-	testApexError(t, `module "myapex" .*: use_vendor: not allowed`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			use_vendor: true,
-		}
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`,
-		setUseVendorAllowListForTest([]string{""}),
-	)
-	// no error with allow list
-	testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			use_vendor: true,
-			updatable: false,
-		}
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`,
-		setUseVendorAllowListForTest([]string{"myapex"}),
-	)
-}
-
-func TestUseVendorFailsIfNotVendorAvailable(t *testing.T) {
-	testApexError(t, `dependency "mylib" of "myapex" missing variant:\n.*image:vendor`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib"],
-			use_vendor: true,
-			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",
-		}
-	`)
-}
-
 func TestVendorApex(t *testing.T) {
-	ctx := testApex(t, `
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
@@ -2752,24 +2684,24 @@
 		}
 	`)
 
-	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"bin/mybin",
 		"lib64/libfoo.so",
 		// TODO(b/159195575): Add an option to use VNDK libs from VNDK APEX
 		"lib64/libc++.so",
 	})
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	apexBundle := result.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, result.TestContext, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
-	androidMk := android.StringRelativeToTop(ctx.Config(), builder.String())
+	androidMk := android.StringRelativeToTop(result.Config, builder.String())
 	installPath := "out/target/product/test_device/vendor/apex"
 	ensureContains(t, androidMk, "LOCAL_MODULE_PATH := "+installPath)
 
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
+	apexManifestRule := result.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
 	requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListNotContains(t, requireNativeLibs, ":vndk")
 }
@@ -2901,41 +2833,6 @@
 	}
 }
 
-func TestAndroidMk_UseVendorRequired(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			use_vendor: true,
-			native_shared_libs: ["mylib"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			vendor_available: true,
-			apex_available: ["myapex"],
-		}
-	`,
-		setUseVendorAllowListForTest([]string{"myapex"}),
-	)
-
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").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()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += libc libm libdl\n")
-}
-
 func TestAndroidMk_VendorApexRequired(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -3290,7 +3187,6 @@
 				"myapex",
 				"otherapex",
 			],
-			use_apex_name_macro: true,
 			recovery_available: true,
 			min_sdk_version: "29",
 		}
@@ -3305,13 +3201,11 @@
 	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")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ 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")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
 
 	// 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
@@ -3320,42 +3214,15 @@
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static_otherapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
 	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	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__")
 
-	// When a dependency of a cc_library sets use_apex_name_macro: true each apex gets a unique
-	// variant.
-
 	// 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__")
 
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_otherapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
 	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
@@ -4385,7 +4252,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			binaries: ["myscript"],
+			sh_binaries: ["myscript"],
 			updatable: false,
 		}
 
@@ -4640,6 +4507,35 @@
 	}
 }
 
+func TestApexSetFilenameOverride(t *testing.T) {
+	testApex(t, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "com.company.android.myapex.apex"
+		}
+	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+
+	testApex(t, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "com.company.android.myapex.capex"
+		}
+	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+
+	testApexError(t, `filename should end in .apex or .capex for apex_set`, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "some-random-suffix"
+		}
+	`)
+}
+
 func TestPrebuiltOverrides(t *testing.T) {
 	ctx := testApex(t, `
 		prebuilt_apex {
@@ -4727,9 +4623,10 @@
 	transform := android.NullFixturePreparer
 
 	checkDexJarBuildPath := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
 		// Make sure the import has been given the correct path to the dex jar.
 		p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
-		dexJarBuildPath := p.DexJarBuildPath()
+		dexJarBuildPath := p.DexJarBuildPath().PathOrNil()
 		stem := android.RemoveOptionalPrebuiltPrefix(name)
 		android.AssertStringEquals(t, "DexJarBuildPath should be apex-related path.",
 			".intermediates/myapex.deapexer/android_common/deapexer/javalib/"+stem+".jar",
@@ -4737,6 +4634,7 @@
 	}
 
 	checkDexJarInstallPath := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
 		// Make sure the import has been given the correct path to the dex jar.
 		p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
 		dexJarBuildPath := p.DexJarInstallPath()
@@ -4747,6 +4645,7 @@
 	}
 
 	ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
 		// Make sure that an apex variant is not created for the source module.
 		android.AssertArrayString(t, "Check if there is no source variant",
 			[]string{"android_common"},
@@ -4784,8 +4683,11 @@
 		// Make sure that dexpreopt can access dex implementation files from the prebuilt.
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
+		deapexerName := deapexerModuleName("myapex")
+		android.AssertStringEquals(t, "APEX module name from deapexer name", "myapex", apexModuleName(deapexerName))
+
 		// Make sure that the deapexer has the correct input APEX.
-		deapexer := ctx.ModuleForTests("myapex.deapexer", "android_common")
+		deapexer := ctx.ModuleForTests(deapexerName, "android_common")
 		rule := deapexer.Rule("deapexer")
 		if expected, actual := []string{"myapex-arm64.apex"}, android.NormalizePathsForTesting(rule.Implicits); !reflect.DeepEqual(expected, actual) {
 			t.Errorf("expected: %q, found: %q", expected, actual)
@@ -4980,8 +4882,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5031,8 +4934,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5160,8 +5064,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5246,8 +5151,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5330,8 +5236,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -6301,7 +6208,7 @@
 	})
 	// Permission XML should point to the activated path of impl jar of java_sdk_library
 	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_myapex").Rule("java_sdk_xml")
-	ensureContains(t, sdkLibrary.RuleParams.Command, `<library name=\"foo\" file=\"/apex/myapex/javalib/foo.jar\"`)
+	ensureMatches(t, sdkLibrary.RuleParams.Command, `<library\\n\s+name=\\\"foo\\\"\\n\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"`)
 }
 
 func TestJavaSDKLibrary_WithinApex(t *testing.T) {
@@ -7132,6 +7039,7 @@
 				`, insert))
 			}
 		}),
+		dexpreopt.FixtureSetBootImageProfiles("art/build/boot/boot-image-profile.txt"),
 	).
 		ExtendWithErrorHandler(errorHandler).
 		RunTestWithBp(t, bp)
@@ -7139,6 +7047,75 @@
 	return result.TestContext
 }
 
+func TestDuplicateDeapexeresFromPrebuiltApexes(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithApexBuildComponents,
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			"Multiple installable prebuilt APEXes provide ambiguous deapexers: com.android.myapex and com.mycompany.android.myapex"))
+
+	bpBase := `
+		apex_set {
+			name: "com.android.myapex",
+			installable: true,
+			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
+			set: "myapex.apks",
+		}
+
+		apex_set {
+			name: "com.mycompany.android.myapex",
+			apex_name: "com.android.myapex",
+			installable: true,
+			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
+			set: "company-myapex.apks",
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "my-bootclasspath-fragment",
+			apex_available: ["com.android.myapex"],
+			%s
+		}
+	`
+
+	t.Run("java_import", func(t *testing.T) {
+		_ = preparers.RunTestWithBp(t, fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+`
+			java_import {
+				name: "libfoo",
+				jars: ["libfoo.jar"],
+				apex_available: ["com.android.myapex"],
+			}
+		`)
+	})
+
+	t.Run("java_sdk_library_import", func(t *testing.T) {
+		_ = preparers.RunTestWithBp(t, fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+`
+			java_sdk_library_import {
+				name: "libfoo",
+				public: {
+					jars: ["libbar.jar"],
+				},
+				apex_available: ["com.android.myapex"],
+			}
+		`)
+	})
+
+	t.Run("prebuilt_bootclasspath_fragment", func(t *testing.T) {
+		_ = preparers.RunTestWithBp(t, fmt.Sprintf(bpBase, `
+			image_name: "art",
+			contents: ["libfoo"],
+		`)+`
+			java_sdk_library_import {
+				name: "libfoo",
+				public: {
+					jars: ["libbar.jar"],
+				},
+				apex_available: ["com.android.myapex"],
+			}
+		`)
+	})
+}
+
 func TestUpdatable_should_set_min_sdk_version(t *testing.T) {
 	testApexError(t, `"myapex" .*: updatable: updatable APEXes should set min_sdk_version`, `
 		apex {
@@ -7170,6 +7147,23 @@
 	`)
 }
 
+func TestUpdatable_cannot_be_vendor_apex(t *testing.T) {
+	testApexError(t, `"myapex" .*: updatable: vendor APEXes are not updatable`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: true,
+			soc_specific: true,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+}
+
 func TestUpdatable_should_not_set_generate_classpaths_proto(t *testing.T) {
 	testApexError(t, `"mysystemserverclasspathfragment" .* it must not set generate_classpaths_proto to false`, `
 		apex {
@@ -7363,8 +7357,9 @@
 					annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 					metadata: "my-bootclasspath-fragment/metadata.csv",
 					index: "my-bootclasspath-fragment/index.csv",
-					stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-					all_flags: "my-bootclasspath-fragment/all-flags.csv",
+					signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+					filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+					filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 				},
 			}
 
@@ -7435,6 +7430,7 @@
 					apex_available: ["myapex"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "30",
 				}
 				java_library {
 					name: "nonbcp_lib2",
@@ -7443,9 +7439,11 @@
 					permitted_packages: ["a.b"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "30",
 				}
 				apex {
 					name: "myapex",
+					min_sdk_version: "30",
 					key: "myapex.key",
 					java_libs: ["bcp_lib1", "nonbcp_lib2"],
 					updatable: false,
@@ -7458,8 +7456,8 @@
 			},
 		},
 		{
-			name:          "Bootclasspath apex jar not satisfying allowed module packages.",
-			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar. Please jarjar or move code around.`,
+			name:          "Bootclasspath apex jar not satisfying allowed module packages on Q.",
+			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar with min_sdk < T. Please jarjar or move code around.`,
 			bp: `
 				java_library {
 					name: "bcp_lib1",
@@ -7468,6 +7466,7 @@
 					permitted_packages: ["foo.bar"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "29",
 				}
 				java_library {
 					name: "bcp_lib2",
@@ -7476,9 +7475,85 @@
 					permitted_packages: ["foo.bar", "bar.baz"],
 					sdk_version: "none",
 					system_modules: "none",
+					min_sdk_version: "29",
 				}
 				apex {
 					name: "myapex",
+					min_sdk_version: "29",
+					key: "myapex.key",
+					java_libs: ["bcp_lib1", "bcp_lib2"],
+					updatable: false,
+				}
+			`,
+			bootJars: []string{"bcp_lib1", "bcp_lib2"},
+			modulesPackages: map[string][]string{
+				"myapex": []string{
+					"foo.bar",
+				},
+			},
+		},
+		{
+			name:          "Bootclasspath apex jar not satisfying allowed module packages on R.",
+			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar with min_sdk < T. Please jarjar or move code around.`,
+			bp: `
+				java_library {
+					name: "bcp_lib1",
+					srcs: ["lib1/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "30",
+				}
+				java_library {
+					name: "bcp_lib2",
+					srcs: ["lib2/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar", "bar.baz"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "30",
+				}
+				apex {
+					name: "myapex",
+					min_sdk_version: "30",
+					key: "myapex.key",
+					java_libs: ["bcp_lib1", "bcp_lib2"],
+					updatable: false,
+				}
+			`,
+			bootJars: []string{"bcp_lib1", "bcp_lib2"},
+			modulesPackages: map[string][]string{
+				"myapex": []string{
+					"foo.bar",
+				},
+			},
+		},
+		{
+			name:          "Bootclasspath apex jar >= T not satisfying Q/R/S allowed module packages.",
+			expectedError: "",
+			bp: `
+				java_library {
+					name: "bcp_lib1",
+					srcs: ["lib1/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "current",
+				}
+				java_library {
+					name: "bcp_lib2",
+					srcs: ["lib2/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar", "bar.baz"],
+					sdk_version: "none",
+					system_modules: "none",
+					min_sdk_version: "current",
+				}
+				apex {
+					name: "myapex",
+					min_sdk_version: "current",
 					key: "myapex.key",
 					java_libs: ["bcp_lib1", "bcp_lib2"],
 					updatable: false,
@@ -7779,6 +7854,28 @@
 			name: "myapex",
 			key: "myapex.key",
 			updatable: false,
+			custom_sign_tool: "sign_myapex",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	apexKeysText := ctx.SingletonForTests("apex_keys_text")
+	content := apexKeysText.MaybeDescription("apexkeys.txt").BuildParams.Args["content"]
+	ensureContains(t, content, `name="myapex.apex" public_key="vendor/foo/devkeys/testkey.avbpubkey" private_key="vendor/foo/devkeys/testkey.pem" container_certificate="vendor/foo/devkeys/test.x509.pem" container_private_key="vendor/foo/devkeys/test.pk8" partition="system_ext" sign_tool="sign_myapex"`)
+}
+
+func TestApexKeysTxtOverrides(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			custom_sign_tool: "sign_myapex",
 		}
 
 		apex_key {
@@ -8355,6 +8452,293 @@
 	`)
 }
 
+func TestAndroidMk_DexpreoptBuiltInstalledForApex(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["foo.java"],
+			apex_available: ["myapex"],
+			installable: true,
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	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")
+}
+
+func TestAndroidMk_DexpreoptBuiltInstalledForApex_Prebuilt(t *testing.T) {
+	ctx := testApex(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_java_libs: ["foo"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: ["myapex"],
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+
+	prebuilt := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+	entriesList := android.AndroidMkEntriesForTest(t, ctx, prebuilt)
+	mainModuleEntries := entriesList[0]
+	android.AssertArrayString(t,
+		"LOCAL_REQUIRED_MODULES",
+		mainModuleEntries.EntryMap["LOCAL_REQUIRED_MODULES"],
+		[]string{
+			"foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex",
+			"foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex",
+		})
+}
+
+func TestAndroidMk_RequiredModules(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+			required: ["otherapex"],
+		}
+
+		apex {
+			name: "otherapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+			required: ["otherapex"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["foo.java"],
+			apex_available: ["myapex", "otherapex"],
+			installable: true,
+		}
+	`)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	var builder strings.Builder
+	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += otherapex")
+}
+
+func TestSdkLibraryCanHaveHigherMinSdkVersion(t *testing.T) {
+	preparer := android.GroupFixturePreparers(
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestWithAndroidBuildComponents,
+		dexpreopt.FixtureSetApexBootJars("myapex:mybootclasspathlib"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:mysystemserverclasspathlib"),
+	)
+
+	// Test java_sdk_library in bootclasspath_fragment may define higher min_sdk_version than the apex
+	t.Run("bootclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		preparer.RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				min_sdk_version: "30",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: ["mybootclasspathlib"],
+				apex_available: ["myapex"],
+			}
+
+			java_sdk_library {
+				name: "mybootclasspathlib",
+				srcs: ["mybootclasspathlib.java"],
+				apex_available: ["myapex"],
+				compile_dex: true,
+				unsafe_ignore_missing_latest_api: true,
+				min_sdk_version: "31",
+				static_libs: ["util"],
+			}
+
+			java_library {
+				name: "util",
+                srcs: ["a.java"],
+				apex_available: ["myapex"],
+				min_sdk_version: "31",
+				static_libs: ["another_util"],
+			}
+
+			java_library {
+				name: "another_util",
+                srcs: ["a.java"],
+				min_sdk_version: "31",
+				apex_available: ["myapex"],
+			}
+		`)
+	})
+
+	// Test java_sdk_library in systemserverclasspath_fragment may define higher min_sdk_version than the apex
+	t.Run("systemserverclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		preparer.RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+				min_sdk_version: "30",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			systemserverclasspath_fragment {
+				name: "mysystemserverclasspathfragment",
+				contents: ["mysystemserverclasspathlib"],
+				apex_available: ["myapex"],
+			}
+
+			java_sdk_library {
+				name: "mysystemserverclasspathlib",
+				srcs: ["mysystemserverclasspathlib.java"],
+				apex_available: ["myapex"],
+				compile_dex: true,
+				min_sdk_version: "32",
+				unsafe_ignore_missing_latest_api: true,
+				static_libs: ["util"],
+			}
+
+			java_library {
+				name: "util",
+                srcs: ["a.java"],
+				apex_available: ["myapex"],
+				min_sdk_version: "31",
+				static_libs: ["another_util"],
+			}
+
+			java_library {
+				name: "another_util",
+                srcs: ["a.java"],
+				min_sdk_version: "31",
+				apex_available: ["myapex"],
+			}
+		`)
+	})
+
+	t.Run("bootclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "mybootclasspathlib".*must set min_sdk_version`)).
+			RunTestWithBp(t, `
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					bootclasspath_fragments: ["mybootclasspathfragment"],
+					min_sdk_version: "30",
+					updatable: false,
+				}
+
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+
+				bootclasspath_fragment {
+					name: "mybootclasspathfragment",
+					contents: ["mybootclasspathlib"],
+					apex_available: ["myapex"],
+				}
+
+				java_sdk_library {
+					name: "mybootclasspathlib",
+					srcs: ["mybootclasspathlib.java"],
+					apex_available: ["myapex"],
+					compile_dex: true,
+					unsafe_ignore_missing_latest_api: true,
+				}
+		`)
+	})
+
+	t.Run("systemserverclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "mysystemserverclasspathlib".*must set min_sdk_version`)).
+			RunTestWithBp(t, `
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+					min_sdk_version: "30",
+					updatable: false,
+				}
+
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+
+				systemserverclasspath_fragment {
+					name: "mysystemserverclasspathfragment",
+					contents: ["mysystemserverclasspathlib"],
+					apex_available: ["myapex"],
+				}
+
+				java_sdk_library {
+					name: "mysystemserverclasspathlib",
+					srcs: ["mysystemserverclasspathlib.java"],
+					apex_available: ["myapex"],
+					compile_dex: true,
+					unsafe_ignore_missing_latest_api: true,
+				}
+		`)
+	})
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 3e19014..ce828e1 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -16,12 +16,15 @@
 
 import (
 	"fmt"
+	"path"
 	"sort"
 	"strings"
 	"testing"
 
 	"android/soong/android"
+	"android/soong/dexpreopt"
 	"android/soong/java"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -34,11 +37,14 @@
 )
 
 // Some additional files needed for the art apex.
-var prepareForTestWithArtApex = android.FixtureMergeMockFs(android.MockFS{
-	"com.android.art.avbpubkey":                          nil,
-	"com.android.art.pem":                                nil,
-	"system/sepolicy/apex/com.android.art-file_contexts": nil,
-})
+var prepareForTestWithArtApex = android.GroupFixturePreparers(
+	android.FixtureMergeMockFs(android.MockFS{
+		"com.android.art.avbpubkey":                          nil,
+		"com.android.art.pem":                                nil,
+		"system/sepolicy/apex/com.android.art-file_contexts": nil,
+	}),
+	dexpreopt.FixtureSetBootImageProfiles("art/build/boot/boot-image-profile.txt"),
+)
 
 func TestBootclasspathFragments(t *testing.T) {
 	result := android.GroupFixturePreparers(
@@ -407,6 +413,7 @@
 		).RunTest(t)
 
 		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
 			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
@@ -436,6 +443,24 @@
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image disable generate profile", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+			dexpreopt.FixtureDisableGenerateProfile(true),
+		).RunTest(t)
+
+		files := getFiles(t, result.TestContext, "com.android.art", "android_common_com.android.art_image")
+		for _, file := range files {
+			matched, _ := path.Match("etc/boot-image.prof", file.path)
+			android.AssertBoolEquals(t, "\"etc/boot-image.prof\" should not be in the APEX", matched, false)
+		}
+	})
+
 	t.Run("boot image files with preferred prebuilt", func(t *testing.T) {
 		result := android.GroupFixturePreparers(
 			commonPreparer,
@@ -450,6 +475,7 @@
 		).RunTest(t)
 
 		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
 			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
@@ -541,7 +567,7 @@
 }
 
 func TestBootclasspathFragmentInPrebuiltArtApex(t *testing.T) {
-	result := android.GroupFixturePreparers(
+	preparers := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
 
@@ -552,7 +578,9 @@
 
 		// Configure some libraries in the art bootclasspath_fragment.
 		java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
-	).RunTestWithBp(t, `
+	)
+
+	bp := `
 		prebuilt_apex {
 			name: "com.android.art",
 			arch: {
@@ -598,22 +626,45 @@
 				all_flags: "mybootclasspathfragment/all-flags.csv",
 			},
 		}
-	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
-		`com.android.art.apex.selector`,
-		`prebuilt_mybootclasspathfragment`,
+		// A prebuilt apex with the same apex_name that shouldn't interfere when it isn't enabled.
+		prebuilt_apex {
+			name: "com.mycompany.android.art",
+			apex_name: "com.android.art",
+			%s
+			src: "com.mycompany.android.art.apex",
+			exported_bootclasspath_fragments: ["mybootclasspathfragment"],
+		}
+	`
+
+	t.Run("disabled alternative APEX", func(t *testing.T) {
+		result := preparers.RunTestWithBp(t, fmt.Sprintf(bp, "enabled: false,"))
+
+		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+			`com.android.art.apex.selector`,
+			`prebuilt_mybootclasspathfragment`,
+		})
+
+		java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_com.android.art", []string{
+			`com.android.art.deapexer`,
+			`dex2oatd`,
+			`prebuilt_bar`,
+			`prebuilt_foo`,
+		})
+
+		module := result.ModuleForTests("mybootclasspathfragment", "android_common_com.android.art")
+		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
+
+		// Check that the right deapexer module was chosen for a boot image.
+		param := module.Output("out/soong/test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot.art")
+		android.AssertStringDoesContain(t, "didn't find the expected deapexer in the input path", param.Input.String(), "/com.android.art.deapexer")
 	})
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_com.android.art", []string{
-		`com.android.art.deapexer`,
-		`dex2oatd`,
-		`prebuilt_bar`,
-		`prebuilt_foo`,
+	t.Run("enabled alternative APEX", func(t *testing.T) {
+		preparers.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			"Multiple installable prebuilt APEXes provide ambiguous deapexers: com.android.art and com.mycompany.android.art")).
+			RunTestWithBp(t, fmt.Sprintf(bp, ""))
 	})
-
-	module := result.ModuleForTests("mybootclasspathfragment", "android_common_com.android.art")
-	checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 }
 
 // checkCopiesToPredefinedLocationForArt checks that the supplied modules are copied to the
@@ -737,7 +788,7 @@
 
 func getDexJarPath(result *android.TestResult, name string) string {
 	module := result.Module(name, "android_common")
-	return module.(java.UsesLibraryDependency).DexJarBuildPath().RelativeToTop().String()
+	return module.(java.UsesLibraryDependency).DexJarBuildPath().Path().RelativeToTop().String()
 }
 
 // TestBootclasspathFragment_HiddenAPIList checks to make sure that the correct parameters are
diff --git a/apex/builder.go b/apex/builder.go
index 5baa5c0..ea25537 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -17,7 +17,6 @@
 import (
 	"encoding/json"
 	"fmt"
-	"path"
 	"path/filepath"
 	"runtime"
 	"sort"
@@ -66,24 +65,13 @@
 	pctx.HostBinToolVariable("extract_apks", "extract_apks")
 	pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
 	pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
+	pctx.HostBinToolVariable("make_erofs", "make_erofs")
 	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
+	pctx.HostBinToolVariable("dexdeps", "dexdeps")
 	pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
 }
 
 var (
-	// Create a canned fs config file where all files and directories are
-	// by default set to (uid/gid/mode) = (1000/1000/0644)
-	// TODO(b/113082813) make this configurable using config.fs syntax
-	generateFsConfig = pctx.StaticRule("generateFsConfig", blueprint.RuleParams{
-		Command: `( set -e; echo '/ 1000 1000 0755' ` +
-			`&& for i in ${ro_paths}; do echo "/$$i 1000 1000 0644"; done ` +
-			`&& for i in  ${exec_paths}; do echo "/$$i 0 2000 0755"; done ` +
-			`&& ( tr ' ' '\n' <${out}.apklist | for i in ${apk_paths}; do read apk; echo "/$$i 0 2000 0755"; zipinfo -1 $$apk | sed "s:\(.*\):/$$i/\1 1000 1000 0644:"; done ) ) > ${out}`,
-		Description:    "fs_config ${out}",
-		Rspfile:        "$out.apklist",
-		RspfileContent: "$in",
-	}, "ro_paths", "exec_paths", "apk_paths")
-
 	apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{
 		Command: `rm -f $out && ${jsonmodify} $in ` +
 			`-a provideNativeLibs ${provideNativeLibs} ` +
@@ -106,7 +94,7 @@
 		Description: "convert ${in}=>${out}",
 	})
 
-	// TODO(b/113233103): make sure that file_contexts is sane, i.e., validate
+	// TODO(b/113233103): make sure that file_contexts is as expected, i.e., validate
 	// against the binary policy using sefcontext_compiler -p <policy>.
 
 	// TODO(b/114327326): automate the generation of file_contexts
@@ -121,7 +109,7 @@
 			`--payload_type image ` +
 			`--key ${key} ${opt_flags} ${image_dir} ${out} `,
 		CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
-			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}",
+			"${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}",
@@ -256,14 +244,24 @@
 // labeled as system_file.
 func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
 	var fileContexts android.Path
+	var fileContextsDir string
 	if a.properties.File_contexts == nil {
 		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
 	} else {
+		if m, t := android.SrcIsModuleWithTag(*a.properties.File_contexts); m != "" {
+			otherModule := android.GetModuleFromPathDep(ctx, m, t)
+			fileContextsDir = ctx.OtherModuleDir(otherModule)
+		}
 		fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
 	}
+	if fileContextsDir == "" {
+		fileContextsDir = filepath.Dir(fileContexts.String())
+	}
+	fileContextsDir += string(filepath.Separator)
+
 	if a.Platform() {
-		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
-			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
+		if !strings.HasPrefix(fileContextsDir, "system/sepolicy/") {
+			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but found in  %q", fileContextsDir)
 		}
 	}
 	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
@@ -403,18 +401,37 @@
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
+	apexName := proptools.StringDefault(a.properties.Apex_name, a.BaseModuleName())
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1: copy built files to appropriate directories under the image directory
 
 	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 
+	installSymbolFiles := !ctx.Config().KatiEnabled() || a.ExportedToMake()
+
+	// 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)
 	for _, fi := range a.filesInfo {
 		destPath := imageDir.Join(ctx, fi.path()).String()
-
 		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
@@ -422,6 +439,8 @@
 		}
 		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
 
+		installMapPath := fi.builtFile
+
 		// Copy the built file to the directory. But if the symlink optimization is turned
 		// on, place a symlink to the corresponding file in /system partition instead.
 		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
@@ -429,20 +448,38 @@
 			pathOnDevice := filepath.Join("/system", fi.path())
 			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
 		} else {
+			var installedPath android.InstallPath
 			if fi.class == appSet {
 				copyCommands = append(copyCommands,
-					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, fi.builtFile.String()))
+					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir,
+						fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String()))
+				if installSymbolFiles {
+					installedPath = ctx.InstallFileWithExtraFilesZip(pathWhenActivated.Join(ctx, fi.installDir),
+						fi.stem(), fi.builtFile, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs())
+				}
 			} else {
 				copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath)
+				if installSymbolFiles {
+					installedPath = ctx.InstallFile(pathWhenActivated.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
+				}
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
-		}
+			if installSymbolFiles {
+				implicitInputs = append(implicitInputs, installedPath)
+			}
 
-		// Create additional symlinks pointing the file inside the APEX (if any). Note that
-		// this is independent from the symlink optimization.
-		for _, symlinkPath := range fi.symlinkPaths() {
-			symlinkDest := imageDir.Join(ctx, symlinkPath).String()
-			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
+			// Create additional symlinks pointing the file inside the APEX (if any). Note that
+			// this is independent from the symlink optimization.
+			for _, symlinkPath := range fi.symlinkPaths() {
+				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)
+					implicitInputs = append(implicitInputs, installedSymlink)
+				}
+			}
+
+			installMapPath = installedPath
 		}
 
 		// Copy the test files (if any)
@@ -459,8 +496,21 @@
 			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
 			implicitInputs = append(implicitInputs, d.SrcPath)
 		}
+
+		installMapSet[installMapPath.String()+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
 	}
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
+	if installSymbolFiles {
+		installedManifest := ctx.InstallFile(pathWhenActivated, "apex_manifest.pb", a.manifestPbOut)
+		installedKey := ctx.InstallFile(pathWhenActivated, "apex_pubkey", a.publicKeyFile)
+		implicitInputs = append(implicitInputs, installedManifest, installedKey)
+	}
+
+	if len(installMapSet) > 0 {
+		var installs []string
+		installs = append(installs, android.SortedStringKeys(installMapSet)...)
+		a.SetLicenseInstallMap(installs)
+	}
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
@@ -514,61 +564,17 @@
 	}
 
 	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
-	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
+	outHostBinDir := ctx.Config().HostToolPath(ctx, "").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
 	// Figure out if need to compress apex.
 	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps()
 	if apexType == imageApex {
+
 		////////////////////////////////////////////////////////////////////////////////////
 		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
 		// in this APEX. The file will be used by apexer in later steps.
-		// TODO(jiyong): make this as a function
-		// TODO(jiyong): use the RuleBuilder
-		var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
-		var executablePaths []string // this also includes dirs
-		var extractedAppSetPaths android.Paths
-		var extractedAppSetDirs []string
-		for _, f := range a.filesInfo {
-			pathInApex := f.path()
-			if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
-				executablePaths = append(executablePaths, pathInApex)
-				for _, d := range f.dataPaths {
-					readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel()))
-				}
-				for _, s := range f.symlinks {
-					executablePaths = append(executablePaths, filepath.Join(f.installDir, s))
-				}
-			} else if f.class == appSet {
-				extractedAppSetPaths = append(extractedAppSetPaths, f.builtFile)
-				extractedAppSetDirs = append(extractedAppSetDirs, f.installDir)
-			} else {
-				readOnlyPaths = append(readOnlyPaths, pathInApex)
-			}
-			dir := f.installDir
-			for !android.InList(dir, executablePaths) && dir != "" {
-				executablePaths = append(executablePaths, dir)
-				dir, _ = filepath.Split(dir) // move up to the parent
-				if len(dir) > 0 {
-					// remove trailing slash
-					dir = dir[:len(dir)-1]
-				}
-			}
-		}
-		sort.Strings(readOnlyPaths)
-		sort.Strings(executablePaths)
-		cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        generateFsConfig,
-			Output:      cannedFsConfig,
-			Description: "generate fs config",
-			Inputs:      extractedAppSetPaths,
-			Args: map[string]string{
-				"ro_paths":   strings.Join(readOnlyPaths, " "),
-				"exec_paths": strings.Join(executablePaths, " "),
-				"apk_paths":  strings.Join(extractedAppSetDirs, " "),
-			},
-		})
+		cannedFsConfig := a.buildCannedFsConfig(ctx)
 		implicitInputs = append(implicitInputs, cannedFsConfig)
 
 		////////////////////////////////////////////////////////////////////////////////////
@@ -697,24 +703,38 @@
 				"readelf":   "${config.ClangBin}/llvm-readelf",
 			},
 		})
-		a.apisUsedByModuleFile = apisUsedbyOutputFile
+		a.nativeApisUsedByModuleFile = apisUsedbyOutputFile
 
-		var libNames []string
+		var nativeLibNames []string
 		for _, f := range a.filesInfo {
 			if f.class == nativeSharedLib {
-				libNames = append(libNames, f.stem())
+				nativeLibNames = append(nativeLibNames, f.stem())
 			}
 		}
 		apisBackedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_backing.txt")
-		ndkLibraryList := android.PathForSource(ctx, "system/core/rootdir/etc/public.libraries.android.txt")
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")).
 			Output(apisBackedbyOutputFile).
-			Input(ndkLibraryList).
-			Flags(libNames)
+			Flags(nativeLibNames)
 		rule.Build("ndk_backedby_list", "Generate API libraries backed by Apex")
-		a.apisBackedByModuleFile = apisBackedbyOutputFile
+		a.nativeApisBackedByModuleFile = apisBackedbyOutputFile
+
+		var javaLibOrApkPath []android.Path
+		for _, f := range a.filesInfo {
+			if f.class == javaSharedLib || f.class == app {
+				javaLibOrApkPath = append(javaLibOrApkPath, f.builtFile)
+			}
+		}
+		javaApiUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.xml")
+		javaUsedByRule := android.NewRuleBuilder(pctx, ctx)
+		javaUsedByRule.Command().
+			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_java_usedby_apex.sh")).
+			BuiltTool("dexdeps").
+			Output(javaApiUsedbyOutputFile).
+			Inputs(javaLibOrApkPath)
+		javaUsedByRule.Build("java_usedby_list", "Generate Java APIs used by Apex")
+		a.javaApisUsedByModuleFile = javaApiUsedbyOutputFile
 
 		bundleConfig := a.buildBundleConfig(ctx)
 
@@ -786,7 +806,7 @@
 
 	if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) {
 		a.isCompressed = true
-		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
+		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix+".unsigned")
 
 		compressRule := android.NewRuleBuilder(pctx, ctx)
 		compressRule.Command().
@@ -800,7 +820,7 @@
 			FlagWithOutput("--output ", unsignedCompressedOutputFile)
 		compressRule.Build("compressRule", "Generate unsigned compressed APEX file")
 
-		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex")
+		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix)
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
 			args["outCommaList"] = signedCompressedOutputFile.String()
 		}
@@ -815,49 +835,55 @@
 		a.outputFile = signedCompressedOutputFile
 	}
 
-	// Install to $OUT/soong/{target,host}/.../apex
-	if a.installable() {
-		ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
+	installSuffix := suffix
+	if a.isCompressed {
+		installSuffix = imageCapexSuffix
 	}
 
+	// Install to $OUT/soong/{target,host}/.../apex.
+	a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile,
+		a.compatSymlinks.Paths()...)
+
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
 }
 
-// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
-type flattenedApexContext struct {
-	android.ModuleContext
-}
-
-func (c *flattenedApexContext) InstallBypassMake() bool {
-	return true
-}
-
 // buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
 // single output file. It is a phony target for all the files under /system/apex/<name> directory.
 // This function creates the installation rules for the files.
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
 	bundleName := a.Name()
+	installedSymlinks := append(android.InstallPaths(nil), a.compatSymlinks...)
 	if a.installable() {
 		for _, fi := range a.filesInfo {
 			dir := filepath.Join("apex", bundleName, fi.installDir)
-			target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
-			for _, sym := range fi.symlinks {
-				ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
+			installDir := android.PathForModuleInstall(ctx, dir)
+			if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
+				// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
+				pathOnDevice := filepath.Join("/system", fi.path())
+				installedSymlinks = append(installedSymlinks,
+					ctx.InstallAbsoluteSymlink(installDir, fi.stem(), pathOnDevice))
+			} else {
+				target := ctx.InstallFile(installDir, fi.stem(), fi.builtFile)
+				for _, sym := range fi.symlinks {
+					installedSymlinks = append(installedSymlinks,
+						ctx.InstallSymlink(installDir, sym, target))
+				}
 			}
 		}
+
+		// Create install rules for the files added in GenerateAndroidBuildActions after
+		// buildFlattenedApex is called.  Add the links to system libs (if any) as dependencies
+		// of the apex_manifest.pb file since it is always present.
+		dir := filepath.Join("apex", bundleName)
+		installDir := android.PathForModuleInstall(ctx, dir)
+		ctx.InstallFile(installDir, "apex_manifest.pb", a.manifestPbOut, installedSymlinks.Paths()...)
+		ctx.InstallFile(installDir, "apex_pubkey", a.publicKeyFile)
 	}
 
 	a.fileContexts = a.buildFileContexts(ctx)
 
-	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
-	// to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
-	// `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
-	// correct path to the flattened APEX (as its contents is installed by Make, not Soong).
-	// TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
-	// it and it actually points to a path that can never be built. Remove this.
-	factx := flattenedApexContext{ctx}
-	a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+	a.outputFile = android.PathForModuleInstall(ctx, "apex", bundleName)
 }
 
 // getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
@@ -989,3 +1015,65 @@
 
 	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
 }
+
+func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
+	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
+	var executablePaths []string // this also includes dirs
+	var appSetDirs []string
+	appSetFiles := make(map[string]android.Path)
+	for _, f := range a.filesInfo {
+		pathInApex := f.path()
+		if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
+			executablePaths = append(executablePaths, pathInApex)
+			for _, d := range f.dataPaths {
+				readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel()))
+			}
+			for _, s := range f.symlinks {
+				executablePaths = append(executablePaths, filepath.Join(f.installDir, s))
+			}
+		} else if f.class == appSet {
+			appSetDirs = append(appSetDirs, f.installDir)
+			appSetFiles[f.installDir] = f.builtFile
+		} else {
+			readOnlyPaths = append(readOnlyPaths, pathInApex)
+		}
+		dir := f.installDir
+		for !android.InList(dir, executablePaths) && dir != "" {
+			executablePaths = append(executablePaths, dir)
+			dir, _ = filepath.Split(dir) // move up to the parent
+			if len(dir) > 0 {
+				// remove trailing slash
+				dir = dir[:len(dir)-1]
+			}
+		}
+	}
+	sort.Strings(readOnlyPaths)
+	sort.Strings(executablePaths)
+	sort.Strings(appSetDirs)
+
+	cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
+	builder := android.NewRuleBuilder(pctx, ctx)
+	cmd := builder.Command()
+	cmd.Text("(")
+	cmd.Text("echo '/ 1000 1000 0755';")
+	for _, p := range readOnlyPaths {
+		cmd.Textf("echo '/%s 1000 1000 0644';", p)
+	}
+	for _, p := range executablePaths {
+		cmd.Textf("echo '/%s 0 2000 0755';", p)
+	}
+	for _, dir := range appSetDirs {
+		cmd.Textf("echo '/%s 0 2000 0755';", dir)
+		file := appSetFiles[dir]
+		cmd.Text("zipinfo -1").Input(file).Textf(`| sed "s:\(.*\):/%s/\1 1000 1000 0644:";`, dir)
+	}
+	// Custom fs_config is "appended" to the last so that entries from the file are preferred
+	// over default ones set above.
+	if a.properties.Canned_fs_config != nil {
+		cmd.Text("cat").Input(android.PathForModuleSrc(ctx, *a.properties.Canned_fs_config))
+	}
+	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
+	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
+
+	return cannedFsConfig.OutputPath
+}
diff --git a/apex/deapexer.go b/apex/deapexer.go
index c70da15..8c9030a 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -15,6 +15,8 @@
 package apex
 
 import (
+	"strings"
+
 	"android/soong/android"
 )
 
@@ -75,6 +77,17 @@
 	inputApex android.Path
 }
 
+// Returns the name of the deapexer module corresponding to an APEX module with the given name.
+func deapexerModuleName(apexModuleName string) string {
+	return apexModuleName + ".deapexer"
+}
+
+// Returns the name of the APEX module corresponding to an deapexer module with
+// the given name. This reverses deapexerModuleName.
+func apexModuleName(deapexerModuleName string) string {
+	return strings.TrimSuffix(deapexerModuleName, ".deapexer")
+}
+
 func privateDeapexerFactory() android.Module {
 	module := &Deapexer{}
 	module.AddProperties(&module.properties, &module.selectedApexProperties)
@@ -97,7 +110,7 @@
 	// Create and remember the directory into which the .apex file's contents will be unpacked.
 	deapexerOutput := android.PathForModuleOut(ctx, "deapexer")
 
-	exports := make(map[string]android.Path)
+	exports := make(map[string]android.WritablePath)
 
 	// Create mappings from apex relative path to the extracted file's path.
 	exportedPaths := make(android.Paths, 0, len(exports))
@@ -113,7 +126,8 @@
 	// apex relative path to extracted file path available for other modules.
 	if len(exports) > 0 {
 		// Make the information available for other modules.
-		ctx.SetProvider(android.DeapexerProvider, android.NewDeapexerInfo(exports))
+		di := android.NewDeapexerInfo(apexModuleName(ctx.ModuleName()), exports)
+		ctx.SetProvider(android.DeapexerProvider, di)
 
 		// Create a sorted list of the files that this exports.
 		exportedPaths = android.SortedUniquePaths(exportedPaths)
@@ -131,6 +145,6 @@
 		for _, p := range exportedPaths {
 			command.Output(p.(android.WritablePath))
 		}
-		builder.Build("deapexer", "deapex "+ctx.ModuleName())
+		builder.Build("deapexer", "deapex "+apexModuleName(ctx.ModuleName()))
 	}
 }
diff --git a/apex/key.go b/apex/key.go
index 468bb8a..829410e 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -34,8 +34,6 @@
 func registerApexKeyBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
 	ctx.RegisterSingletonType("apex_keys_text", apexKeysTextFactory)
-
-	android.RegisterBp2BuildMutator("apex_key", ApexKeyBp2Build)
 }
 
 type apexKey struct {
@@ -123,13 +121,18 @@
 		containerCertificate string
 		containerPrivateKey  string
 		partition            string
+		signTool             string
 	}
 	toString := func(e apexKeyEntry) string {
-		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\n"
+		signTool := ""
+		if e.signTool != "" {
+			signTool = fmt.Sprintf(" sign_tool=%q", e.signTool)
+		}
+		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q%s\n"
 		if e.presigned {
-			return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition)
+			return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition, signTool)
 		} else {
-			return fmt.Sprintf(format, e.name, e.publicKey, e.privateKey, e.containerCertificate, e.containerPrivateKey, e.partition)
+			return fmt.Sprintf(format, e.name, e.publicKey, e.privateKey, e.containerCertificate, e.containerPrivateKey, e.partition, signTool)
 		}
 	}
 
@@ -145,6 +148,7 @@
 				containerCertificate: pem.String(),
 				containerPrivateKey:  key.String(),
 				partition:            m.PartitionTag(ctx.DeviceConfig()),
+				signTool:             proptools.String(m.properties.Custom_sign_tool),
 			}
 		}
 	})
@@ -203,20 +207,9 @@
 	Private_key bazel.LabelAttribute
 }
 
-func ApexKeyBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*apexKey)
-	if !ok {
-		// Not an APEX key
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-	if ctx.ModuleType() != "apex_key" {
-		return
-	}
-
-	apexKeyBp2BuildInternal(ctx, module)
+// ConvertWithBp2build performs conversion apexKey for bp2build
+func (m *apexKey) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	apexKeyBp2BuildInternal(ctx, m)
 }
 
 func apexKeyBp2BuildInternal(ctx android.TopDownMutatorContext, module *apexKey) {
@@ -240,5 +233,5 @@
 		Bzl_load_location: "//build/bazel/rules:apex_key.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 513ddc0..06c39ee 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/java"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -167,6 +168,76 @@
 	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
+// TestPlatformBootclasspath_LegacyPrebuiltFragment verifies that the
+// prebuilt_bootclasspath_fragment falls back to using the complete stub-flags/all-flags if the
+// filtered files are not provided.
+//
+// TODO: Remove once all prebuilts use the filtered_... properties.
+func TestPlatformBootclasspath_LegacyPrebuiltFragment(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
+		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
+		// because AlwaysUsePrebuiltSdks() is true.
+		java_sdk_library_import {
+			name: "foo",
+			prefer: false,
+			shared_library: false,
+			permitted_packages: ["foo"],
+			public: {
+				jars: ["sdk_library/public/foo-stubs.jar"],
+				stub_srcs: ["sdk_library/public/foo_stub_sources"],
+				current_api: "sdk_library/public/foo.txt",
+				removed_api: "sdk_library/public/foo-removed.txt",
+				sdk_version: "current",
+			},
+			apex_available: ["myapex"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "prebuilt-stub-flags.csv",
+				annotation_flags: "prebuilt-annotation-flags.csv",
+				metadata: "prebuilt-metadata.csv",
+				index: "prebuilt-index.csv",
+				all_flags: "prebuilt-all-flags.csv",
+			},
+		}
+
+		platform_bootclasspath {
+			name: "myplatform-bootclasspath",
+			fragments: [
+				{
+					apex: "myapex",
+					module:"mybootclasspath-fragment",
+				},
+			],
+		}
+`,
+	)
+
+	pbcp := result.Module("myplatform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, java.MonolithicHiddenAPIInfoProvider).(java.MonolithicHiddenAPIInfo)
+
+	android.AssertArrayString(t, "stub flags", []string{"prebuilt-stub-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"prebuilt-all-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+}
+
 func TestPlatformBootclasspathDependencies(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
@@ -325,31 +396,15 @@
 }
 
 // TestPlatformBootclasspath_AlwaysUsePrebuiltSdks verifies that the build does not fail when
-// AlwaysUsePrebuiltSdk() returns true. The structure of the modules in this test matches what
-// currently exists in some places in the Android build but it is not the intended structure. It is
-// in fact an invalid structure that should cause build failures. However, fixing that structure
-// will take too long so in the meantime this tests the workarounds to avoid build breakages.
-//
-// The main issues with this structure are:
-// 1. There is no prebuilt_bootclasspath_fragment referencing the "foo" java_sdk_library_import.
-// 2. There is no prebuilt_apex/apex_set which makes the dex implementation jar available to the
-//    prebuilt_bootclasspath_fragment and the "foo" java_sdk_library_import.
-//
-// Together these cause the following symptoms:
-// 1. The "foo" java_sdk_library_import does not have a dex implementation jar.
-// 2. The "foo" java_sdk_library_import does not have a myapex variant.
-//
-// TODO(b/179354495): Fix the structure in this test once the main Android build has been fixed.
+// AlwaysUsePrebuiltSdk() returns true.
 func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
 		// Configure two libraries, the first is a java_sdk_library whose prebuilt will be used because
-		// of AlwaysUsePrebuiltsSdk() but does not have an appropriate apex variant and does not provide
-		// a boot dex jar. The second is a normal library that is unaffected. The order matters because
-		// if the dependency on myapex:foo is filtered out because of either of those conditions then
-		// the dependencies resolved by the platform_bootclasspath will not match the configured list
-		// and so will fail the test.
+		// of AlwaysUsePrebuiltsSdk(). The second is a normal library that is unaffected. The order
+		// matters, so that the dependencies resolved by the platform_bootclasspath matches the
+		// configured list.
 		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -394,6 +449,12 @@
 			permitted_packages: ["foo"],
 		}
 
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
 		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
 		// because AlwaysUsePrebuiltSdks() is true.
 		java_sdk_library_import {
@@ -423,6 +484,23 @@
 			],
 		}
 
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "",
+				annotation_flags: "",
+				metadata: "",
+				index: "",
+				all_flags: "",
+			},
+		}
+
 		platform_bootclasspath {
 			name: "myplatform-bootclasspath",
 			fragments: [
@@ -437,7 +515,7 @@
 
 	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
 		// The configured contents of BootJars.
-		"platform:prebuilt_foo", // Note: This is the platform not myapex variant.
+		"myapex:prebuilt_foo",
 		"myapex:bar",
 	})
 
@@ -456,16 +534,15 @@
 
 		// The platform_bootclasspath intentionally adds dependencies on both source and prebuilt
 		// modules when available as it does not know which one will be preferred.
-		//
-		// The source module has an APEX variant but the prebuilt does not.
 		"myapex:foo",
-		"platform:prebuilt_foo",
+		"myapex:prebuilt_foo",
 
 		// Only a source module exists.
 		"myapex:bar",
 
 		// The fragments.
 		"myapex:mybootclasspath-fragment",
+		"myapex:prebuilt_mybootclasspath-fragment",
 	})
 }
 
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 1bb0fb5..02d8075 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
@@ -54,18 +55,17 @@
 
 	installDir      android.InstallPath
 	installFilename string
+	installedFile   android.InstallPath
 	outputApex      android.WritablePath
 
 	// A list of apexFile objects created in prebuiltCommon.initApexFilesForAndroidMk which are used
 	// to create make modules in prebuiltCommon.AndroidMkEntries.
 	apexFilesForAndroidMk []apexFile
 
-	// list of commands to create symlinks for backward compatibility.
-	// these commands will be attached as LOCAL_POST_INSTALL_CMD
-	compatSymlinks []string
+	// Installed locations of symlinks for backward compatibility.
+	compatSymlinks android.InstallPaths
 
-	hostRequired        []string
-	postInstallCommands []string
+	hostRequired []string
 }
 
 type sanitizedPrebuilt interface {
@@ -103,6 +103,10 @@
 	// List of bootclasspath fragments inside this prebuilt APEX bundle and for which this APEX
 	// bundle will create an APEX variant.
 	Exported_bootclasspath_fragments []string
+
+	// List of systemserverclasspath fragments inside this prebuilt APEX bundle and for which this
+	// APEX bundle will create an APEX variant.
+	Exported_systemserverclasspath_fragments []string
 }
 
 // initPrebuiltCommon initializes the prebuiltCommon structure and performs initialization of the
@@ -134,10 +138,6 @@
 	// to build the prebuilts themselves.
 	forceDisable = forceDisable || ctx.Config().UnbundledBuild()
 
-	// Force disable the prebuilts when coverage is enabled.
-	forceDisable = forceDisable || ctx.DeviceConfig().NativeCoverageEnabled()
-	forceDisable = forceDisable || ctx.Config().IsEnvTrue("EMMA_INSTRUMENT")
-
 	// b/137216042 don't use prebuilts when address sanitizer is on, unless the prebuilt has a sanitized source
 	sanitized := ctx.Module().(sanitizedPrebuilt)
 	forceDisable = forceDisable || (android.InList("address", ctx.Config().SanitizeDevice()) && !sanitized.hasSanitizedSource("address"))
@@ -174,20 +174,30 @@
 		tag := ctx.OtherModuleDependencyTag(child)
 
 		name := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(child))
-		if java.IsBootclasspathFragmentContentDepTag(tag) || tag == exportedJavaLibTag {
+		if java.IsBootclasspathFragmentContentDepTag(tag) ||
+			java.IsSystemServerClasspathFragmentContentDepTag(tag) || tag == exportedJavaLibTag {
 			// If the exported java module provides a dex jar path then add it to the list of apexFiles.
-			path := child.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-			if path != nil {
-				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, apexFile{
+			path := child.(interface {
+				DexJarBuildPath() java.OptionalDexJarPath
+			}).DexJarBuildPath()
+			if path.IsSet() {
+				af := apexFile{
 					module:              child,
 					moduleDir:           ctx.OtherModuleDir(child),
 					androidMkModuleName: name,
-					builtFile:           path,
+					builtFile:           path.Path(),
 					class:               javaSharedLib,
-				})
+				}
+				if module, ok := child.(java.DexpreopterInterface); ok {
+					for _, install := range module.DexpreoptBuiltInstalledForApex() {
+						af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+					}
+				}
+				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, af)
 			}
-		} else if tag == exportedBootclasspathFragmentTag {
-			// Visit the children of the bootclasspath_fragment.
+		} else if tag == exportedBootclasspathFragmentTag ||
+			tag == exportedSystemserverclasspathFragmentTag {
+			// Visit the children of the bootclasspath_fragment and systemserver_fragment.
 			return true
 		}
 
@@ -195,6 +205,14 @@
 	})
 }
 
+func (p *prebuiltCommon) addRequiredModules(entries *android.AndroidMkEntries) {
+	for _, fi := range p.apexFilesForAndroidMk {
+		entries.AddStrings("LOCAL_REQUIRED_MODULES", fi.requiredModuleNames...)
+		entries.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", fi.targetRequiredModuleNames...)
+		entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", fi.hostRequiredModuleNames...)
+	}
+}
+
 func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := []android.AndroidMkEntries{
 		{
@@ -204,15 +222,13 @@
 			Host_required: p.hostRequired,
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+					entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
 					entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
+					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", p.installedFile)
+					entries.SetString("LOCAL_SOONG_INSTALL_PAIRS", p.outputApex.String()+":"+p.installedFile.String())
 					entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
 					entries.AddStrings("LOCAL_OVERRIDES_MODULES", p.prebuiltCommonProperties.Overrides...)
-					postInstallCommands := append([]string{}, p.postInstallCommands...)
-					postInstallCommands = append(postInstallCommands, p.compatSymlinks...)
-					if len(postInstallCommands) > 0 {
-						entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(postInstallCommands, " && "))
-					}
+					p.addRequiredModules(entries)
 				},
 			},
 		},
@@ -240,23 +256,15 @@
 		Include:      "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
+				entries.SetString("LOCAL_SOONG_INSTALLED_MODULE", filepath.Join(p.installDir.String(), fi.stem()))
+				entries.SetString("LOCAL_SOONG_INSTALL_PAIRS",
+					fi.builtFile.String()+":"+filepath.Join(p.installDir.String(), fi.stem()))
 
 				// soong_java_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .jar  Therefore
 				// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 				// we will have foo.jar.jar
 				entries.SetString("LOCAL_MODULE_STEM", strings.TrimSuffix(fi.stem(), ".jar"))
-				var classesJar android.Path
-				var headerJar android.Path
-				if javaModule, ok := fi.module.(java.ApexDependency); ok {
-					classesJar = javaModule.ImplementationAndResourcesJars()[0]
-					headerJar = javaModule.HeaderJars()[0]
-				} else {
-					classesJar = fi.builtFile
-					headerJar = fi.builtFile
-				}
-				entries.SetString("LOCAL_SOONG_CLASSES_JAR", classesJar.String())
-				entries.SetString("LOCAL_SOONG_HEADER_JAR", headerJar.String())
 				entries.SetString("LOCAL_SOONG_DEX_JAR", fi.builtFile.String())
 				entries.SetString("LOCAL_DEX_PREOPT", "false")
 			},
@@ -294,21 +302,31 @@
 	}
 }
 
+func (p *prebuiltCommon) getExportedDependencies() map[string]exportedDependencyTag {
+	dependencies := make(map[string]exportedDependencyTag)
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_java_libs {
+		dependencies[dep] = exportedJavaLibTag
+	}
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_bootclasspath_fragments {
+		dependencies[dep] = exportedBootclasspathFragmentTag
+	}
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_systemserverclasspath_fragments {
+		dependencies[dep] = exportedSystemserverclasspathFragmentTag
+	}
+
+	return dependencies
+}
+
 // prebuiltApexContentsDeps adds dependencies onto the prebuilt apex module's contents.
 func (p *prebuiltCommon) prebuiltApexContentsDeps(ctx android.BottomUpMutatorContext) {
 	module := ctx.Module()
-	// Add dependencies onto the java modules that represent the java libraries that are provided by
-	// and exported from this prebuilt apex.
-	for _, exported := range p.prebuiltCommonProperties.Exported_java_libs {
-		dep := android.PrebuiltNameFromSource(exported)
-		ctx.AddDependency(module, exportedJavaLibTag, dep)
-	}
 
-	// Add dependencies onto the bootclasspath fragment modules that are exported from this prebuilt
-	// apex.
-	for _, exported := range p.prebuiltCommonProperties.Exported_bootclasspath_fragments {
-		dep := android.PrebuiltNameFromSource(exported)
-		ctx.AddDependency(module, exportedBootclasspathFragmentTag, dep)
+	for dep, tag := range p.getExportedDependencies() {
+		prebuiltDep := android.PrebuiltNameFromSource(dep)
+		ctx.AddDependency(module, tag, prebuiltDep)
 	}
 }
 
@@ -559,9 +577,9 @@
 // A deapexer module is only needed when the prebuilt apex specifies one or more modules in either
 // the `exported_java_libs` or `exported_bootclasspath_fragments` properties as that indicates that
 // the listed modules need access to files from within the prebuilt .apex file.
-func createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string, properties *PrebuiltCommonProperties) {
+func (p *prebuiltCommon) createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string) {
 	// Only create the deapexer module if it is needed.
-	if len(properties.Exported_java_libs)+len(properties.Exported_bootclasspath_fragments) == 0 {
+	if len(p.getExportedDependencies()) == 0 {
 		return
 	}
 
@@ -614,10 +632,6 @@
 	)
 }
 
-func deapexerModuleName(baseModuleName string) string {
-	return baseModuleName + ".deapexer"
-}
-
 func apexSelectorModuleName(baseModuleName string) string {
 	return baseModuleName + ".apex.selector"
 }
@@ -661,8 +675,9 @@
 var _ android.RequiresFilesFromPrebuiltApexTag = exportedDependencyTag{}
 
 var (
-	exportedJavaLibTag               = exportedDependencyTag{name: "exported_java_libs"}
-	exportedBootclasspathFragmentTag = exportedDependencyTag{name: "exported_bootclasspath_fragments"}
+	exportedJavaLibTag                       = exportedDependencyTag{name: "exported_java_libs"}
+	exportedBootclasspathFragmentTag         = exportedDependencyTag{name: "exported_bootclasspath_fragments"}
+	exportedSystemserverclasspathFragmentTag = exportedDependencyTag{name: "exported_systemserverclasspath_fragments"}
 )
 
 var _ prebuiltApexModuleCreator = (*Prebuilt)(nil)
@@ -703,7 +718,7 @@
 	createApexSelectorModule(ctx, apexSelectorModuleName, &p.properties.ApexFileProperties)
 
 	apexFileSource := ":" + apexSelectorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, p.prebuiltCommonProperties)
+	p.createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource)
 
 	// Add a source reference to retrieve the selected apex from the selector module.
 	p.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -742,15 +757,15 @@
 	// Save the files that need to be made available to Make.
 	p.initApexFilesForAndroidMk(ctx)
 
-	if p.installable() {
-		ctx.InstallFile(p.installDir, p.installFilename, p.inputApex)
-	}
-
 	// in case that prebuilt_apex replaces source apex (using prefer: prop)
-	p.compatSymlinks = makeCompatSymlinks(p.BaseModuleName(), ctx)
+	p.compatSymlinks = makeCompatSymlinks(p.BaseModuleName(), ctx, true)
 	// or that prebuilt_apex overrides other apexes (using overrides: prop)
 	for _, overridden := range p.prebuiltCommonProperties.Overrides {
-		p.compatSymlinks = append(p.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
+		p.compatSymlinks = append(p.compatSymlinks, makeCompatSymlinks(overridden, ctx, true)...)
+	}
+
+	if p.installable() {
+		p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, p.compatSymlinks.Paths()...)
 	}
 }
 
@@ -906,7 +921,7 @@
 	createApexExtractorModule(ctx, apexExtractorModuleName, &a.properties.ApexExtractorProperties)
 
 	apexFileSource := ":" + apexExtractorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, a.prebuiltCommonProperties)
+	a.createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource)
 
 	// After passing the arch specific src properties to the creating the apex selector module
 	a.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -924,8 +939,8 @@
 
 func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.installFilename = a.InstallFilename()
-	if !strings.HasSuffix(a.installFilename, imageApexSuffix) {
-		ctx.ModuleErrorf("filename should end in %s for apex_set", imageApexSuffix)
+	if !strings.HasSuffix(a.installFilename, imageApexSuffix) && !strings.HasSuffix(a.installFilename, imageCapexSuffix) {
+		ctx.ModuleErrorf("filename should end in %s or %s for apex_set", imageApexSuffix, imageCapexSuffix)
 	}
 
 	inputApex := android.OptionalPathForModuleSrc(ctx, a.prebuiltCommonProperties.Selected_apex).Path()
@@ -946,25 +961,14 @@
 
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	if a.installable() {
-		ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
+		a.installedFile = ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
 	}
 
 	// in case that apex_set replaces source apex (using prefer: prop)
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, true)
 	// or that apex_set overrides other apexes (using overrides: prop)
 	for _, overridden := range a.prebuiltCommonProperties.Overrides {
-		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
-	}
-
-	if ctx.Config().InstallExtraFlattenedApexes() {
-		// flattened apex should be in /system_ext/apex
-		flattenedApexDir := android.PathForModuleInstall(&systemExtContext{ctx}, "apex", a.BaseModuleName())
-		a.postInstallCommands = append(a.postInstallCommands,
-			fmt.Sprintf("$(HOST_OUT_EXECUTABLES)/deapexer --debugfs_path $(HOST_OUT_EXECUTABLES)/debugfs extract %s %s",
-				a.outputApex.String(),
-				flattenedApexDir.ToMakePath().String(),
-			))
-		a.hostRequired = []string{"deapexer", "debugfs"}
+		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx, true)...)
 	}
 }
 
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index a8d5931..d037664 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -138,7 +138,7 @@
 		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
 	).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`in contents must also be declared in PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS`)).
+			`in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS`)).
 		RunTestWithBp(t, `
 			apex {
 				name: "myapex",
@@ -181,3 +181,145 @@
 			}
 		`)
 }
+
+func TestPrebuiltSystemserverclasspathFragmentContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		prebuilt_systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			prefer: true,
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex", []string{
+		`myapex.apex.selector`,
+		`prebuilt_mysystemserverclasspathfragment`,
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`myapex.deapexer`,
+		`prebuilt_foo`,
+	})
+}
+
+func TestSystemserverclasspathFragmentStandaloneContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			standalone_contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		"etc/classpaths/systemserverclasspath.pb",
+		"javalib/foo.jar",
+	})
+}
+
+func TestPrebuiltStandaloneSystemserverclasspathFragmentContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		prebuilt_systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			prefer: true,
+			standalone_contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`myapex.deapexer`,
+		`prebuilt_foo`,
+	})
+}
diff --git a/apex/testing.go b/apex/testing.go
index 69bd73e..337c862 100644
--- a/apex/testing.go
+++ b/apex/testing.go
@@ -24,6 +24,7 @@
 	android.MockFS{
 		// Needed by apex.
 		"system/core/rootdir/etc/public.libraries.android.txt": nil,
+		"build/soong/scripts/gen_java_usedby_apex.sh":          nil,
 		"build/soong/scripts/gen_ndk_backedby_apex.sh":         nil,
 		// Needed by prebuilt_apex.
 		"build/soong/scripts/unpack-prebuilt-apex.sh": nil,
diff --git a/apex/vndk.go b/apex/vndk.go
index 75c0fb0..ef3e5e1 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -15,7 +15,6 @@
 package apex
 
 import (
-	"path/filepath"
 	"strings"
 
 	"android/soong/android"
@@ -96,11 +95,14 @@
 }
 
 // name is module.BaseModuleName() which is used as LOCAL_MODULE_NAME and also LOCAL_OVERRIDES_*
-func makeCompatSymlinks(name string, ctx android.ModuleContext) (symlinks []string) {
+func makeCompatSymlinks(name string, ctx android.ModuleContext, primaryApex bool) (symlinks android.InstallPaths) {
 	// small helper to add symlink commands
-	addSymlink := func(target, dir, linkName string) {
-		link := filepath.Join(dir, linkName)
-		symlinks = append(symlinks, "mkdir -p "+dir+" && rm -rf "+link+" && ln -sf "+target+" "+link)
+	addSymlink := func(target string, dir android.InstallPath, linkName string) {
+		if primaryApex {
+			symlinks = append(symlinks, ctx.InstallAbsoluteSymlink(dir, linkName, target))
+		} else {
+			symlinks = append(symlinks, dir.Join(ctx, linkName))
+		}
 	}
 
 	// TODO(b/142911355): [VNDK APEX] Fix hard-coded references to /system/lib/vndk
@@ -118,14 +120,15 @@
 		// the name of vndk apex is formatted "com.android.vndk.v" + version
 		apexName := vndkApexNamePrefix + vndkVersion
 		if ctx.Config().Android64() {
-			addSymlink("/apex/"+apexName+"/lib64", "$(TARGET_OUT)/lib64", "vndk-sp-"+vndkVersion)
-			addSymlink("/apex/"+apexName+"/lib64", "$(TARGET_OUT)/lib64", "vndk-"+vndkVersion)
+			dir := android.PathForModuleInPartitionInstall(ctx, "system", "lib64")
+			addSymlink("/apex/"+apexName+"/lib64", dir, "vndk-sp-"+vndkVersion)
+			addSymlink("/apex/"+apexName+"/lib64", dir, "vndk-"+vndkVersion)
 		}
 		if !ctx.Config().Android64() || ctx.DeviceConfig().DeviceSecondaryArch() != "" {
-			addSymlink("/apex/"+apexName+"/lib", "$(TARGET_OUT)/lib", "vndk-sp-"+vndkVersion)
-			addSymlink("/apex/"+apexName+"/lib", "$(TARGET_OUT)/lib", "vndk-"+vndkVersion)
+			dir := android.PathForModuleInPartitionInstall(ctx, "system", "lib")
+			addSymlink("/apex/"+apexName+"/lib", dir, "vndk-sp-"+vndkVersion)
+			addSymlink("/apex/"+apexName+"/lib", dir, "vndk-"+vndkVersion)
 		}
-		return
 	}
 
 	// http://b/121248172 - create a link from /system/usr/icu to
@@ -133,19 +136,9 @@
 	// A symlink can't overwrite a directory and the /system/usr/icu directory once
 	// existed so the required structure must be created whatever we find.
 	if name == "com.android.i18n" {
-		addSymlink("/apex/com.android.i18n/etc/icu", "$(TARGET_OUT)/usr", "icu")
-		return
+		dir := android.PathForModuleInPartitionInstall(ctx, "system", "usr")
+		addSymlink("/apex/com.android.i18n/etc/icu", dir, "icu")
 	}
 
-	// TODO(b/124106384): Clean up compat symlinks for ART binaries.
-	if name == "com.android.art" || strings.HasPrefix(name, "com.android.art.") {
-		addSymlink("/apex/com.android.art/bin/dalvikvm", "$(TARGET_OUT)/bin", "dalvikvm")
-		dex2oat := "dex2oat32"
-		if ctx.Config().Android64() {
-			dex2oat = "dex2oat64"
-		}
-		addSymlink("/apex/com.android.art/bin/"+dex2oat, "$(TARGET_OUT)/bin", "dex2oat")
-		return
-	}
-	return
+	return symlinks
 }
diff --git a/bazel/Android.bp b/bazel/Android.bp
index b68d65b..80af2bd 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -14,6 +14,7 @@
     testSrcs: [
         "aquery_test.go",
         "properties_test.go",
+        "testing.go",
     ],
     pluginFor: [
         "soong_build",
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 8741afb..fd8cf67 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -59,6 +60,8 @@
 	InputDepSetIds       []int
 	Mnemonic             string
 	OutputIds            []int
+	TemplateContent      string
+	Substitutions        []KeyValuePair
 }
 
 // actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
@@ -100,6 +103,20 @@
 	artifactIdToPath map[int]string
 }
 
+// The tokens should be substituted with the value specified here, instead of the
+// one returned in 'substitutions' of TemplateExpand action.
+var TemplateActionOverriddenTokens = map[string]string{
+	// Uses "python3" for %python_binary% instead of the value returned by aquery
+	// which is "py3wrapper.sh". See removePy3wrapperScript.
+	"%python_binary%": "python3",
+}
+
+// This pattern matches the MANIFEST file created for a py_binary target.
+var manifestFilePattern = regexp.MustCompile(".*/.+\\.runfiles/MANIFEST$")
+
+// The file name of py3wrapper.sh, which is used by py_binary targets.
+var py3wrapperFileName = "/py3wrapper.sh"
+
 func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
 	pathFragments := map[int]pathFragment{}
 	for _, pathFragment := range aqueryResult.PathFragments {
@@ -163,7 +180,31 @@
 			}
 		}
 	}
-	return inputPaths, nil
+
+	// TODO(b/197135294): Clean up this custom runfiles handling logic when
+	// SourceSymlinkManifest and SymlinkTree actions are supported.
+	filteredInputPaths := filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths)
+
+	return filteredInputPaths, nil
+}
+
+// See go/python-binary-host-mixed-build for more details.
+// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
+// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
+// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+// but it doesn't contain sufficient information so no Ninja build statements are generated
+// for creating it.
+// So in mixed build mode, when these two are used as input of some Ninja build statement,
+// since there is no build statement to create them, they should be removed from input paths.
+func filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths []string) []string {
+	filteredInputPaths := []string{}
+	for _, path := range inputPaths {
+		if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
+			continue
+		}
+		filteredInputPaths = append(filteredInputPaths, path)
+	}
+	return filteredInputPaths
 }
 
 func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
@@ -230,7 +271,7 @@
 		}
 
 		buildStatement := BuildStatement{
-			Command:     strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
+			Command:     strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " "),
 			Depfile:     depfile,
 			OutputPaths: outputPaths,
 			InputPaths:  inputPaths,
@@ -245,9 +286,49 @@
 			out := outputPaths[0]
 			outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
 			out = proptools.ShellEscapeIncludingSpaces(out)
-			in := proptools.ShellEscapeIncludingSpaces(inputPaths[0])
-			buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in)
+			in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
+			// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
+			buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
 			buildStatement.SymlinkPaths = outputPaths[:]
+		} else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
+			if len(outputPaths) != 1 {
+				return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
+			}
+			expandedTemplateContent := expandTemplateContent(actionEntry)
+			// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
+			// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
+			// change \n to space and mess up the format of Python programs.
+			// sed is used to convert \\n back to \n before saving to output file.
+			// See go/python-binary-host-mixed-build for more details.
+			command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
+				escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
+			buildStatement.Command = command
+		} else if isPythonZipperAction(actionEntry) {
+			if len(inputPaths) < 1 || len(outputPaths) != 1 {
+				return nil, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
+			}
+			buildStatement.InputPaths, buildStatement.Command = removePy3wrapperScript(buildStatement)
+			buildStatement.Command = addCommandForPyBinaryRunfilesDir(buildStatement, inputPaths[0], outputPaths[0])
+			// Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
+			// In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
+			// which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
+			//
+			// The following logic relies on that Bazel aquery output returns actions in the order that
+			// PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
+			// in that order, the following logic might not find the build statement generated for Python binary
+			// stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
+			// See go/python-binary-host-mixed-build for more details.
+			pythonZipFilePath := outputPaths[0]
+			pyBinaryFound := false
+			for i, _ := range buildStatements {
+				if len(buildStatements[i].OutputPaths) == 1 && buildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
+					buildStatements[i].InputPaths = append(buildStatements[i].InputPaths, pythonZipFilePath)
+					pyBinaryFound = true
+				}
+			}
+			if !pyBinaryFound {
+				return nil, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
+			}
 		} else if len(actionEntry.Arguments) < 1 {
 			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
 		}
@@ -257,10 +338,85 @@
 	return buildStatements, nil
 }
 
+// expandTemplateContent substitutes the tokens in a template.
+func expandTemplateContent(actionEntry action) string {
+	replacerString := []string{}
+	for _, pair := range actionEntry.Substitutions {
+		value := pair.Value
+		if val, ok := TemplateActionOverriddenTokens[pair.Key]; ok {
+			value = val
+		}
+		replacerString = append(replacerString, pair.Key, value)
+	}
+	replacer := strings.NewReplacer(replacerString...)
+	return replacer.Replace(actionEntry.TemplateContent)
+}
+
+func escapeCommandlineArgument(str string) string {
+	// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
+	replacer := strings.NewReplacer(
+		`\`, `\\`,
+		`$`, `\$`,
+		"`", "\\`",
+		`"`, `\"`,
+		"\n", "\\n",
+		`'`, `'"'"'`,
+	)
+	return replacer.Replace(str)
+}
+
+// removePy3wrapperScript removes py3wrapper.sh from the input paths and command of the action of
+// creating python zip file in mixed build mode. py3wrapper.sh is returned as input by aquery but
+// there is no action returned by aquery for creating it. So in mixed build "python3" is used
+// as the PYTHON_BINARY in python binary stub script, and py3wrapper.sh is not needed and should be
+// removed from input paths and command of creating python zip file.
+// See go/python-binary-host-mixed-build for more details.
+// TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
+func removePy3wrapperScript(bs BuildStatement) (newInputPaths []string, newCommand string) {
+	// Remove from inputs
+	filteredInputPaths := []string{}
+	for _, path := range bs.InputPaths {
+		if !strings.HasSuffix(path, py3wrapperFileName) {
+			filteredInputPaths = append(filteredInputPaths, path)
+		}
+	}
+	newInputPaths = filteredInputPaths
+
+	// Remove from command line
+	var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
+	newCommand = re.ReplaceAllString(bs.Command, "")
+	return
+}
+
+// addCommandForPyBinaryRunfilesDir adds commands creating python binary runfiles directory.
+// runfiles directory is created by using MANIFEST file and MANIFEST file is the output of
+// SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+// but since SourceSymlinkManifest doesn't contain sufficient information
+// so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
+// See go/python-binary-host-mixed-build for more details.
+// TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
+func addCommandForPyBinaryRunfilesDir(bs BuildStatement, zipperCommandPath, zipFilePath string) string {
+	// Unzip the zip file, zipFilePath looks like <python_binary>.zip
+	runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
+	command := fmt.Sprintf("%s x %s -d %s", zipperCommandPath, zipFilePath, runfilesDirName)
+	// Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
+	// when running the python binary stub script.
+	command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
+	return bs.Command + " && " + command
+}
+
 func isSymlinkAction(a action) bool {
 	return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
 }
 
+func isTemplateExpandAction(a action) bool {
+	return a.Mnemonic == "TemplateExpand"
+}
+
+func isPythonZipperAction(a action) bool {
+	return a.Mnemonic == "PythonZipper"
+}
+
 func shouldSkipAction(a action) bool {
 	// TODO(b/180945121): Handle complex symlink actions.
 	if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 43e4155..68e50c2 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -709,7 +709,7 @@
 	}
 	expectedBuildStatements := []BuildStatement{
 		BuildStatement{
-			Command:     "/bin/bash -c touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out",
+			Command:     "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
 			OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
 			InputPaths:  inputPaths,
 			Mnemonic:    "Action",
@@ -859,7 +859,7 @@
 		BuildStatement{
 			Command: "mkdir -p one/symlink_subdir && " +
 				"rm -f one/symlink_subdir/symlink && " +
-				"ln -rsf one/file_subdir/file one/symlink_subdir/symlink",
+				"ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink",
 			InputPaths:   []string{"one/file_subdir/file"},
 			OutputPaths:  []string{"one/symlink_subdir/symlink"},
 			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
@@ -923,14 +923,14 @@
 		BuildStatement{
 			Command: "mkdir -p 'one/symlink subdir' && " +
 				"rm -f 'one/symlink subdir/symlink' && " +
-				"ln -rsf 'one/file subdir/file' 'one/symlink subdir/symlink'",
+				"ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'",
 			InputPaths:   []string{"one/file subdir/file"},
 			OutputPaths:  []string{"one/symlink subdir/symlink"},
 			SymlinkPaths: []string{"one/symlink subdir/symlink"},
 			Mnemonic:     "SolibSymlink",
 		},
 	}
-	assertBuildStatements(t, actual, expectedBuildStatements)
+	assertBuildStatements(t, expectedBuildStatements, actual)
 }
 
 func TestSymlinkMultipleInputs(t *testing.T) {
@@ -1015,6 +1015,355 @@
 	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
 }
 
+func TestTemplateExpandActionSubstitutions(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "outputIds": [1],
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "template_file"
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " +
+				"chmod a+x template_file'",
+			OutputPaths: []string{"template_file"},
+			Mnemonic:    "TemplateExpand",
+		},
+	}
+	assertBuildStatements(t, expectedBuildStatements, actual)
+}
+
+func TestTemplateExpandActionNoOutput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "template_file"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 output to template expand action, got: output []`)
+}
+
+func TestPythonZipperActionSuccess(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  },{
+    "id": 3,
+    "pathFragmentId": 3
+  },{
+    "id": 4,
+    "pathFragmentId": 4
+  },{
+    "id": 5,
+    "pathFragmentId": 10
+  },{
+    "id": 10,
+    "pathFragmentId": 20
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "outputIds": [1],
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  },{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "outputIds": [2],
+    "inputDepSetIds": [1],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [4, 3, 5]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  },{
+    "id": 3,
+    "label": "python_binary.py"
+  },{
+    "id": 9,
+    "label": ".."
+  }, {
+    "id": 8,
+    "label": "bazel_tools",
+    "parentId": 9
+  }, {
+    "id": 7,
+    "label": "tools",
+    "parentId": 8
+  }, {
+    "id": 6,
+    "label": "zip",
+    "parentId": 7
+  }, {
+    "id": 5,
+    "label": "zipper",
+    "parentId": 6
+  }, {
+    "id": 4,
+    "label": "zipper",
+    "parentId": 5
+  },{
+    "id": 16,
+    "label": "bazel-out"
+  },{
+    "id": 15,
+    "label": "bazel_tools",
+    "parentId": 16
+  }, {
+    "id": 14,
+    "label": "k8-fastbuild",
+    "parentId": 15
+  }, {
+    "id": 13,
+    "label": "bin",
+    "parentId": 14
+  }, {
+    "id": 12,
+    "label": "tools",
+    "parentId": 13
+  }, {
+    "id": 11,
+    "label": "python",
+    "parentId": 12
+  }, {
+    "id": 10,
+    "label": "py3wrapper.sh",
+    "parentId": 11
+  },{
+    "id": 20,
+    "label": "python_binary"
+  }]
+}`
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > python_binary && " +
+				"chmod a+x python_binary'",
+			InputPaths:  []string{"python_binary.zip"},
+			OutputPaths: []string{"python_binary"},
+			Mnemonic:    "TemplateExpand",
+		},
+		BuildStatement{
+			Command: "../bazel_tools/tools/zip/zipper/zipper cC python_binary.zip __main__.py=bazel-out/k8-fastbuild/bin/python_binary.temp " +
+				"__init__.py= runfiles/__main__/__init__.py= runfiles/__main__/python_binary.py=python_binary.py  && " +
+				"../bazel_tools/tools/zip/zipper/zipper x python_binary.zip -d python_binary.runfiles && ln -sf runfiles/__main__ python_binary.runfiles",
+			InputPaths:  []string{"../bazel_tools/tools/zip/zipper/zipper", "python_binary.py"},
+			OutputPaths: []string{"python_binary.zip"},
+			Mnemonic:    "PythonZipper",
+		},
+	}
+	assertBuildStatements(t, expectedBuildStatements, actual)
+}
+
+func TestPythonZipperActionNoInput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  }]
+}`
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input [], output ["python_binary.zip"]`)
+}
+
+func TestPythonZipperActionNoOutput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  },{
+    "id": 3,
+    "pathFragmentId": 3
+  },{
+    "id": 4,
+    "pathFragmentId": 4
+  },{
+    "id": 5,
+    "pathFragmentId": 10
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "inputDepSetIds": [1]
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [4, 3, 5]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  },{
+    "id": 3,
+    "label": "python_binary.py"
+  },{
+    "id": 9,
+    "label": ".."
+  }, {
+    "id": 8,
+    "label": "bazel_tools",
+    "parentId": 9
+  }, {
+    "id": 7,
+    "label": "tools",
+    "parentId": 8
+  }, {
+    "id": 6,
+    "label": "zip",
+    "parentId": 7
+  }, {
+    "id": 5,
+    "label": "zipper",
+    "parentId": 6
+  }, {
+    "id": 4,
+    "label": "zipper",
+    "parentId": 5
+  },{
+    "id": 16,
+    "label": "bazel-out"
+  },{
+    "id": 15,
+    "label": "bazel_tools",
+    "parentId": 16
+  }, {
+    "id": 14,
+    "label": "k8-fastbuild",
+    "parentId": 15
+  }, {
+    "id": 13,
+    "label": "bin",
+    "parentId": 14
+  }, {
+    "id": 12,
+    "label": "tools",
+    "parentId": 13
+  }, {
+    "id": 11,
+    "label": "python",
+    "parentId": 12
+  }, {
+    "id": 10,
+    "label": "py3wrapper.sh",
+    "parentId": 11
+  }]
+}`
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input ["../bazel_tools/tools/zip/zipper/zipper" "python_binary.py"], output []`)
+}
+
 func assertError(t *testing.T, err error, expected string) {
 	t.Helper()
 	if err == nil {
@@ -1029,7 +1378,7 @@
 func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
 	t.Helper()
 	if len(expected) != len(actual) {
-		t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
+		t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v",
 			len(expected), len(actual), expected, actual)
 		return
 	}
@@ -1040,7 +1389,7 @@
 				continue ACTUAL_LOOP
 			}
 		}
-		t.Errorf("unexpected build statement %v.\n expected: %v",
+		t.Errorf("unexpected build statement %#v.\n expected: %#v",
 			actualStatement, expected)
 		return
 	}
diff --git a/bazel/configurability.go b/bazel/configurability.go
index 7aaff9a..1993f76 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -39,6 +39,7 @@
 	osArchAndroidArm64      = "android_arm64"
 	osArchAndroidX86        = "android_x86"
 	osArchAndroidX86_64     = "android_x86_64"
+	osArchDarwinArm64       = "darwin_arm64"
 	osArchDarwinX86_64      = "darwin_x86_64"
 	osArchLinuxX86          = "linux_glibc_x86"
 	osArchLinuxX86_64       = "linux_glibc_x86_64"
@@ -91,16 +92,12 @@
 		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
 	}
 
-	platformBionicMap = map[string]string{
-		"bionic":                   "//build/bazel/platforms/os:bionic",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
-	}
-
 	platformOsArchMap = map[string]string{
 		osArchAndroidArm:           "//build/bazel/platforms/os_arch:android_arm",
 		osArchAndroidArm64:         "//build/bazel/platforms/os_arch:android_arm64",
 		osArchAndroidX86:           "//build/bazel/platforms/os_arch:android_x86",
 		osArchAndroidX86_64:        "//build/bazel/platforms/os_arch:android_x86_64",
+		osArchDarwinArm64:          "//build/bazel/platforms/os_arch:darwin_arm64",
 		osArchDarwinX86_64:         "//build/bazel/platforms/os_arch:darwin_x86_64",
 		osArchLinuxX86:             "//build/bazel/platforms/os_arch:linux_glibc_x86",
 		osArchLinuxX86_64:          "//build/bazel/platforms/os_arch:linux_glibc_x86_64",
@@ -122,7 +119,6 @@
 	arch
 	os
 	osArch
-	bionic
 	productVariables
 )
 
@@ -132,7 +128,6 @@
 		arch:             "arch",
 		os:               "os",
 		osArch:           "arch_os",
-		bionic:           "bionic",
 		productVariables: "product_variables",
 	}[ct]
 }
@@ -155,10 +150,6 @@
 		if _, ok := platformOsArchMap[config]; !ok {
 			panic(fmt.Errorf("Unknown os+arch: %s", config))
 		}
-	case bionic:
-		if _, ok := platformBionicMap[config]; !ok {
-			panic(fmt.Errorf("Unknown for %s: %s", ct.String(), config))
-		}
 	case productVariables:
 		// do nothing
 	default:
@@ -167,9 +158,9 @@
 }
 
 // SelectKey returns the Bazel select key for a given configurationType and config string.
-func (ct configurationType) SelectKey(config string) string {
-	ct.validateConfig(config)
-	switch ct {
+func (ca ConfigurationAxis) SelectKey(config string) string {
+	ca.validateConfig(config)
+	switch ca.configurationType {
 	case noConfig:
 		panic(fmt.Errorf("SelectKey is unnecessary for noConfig ConfigurationType "))
 	case arch:
@@ -178,15 +169,14 @@
 		return platformOsMap[config]
 	case osArch:
 		return platformOsArchMap[config]
-	case bionic:
-		return platformBionicMap[config]
 	case productVariables:
-		if config == ConditionsDefaultConfigKey {
+		if strings.HasSuffix(config, ConditionsDefaultConfigKey) {
+			// e.g. "acme__feature1__conditions_default" or "android__board__conditions_default"
 			return ConditionsDefaultSelectKey
 		}
-		return fmt.Sprintf("%s:%s", productVariableBazelPackage, strings.ToLower(config))
+		return fmt.Sprintf("%s:%s", productVariableBazelPackage, config)
 	default:
-		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
+		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ca.configurationType))
 	}
 }
 
@@ -199,8 +189,6 @@
 	OsConfigurationAxis = ConfigurationAxis{configurationType: os}
 	// An axis for arch+os-specific configurations
 	OsArchConfigurationAxis = ConfigurationAxis{configurationType: osArch}
-	// An axis for bionic os-specific configurations
-	BionicConfigurationAxis = ConfigurationAxis{configurationType: bionic}
 )
 
 // ProductVariableConfigurationAxis returns an axis for the given product variable
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 131f0ec..41f9886 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -17,6 +17,7 @@
 	CcStaticLibraryFiles []string
 	Includes             []string
 	SystemIncludes       []string
+	Headers              []string
 	// Archives owned by the current target (not by its dependencies). These will
 	// be a subset of OutputFiles. (or static libraries, this will be equal to OutputFiles,
 	// but general cc_library will also have dynamic libraries in output files).
@@ -25,6 +26,7 @@
 	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
 	// but general cc_library will also have dynamic libraries in output files).
 	RootDynamicLibraries []string
+	TocFile              string
 }
 
 type getOutputFilesRequestType struct{}
@@ -100,39 +102,55 @@
 func (g getCcInfoType) StarlarkFunctionBody() string {
 	return `
 outputFiles = [f.path for f in target.files.to_list()]
+cc_info = providers(target)["CcInfo"]
 
-includes = providers(target)["CcInfo"].compilation_context.includes.to_list()
-system_includes = providers(target)["CcInfo"].compilation_context.system_includes.to_list()
+includes = cc_info.compilation_context.includes.to_list()
+system_includes = cc_info.compilation_context.system_includes.to_list()
+headers = [f.path for f in cc_info.compilation_context.headers.to_list()]
 
 ccObjectFiles = []
 staticLibraries = []
 rootStaticArchives = []
-linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+linker_inputs = cc_info.linking_context.linker_inputs.to_list()
 
-for linker_input in linker_inputs:
-  for library in linker_input.libraries:
-    for object in library.objects:
-      ccObjectFiles += [object.path]
-    if library.static_library:
-      staticLibraries.append(library.static_library.path)
-      if linker_input.owner == target.label:
-        rootStaticArchives.append(library.static_library.path)
+static_info_tag = "//build/bazel/rules:cc_library_static.bzl%CcStaticLibraryInfo"
+if static_info_tag in providers(target):
+  static_info = providers(target)[static_info_tag]
+  ccObjectFiles = [f.path for f in static_info.objects]
+  rootStaticArchives = [static_info.root_static_archive.path]
+else:
+  for linker_input in linker_inputs:
+    for library in linker_input.libraries:
+      for object in library.objects:
+        ccObjectFiles += [object.path]
+      if library.static_library:
+        staticLibraries.append(library.static_library.path)
+        if linker_input.owner == target.label:
+          rootStaticArchives.append(library.static_library.path)
 
 rootDynamicLibraries = []
 
-if "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo" in providers(target):
-  shared_info = providers(target)["@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"]
+shared_info_tag = "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"
+if shared_info_tag in providers(target):
+  shared_info = providers(target)[shared_info_tag]
   for lib in shared_info.linker_input.libraries:
     rootDynamicLibraries += [lib.dynamic_library.path]
 
+toc_file = ""
+toc_file_tag = "//build/bazel/rules:generate_toc.bzl%CcTocInfo"
+if toc_file_tag in providers(target):
+  toc_file = providers(target)[toc_file_tag].toc.path
+
 returns = [
   outputFiles,
   staticLibraries,
   ccObjectFiles,
   includes,
   system_includes,
+  headers,
   rootStaticArchives,
-  rootDynamicLibraries
+  rootDynamicLibraries,
+  [toc_file]
 ]
 
 return "|".join([", ".join(r) for r in returns])`
@@ -146,7 +164,7 @@
 	var ccObjects []string
 
 	splitString := strings.Split(rawString, "|")
-	if expectedLen := 7; len(splitString) != expectedLen {
+	if expectedLen := 9; len(splitString) != expectedLen {
 		return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString)
 	}
 	outputFilesString := splitString[0]
@@ -157,16 +175,20 @@
 	ccObjects = splitOrEmpty(ccObjectsString, ", ")
 	includes := splitOrEmpty(splitString[3], ", ")
 	systemIncludes := splitOrEmpty(splitString[4], ", ")
-	rootStaticArchives := splitOrEmpty(splitString[5], ", ")
-	rootDynamicLibraries := splitOrEmpty(splitString[6], ", ")
+	headers := splitOrEmpty(splitString[5], ", ")
+	rootStaticArchives := splitOrEmpty(splitString[6], ", ")
+	rootDynamicLibraries := splitOrEmpty(splitString[7], ", ")
+	tocFile := splitString[8] // NOTE: Will be the empty string if there wasn't
 	return CcInfo{
 		OutputFiles:          outputFiles,
 		CcObjectFiles:        ccObjects,
 		CcStaticLibraryFiles: ccStaticLibraries,
 		Includes:             includes,
 		SystemIncludes:       systemIncludes,
+		Headers:              headers,
 		RootStaticArchives:   rootStaticArchives,
 		RootDynamicLibraries: rootDynamicLibraries,
+		TocFile:              tocFile,
 	}, nil
 }
 
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 49019ab..d3bcb45 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -71,54 +71,60 @@
 	}{
 		{
 			description: "no result",
-			input:       "||||||",
+			input:       "||||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{},
 				CcObjectFiles:        []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				Headers:              []string{},
 				RootStaticArchives:   []string{},
 				RootDynamicLibraries: []string{},
+				TocFile:              "",
 			},
 		},
 		{
 			description: "only output",
-			input:       "test||||||",
+			input:       "test||||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"test"},
 				CcObjectFiles:        []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				Headers:              []string{},
 				RootStaticArchives:   []string{},
 				RootDynamicLibraries: []string{},
+				TocFile:              "",
 			},
 		},
 		{
 			description: "all items set",
-			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|rootstaticarchive1|rootdynamiclibrary1",
+			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|dir/subdir/hdr.h|rootstaticarchive1|rootdynamiclibrary1|lib.so.toc",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
 				CcObjectFiles:        []string{"object1", "object2"},
 				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
 				Includes:             []string{".", "dir/subdir"},
 				SystemIncludes:       []string{"system/dir", "system/other/dir"},
+				Headers:              []string{"dir/subdir/hdr.h"},
 				RootStaticArchives:   []string{"rootstaticarchive1"},
 				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
+				TocFile:              "lib.so.toc",
 			},
 		},
 		{
 			description:          "too few result splits",
 			input:                "|",
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, []string{"", ""}),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 9, []string{"", ""}),
 		},
 		{
 			description:          "too many result splits",
-			input:                strings.Repeat("|", 8),
+			input:                strings.Repeat("|", 50),
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, make([]string, 9)),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 9, make([]string, 51)),
 		},
 	}
 	for _, tc := range testCases {
diff --git a/bazel/properties.go b/bazel/properties.go
index 1a846ba..76be058 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -20,6 +20,8 @@
 	"regexp"
 	"sort"
 	"strings"
+
+	"github.com/google/blueprint"
 )
 
 // BazelTargetModuleProperties contain properties and metadata used for
@@ -32,12 +34,6 @@
 	Bzl_load_location string `blueprint:"mutated"`
 }
 
-const BazelTargetModuleNamePrefix = "__bp2build__"
-
-func StripNamePrefix(moduleName string) string {
-	return strings.TrimPrefix(moduleName, BazelTargetModuleNamePrefix)
-}
-
 var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
 
 // Label is used to represent a Bazel compatible Label. Also stores the original
@@ -111,6 +107,14 @@
 	return dirs
 }
 
+// Add inserts the label Label at the end of the LabelList.
+func (ll *LabelList) Add(label *Label) {
+	if label == nil {
+		return
+	}
+	ll.Includes = append(ll.Includes, *label)
+}
+
 // Append appends the fields of other labelList to the corresponding fields of ll.
 func (ll *LabelList) Append(other LabelList) {
 	if len(ll.Includes) > 0 || len(other.Includes) > 0 {
@@ -168,118 +172,36 @@
 // Subtract needle from haystack
 func SubtractStrings(haystack []string, needle []string) []string {
 	// This is really a set
-	remainder := make(map[string]bool)
-
-	for _, s := range haystack {
-		remainder[s] = true
-	}
+	needleMap := make(map[string]bool)
 	for _, s := range needle {
-		delete(remainder, s)
+		needleMap[s] = true
 	}
 
 	var strings []string
-	for s, _ := range remainder {
-		strings = append(strings, s)
+	for _, s := range haystack {
+		if exclude := needleMap[s]; !exclude {
+			strings = append(strings, s)
+		}
 	}
 
-	sort.SliceStable(strings, func(i, j int) bool {
-		return strings[i] < strings[j]
-	})
-
 	return strings
 }
 
-// Map a function over all labels in a LabelList.
-func MapLabelList(mapOver LabelList, mapFn func(string) string) LabelList {
-	var includes []Label
-	for _, inc := range mapOver.Includes {
-		mappedLabel := Label{Label: mapFn(inc.Label), OriginalModuleName: inc.OriginalModuleName}
-		includes = append(includes, mappedLabel)
-	}
-	// mapFn is not applied over excludes, but they are propagated as-is.
-	return LabelList{Includes: includes, Excludes: mapOver.Excludes}
-}
-
-// Map a function over all Labels in a LabelListAttribute
-func MapLabelListAttribute(mapOver LabelListAttribute, mapFn func(string) string) LabelListAttribute {
-	var result LabelListAttribute
-
-	result.Value = MapLabelList(mapOver.Value, mapFn)
-
-	for axis, configToLabels := range mapOver.ConfigurableValues {
-		for config, value := range configToLabels {
-			result.SetSelectValue(axis, config, MapLabelList(value, mapFn))
-		}
-	}
-
-	return result
-}
-
-// Return all needles in a given haystack, where needleFn is true for needles.
-func FilterLabelList(haystack LabelList, needleFn func(string) bool) LabelList {
-	var includes []Label
-	for _, inc := range haystack.Includes {
-		if needleFn(inc.Label) {
-			includes = append(includes, inc)
-		}
-	}
-	// needleFn is not applied over excludes, but they are propagated as-is.
-	return LabelList{Includes: includes, Excludes: haystack.Excludes}
-}
-
-// Return all needles in a given haystack, where needleFn is true for needles.
-func FilterLabelListAttribute(haystack LabelListAttribute, needleFn func(string) bool) LabelListAttribute {
-	result := MakeLabelListAttribute(FilterLabelList(haystack.Value, needleFn))
-
-	for config, selects := range haystack.ConfigurableValues {
-		newSelects := make(labelListSelectValues, len(selects))
-		for k, v := range selects {
-			newSelects[k] = FilterLabelList(v, needleFn)
-		}
-		result.ConfigurableValues[config] = newSelects
-	}
-
-	return result
-}
-
-// Subtract needle from haystack
-func SubtractBazelLabelListAttribute(haystack LabelListAttribute, needle LabelListAttribute) LabelListAttribute {
-	result := MakeLabelListAttribute(SubtractBazelLabelList(haystack.Value, needle.Value))
-
-	for config, selects := range haystack.ConfigurableValues {
-		newSelects := make(labelListSelectValues, len(selects))
-		needleSelects := needle.ConfigurableValues[config]
-
-		for k, v := range selects {
-			newSelects[k] = SubtractBazelLabelList(v, needleSelects[k])
-		}
-		result.ConfigurableValues[config] = newSelects
-	}
-
-	return result
-}
-
 // Subtract needle from haystack
 func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
 	// This is really a set
-	remainder := make(map[Label]bool)
-
-	for _, label := range haystack {
-		remainder[label] = true
-	}
-	for _, label := range needle {
-		delete(remainder, label)
+	needleMap := make(map[Label]bool)
+	for _, s := range needle {
+		needleMap[s] = true
 	}
 
 	var labels []Label
-	for label, _ := range remainder {
-		labels = append(labels, label)
+	for _, label := range haystack {
+		if exclude := needleMap[label]; !exclude {
+			labels = append(labels, label)
+		}
 	}
 
-	sort.SliceStable(labels, func(i, j int) bool {
-		return labels[i].Label < labels[j].Label
-	})
-
 	return labels
 }
 
@@ -338,7 +260,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		la.Value = &value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if la.ConfigurableValues == nil {
 			la.ConfigurableValues = make(configurableLabels)
 		}
@@ -354,7 +276,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return *la.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return *la.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
@@ -411,7 +333,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		ba.Value = value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if ba.ConfigurableValues == nil {
 			ba.ConfigurableValues = make(configurableBools)
 		}
@@ -427,7 +349,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return ba.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if v, ok := ba.ConfigurableValues[axis][config]; ok {
 			return &v
 		} else {
@@ -452,9 +374,23 @@
 // labelListSelectValues supports config-specific label_list typed Bazel attribute values.
 type labelListSelectValues map[string]LabelList
 
-func (ll labelListSelectValues) appendSelects(other labelListSelectValues) {
+func (ll labelListSelectValues) addSelects(label labelSelectValues) {
+	for k, v := range label {
+		if label == nil {
+			continue
+		}
+		l := ll[k]
+		(&l).Add(v)
+		ll[k] = l
+	}
+}
+
+func (ll labelListSelectValues) appendSelects(other labelListSelectValues, forceSpecifyEmptyList bool) {
 	for k, v := range other {
 		l := ll[k]
+		if forceSpecifyEmptyList && l.IsNil() && !v.IsNil() {
+			l.Includes = []Label{}
+		}
 		(&l).Append(v)
 		ll[k] = l
 	}
@@ -486,6 +422,12 @@
 	// This mode facilitates use of attribute defaults: an empty list should
 	// override the default.
 	ForceSpecifyEmptyList bool
+
+	// If true, signal the intent to the code generator to emit all select keys,
+	// even if the Includes list for that key is empty. This mode facilitates
+	// specific select statements where an empty list for a non-default select
+	// key has a meaning.
+	EmitEmptyList bool
 }
 
 type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
@@ -504,17 +446,22 @@
 	cll[axis][config] = list
 }
 
-func (cll configurableLabelLists) Append(other configurableLabelLists) {
+func (cll configurableLabelLists) Append(other configurableLabelLists, forceSpecifyEmptyList bool) {
 	for axis, otherSelects := range other {
 		selects := cll[axis]
 		if selects == nil {
 			selects = make(labelListSelectValues, len(otherSelects))
 		}
-		selects.appendSelects(otherSelects)
+		selects.appendSelects(otherSelects, forceSpecifyEmptyList)
 		cll[axis] = selects
 	}
 }
 
+func (lla *LabelListAttribute) Clone() *LabelListAttribute {
+	result := &LabelListAttribute{ForceSpecifyEmptyList: lla.ForceSpecifyEmptyList}
+	return result.Append(*lla)
+}
+
 // MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
 func MakeLabelListAttribute(value LabelList) LabelListAttribute {
 	return LabelListAttribute{
@@ -533,7 +480,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		lla.Value = list
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if lla.ConfigurableValues == nil {
 			lla.ConfigurableValues = make(configurableLabelLists)
 		}
@@ -549,7 +496,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return lla.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return lla.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
@@ -568,16 +515,37 @@
 }
 
 // Append all values, including os and arch specific ones, from another
-// LabelListAttribute to this LabelListAttribute.
-func (lla *LabelListAttribute) Append(other LabelListAttribute) {
-	if lla.ForceSpecifyEmptyList && !other.Value.IsNil() {
+// LabelListAttribute to this LabelListAttribute. Returns this LabelListAttribute.
+func (lla *LabelListAttribute) Append(other LabelListAttribute) *LabelListAttribute {
+	forceSpecifyEmptyList := lla.ForceSpecifyEmptyList || other.ForceSpecifyEmptyList
+	if forceSpecifyEmptyList && lla.Value.IsNil() && !other.Value.IsNil() {
 		lla.Value.Includes = []Label{}
 	}
 	lla.Value.Append(other.Value)
 	if lla.ConfigurableValues == nil {
 		lla.ConfigurableValues = make(configurableLabelLists)
 	}
-	lla.ConfigurableValues.Append(other.ConfigurableValues)
+	lla.ConfigurableValues.Append(other.ConfigurableValues, forceSpecifyEmptyList)
+	return lla
+}
+
+// Add inserts the labels for each axis of LabelAttribute at the end of corresponding axis's
+// LabelList within the LabelListAttribute
+func (lla *LabelListAttribute) Add(label *LabelAttribute) {
+	if label == nil {
+		return
+	}
+
+	lla.Value.Add(label.Value)
+	if lla.ConfigurableValues == nil && label.ConfigurableValues != nil {
+		lla.ConfigurableValues = make(configurableLabelLists)
+	}
+	for axis, _ := range label.ConfigurableValues {
+		if _, exists := lla.ConfigurableValues[axis]; !exists {
+			lla.ConfigurableValues[axis] = make(labelListSelectValues)
+		}
+		lla.ConfigurableValues[axis].addSelects(label.ConfigurableValues[axis])
+	}
 }
 
 // HasConfigurableValues returns true if the attribute contains axis-specific label list values.
@@ -620,9 +588,13 @@
 			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(val, lla.Value)
 		}
 
-		// Now that the Value list is finalized for this axis, compare it with the original
-		// list, and put the difference into the default condition for the axis.
-		lla.ConfigurableValues[axis][ConditionsDefaultConfigKey] = SubtractBazelLabelList(baseLabels, lla.Value)
+		// Now that the Value list is finalized for this axis, compare it with
+		// the original list, and union the difference with the default
+		// condition for the axis.
+		difference := SubtractBazelLabelList(baseLabels, lla.Value)
+		existingDefaults := lla.ConfigurableValues[axis][ConditionsDefaultConfigKey]
+		existingDefaults.Append(difference)
+		lla.ConfigurableValues[axis][ConditionsDefaultConfigKey] = FirstUniqueBazelLabelList(existingDefaults)
 
 		// if everything ends up without includes, just delete the axis
 		if !lla.ConfigurableValues[axis].HasConfigurableValues() {
@@ -631,6 +603,144 @@
 	}
 }
 
+// OtherModuleContext is a limited context that has methods with information about other modules.
+type OtherModuleContext interface {
+	ModuleFromName(name string) (blueprint.Module, bool)
+	OtherModuleType(m blueprint.Module) string
+	OtherModuleName(m blueprint.Module) string
+	OtherModuleDir(m blueprint.Module) string
+	ModuleErrorf(fmt string, args ...interface{})
+}
+
+// LabelMapper is a function that takes a OtherModuleContext and returns a (potentially changed)
+// label and whether it was changed.
+type LabelMapper func(OtherModuleContext, Label) (string, bool)
+
+// LabelPartition contains descriptions of a partition for labels
+type LabelPartition struct {
+	// Extensions to include in this partition
+	Extensions []string
+	// LabelMapper is a function that can map a label to a new label, and indicate whether to include
+	// the mapped label in the partition
+	LabelMapper LabelMapper
+	// Whether to store files not included in any other partition in a group of LabelPartitions
+	// Only one partition in a group of LabelPartitions can enabled Keep_remainder
+	Keep_remainder bool
+}
+
+// LabelPartitions is a map of partition name to a LabelPartition describing the elements of the
+// partition
+type LabelPartitions map[string]LabelPartition
+
+// filter returns a pointer to a label if the label should be included in the partition or nil if
+// not.
+func (lf LabelPartition) filter(ctx OtherModuleContext, label Label) *Label {
+	if lf.LabelMapper != nil {
+		if newLabel, changed := lf.LabelMapper(ctx, label); changed {
+			return &Label{newLabel, label.OriginalModuleName}
+		}
+	}
+	for _, ext := range lf.Extensions {
+		if strings.HasSuffix(label.Label, ext) {
+			return &label
+		}
+	}
+
+	return nil
+}
+
+// PartitionToLabelListAttribute is map of partition name to a LabelListAttribute
+type PartitionToLabelListAttribute map[string]LabelListAttribute
+
+type partitionToLabelList map[string]*LabelList
+
+func (p partitionToLabelList) appendIncludes(partition string, label Label) {
+	if _, ok := p[partition]; !ok {
+		p[partition] = &LabelList{}
+	}
+	p[partition].Includes = append(p[partition].Includes, label)
+}
+
+func (p partitionToLabelList) excludes(partition string, excludes []Label) {
+	if _, ok := p[partition]; !ok {
+		p[partition] = &LabelList{}
+	}
+	p[partition].Excludes = excludes
+}
+
+// PartitionLabelListAttribute partitions a LabelListAttribute into the requested partitions
+func PartitionLabelListAttribute(ctx OtherModuleContext, lla *LabelListAttribute, partitions LabelPartitions) PartitionToLabelListAttribute {
+	ret := PartitionToLabelListAttribute{}
+	var partitionNames []string
+	// Stored as a pointer to distinguish nil (no remainder partition) from empty string partition
+	var remainderPartition *string
+	for p, f := range partitions {
+		partitionNames = append(partitionNames, p)
+		if f.Keep_remainder {
+			if remainderPartition != nil {
+				panic("only one partition can store the remainder")
+			}
+			// If we take the address of p in a loop, we'll end up with the last value of p in
+			// remainderPartition, we want the requested partition
+			capturePartition := p
+			remainderPartition = &capturePartition
+		}
+	}
+
+	partitionLabelList := func(axis ConfigurationAxis, config string) {
+		value := lla.SelectValue(axis, config)
+		partitionToLabels := partitionToLabelList{}
+		for _, item := range value.Includes {
+			wasFiltered := false
+			var inPartition *string
+			for partition, f := range partitions {
+				filtered := f.filter(ctx, item)
+				if filtered == nil {
+					// did not match this filter, keep looking
+					continue
+				}
+				wasFiltered = true
+				partitionToLabels.appendIncludes(partition, *filtered)
+				// don't need to check other partitions if this filter used the item,
+				// continue checking if mapped to another name
+				if *filtered == item {
+					if inPartition != nil {
+						ctx.ModuleErrorf("%q was found in multiple partitions: %q, %q", item.Label, *inPartition, partition)
+					}
+					capturePartition := partition
+					inPartition = &capturePartition
+				}
+			}
+
+			// if not specified in a partition, add to remainder partition if one exists
+			if !wasFiltered && remainderPartition != nil {
+				partitionToLabels.appendIncludes(*remainderPartition, item)
+			}
+		}
+
+		// ensure empty lists are maintained
+		if value.Excludes != nil {
+			for _, partition := range partitionNames {
+				partitionToLabels.excludes(partition, value.Excludes)
+			}
+		}
+
+		for partition, list := range partitionToLabels {
+			val := ret[partition]
+			(&val).SetSelectValue(axis, config, *list)
+			ret[partition] = val
+		}
+	}
+
+	partitionLabelList(NoConfigAxis, "")
+	for axis, configToList := range lla.ConfigurableValues {
+		for config, _ := range configToList {
+			partitionLabelList(axis, config)
+		}
+	}
+	return ret
+}
+
 // StringListAttribute corresponds to the string_list Bazel attribute type with
 // support for additional metadata, like configurations.
 type StringListAttribute struct {
@@ -695,12 +805,18 @@
 
 // Append appends all values, including os and arch specific ones, from another
 // StringListAttribute to this StringListAttribute
-func (sla *StringListAttribute) Append(other StringListAttribute) {
+func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
 	sla.Value = append(sla.Value, other.Value...)
 	if sla.ConfigurableValues == nil {
 		sla.ConfigurableValues = make(configurableStringLists)
 	}
 	sla.ConfigurableValues.Append(other.ConfigurableValues)
+	return sla
+}
+
+func (sla *StringListAttribute) Clone() *StringListAttribute {
+	result := &StringListAttribute{}
+	return result.Append(*sla)
 }
 
 // SetSelectValue set a value for a bazel select for the given axis, config and value.
@@ -709,7 +825,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		sla.Value = list
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if sla.ConfigurableValues == nil {
 			sla.ConfigurableValues = make(configurableStringLists)
 		}
@@ -725,7 +841,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return sla.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return sla.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
@@ -743,6 +859,31 @@
 	return keys
 }
 
+// DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
+// configuration-specific values. For example, if we would convert this StringListAttribute as:
+// ["a", "b", "c"] + select({
+//    "//condition:one": ["a", "d"],
+//    "//conditions:default": [],
+// })
+// after this function, we would convert this StringListAttribute as:
+// ["a", "b", "c"] + select({
+//    "//condition:one": ["d"],
+//    "//conditions:default": [],
+// })
+func (sla *StringListAttribute) DeduplicateAxesFromBase() {
+	base := sla.Value
+	for axis, configToList := range sla.ConfigurableValues {
+		for config, list := range configToList {
+			remaining := SubtractStrings(list, base)
+			if len(remaining) == 0 {
+				delete(sla.ConfigurableValues[axis], config)
+			} else {
+				sla.ConfigurableValues[axis][config] = remaining
+			}
+		}
+	}
+}
+
 // TryVariableSubstitution, replace string substitution formatting within each string in slice with
 // Starlark string.format compatible tag for productVariable.
 func TryVariableSubstitutions(slice []string, productVariable string) ([]string, bool) {
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 9464245..c7f9776 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -16,7 +16,10 @@
 
 import (
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func TestUniqueBazelLabels(t *testing.T) {
@@ -233,8 +236,9 @@
 		),
 		ConfigurableValues: configurableLabelLists{
 			ArchConfigurationAxis: labelListSelectValues{
-				"arm": makeLabelList([]string{}, []string{"arm_exclude"}),
-				"x86": makeLabelList([]string{"x86_include"}, []string{}),
+				"arm":                      makeLabelList([]string{}, []string{"arm_exclude"}),
+				"x86":                      makeLabelList([]string{"x86_include"}, []string{}),
+				ConditionsDefaultConfigKey: makeLabelList([]string{"default_include"}, []string{}),
 			},
 			OsConfigurationAxis: labelListSelectValues{
 				"android": makeLabelList([]string{}, []string{"android_exclude"}),
@@ -243,7 +247,13 @@
 			OsArchConfigurationAxis: labelListSelectValues{
 				"linux_x86": makeLabelList([]string{"linux_x86_include"}, []string{}),
 			},
-			ProductVariableConfigurationAxis("a"): labelListSelectValues{
+			ProductVariableConfigurationAxis("product_with_defaults"): labelListSelectValues{
+				"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{}),
+			},
+			ProductVariableConfigurationAxis("product_only_with_excludes"): labelListSelectValues{
 				"a": makeLabelList([]string{}, []string{"not_in_value"}),
 			},
 		},
@@ -251,25 +261,31 @@
 
 	attr.ResolveExcludes()
 
-	expectedBaseIncludes := []Label{Label{Label: "all_include"}}
+	expectedBaseIncludes := []Label{{Label: "all_include"}}
 	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value.Includes) {
 		t.Errorf("Expected Value includes %q, got %q", attr.Value.Includes, expectedBaseIncludes)
 	}
 	var nilLabels []Label
 	expectedConfiguredIncludes := map[ConfigurationAxis]map[string][]Label{
-		ArchConfigurationAxis: map[string][]Label{
-			"arm":                nilLabels,
-			"x86":                makeLabels("arm_exclude", "x86_include"),
-			"conditions_default": makeLabels("arm_exclude"),
+		ArchConfigurationAxis: {
+			"arm":                      nilLabels,
+			"x86":                      makeLabels("arm_exclude", "x86_include"),
+			ConditionsDefaultConfigKey: makeLabels("arm_exclude", "default_include"),
 		},
-		OsConfigurationAxis: map[string][]Label{
-			"android":            nilLabels,
-			"linux":              makeLabels("android_exclude", "linux_include"),
-			"conditions_default": makeLabels("android_exclude"),
+		OsConfigurationAxis: {
+			"android":                  nilLabels,
+			"linux":                    makeLabels("android_exclude", "linux_include"),
+			ConditionsDefaultConfigKey: makeLabels("android_exclude"),
 		},
-		OsArchConfigurationAxis: map[string][]Label{
-			"linux_x86":          makeLabels("linux_x86_include"),
-			"conditions_default": nilLabels,
+		OsArchConfigurationAxis: {
+			"linux_x86":                makeLabels("linux_x86_include"),
+			ConditionsDefaultConfigKey: nilLabels,
+		},
+		ProductVariableConfigurationAxis("product_with_defaults"): {
+			"a":                        nilLabels,
+			"b":                        makeLabels("b_val"),
+			"c":                        makeLabels("c_val"),
+			ConditionsDefaultConfigKey: makeLabels("c_val", "default", "default2"),
 		},
 	}
 	for _, axis := range attr.SortedConfigurationAxes() {
@@ -285,7 +301,294 @@
 		for config, value := range gotForAxis {
 			if expected, ok := expectedForAxis[config]; ok {
 				if !reflect.DeepEqual(expected, value.Includes) {
-					t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value.Includes)
+					t.Errorf("For %s,\nexpected: %#v\ngot %#v", axis, expected, value.Includes)
+				}
+			} else {
+				t.Errorf("Got unexpected config %q for %s", config, axis)
+			}
+		}
+	}
+}
+
+// labelAddSuffixForTypeMapper returns a LabelMapper that adds suffix to label name for modules of
+// typ
+func labelAddSuffixForTypeMapper(suffix, typ string) LabelMapper {
+	return func(omc OtherModuleContext, label Label) (string, bool) {
+		m, ok := omc.ModuleFromName(label.Label)
+		if !ok {
+			return label.Label, false
+		}
+		mTyp := omc.OtherModuleType(m)
+		if typ == mTyp {
+			return label.Label + suffix, true
+		}
+		return label.Label, false
+	}
+}
+
+func TestPartitionLabelListAttribute(t *testing.T) {
+	testCases := []struct {
+		name           string
+		ctx            *otherModuleTestContext
+		labelList      LabelListAttribute
+		filters        LabelPartitions
+		expected       PartitionToLabelListAttribute
+		expectedErrMsg *string
+	}{
+		{
+			name: "no configurable values",
+			ctx:  &otherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, remainder partition",
+			ctx:  &otherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}, Keep_remainder: true},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "d.d", "e.e"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, empty partition",
+			ctx:  &otherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "c.c"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, has map",
+			ctx: &otherModuleTestContext{
+				modules: []testModuleInfo{testModuleInfo{name: "srcs", typ: "fg", dir: "dir"}},
+			},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "srcs", "b.b", "c.c"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}, LabelMapper: labelAddSuffixForTypeMapper("_a", "fg")},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "srcs_a"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "configurable values, keeps empty if excludes",
+			ctx:  &otherModuleTestContext{},
+			labelList: LabelListAttribute{
+				ConfigurableValues: configurableLabelLists{
+					ArchConfigurationAxis: labelListSelectValues{
+						"x86":    makeLabelList([]string{"a.a", "c.c"}, []string{}),
+						"arm":    makeLabelList([]string{"b.b"}, []string{}),
+						"x86_64": makeLabelList([]string{"b.b"}, []string{"d.d"}),
+					},
+				},
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"x86":    makeLabelList([]string{"a.a"}, []string{}),
+							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
+						},
+					},
+				},
+				"B": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"arm":    makeLabelList([]string{"b.b"}, []string{}),
+							"x86_64": makeLabelList([]string{"b.b"}, []string{"c.c"}),
+						},
+					},
+				},
+				"C": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"x86":    makeLabelList([]string{"c.c"}, []string{}),
+							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "error for multiple partitions same value",
+			ctx:  &otherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A":       LabelPartition{Extensions: []string{".a"}},
+				"other A": LabelPartition{Extensions: []string{".a"}},
+			},
+			expected:       PartitionToLabelListAttribute{},
+			expectedErrMsg: proptools.StringPtr(`"a.a" was found in multiple partitions:`),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := PartitionLabelListAttribute(tc.ctx, &tc.labelList, tc.filters)
+
+			if hasErrors, expectsErr := len(tc.ctx.errors) > 0, tc.expectedErrMsg != nil; hasErrors != expectsErr {
+				t.Errorf("Unexpected error(s): %q, expected: %q", tc.ctx.errors, *tc.expectedErrMsg)
+			} else if tc.expectedErrMsg != nil {
+				found := false
+				for _, err := range tc.ctx.errors {
+					if strings.Contains(err, *tc.expectedErrMsg) {
+						found = true
+						break
+					}
+				}
+
+				if !found {
+					t.Errorf("Expected error message: %q, got %q", *tc.expectedErrMsg, tc.ctx.errors)
+				}
+				return
+			}
+
+			if len(tc.expected) != len(got) {
+				t.Errorf("Expected %d partitions, got %d partitions", len(tc.expected), len(got))
+			}
+			for partition, expectedLla := range tc.expected {
+				gotLla, ok := got[partition]
+				if !ok {
+					t.Errorf("Expected partition %q, but it was not found %v", partition, got)
+					continue
+				}
+				expectedLabelList := expectedLla.Value
+				gotLabelList := gotLla.Value
+				if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
+					t.Errorf("Expected no config includes %v, got %v", expectedLabelList.Includes, gotLabelList.Includes)
+				}
+				expectedAxes := expectedLla.SortedConfigurationAxes()
+				gotAxes := gotLla.SortedConfigurationAxes()
+				if !reflect.DeepEqual(expectedAxes, gotAxes) {
+					t.Errorf("Expected axes %v, got %v (%#v)", expectedAxes, gotAxes, gotLla)
+				}
+				for _, axis := range expectedLla.SortedConfigurationAxes() {
+					if _, exists := gotLla.ConfigurableValues[axis]; !exists {
+						t.Errorf("Expected %s to be a supported axis, but it was not found", axis)
+					}
+					if expected, got := expectedLla.ConfigurableValues[axis], gotLla.ConfigurableValues[axis]; len(expected) != len(got) {
+						t.Errorf("For axis %q: expected configs %v, got %v", axis, expected, got)
+					}
+					for config, expectedLabelList := range expectedLla.ConfigurableValues[axis] {
+						gotLabelList, exists := gotLla.ConfigurableValues[axis][config]
+						if !exists {
+							t.Errorf("Expected %s to be a supported config, but config was not found", config)
+							continue
+						}
+						if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
+							t.Errorf("Expected %s %s includes %v, got %v", axis, config, expectedLabelList.Includes, gotLabelList.Includes)
+						}
+					}
+				}
+			}
+		})
+	}
+}
+
+func TestDeduplicateAxesFromBase(t *testing.T) {
+	attr := StringListAttribute{
+		Value: []string{
+			"all_include",
+			"arm_include",
+			"android_include",
+			"linux_x86_include",
+		},
+		ConfigurableValues: configurableStringLists{
+			ArchConfigurationAxis: stringListSelectValues{
+				"arm": []string{"arm_include"},
+				"x86": []string{"x86_include"},
+			},
+			OsConfigurationAxis: stringListSelectValues{
+				"android": []string{"android_include"},
+				"linux":   []string{"linux_include"},
+			},
+			OsArchConfigurationAxis: stringListSelectValues{
+				"linux_x86": {"linux_x86_include"},
+			},
+			ProductVariableConfigurationAxis("a"): stringListSelectValues{
+				"a": []string{"not_in_value"},
+			},
+		},
+	}
+
+	attr.DeduplicateAxesFromBase()
+
+	expectedBaseIncludes := []string{
+		"all_include",
+		"arm_include",
+		"android_include",
+		"linux_x86_include",
+	}
+	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value) {
+		t.Errorf("Expected Value includes %q, got %q", attr.Value, expectedBaseIncludes)
+	}
+	expectedConfiguredIncludes := configurableStringLists{
+		ArchConfigurationAxis: stringListSelectValues{
+			"x86": []string{"x86_include"},
+		},
+		OsConfigurationAxis: stringListSelectValues{
+			"linux": []string{"linux_include"},
+		},
+		OsArchConfigurationAxis: stringListSelectValues{},
+		ProductVariableConfigurationAxis("a"): stringListSelectValues{
+			"a": []string{"not_in_value"},
+		},
+	}
+	for _, axis := range attr.SortedConfigurationAxes() {
+		if _, ok := expectedConfiguredIncludes[axis]; !ok {
+			t.Errorf("Found unexpected axis %s", axis)
+			continue
+		}
+		expectedForAxis := expectedConfiguredIncludes[axis]
+		gotForAxis := attr.ConfigurableValues[axis]
+		if len(expectedForAxis) != len(gotForAxis) {
+			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
+		}
+		for config, value := range gotForAxis {
+			if expected, ok := expectedForAxis[config]; ok {
+				if !reflect.DeepEqual(expected, value) {
+					t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value)
 				}
 			} else {
 				t.Errorf("Got unexpected config %q for %s", config, axis)
diff --git a/bazel/testing.go b/bazel/testing.go
new file mode 100644
index 0000000..23c8350
--- /dev/null
+++ b/bazel/testing.go
@@ -0,0 +1,105 @@
+// Copyright 2021 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 bazel
+
+import (
+	"fmt"
+
+	"github.com/google/blueprint"
+)
+
+// testModuleInfo implements blueprint.Module interface with sufficient information to mock a subset of
+// a blueprint ModuleContext
+type testModuleInfo struct {
+	name string
+	typ  string
+	dir  string
+}
+
+// Name returns name for testModuleInfo -- required to implement blueprint.Module
+func (mi testModuleInfo) Name() string {
+	return mi.name
+}
+
+// GenerateBuildActions unused, but required to implmeent blueprint.Module
+func (mi testModuleInfo) GenerateBuildActions(blueprint.ModuleContext) {}
+
+func (mi testModuleInfo) equals(other testModuleInfo) bool {
+	return mi.name == other.name && mi.typ == other.typ && mi.dir == other.dir
+}
+
+// ensure testModuleInfo implements blueprint.Module
+var _ blueprint.Module = testModuleInfo{}
+
+// otherModuleTestContext is a mock context that implements OtherModuleContext
+type otherModuleTestContext struct {
+	modules []testModuleInfo
+	errors  []string
+}
+
+// ModuleFromName retrieves the testModuleInfo corresponding to name, if it exists
+func (omc *otherModuleTestContext) ModuleFromName(name string) (blueprint.Module, bool) {
+	for _, m := range omc.modules {
+		if m.name == name {
+			return m, true
+		}
+	}
+	return testModuleInfo{}, false
+}
+
+// testModuleInfo returns the testModuleInfo corresponding to a blueprint.Module if it exists in omc
+func (omc *otherModuleTestContext) testModuleInfo(m blueprint.Module) (testModuleInfo, bool) {
+	mi, ok := m.(testModuleInfo)
+	if !ok {
+		return testModuleInfo{}, false
+	}
+	for _, other := range omc.modules {
+		if other.equals(mi) {
+			return mi, true
+		}
+	}
+	return testModuleInfo{}, false
+}
+
+// OtherModuleType returns type of m if it exists in omc
+func (omc *otherModuleTestContext) OtherModuleType(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.typ
+	}
+	return ""
+}
+
+// OtherModuleName returns name of m if it exists in omc
+func (omc *otherModuleTestContext) OtherModuleName(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.name
+	}
+	return ""
+}
+
+// OtherModuleDir returns dir of m if it exists in omc
+func (omc *otherModuleTestContext) OtherModuleDir(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.dir
+	}
+	return ""
+}
+
+func (omc *otherModuleTestContext) ModuleErrorf(format string, args ...interface{}) {
+	omc.errors = append(omc.errors, fmt.Sprintf(format, args...))
+}
+
+// Ensure otherModuleTestContext implements OtherModuleContext
+var _ OtherModuleContext = &otherModuleTestContext{}
diff --git a/bootstrap.bash b/bootstrap.bash
index 4db8539..726692a 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -15,9 +15,9 @@
 # limitations under the License.
 
 echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
-echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Use `m --soong-only` with a standalone OUT_DIR instead.' >&2
 echo 'Without envsetup.sh, use:' >&2
-echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '  build/soong/soong_ui.bash --make-mode --soong-only' >&2
 echo '======================================================' >&2
 exit 1
 
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index b1ccc96..ae0fb11 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -11,7 +11,6 @@
         "build_conversion.go",
         "bzl_conversion.go",
         "configurability.go",
-        "compatibility.go",
         "constants.go",
         "conversion.go",
         "metrics.go",
@@ -19,6 +18,8 @@
     ],
     deps: [
         "soong-android",
+        "soong-android-soongconfig",
+        "soong-shared",
         "soong-apex",
         "soong-bazel",
         "soong-cc",
@@ -27,23 +28,31 @@
         "soong-genrule",
         "soong-python",
         "soong-sh",
+        "soong-ui-metrics",
     ],
     testSrcs: [
         "android_app_certificate_conversion_test.go",
+        "android_app_conversion_test.go",
         "apex_conversion_test.go",
         "apex_key_conversion_test.go",
         "build_conversion_test.go",
         "bzl_conversion_test.go",
+        "cc_binary_conversion_test.go",
+        "cc_genrule_conversion_test.go",
         "cc_library_conversion_test.go",
         "cc_library_headers_conversion_test.go",
+        "cc_library_shared_conversion_test.go",
         "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
         "conversion_test.go",
+        "filegroup_conversion_test.go",
+        "genrule_conversion_test.go",
         "performance_test.go",
         "prebuilt_etc_conversion_test.go",
         "python_binary_conversion_test.go",
         "python_library_conversion_test.go",
         "sh_conversion_test.go",
+        "soong_config_module_type_conversion_test.go",
         "testing.go",
     ],
     pluginFor: [
diff --git a/bp2build/android_app_certificate_conversion_test.go b/bp2build/android_app_certificate_conversion_test.go
index 022c687..035a352 100644
--- a/bp2build/android_app_certificate_conversion_test.go
+++ b/bp2build/android_app_certificate_conversion_test.go
@@ -31,19 +31,19 @@
 
 func TestAndroidAppCertificateSimple(t *testing.T) {
 	runAndroidAppCertificateTestCase(t, bp2buildTestCase{
-		description:                        "Android app certificate - simple example",
-		moduleTypeUnderTest:                "android_app_certificate",
-		moduleTypeUnderTestFactory:         java.AndroidAppCertificateFactory,
-		moduleTypeUnderTestBp2BuildMutator: java.AndroidAppCertificateBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "Android app certificate - simple example",
+		moduleTypeUnderTest:        "android_app_certificate",
+		moduleTypeUnderTestFactory: java.AndroidAppCertificateFactory,
+		filesystem:                 map[string]string{},
 		blueprint: `
 android_app_certificate {
         name: "com.android.apogee.cert",
         certificate: "chamber_of_secrets_dir",
 }
 `,
-		expectedBazelTargets: []string{`android_app_certificate(
-    name = "com.android.apogee.cert",
-    certificate = "chamber_of_secrets_dir",
-)`}})
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_app_certificate", "com.android.apogee.cert", attrNameToString{
+				"certificate": `"chamber_of_secrets_dir"`,
+			}),
+		}})
 }
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
new file mode 100644
index 0000000..153817b
--- /dev/null
+++ b/bp2build/android_app_conversion_test.go
@@ -0,0 +1,90 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+
+	"testing"
+)
+
+func runAndroidAppTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerAndroidAppModuleTypes, tc)
+}
+
+func registerAndroidAppModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestMinimalAndroidApp(t *testing.T) {
+	runAndroidAppTestCase(t, bp2buildTestCase{
+		description:                "Android app - simple example",
+		moduleTypeUnderTest:        "android_app",
+		moduleTypeUnderTestFactory: java.AndroidAppFactory,
+		filesystem: map[string]string{
+			"app.java":            "",
+			"res/res.png":         "",
+			"AndroidManifest.xml": "",
+		},
+		blueprint: `
+android_app {
+        name: "TestApp",
+        srcs: ["app.java"],
+        sdk_version: "current",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_binary", "TestApp", attrNameToString{
+				"srcs":           `["app.java"]`,
+				"manifest":       `"AndroidManifest.xml"`,
+				"resource_files": `["res/res.png"]`,
+			}),
+		}})
+}
+
+func TestAndroidAppAllSupportedFields(t *testing.T) {
+	runAndroidAppTestCase(t, bp2buildTestCase{
+		description:                "Android app - all supported fields",
+		moduleTypeUnderTest:        "android_app",
+		moduleTypeUnderTestFactory: java.AndroidAppFactory,
+		filesystem: map[string]string{
+			"app.java":                     "",
+			"resa/res.png":                 "",
+			"resb/res.png":                 "",
+			"manifest/AndroidManifest.xml": "",
+		},
+		blueprint: `
+android_app {
+        name: "TestApp",
+        srcs: ["app.java"],
+        sdk_version: "current",
+        package_name: "com.google",
+        resource_dirs: ["resa", "resb"],
+        manifest: "manifest/AndroidManifest.xml",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_binary", "TestApp", attrNameToString{
+				"srcs":     `["app.java"]`,
+				"manifest": `"manifest/AndroidManifest.xml"`,
+				"resource_files": `[
+        "resa/res.png",
+        "resb/res.png",
+    ]`,
+				"custom_package": `"com.google"`,
+			}),
+		}})
+}
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index 456f18a..a3825e6 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -19,6 +19,7 @@
 	"android/soong/apex"
 	"android/soong/cc"
 	"android/soong/java"
+	"android/soong/sh"
 
 	"testing"
 )
@@ -32,6 +33,8 @@
 	// CC module types needed as they can be APEX dependencies
 	cc.RegisterCCBuildComponents(ctx)
 
+	ctx.RegisterModuleType("sh_binary", sh.ShBinaryFactory)
+	ctx.RegisterModuleType("cc_binary", cc.BinaryFactory)
 	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
 	ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory)
 	ctx.RegisterModuleType("android_app_certificate", java.AndroidAppCertificateFactory)
@@ -40,60 +43,62 @@
 
 func TestApexBundleSimple(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
-		description:                        "apex - simple example",
-		moduleTypeUnderTest:                "apex",
-		moduleTypeUnderTestFactory:         apex.BundleFactory,
-		moduleTypeUnderTestBp2BuildMutator: apex.ApexBundleBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "apex - example with all props",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
 		blueprint: `
 apex_key {
-        name: "com.android.apogee.key",
-        public_key: "com.android.apogee.avbpubkey",
-        private_key: "com.android.apogee.pem",
+	name: "com.android.apogee.key",
+	public_key: "com.android.apogee.avbpubkey",
+	private_key: "com.android.apogee.pem",
 	bazel_module: { bp2build_available: false },
 }
 
 android_app_certificate {
-        name: "com.android.apogee.certificate",
-        certificate: "com.android.apogee",
-        bazel_module: { bp2build_available: false },
-}
-
-cc_library {
-        name: "native_shared_lib_1",
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
 	bazel_module: { bp2build_available: false },
 }
 
 cc_library {
-        name: "native_shared_lib_2",
+	name: "native_shared_lib_1",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_2",
 	bazel_module: { bp2build_available: false },
 }
 
 // TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
-        name: "pretend_prebuilt_1",
-        bazel_module: { bp2build_available: false },
+	name: "pretend_prebuilt_1",
+	bazel_module: { bp2build_available: false },
 }
 
 // TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
-        name: "pretend_prebuilt_2",
-        bazel_module: { bp2build_available: false },
+	name: "pretend_prebuilt_2",
+	bazel_module: { bp2build_available: false },
 }
 
 filegroup {
 	name: "com.android.apogee-file_contexts",
-        srcs: [
-                "com.android.apogee-file_contexts",
-        ],
-        bazel_module: { bp2build_available: false },
+	srcs: [
+			"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
 }
 
+cc_binary { name: "cc_binary_1", bazel_module: { bp2build_available: false } }
+sh_binary { name: "sh_binary_2", bazel_module: { bp2build_available: false } }
+
 apex {
 	name: "com.android.apogee",
 	manifest: "apogee_manifest.json",
 	androidManifest: "ApogeeAndroidManifest.xml",
-        file_contexts: "com.android.apogee-file_contexts",
+	file_contexts: "com.android.apogee-file_contexts",
 	min_sdk_version: "29",
 	key: "com.android.apogee.key",
 	certificate: "com.android.apogee.certificate",
@@ -104,8 +109,8 @@
 	    "native_shared_lib_2",
 	],
 	binaries: [
-            "binary_1",
-	    "binary_2",
+		"cc_binary_1",
+		"sh_binary_2",
 	],
 	prebuilts: [
 	    "pretend_prebuilt_1",
@@ -113,57 +118,56 @@
 	],
 }
 `,
-		expectedBazelTargets: []string{`apex(
-    name = "com.android.apogee",
-    android_manifest = "ApogeeAndroidManifest.xml",
-    binaries = [
-        "binary_1",
-        "binary_2",
-    ],
-    certificate = ":com.android.apogee.certificate",
-    file_contexts = ":com.android.apogee-file_contexts",
-    installable = False,
-    key = ":com.android.apogee.key",
-    manifest = "apogee_manifest.json",
-    min_sdk_version = "29",
-    native_shared_libs = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"android_manifest": `"ApogeeAndroidManifest.xml"`,
+				"binaries": `[
+        ":cc_binary_1",
+        ":sh_binary_2",
+    ]`,
+				"certificate":     `":com.android.apogee.certificate"`,
+				"file_contexts":   `":com.android.apogee-file_contexts"`,
+				"installable":     "False",
+				"key":             `":com.android.apogee.key"`,
+				"manifest":        `"apogee_manifest.json"`,
+				"min_sdk_version": `"29"`,
+				"native_shared_libs": `[
         ":native_shared_lib_1",
         ":native_shared_lib_2",
-    ],
-    prebuilts = [
+    ]`,
+				"prebuilts": `[
         ":pretend_prebuilt_1",
         ":pretend_prebuilt_2",
-    ],
-    updatable = False,
-)`}})
+    ]`,
+				"updatable": "False",
+			}),
+		}})
 }
 
 func TestApexBundleDefaultPropertyValues(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
-		description:                        "apex - default property values",
-		moduleTypeUnderTest:                "apex",
-		moduleTypeUnderTestFactory:         apex.BundleFactory,
-		moduleTypeUnderTestBp2BuildMutator: apex.ApexBundleBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "apex - default property values",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
 		blueprint: `
 apex {
 	name: "com.android.apogee",
 	manifest: "apogee_manifest.json",
 }
 `,
-		expectedBazelTargets: []string{`apex(
-    name = "com.android.apogee",
-    manifest = "apogee_manifest.json",
-)`}})
+		expectedBazelTargets: []string{makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+			"manifest": `"apogee_manifest.json"`,
+		}),
+		}})
 }
 
 func TestApexBundleHasBazelModuleProps(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
-		description:                        "apex - has bazel module props",
-		moduleTypeUnderTest:                "apex",
-		moduleTypeUnderTestFactory:         apex.BundleFactory,
-		moduleTypeUnderTestBp2BuildMutator: apex.ApexBundleBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "apex - has bazel module props",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
 		blueprint: `
 apex {
 	name: "apogee",
@@ -171,8 +175,8 @@
 	bazel_module: { bp2build_available: true },
 }
 `,
-		expectedBazelTargets: []string{`apex(
-    name = "apogee",
-    manifest = "manifest.json",
-)`}})
+		expectedBazelTargets: []string{makeBazelTarget("apex", "apogee", attrNameToString{
+			"manifest": `"manifest.json"`,
+		}),
+		}})
 }
diff --git a/bp2build/apex_key_conversion_test.go b/bp2build/apex_key_conversion_test.go
index 8e1aa09..1d949901 100644
--- a/bp2build/apex_key_conversion_test.go
+++ b/bp2build/apex_key_conversion_test.go
@@ -31,11 +31,10 @@
 
 func TestApexKeySimple(t *testing.T) {
 	runApexKeyTestCase(t, bp2buildTestCase{
-		description:                        "apex key - simple example",
-		moduleTypeUnderTest:                "apex_key",
-		moduleTypeUnderTestFactory:         apex.ApexKeyFactory,
-		moduleTypeUnderTestBp2BuildMutator: apex.ApexKeyBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "apex key - simple example",
+		moduleTypeUnderTest:        "apex_key",
+		moduleTypeUnderTestFactory: apex.ApexKeyFactory,
+		filesystem:                 map[string]string{},
 		blueprint: `
 apex_key {
         name: "com.android.apogee.key",
@@ -43,9 +42,9 @@
         private_key: "com.android.apogee.pem",
 }
 `,
-		expectedBazelTargets: []string{`apex_key(
-    name = "com.android.apogee.key",
-    private_key = "com.android.apogee.pem",
-    public_key = "com.android.apogee.avbpubkey",
-)`}})
+		expectedBazelTargets: []string{makeBazelTarget("apex_key", "com.android.apogee.key", attrNameToString{
+			"private_key": `"com.android.apogee.pem"`,
+			"public_key":  `"com.android.apogee.avbpubkey"`,
+		}),
+		}})
 }
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 06a7306..b0c3899 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -15,10 +15,12 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/bazel"
 	"fmt"
 	"os"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/bazel"
 )
 
 // Codegen is the backend of bp2build. The code generator is responsible for
@@ -29,14 +31,22 @@
 	bp2buildDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(bp2buildDir)
 
-	buildToTargets, metrics, compatLayer := GenerateBazelTargets(ctx, true)
-	bp2buildFiles := CreateBazelFiles(nil, buildToTargets, ctx.mode)
+	res, errs := GenerateBazelTargets(ctx, true)
+	if len(errs) > 0 {
+		errMsgs := make([]string, len(errs))
+		for i, err := range errs {
+			errMsgs[i] = fmt.Sprintf("%q", err)
+		}
+		fmt.Printf("ERROR: Encountered %d error(s): \nERROR: %s", len(errs), strings.Join(errMsgs, "\n"))
+		os.Exit(1)
+	}
+	bp2buildFiles := CreateBazelFiles(nil, res.buildFileToTargets, ctx.mode)
 	writeFiles(ctx, bp2buildDir, bp2buildFiles)
 
 	soongInjectionDir := android.PathForOutput(ctx, bazel.SoongInjectionDirName)
-	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles(compatLayer))
+	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles(ctx.Config(), res.metrics))
 
-	return metrics
+	return res.metrics
 }
 
 // Get the output directory and create it if it doesn't exist.
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index f652a35..54b59af 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -153,10 +153,11 @@
 }
 
 type CodegenContext struct {
-	config         android.Config
-	context        android.Context
-	mode           CodegenMode
-	additionalDeps []string
+	config             android.Config
+	context            android.Context
+	mode               CodegenMode
+	additionalDeps     []string
+	unconvertedDepMode unconvertedDepsMode
 }
 
 func (c *CodegenContext) Mode() CodegenMode {
@@ -181,6 +182,16 @@
 	QueryView
 )
 
+type unconvertedDepsMode int
+
+const (
+	// Include a warning in conversion metrics about converted modules with unconverted direct deps
+	warnUnconvertedDeps unconvertedDepsMode = iota
+	// Error and fail conversion if encountering a module with unconverted direct deps
+	// Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
+	errorModulesUnconvertedDeps
+)
+
 func (mode CodegenMode) String() string {
 	switch mode {
 	case Bp2Build:
@@ -211,10 +222,15 @@
 // NewCodegenContext creates a wrapper context that conforms to PathContext for
 // writing BUILD files in the output directory.
 func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) *CodegenContext {
+	var unconvertedDeps unconvertedDepsMode
+	if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
+		unconvertedDeps = errorModulesUnconvertedDeps
+	}
 	return &CodegenContext{
-		context: context,
-		config:  config,
-		mode:    mode,
+		context:            context,
+		config:             config,
+		mode:               mode,
+		unconvertedDepMode: unconvertedDeps,
 	}
 }
 
@@ -223,28 +239,33 @@
 func propsToAttributes(props map[string]string) string {
 	var attributes string
 	for _, propName := range android.SortedStringKeys(props) {
-		if shouldGenerateAttribute(propName) {
-			attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
-		}
+		attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
 	}
 	return attributes
 }
 
-func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (map[string]BazelTargets, CodegenMetrics, CodegenCompatLayer) {
+type conversionResults struct {
+	buildFileToTargets map[string]BazelTargets
+	metrics            CodegenMetrics
+}
+
+func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
+	return r.buildFileToTargets
+}
+
+func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
 	buildFileToTargets := make(map[string]BazelTargets)
 	buildFileToAppend := make(map[string]bool)
 
 	// Simple metrics tracking for bp2build
 	metrics := CodegenMetrics{
-		RuleClassCount: make(map[string]int),
-	}
-
-	compatLayer := CodegenCompatLayer{
-		NameToLabelMap: make(map[string]string),
+		ruleClassCount: make(map[string]uint64),
 	}
 
 	dirs := make(map[string]bool)
 
+	var errs []error
+
 	bpCtx := ctx.Context()
 	bpCtx.VisitAllModules(func(m blueprint.Module) {
 		dir := bpCtx.ModuleDir(m)
@@ -254,35 +275,63 @@
 
 		switch ctx.Mode() {
 		case Bp2Build:
+			// There are two main ways of converting a Soong module to Bazel:
+			// 1) Manually handcrafting a Bazel target and associating the module with its label
+			// 2) Automatically generating with bp2build converters
+			//
+			// bp2build converters are used for the majority of modules.
 			if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
-				metrics.handCraftedTargetCount += 1
-				metrics.TotalModuleCount += 1
-				compatLayer.AddNameToLabelEntry(m.Name(), b.HandcraftedLabel())
+				// Handle modules converted to handcrafted targets.
+				//
+				// Since these modules are associated with some handcrafted
+				// target in a BUILD file, we simply append the entire contents
+				// of that BUILD file to the generated BUILD file.
+				//
+				// The append operation is only done once, even if there are
+				// multiple modules from the same directory associated to
+				// targets in the same BUILD file (or package).
+
+				// Log the module.
+				metrics.AddConvertedModule(m.Name(), Handcrafted)
+
 				pathToBuildFile := getBazelPackagePath(b)
-				// We are using the entire contents of handcrafted build file, so if multiple targets within
-				// a package have handcrafted targets, we only want to include the contents one time.
 				if _, exists := buildFileToAppend[pathToBuildFile]; exists {
+					// Append the BUILD file content once per package, at most.
 					return
 				}
 				t, err := getHandcraftedBuildContent(ctx, b, pathToBuildFile)
 				if err != nil {
-					panic(fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
+					errs = append(errs, fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
+					return
 				}
 				targets = append(targets, t)
 				// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
 				// something more targeted based on the rule type and target
 				buildFileToAppend[pathToBuildFile] = true
 			} else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
+				// Handle modules converted to generated targets.
+
+				// Log the module.
+				metrics.AddConvertedModule(m.Name(), Generated)
+
+				// Handle modules with unconverted deps. By default, emit a warning.
+				if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
+					msg := fmt.Sprintf("%q depends on unconverted modules: %s", m.Name(), strings.Join(unconvertedDeps, ", "))
+					if ctx.unconvertedDepMode == warnUnconvertedDeps {
+						metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
+					} else if ctx.unconvertedDepMode == errorModulesUnconvertedDeps {
+						errs = append(errs, fmt.Errorf(msg))
+						return
+					}
+				}
 				targets = generateBazelTargets(bpCtx, aModule)
 				for _, t := range targets {
-					if t.name == m.Name() {
-						// only add targets that exist in Soong to compatibility layer
-						compatLayer.AddNameToLabelEntry(m.Name(), t.Label())
-					}
-					metrics.RuleClassCount[t.ruleClass] += 1
+					// A module can potentially generate more than 1 Bazel
+					// target, each of a different rule class.
+					metrics.IncrementRuleClassCount(t.ruleClass)
 				}
 			} else {
-				metrics.TotalModuleCount += 1
+				metrics.IncrementUnconvertedCount()
 				return
 			}
 		case QueryView:
@@ -295,11 +344,17 @@
 			t := generateSoongModuleTarget(bpCtx, m)
 			targets = append(targets, t)
 		default:
-			panic(fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
+			errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
+			return
 		}
 
 		buildFileToTargets[dir] = append(buildFileToTargets[dir], targets...)
 	})
+
+	if len(errs) > 0 {
+		return conversionResults{}, errs
+	}
+
 	if generateFilegroups {
 		// Add a filegroup target that exposes all sources in the subtree of this package
 		// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
@@ -312,7 +367,10 @@
 		}
 	}
 
-	return buildFileToTargets, metrics, compatLayer
+	return conversionResults{
+		buildFileToTargets: buildFileToTargets,
+		metrics:            metrics,
+	}, errs
 }
 
 func getBazelPackagePath(b android.Bazelable) string {
@@ -351,7 +409,7 @@
 	TargetPackage() string
 	BazelRuleClass() string
 	BazelRuleLoadLocation() string
-	BazelAttributes() interface{}
+	BazelAttributes() []interface{}
 }
 
 func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) BazelTarget {
@@ -359,9 +417,11 @@
 	bzlLoadLocation := m.BazelRuleLoadLocation()
 
 	// extract the bazel attributes from the module.
-	props := extractModuleProperties([]interface{}{m.BazelAttributes()})
+	attrs := m.BazelAttributes()
+	props := extractModuleProperties(attrs, true)
 
-	delete(props.Attrs, "bp2build_available")
+	// name is handled in a special manner
+	delete(props.Attrs, "name")
 
 	// Return the Bazel target with rule class and attributes, ready to be
 	// code-generated.
@@ -396,6 +456,10 @@
 			depLabels[qualifiedTargetLabel(ctx, depModule)] = true
 		})
 	}
+
+	for p, _ := range ignoredPropNames {
+		delete(props.Attrs, p)
+	}
 	attributes := propsToAttributes(props.Attrs)
 
 	depLabelList := "[\n"
@@ -422,14 +486,14 @@
 	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
 	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
 	if aModule, ok := m.(android.Module); ok {
-		return extractModuleProperties(aModule.GetProperties())
+		return extractModuleProperties(aModule.GetProperties(), false)
 	}
 
 	return BazelAttributes{}
 }
 
 // Generically extract module properties and types into a map, keyed by the module property name.
-func extractModuleProperties(props []interface{}) BazelAttributes {
+func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) BazelAttributes {
 	ret := map[string]string{}
 
 	// Iterate over this android.Module's property structs.
@@ -443,6 +507,11 @@
 		if isStructPtr(propertiesValue.Type()) {
 			structValue := propertiesValue.Elem()
 			for k, v := range extractStructProperties(structValue, 0) {
+				if existing, exists := ret[k]; checkForDuplicateProperties && exists {
+					panic(fmt.Errorf(
+						"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
+						k, existing))
+				}
 				ret[k] = v
 			}
 		} else {
@@ -463,8 +532,8 @@
 
 // prettyPrint a property value into the equivalent Starlark representation
 // recursively.
-func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
-	if isZero(propertyValue) {
+func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
+	if !emitZeroValues && isZero(propertyValue) {
 		// A property value being set or unset actually matters -- Soong does set default
 		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
 		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
@@ -487,7 +556,7 @@
 	case reflect.Int, reflect.Uint, reflect.Int64:
 		ret = fmt.Sprintf("%v", propertyValue.Interface())
 	case reflect.Ptr:
-		return prettyPrint(propertyValue.Elem(), indent)
+		return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
 	case reflect.Slice:
 		if propertyValue.Len() == 0 {
 			return "[]", nil
@@ -496,7 +565,7 @@
 		if propertyValue.Len() == 1 {
 			// Single-line list for list with only 1 element
 			ret += "["
-			indexedValue, err := prettyPrint(propertyValue.Index(0), indent)
+			indexedValue, err := prettyPrint(propertyValue.Index(0), indent, emitZeroValues)
 			if err != nil {
 				return "", err
 			}
@@ -506,7 +575,7 @@
 			// otherwise, use a multiline list.
 			ret += "[\n"
 			for i := 0; i < propertyValue.Len(); i++ {
-				indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
+				indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1, emitZeroValues)
 				if err != nil {
 					return "", err
 				}
@@ -576,8 +645,22 @@
 			continue
 		}
 
+		// if the struct is embedded (anonymous), flatten the properties into the containing struct
+		if field.Anonymous {
+			if field.Type.Kind() == reflect.Ptr {
+				fieldValue = fieldValue.Elem()
+			}
+			if fieldValue.Type().Kind() == reflect.Struct {
+				propsToMerge := extractStructProperties(fieldValue, indent)
+				for prop, value := range propsToMerge {
+					ret[prop] = value
+				}
+				continue
+			}
+		}
+
 		propertyName := proptools.PropertyNameForField(field.Name)
-		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
+		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1, false)
 		if err != nil {
 			panic(
 				fmt.Errorf(
@@ -615,9 +698,9 @@
 		} else {
 			return true
 		}
-	// Always print bools, if you want a bool attribute to be able to take the default value, use a
-	// bool pointer instead
-	case reflect.Bool:
+	// Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
+	// pointer instead
+	case reflect.Bool, reflect.String:
 		return false
 	default:
 		if !value.IsValid() {
@@ -648,10 +731,6 @@
 	return strings.Repeat("    ", indent)
 }
 
-func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
-	return strings.Replace(c.ModuleName(logicModule), bazel.BazelTargetModuleNamePrefix, "", 1)
-}
-
 func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
 	name := ""
 	if c.ModuleSubDir(logicModule) != "" {
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 0d9106c..1440b6f 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -15,10 +15,12 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/genrule"
+	"fmt"
 	"strings"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/python"
 )
 
 func TestGenerateSoongModuleTargets(t *testing.T) {
@@ -39,6 +41,7 @@
     soong_module_deps = [
     ],
     bool_prop = False,
+    string_prop = "",
 )`,
 		},
 		{
@@ -56,6 +59,7 @@
     soong_module_deps = [
     ],
     bool_prop = True,
+    string_prop = "",
 )`,
 		},
 		{
@@ -74,6 +78,7 @@
     ],
     bool_prop = False,
     owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
+    string_prop = "",
 )`,
 		},
 		{
@@ -92,6 +97,7 @@
     ],
     bool_prop = False,
     required = ["bar"],
+    string_prop = "",
 )`,
 		},
 		{
@@ -109,6 +115,7 @@
     soong_module_deps = [
     ],
     bool_prop = False,
+    string_prop = "",
     target_required = [
         "qux",
         "bazqux",
@@ -145,6 +152,7 @@
         "tag": ".bar",
         "targets": ["goal_bar"],
     }],
+    string_prop = "",
 )`,
 		},
 		{
@@ -177,6 +185,7 @@
     }],
     owner = "custom_owner",
     required = ["bar"],
+    string_prop = "",
     target_required = [
         "qux",
         "bazqux",
@@ -200,7 +209,8 @@
 			android.FailIfErrored(t, errs)
 
 			codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
-			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 			if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
 				t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
 			}
@@ -218,47 +228,59 @@
 }
 
 func TestGenerateBazelTargetModules(t *testing.T) {
-	testCases := []struct {
-		name                 string
-		bp                   string
-		expectedBazelTargets []string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			bp: `custom {
+			description: "string ptr props",
+			blueprint: `custom {
 	name: "foo",
-    string_list_prop: ["a", "b"],
-    string_prop: "a",
+    string_ptr_prop: "",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "foo",
-    string_list_prop = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_ptr_prop": `""`,
+				}),
+			},
+		},
+		{
+			description: "string props",
+			blueprint: `custom {
+  name: "foo",
+    string_list_prop: ["a", "b"],
+    string_ptr_prop: "a",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_list_prop": `[
         "a",
         "b",
-    ],
-    string_prop = "a",
-)`,
+    ]`,
+					"string_ptr_prop": `"a"`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
-	name: "control_characters",
+			description: "control characters",
+			blueprint: `custom {
+    name: "foo",
     string_list_prop: ["\t", "\n"],
-    string_prop: "a\t\n\r",
+    string_ptr_prop: "a\t\n\r",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "control_characters",
-    string_list_prop = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_list_prop": `[
         "\t",
         "\n",
-    ],
-    string_prop = "a\t\n\r",
-)`,
+    ]`,
+					"string_ptr_prop": `"a\t\n\r"`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "handles dep",
+			blueprint: `custom {
   name: "has_dep",
   arch_paths: [":dep"],
   bazel_module: { bp2build_available: true },
@@ -269,37 +291,109 @@
   arch_paths: ["abc"],
   bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "dep",
-    arch_paths = ["abc"],
-)`,
-				`custom(
-    name = "has_dep",
-    arch_paths = [":dep"],
-)`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "dep", attrNameToString{
+					"arch_paths": `["abc"]`,
+				}),
+				makeBazelTarget("custom", "has_dep", attrNameToString{
+					"arch_paths": `[":dep"]`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "arch-variant srcs",
+			blueprint: `custom {
     name: "arch_paths",
     arch: {
-      x86: {
-        arch_paths: ["abc"],
-      },
+      x86: { arch_paths: ["x86.txt"] },
+      x86_64:  { arch_paths: ["x86_64.txt"] },
+      arm:  { arch_paths: ["arm.txt"] },
+      arm64:  { arch_paths: ["arm64.txt"] },
+    },
+    target: {
+      linux: { arch_paths: ["linux.txt"] },
+      bionic: { arch_paths: ["bionic.txt"] },
+      host: { arch_paths: ["host.txt"] },
+      not_windows: { arch_paths: ["not_windows.txt"] },
+      android: { arch_paths: ["android.txt"] },
+      linux_musl: { arch_paths: ["linux_musl.txt"] },
+      musl: { arch_paths: ["musl.txt"] },
+      linux_glibc: { arch_paths: ["linux_glibc.txt"] },
+      glibc: { arch_paths: ["glibc.txt"] },
+      linux_bionic: { arch_paths: ["linux_bionic.txt"] },
+      darwin: { arch_paths: ["darwin.txt"] },
+      windows: { arch_paths: ["windows.txt"] },
+    },
+    multilib: {
+        lib32: { arch_paths: ["lib32.txt"] },
+        lib64: { arch_paths: ["lib64.txt"] },
     },
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "arch_paths",
-    arch_paths = select({
-        "//build/bazel/platforms/arch:x86": ["abc"],
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "arch_paths", attrNameToString{
+					"arch_paths": `select({
+        "//build/bazel/platforms/arch:arm": [
+            "arm.txt",
+            "lib32.txt",
+        ],
+        "//build/bazel/platforms/arch:arm64": [
+            "arm64.txt",
+            "lib64.txt",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            "x86.txt",
+            "lib32.txt",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "x86_64.txt",
+            "lib64.txt",
+        ],
         "//conditions:default": [],
-    }),
-)`,
+    }) + select({
+        "//build/bazel/platforms/os:android": [
+            "linux.txt",
+            "bionic.txt",
+            "android.txt",
+        ],
+        "//build/bazel/platforms/os:darwin": [
+            "host.txt",
+            "darwin.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux": [
+            "host.txt",
+            "linux.txt",
+            "glibc.txt",
+            "linux_glibc.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            "host.txt",
+            "linux.txt",
+            "bionic.txt",
+            "linux_bionic.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux_musl": [
+            "host.txt",
+            "linux.txt",
+            "musl.txt",
+            "linux_musl.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:windows": [
+            "host.txt",
+            "windows.txt",
+        ],
+        "//conditions:default": [],
+    })`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "arch-variant deps",
+			blueprint: `custom {
   name: "has_dep",
   arch: {
     x86: {
@@ -314,54 +408,82 @@
     arch_paths: ["abc"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "dep",
-    arch_paths = ["abc"],
-)`,
-				`custom(
-    name = "has_dep",
-    arch_paths = select({
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "dep", attrNameToString{
+					"arch_paths": `["abc"]`,
+				}),
+				makeBazelTarget("custom", "has_dep", attrNameToString{
+					"arch_paths": `select({
         "//build/bazel/platforms/arch:x86": [":dep"],
         "//conditions:default": [],
-    }),
-)`,
+    })`,
+				}),
+			},
+		},
+		{
+			description: "embedded props",
+			blueprint: `custom {
+    name: "embedded_props",
+    embedded_prop: "abc",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "embedded_props", attrNameToString{
+					"embedded_attr": `"abc"`,
+				}),
+			},
+		},
+		{
+			description: "ptr to embedded props",
+			blueprint: `custom {
+    name: "ptr_to_embedded_props",
+    other_embedded_prop: "abc",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "ptr_to_embedded_props", attrNameToString{
+					"other_embedded_attr": `"abc"`,
+				}),
 			},
 		},
 	}
 
 	dir := "."
 	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
+		t.Run(testCase.description, func(t *testing.T) {
+			config := android.TestConfig(buildDir, nil, testCase.blueprint, nil)
+			ctx := android.NewTestContext(config)
 
-		registerCustomModuleForBp2buildConversion(ctx)
+			registerCustomModuleForBp2buildConversion(ctx)
 
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		if errored(t, "", errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if errored(t, "", errs) {
-			continue
-		}
+			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+			if errored(t, testCase, errs) {
+				return
+			}
+			_, errs = ctx.ResolveDependencies(config)
+			if errored(t, testCase, errs) {
+				return
+			}
 
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("Expected %d bazel target, got %d", expectedCount, actualCount)
-		} else {
-			for i, expectedBazelTarget := range testCase.expectedBazelTargets {
-				actualBazelTarget := bazelTargets[i]
-				if actualBazelTarget.content != expectedBazelTarget {
-					t.Errorf(
-						"Expected generated Bazel target to be '%s', got '%s'",
-						expectedBazelTarget,
-						actualBazelTarget.content,
-					)
+			if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+				t.Errorf("Expected %d bazel target (%s),\ngot %d (%s)", expectedCount, testCase.expectedBazelTargets, actualCount, bazelTargets)
+			} else {
+				for i, expectedBazelTarget := range testCase.expectedBazelTargets {
+					actualBazelTarget := bazelTargets[i]
+					if actualBazelTarget.content != expectedBazelTarget {
+						t.Errorf(
+							"Expected generated Bazel target to be '%s', got '%s'",
+							expectedBazelTarget,
+							actualBazelTarget.content,
+						)
+					}
 				}
 			}
-		}
+		})
 	}
 }
 
@@ -474,6 +596,7 @@
 		{
 			bp: `custom {
     name: "bar",
+    one_to_many_prop: true,
     bazel_module: { bp2build_available: true  },
 }`,
 			expectedBazelTarget: `my_library(
@@ -498,7 +621,6 @@
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutatorFromStarlark)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
@@ -507,7 +629,8 @@
 		android.FailIfErrored(t, errs)
 
 		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+		bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+		android.FailIfErrored(t, err)
 		if actualCount := len(bazelTargets); actualCount != testCase.expectedBazelTargetCount {
 			t.Fatalf("Expected %d bazel target, got %d", testCase.expectedBazelTargetCount, actualCount)
 		}
@@ -533,119 +656,85 @@
 }
 
 func TestModuleTypeBp2Build(t *testing.T) {
-	otherGenruleBp := map[string]string{
-		"other/Android.bp": `genrule {
-    name: "foo.tool",
-    out: ["foo_tool.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-}
-genrule {
-    name: "other.tool",
-    out: ["other_tool.out"],
-    srcs: ["other_tool.in"],
-    cmd: "cp $(in) $(out)",
-}`,
-	}
-
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup with does not specify srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with does not specify srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`,
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
 			},
 		},
 		{
-			description:                        "filegroup with no srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with no srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: [],
     bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`,
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
 			},
 		},
 		{
-			description:                        "filegroup with srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["a", "b"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
         "a",
         "b",
-    ],
-)`,
+    ]`,
+				}),
 			},
 		},
 		{
-			description:                        "filegroup with excludes srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with excludes srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["a", "b"],
     exclude_srcs: ["a"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = ["b"],
-)`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `["b"]`,
+				}),
 			},
 		},
 		{
-			description:                        "filegroup with glob",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "foo",
+			description:                "filegroup with glob",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+    name: "fg_foo",
     srcs: ["**/*.txt"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
         "other/a.txt",
         "other/b.txt",
         "other/subdir/a.txt",
-    ],
-)`,
+    ]`,
+				}),
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/a.txt":        "",
 				"other/b.txt":        "",
 				"other/subdir/a.txt": "",
@@ -653,26 +742,11 @@
 			},
 		},
 		{
-			description:                        "filegroup with glob in subdir",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "foo",
-    srcs: ["a.txt"],
-    bazel_module: { bp2build_available: true },
-}`,
-			dir: "other",
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
-        "a.txt",
-        "b.txt",
-        "subdir/a.txt",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			description:                "filegroup with glob in subdir",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			dir:                        "other",
+			filesystem: map[string]string{
 				"other/Android.bp": `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
@@ -683,13 +757,50 @@
 				"other/subdir/a.txt": "",
 				"other/file":         "",
 			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a.txt",
+        "b.txt",
+        "subdir/a.txt",
+    ]`,
+				}),
+			},
 		},
 		{
-			description:                        "depends_on_other_dir_module",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "depends_on_other_dir_module",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+    name: "fg_foo",
+    srcs: [
+        ":foo",
+        "c",
+    ],
+    bazel_module: { bp2build_available: true },
+}`,
+			filesystem: map[string]string{
+				"other/Android.bp": `filegroup {
+    name: "foo",
+    srcs: ["a", "b"],
+    bazel_module: { bp2build_available: true },
+}`,
+			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "//other:foo",
+        "c",
+    ]`,
+				}),
+			},
+		},
+		{
+			description:                "depends_on_other_unconverted_module_error",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			unconvertedDepsMode:        errorModulesUnconvertedDeps,
+			blueprint: `filegroup {
     name: "foobar",
     srcs: [
         ":foo",
@@ -697,477 +808,37 @@
     ],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "foobar",
-    srcs = [
-        "//other:foo",
-        "c",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			expectedErr: fmt.Errorf(`"foobar" depends on unconverted modules: foo`),
+			filesystem: map[string]string{
 				"other/Android.bp": `filegroup {
     name: "foo",
     srcs: ["a", "b"],
 }`,
 			},
 		},
-		{
-			description:                        "genrule with command line variable replacements",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo.tool",
-    out: ["foo_tool.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-
-genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tool"],
-    cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{
-				`genrule(
-    name = "foo",
-    cmd = "$(location :foo.tool) --genDir=$(GENDIR) arg $(SRCS) $(OUTS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [":foo.tool"],
-)`,
-				`genrule(
-    name = "foo.tool",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["foo_tool.out"],
-    srcs = ["foo_tool.in"],
-)`,
-			},
-		},
-		{
-			description:                        "genrule using $(locations :label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo.tools",
-    out: ["foo_tool.out", "foo_tool2.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-
-genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tools"],
-    cmd: "$(locations :foo.tools) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations :foo.tools) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [":foo.tools"],
-)`,
-				`genrule(
-    name = "foo.tools",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = [
-        "foo_tool.out",
-        "foo_tool2.out",
-    ],
-    srcs = ["foo_tool.in"],
-)`,
-			},
-		},
-		{
-			description:                        "genrule using $(locations //absolute:label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tool_files: [":foo.tool"],
-    cmd: "$(locations :foo.tool) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = ["//other:foo.tool"],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule srcs using $(locations //absolute:label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: [":other.tool"],
-    tool_files: [":foo.tool"],
-    cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)",
-    outs = ["foo.out"],
-    srcs = ["//other:other.tool"],
-    tools = ["//other:foo.tool"],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule using $(location) label should substitute first tool label automatically",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tool_files: [":foo.tool", ":other.tool"],
-    cmd: "$(location) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(location //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [
-        "//other:foo.tool",
-        "//other:other.tool",
-    ],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule using $(locations) label should substitute first tool label automatically",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tool", ":other.tool"],
-    cmd: "$(locations) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [
-        "//other:foo.tool",
-        "//other:other.tool",
-    ],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule without tools or tool_files can convert successfully",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-)`,
-			},
-		},
 	}
 
-	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
+		t.Run(testCase.description, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, testCase)
+		})
 	}
 }
 
 type bp2buildMutator = func(android.TopDownMutatorContext)
 
-func TestBp2BuildInlinesDefaults(t *testing.T) {
-	testCases := []struct {
-		moduleTypesUnderTest      map[string]android.ModuleFactory
-		bp2buildMutatorsUnderTest map[string]bp2buildMutator
-		bp                        string
-		expectedBazelTarget       string
-		description               string
-	}{
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults",
-    cmd: "do-something $(in) $(out)",
-}
-genrule {
-    name: "gen",
-    out: ["out"],
-    srcs: ["in1"],
-    defaults: ["gen_defaults"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "do-something $(SRCS) $(OUTS)",
-    outs = ["out"],
-    srcs = ["in1"],
-)`,
-			description: "genrule applies properties from a genrule_defaults dependency if not specified",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults",
-    out: ["out-from-defaults"],
-    srcs: ["in-from-defaults"],
-    cmd: "cmd-from-defaults",
-}
-genrule {
-    name: "gen",
-    out: ["out"],
-    srcs: ["in1"],
-    defaults: ["gen_defaults"],
-    cmd: "do-something $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "do-something $(SRCS) $(OUTS)",
-    outs = [
-        "out-from-defaults",
-        "out",
-    ],
-    srcs = [
-        "in-from-defaults",
-        "in1",
-    ],
-)`,
-			description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults1",
-    cmd: "cp $(in) $(out)",
-}
-
-genrule_defaults {
-    name: "gen_defaults2",
-    srcs: ["in1"],
-}
-
-genrule {
-    name: "gen",
-    out: ["out"],
-    defaults: ["gen_defaults1", "gen_defaults2"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["out"],
-    srcs = ["in1"],
-)`,
-			description: "genrule applies properties from list of genrule_defaults",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults1",
-    defaults: ["gen_defaults2"],
-    cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
-}
-
-genrule_defaults {
-    name: "gen_defaults2",
-    defaults: ["gen_defaults3"],
-    cmd: "cmd2 $(in) $(out)",
-    out: ["out-from-2"],
-    srcs: ["in1"],
-}
-
-genrule_defaults {
-    name: "gen_defaults3",
-    out: ["out-from-3"],
-    srcs: ["srcs-from-3"],
-}
-
-genrule {
-    name: "gen",
-    out: ["out"],
-    defaults: ["gen_defaults1"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "cmd1 $(SRCS) $(OUTS)",
-    outs = [
-        "out-from-3",
-        "out-from-2",
-        "out",
-    ],
-    srcs = [
-        "srcs-from-3",
-        "in1",
-    ],
-)`,
-			description: "genrule applies properties from genrule_defaults transitively",
-		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
-		for m, factory := range testCase.moduleTypesUnderTest {
-			ctx.RegisterModuleType(m, factory)
-		}
-		for mutator, f := range testCase.bp2buildMutatorsUnderTest {
-			ctx.RegisterBp2BuildMutator(mutator, f)
-		}
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.ResolveDependencies(config)
-		android.FailIfErrored(t, errs)
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount := len(bazelTargets); actualCount != 1 {
-			t.Fatalf("%s: Expected 1 bazel target, got %d", testCase.description, actualCount)
-		}
-
-		actualBazelTarget := bazelTargets[0]
-		if actualBazelTarget.content != testCase.expectedBazelTarget {
-			t.Errorf(
-				"%s: Expected generated Bazel target to be '%s', got '%s'",
-				testCase.description,
-				testCase.expectedBazelTarget,
-				actualBazelTarget.content,
-			)
-		}
-	}
-}
-
 func TestAllowlistingBp2buildTargetsExplicitly(t *testing.T) {
 	testCases := []struct {
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator bp2buildMutator
-		bp                                 string
-		expectedCount                      int
-		description                        string
+		moduleTypeUnderTest        string
+		moduleTypeUnderTestFactory android.ModuleFactory
+		bp                         string
+		expectedCount              int
+		description                string
 	}{
 		{
-			description:                        "explicitly unavailable",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "explicitly unavailable",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1176,10 +847,9 @@
 			expectedCount: 0,
 		},
 		{
-			description:                        "implicitly unavailable",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "implicitly unavailable",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1187,10 +857,9 @@
 			expectedCount: 0,
 		},
 		{
-			description:                        "explicitly available",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "explicitly available",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1199,12 +868,12 @@
 			expectedCount: 1,
 		},
 		{
-			description:                        "generates more than 1 target if needed",
-			moduleTypeUnderTest:                "custom",
-			moduleTypeUnderTestFactory:         customModuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: customBp2BuildMutatorFromStarlark,
+			description:                "generates more than 1 target if needed",
+			moduleTypeUnderTest:        "custom",
+			moduleTypeUnderTestFactory: customModuleFactory,
 			bp: `custom {
     name: "foo",
+    one_to_many_prop: true,
     bazel_module: { bp2build_available: true },
 }`,
 			expectedCount: 3,
@@ -1217,7 +886,6 @@
 			config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 			ctx := android.NewTestContext(config)
 			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-			ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
 			ctx.RegisterForBazelConversion()
 
 			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
@@ -1226,7 +894,8 @@
 			android.FailIfErrored(t, errs)
 
 			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 			if actualCount := len(bazelTargets); actualCount != testCase.expectedCount {
 				t.Fatalf("%s: Expected %d bazel target, got %d", testCase.description, testCase.expectedCount, actualCount)
 			}
@@ -1236,20 +905,18 @@
 
 func TestAllowlistingBp2buildTargetsWithConfig(t *testing.T) {
 	testCases := []struct {
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator bp2buildMutator
-		expectedCount                      map[string]int
-		description                        string
-		bp2buildConfig                     android.Bp2BuildConfig
-		checkDir                           string
-		fs                                 map[string]string
+		moduleTypeUnderTest        string
+		moduleTypeUnderTestFactory android.ModuleFactory
+		expectedCount              map[string]int
+		description                string
+		bp2buildConfig             android.Bp2BuildConfig
+		checkDir                   string
+		fs                         map[string]string
 	}{
 		{
-			description:                        "test bp2build config package and subpackages config",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "test bp2build config package and subpackages config",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			expectedCount: map[string]int{
 				"migrated":                           1,
 				"migrated/but_not_really":            0,
@@ -1271,10 +938,9 @@
 			},
 		},
 		{
-			description:                        "test bp2build config opt-in and opt-out",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "test bp2build config opt-in and opt-out",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			expectedCount: map[string]int{
 				"package-opt-in":             2,
 				"package-opt-in/subpackage":  0,
@@ -1325,7 +991,6 @@
 		config := android.TestConfig(buildDir, nil, "", fs)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
 		ctx.RegisterBp2BuildConfig(testCase.bp2buildConfig)
 		ctx.RegisterForBazelConversion()
 
@@ -1338,7 +1003,8 @@
 
 		// For each directory, test that the expected number of generated targets is correct.
 		for dir, expectedCount := range testCase.expectedCount {
-			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 			if actualCount := len(bazelTargets); actualCount != expectedCount {
 				t.Fatalf(
 					"%s: Expected %d bazel target for %s package, got %d",
@@ -1353,109 +1019,93 @@
 }
 
 func TestCombineBuildFilesBp2buildTargets(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup bazel_module.label",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup bazel_module.label",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     bazel_module: { label: "//other:fg_foo" },
 }`,
 			expectedBazelTargets: []string{
 				`// BUILD file`,
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
 		},
 		{
-			description:                        "multiple bazel_module.label same BUILD",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-		    name: "fg_foo",
-		    bazel_module: { label: "//other:fg_foo" },
-		}
+			description:                "multiple bazel_module.label same BUILD",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+        name: "fg_foo",
+        bazel_module: { label: "//other:fg_foo" },
+    }
 
-		filegroup {
-		    name: "foo",
-		    bazel_module: { label: "//other:foo" },
-		}`,
+    filegroup {
+        name: "foo",
+        bazel_module: { label: "//other:foo" },
+    }`,
 			expectedBazelTargets: []string{
 				`// BUILD file`,
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
 		},
 		{
-			description:                        "filegroup bazel_module.label and bp2build in subdir",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			dir:                                "other",
-			bp:                                 ``,
-			fs: map[string]string{
+			description:                "filegroup bazel_module.label and bp2build in subdir",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			dir:                        "other",
+			blueprint:                  ``,
+			filesystem: map[string]string{
 				"other/Android.bp": `filegroup {
-				name: "fg_foo",
-				bazel_module: {
-					bp2build_available: true,
-				},
-			}
-			filegroup {
-				name: "fg_bar",
-				bazel_module: {
-					label: "//other:fg_bar"
-				},
-			}`,
+        name: "fg_foo",
+        bazel_module: {
+          bp2build_available: true,
+        },
+      }
+      filegroup {
+        name: "fg_bar",
+        bazel_module: {
+          label: "//other:fg_bar"
+        },
+      }`,
 				"other/BUILD.bazel": `// definition for fg_bar`,
 			},
 			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`, `// definition for fg_bar`,
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
+				`// definition for fg_bar`,
 			},
 		},
 		{
-			description:                        "filegroup bazel_module.label and filegroup bp2build",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-		    name: "fg_foo",
-		    bazel_module: {
-		      label: "//other:fg_foo",
-		    },
-		}
+			description:                "filegroup bazel_module.label and filegroup bp2build",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 
-		filegroup {
-		    name: "fg_bar",
-		    bazel_module: {
-		      bp2build_available: true,
-		    },
-		}`,
-			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_bar",
-)`,
-				`// BUILD file`,
-			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
+			blueprint: `filegroup {
+        name: "fg_foo",
+        bazel_module: {
+          label: "//other:fg_foo",
+        },
+    }
+
+    filegroup {
+        name: "fg_bar",
+        bazel_module: {
+          bp2build_available: true,
+        },
+    }`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_bar", map[string]string{}),
+				`// BUILD file`,
+			},
 		},
 	}
 
@@ -1466,24 +1116,23 @@
 			toParse := []string{
 				"Android.bp",
 			}
-			for f, content := range testCase.fs {
+			for f, content := range testCase.filesystem {
 				if strings.HasSuffix(f, "Android.bp") {
 					toParse = append(toParse, f)
 				}
 				fs[f] = []byte(content)
 			}
-			config := android.TestConfig(buildDir, nil, testCase.bp, fs)
+			config := android.TestConfig(buildDir, nil, testCase.blueprint, fs)
 			ctx := android.NewTestContext(config)
 			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-			ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
 			ctx.RegisterForBazelConversion()
 
 			_, errs := ctx.ParseFileList(dir, toParse)
-			if errored(t, testCase.description, errs) {
+			if errored(t, testCase, errs) {
 				return
 			}
 			_, errs = ctx.ResolveDependencies(config)
-			if errored(t, testCase.description, errs) {
+			if errored(t, testCase, errs) {
 				return
 			}
 
@@ -1491,7 +1140,9 @@
 			if testCase.dir != "" {
 				checkDir = testCase.dir
 			}
-			bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
+			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, checkDir)
+			android.FailIfErrored(t, err)
 			bazelTargets.sort()
 			actualCount := len(bazelTargets)
 			expectedCount := len(testCase.expectedBazelTargets)
@@ -1517,38 +1168,18 @@
 }
 
 func TestGlobExcludeSrcs(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup top level exclude_srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup top level exclude_srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
     exclude_srcs: ["c.txt"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
-        "a.txt",
-        "b.txt",
-        "//dir:e.txt",
-        "//dir:f.txt",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"a.txt":          "",
 				"b.txt":          "",
 				"c.txt":          "",
@@ -1556,15 +1187,24 @@
 				"dir/e.txt":      "",
 				"dir/f.txt":      "",
 			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a.txt",
+        "b.txt",
+        "//dir:e.txt",
+        "//dir:f.txt",
+    ]`,
+				}),
+			},
 		},
 		{
-			description:                        "filegroup in subdir exclude_srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp:                                 "",
-			dir:                                "dir",
-			fs: map[string]string{
+			description:                "filegroup in subdir exclude_srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint:                  "",
+			dir:                        "dir",
+			filesystem: map[string]string{
 				"dir/Android.bp": `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
@@ -1578,63 +1218,118 @@
 				"dir/subdir/e.txt":      "",
 				"dir/subdir/f.txt":      "",
 			},
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
         "a.txt",
         "//dir/subdir:e.txt",
         "//dir/subdir:f.txt",
-    ],
-)`,
+    ]`,
+				}),
 			},
 		},
 	}
 
-	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+		t.Run(testCase.description, func(t *testing.T) {
+			runBp2BuildTestCaseSimple(t, testCase)
+		})
+	}
+}
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
+func TestCommonBp2BuildModuleAttrs(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description:                "Required into data test",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("filegroup", "reqd") + `
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"data": `[":reqd"]`,
+				}),
+			},
+		},
+		{
+			description:                "Required via arch into data test",
+			moduleTypeUnderTest:        "python_library",
+			moduleTypeUnderTestFactory: python.PythonLibraryFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("python_library", "reqdx86") +
+				simpleModuleDoNotConvertBp2build("python_library", "reqdarm") + `
+python_library {
+    name: "fg_foo",
+    arch: {
+       arm: {
+         required: ["reqdarm"],
+       },
+       x86: {
+         required: ["reqdx86"],
+       },
+    },
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "fg_foo", map[string]string{
+					"data": `select({
+        "//build/bazel/platforms/arch:arm": [":reqdarm"],
+        "//build/bazel/platforms/arch:x86": [":reqdx86"],
+        "//conditions:default": [],
+    })`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
+		},
+		{
+			description:                "Required appended to data test",
+			moduleTypeUnderTest:        "python_library",
+			moduleTypeUnderTestFactory: python.PythonLibraryFactory,
+			filesystem: map[string]string{
+				"data.bin": "",
+				"src.py":   "",
+			},
+			blueprint: simpleModuleDoNotConvertBp2build("python_library", "reqd") + `
+python_library {
+    name: "fg_foo",
+    data: ["data.bin"],
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "fg_foo", map[string]string{
+					"data": `[
+        "data.bin",
+        ":reqd",
+    ]`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
+		},
+		{
+			description:                "All props-to-attrs at once together test",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("filegroup", "reqd") + `
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"data": `[":reqd"]`,
+				}),
+			},
+		},
+	}
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			runBp2BuildTestCaseSimple(t, tc)
+		})
 	}
 }
diff --git a/bp2build/bzl_conversion.go b/bp2build/bzl_conversion.go
index f2f6b01..992cc1c 100644
--- a/bp2build/bzl_conversion.go
+++ b/bp2build/bzl_conversion.go
@@ -160,8 +160,15 @@
 		if shouldSkipStructField(field) {
 			continue
 		}
-
-		properties = append(properties, extractPropertyDescriptions(field.Name, field.Type)...)
+		subProps := extractPropertyDescriptions(field.Name, field.Type)
+		// if the struct is embedded (anonymous), flatten the properties into the containing struct
+		if field.Anonymous {
+			for _, prop := range subProps {
+				properties = append(properties, prop.properties...)
+			}
+		} else {
+			properties = append(properties, subProps...)
+		}
 	}
 	return properties
 }
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 9e0c0a1..f3345a6 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -92,6 +92,7 @@
         # bazel_module end
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -99,6 +100,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
@@ -118,6 +121,7 @@
         "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -125,6 +129,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
@@ -144,6 +150,7 @@
         "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -151,6 +158,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
new file mode 100644
index 0000000..a156480
--- /dev/null
+++ b/bp2build/cc_binary_conversion_test.go
@@ -0,0 +1,502 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/genrule"
+	"fmt"
+	"strings"
+	"testing"
+)
+
+const (
+	ccBinaryTypePlaceHolder = "{rule_name}"
+)
+
+type testBazelTarget struct {
+	typ   string
+	name  string
+	attrs attrNameToString
+}
+
+func generateBazelTargetsForTest(targets []testBazelTarget) []string {
+	ret := make([]string, 0, len(targets))
+	for _, t := range targets {
+		ret = append(ret, makeBazelTarget(t.typ, t.name, t.attrs))
+	}
+	return ret
+}
+
+type ccBinaryBp2buildTestCase struct {
+	description string
+	blueprint   string
+	targets     []testBazelTarget
+}
+
+func registerCcBinaryModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+}
+
+var binaryReplacer = strings.NewReplacer(ccBinaryTypePlaceHolder, "cc_binary")
+var hostBinaryReplacer = strings.NewReplacer(ccBinaryTypePlaceHolder, "cc_binary_host")
+
+func runCcBinaryTests(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	runCcBinaryTestCase(t, tc)
+	runCcHostBinaryTestCase(t, tc)
+}
+
+func runCcBinaryTestCase(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	moduleTypeUnderTest := "cc_binary"
+	testCase := bp2buildTestCase{
+		expectedBazelTargets:       generateBazelTargetsForTest(tc.targets),
+		moduleTypeUnderTest:        moduleTypeUnderTest,
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		description:                fmt.Sprintf("%s %s", moduleTypeUnderTest, tc.description),
+		blueprint:                  binaryReplacer.Replace(tc.blueprint),
+	}
+	t.Run(testCase.description, func(t *testing.T) {
+		t.Helper()
+		runBp2BuildTestCase(t, registerCcBinaryModuleTypes, testCase)
+	})
+}
+
+func runCcHostBinaryTestCase(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	testCase := tc
+	for i, tar := range testCase.targets {
+		if tar.typ != "cc_binary" {
+			continue
+		}
+		tar.attrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		testCase.targets[i] = tar
+	}
+	moduleTypeUnderTest := "cc_binary_host"
+	t.Run(testCase.description, func(t *testing.T) {
+		runBp2BuildTestCase(t, registerCcBinaryModuleTypes, bp2buildTestCase{
+			expectedBazelTargets:       generateBazelTargetsForTest(testCase.targets),
+			moduleTypeUnderTest:        moduleTypeUnderTest,
+			moduleTypeUnderTestFactory: cc.BinaryHostFactory,
+			description:                fmt.Sprintf("%s %s", moduleTypeUnderTest, tc.description),
+			blueprint:                  hostBinaryReplacer.Replace(testCase.blueprint),
+		})
+	})
+}
+
+func TestBasicCcBinary(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: "basic -- properties -> attrs with little/no transformation",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    srcs: ["a.cc"],
+    local_include_dirs: ["dir"],
+    include_dirs: ["absolute_dir"],
+    cflags: ["-Dcopt"],
+    cppflags: ["-Dcppflag"],
+    conlyflags: ["-Dconlyflag"],
+    asflags: ["-Dasflag"],
+    ldflags: ["ld-flag"],
+    rtti: true,
+    strip: {
+        all: true,
+        keep_symbols: true,
+        keep_symbols_and_debug_frame: true,
+        keep_symbols_list: ["symbol"],
+        none: true,
+    },
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"absolute_includes": `["absolute_dir"]`,
+				"asflags":           `["-Dasflag"]`,
+				"conlyflags":        `["-Dconlyflag"]`,
+				"copts":             `["-Dcopt"]`,
+				"cppflags":          `["-Dcppflag"]`,
+				"linkopts":          `["ld-flag"]`,
+				"local_includes": `[
+        "dir",
+        ".",
+    ]`,
+				"rtti": `True`,
+				"srcs": `["a.cc"]`,
+				"strip": `{
+        "all": True,
+        "keep_symbols": True,
+        "keep_symbols_and_debug_frame": True,
+        "keep_symbols_list": ["symbol"],
+        "none": True,
+    }`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryWithSharedLdflagDisableFeature(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: `ldflag "-shared" disables static_flag feature`,
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    ldflags: ["-shared"],
+    include_build_directory: false,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"features": `["-static_flag"]`,
+				"linkopts": `["-shared"]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryWithLinkStatic(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: "link static",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    static_executable: true,
+    include_build_directory: false,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"linkshared": `False`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryVersionScript(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: `version script`,
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    include_build_directory: false,
+    version_script: "vs",
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"additional_linker_inputs": `["vs"]`,
+				"linkopts":                 `["-Wl,--version-script,$(location vs)"]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinarySplitSrcsByLang(t *testing.T) {
+	runCcHostBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "split srcs by lang",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    srcs: [
+        "asonly.S",
+        "conly.c",
+        "cpponly.cpp",
+        ":fg_foo",
+    ],
+    include_build_directory: false,
+}
+` + simpleModuleDoNotConvertBp2build("filegroup", "fg_foo"),
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"srcs": `[
+        "cpponly.cpp",
+        ":fg_foo_cpp_srcs",
+    ]`,
+				"srcs_as": `[
+        "asonly.S",
+        ":fg_foo_as_srcs",
+    ]`,
+				"srcs_c": `[
+        "conly.c",
+        ":fg_foo_c_srcs",
+    ]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryDoNotDistinguishBetweenDepsAndImplementationDeps(t *testing.T) {
+	runCcBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "no implementation deps",
+		blueprint: `
+genrule {
+    name: "generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+genrule {
+    name: "export_generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+{rule_name} {
+    name: "foo",
+    srcs: ["foo.cpp"],
+    shared_libs: ["implementation_shared_dep", "shared_dep"],
+    export_shared_lib_headers: ["shared_dep"],
+    static_libs: ["implementation_static_dep", "static_dep"],
+    export_static_lib_headers: ["static_dep", "whole_static_dep"],
+    whole_static_libs: ["not_explicitly_exported_whole_static_dep", "whole_static_dep"],
+    include_build_directory: false,
+    generated_headers: ["generated_hdr", "export_generated_hdr"],
+    export_generated_headers: ["export_generated_hdr"],
+}
+` +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep"),
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"deps": `[
+        ":implementation_static_dep",
+        ":static_dep",
+    ]`,
+				"dynamic_deps": `[
+        ":implementation_shared_dep",
+        ":shared_dep",
+    ]`,
+				"srcs": `[
+        "foo.cpp",
+        ":generated_hdr",
+        ":export_generated_hdr",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep",
+        ":whole_static_dep",
+    ]`,
+				"local_includes": `["."]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryNocrtTests(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "nocrt: true",
+			soongProperty: `nocrt: true,`,
+			bazelAttr:     attrNameToString{"link_crt": `False`},
+		},
+		{
+			description:   "nocrt: false",
+			soongProperty: `nocrt: false,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description: "nocrt: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinaryNo_libcrtTests(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "no_libcrt: true",
+			soongProperty: `no_libcrt: true,`,
+			bazelAttr:     attrNameToString{"use_libcrt": `False`},
+		},
+		{
+			description:   "no_libcrt: false",
+			soongProperty: `no_libcrt: false,`,
+			bazelAttr:     attrNameToString{"use_libcrt": `True`},
+		},
+		{
+			description: "no_libcrt: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinaryPropertiesToFeatures(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "pack_relocation: true",
+			soongProperty: `pack_relocations: true,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description:   "pack_relocations: false",
+			soongProperty: `pack_relocations: false,`,
+			bazelAttr:     attrNameToString{"features": `["disable_pack_relocations"]`},
+		},
+		{
+			description: "pack_relocations: not set",
+			bazelAttr:   attrNameToString{},
+		},
+		{
+			description:   "pack_relocation: true",
+			soongProperty: `allow_undefined_symbols: true,`,
+			bazelAttr:     attrNameToString{"features": `["-no_undefined_symbols"]`},
+		},
+		{
+			description:   "allow_undefined_symbols: false",
+			soongProperty: `allow_undefined_symbols: false,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description: "allow_undefined_symbols: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinarySharedProto(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		blueprint: soongCcProtoLibraries + `{rule_name} {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+	},
+	include_build_directory: false,
+}`,
+		targets: []testBazelTarget{
+			{"proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}}, {"cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}}, {"cc_binary", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}},
+		},
+	})
+}
+
+func TestCcBinaryStaticProto(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		blueprint: soongCcProtoLibraries + `{rule_name} {
+	name: "foo",
+	srcs: ["foo.proto"],
+	static_executable: true,
+	proto: {
+		canonical_path_from_root: false,
+	},
+	include_build_directory: false,
+}`,
+		targets: []testBazelTarget{
+			{"proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}}, {"cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}}, {"cc_binary", "foo", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"linkshared":         `False`,
+			}},
+		},
+	})
+}
diff --git a/bp2build/cc_genrule_conversion_test.go b/bp2build/cc_genrule_conversion_test.go
new file mode 100644
index 0000000..440b462
--- /dev/null
+++ b/bp2build/cc_genrule_conversion_test.go
@@ -0,0 +1,235 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+var otherCcGenruleBp = map[string]string{
+	"other/Android.bp": `cc_genrule {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+}
+cc_genrule {
+    name: "other.tool",
+    out: ["other_tool.out"],
+    srcs: ["other_tool.in"],
+    cmd: "cp $(in) $(out)",
+}`,
+}
+
+func runCcGenruleTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "cc_genrule"
+	(&tc).moduleTypeUnderTestFactory = cc.GenRuleFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestCliVariableReplacement(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule with command line variable replacements",
+		blueprint: `cc_genrule {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+
+cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tool"],
+    cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":   `"$(location :foo.tool) --genDir=$(RULEDIR) arg $(SRCS) $(OUTS)"`,
+				"outs":  `["foo.out"]`,
+				"srcs":  `["foo.in"]`,
+				"tools": `[":foo.tool"]`,
+			}),
+			makeBazelTarget("genrule", "foo.tool", attrNameToString{
+				"cmd":  `"cp $(SRCS) $(OUTS)"`,
+				"outs": `["foo_tool.out"]`,
+				"srcs": `["foo_tool.in"]`,
+			}),
+		},
+	})
+}
+
+func TestUsingLocationsLabel(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule using $(locations :label)",
+		blueprint: `cc_genrule {
+    name: "foo.tools",
+    out: ["foo_tool.out", "foo_tool2.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+
+cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tools"],
+    cmd: "$(locations :foo.tools) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":   `"$(locations :foo.tools) -s $(OUTS) $(SRCS)"`,
+				"outs":  `["foo.out"]`,
+				"srcs":  `["foo.in"]`,
+				"tools": `[":foo.tools"]`,
+			}),
+			makeBazelTarget("genrule", "foo.tools", attrNameToString{
+				"cmd": `"cp $(SRCS) $(OUTS)"`,
+				"outs": `[
+        "foo_tool.out",
+        "foo_tool2.out",
+    ]`,
+				"srcs": `["foo_tool.in"]`,
+			}),
+		},
+	})
+}
+
+func TestUsingLocationsAbsoluteLabel(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule using $(locations //absolute:label)",
+		blueprint: `cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+		filesystem: otherCcGenruleBp,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+				"outs":  `["foo.out"]`,
+				"srcs":  `["foo.in"]`,
+				"tools": `["//other:foo.tool"]`,
+			}),
+		},
+	})
+}
+
+func TestSrcsUsingAbsoluteLabel(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule srcs using $(locations //absolute:label)",
+		blueprint: `cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: [":other.tool"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
+    bazel_module: { bp2build_available: true },
+}`,
+		filesystem: otherCcGenruleBp,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)"`,
+				"outs":  `["foo.out"]`,
+				"srcs":  `["//other:other.tool"]`,
+				"tools": `["//other:foo.tool"]`,
+			}),
+		},
+	})
+}
+
+func TestLocationsLabelUsesFirstToolFile(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule using $(location) label should substitute first tool label automatically",
+		blueprint: `cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool", ":other.tool"],
+    cmd: "$(location) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+		filesystem: otherCcGenruleBp,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":  `"$(location //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+				"outs": `["foo.out"]`,
+				"srcs": `["foo.in"]`,
+				"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestLocationsLabelUsesFirstTool(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule using $(locations) label should substitute first tool label automatically",
+		blueprint: `cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tool", ":other.tool"],
+    cmd: "$(locations) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+		filesystem: otherCcGenruleBp,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":  `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+				"outs": `["foo.out"]`,
+				"srcs": `["foo.in"]`,
+				"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestWithoutToolsOrToolFiles(t *testing.T) {
+	runCcGenruleTestCase(t, bp2buildTestCase{
+		description: "cc_genrule without tools or tool_files can convert successfully",
+		blueprint: `cc_genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("genrule", "foo", attrNameToString{
+				"cmd":  `"cp $(SRCS) $(OUTS)"`,
+				"outs": `["foo.out"]`,
+				"srcs": `["foo.in"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index c840016..eaceea9 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -15,6 +15,7 @@
 package bp2build
 
 import (
+	"fmt"
 	"testing"
 
 	"android/soong/android"
@@ -25,19 +26,32 @@
 	// See cc/testing.go for more context
 	soongCcLibraryPreamble = `
 cc_defaults {
-  name: "linux_bionic_supported",
+    name: "linux_bionic_supported",
 }
 
 toolchain_library {
-  name: "libclang_rt.builtins-x86_64-android",
-  defaults: ["linux_bionic_supported"],
-  vendor_available: true,
-  vendor_ramdisk_available: true,
-  product_available: true,
-  recovery_available: true,
-  native_bridge_supported: true,
-  src: "",
+    name: "libclang_rt.builtins-x86_64-android",
+    defaults: ["linux_bionic_supported"],
+    vendor_available: true,
+    vendor_ramdisk_available: true,
+    product_available: true,
+    recovery_available: true,
+    native_bridge_supported: true,
+    src: "",
 }`
+
+	soongCcProtoLibraries = `
+cc_library {
+	name: "libprotobuf-cpp-lite",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "libprotobuf-cpp-full",
+	bazel_module: { bp2build_available: false },
+}`
+
+	soongCcProtoPreamble = soongCcLibraryPreamble + soongCcProtoLibraries
 )
 
 func runCcLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
@@ -56,10 +70,9 @@
 
 func TestCcLibrarySimple(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library - simple example",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library - simple example",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"android.cpp": "",
 			"bionic.cpp":  "",
@@ -80,8 +93,8 @@
 			"x86_64.cpp":       "",
 			"foo-dir/a.h":      "",
 		},
-		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
+		blueprint: soongCcLibraryPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library_headers", "some-headers") + `
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
@@ -113,44 +126,41 @@
           srcs: ["bionic.cpp"]
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-Wall",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [":some-headers"],
-    includes = ["foo-dir"],
-    linkopts = ["-Wl,--exclude-libs=bar.a"] + select({
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"copts":               `["-Wall"]`,
+			"export_includes":     `["foo-dir"]`,
+			"implementation_deps": `[":some-headers"]`,
+			"linkopts": `["-Wl,--exclude-libs=bar.a"] + select({
         "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=baz.a"],
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=qux.a"],
         "//conditions:default": [],
-    }),
-    srcs = ["impl.cpp"] + select({
+    })`,
+			"srcs": `["impl.cpp"] + select({
         "//build/bazel/platforms/arch:x86": ["x86.cpp"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms/os:android": ["android.cpp"],
+        "//build/bazel/platforms/os:android": [
+            "bionic.cpp",
+            "android.cpp",
+        ],
         "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
         "//build/bazel/platforms/os:linux": ["linux.cpp"],
+        "//build/bazel/platforms/os:linux_bionic": ["bionic.cpp"],
         "//conditions:default": [],
-    }) + select({
-        "//build/bazel/platforms/os:bionic": ["bionic.cpp"],
-        "//conditions:default": [],
-    }),
-)`}})
+    })`,
+		}),
+	})
 }
 
 func TestCcLibraryTrimmedLdAndroid(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library - trimmed example of //bionic/linker:ld-android",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library - trimmed example of //bionic/linker:ld-android",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"ld-android.cpp":           "",
 			"linked_list.h":            "",
@@ -158,8 +168,8 @@
 			"linker_block_allocator.h": "",
 			"linker_cfi.h":             "",
 		},
-		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "libc_headers" }
+		blueprint: soongCcLibraryPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library_headers", "libc_headers") + `
 cc_library {
     name: "fake-ld-android",
     srcs: ["ld_android.cpp"],
@@ -186,20 +196,19 @@
             ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "fake-ld-android",
-    copts = [
+		expectedBazelTargets: makeCcLibraryTargets("fake-ld-android", attrNameToString{
+			"srcs": `["ld_android.cpp"]`,
+			"copts": `[
         "-Wall",
         "-Wextra",
         "-Wunused",
         "-Werror",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [":libc_headers"],
-    linkopts = [
+    ]`,
+			"implementation_deps": `[":libc_headers"]`,
+			"linkopts": `[
         "-Wl,--exclude-libs=libgcc.a",
         "-Wl,--exclude-libs=libgcc_stripped.a",
         "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
@@ -210,19 +219,17 @@
         "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=libgcc_eh.a"],
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=libgcc_eh.a"],
         "//conditions:default": [],
-    }),
-    srcs = ["ld_android.cpp"],
-)`},
+    })`,
+		}),
 	})
 }
 
 func TestCcLibraryExcludeSrcs(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "external",
+		description:                "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "external",
 		filesystem: map[string]string{
 			"external/math/cosf.c":      "",
 			"external/math/erf.c":       "",
@@ -257,32 +264,28 @@
 `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "fake-libarm-optimized-routines-math",
-    copts = [
-        "-Iexternal",
-        "-I$(BINDIR)/external",
-    ] + select({
+		expectedBazelTargets: makeCcLibraryTargets("fake-libarm-optimized-routines-math", attrNameToString{
+			"copts": `select({
         "//build/bazel/platforms/arch:arm64": ["-DHAVE_FAST_FMA=1"],
         "//conditions:default": [],
-    }),
-    srcs_c = ["math/cosf.c"],
-)`},
+    })`,
+			"local_includes": `["."]`,
+			"srcs_c":         `["math/cosf.c"]`,
+		}),
 	})
 }
 
 func TestCcLibrarySharedStaticProps(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library shared/static props",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		description:                "cc_library shared/static props",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
-			"foo/bar/both.cpp":       "",
-			"foo/bar/sharedonly.cpp": "",
-			"foo/bar/staticonly.cpp": "",
-			"foo/bar/Android.bp": `
+			"both.cpp":       "",
+			"sharedonly.cpp": "",
+			"staticonly.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     srcs: ["both.cpp"],
@@ -304,64 +307,234 @@
         static_libs: ["static_dep_for_shared"],
         whole_static_libs: ["whole_static_lib_for_shared"],
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 
-cc_library_static { name: "static_dep_for_shared" }
+cc_library_static {
+    name: "static_dep_for_shared",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library_static { name: "static_dep_for_static" }
+cc_library_static {
+    name: "static_dep_for_static",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library_static { name: "static_dep_for_both" }
+cc_library_static {
+    name: "static_dep_for_both",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library_static { name: "whole_static_lib_for_shared" }
+cc_library_static {
+    name: "whole_static_lib_for_shared",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library_static { name: "whole_static_lib_for_static" }
+cc_library_static {
+    name: "whole_static_lib_for_static",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library_static { name: "whole_static_lib_for_both" }
+cc_library_static {
+    name: "whole_static_lib_for_both",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library { name: "shared_dep_for_shared" }
+cc_library {
+    name: "shared_dep_for_shared",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library { name: "shared_dep_for_static" }
+cc_library {
+    name: "shared_dep_for_static",
+    bazel_module: { bp2build_available: false },
+}
 
-cc_library { name: "shared_dep_for_both" }
+cc_library {
+    name: "shared_dep_for_both",
+    bazel_module: { bp2build_available: false },
+}
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
         "bothflag",
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    dynamic_deps = [":shared_dep_for_both"],
-    implementation_deps = [":static_dep_for_both"],
-    shared = {
-        "copts": ["sharedflag"],
-        "dynamic_deps": [":shared_dep_for_shared"],
-        "srcs": ["sharedonly.cpp"],
-        "static_deps": [":static_dep_for_shared"],
-        "whole_archive_deps": [":whole_static_lib_for_shared"],
-    },
-    srcs = ["both.cpp"],
-    static = {
-        "copts": ["staticflag"],
-        "dynamic_deps": [":shared_dep_for_static"],
-        "srcs": ["staticonly.cpp"],
-        "static_deps": [":static_dep_for_static"],
-        "whole_archive_deps": [":whole_static_lib_for_static"],
-    },
-    whole_archive_deps = [":whole_static_lib_for_both"],
-)`},
+        "staticflag",
+    ]`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_static",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both",
+        ":whole_static_lib_for_static",
+    ]`}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ]`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_shared",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both",
+        ":whole_static_lib_for_shared",
+    ]`,
+			}),
+		},
 	})
 }
 
+func TestCcLibraryDeps(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared/static props",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"both.cpp":       "",
+			"sharedonly.cpp": "",
+			"staticonly.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "a",
+    srcs: ["both.cpp"],
+    cflags: ["bothflag"],
+    shared_libs: ["implementation_shared_dep_for_both", "shared_dep_for_both"],
+    export_shared_lib_headers: ["shared_dep_for_both"],
+    static_libs: ["implementation_static_dep_for_both", "static_dep_for_both"],
+    export_static_lib_headers: ["static_dep_for_both", "whole_static_dep_for_both"],
+    whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_both", "whole_static_dep_for_both"],
+    static: {
+        srcs: ["staticonly.cpp"],
+        cflags: ["staticflag"],
+        shared_libs: ["implementation_shared_dep_for_static", "shared_dep_for_static"],
+        export_shared_lib_headers: ["shared_dep_for_static"],
+        static_libs: ["implementation_static_dep_for_static", "static_dep_for_static"],
+        export_static_lib_headers: ["static_dep_for_static", "whole_static_dep_for_static"],
+        whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_static", "whole_static_dep_for_static"],
+    },
+    shared: {
+        srcs: ["sharedonly.cpp"],
+        cflags: ["sharedflag"],
+        shared_libs: ["implementation_shared_dep_for_shared", "shared_dep_for_shared"],
+        export_shared_lib_headers: ["shared_dep_for_shared"],
+        static_libs: ["implementation_static_dep_for_shared", "static_dep_for_shared"],
+        export_static_lib_headers: ["static_dep_for_shared", "whole_static_dep_for_shared"],
+        whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_shared", "whole_static_dep_for_shared"],
+    },
+    include_build_directory: false,
+}
+` + simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_both"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "staticflag",
+    ]`,
+				"deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ]`,
+				"dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_static",
+    ]`,
+				"implementation_deps": `[
+        ":implementation_static_dep_for_both",
+        ":implementation_static_dep_for_static",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":implementation_shared_dep_for_both",
+        ":implementation_shared_dep_for_static",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep_for_both",
+        ":whole_static_dep_for_both",
+        ":not_explicitly_exported_whole_static_dep_for_static",
+        ":whole_static_dep_for_static",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ]`,
+				"deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ]`,
+				"dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_shared",
+    ]`,
+				"implementation_deps": `[
+        ":implementation_static_dep_for_both",
+        ":implementation_static_dep_for_shared",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":implementation_shared_dep_for_both",
+        ":implementation_shared_dep_for_shared",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep_for_both",
+        ":whole_static_dep_for_both",
+        ":not_explicitly_exported_whole_static_dep_for_shared",
+        ":whole_static_dep_for_shared",
+    ]`,
+			})},
+	},
+	)
+}
+
 func TestCcLibraryWholeStaticLibsAlwaysLink(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
 		filesystem: map[string]string{
 			"foo/bar/Android.bp": `
 cc_library {
@@ -374,6 +547,7 @@
         whole_static_libs: ["whole_static_lib_for_shared"],
     },
     bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 
 cc_prebuilt_library_static { name: "whole_static_lib_for_shared" }
@@ -384,30 +558,30 @@
 `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    shared = {
-        "whole_archive_deps": [":whole_static_lib_for_shared_alwayslink"],
-    },
-    static = {
-        "whole_archive_deps": [":whole_static_lib_for_static_alwayslink"],
-    },
-    whole_archive_deps = [":whole_static_lib_for_both_alwayslink"],
-)`},
-	})
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both_alwayslink",
+        ":whole_static_lib_for_static_alwayslink",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both_alwayslink",
+        ":whole_static_lib_for_shared_alwayslink",
+    ]`,
+			}),
+		},
+	},
+	)
 }
 
 func TestCcLibrarySharedStaticPropsInArch(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library shared/static props in arch",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		description:                "cc_library shared/static props in arch",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
 		filesystem: map[string]string{
 			"foo/bar/arm.cpp":        "",
 			"foo/bar/x86.cpp":        "",
@@ -478,74 +652,86 @@
 `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
         "bothflag",
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    implementation_deps = [":static_dep_for_both"],
-    shared = {
-        "copts": ["sharedflag"] + select({
-            "//build/bazel/platforms/arch:arm": ["-DARM_SHARED"],
-            "//conditions:default": [],
-        }) + select({
-            "//build/bazel/platforms/os:android": ["-DANDROID_SHARED"],
-            "//conditions:default": [],
-        }) + select({
-            "//build/bazel/platforms/os_arch:android_arm": ["-DANDROID_ARM_SHARED"],
-            "//conditions:default": [],
-        }),
-        "dynamic_deps": select({
-            "//build/bazel/platforms/arch:arm": [":arm_shared_dep_for_shared"],
-            "//conditions:default": [],
-        }),
-        "srcs": ["sharedonly.cpp"] + select({
-            "//build/bazel/platforms/arch:arm": ["arm_shared.cpp"],
-            "//conditions:default": [],
-        }) + select({
-            "//build/bazel/platforms/os:android": ["android_shared.cpp"],
-            "//conditions:default": [],
-        }),
-        "static_deps": [":static_dep_for_shared"] + select({
-            "//build/bazel/platforms/arch:arm": [":arm_static_dep_for_shared"],
-            "//conditions:default": [],
-        }) + select({
-            "//build/bazel/platforms/os:android": [":android_dep_for_shared"],
-            "//conditions:default": [],
-        }),
-        "whole_archive_deps": select({
-            "//build/bazel/platforms/arch:arm": [":arm_whole_static_dep_for_shared"],
-            "//conditions:default": [],
-        }),
-    },
-    srcs = ["both.cpp"],
-    static = {
-        "copts": ["staticflag"] + select({
-            "//build/bazel/platforms/arch:x86": ["-DX86_STATIC"],
-            "//conditions:default": [],
-        }),
-        "srcs": ["staticonly.cpp"] + select({
-            "//build/bazel/platforms/arch:x86": ["x86_static.cpp"],
-            "//conditions:default": [],
-        }),
-        "static_deps": [":static_dep_for_static"] + select({
-            "//build/bazel/platforms/arch:x86": [":x86_dep_for_static"],
-            "//conditions:default": [],
-        }),
-    },
-)`},
-	})
+        "staticflag",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": ["-DX86_STATIC"],
+        "//conditions:default": [],
+    })`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [":x86_dep_for_static"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": ["x86_static.cpp"],
+        "//conditions:default": [],
+    })`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": ["-DARM_SHARED"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["-DANDROID_SHARED"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os_arch:android_arm": ["-DANDROID_ARM_SHARED"],
+        "//conditions:default": [],
+    })`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [":arm_static_dep_for_shared"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":android_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_shared_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": ["arm_shared.cpp"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["android_shared.cpp"],
+        "//conditions:default": [],
+    })`,
+				"whole_archive_deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_whole_static_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	},
+	)
 }
 
 func TestCcLibrarySharedStaticPropsWithMixedSources(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library shared/static props with c/cpp/s mixed sources",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		description:                "cc_library shared/static props with c/cpp/s mixed sources",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
 		filesystem: map[string]string{
 			"foo/bar/both_source.cpp":   "",
 			"foo/bar/both_source.cc":    "",
@@ -571,27 +757,27 @@
     "both_source.c",
     "both_source.s",
     "both_source.S",
-        ":both_filegroup",
+    ":both_filegroup",
   ],
     static: {
-    srcs: [
-      "static_source.cpp",
-      "static_source.cc",
-      "static_source.c",
-      "static_source.s",
-      "static_source.S",
-      ":static_filegroup",
-    ],
+        srcs: [
+          "static_source.cpp",
+          "static_source.cc",
+          "static_source.c",
+          "static_source.s",
+          "static_source.S",
+          ":static_filegroup",
+        ],
     },
     shared: {
-    srcs: [
-      "shared_source.cpp",
-      "shared_source.cc",
-      "shared_source.c",
-      "shared_source.s",
-      "shared_source.S",
-      ":shared_filegroup",
-    ],
+        srcs: [
+          "shared_source.cpp",
+          "shared_source.cc",
+          "shared_source.c",
+          "shared_source.s",
+          "shared_source.S",
+          ":shared_filegroup",
+        ],
     },
     bazel_module: { bp2build_available: true },
 }
@@ -619,73 +805,65 @@
 `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    asflags = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    shared = {
-        "srcs": [
-            ":shared_filegroup_cpp_srcs",
-            "shared_source.cc",
-            "shared_source.cpp",
-        ],
-        "srcs_as": [
-            "shared_source.s",
-            "shared_source.S",
-            ":shared_filegroup_as_srcs",
-        ],
-        "srcs_c": [
-            "shared_source.c",
-            ":shared_filegroup_c_srcs",
-        ],
-    },
-    srcs = [
-        ":both_filegroup_cpp_srcs",
-        "both_source.cc",
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"local_includes": `["."]`,
+				"srcs": `[
         "both_source.cpp",
-    ],
-    srcs_as = [
+        "both_source.cc",
+        ":both_filegroup_cpp_srcs",
+        "static_source.cpp",
+        "static_source.cc",
+        ":static_filegroup_cpp_srcs",
+    ]`,
+				"srcs_as": `[
         "both_source.s",
         "both_source.S",
         ":both_filegroup_as_srcs",
-    ],
-    srcs_c = [
+        "static_source.s",
+        "static_source.S",
+        ":static_filegroup_as_srcs",
+    ]`,
+				"srcs_c": `[
         "both_source.c",
         ":both_filegroup_c_srcs",
-    ],
-    static = {
-        "srcs": [
-            ":static_filegroup_cpp_srcs",
-            "static_source.cc",
-            "static_source.cpp",
-        ],
-        "srcs_as": [
-            "static_source.s",
-            "static_source.S",
-            ":static_filegroup_as_srcs",
-        ],
-        "srcs_c": [
-            "static_source.c",
-            ":static_filegroup_c_srcs",
-        ],
-    },
-)`},
-	})
+        "static_source.c",
+        ":static_filegroup_c_srcs",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both_source.cpp",
+        "both_source.cc",
+        ":both_filegroup_cpp_srcs",
+        "shared_source.cpp",
+        "shared_source.cc",
+        ":shared_filegroup_cpp_srcs",
+    ]`,
+				"srcs_as": `[
+        "both_source.s",
+        "both_source.S",
+        ":both_filegroup_as_srcs",
+        "shared_source.s",
+        "shared_source.S",
+        ":shared_filegroup_as_srcs",
+    ]`,
+				"srcs_c": `[
+        "both_source.c",
+        ":both_filegroup_c_srcs",
+        "shared_source.c",
+        ":shared_filegroup_c_srcs",
+    ]`,
+			})}})
 }
 
 func TestCcLibraryNonConfiguredVersionScript(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library non-configured version script",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		description:                "cc_library non-configured version script",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
 		filesystem: map[string]string{
 			"foo/bar/Android.bp": `
 cc_library {
@@ -693,117 +871,128 @@
     srcs: ["a.cpp"],
     version_script: "v.map",
     bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    srcs = ["a.cpp"],
-    version_script = "v.map",
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"additional_linker_inputs": `["v.map"]`,
+			"linkopts":                 `["-Wl,--version-script,$(location v.map)"]`,
+			"srcs":                     `["a.cpp"]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibraryConfiguredVersionScript(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library configured version script",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
+		description:                "cc_library configured version script",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
 		filesystem: map[string]string{
 			"foo/bar/Android.bp": `
-    cc_library {
-       name: "a",
-       srcs: ["a.cpp"],
-       arch: {
-         arm: {
-           version_script: "arm.map",
-         },
-         arm64: {
-           version_script: "arm64.map",
-         },
-       },
+cc_library {
+   name: "a",
+   srcs: ["a.cpp"],
+   arch: {
+     arm: {
+       version_script: "arm.map",
+     },
+     arm64: {
+       version_script: "arm64.map",
+     },
+   },
 
-       bazel_module: { bp2build_available: true },
-    }
+   bazel_module: { bp2build_available: true },
+    include_build_directory: false,
+}
     `,
 		},
 		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    srcs = ["a.cpp"],
-    version_script = select({
-        "//build/bazel/platforms/arch:arm": "arm.map",
-        "//build/bazel/platforms/arch:arm64": "arm64.map",
-        "//conditions:default": None,
-    }),
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"additional_linker_inputs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.map"],
+        "//build/bazel/platforms/arch:arm64": ["arm64.map"],
+        "//conditions:default": [],
+    })`,
+			"linkopts": `select({
+        "//build/bazel/platforms/arch:arm": ["-Wl,--version-script,$(location arm.map)"],
+        "//build/bazel/platforms/arch:arm64": ["-Wl,--version-script,$(location arm64.map)"],
+        "//conditions:default": [],
+    })`,
+			"srcs": `["a.cpp"]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibrarySharedLibs(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library shared_libs",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
+		description:                "cc_library shared_libs",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "mylib",
-    bazel_module: { bp2build_available: true },
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library {
     name: "a",
     shared_libs: ["mylib",],
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    dynamic_deps = [":mylib"],
-)`, `cc_library(
-    name = "mylib",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"implementation_dynamic_deps": `[":mylib"]`,
+		}),
+	},
+	)
 }
 
-func TestCcLibraryPackRelocations(t *testing.T) {
+func TestCcLibraryFeatures(t *testing.T) {
+	expected_targets := []string{}
+	expected_targets = append(expected_targets, makeCcLibraryTargets("a", attrNameToString{
+		"features": `[
+        "disable_pack_relocations",
+        "-no_undefined_symbols",
+    ]`,
+		"srcs": `["a.cpp"]`,
+	})...)
+	expected_targets = append(expected_targets, makeCcLibraryTargets("b", attrNameToString{
+		"features": `select({
+        "//build/bazel/platforms/arch:x86_64": [
+            "disable_pack_relocations",
+            "-no_undefined_symbols",
+        ],
+        "//conditions:default": [],
+    })`,
+		"srcs": `["b.cpp"]`,
+	})...)
+	expected_targets = append(expected_targets, makeCcLibraryTargets("c", attrNameToString{
+		"features": `select({
+        "//build/bazel/platforms/os:darwin": [
+            "disable_pack_relocations",
+            "-no_undefined_symbols",
+        ],
+        "//conditions:default": [],
+    })`,
+		"srcs": `["c.cpp"]`,
+	})...)
+
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library pack_relocations test",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
+		description:                "cc_library pack_relocations test",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     srcs: ["a.cpp"],
     pack_relocations: false,
-    bazel_module: { bp2build_available: true },
+    allow_undefined_symbols: true,
+    include_build_directory: false,
 }
 
 cc_library {
@@ -811,10 +1000,11 @@
     srcs: ["b.cpp"],
     arch: {
         x86_64: {
-    pack_relocations: false,
-  },
+            pack_relocations: false,
+            allow_undefined_symbols: true,
+        },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 
 cc_library {
@@ -822,117 +1012,67 @@
     srcs: ["c.cpp"],
     target: {
         darwin: {
-    pack_relocations: false,
-  },
+            pack_relocations: false,
+            allow_undefined_symbols: true,
+        },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }`,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    linkopts = ["-Wl,--pack-dyn-relocs=none"],
-    srcs = ["a.cpp"],
-)`, `cc_library(
-    name = "b",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    linkopts = select({
-        "//build/bazel/platforms/arch:x86_64": ["-Wl,--pack-dyn-relocs=none"],
-        "//conditions:default": [],
-    }),
-    srcs = ["b.cpp"],
-)`, `cc_library(
-    name = "c",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    linkopts = select({
-        "//build/bazel/platforms/os:darwin": ["-Wl,--pack-dyn-relocs=none"],
-        "//conditions:default": [],
-    }),
-    srcs = ["c.cpp"],
-)`},
+		expectedBazelTargets: expected_targets,
 	})
 }
 
 func TestCcLibrarySpacesInCopts(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library spaces in copts",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
+		description:                "cc_library spaces in copts",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     cflags: ["-include header.h",],
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"copts": `[
         "-include",
         "header.h",
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-)`},
-	})
+    ]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibraryCppFlagsGoesIntoCopts(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library cppflags usage",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `cc_library {
+		description:                "cc_library cppflags usage",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `cc_library {
     name: "a",
     srcs: ["a.cpp"],
-    cflags: [
-    "-Wall",
-  ],
+    cflags: ["-Wall"],
     cppflags: [
         "-fsigned-char",
         "-pedantic",
-  ],
+    ],
     arch: {
         arm64: {
             cppflags: ["-DARM64=1"],
+        },
     },
-  },
     target: {
         android: {
             cppflags: ["-DANDROID=1"],
+        },
     },
-  },
-    bazel_module: { bp2build_available: true  },
+    include_build_directory: false,
 }
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Wall",
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    cppflags = [
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"copts": `["-Wall"]`,
+			"cppflags": `[
         "-fsigned-char",
         "-pedantic",
     ] + select({
@@ -941,60 +1081,18 @@
     }) + select({
         "//build/bazel/platforms/os:android": ["-DANDROID=1"],
         "//conditions:default": [],
-    }),
-    srcs = ["a.cpp"],
-)`},
-	})
-}
-
-func TestCcLibraryLabelAttributeGetTargetProperties(t *testing.T) {
-	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library GetTargetProperties on a LabelAttribute",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
-    cc_library {
-       name: "a",
-       srcs: ["a.cpp"],
-       target: {
-         android_arm: {
-           version_script: "android_arm.map",
-         },
-         linux_bionic_arm64: {
-           version_script: "linux_bionic_arm64.map",
-         },
-       },
-
-       bazel_module: { bp2build_available: true },
-    }
-    `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    srcs = ["a.cpp"],
-    version_script = select({
-        "//build/bazel/platforms/os_arch:android_arm": "android_arm.map",
-        "//build/bazel/platforms/os_arch:linux_bionic_arm64": "linux_bionic_arm64.map",
-        "//conditions:default": None,
-    }),
-)`},
-	})
+    })`,
+			"srcs": `["a.cpp"]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibraryExcludeLibs(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		filesystem:                         map[string]string{},
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem:                 map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library {
     name: "foo_static",
@@ -1031,6 +1129,7 @@
             ],
         },
     },
+    include_build_directory: false,
 }
 
 cc_library {
@@ -1068,147 +1167,186 @@
     bazel_module: { bp2build_available: false },
 }
 `,
-		expectedBazelTargets: []string{
-			`cc_library(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    dynamic_deps = select({
+		expectedBazelTargets: makeCcLibraryTargets("foo_static", attrNameToString{
+			"implementation_deps": `select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_static_lib_excludes_bp2build_cc_library_static"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [],
+        "//conditions:default": [":malloc_not_svelte_static_lib_excludes_bp2build_cc_library_static"],
+    })`,
+			"implementation_dynamic_deps": `select({
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": [":arm_shared_lib_excludes"],
     }) + select({
         "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_shared_lib"],
         "//conditions:default": [],
-    }),
-    implementation_deps = select({
+    })`,
+			"srcs_c": `["common.c"]`,
+			"whole_archive_deps": `select({
         "//build/bazel/platforms/arch:arm": [],
-        "//conditions:default": [":arm_static_lib_excludes"],
+        "//conditions:default": [":arm_whole_static_lib_excludes_bp2build_cc_library_static"],
     }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": [],
-        "//conditions:default": [":malloc_not_svelte_static_lib_excludes"],
-    }),
-    srcs_c = ["common.c"],
-    whole_archive_deps = select({
-        "//build/bazel/platforms/arch:arm": [],
-        "//conditions:default": [":arm_whole_static_lib_excludes"],
-    }) + select({
-        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib"],
-        "//conditions:default": [":malloc_not_svelte_whole_static_lib_excludes"],
-    }),
-)`,
-		},
-	})
+        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib_bp2build_cc_library_static"],
+        "//conditions:default": [":malloc_not_svelte_whole_static_lib_excludes_bp2build_cc_library_static"],
+    })`,
+		}),
+	},
+	)
 }
 
 func TestCCLibraryNoCrtTrue(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library - simple example",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library - nocrt: true emits attribute",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"impl.cpp": "",
 		},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
-    no_libcrt: true,
+    nocrt: true,
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["impl.cpp"],
-    use_libcrt = False,
-)`}})
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"link_crt": `False`,
+			"srcs":     `["impl.cpp"]`,
+		}),
+	},
+	)
 }
 
 func TestCCLibraryNoCrtFalse(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library - nocrt: false - does not emit attribute",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"impl.cpp": "",
 		},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
-    no_libcrt: false,
+    nocrt: false,
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["impl.cpp"],
-    use_libcrt = True,
-)`}})
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+		}),
+	})
 }
 
 func TestCCLibraryNoCrtArchVariant(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library - nocrt in select",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"impl.cpp": "",
 		},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
     arch: {
         arm: {
-            no_libcrt: true,
+            nocrt: true,
         },
         x86: {
-            no_libcrt: true,
+            nocrt: false,
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["impl.cpp"],
-    use_libcrt = select({
-        "//build/bazel/platforms/arch:arm": False,
-        "//build/bazel/platforms/arch:x86": False,
-        "//conditions:default": None,
-    }),
-)`}})
+		expectedErr: fmt.Errorf("Android.bp:16:1: module \"foo-lib\": nocrt is not supported for arch variants"),
+	})
 }
 
-func TestCCLibraryNoCrtArchVariantWithDefault(t *testing.T) {
+func TestCCLibraryNoLibCrtTrue(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		filesystem: map[string]string{
 			"impl.cpp": "",
 		},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: true,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs":       `["impl.cpp"]`,
+			"use_libcrt": `False`,
+		}),
+	})
+}
+
+func makeCcLibraryTargets(name string, attrs attrNameToString) []string {
+	STATIC_ONLY_ATTRS := map[string]bool{}
+	SHARED_ONLY_ATTRS := map[string]bool{
+		"link_crt":                 true,
+		"additional_linker_inputs": true,
+		"linkopts":                 true,
+		"strip":                    true,
+	}
+	sharedAttrs := attrNameToString{}
+	staticAttrs := attrNameToString{}
+	for key, val := range attrs {
+		if _, staticOnly := STATIC_ONLY_ATTRS[key]; !staticOnly {
+			sharedAttrs[key] = val
+		}
+		if _, sharedOnly := SHARED_ONLY_ATTRS[key]; !sharedOnly {
+			staticAttrs[key] = val
+		}
+	}
+	sharedTarget := makeBazelTarget("cc_library_shared", name, sharedAttrs)
+	staticTarget := makeBazelTarget("cc_library_static", name+"_bp2build_cc_library_static", staticAttrs)
+
+	return []string{staticTarget, sharedTarget}
+}
+
+func TestCCLibraryNoLibCrtFalse(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
     no_libcrt: false,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs":       `["impl.cpp"]`,
+			"use_libcrt": `True`,
+		}),
+	})
+}
+
+func TestCCLibraryNoLibCrtArchVariant(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
     arch: {
         arm: {
             no_libcrt: true,
@@ -1217,141 +1355,106 @@
             no_libcrt: true,
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["impl.cpp"],
-    use_libcrt = select({
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+			"use_libcrt": `select({
         "//build/bazel/platforms/arch:arm": False,
         "//build/bazel/platforms/arch:x86": False,
-        "//conditions:default": True,
-    }),
-)`}})
+        "//conditions:default": None,
+    })`,
+		}),
+	})
 }
 
 func TestCcLibraryStrip(t *testing.T) {
+	expectedTargets := []string{}
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("all", attrNameToString{
+		"strip": `{
+        "all": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols", attrNameToString{
+		"strip": `{
+        "keep_symbols": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols_and_debug_frame", attrNameToString{
+		"strip": `{
+        "keep_symbols_and_debug_frame": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols_list", attrNameToString{
+		"strip": `{
+        "keep_symbols_list": ["symbol"],
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("none", attrNameToString{
+		"strip": `{
+        "none": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("nothing", attrNameToString{})...)
+
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library strip args",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
+		description:                "cc_library strip args",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "nothing",
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 cc_library {
     name: "keep_symbols",
-    bazel_module: { bp2build_available: true },
     strip: {
-		keep_symbols: true,
-	}
+        keep_symbols: true,
+    },
+    include_build_directory: false,
 }
 cc_library {
     name: "keep_symbols_and_debug_frame",
-    bazel_module: { bp2build_available: true },
     strip: {
-		keep_symbols_and_debug_frame: true,
-	}
+        keep_symbols_and_debug_frame: true,
+    },
+    include_build_directory: false,
 }
 cc_library {
     name: "none",
-    bazel_module: { bp2build_available: true },
     strip: {
-		none: true,
-	}
+        none: true,
+    },
+    include_build_directory: false,
 }
 cc_library {
     name: "keep_symbols_list",
-    bazel_module: { bp2build_available: true },
     strip: {
-		keep_symbols_list: ["symbol"],
-	}
+        keep_symbols_list: ["symbol"],
+    },
+    include_build_directory: false,
 }
 cc_library {
     name: "all",
-    bazel_module: { bp2build_available: true },
     strip: {
-		all: true,
-	}
+        all: true,
+    },
+    include_build_directory: false,
 }
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "all",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
-        "all": True,
-    },
-)`, `cc_library(
-    name = "keep_symbols",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
-        "keep_symbols": True,
-    },
-)`, `cc_library(
-    name = "keep_symbols_and_debug_frame",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
-        "keep_symbols_and_debug_frame": True,
-    },
-)`, `cc_library(
-    name = "keep_symbols_list",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
-        "keep_symbols_list": ["symbol"],
-    },
-)`, `cc_library(
-    name = "none",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
-        "none": True,
-    },
-)`, `cc_library(
-    name = "nothing",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-)`},
+		expectedBazelTargets: expectedTargets,
 	})
 }
 
 func TestCcLibraryStripWithArch(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library strip args",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-		dir:                                "foo/bar",
-		filesystem: map[string]string{
-			"foo/bar/Android.bp": `
+		description:                "cc_library strip args",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "multi-arch",
-    bazel_module: { bp2build_available: true },
     target: {
         darwin: {
             strip: {
@@ -1370,18 +1473,12 @@
                 keep_symbols: true,
             },
         },
-    }
+    },
+    include_build_directory: false,
 }
 `,
-		},
-		blueprint: soongCcLibraryPreamble,
-		expectedBazelTargets: []string{`cc_library(
-    name = "multi-arch",
-    copts = [
-        "-Ifoo/bar",
-        "-I$(BINDIR)/foo/bar",
-    ],
-    strip = {
+		expectedBazelTargets: makeCcLibraryTargets("multi-arch", attrNameToString{
+			"strip": `{
         "keep_symbols": select({
             "//build/bazel/platforms/arch:arm64": True,
             "//conditions:default": None,
@@ -1397,94 +1494,82 @@
             ],
             "//conditions:default": [],
         }),
-    },
-)`},
-	})
+    }`,
+		}),
+	},
+	)
 }
 
 func TestCcLibrary_SystemSharedLibsRootEmpty(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty at root",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty at root",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "root_empty",
-	  system_shared_libs: [],
+    system_shared_libs: [],
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "root_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    system_dynamic_deps = [],
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("root_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibrary_SystemSharedLibsStaticEmpty(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty for static variant",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty for static variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "static_empty",
     static: {
-				system_shared_libs: [],
-		},
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "static_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    static = {
-        "system_dynamic_deps": [],
-    },
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "static_empty_bp2build_cc_library_static", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+			makeBazelTarget("cc_library_shared", "static_empty", attrNameToString{}),
+		},
 	})
 }
 
 func TestCcLibrary_SystemSharedLibsSharedEmpty(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty for shared variant",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty for shared variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "shared_empty",
     shared: {
-				system_shared_libs: [],
-		},
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "shared_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    shared = {
-        "system_dynamic_deps": [],
-    },
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "shared_empty_bp2build_cc_library_static", attrNameToString{}),
+			makeBazelTarget("cc_library_shared", "shared_empty", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+		},
 	})
 }
 
 func TestCcLibrary_SystemSharedLibsSharedBionicEmpty(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty for shared, bionic variant",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty for shared, bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "shared_empty",
@@ -1494,19 +1579,16 @@
                 system_shared_libs: [],
             }
         }
-		},
+    },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "shared_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    shared = {
-        "system_dynamic_deps": [],
-    },
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "shared_empty_bp2build_cc_library_static", attrNameToString{}),
+			makeBazelTarget("cc_library_shared", "shared_empty", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+		},
 	})
 }
 
@@ -1516,10 +1598,9 @@
 	// only for linux_bionic, but `android` had `["libc", "libdl", "libm"].
 	// b/195791252 tracks the fix.
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty for linux_bionic variant",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty for linux_bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "target_linux_bionic_empty",
@@ -1528,25 +1609,21 @@
             system_shared_libs: [],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "target_linux_bionic_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    system_dynamic_deps = [],
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("target_linux_bionic_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibrary_SystemSharedLibsBionicEmpty(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs empty for bionic variant",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs empty for bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "target_bionic_empty",
@@ -1555,59 +1632,530 @@
             system_shared_libs: [],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "target_bionic_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    system_dynamic_deps = [],
-)`},
-	})
+		expectedBazelTargets: makeCcLibraryTargets("target_bionic_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
 }
 
 func TestCcLibrary_SystemSharedLibsSharedAndRoot(t *testing.T) {
 	runCcLibraryTestCase(t, bp2buildTestCase{
-		description:                        "cc_library system_shared_libs set for shared and root",
-		moduleTypeUnderTest:                "cc_library",
-		moduleTypeUnderTestFactory:         cc.LibraryFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		description:                "cc_library system_shared_libs set for shared and root",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
 		blueprint: soongCcLibraryPreamble + `
-cc_library {name: "libc"}
-cc_library {name: "libm"}
+cc_library {
+    name: "libc",
+    bazel_module: { bp2build_available: false },
+}
+cc_library {
+    name: "libm",
+    bazel_module: { bp2build_available: false },
+}
 
 cc_library {
     name: "foo",
     system_shared_libs: ["libc"],
     shared: {
-				system_shared_libs: ["libm"],
+        system_shared_libs: ["libm"],
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library(
-    name = "foo",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    shared = {
-        "system_dynamic_deps": [":libm"],
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"system_dynamic_deps": `[":libc"]`,
+			}),
+			makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"system_dynamic_deps": `[
+        ":libc",
+        ":libm",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryOsSelects(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - selects for all os targets",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["base.cpp"],
+    target: {
+        android: {
+            srcs: ["android.cpp"],
+        },
+        linux: {
+            srcs: ["linux.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["linux_glibc.cpp"],
+        },
+        darwin: {
+            srcs: ["darwin.cpp"],
+        },
+        bionic: {
+            srcs: ["bionic.cpp"],
+        },
+        linux_musl: {
+            srcs: ["linux_musl.cpp"],
+        },
+        windows: {
+            srcs: ["windows.cpp"],
+        },
     },
-    system_dynamic_deps = [":libc"],
-)`, `cc_library(
-    name = "libc",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library(
-    name = "libm",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`},
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["base.cpp"] + select({
+        "//build/bazel/platforms/os:android": [
+            "linux.cpp",
+            "bionic.cpp",
+            "android.cpp",
+        ],
+        "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
+        "//build/bazel/platforms/os:linux": [
+            "linux.cpp",
+            "linux_glibc.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            "linux.cpp",
+            "bionic.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_musl": [
+            "linux.cpp",
+            "linux_musl.cpp",
+        ],
+        "//build/bazel/platforms/os:windows": ["windows.cpp"],
+        "//conditions:default": [],
+    })`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryCppStdWithGnuExtensions_ConvertsToFeatureAttr(t *testing.T) {
+	type testCase struct {
+		cpp_std        string
+		c_std          string
+		gnu_extensions string
+		bazel_cpp_std  string
+		bazel_c_std    string
+	}
+
+	testCases := []testCase{
+		// Existing usages of cpp_std in AOSP are:
+		// experimental, c++11, c++17, c++2a, c++98, gnu++11, gnu++17
+		//
+		// not set, only emit if gnu_extensions is disabled. the default (gnu+17
+		// is set in the toolchain.)
+		{cpp_std: "", gnu_extensions: "", bazel_cpp_std: ""},
+		{cpp_std: "", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "", gnu_extensions: "true", bazel_cpp_std: ""},
+		// experimental defaults to gnu++2a
+		{cpp_std: "experimental", gnu_extensions: "", bazel_cpp_std: "gnu++2a"},
+		{cpp_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++2a", bazel_c_std: "c99"},
+		{cpp_std: "experimental", gnu_extensions: "true", bazel_cpp_std: "gnu++2a"},
+		// Explicitly setting a c++ std does not use replace gnu++ std even if
+		// gnu_extensions is true.
+		// "c++11",
+		{cpp_std: "c++11", gnu_extensions: "", bazel_cpp_std: "c++11"},
+		{cpp_std: "c++11", gnu_extensions: "false", bazel_cpp_std: "c++11", bazel_c_std: "c99"},
+		{cpp_std: "c++11", gnu_extensions: "true", bazel_cpp_std: "c++11"},
+		// "c++17",
+		{cpp_std: "c++17", gnu_extensions: "", bazel_cpp_std: "c++17"},
+		{cpp_std: "c++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "c++17", gnu_extensions: "true", bazel_cpp_std: "c++17"},
+		// "c++2a",
+		{cpp_std: "c++2a", gnu_extensions: "", bazel_cpp_std: "c++2a"},
+		{cpp_std: "c++2a", gnu_extensions: "false", bazel_cpp_std: "c++2a", bazel_c_std: "c99"},
+		{cpp_std: "c++2a", gnu_extensions: "true", bazel_cpp_std: "c++2a"},
+		// "c++98",
+		{cpp_std: "c++98", gnu_extensions: "", bazel_cpp_std: "c++98"},
+		{cpp_std: "c++98", gnu_extensions: "false", bazel_cpp_std: "c++98", bazel_c_std: "c99"},
+		{cpp_std: "c++98", gnu_extensions: "true", bazel_cpp_std: "c++98"},
+		// gnu++ is replaced with c++ if gnu_extensions is explicitly false.
+		// "gnu++11",
+		{cpp_std: "gnu++11", gnu_extensions: "", bazel_cpp_std: "gnu++11"},
+		{cpp_std: "gnu++11", gnu_extensions: "false", bazel_cpp_std: "c++11", bazel_c_std: "c99"},
+		{cpp_std: "gnu++11", gnu_extensions: "true", bazel_cpp_std: "gnu++11"},
+		// "gnu++17",
+		{cpp_std: "gnu++17", gnu_extensions: "", bazel_cpp_std: "gnu++17"},
+		{cpp_std: "gnu++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17"},
+
+		// some c_std test cases
+		{c_std: "experimental", gnu_extensions: "", bazel_c_std: "gnu11"},
+		{c_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
+		{c_std: "experimental", gnu_extensions: "true", bazel_c_std: "gnu11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
+	}
+	for i, tc := range testCases {
+		name_prefix := fmt.Sprintf("a_%v", i)
+		cppStdProp := ""
+		if tc.cpp_std != "" {
+			cppStdProp = fmt.Sprintf("    cpp_std: \"%s\",", tc.cpp_std)
+		}
+		cStdProp := ""
+		if tc.c_std != "" {
+			cStdProp = fmt.Sprintf("    c_std: \"%s\",", tc.c_std)
+		}
+		gnuExtensionsProp := ""
+		if tc.gnu_extensions != "" {
+			gnuExtensionsProp = fmt.Sprintf("    gnu_extensions: %s,", tc.gnu_extensions)
+		}
+		attrs := attrNameToString{}
+		if tc.bazel_cpp_std != "" {
+			attrs["cpp_std"] = fmt.Sprintf(`"%s"`, tc.bazel_cpp_std)
+		}
+		if tc.bazel_c_std != "" {
+			attrs["c_std"] = fmt.Sprintf(`"%s"`, tc.bazel_c_std)
+		}
+
+		runCcLibraryTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library",
+			moduleTypeUnderTestFactory: cc.LibraryFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library {
+	name: "%s_full",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: makeCcLibraryTargets(name_prefix+"_full", attrs),
+		})
+
+		runCcLibraryStaticTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library_static with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library_static",
+			moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library_static {
+	name: "%s_static",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: []string{
+				makeBazelTarget("cc_library_static", name_prefix+"_static", attrs),
+			},
+		})
+
+		runCcLibrarySharedTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library_shared with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library_shared",
+			moduleTypeUnderTestFactory: cc.LibrarySharedFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library_shared {
+	name: "%s_shared",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: []string{
+				makeBazelTarget("cc_library_shared", name_prefix+"_shared", attrs),
+			},
+		})
+	}
+}
+
+func TestCcLibraryProtoSimple(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs":                `["foo.proto"]`,
+				"strip_import_prefix": `""`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoNoCanonicalPathFromRoot(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: { canonical_path_from_root: false},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoExplicitCanonicalPathFromRoot(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: { canonical_path_from_root: true},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs":                `["foo.proto"]`,
+				"strip_import_prefix": `""`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoFull(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+		type: "full",
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_proto_library", "foo_cc_proto", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto"]`,
+				"deps":                              `[":libprotobuf-cpp-full"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-full"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoLite(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+		type: "lite",
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoExportHeaders(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoFilegroups(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble +
+			simpleModuleDoNotConvertBp2build("filegroup", "a_fg_proto") +
+			simpleModuleDoNotConvertBp2build("filegroup", "b_protos") +
+			simpleModuleDoNotConvertBp2build("filegroup", "c-proto-srcs") +
+			simpleModuleDoNotConvertBp2build("filegroup", "proto-srcs-d") + `
+cc_library {
+	name: "a",
+	srcs: [":a_fg_proto"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "b",
+	srcs: [":b_protos"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "c",
+	srcs: [":c-proto-srcs"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "d",
+	srcs: [":proto-srcs-d"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "a_proto", attrNameToString{
+				"srcs": `[":a_fg_proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "a_cc_proto_lite", attrNameToString{
+				"deps": `[":a_proto"]`,
+			}), makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "b_proto", attrNameToString{
+				"srcs": `[":b_protos"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "b_cc_proto_lite", attrNameToString{
+				"deps": `[":b_proto"]`,
+			}), makeBazelTarget("cc_library_static", "b_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "b", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "c_proto", attrNameToString{
+				"srcs": `[":c-proto-srcs"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "c_cc_proto_lite", attrNameToString{
+				"deps": `[":c_proto"]`,
+			}), makeBazelTarget("cc_library_static", "c_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "c", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "d_proto", attrNameToString{
+				"srcs": `[":proto-srcs-d"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "d_cc_proto_lite", attrNameToString{
+				"deps": `[":d_proto"]`,
+			}), makeBazelTarget("cc_library_static", "d_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "d", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}),
+		},
 	})
 }
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index ea2c10a..594c050 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -25,18 +25,18 @@
 	// See cc/testing.go for more context
 	soongCcLibraryHeadersPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
+    name: "linux_bionic_supported",
 }
 
 toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+    name: "libclang_rt.builtins-x86_64-android",
+    defaults: ["linux_bionic_supported"],
+    vendor_available: true,
+    vendor_ramdisk_available: true,
+    product_available: true,
+    recovery_available: true,
+    native_bridge_supported: true,
+    src: "",
 }`
 )
 
@@ -78,10 +78,9 @@
 
 func TestCcLibraryHeadersSimple(t *testing.T) {
 	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_headers test",
-		moduleTypeUnderTest:                "cc_library_headers",
-		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		description:                "cc_library_headers test",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
 		filesystem: map[string]string{
 			"lib-1/lib1a.h":                        "",
 			"lib-1/lib1b.h":                        "",
@@ -99,11 +98,13 @@
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
     name: "lib-2",
     export_include_dirs: ["lib-2"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
@@ -113,7 +114,7 @@
 
     arch: {
         arm64: {
-	    // We expect dir-1 headers to be dropped, because dir-1 is already in export_include_dirs
+      // We expect dir-1 headers to be dropped, because dir-1 is already in export_include_dirs
             export_include_dirs: ["arch_arm64_exported_include_dir", "dir-1"],
         },
         x86: {
@@ -126,17 +127,9 @@
 
     // TODO: Also support export_header_lib_headers
 }`,
-		expectedBazelTargets: []string{`cc_library_headers(
-    name = "foo_headers",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [
-        ":lib-1",
-        ":lib-2",
-    ],
-    includes = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"export_includes": `[
         "dir-1",
         "dir-2",
     ] + select({
@@ -144,39 +137,47 @@
         "//build/bazel/platforms/arch:x86": ["arch_x86_exported_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["arch_x86_64_exported_include_dir"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_headers(
-    name = "lib-1",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["lib-1"],
-)`, `cc_library_headers(
-    name = "lib-2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["lib-2"],
-)`},
+    })`,
+				"implementation_deps": `[
+        ":lib-1",
+        ":lib-2",
+    ]`,
+			}),
+		},
 	})
 }
 
-func TestCcLibraryHeadersOSSpecificHeader(t *testing.T) {
+func TestCcLibraryHeadersOsSpecificHeader(t *testing.T) {
 	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_headers test with os-specific header_libs props",
-		moduleTypeUnderTest:                "cc_library_headers",
-		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "cc_library_headers test with os-specific header_libs props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "android-lib" }
-cc_library_headers { name: "base-lib" }
-cc_library_headers { name: "darwin-lib" }
-cc_library_headers { name: "linux-lib" }
-cc_library_headers { name: "linux_bionic-lib" }
-cc_library_headers { name: "windows-lib" }
+cc_library_headers {
+    name: "android-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "base-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "darwin-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "linux-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "linux_bionic-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "windows-lib",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_headers {
     name: "foo_headers",
     header_libs: ["base-lib"],
@@ -187,126 +188,80 @@
         linux_glibc: { header_libs: ["linux-lib"] },
         windows: { header_libs: ["windows-lib"] },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_headers(
-    name = "android-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "base-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "darwin-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "foo_headers",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [":base-lib"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"implementation_deps": `[":base-lib"] + select({
         "//build/bazel/platforms/os:android": [":android-lib"],
         "//build/bazel/platforms/os:darwin": [":darwin-lib"],
         "//build/bazel/platforms/os:linux": [":linux-lib"],
         "//build/bazel/platforms/os:linux_bionic": [":linux_bionic-lib"],
         "//build/bazel/platforms/os:windows": [":windows-lib"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_headers(
-    name = "linux-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "linux_bionic-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "windows-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryHeadersOsSpecficHeaderLibsExportHeaderLibHeaders(t *testing.T) {
 	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
-		moduleTypeUnderTest:                "cc_library_headers",
-		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
 		blueprint: soongCcLibraryPreamble + `
-cc_library_headers { name: "android-lib" }
-cc_library_headers { name: "exported-lib" }
+cc_library_headers {
+    name: "android-lib",
+    bazel_module: { bp2build_available: false },
+  }
+cc_library_headers {
+    name: "exported-lib",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_headers {
     name: "foo_headers",
     target: {
-        android: { header_libs: ["android-lib"], export_header_lib_headers: ["exported-lib"] },
+        android: {
+            header_libs: ["android-lib", "exported-lib"],
+            export_header_lib_headers: ["exported-lib"]
+        },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_headers(
-    name = "android-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "exported-lib",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-)`, `cc_library_headers(
-    name = "foo_headers",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    deps = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"deps": `select({
         "//build/bazel/platforms/os:android": [":exported-lib"],
         "//conditions:default": [],
-    }),
-    implementation_deps = select({
+    })`,
+				"implementation_deps": `select({
         "//build/bazel/platforms/os:android": [":android-lib"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryHeadersArchAndTargetExportSystemIncludes(t *testing.T) {
 	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
-		moduleTypeUnderTest:                "cc_library_headers",
-		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-		filesystem:                         map[string]string{},
+		description:                "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
 		blueprint: soongCcLibraryPreamble + `cc_library_headers {
     name: "foo_headers",
     export_system_include_dirs: [
-	"shared_include_dir",
+        "shared_include_dir",
     ],
     target: {
-	android: {
-	    export_system_include_dirs: [
-		"android_include_dir",
+        android: {
+            export_system_include_dirs: [
+                "android_include_dir",
             ],
-	},
+        },
         linux_glibc: {
             export_system_include_dirs: [
                 "linux_include_dir",
@@ -320,24 +275,21 @@
     },
     arch: {
         arm: {
-	    export_system_include_dirs: [
-		"arm_include_dir",
+            export_system_include_dirs: [
+                "arm_include_dir",
             ],
-	},
+        },
         x86_64: {
             export_system_include_dirs: [
                 "x86_64_include_dir",
             ],
         },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_headers(
-    name = "foo_headers",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["shared_include_dir"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"export_system_includes": `["shared_include_dir"] + select({
         "//build/bazel/platforms/arch:arm": ["arm_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64_include_dir"],
         "//conditions:default": [],
@@ -346,17 +298,17 @@
         "//build/bazel/platforms/os:darwin": ["darwin_include_dir"],
         "//build/bazel/platforms/os:linux": ["linux_include_dir"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryHeadersNoCrtIgnored(t *testing.T) {
 	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_headers test",
-		moduleTypeUnderTest:                "cc_library_headers",
-		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		description:                "cc_library_headers test",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
 		filesystem: map[string]string{
 			"lib-1/lib1a.h":                        "",
 			"lib-1/lib1b.h":                        "",
@@ -375,14 +327,12 @@
     name: "lib-1",
     export_include_dirs: ["lib-1"],
     no_libcrt: true,
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_headers(
-    name = "lib-1",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["lib-1"],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "lib-1", attrNameToString{
+				"export_includes": `["lib-1"]`,
+			}),
+		},
 	})
 }
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
new file mode 100644
index 0000000..97a600a
--- /dev/null
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -0,0 +1,466 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+const (
+	// See cc/testing.go for more context
+	// TODO(alexmarquez): Split out the preamble into common code?
+	soongCcLibrarySharedPreamble = soongCcLibraryStaticPreamble
+)
+
+func registerCcLibrarySharedModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+}
+
+func runCcLibrarySharedTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "cc_library_shared"
+	(&tc).moduleTypeUnderTestFactory = cc.LibrarySharedFactory
+	runBp2BuildTestCase(t, registerCcLibrarySharedModuleTypes, tc)
+}
+
+func TestCcLibrarySharedSimple(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared simple overall test",
+		filesystem: map[string]string{
+			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+			"include_dir_1/include_dir_1_a.h": "",
+			"include_dir_1/include_dir_1_b.h": "",
+			"include_dir_2/include_dir_2_a.h": "",
+			"include_dir_2/include_dir_2_b.h": "",
+			// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+			"local_include_dir_1/local_include_dir_1_a.h": "",
+			"local_include_dir_1/local_include_dir_1_b.h": "",
+			"local_include_dir_2/local_include_dir_2_a.h": "",
+			"local_include_dir_2/local_include_dir_2_b.h": "",
+			// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+			"export_include_dir_1/export_include_dir_1_a.h": "",
+			"export_include_dir_1/export_include_dir_1_b.h": "",
+			"export_include_dir_2/export_include_dir_2_a.h": "",
+			"export_include_dir_2/export_include_dir_2_b.h": "",
+			// NOTE: Soong implicitly includes headers in the current directory
+			"implicit_include_1.h": "",
+			"implicit_include_2.h": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_headers {
+    name: "header_lib_1",
+    export_include_dirs: ["header_lib_1"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_headers {
+    name: "header_lib_2",
+    export_include_dirs: ["header_lib_2"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_1",
+    srcs: ["shared_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_2",
+    srcs: ["shared_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_1",
+    srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_2",
+    srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "foo_shared",
+    srcs: [
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ],
+    cflags: [
+        "-Dflag1",
+        "-Dflag2"
+    ],
+    shared_libs: [
+        "shared_lib_1",
+        "shared_lib_2"
+    ],
+    whole_static_libs: [
+        "whole_static_lib_1",
+        "whole_static_lib_2"
+    ],
+    include_dirs: [
+        "include_dir_1",
+        "include_dir_2",
+    ],
+    local_include_dirs: [
+        "local_include_dir_1",
+        "local_include_dir_2",
+    ],
+    export_include_dirs: [
+        "export_include_dir_1",
+        "export_include_dir_2"
+    ],
+    header_libs: [
+        "header_lib_1",
+        "header_lib_2"
+    ],
+
+    // TODO: Also support export_header_lib_headers
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"absolute_includes": `[
+        "include_dir_1",
+        "include_dir_2",
+    ]`,
+				"copts": `[
+        "-Dflag1",
+        "-Dflag2",
+    ]`,
+				"export_includes": `[
+        "export_include_dir_1",
+        "export_include_dir_2",
+    ]`,
+				"implementation_deps": `[
+        ":header_lib_1",
+        ":header_lib_2",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_lib_1",
+        ":shared_lib_2",
+    ]`,
+				"local_includes": `[
+        "local_include_dir_1",
+        "local_include_dir_2",
+        ".",
+    ]`,
+				"srcs": `[
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_1",
+        ":whole_static_lib_2",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedArchSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared arch-specific shared_libs with whole_static_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    arch: { arm64: { shared_libs: ["shared_dep"], whole_static_libs: ["static_dep"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep"],
+        "//conditions:default": [],
+    })`,
+				"whole_archive_deps": `select({
+        "//build/bazel/platforms/arch:arm64": [":static_dep"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedOsSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared os-specific shared_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    target: { android: { shared_libs: ["shared_dep"], } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/os:android": [":shared_dep"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedBaseArchOsSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared base, arch, and os-specific shared_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep2",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep3",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    shared_libs: ["shared_dep"],
+    target: { android: { shared_libs: ["shared_dep2"] } },
+    arch: { arm64: { shared_libs: ["shared_dep3"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `[":shared_dep"] + select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep3"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":shared_dep2"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedSimpleExcludeSrcs(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared simple exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":       "",
+			"foo-a.c":        "",
+			"foo-excluded.c": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["common.c", "foo-*.c"],
+    exclude_srcs: ["foo-excluded.c"],
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"srcs_c": `[
+        "common.c",
+        "foo-a.c",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedStrip(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared stripping",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    strip: {
+        keep_symbols: false,
+        keep_symbols_and_debug_frame: true,
+        keep_symbols_list: ["sym", "sym2"],
+        all: true,
+        none: false,
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"strip": `{
+        "all": True,
+        "keep_symbols": False,
+        "keep_symbols_and_debug_frame": True,
+        "keep_symbols_list": [
+            "sym",
+            "sym2",
+        ],
+        "none": False,
+    }`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedVersionScript(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared version script",
+		filesystem: map[string]string{
+			"version_script": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    version_script: "version_script",
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"additional_linker_inputs": `["version_script"]`,
+				"linkopts":                 `["-Wl,--version-script,$(location version_script)"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtTrue(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt: true emits attribute",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    nocrt: true,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"link_crt": `False`,
+				"srcs":     `["impl.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtFalse(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt: false doesn't emit attribute",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    nocrt: false,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"srcs": `["impl.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtArchVariant(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt in select",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            nocrt: true,
+        },
+        x86: {
+            nocrt: false,
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedErr: fmt.Errorf("Android.bp:16:1: module \"foo_shared\": nocrt is not supported for arch variants"),
+	})
+}
+
+func TestCcLibrarySharedProto(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_shared {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedUseVersionLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_shared {
+        name: "foo",
+        use_version_lib: true,
+        include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"use_version_lib": "True",
+			}),
+		},
+	})
+}
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index d9145f6..e2e55dd 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -26,18 +26,18 @@
 	// See cc/testing.go for more context
 	soongCcLibraryStaticPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
+    name: "linux_bionic_supported",
 }
 
 toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+    name: "libclang_rt.builtins-x86_64-android",
+    defaults: ["linux_bionic_supported"],
+    vendor_available: true,
+    vendor_ramdisk_available: true,
+    product_available: true,
+    recovery_available: true,
+    native_bridge_supported: true,
+    src: "",
 }`
 )
 
@@ -79,15 +79,15 @@
 
 func runCcLibraryStaticTestCase(t *testing.T, tc bp2buildTestCase) {
 	t.Helper()
+
+	(&tc).moduleTypeUnderTest = "cc_library_static"
+	(&tc).moduleTypeUnderTestFactory = cc.LibraryStaticFactory
 	runBp2BuildTestCase(t, registerCcLibraryStaticModuleTypes, tc)
 }
 
 func TestCcLibraryStaticSimple(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static test",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static test",
 		filesystem: map[string]string{
 			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
 			"include_dir_1/include_dir_1_a.h": "",
@@ -112,31 +112,37 @@
 cc_library_headers {
     name: "header_lib_1",
     export_include_dirs: ["header_lib_1"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
     name: "header_lib_2",
     export_include_dirs: ["header_lib_2"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "static_lib_1",
     srcs: ["static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "static_lib_2",
     srcs: ["static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "whole_static_lib_1",
     srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "whole_static_lib_2",
     srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
@@ -176,83 +182,47 @@
 
     // TODO: Also support export_header_lib_headers
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `[
+        "include_dir_1",
+        "include_dir_2",
+    ]`,
+				"copts": `[
         "-Dflag1",
         "-Dflag2",
-        "-Iinclude_dir_1",
-        "-I$(BINDIR)/include_dir_1",
-        "-Iinclude_dir_2",
-        "-I$(BINDIR)/include_dir_2",
-        "-Ilocal_include_dir_1",
-        "-I$(BINDIR)/local_include_dir_1",
-        "-Ilocal_include_dir_2",
-        "-I$(BINDIR)/local_include_dir_2",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [
+    ]`,
+				"export_includes": `[
+        "export_include_dir_1",
+        "export_include_dir_2",
+    ]`,
+				"implementation_deps": `[
         ":header_lib_1",
         ":header_lib_2",
         ":static_lib_1",
         ":static_lib_2",
-    ],
-    includes = [
-        "export_include_dir_1",
-        "export_include_dir_2",
-    ],
-    linkstatic = True,
-    srcs = [
+    ]`,
+				"local_includes": `[
+        "local_include_dir_1",
+        "local_include_dir_2",
+        ".",
+    ]`,
+				"srcs": `[
         "foo_static1.cc",
         "foo_static2.cc",
-    ],
-    whole_archive_deps = [
+    ]`,
+				"whole_archive_deps": `[
         ":whole_static_lib_1",
         ":whole_static_lib_2",
-    ],
-)`, `cc_library_static(
-    name = "static_lib_1",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["static_lib_1.cc"],
-)`, `cc_library_static(
-    name = "static_lib_2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["static_lib_2.cc"],
-)`, `cc_library_static(
-    name = "whole_static_lib_1",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["whole_static_lib_1.cc"],
-)`, `cc_library_static(
-    name = "whole_static_lib_2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["whole_static_lib_2.cc"],
-)`},
+    ]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticSubpackage(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static subpackage test",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static subpackage test",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp":                         "",
@@ -270,31 +240,23 @@
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
-    srcs: [
-    ],
+    srcs: [],
     include_dirs: [
-	"subpackage",
+        "subpackage",
     ],
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-I$(BINDIR)/subpackage",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes":    `["."]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticExportIncludeDir(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static export include dir",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static export include dir",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp":                         "",
@@ -305,25 +267,19 @@
 cc_library_static {
     name: "foo_static",
     export_include_dirs: ["subpackage"],
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["subpackage"],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_includes": `["subpackage"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticExportSystemIncludeDir(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static export system include dir",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static export system include dir",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp":                         "",
@@ -334,26 +290,20 @@
 cc_library_static {
     name: "foo_static",
     export_system_include_dirs: ["subpackage"],
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    includes = ["subpackage"],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_system_includes": `["subpackage"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticManyIncludeDirs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		dir:                                "subpackage",
+		description: "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
+		dir:         "subpackage",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp": `
@@ -377,32 +327,25 @@
 			"subpackage3/subsubpackage/header.h":         "",
 		},
 		blueprint: soongCcLibraryStaticPreamble,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage/subsubpackage",
-        "-I$(BINDIR)/subpackage/subsubpackage",
-        "-Isubpackage2",
-        "-I$(BINDIR)/subpackage2",
-        "-Isubpackage3/subsubpackage",
-        "-I$(BINDIR)/subpackage3/subsubpackage",
-        "-Isubpackage/subsubpackage2",
-        "-I$(BINDIR)/subpackage/subsubpackage2",
-        "-Isubpackage",
-        "-I$(BINDIR)/subpackage",
-    ],
-    includes = ["./exported_subsubpackage"],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `[
+        "subpackage/subsubpackage",
+        "subpackage2",
+        "subpackage3/subsubpackage",
+    ]`,
+				"export_includes": `["./exported_subsubpackage"]`,
+				"local_includes": `[
+        "subsubpackage2",
+        ".",
+    ]`,
+			})},
 	})
 }
 
 func TestCcLibraryStaticIncludeBuildDirectoryDisabled(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static include_build_directory disabled",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static include_build_directory disabled",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp":                         "",
@@ -416,25 +359,18 @@
     local_include_dirs: ["subpackage2"],
     include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-I$(BINDIR)/subpackage",
-        "-Isubpackage2",
-        "-I$(BINDIR)/subpackage2",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes":    `["subpackage2"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticIncludeBuildDirectoryEnabled(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static include_build_directory enabled",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static include_build_directory enabled",
 		filesystem: map[string]string{
 			// subpackage with subdirectory
 			"subpackage/Android.bp":                         "",
@@ -450,187 +386,131 @@
     local_include_dirs: ["subpackage2"],
     include_build_directory: true,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-I$(BINDIR)/subpackage",
-        "-Isubpackage2",
-        "-I$(BINDIR)/subpackage2",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes": `[
+        "subpackage2",
+        ".",
+    ]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticArchSpecificStaticLib(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch-specific static_libs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static arch-specific static_libs",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     arch: { arm64: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `select({
         "//build/bazel/platforms/arch:arm64": [":static_dep"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = select({
+    })`,
+				"whole_archive_deps": `select({
         "//build/bazel/platforms/arch:arm64": [":static_dep2"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOsSpecificStaticLib(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static os-specific static_libs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static os-specific static_libs",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     target: { android: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `select({
         "//build/bazel/platforms/os:android": [":static_dep"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = select({
+    })`,
+				"whole_archive_deps": `select({
         "//build/bazel/platforms/os:android": [":static_dep2"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticBaseArchOsSpecificStaticLib(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static base, arch and os-specific static_libs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static base, arch and os-specific static_libs",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
-cc_library_static { name: "static_dep3" }
-cc_library_static { name: "static_dep4" }
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep3",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep4",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     static_libs: ["static_dep"],
     whole_static_libs: ["static_dep2"],
     target: { android: { static_libs: ["static_dep3"] } },
     arch: { arm64: { static_libs: ["static_dep4"] } },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [":static_dep"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `[":static_dep"] + select({
         "//build/bazel/platforms/arch:arm64": [":static_dep4"],
         "//conditions:default": [],
     }) + select({
         "//build/bazel/platforms/os:android": [":static_dep3"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = [":static_dep2"],
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep3",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep4",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+    })`,
+				"whole_archive_deps": `[":static_dep2"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticSimpleExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static simple exclude_srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static simple exclude_srcs",
 		filesystem: map[string]string{
 			"common.c":       "",
 			"foo-a.c":        "",
@@ -641,28 +521,22 @@
     name: "foo_static",
     srcs: ["common.c", "foo-*.c"],
     exclude_srcs: ["foo-excluded.c"],
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `[
         "common.c",
         "foo-a.c",
-    ],
-)`},
+    ]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOneArchSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static one arch specific srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static one arch specific srcs",
 		filesystem: map[string]string{
 			"common.c":  "",
 			"foo-arm.c": "",
@@ -671,29 +545,23 @@
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c"],
-    arch: { arm: { srcs: ["foo-arm.c"] } }
+    arch: { arm: { srcs: ["foo-arm.c"] } },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["foo-arm.c"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOneArchSrcsExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static one arch specific srcs and exclude_srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static one arch specific srcs and exclude_srcs",
 		filesystem: map[string]string{
 			"common.c":           "",
 			"for-arm.c":          "",
@@ -708,28 +576,22 @@
     arch: {
         arm: { srcs: ["for-arm.c"], exclude_srcs: ["not-for-arm.c"] },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-arm.c"],
         "//conditions:default": ["not-for-arm.c"],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticTwoArchExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch specific exclude_srcs for 2 architectures",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static arch specific exclude_srcs for 2 architectures",
 		filesystem: map[string]string{
 			"common.c":      "",
 			"for-arm.c":     "",
@@ -746,37 +608,32 @@
         arm: { srcs: ["for-arm.c"], exclude_srcs: ["not-for-arm.c"] },
         x86: { srcs: ["for-x86.c"], exclude_srcs: ["not-for-x86.c"] },
     },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-arm.c",
             "not-for-x86.c",
+            "for-arm.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-x86.c",
             "not-for-arm.c",
+            "for-x86.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
             "not-for-x86.c",
         ],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
+
 func TestCcLibraryStaticFourArchExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch specific exclude_srcs for 4 architectures",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static arch specific exclude_srcs for 4 architectures",
 		filesystem: map[string]string{
 			"common.c":             "",
 			"for-arm.c":            "",
@@ -799,39 +656,35 @@
         arm64: { srcs: ["for-arm64.c"], exclude_srcs: ["not-for-arm64.c"] },
         x86: { srcs: ["for-x86.c"], exclude_srcs: ["not-for-x86.c"] },
         x86_64: { srcs: ["for-x86_64.c"], exclude_srcs: ["not-for-x86_64.c"] },
-	},
+  },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-arm.c",
             "not-for-arm64.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-arm64.c",
             "not-for-arm.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-x86.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-x86_64.c",
+            "for-x86.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-x86_64.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-x86.c",
+            "for-x86_64.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
@@ -839,17 +692,15 @@
             "not-for-x86.c",
             "not-for-x86_64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOneArchEmpty(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static one arch empty",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static one arch empty",
 		filesystem: map[string]string{
 			"common.cc":       "",
 			"foo-no-arm.cc":   "",
@@ -863,28 +714,22 @@
     arch: {
         arm: { exclude_srcs: ["foo-no-arm.cc"] },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["common.cc"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `["common.cc"] + select({
         "//build/bazel/platforms/arch:arm": [],
         "//conditions:default": ["foo-no-arm.cc"],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOneArchEmptyOtherSet(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static one arch empty other set",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static one arch empty other set",
 		filesystem: map[string]string{
 			"common.cc":       "",
 			"foo-no-arm.cc":   "",
@@ -900,64 +745,48 @@
         arm: { exclude_srcs: ["foo-no-arm.cc"] },
         x86: { srcs: ["x86-only.cc"] },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = ["common.cc"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `["common.cc"] + select({
         "//build/bazel/platforms/arch:arm": [],
         "//build/bazel/platforms/arch:x86": [
             "foo-no-arm.cc",
             "x86-only.cc",
         ],
         "//conditions:default": ["foo-no-arm.cc"],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticMultipleDepSameName(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static multiple dep same name panic",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static multiple dep same name panic",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     static_libs: ["static_dep", "static_dep"],
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    implementation_deps = [":static_dep"],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `[":static_dep"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticOneMultilibSrcsExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static 1 multilib srcs and exclude_srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static 1 multilib srcs and exclude_srcs",
 		filesystem: map[string]string{
 			"common.c":        "",
 			"for-lib32.c":     "",
@@ -970,29 +799,23 @@
     multilib: {
         lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
     },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-lib32.c"],
         "//build/bazel/platforms/arch:x86": ["for-lib32.c"],
         "//conditions:default": ["not-for-lib32.c"],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticTwoMultilibSrcsExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static 2 multilib srcs and exclude_srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static 2 multilib srcs and exclude_srcs",
 		filesystem: map[string]string{
 			"common.c":        "",
 			"for-lib32.c":     "",
@@ -1002,52 +825,46 @@
 		},
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
-    name: "foo_static2",
+    name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
     multilib: {
         lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
         lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
     },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static2",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-lib32.c",
             "not-for-lib64.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-lib64.c",
             "not-for-lib32.c",
+            "for-lib64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-lib32.c",
             "not-for-lib64.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-lib64.c",
             "not-for-lib32.c",
+            "for-lib64.c",
         ],
         "//conditions:default": [
             "not-for-lib32.c",
             "not-for-lib64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibrarySTaticArchMultilibSrcsExcludeSrcs(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch and multilib srcs and exclude_srcs",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static arch and multilib srcs and exclude_srcs",
 		filesystem: map[string]string{
 			"common.c":             "",
 			"for-arm.c":            "",
@@ -1066,7 +883,7 @@
 		},
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
-   name: "foo_static3",
+   name: "foo_static",
    srcs: ["common.c", "not-for-*.c"],
    exclude_srcs: ["not-for-everything.c"],
    arch: {
@@ -1079,46 +896,42 @@
        lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
        lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
    },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static3",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
+            "not-for-arm64.c",
+            "not-for-lib64.c",
+            "not-for-x86.c",
+            "not-for-x86_64.c",
             "for-arm.c",
             "for-lib32.c",
-            "not-for-arm64.c",
-            "not-for-lib64.c",
-            "not-for-x86.c",
-            "not-for-x86_64.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-arm64.c",
-            "for-lib64.c",
             "not-for-arm.c",
             "not-for-lib32.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm64.c",
+            "for-lib64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-lib32.c",
-            "for-x86.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-lib64.c",
             "not-for-x86_64.c",
+            "for-x86.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-lib64.c",
-            "for-x86_64.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-lib32.c",
             "not-for-x86.c",
+            "for-x86_64.c",
+            "for-lib64.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
@@ -1128,108 +941,137 @@
             "not-for-x86.c",
             "not-for-x86_64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticGeneratedHeadersAllPartitions(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcLibraryStaticPreamble + `
+genrule {
+    name: "generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+genrule {
+    name: "export_generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "foo_static",
+    srcs: ["cpp_src.cpp", "as_src.S", "c_src.c"],
+    generated_headers: ["generated_hdr", "export_generated_hdr"],
+    export_generated_headers: ["export_generated_hdr"],
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_includes": `["."]`,
+				"local_includes":  `["."]`,
+				"hdrs":            `[":export_generated_hdr"]`,
+				"srcs": `[
+        "cpp_src.cpp",
+        ":generated_hdr",
+    ]`,
+				"srcs_as": `[
+        "as_src.S",
+        ":generated_hdr",
+    ]`,
+				"srcs_c": `[
+        "c_src.c",
+        ":generated_hdr",
+    ]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticArchSrcsExcludeSrcsGeneratedFiles(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch srcs/exclude_srcs with generated files",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static arch srcs/exclude_srcs with generated files",
 		filesystem: map[string]string{
 			"common.cpp":             "",
 			"for-x86.cpp":            "",
 			"not-for-x86.cpp":        "",
 			"not-for-everything.cpp": "",
-			"dep/Android.bp": `
-genrule {
-	name: "generated_src_other_pkg",
-	out: ["generated_src_other_pkg.cpp"],
-	cmd: "nothing to see here",
-}
-
-genrule {
-	name: "generated_hdr_other_pkg",
-	out: ["generated_hdr_other_pkg.cpp"],
-	cmd: "nothing to see here",
-}
-
-genrule {
-	name: "generated_hdr_other_pkg_x86",
-	out: ["generated_hdr_other_pkg_x86.cpp"],
-	cmd: "nothing to see here",
-}`,
+			"dep/Android.bp": simpleModuleDoNotConvertBp2build("genrule", "generated_src_other_pkg") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_src_other_pkg_x86") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg_x86") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg_android"),
 		},
-		blueprint: soongCcLibraryStaticPreamble + `
-genrule {
-    name: "generated_src",
-    out: ["generated_src.cpp"],
-    cmd: "nothing to see here",
-}
-
-genrule {
-    name: "generated_src_x86",
-    out: ["generated_src_x86.cpp"],
-    cmd: "nothing to see here",
-}
-
-genrule {
-    name: "generated_hdr",
-    out: ["generated_hdr.h"],
-    cmd: "nothing to see here",
-}
-
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src_not_x86") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src_android") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_hdr") + `
 cc_library_static {
-   name: "foo_static3",
-   srcs: ["common.cpp", "not-for-*.cpp"],
-   exclude_srcs: ["not-for-everything.cpp"],
-   generated_sources: ["generated_src", "generated_src_other_pkg"],
-   generated_headers: ["generated_hdr", "generated_hdr_other_pkg"],
-   arch: {
-       x86: {
-           srcs: ["for-x86.cpp"],
-           exclude_srcs: ["not-for-x86.cpp"],
-           generated_sources: ["generated_src_x86"],
-           generated_headers: ["generated_hdr_other_pkg_x86"],
-       },
-   },
+    name: "foo_static",
+    srcs: ["common.cpp", "not-for-*.cpp"],
+    exclude_srcs: ["not-for-everything.cpp"],
+    generated_sources: ["generated_src", "generated_src_other_pkg", "generated_src_not_x86"],
+    generated_headers: ["generated_hdr", "generated_hdr_other_pkg"],
+    export_generated_headers: ["generated_hdr_other_pkg"],
+    arch: {
+        x86: {
+          srcs: ["for-x86.cpp"],
+          exclude_srcs: ["not-for-x86.cpp"],
+          generated_headers: ["generated_hdr_other_pkg_x86"],
+          exclude_generated_sources: ["generated_src_not_x86"],
+    export_generated_headers: ["generated_hdr_other_pkg_x86"],
+        },
+    },
+    target: {
+        android: {
+            generated_sources: ["generated_src_android"],
+            generated_headers: ["generated_hdr_other_pkg_android"],
+    export_generated_headers: ["generated_hdr_other_pkg_android"],
+        },
+    },
+
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static3",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs = [
-        "//dep:generated_hdr_other_pkg",
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `[
+        "common.cpp",
+        ":generated_src",
         "//dep:generated_src_other_pkg",
         ":generated_hdr",
-        ":generated_src",
-        "common.cpp",
     ] + select({
-        "//build/bazel/platforms/arch:x86": [
-            "//dep:generated_hdr_other_pkg_x86",
-            ":generated_src_x86",
-            "for-x86.cpp",
+        "//build/bazel/platforms/arch:x86": ["for-x86.cpp"],
+        "//conditions:default": [
+            "not-for-x86.cpp",
+            ":generated_src_not_x86",
         ],
-        "//conditions:default": ["not-for-x86.cpp"],
-    }),
-)`},
+    }) + select({
+        "//build/bazel/platforms/os:android": [":generated_src_android"],
+        "//conditions:default": [],
+    })`,
+				"hdrs": `["//dep:generated_hdr_other_pkg"] + select({
+        "//build/bazel/platforms/arch:x86": ["//dep:generated_hdr_other_pkg_x86"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["//dep:generated_hdr_other_pkg_android"],
+        "//conditions:default": [],
+    })`,
+				"local_includes":           `["."]`,
+				"export_absolute_includes": `["dep"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticGetTargetProperties(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
 
-		description:                        "cc_library_static complex GetTargetProperties",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static complex GetTargetProperties",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
@@ -1256,15 +1098,11 @@
             srcs: ["linux_bionic_x86_64_src.c"],
         },
     },
+    include_build_directory: false,
 }`,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_c = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `select({
         "//build/bazel/platforms/os:android": ["android_src.c"],
         "//conditions:default": [],
     }) + select({
@@ -1275,17 +1113,15 @@
         "//build/bazel/platforms/os_arch:linux_bionic_arm64": ["linux_bionic_arm64_src.c"],
         "//build/bazel/platforms/os_arch:linux_bionic_x86_64": ["linux_bionic_x86_64_src.c"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticProductVariableSelects(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static product variable selects",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static product variable selects",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
@@ -1301,13 +1137,11 @@
         cflags: ["-Wbinder32bit"],
       },
     },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"copts": `select({
         "//build/bazel/product_variables:binder32bit": ["-Wbinder32bit"],
         "//conditions:default": [],
     }) + select({
@@ -1316,20 +1150,17 @@
     }) + select({
         "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    srcs_c = ["common.c"],
-)`},
+    })`,
+				"srcs_c": `["common.c"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticProductVariableArchSpecificSelects(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static arch-specific product variable selects",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static arch-specific product variable selects",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
@@ -1339,40 +1170,38 @@
         cflags: ["-Wmalloc_not_svelte"],
       },
     },
-		arch: {
-				arm64: {
-						product_variables: {
-								malloc_not_svelte: {
-										cflags: ["-Warm64_malloc_not_svelte"],
-								},
-						},
-				},
-		},
-		multilib: {
-				lib32: {
-						product_variables: {
-								malloc_not_svelte: {
-										cflags: ["-Wlib32_malloc_not_svelte"],
-								},
-						},
-				},
-		},
-		target: {
-				android: {
-						product_variables: {
-								malloc_not_svelte: {
-										cflags: ["-Wandroid_malloc_not_svelte"],
-								},
-						},
-				}
-		},
+    arch: {
+        arm64: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Warm64_malloc_not_svelte"],
+                },
+            },
+        },
+    },
+    multilib: {
+        lib32: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Wlib32_malloc_not_svelte"],
+                },
+            },
+        },
+    },
+    target: {
+        android: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Wandroid_malloc_not_svelte"],
+                },
+            },
+        }
+    },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"copts": `select({
         "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
         "//conditions:default": [],
     }) + select({
@@ -1387,20 +1216,17 @@
     }) + select({
         "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    srcs_c = ["common.c"],
-)`},
+    })`,
+				"srcs_c": `["common.c"]`,
+			}),
+		},
 	})
 }
 
 func TestCcLibraryStaticProductVariableStringReplacement(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static product variable string replacement",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		filesystem:                         map[string]string{},
+		description: "cc_library_static product variable string replacement",
+		filesystem:  map[string]string{},
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
@@ -1410,86 +1236,65 @@
           asflags: ["-DPLATFORM_SDK_VERSION=%d"],
       },
     },
+    include_build_directory: false,
 } `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    asflags = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"asflags": `select({
         "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
-    }),
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    srcs_as = ["common.S"],
-)`},
+    })`,
+				"srcs_as": `["common.S"]`,
+			}),
+		},
 	})
 }
 
 func TestStaticLibrary_SystemSharedLibsRootEmpty(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_lib empty root",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static system_shared_lib empty root",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "root_empty",
-	  system_shared_libs: [],
+    system_shared_libs: [],
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "root_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = [],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "root_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
 	})
 }
 
 func TestStaticLibrary_SystemSharedLibsStaticEmpty(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_lib empty static default",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static system_shared_lib empty static default",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_defaults {
     name: "static_empty_defaults",
     static: {
-				system_shared_libs: [],
-		},
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
 }
 cc_library_static {
     name: "static_empty",
-	  defaults: ["static_empty_defaults"],
+    defaults: ["static_empty_defaults"],
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "static_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = [],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "static_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
 	})
 }
 
 func TestStaticLibrary_SystemSharedLibsBionicEmpty(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_lib empty for bionic variant",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static system_shared_lib empty for bionic variant",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "target_bionic_empty",
@@ -1498,17 +1303,14 @@
             system_shared_libs: [],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "target_bionic_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = [],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_bionic_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
 	})
 }
 
@@ -1518,10 +1320,7 @@
 	// only for linux_bionic, but `android` had `["libc", "libdl", "libm"].
 	// b/195791252 tracks the fix.
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_lib empty for linux_bionic variant",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		description: "cc_library_static system_shared_lib empty for linux_bionic variant",
 		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "target_linux_bionic_empty",
@@ -1530,29 +1329,22 @@
             system_shared_libs: [],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "target_linux_bionic_empty",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = [],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_linux_bionic_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
 	})
 }
 
 func TestStaticLibrary_SystemSharedLibsBionic(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_libs set for bionic variant",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		blueprint: soongCcLibraryStaticPreamble + `
-cc_library{name: "libc"}
-
+		description: "cc_library_static system_shared_libs set for bionic variant",
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library", "libc") + `
 cc_library_static {
     name: "target_bionic",
     target: {
@@ -1560,54 +1352,84 @@
             system_shared_libs: ["libc"],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "target_bionic",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = select({
-        "//build/bazel/platforms/os:bionic": [":libc"],
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_bionic", attrNameToString{
+				"system_dynamic_deps": `select({
+        "//build/bazel/platforms/os:android": [":libc"],
+        "//build/bazel/platforms/os:linux_bionic": [":libc"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
 	})
 }
 
 func TestStaticLibrary_SystemSharedLibsLinuxRootAndLinuxBionic(t *testing.T) {
 	runCcLibraryStaticTestCase(t, bp2buildTestCase{
-		description:                        "cc_library_static system_shared_libs set for root and linux_bionic variant",
-		moduleTypeUnderTest:                "cc_library_static",
-		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-		blueprint: soongCcLibraryStaticPreamble + `
-cc_library{name: "libc"}
-cc_library{name: "libm"}
-
+		description: "cc_library_static system_shared_libs set for root and linux_bionic variant",
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library", "libc") +
+			simpleModuleDoNotConvertBp2build("cc_library", "libm") + `
 cc_library_static {
     name: "target_linux_bionic",
-		system_shared_libs: ["libc"],
+    system_shared_libs: ["libc"],
     target: {
         linux_bionic: {
             system_shared_libs: ["libm"],
         },
     },
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_library_static(
-    name = "target_linux_bionic",
-    copts = [
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    linkstatic = True,
-    system_dynamic_deps = [":libc"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_linux_bionic", attrNameToString{
+				"system_dynamic_deps": `[":libc"] + select({
         "//build/bazel/platforms/os:linux_bionic": [":libm"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticProto(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_static {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		canonical_path_from_root: false,
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticUseVersionLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_static {
+	name: "foo",
+	use_version_lib: true,
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo", attrNameToString{
+				"use_version_lib": "True",
+			}),
+		},
 	})
 }
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 9ac28a5..0a6c317 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -28,15 +28,14 @@
 
 func runCcObjectTestCase(t *testing.T, tc bp2buildTestCase) {
 	t.Helper()
+	(&tc).moduleTypeUnderTest = "cc_object"
+	(&tc).moduleTypeUnderTestFactory = cc.ObjectFactory
 	runBp2BuildTestCase(t, registerCcObjectModuleTypes, tc)
 }
 
 func TestCcObjectSimple(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "simple cc_object generates cc_object with include header dep",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "simple cc_object generates cc_object with include header dep",
 		filesystem: map[string]string{
 			"a/b/foo.h":     "",
 			"a/b/bar.h":     "",
@@ -58,34 +57,30 @@
     exclude_srcs: ["a/b/exclude.c"],
 }
 `,
-		expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `[
         "-fno-addrsig",
         "-Wno-gcc-compat",
         "-Wall",
         "-Werror",
-        "-Iinclude",
-        "-I$(BINDIR)/include",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["a/b/c.c"],
-)`,
+    ]`,
+				"local_includes": `[
+        "include",
+        ".",
+    ]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
 func TestCcObjectDefaults(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "simple cc_object with defaults",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 		blueprint: `cc_object {
     name: "foo",
     system_shared_libs: [],
-    local_include_dirs: ["include"],
     srcs: [
         "a/b/*.h",
         "a/b/c.c"
@@ -102,35 +97,26 @@
 cc_defaults {
     name: "foo_bar_defaults",
     cflags: [
-        "-Wno-gcc-compat",
-        "-Wall",
         "-Werror",
     ],
 }
 `,
-		expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = [
-        "-Wno-gcc-compat",
-        "-Wall",
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `[
         "-Werror",
         "-fno-addrsig",
-        "-Iinclude",
-        "-I$(BINDIR)/include",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["a/b/c.c"],
-)`,
+    ]`,
+				"local_includes":      `["."]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		}})
 }
 
 func TestCcObjectCcObjetDepsInObjs(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object with cc_object deps in objs props",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object with cc_object deps in objs props",
 		filesystem: map[string]string{
 			"a/b/c.c": "",
 			"x/y/z.c": "",
@@ -140,42 +126,34 @@
     system_shared_libs: [],
     srcs: ["a/b/c.c"],
     objs: ["bar"],
+    include_build_directory: false,
 }
 
 cc_object {
     name: "bar",
     system_shared_libs: [],
     srcs: ["x/y/z.c"],
+    include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_object(
-    name = "bar",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    srcs = ["x/y/z.c"],
-)`, `cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ],
-    deps = [":bar"],
-    srcs = ["a/b/c.c"],
-)`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "bar", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"srcs":                `["x/y/z.c"]`,
+				"system_dynamic_deps": `[]`,
+			}), makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"deps":                `[":bar"]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
 func TestCcObjectIncludeBuildDirFalse(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object with include_build_dir: false",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object with include_build_dir: false",
 		filesystem: map[string]string{
 			"a/b/c.c": "",
 			"x/y/z.c": "",
@@ -187,21 +165,19 @@
     include_build_directory: false,
 }
 `,
-		expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = ["-fno-addrsig"],
-    srcs = ["a/b/c.c"],
-)`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
 func TestCcObjectProductVariable(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object with product variable",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object with product variable",
 		blueprint: `cc_object {
     name: "foo",
     system_shared_libs: [],
@@ -214,25 +190,23 @@
     srcs: ["src.S"],
 }
 `,
-		expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    asflags = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"asflags": `select({
         "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
-    }),
-    copts = ["-fno-addrsig"],
-    srcs_as = ["src.S"],
-)`,
+    })`,
+				"copts":               `["-fno-addrsig"]`,
+				"srcs_as":             `["src.S"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
 func TestCcObjectCflagsOneArch(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object setting cflags for one arch",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object setting cflags for one arch",
 		blueprint: `cc_object {
     name: "foo",
     system_shared_libs: [],
@@ -245,34 +219,28 @@
             srcs: ["arch/arm/file.cpp"], // label list
         },
     },
+    include_build_directory: false,
 }
 `,
 		expectedBazelTargets: []string{
-			`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"] + select({
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//conditions:default": [],
-    }),
-    srcs = ["a.cpp"] + select({
+    })`,
+				"srcs": `["a.cpp"] + select({
         "//build/bazel/platforms/arch:arm": ["arch/arm/file.cpp"],
         "//conditions:default": [],
-    }),
-)`,
+    })`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
 func TestCcObjectCflagsFourArch(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object setting cflags for 4 architectures",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object setting cflags for 4 architectures",
 		blueprint: `cc_object {
     name: "foo",
     system_shared_libs: [],
@@ -295,72 +263,157 @@
             cflags: ["-Wall"],
         },
     },
+    include_build_directory: false,
 }
 `,
 		expectedBazelTargets: []string{
-			`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"] + select({
         "//build/bazel/platforms/arch:arm": ["-Wall"],
         "//build/bazel/platforms/arch:arm64": ["-Wall"],
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//build/bazel/platforms/arch:x86_64": ["-fPIC"],
         "//conditions:default": [],
-    }),
-    srcs = ["base.cpp"] + select({
+    })`,
+				"srcs": `["base.cpp"] + select({
         "//build/bazel/platforms/arch:arm": ["arm.cpp"],
         "//build/bazel/platforms/arch:arm64": ["arm64.cpp"],
         "//build/bazel/platforms/arch:x86": ["x86.cpp"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
-    }),
-)`,
+    })`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
 	})
 }
 
-func TestCcObjectCflagsMultiOs(t *testing.T) {
+func TestCcObjectLinkerScript(t *testing.T) {
 	runCcObjectTestCase(t, bp2buildTestCase{
-		description:                        "cc_object setting cflags for multiple OSes",
-		moduleTypeUnderTest:                "cc_object",
-		moduleTypeUnderTestFactory:         cc.ObjectFactory,
-		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		description: "cc_object setting linker_script",
 		blueprint: `cc_object {
     name: "foo",
-    system_shared_libs: [],
     srcs: ["base.cpp"],
-    target: {
-        android: {
-            cflags: ["-fPIC"],
-        },
-        windows: {
-            cflags: ["-fPIC"],
-        },
-        darwin: {
-            cflags: ["-Wall"],
-        },
-    },
+    linker_script: "bunny.lds",
+    include_build_directory: false,
 }
 `,
 		expectedBazelTargets: []string{
-			`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-        "-I$(BINDIR)/.",
-    ] + select({
-        "//build/bazel/platforms/os:android": ["-fPIC"],
-        "//build/bazel/platforms/os:darwin": ["-Wall"],
-        "//build/bazel/platforms/os:windows": ["-fPIC"],
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":         `["-fno-addrsig"]`,
+				"linker_script": `"bunny.lds"`,
+				"srcs":          `["base.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcObjectDepsAndLinkerScriptSelects(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting deps and linker_script across archs",
+		blueprint: `cc_object {
+    name: "foo",
+    srcs: ["base.cpp"],
+    arch: {
+        x86: {
+            objs: ["x86_obj"],
+            linker_script: "x86.lds",
+        },
+        x86_64: {
+            objs: ["x86_64_obj"],
+            linker_script: "x86_64.lds",
+        },
+        arm: {
+            objs: ["arm_obj"],
+            linker_script: "arm.lds",
+        },
+    },
+    include_build_directory: false,
+}
+
+cc_object {
+    name: "x86_obj",
+    system_shared_libs: [],
+    srcs: ["x86.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+
+cc_object {
+    name: "x86_64_obj",
+    system_shared_libs: [],
+    srcs: ["x86_64.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+
+cc_object {
+    name: "arm_obj",
+    system_shared_libs: [],
+    srcs: ["arm.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"]`,
+				"deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_obj"],
+        "//build/bazel/platforms/arch:x86": [":x86_obj"],
+        "//build/bazel/platforms/arch:x86_64": [":x86_64_obj"],
         "//conditions:default": [],
-    }),
-    srcs = ["base.cpp"],
-)`,
+    })`,
+				"linker_script": `select({
+        "//build/bazel/platforms/arch:arm": "arm.lds",
+        "//build/bazel/platforms/arch:x86": "x86.lds",
+        "//build/bazel/platforms/arch:x86_64": "x86_64.lds",
+        "//conditions:default": None,
+    })`,
+				"srcs": `["base.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcObjectSelectOnLinuxAndBionicArchs(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting srcs based on linux and bionic archs",
+		blueprint: `cc_object {
+    name: "foo",
+    srcs: ["base.cpp"],
+    target: {
+        linux_arm64: {
+            srcs: ["linux_arm64.cpp",]
+        },
+        linux_x86: {
+            srcs: ["linux_x86.cpp",]
+        },
+        bionic_arm64: {
+            srcs: ["bionic_arm64.cpp",]
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"]`,
+				"srcs": `["base.cpp"] + select({
+        "//build/bazel/platforms/os_arch:android_arm64": [
+            "linux_arm64.cpp",
+            "bionic_arm64.cpp",
+        ],
+        "//build/bazel/platforms/os_arch:android_x86": ["linux_x86.cpp"],
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": [
+            "linux_arm64.cpp",
+            "bionic_arm64.cpp",
+        ],
+        "//build/bazel/platforms/os_arch:linux_glibc_x86": ["linux_x86.cpp"],
+        "//build/bazel/platforms/os_arch:linux_musl_x86": ["linux_x86.cpp"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
 	})
 }
diff --git a/bp2build/cc_prebuilt_library_shared_test.go b/bp2build/cc_prebuilt_library_shared_test.go
new file mode 100644
index 0000000..ef2fddc
--- /dev/null
+++ b/bp2build/cc_prebuilt_library_shared_test.go
@@ -0,0 +1,61 @@
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/cc"
+)
+
+func TestSharedPrebuiltLibrary(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library shared simple",
+			moduleTypeUnderTest:        "cc_prebuilt_library_shared",
+			moduleTypeUnderTestFactory: cc.PrebuiltSharedLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_shared {
+	name: "libtest",
+	srcs: ["libf.so"],
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `"libf.so"`,
+				}),
+			},
+		})
+}
+
+func TestSharedPrebuiltLibraryWithArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library shared with arch variance",
+			moduleTypeUnderTest:        "cc_prebuilt_library_shared",
+			moduleTypeUnderTestFactory: cc.PrebuiltSharedLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_shared {
+	name: "libtest",
+	arch: {
+		arm64: { srcs: ["libf.so"], },
+		arm: { srcs: ["libg.so"], },
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `select({
+        "//build/bazel/platforms/arch:arm": "libg.so",
+        "//build/bazel/platforms/arch:arm64": "libf.so",
+        "//conditions:default": None,
+    })`,
+				}),
+			},
+		})
+}
diff --git a/bp2build/compatibility.go b/bp2build/compatibility.go
deleted file mode 100644
index 5baa524..0000000
--- a/bp2build/compatibility.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package bp2build
-
-import (
-	"android/soong/bazel"
-	"fmt"
-)
-
-// Data from the code generation process that is used to improve compatibility
-// between build systems.
-type CodegenCompatLayer struct {
-	// A map from the original module name to the generated/handcrafted Bazel
-	// label for legacy build systems to be able to build a fully-qualified
-	// Bazel target from an unique module name.
-	NameToLabelMap map[string]string
-}
-
-// Log an entry of module name -> Bazel target label.
-func (compatLayer CodegenCompatLayer) AddNameToLabelEntry(name, label string) {
-	// The module name may be prefixed with bazel.BazelTargetModuleNamePrefix if
-	// generated from bp2build.
-	name = bazel.StripNamePrefix(name)
-	if existingLabel, ok := compatLayer.NameToLabelMap[name]; ok {
-		panic(fmt.Errorf(
-			"Module '%s' maps to more than one Bazel target label: %s, %s. "+
-				"This shouldn't happen. It probably indicates a bug with the bp2build internals.",
-			name,
-			existingLabel,
-			label))
-	}
-	compatLayer.NameToLabelMap[name] = label
-}
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 005f13d..c953259 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -48,6 +48,12 @@
 		}
 	}
 
+	// if there is a select, use the base value as the conditions default value
+	if len(ret) > 0 {
+		ret[bazel.ConditionsDefaultSelectKey] = value
+		value = reflect.Zero(value.Type())
+	}
+
 	return value, []selects{ret}
 }
 
@@ -89,7 +95,7 @@
 				continue
 			}
 			selectKey := axis.SelectKey(config)
-			if use, value := labelListSelectValue(selectKey, labels); use {
+			if use, value := labelListSelectValue(selectKey, labels, list.EmitEmptyList); use {
 				archSelects[selectKey] = value
 			}
 		}
@@ -101,8 +107,8 @@
 	return value, ret
 }
 
-func labelListSelectValue(selectKey string, list bazel.LabelList) (bool, reflect.Value) {
-	if selectKey == bazel.ConditionsDefaultSelectKey || len(list.Includes) > 0 {
+func labelListSelectValue(selectKey string, list bazel.LabelList, emitEmptyList bool) (bool, reflect.Value) {
+	if selectKey == bazel.ConditionsDefaultSelectKey || emitEmptyList || len(list.Includes) > 0 {
 		return true, reflect.ValueOf(list.Includes)
 	} else if len(list.Excludes) > 0 {
 		// if there is still an excludes -- we need to have an empty list for this select & use the
@@ -123,6 +129,7 @@
 	var value reflect.Value
 	var configurableAttrs []selects
 	var defaultSelectValue *string
+	var emitZeroValues bool
 	// If true, print the default attribute value, even if the attribute is zero.
 	shouldPrintDefault := false
 	switch list := v.(type) {
@@ -131,6 +138,7 @@
 		defaultSelectValue = &emptyBazelList
 	case bazel.LabelListAttribute:
 		value, configurableAttrs = getLabelListValues(list)
+		emitZeroValues = list.EmitEmptyList
 		defaultSelectValue = &emptyBazelList
 		if list.ForceSpecifyEmptyList && (!value.IsNil() || list.HasConfigurableValues()) {
 			shouldPrintDefault = true
@@ -148,7 +156,7 @@
 	var err error
 	ret := ""
 	if value.Kind() != reflect.Invalid {
-		s, err := prettyPrint(value, indent)
+		s, err := prettyPrint(value, indent, false) // never emit zero values for the base value
 		if err != nil {
 			return ret, err
 		}
@@ -157,7 +165,7 @@
 	}
 	// Convenience function to append selects components to an attribute value.
 	appendSelects := func(selectsData selects, defaultValue *string, s string) (string, error) {
-		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent)
+		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent, emitZeroValues)
 		if err != nil {
 			return "", err
 		}
@@ -184,7 +192,7 @@
 
 // prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
 // to construct a select map for any kind of attribute type.
-func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *string, indent int) (string, error) {
+func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *string, indent int, emitZeroValues bool) (string, error) {
 	if selectMap == nil {
 		return "", nil
 	}
@@ -196,11 +204,11 @@
 			continue
 		}
 		value := selectMap[selectKey]
-		if isZero(value) {
+		if isZero(value) && !emitZeroValues {
 			// Ignore zero values to not generate empty lists.
 			continue
 		}
-		s, err := prettyPrintSelectEntry(value, selectKey, indent)
+		s, err := prettyPrintSelectEntry(value, selectKey, indent, emitZeroValues)
 		if err != nil {
 			return "", err
 		}
@@ -221,7 +229,7 @@
 	ret += selects
 
 	// Handle the default condition
-	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent)
+	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent, emitZeroValues)
 	if err != nil {
 		return "", err
 	}
@@ -243,9 +251,9 @@
 
 // prettyPrintSelectEntry converts a reflect.Value into an entry in a select map
 // with a provided key.
-func prettyPrintSelectEntry(value reflect.Value, key string, indent int) (string, error) {
+func prettyPrintSelectEntry(value reflect.Value, key string, indent int, emitZeroValues bool) (string, error) {
 	s := makeIndent(indent + 1)
-	v, err := prettyPrint(value, indent+1)
+	v, err := prettyPrint(value, indent+1, emitZeroValues)
 	if err != nil {
 		return "", err
 	}
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 75bc2b4..81a4b26 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -1,12 +1,13 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/cc/config"
 	"fmt"
 	"reflect"
 	"strings"
 
+	"android/soong/android"
+	"android/soong/cc/config"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -16,29 +17,21 @@
 	Contents string
 }
 
-func CreateSoongInjectionFiles(compatLayer CodegenCompatLayer) []BazelFile {
+func CreateSoongInjectionFiles(cfg android.Config, metrics CodegenMetrics) []BazelFile {
 	var files []BazelFile
 
 	files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
-	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars()))
+	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars(cfg)))
 
-	files = append(files, newFile("module_name_to_label", GeneratedBuildFileName, nameToLabelAliases(compatLayer.NameToLabelMap)))
+	files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.convertedModules, "\n")))
+
+	files = append(files, newFile("product_config", "soong_config_variables.bzl", cfg.Bp2buildSoongConfigDefinitions.String()))
 
 	return files
 }
 
-func nameToLabelAliases(nameToLabelMap map[string]string) string {
-	ret := make([]string, len(nameToLabelMap))
-
-	for k, v := range nameToLabelMap {
-		// v is the fully qualified label rooted at '//'
-		ret = append(ret, fmt.Sprintf(
-			`alias(
-    name = "%s",
-    actual = "@%s",
-)`, k, v))
-	}
-	return strings.Join(ret, "\n\n")
+func convertedModules(convertedModules []string) string {
+	return strings.Join(convertedModules, "\n")
 }
 
 func CreateBazelFiles(
@@ -152,7 +145,7 @@
 }
 
 func shouldSkipStructField(field reflect.StructField) bool {
-	if field.PkgPath != "" {
+	if field.PkgPath != "" && !field.Anonymous {
 		// Skip unexported fields. Some properties are
 		// internal to Soong only, and these fields do not have PkgPath.
 		return true
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 56ea589..3e6d9e6 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -17,6 +17,8 @@
 import (
 	"sort"
 	"testing"
+
+	"android/soong/android"
 )
 
 type bazelFilepath struct {
@@ -80,7 +82,8 @@
 }
 
 func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
-	files := CreateSoongInjectionFiles(CodegenCompatLayer{})
+	testConfig := android.TestConfig("", make(map[string]string), "", make(map[string][]byte))
+	files := CreateSoongInjectionFiles(testConfig, CodegenMetrics{})
 
 	expectedFilePaths := []bazelFilepath{
 		{
@@ -92,8 +95,12 @@
 			basename: "constants.bzl",
 		},
 		{
-			dir:      "module_name_to_label",
-			basename: GeneratedBuildFileName,
+			dir:      "metrics",
+			basename: "converted_modules.txt",
+		},
+		{
+			dir:      "product_config",
+			basename: "soong_config_variables.bzl",
 		},
 	}
 
@@ -107,9 +114,5 @@
 		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
 			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
 		}
-
-		if expectedFile.basename != GeneratedBuildFileName && actualFile.Contents == "" {
-			t.Errorf("Contents of %s unexpected empty.", actualFile)
-		}
 	}
 }
diff --git a/bp2build/filegroup_conversion_test.go b/bp2build/filegroup_conversion_test.go
new file mode 100644
index 0000000..b43cf53
--- /dev/null
+++ b/bp2build/filegroup_conversion_test.go
@@ -0,0 +1,58 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"fmt"
+
+	"testing"
+)
+
+func runFilegroupTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "filegroup"
+	(&tc).moduleTypeUnderTestFactory = android.FileGroupFactory
+	runBp2BuildTestCase(t, registerFilegroupModuleTypes, tc)
+}
+
+func registerFilegroupModuleTypes(ctx android.RegistrationContext) {}
+
+func TestFilegroupSameNameAsFile_OneFile(t *testing.T) {
+	runFilegroupTestCase(t, bp2buildTestCase{
+		description: "filegroup - same name as file, with one file",
+		filesystem:  map[string]string{},
+		blueprint: `
+filegroup {
+    name: "foo",
+    srcs: ["foo"],
+}
+`,
+		expectedBazelTargets: []string{}})
+}
+
+func TestFilegroupSameNameAsFile_MultipleFiles(t *testing.T) {
+	runFilegroupTestCase(t, bp2buildTestCase{
+		description: "filegroup - same name as file, with multiple files",
+		filesystem:  map[string]string{},
+		blueprint: `
+filegroup {
+	name: "foo",
+	srcs: ["foo", "bar"],
+}
+`,
+		expectedErr: fmt.Errorf("filegroup 'foo' cannot contain a file with the same name"),
+	})
+}
diff --git a/bp2build/genrule_conversion_test.go b/bp2build/genrule_conversion_test.go
new file mode 100644
index 0000000..fd631a5
--- /dev/null
+++ b/bp2build/genrule_conversion_test.go
@@ -0,0 +1,363 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/genrule"
+	"testing"
+)
+
+func registerGenruleModuleTypes(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("genrule_defaults", func() android.Module { return genrule.DefaultsFactory() })
+}
+
+func runGenruleTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "genrule"
+	(&tc).moduleTypeUnderTestFactory = genrule.GenRuleFactory
+	runBp2BuildTestCase(t, registerGenruleModuleTypes, tc)
+}
+
+func TestGenruleBp2Build(t *testing.T) {
+	otherGenruleBp := map[string]string{
+		"other/Android.bp": `genrule {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+}
+genrule {
+    name: "other.tool",
+    out: ["other_tool.out"],
+    srcs: ["other_tool.in"],
+    cmd: "cp $(in) $(out)",
+}`,
+	}
+
+	testCases := []bp2buildTestCase{
+		{
+			description: "genrule with command line variable replacements",
+			blueprint: `genrule {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+
+genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tool"],
+    cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":   `"$(location :foo.tool) --genDir=$(GENDIR) arg $(SRCS) $(OUTS)"`,
+					"outs":  `["foo.out"]`,
+					"srcs":  `["foo.in"]`,
+					"tools": `[":foo.tool"]`,
+				}),
+				makeBazelTarget("genrule", "foo.tool", attrNameToString{
+					"cmd":  `"cp $(SRCS) $(OUTS)"`,
+					"outs": `["foo_tool.out"]`,
+					"srcs": `["foo_tool.in"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule using $(locations :label)",
+			blueprint: `genrule {
+    name: "foo.tools",
+    out: ["foo_tool.out", "foo_tool2.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+
+genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tools"],
+    cmd: "$(locations :foo.tools) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":   `"$(locations :foo.tools) -s $(OUTS) $(SRCS)"`,
+					"outs":  `["foo.out"]`,
+					"srcs":  `["foo.in"]`,
+					"tools": `[":foo.tools"]`,
+				}),
+				makeBazelTarget("genrule", "foo.tools", attrNameToString{
+					"cmd": `"cp $(SRCS) $(OUTS)"`,
+					"outs": `[
+        "foo_tool.out",
+        "foo_tool2.out",
+    ]`,
+					"srcs": `["foo_tool.in"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule using $(locations //absolute:label)",
+			blueprint: `genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+					"outs":  `["foo.out"]`,
+					"srcs":  `["foo.in"]`,
+					"tools": `["//other:foo.tool"]`,
+				}),
+			},
+			filesystem: otherGenruleBp,
+		},
+		{
+			description: "genrule srcs using $(locations //absolute:label)",
+			blueprint: `genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: [":other.tool"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)"`,
+					"outs":  `["foo.out"]`,
+					"srcs":  `["//other:other.tool"]`,
+					"tools": `["//other:foo.tool"]`,
+				}),
+			},
+			filesystem: otherGenruleBp,
+		},
+		{
+			description: "genrule using $(location) label should substitute first tool label automatically",
+			blueprint: `genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool", ":other.tool"],
+    cmd: "$(location) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":  `"$(location //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+					"outs": `["foo.out"]`,
+					"srcs": `["foo.in"]`,
+					"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+				}),
+			},
+			filesystem: otherGenruleBp,
+		},
+		{
+			description: "genrule using $(locations) label should substitute first tool label automatically",
+			blueprint: `genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tool", ":other.tool"],
+    cmd: "$(locations) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":  `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+					"outs": `["foo.out"]`,
+					"srcs": `["foo.in"]`,
+					"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+				}),
+			},
+			filesystem: otherGenruleBp,
+		},
+		{
+			description: "genrule without tools or tool_files can convert successfully",
+			blueprint: `genrule {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "foo", attrNameToString{
+					"cmd":  `"cp $(SRCS) $(OUTS)"`,
+					"outs": `["foo.out"]`,
+					"srcs": `["foo.in"]`,
+				}),
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			runGenruleTestCase(t, testCase)
+		})
+	}
+}
+
+func TestBp2BuildInlinesDefaults(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description: "genrule applies properties from a genrule_defaults dependency if not specified",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults",
+    cmd: "do-something $(in) $(out)",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd":  `"do-something $(SRCS) $(OUTS)"`,
+					"outs": `["out"]`,
+					"srcs": `["in1"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults",
+    out: ["out-from-defaults"],
+    srcs: ["in-from-defaults"],
+    cmd: "cmd-from-defaults",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+    cmd: "do-something $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd": `"do-something $(SRCS) $(OUTS)"`,
+					"outs": `[
+        "out-from-defaults",
+        "out",
+    ]`,
+					"srcs": `[
+        "in-from-defaults",
+        "in1",
+    ]`,
+				}),
+			},
+		},
+		{
+			description: "genrule applies properties from list of genrule_defaults",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults1",
+    cmd: "cp $(in) $(out)",
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    srcs: ["in1"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1", "gen_defaults2"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd":  `"cp $(SRCS) $(OUTS)"`,
+					"outs": `["out"]`,
+					"srcs": `["in1"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule applies properties from genrule_defaults transitively",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults1",
+    defaults: ["gen_defaults2"],
+    cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    defaults: ["gen_defaults3"],
+    cmd: "cmd2 $(in) $(out)",
+    out: ["out-from-2"],
+    srcs: ["in1"],
+}
+
+genrule_defaults {
+    name: "gen_defaults3",
+    out: ["out-from-3"],
+    srcs: ["srcs-from-3"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd": `"cmd1 $(SRCS) $(OUTS)"`,
+					"outs": `[
+        "out-from-3",
+        "out-from-2",
+        "out",
+    ]`,
+					"srcs": `[
+        "srcs-from-3",
+        "in1",
+    ]`,
+				}),
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			runGenruleTestCase(t, testCase)
+		})
+	}
+}
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
new file mode 100644
index 0000000..96b8958
--- /dev/null
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -0,0 +1,63 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/java"
+)
+
+func runJavaBinaryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_binary_host"
+	(&tc).moduleTypeUnderTestFactory = java.BinaryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("cc_library_host_shared", cc.LibraryHostSharedFactory)
+	}, tc)
+}
+
+var fs = map[string]string{
+	"test.mf": "Main-Class: com.android.test.MainClass",
+	"other/Android.bp": `cc_library_host_shared {
+    name: "jni-lib-1",
+    stl: "none",
+}`,
+}
+
+func TestJavaBinaryHost(t *testing.T) {
+	runJavaBinaryHostTestCase(t, bp2buildTestCase{
+		description: "java_binary_host with srcs, exclude_srcs, jni_libs and manifest.",
+		filesystem:  fs,
+		blueprint: `java_binary_host {
+    name: "java-binary-host-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    manifest: "test.mf",
+    jni_libs: ["jni-lib-1"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
+				"srcs":       `["a.java"]`,
+				"main_class": `"com.android.test.MainClass"`,
+				"deps":       `["//other:jni-lib-1"]`,
+				"jvm_flags":  `["-Djava.library.path=$${RUNPATH}other"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
new file mode 100644
index 0000000..5c65ec2
--- /dev/null
+++ b/bp2build/java_library_conversion_test.go
@@ -0,0 +1,57 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_library"
+	(&tc).moduleTypeUnderTestFactory = java.LibraryFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestJavaLibrary(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		description: "java_library with srcs, exclude_srcs and libs",
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[":java-lib-2"]`,
+			}),
+			makeBazelTarget("java_library", "java-lib-2", attrNameToString{
+				"srcs": `["b.java"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
new file mode 100644
index 0000000..6ac82db
--- /dev/null
+++ b/bp2build/java_library_host_conversion_test.go
@@ -0,0 +1,57 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaLibraryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_library_host"
+	(&tc).moduleTypeUnderTestFactory = java.LibraryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestJavaLibraryHost(t *testing.T) {
+	runJavaLibraryHostTestCase(t, bp2buildTestCase{
+		description: "java_library_host with srcs, exclude_srcs and libs",
+		blueprint: `java_library_host {
+    name: "java-lib-host-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    libs: ["java-lib-host-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library_host {
+    name: "java-lib-host-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-host-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[":java-lib-host-2"]`,
+			}),
+			makeBazelTarget("java_library", "java-lib-host-2", attrNameToString{
+				"srcs": `["c.java"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index 65b06c6..68ac544 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -1,34 +1,137 @@
 package bp2build
 
 import (
-	"android/soong/android"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/shared"
+	"android/soong/ui/metrics/bp2build_metrics_proto"
 )
 
 // Simple metrics struct to collect information about a Blueprint to BUILD
 // conversion process.
 type CodegenMetrics struct {
-	// Total number of Soong/Blueprint modules
-	TotalModuleCount int
+	// Total number of Soong modules converted to generated targets
+	generatedModuleCount uint64
+
+	// Total number of Soong modules converted to handcrafted targets
+	handCraftedModuleCount uint64
+
+	// Total number of unconverted Soong modules
+	unconvertedModuleCount uint64
 
 	// Counts of generated Bazel targets per Bazel rule class
-	RuleClassCount map[string]int
+	ruleClassCount map[string]uint64
 
-	// Total number of handcrafted targets
-	handCraftedTargetCount int
+	// List of modules with unconverted deps
+	// NOTE: NOT in the .proto
+	moduleWithUnconvertedDepsMsgs []string
+
+	// List of converted modules
+	convertedModules []string
+}
+
+// Serialize returns the protoized version of CodegenMetrics: bp2build_metrics_proto.Bp2BuildMetrics
+func (metrics *CodegenMetrics) Serialize() bp2build_metrics_proto.Bp2BuildMetrics {
+	return bp2build_metrics_proto.Bp2BuildMetrics{
+		GeneratedModuleCount:   metrics.generatedModuleCount,
+		HandCraftedModuleCount: metrics.handCraftedModuleCount,
+		UnconvertedModuleCount: metrics.unconvertedModuleCount,
+		RuleClassCount:         metrics.ruleClassCount,
+		ConvertedModules:       metrics.convertedModules,
+	}
 }
 
 // Print the codegen metrics to stdout.
-func (metrics CodegenMetrics) Print() {
-	generatedTargetCount := 0
-	for _, ruleClass := range android.SortedStringKeys(metrics.RuleClassCount) {
-		count := metrics.RuleClassCount[ruleClass]
+func (metrics *CodegenMetrics) Print() {
+	generatedTargetCount := uint64(0)
+	for _, ruleClass := range android.SortedStringKeys(metrics.ruleClassCount) {
+		count := metrics.ruleClassCount[ruleClass]
 		fmt.Printf("[bp2build] %s: %d targets\n", ruleClass, count)
 		generatedTargetCount += count
 	}
 	fmt.Printf(
-		"[bp2build] Generated %d total BUILD targets and included %d handcrafted BUILD targets from %d Android.bp modules.\n",
+		"[bp2build] Converted %d Android.bp modules to %d total generated BUILD targets. Included %d handcrafted BUILD targets. There are %d total Android.bp modules.\n%d converted modules have unconverted deps: \n\t%s",
+		metrics.generatedModuleCount,
 		generatedTargetCount,
-		metrics.handCraftedTargetCount,
-		metrics.TotalModuleCount)
+		metrics.handCraftedModuleCount,
+		metrics.TotalModuleCount(),
+		len(metrics.moduleWithUnconvertedDepsMsgs),
+		strings.Join(metrics.moduleWithUnconvertedDepsMsgs, "\n\t"))
+}
+
+const bp2buildMetricsFilename = "bp2build_metrics.pb"
+
+// fail prints $PWD to stderr, followed by the given printf string and args (vals),
+// then the given alert, and then exits with 1 for failure
+func fail(err error, alertFmt string, vals ...interface{}) {
+	cwd, wderr := os.Getwd()
+	if wderr != nil {
+		cwd = "FAILED TO GET $PWD: " + wderr.Error()
+	}
+	fmt.Fprintf(os.Stderr, "\nIn "+cwd+":\n"+alertFmt+"\n"+err.Error()+"\n", vals...)
+	os.Exit(1)
+}
+
+// Write the bp2build-protoized codegen metrics into the given directory
+func (metrics *CodegenMetrics) Write(dir string) {
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		// The metrics dir doesn't already exist, so create it (and parents)
+		if err := os.MkdirAll(dir, 0755); err != nil { // rx for all; w for user
+			fail(err, "Failed to `mkdir -p` %s", dir)
+		}
+	} else if err != nil {
+		fail(err, "Failed to `stat` %s", dir)
+	}
+	metricsFile := filepath.Join(dir, bp2buildMetricsFilename)
+	if err := metrics.dump(metricsFile); err != nil {
+		fail(err, "Error outputting %s", metricsFile)
+	}
+	if _, err := os.Stat(metricsFile); err != nil {
+		fail(err, "MISSING BP2BUILD METRICS OUTPUT: Failed to `stat` %s", metricsFile)
+	} else {
+		fmt.Printf("\nWrote bp2build metrics to: %s\n", metricsFile)
+	}
+}
+
+func (metrics *CodegenMetrics) IncrementRuleClassCount(ruleClass string) {
+	metrics.ruleClassCount[ruleClass] += 1
+}
+
+func (metrics *CodegenMetrics) IncrementUnconvertedCount() {
+	metrics.unconvertedModuleCount += 1
+}
+
+func (metrics *CodegenMetrics) TotalModuleCount() uint64 {
+	return metrics.handCraftedModuleCount +
+		metrics.generatedModuleCount +
+		metrics.unconvertedModuleCount
+}
+
+// Dump serializes the metrics to the given filename
+func (metrics *CodegenMetrics) dump(filename string) (err error) {
+	ser := metrics.Serialize()
+	return shared.Save(&ser, filename)
+}
+
+type ConversionType int
+
+const (
+	Generated ConversionType = iota
+	Handcrafted
+)
+
+func (metrics *CodegenMetrics) AddConvertedModule(moduleName string, conversionType ConversionType) {
+	// Undo prebuilt_ module name prefix modifications
+	moduleName = android.RemoveOptionalPrebuiltPrefix(moduleName)
+	metrics.convertedModules = append(metrics.convertedModules, moduleName)
+
+	if conversionType == Handcrafted {
+		metrics.handCraftedModuleCount += 1
+	} else if conversionType == Generated {
+		metrics.generatedModuleCount += 1
+	}
 }
diff --git a/bp2build/performance_test.go b/bp2build/performance_test.go
index 3283952..c4bbae2 100644
--- a/bp2build/performance_test.go
+++ b/bp2build/performance_test.go
@@ -29,6 +29,10 @@
 	"testing"
 )
 
+const (
+	performance_test_dir = "."
+)
+
 func genCustomModule(i int, convert bool) string {
 	var conversionString string
 	if convert {
@@ -76,34 +80,83 @@
 	return strings.Join(bp, "\n\n")
 }
 
+type testConfig struct {
+	config     android.Config
+	ctx        *android.TestContext
+	codegenCtx *CodegenContext
+}
+
+func (tc testConfig) parse() []error {
+	_, errs := tc.ctx.ParseFileList(performance_test_dir, []string{"Android.bp"})
+	return errs
+}
+
+func (tc testConfig) resolveDependencies() []error {
+	_, errs := tc.ctx.ResolveDependencies(tc.config)
+	return errs
+}
+
+func (tc testConfig) convert() {
+	generateBazelTargetsForDir(tc.codegenCtx, performance_test_dir)
+}
+
+func setup(builddir string, tcSize float64) testConfig {
+	config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
+	ctx := android.NewTestContext(config)
+
+	registerCustomModuleForBp2buildConversion(ctx)
+	codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+	return testConfig{
+		config,
+		ctx,
+		codegenCtx,
+	}
+}
+
 var pctToConvert = []float64{0.0, 0.01, 0.05, 0.10, 0.25, 0.5, 0.75, 1.0}
 
+// This is not intended to test performance, but to verify performance infra continues to work
+func TestConvertManyModulesFull(t *testing.T) {
+	for _, tcSize := range pctToConvert {
+
+		t.Run(fmt.Sprintf("pctConverted %f", tcSize), func(t *testing.T) {
+			testConfig := setup(buildDir, tcSize)
+
+			errs := testConfig.parse()
+			if len(errs) > 0 {
+				t.Fatalf("Unexpected errors: %s", errs)
+			}
+
+			errs = testConfig.resolveDependencies()
+			if len(errs) > 0 {
+				t.Fatalf("Unexpected errors: %s", errs)
+			}
+
+			testConfig.convert()
+		})
+	}
+}
+
 func BenchmarkManyModulesFull(b *testing.B) {
-	dir := "."
 	for _, tcSize := range pctToConvert {
 
 		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
 			for n := 0; n < b.N; n++ {
 				b.StopTimer()
-				// setup we don't want to measure
-				config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
-				ctx := android.NewTestContext(config)
-
-				registerCustomModuleForBp2buildConversion(ctx)
-				codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+				testConfig := setup(buildDir, tcSize)
 
 				b.StartTimer()
-				_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+				errs := testConfig.parse()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
-				_, errs = ctx.ResolveDependencies(config)
+				errs = testConfig.resolveDependencies()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
-				generateBazelTargetsForDir(codegenCtx, dir)
+				testConfig.convert()
 				b.StopTimer()
 			}
 		})
@@ -111,63 +164,53 @@
 }
 
 func BenchmarkManyModulesResolveDependencies(b *testing.B) {
-	dir := "."
 	for _, tcSize := range pctToConvert {
 
 		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
 			for n := 0; n < b.N; n++ {
 				b.StopTimer()
 				// setup we don't want to measure
-				config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
-				ctx := android.NewTestContext(config)
+				testConfig := setup(buildDir, tcSize)
 
-				registerCustomModuleForBp2buildConversion(ctx)
-				codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-
-				_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+				errs := testConfig.parse()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
 				b.StartTimer()
-				_, errs = ctx.ResolveDependencies(config)
+				errs = testConfig.resolveDependencies()
 				b.StopTimer()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
-				generateBazelTargetsForDir(codegenCtx, dir)
+				testConfig.convert()
 			}
 		})
 	}
 }
 
 func BenchmarkManyModulesGenerateBazelTargetsForDir(b *testing.B) {
-	dir := "."
 	for _, tcSize := range pctToConvert {
 
 		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
 			for n := 0; n < b.N; n++ {
 				b.StopTimer()
 				// setup we don't want to measure
-				config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
-				ctx := android.NewTestContext(config)
+				testConfig := setup(buildDir, tcSize)
 
-				registerCustomModuleForBp2buildConversion(ctx)
-				codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-
-				_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+				errs := testConfig.parse()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
-				_, errs = ctx.ResolveDependencies(config)
+				errs = testConfig.resolveDependencies()
 				if len(errs) > 0 {
 					b.Fatalf("Unexpected errors: %s", errs)
 				}
 
 				b.StartTimer()
-				generateBazelTargetsForDir(codegenCtx, dir)
+				testConfig.convert()
 				b.StopTimer()
 			}
 		})
diff --git a/bp2build/prebuilt_etc_conversion_test.go b/bp2build/prebuilt_etc_conversion_test.go
index 4e25d1b..5065893 100644
--- a/bp2build/prebuilt_etc_conversion_test.go
+++ b/bp2build/prebuilt_etc_conversion_test.go
@@ -23,6 +23,8 @@
 
 func runPrebuiltEtcTestCase(t *testing.T, tc bp2buildTestCase) {
 	t.Helper()
+	(&tc).moduleTypeUnderTest = "prebuilt_etc"
+	(&tc).moduleTypeUnderTestFactory = etc.PrebuiltEtcFactory
 	runBp2BuildTestCase(t, registerPrebuiltEtcModuleTypes, tc)
 }
 
@@ -31,11 +33,8 @@
 
 func TestPrebuiltEtcSimple(t *testing.T) {
 	runPrebuiltEtcTestCase(t, bp2buildTestCase{
-		description:                        "prebuilt_etc - simple example",
-		moduleTypeUnderTest:                "prebuilt_etc",
-		moduleTypeUnderTestFactory:         etc.PrebuiltEtcFactory,
-		moduleTypeUnderTestBp2BuildMutator: etc.PrebuiltEtcBp2Build,
-		filesystem:                         map[string]string{},
+		description: "prebuilt_etc - simple example",
+		filesystem:  map[string]string{},
 		blueprint: `
 prebuilt_etc {
     name: "apex_tz_version",
@@ -45,11 +44,45 @@
     installable: false,
 }
 `,
-		expectedBazelTargets: []string{`prebuilt_etc(
-    name = "apex_tz_version",
-    filename = "tz_version",
-    installable = False,
-    src = "version/tz_version",
-    sub_dir = "tz",
-)`}})
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_etc", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src":         `"version/tz_version"`,
+				"sub_dir":     `"tz"`,
+			})}})
+}
+
+func TestPrebuiltEtcArchVariant(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description: "prebuilt_etc - arch variant",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    sub_dir: "tz",
+    installable: false,
+    arch: {
+      arm: {
+        src: "arm",
+      },
+      arm64: {
+        src: "arm64",
+      },
+    }
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_etc", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src": `select({
+        "//build/bazel/platforms/arch:arm": "arm",
+        "//build/bazel/platforms/arch:arm64": "arm64",
+        "//conditions:default": "version/tz_version",
+    })`,
+				"sub_dir": `"tz"`,
+			})}})
 }
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 6f6fc11..40c8ba1 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -7,7 +7,8 @@
 	"android/soong/python"
 )
 
-func runBp2BuildTestCaseWithLibs(t *testing.T, tc bp2buildTestCase) {
+func runBp2BuildTestCaseWithPythonLibraries(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
 	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
 		ctx.RegisterModuleType("python_library", python.PythonLibraryFactory)
 		ctx.RegisterModuleType("python_library_host", python.PythonLibraryHostFactory)
@@ -15,11 +16,10 @@
 }
 
 func TestPythonBinaryHostSimple(t *testing.T) {
-	runBp2BuildTestCaseWithLibs(t, bp2buildTestCase{
-		description:                        "simple python_binary_host converts to a native py_binary",
-		moduleTypeUnderTest:                "python_binary_host",
-		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+	runBp2BuildTestCaseWithPythonLibraries(t, bp2buildTestCase{
+		description:                "simple python_binary_host converts to a native py_binary",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
 		filesystem: map[string]string{
 			"a.py":           "",
 			"b/c.py":         "",
@@ -39,29 +39,28 @@
     python_library_host {
       name: "bar",
       srcs: ["b/e.py"],
-      bazel_module: { bp2build_available: true },
+      bazel_module: { bp2build_available: false },
     }`,
-		expectedBazelTargets: []string{`py_binary(
-    name = "foo",
-    data = ["files/data.txt"],
-    deps = [":bar"],
-    main = "a.py",
-    srcs = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"data": `["files/data.txt"]`,
+				"deps": `[":bar"]`,
+				"main": `"a.py"`,
+				"srcs": `[
         "a.py",
         "b/c.py",
         "b/d.py",
-    ],
-)`,
+    ]`,
+			}),
 		},
 	})
 }
 
 func TestPythonBinaryHostPy2(t *testing.T) {
 	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
-		description:                        "py2 python_binary_host",
-		moduleTypeUnderTest:                "python_binary_host",
-		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		description:                "py2 python_binary_host",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
 		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
@@ -77,21 +76,20 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-		expectedBazelTargets: []string{`py_binary(
-    name = "foo",
-    python_version = "PY2",
-    srcs = ["a.py"],
-)`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"python_version": `"PY2"`,
+				"srcs":           `["a.py"]`,
+			}),
 		},
 	})
 }
 
 func TestPythonBinaryHostPy3(t *testing.T) {
 	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
-		description:                        "py3 python_binary_host",
-		moduleTypeUnderTest:                "python_binary_host",
-		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		description:                "py3 python_binary_host",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
 		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
@@ -109,10 +107,41 @@
 `,
 		expectedBazelTargets: []string{
 			// python_version is PY3 by default.
-			`py_binary(
-    name = "foo",
-    srcs = ["a.py"],
-)`,
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"srcs": `["a.py"]`,
+			}),
+		},
+	})
+}
+
+func TestPythonBinaryHostArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
+		description:                "test arch variants",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: `python_binary_host {
+					 name: "foo-arm",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo-arm", attrNameToString{
+				"srcs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
 	})
 }
diff --git a/bp2build/python_library_conversion_test.go b/bp2build/python_library_conversion_test.go
index b6f45e5..6b26105 100644
--- a/bp2build/python_library_conversion_test.go
+++ b/bp2build/python_library_conversion_test.go
@@ -11,38 +11,47 @@
 // TODO(alexmarquez): Should be lifted into a generic Bp2Build file
 type PythonLibBp2Build func(ctx android.TopDownMutatorContext)
 
-func TestPythonLibrary(t *testing.T) {
-	testPythonLib(t, "python_library",
-		python.PythonLibraryFactory, python.PythonLibraryBp2Build,
-		func(ctx android.RegistrationContext) {})
-}
-
-func TestPythonLibraryHost(t *testing.T) {
-	testPythonLib(t, "python_library_host",
-		python.PythonLibraryHostFactory, python.PythonLibraryHostBp2Build,
-		func(ctx android.RegistrationContext) {
-			ctx.RegisterModuleType("python_library", python.PythonLibraryFactory)
-		})
-}
-
-func testPythonLib(t *testing.T, modType string,
-	factory android.ModuleFactory, mutator PythonLibBp2Build,
-	registration func(ctx android.RegistrationContext)) {
+func runPythonLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
 	t.Helper()
-	// Simple
-	runBp2BuildTestCase(t, registration, bp2buildTestCase{
-		description:                        fmt.Sprintf("simple %s converts to a native py_library", modType),
-		moduleTypeUnderTest:                modType,
-		moduleTypeUnderTestFactory:         factory,
-		moduleTypeUnderTestBp2BuildMutator: mutator,
-		filesystem: map[string]string{
-			"a.py":           "",
-			"b/c.py":         "",
-			"b/d.py":         "",
-			"b/e.py":         "",
-			"files/data.txt": "",
-		},
-		blueprint: fmt.Sprintf(`%s {
+	testCase := tc
+	testCase.description = fmt.Sprintf(testCase.description, "python_library")
+	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library")
+	testCase.moduleTypeUnderTest = "python_library"
+	testCase.moduleTypeUnderTestFactory = python.PythonLibraryFactory
+	runBp2BuildTestCaseSimple(t, testCase)
+}
+
+func runPythonLibraryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	testCase := tc
+	testCase.description = fmt.Sprintf(testCase.description, "python_library_host")
+	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library_host")
+	testCase.moduleTypeUnderTest = "python_library_host"
+	testCase.moduleTypeUnderTestFactory = python.PythonLibraryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("python_library", python.PythonLibraryFactory)
+	},
+		testCase)
+}
+
+func runPythonLibraryTestCases(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runPythonLibraryTestCase(t, tc)
+	runPythonLibraryHostTestCase(t, tc)
+}
+
+func TestSimplePythonLib(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description: "simple %s converts to a native py_library",
+			filesystem: map[string]string{
+				"a.py":           "",
+				"b/c.py":         "",
+				"b/d.py":         "",
+				"b/e.py":         "",
+				"files/data.txt": "",
+			},
+			blueprint: `%s {
     name: "foo",
     srcs: ["**/*.py"],
     exclude_srcs: ["b/e.py"],
@@ -54,28 +63,23 @@
       name: "bar",
       srcs: ["b/e.py"],
       bazel_module: { bp2build_available: false },
-    }`, modType),
-		expectedBazelTargets: []string{`py_library(
-    name = "foo",
-    data = ["files/data.txt"],
-    deps = [":bar"],
-    srcs = [
+    }`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "foo", attrNameToString{
+					"data": `["files/data.txt"]`,
+					"deps": `[":bar"]`,
+					"srcs": `[
         "a.py",
         "b/c.py",
         "b/d.py",
-    ],
-    srcs_version = "PY3",
-)`,
+    ]`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
 		},
-	})
-
-	// PY2
-	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
-		description:                        fmt.Sprintf("py2 %s converts to a native py_library", modType),
-		moduleTypeUnderTest:                modType,
-		moduleTypeUnderTestFactory:         factory,
-		moduleTypeUnderTestBp2BuildMutator: mutator,
-		blueprint: fmt.Sprintf(`%s {
+		{
+			description: "py2 %s converts to a native py_library",
+			blueprint: `%s {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -88,22 +92,17 @@
     },
 
     bazel_module: { bp2build_available: true },
-}`, modType),
-		expectedBazelTargets: []string{`py_library(
-    name = "foo",
-    srcs = ["a.py"],
-    srcs_version = "PY2",
-)`,
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "foo", attrNameToString{
+					"srcs":         `["a.py"]`,
+					"srcs_version": `"PY2"`,
+				}),
+			},
 		},
-	})
-
-	// PY3
-	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
-		description:                        fmt.Sprintf("py3 %s converts to a native py_library", modType),
-		moduleTypeUnderTest:                modType,
-		moduleTypeUnderTestFactory:         factory,
-		moduleTypeUnderTestBp2BuildMutator: mutator,
-		blueprint: fmt.Sprintf(`%s {
+		{
+			description: "py3 %s converts to a native py_library",
+			blueprint: `%s {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -116,22 +115,17 @@
     },
 
     bazel_module: { bp2build_available: true },
-}`, modType),
-		expectedBazelTargets: []string{`py_library(
-    name = "foo",
-    srcs = ["a.py"],
-    srcs_version = "PY3",
-)`,
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "foo", attrNameToString{
+					"srcs":         `["a.py"]`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
 		},
-	})
-
-	// Both
-	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
-		description:                        fmt.Sprintf("py2&3 %s converts to a native py_library", modType),
-		moduleTypeUnderTest:                modType,
-		moduleTypeUnderTestFactory:         factory,
-		moduleTypeUnderTestBp2BuildMutator: mutator,
-		blueprint: fmt.Sprintf(`%s {
+		{
+			description: "py2&3 %s converts to a native py_library",
+			blueprint: `%s {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -144,13 +138,50 @@
     },
 
     bazel_module: { bp2build_available: true },
-}`, modType),
+}`,
+			expectedBazelTargets: []string{
+				// srcs_version is PY2ANDPY3 by default.
+				makeBazelTarget("py_library", "foo", attrNameToString{
+					"srcs": `["a.py"]`,
+				}),
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			runPythonLibraryTestCases(t, tc)
+		})
+	}
+}
+
+func TestPythonArchVariance(t *testing.T) {
+	runPythonLibraryTestCases(t, bp2buildTestCase{
+		description: "test %s arch variants",
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: `%s {
+					 name: "foo",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`,
 		expectedBazelTargets: []string{
-			// srcs_version is PY2ANDPY3 by default.
-			`py_library(
-    name = "foo",
-    srcs = ["a.py"],
-)`,
+			makeBazelTarget("py_library", "foo", attrNameToString{
+				"srcs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    })`,
+				"srcs_version": `"PY3"`,
+			}),
 		},
 	})
 }
diff --git a/bp2build/sh_conversion_test.go b/bp2build/sh_conversion_test.go
index 82e0a14..f6d2a20 100644
--- a/bp2build/sh_conversion_test.go
+++ b/bp2build/sh_conversion_test.go
@@ -55,18 +55,21 @@
 
 func TestShBinarySimple(t *testing.T) {
 	runShBinaryTestCase(t, bp2buildTestCase{
-		description:                        "sh_binary test",
-		moduleTypeUnderTest:                "sh_binary",
-		moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
-		moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
+		description:                "sh_binary test",
+		moduleTypeUnderTest:        "sh_binary",
+		moduleTypeUnderTestFactory: sh.ShBinaryFactory,
 		blueprint: `sh_binary {
     name: "foo",
     src: "foo.sh",
+    filename: "foo.exe",
+    sub_dir: "sub",
     bazel_module: { bp2build_available: true },
 }`,
-		expectedBazelTargets: []string{`sh_binary(
-    name = "foo",
-    srcs = ["foo.sh"],
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("sh_binary", "foo", attrNameToString{
+				"srcs":     `["foo.sh"]`,
+				"filename": `"foo.exe"`,
+				"sub_dir":  `"sub"`,
+			})},
 	})
 }
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
new file mode 100644
index 0000000..f1489aa
--- /dev/null
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -0,0 +1,836 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"testing"
+)
+
+func runSoongConfigModuleTypeTest(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerSoongConfigModuleTypes, tc)
+}
+
+func registerSoongConfigModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+
+	ctx.RegisterModuleType("soong_config_module_type_import", android.SoongConfigModuleTypeImportFactory)
+	ctx.RegisterModuleType("soong_config_module_type", android.SoongConfigModuleTypeFactory)
+	ctx.RegisterModuleType("soong_config_string_variable", android.SoongConfigStringVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_bool_variable", android.SoongConfigBoolVariableDummyFactory)
+
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+}
+
+func TestSoongConfigModuleType(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	bool_variables: ["feature1"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - soong_config_module_type is supported in bp2build",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__feature1__enabled": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleTypeImport(t *testing.T) {
+	configBp := `
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	bool_variables: ["feature1"],
+	properties: ["cflags"],
+}
+`
+	bp := `
+soong_config_module_type_import {
+	from: "foo/bar/SoongConfig.bp",
+	module_types: ["custom_cc_library_static"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - soong_config_module_type_import is supported in bp2build",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		filesystem: map[string]string{
+			"foo/bar/SoongConfig.bp": configBp,
+		},
+		blueprint: bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__feature1__enabled": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringVar(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["board"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"]
+			},
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for string vars",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringAndBoolVar(t *testing.T) {
+	bp := `
+soong_config_bool_variable {
+	name: "feature1",
+}
+
+soong_config_bool_variable {
+	name: "feature2",
+}
+
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["feature1", "feature2", "board"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+		feature2: {
+			cflags: ["-DFEATURE2"],
+			conditions_default: {
+				cflags: ["-DDEFAULT2"],
+			},
+		},
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"]
+			},
+		},
+	},
+}`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for multiple variable types",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:acme__feature1__enabled": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }) + select({
+        "//build/bazel/product_variables:acme__feature2__enabled": ["-DFEATURE2"],
+        "//conditions:default": ["-DDEFAULT2"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringVar_LabelListDeps(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["board"],
+	properties: ["cflags", "static_libs"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+				static_libs: ["soc_a_dep"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+				static_libs: ["soc_b_dep"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"],
+				static_libs: ["soc_default_static_dep"],
+			},
+		},
+	},
+}`
+
+	otherDeps := `
+cc_library_static { name: "soc_a_dep", bazel_module: { bp2build_available: false } }
+cc_library_static { name: "soc_b_dep", bazel_module: { bp2build_available: false } }
+cc_library_static { name: "soc_default_static_dep", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for label list attributes",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }),
+    implementation_deps = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["//foo/bar:soc_a_dep"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["//foo/bar:soc_b_dep"],
+        "//conditions:default": ["//foo/bar:soc_default_static_dep"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_SingleNamespace(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "vendor_foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_foo",
+	bool_variables: ["feature"],
+	properties: ["cflags", "cppflags"],
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults_1",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_feature_1"],
+			conditions_default: {
+				cflags: ["-cflag_default_1"],
+			},
+		},
+	},
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults_2",
+	defaults: ["foo_defaults_1"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_feature_2"],
+			conditions_default: {
+				cflags: ["-cflag_default_2"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults_2"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - defaults with a single namespace",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    copts = select({
+        "//build/bazel/product_variables:vendor_foo__feature__enabled": [
+            "-cflag_feature_2",
+            "-cflag_feature_1",
+        ],
+        "//conditions:default": [
+            "-cflag_default_2",
+            "-cflag_default_1",
+        ],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_MultipleDefaults_SingleNamespace(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "acme",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "bar_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "acme",
+	bool_variables: ["feature"],
+	properties: ["cflags", "asflags"],
+}
+
+foo_cc_defaults {
+	name: "foo_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_foo"],
+			conditions_default: {
+				cflags: ["-cflag_default_foo"],
+			},
+		},
+	},
+}
+
+bar_cc_defaults {
+	name: "bar_defaults",
+	srcs: ["file.S"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_bar"],
+			asflags: ["-asflag_bar"],
+			conditions_default: {
+				asflags: ["-asflag_default_bar"],
+				cflags: ["-cflag_default_bar"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults", "bar_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+	name: "lib2",
+	defaults: ["bar_defaults", "foo_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - multiple defaults with a single namespace",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    asflags = select({
+        "//build/bazel/product_variables:acme__feature__enabled": ["-asflag_bar"],
+        "//conditions:default": ["-asflag_default_bar"],
+    }),
+    copts = select({
+        "//build/bazel/product_variables:acme__feature__enabled": [
+            "-cflag_foo",
+            "-cflag_bar",
+        ],
+        "//conditions:default": [
+            "-cflag_default_foo",
+            "-cflag_default_bar",
+        ],
+    }),
+    local_includes = ["."],
+    srcs_as = ["file.S"],
+)`,
+			`cc_library_static(
+    name = "lib2",
+    asflags = select({
+        "//build/bazel/product_variables:acme__feature__enabled": ["-asflag_bar"],
+        "//conditions:default": ["-asflag_default_bar"],
+    }),
+    copts = select({
+        "//build/bazel/product_variables:acme__feature__enabled": [
+            "-cflag_bar",
+            "-cflag_foo",
+        ],
+        "//conditions:default": [
+            "-cflag_default_bar",
+            "-cflag_default_foo",
+        ],
+    }),
+    local_includes = ["."],
+    srcs_as = ["file.S"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_MultipleNamespaces(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "vendor_foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_foo",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "vendor_bar_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_bar",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "vendor_qux_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_qux",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_FOO_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_FOO_DEFAULT"],
+			},
+		},
+	},
+}
+
+vendor_bar_cc_defaults {
+	name: "bar_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_BAR_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_BAR_DEFAULT"],
+			},
+		},
+	},
+}
+
+vendor_qux_cc_defaults {
+	name: "qux_defaults",
+	defaults: ["bar_defaults"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_QUX_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_QUX_DEFAULT"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults", "qux_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - defaults with multiple namespaces",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    copts = select({
+        "//build/bazel/product_variables:vendor_bar__feature__enabled": ["-DVENDOR_BAR_FEATURE"],
+        "//conditions:default": ["-DVENDOR_BAR_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:vendor_foo__feature__enabled": ["-DVENDOR_FOO_FEATURE"],
+        "//conditions:default": ["-DVENDOR_FOO_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:vendor_qux__feature__enabled": ["-DVENDOR_QUX_FEATURE"],
+        "//conditions:default": ["-DVENDOR_QUX_DEFAULT"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults(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",
+        "static_libs",
+    ],
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_lib_a_defaults",
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_a",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_a",
+                ],
+            },
+        },
+    },
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_merged_defaults",
+    defaults: ["library_linking_strategy_lib_a_defaults"],
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_b",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary",
+    srcs: ["library_linking_strategy.cc"],
+    defaults: ["library_linking_strategy_merged_defaults"],
+}`
+
+	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_default", 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{`cc_binary(
+    name = "library_linking_strategy_sample_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+            "//foo/bar:lib_b_bp2build_cc_library_static",
+            "//foo/bar:lib_a_bp2build_cc_library_static",
+        ],
+        "//conditions:default": [],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": [
+            "//foo/bar:lib_b",
+            "//foo/bar:lib_a",
+        ],
+    }),
+    local_includes = ["."],
+    srcs = ["library_linking_strategy.cc"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_Another(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",
+        "static_libs",
+    ],
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_sample_defaults",
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_a",
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_a",
+                    "lib_b",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary",
+    srcs: ["library_linking_strategy.cc"],
+    defaults: ["library_linking_strategy_sample_defaults"],
+}`
+
+	otherDeps := `
+cc_library { name: "lib_a", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_b", 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{`cc_binary(
+    name = "library_linking_strategy_sample_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+            "//foo/bar:lib_a_bp2build_cc_library_static",
+            "//foo/bar:lib_b_bp2build_cc_library_static",
+        ],
+        "//conditions:default": [],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": [
+            "//foo/bar:lib_a",
+            "//foo/bar:lib_b",
+        ],
+    }),
+    local_includes = ["."],
+    srcs = ["library_linking_strategy.cc"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_UnusedProps(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+    name: "alphabet",
+    values: [
+        "a",
+        "b",
+        "c", // unused
+    ],
+}
+
+soong_config_module_type {
+    name: "alphabet_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    variables: ["alphabet"],
+    properties: [
+        "cflags", // unused
+        "shared_libs",
+        "static_libs",
+    ],
+}
+
+alphabet_cc_defaults {
+    name: "alphabet_sample_cc_defaults",
+    soong_config_variables: {
+        alphabet: {
+            a: {
+                shared_libs: [
+                    "lib_a",
+                ],
+            },
+            b: {
+                shared_libs: [
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                static_libs: [
+                    "lib_default",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "alphabet_binary",
+    srcs: ["main.cc"],
+    defaults: ["alphabet_sample_cc_defaults"],
+}`
+
+	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_default", 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{`cc_binary(
+    name = "alphabet_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__alphabet__a": [],
+        "//build/bazel/product_variables:android__alphabet__b": [],
+        "//conditions:default": ["//foo/bar:lib_default_bp2build_cc_library_static"],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__alphabet__a": ["//foo/bar:lib_a"],
+        "//build/bazel/product_variables:android__alphabet__b": ["//foo/bar:lib_b"],
+        "//conditions:default": [],
+    }),
+    local_includes = ["."],
+    srcs = ["main.cc"],
+)`}})
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index a549a93..8ae1a38 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -20,6 +20,7 @@
 */
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -36,14 +37,34 @@
 	buildDir string
 )
 
-func errored(t *testing.T, desc string, errs []error) bool {
+func checkError(t *testing.T, errs []error, expectedErr error) bool {
 	t.Helper()
+
+	if len(errs) != 1 {
+		return false
+	}
+	if errs[0].Error() == expectedErr.Error() {
+		return true
+	}
+
+	return false
+}
+
+func errored(t *testing.T, tc bp2buildTestCase, errs []error) bool {
+	t.Helper()
+	if tc.expectedErr != nil {
+		// Rely on checkErrors, as this test case is expected to have an error.
+		return false
+	}
+
 	if len(errs) > 0 {
 		for _, err := range errs {
-			t.Errorf("%s: %s", desc, err)
+			t.Errorf("%s: %s", tc.description, err)
 		}
 		return true
 	}
+
+	// All good, continue execution.
 	return false
 }
 
@@ -53,14 +74,15 @@
 }
 
 type bp2buildTestCase struct {
-	description                        string
-	moduleTypeUnderTest                string
-	moduleTypeUnderTestFactory         android.ModuleFactory
-	moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-	blueprint                          string
-	expectedBazelTargets               []string
-	filesystem                         map[string]string
-	dir                                string
+	description                string
+	moduleTypeUnderTest        string
+	moduleTypeUnderTestFactory android.ModuleFactory
+	blueprint                  string
+	expectedBazelTargets       []string
+	filesystem                 map[string]string
+	dir                        string
+	expectedErr                error
+	unconvertedDepsMode        unconvertedDepsMode
 }
 
 func runBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc bp2buildTestCase) {
@@ -82,15 +104,19 @@
 	registerModuleTypes(ctx)
 	ctx.RegisterModuleType(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestFactory)
 	ctx.RegisterBp2BuildConfig(bp2buildConfig)
-	ctx.RegisterBp2BuildMutator(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestBp2BuildMutator)
 	ctx.RegisterForBazelConversion()
 
-	_, errs := ctx.ParseFileList(dir, toParse)
-	if errored(t, tc.description, errs) {
+	_, parseErrs := ctx.ParseFileList(dir, toParse)
+	if errored(t, tc, parseErrs) {
 		return
 	}
-	_, errs = ctx.ResolveDependencies(config)
-	if errored(t, tc.description, errs) {
+	_, resolveDepsErrs := ctx.ResolveDependencies(config)
+	if errored(t, tc, resolveDepsErrs) {
+		return
+	}
+
+	errs := append(parseErrs, resolveDepsErrs...)
+	if tc.expectedErr != nil && checkError(t, errs, tc.expectedErr) {
 		return
 	}
 
@@ -99,29 +125,43 @@
 		checkDir = tc.dir
 	}
 	codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-	bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
+	codegenCtx.unconvertedDepMode = tc.unconvertedDepsMode
+	bazelTargets, errs := generateBazelTargetsForDir(codegenCtx, checkDir)
+	if tc.expectedErr != nil && checkError(t, errs, tc.expectedErr) {
+		return
+	} else {
+		android.FailIfErrored(t, errs)
+	}
 	if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount {
-		t.Errorf("%s: Expected %d bazel target, got %d; %v",
-			tc.description, expectedCount, actualCount, bazelTargets)
+		t.Errorf("%s: Expected %d bazel target (%s), got `%d`` (%s)",
+			tc.description, expectedCount, tc.expectedBazelTargets, actualCount, bazelTargets)
 	} else {
 		for i, target := range bazelTargets {
 			if w, g := tc.expectedBazelTargets[i], target.content; w != g {
 				t.Errorf(
-					"%s: Expected generated Bazel target to be '%s', got '%s'",
-					tc.description,
-					w,
-					g,
-				)
+					"%s: Expected generated Bazel target to be `%s`, got `%s`",
+					tc.description, w, g)
 			}
 		}
 	}
 }
 
 type nestedProps struct {
-	Nested_prop string
+	Nested_prop *string
+}
+
+type EmbeddedProps struct {
+	Embedded_prop *string
+}
+
+type OtherEmbeddedProps struct {
+	Other_embedded_prop *string
 }
 
 type customProps struct {
+	EmbeddedProps
+	*OtherEmbeddedProps
+
 	Bool_prop     bool
 	Bool_ptr_prop *bool
 	// Ensure that properties tagged `blueprint:mutated` are omitted
@@ -136,6 +176,9 @@
 
 	Arch_paths         []string `android:"path,arch_variant"`
 	Arch_paths_exclude []string `android:"path,arch_variant"`
+
+	// Prop used to indicate this conversion should be 1 module -> multiple targets
+	One_to_many_prop *bool
 }
 
 type customModule struct {
@@ -219,89 +262,112 @@
 	return m
 }
 
+type EmbeddedAttr struct {
+	Embedded_attr *string
+}
+
+type OtherEmbeddedAttr struct {
+	Other_embedded_attr *string
+}
+
 type customBazelModuleAttributes struct {
-	String_prop      string
+	EmbeddedAttr
+	*OtherEmbeddedAttr
+	String_ptr_prop  *string
 	String_list_prop []string
 	Arch_paths       bazel.LabelListAttribute
 }
 
-type customBazelModule struct {
-	android.BazelTargetModuleBase
-	customBazelModuleAttributes
-}
+func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	paths := bazel.LabelListAttribute{}
 
-func customBp2BuildMutator(ctx android.TopDownMutatorContext) {
-	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build(ctx) {
-			return
-		}
+	if p := m.props.One_to_many_prop; p != nil && *p {
+		customBp2buildOneToMany(ctx, m)
+		return
+	}
 
-		paths := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.props.Arch_paths, m.props.Arch_paths_exclude))
-
-		for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
-			for config, props := range configToProps {
-				if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil {
-					paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, archProps.Arch_paths, archProps.Arch_paths_exclude))
-				}
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
+		for config, props := range configToProps {
+			if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil {
+				paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, archProps.Arch_paths, archProps.Arch_paths_exclude))
 			}
 		}
-
-		paths.ResolveExcludes()
-
-		attrs := &customBazelModuleAttributes{
-			String_prop:      m.props.String_prop,
-			String_list_prop: m.props.String_list_prop,
-			Arch_paths:       paths,
-		}
-
-		props := bazel.BazelTargetModuleProperties{
-			Rule_class: "custom",
-		}
-
-		ctx.CreateBazelTargetModule(m.Name(), props, attrs)
 	}
+
+	paths.ResolveExcludes()
+
+	attrs := &customBazelModuleAttributes{
+		String_ptr_prop:  m.props.String_ptr_prop,
+		String_list_prop: m.props.String_list_prop,
+		Arch_paths:       paths,
+	}
+	attrs.Embedded_attr = m.props.Embedded_prop
+	if m.props.OtherEmbeddedProps != nil {
+		attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop}
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "custom",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 // A bp2build mutator that uses load statements and creates a 1:M mapping from
 // module to target.
-func customBp2BuildMutatorFromStarlark(ctx android.TopDownMutatorContext) {
-	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build(ctx) {
-			return
-		}
+func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) {
 
-		baseName := m.Name()
-		attrs := &customBazelModuleAttributes{}
+	baseName := m.Name()
+	attrs := &customBazelModuleAttributes{}
 
-		myLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "my_library",
-			Bzl_load_location: "//build/bazel/rules:rules.bzl",
-		}
-		ctx.CreateBazelTargetModule(baseName, myLibraryProps, attrs)
-
-		protoLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "proto_library",
-			Bzl_load_location: "//build/bazel/rules:proto.bzl",
-		}
-		ctx.CreateBazelTargetModule(baseName+"_proto_library_deps", protoLibraryProps, attrs)
-
-		myProtoLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "my_proto_library",
-			Bzl_load_location: "//build/bazel/rules:proto.bzl",
-		}
-		ctx.CreateBazelTargetModule(baseName+"_my_proto_library_deps", myProtoLibraryProps, attrs)
+	myLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "my_library",
+		Bzl_load_location: "//build/bazel/rules:rules.bzl",
 	}
+	ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs)
+
+	protoLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "proto_library",
+		Bzl_load_location: "//build/bazel/rules:proto.bzl",
+	}
+	ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs)
+
+	myProtoLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "my_proto_library",
+		Bzl_load_location: "//build/bazel/rules:proto.bzl",
+	}
+	ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs)
 }
 
 // Helper method for tests to easily access the targets in a dir.
-func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) BazelTargets {
+func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) {
 	// TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely
-	buildFileToTargets, _, _ := GenerateBazelTargets(codegenCtx, false)
-	return buildFileToTargets[dir]
+	res, err := GenerateBazelTargets(codegenCtx, false)
+	return res.buildFileToTargets[dir], err
 }
 
 func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) {
 	ctx.RegisterModuleType("custom", customModuleFactory)
-	ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutator)
 	ctx.RegisterForBazelConversion()
 }
+
+func simpleModuleDoNotConvertBp2build(typ, name string) string {
+	return fmt.Sprintf(`
+%s {
+		name: "%s",
+		bazel_module: { bp2build_available: false },
+}`, typ, name)
+}
+
+type attrNameToString map[string]string
+
+func makeBazelTarget(typ, name string, attrs attrNameToString) string {
+	attrStrings := make([]string, 0, len(attrs)+1)
+	attrStrings = append(attrStrings, fmt.Sprintf(`    name = "%s",`, name))
+	for _, k := range android.SortedStringKeys(attrs) {
+		attrStrings = append(attrStrings, fmt.Sprintf("    %s = %s,", k, attrs[k]))
+	}
+	return fmt.Sprintf(`%s(
+%s
+)`, typ, strings.Join(attrStrings, "\n"))
+}
diff --git a/bpfix/Android.bp b/bpfix/Android.bp
index 345dbd0..a72d9b4 100644
--- a/bpfix/Android.bp
+++ b/bpfix/Android.bp
@@ -52,5 +52,6 @@
     ],
     deps: [
         "blueprint-parser",
+        "blueprint-pathtools",
     ],
 }
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index a608630..c0925fe 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -19,12 +19,18 @@
 import (
 	"bytes"
 	"errors"
+	"flag"
 	"fmt"
 	"io"
+	"io/ioutil"
+	"os"
 	"path/filepath"
+	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
 )
 
 // Reformat takes a blueprint file as a string and returns a formatted version
@@ -137,11 +143,35 @@
 		Fix:  runPatchListMod(removeObsoleteProperty("sanitize.scudo")),
 	},
 	{
+		Name: "removeAndroidLicenseKinds",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_kinds")),
+	},
+	{
+		Name: "removeAndroidLicenseConditions",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_conditions")),
+	},
+	{
+		Name: "removeAndroidLicenseFiles",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_files")),
+	},
+	{
 		Name: "formatFlagProperties",
 		Fix:  runPatchListMod(formatFlagProperties),
 	},
 }
 
+// for fix that only need to run once
+var fixStepsOnce = []FixStep{
+	{
+		Name: "haveSameLicense",
+		Fix:  haveSameLicense,
+	},
+	{
+		Name: "rewriteLicenseProperties",
+		Fix:  runPatchListMod(rewriteLicenseProperty(nil, "")),
+	},
+}
+
 func NewFixRequest() FixRequest {
 	return FixRequest{}
 }
@@ -196,6 +226,16 @@
 		return nil, err
 	}
 
+	// run fix that is expected to run once first
+	configOnce := NewFixRequest()
+	configOnce.steps = append(configOnce.steps, fixStepsOnce...)
+	if len(configOnce.steps) > 0 {
+		err = f.fixTreeOnce(configOnce)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	maxNumIterations := 20
 	i := 0
 	for {
@@ -1413,3 +1453,339 @@
 	}
 	return nil
 }
+
+func rewriteLicenseProperty(fs pathtools.FileSystem, relativePath string) patchListModFunction {
+	return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
+		return rewriteLicenseProperties(mod, patchList, fs, relativePath)
+	}
+}
+
+// rewrite the "android_license_kinds" and "android_license_files" properties to a package module
+// (and a license module when needed).
+func rewriteLicenseProperties(mod *parser.Module, patchList *parser.PatchList, fs pathtools.FileSystem,
+	relativePath string) error {
+	// if a package module has been added, no more action is needed.
+	for _, patch := range *patchList {
+		if strings.Contains(patch.Replacement, "package {") {
+			return nil
+		}
+	}
+
+	// initial the fs
+	if fs == nil {
+		fs = pathtools.NewOsFs(os.Getenv("ANDROID_BUILD_TOP"))
+	}
+
+	// initial the relativePath
+	if len(relativePath) == 0 {
+		relativePath = getModuleRelativePath()
+	}
+	// validate the relativePath
+	ok := hasFile(relativePath+"/Android.mk", fs)
+	// some modules in the existing test cases in the androidmk_test.go do not have a valid path
+	if !ok && len(relativePath) > 0 {
+		return fmt.Errorf("Cannot find an Android.mk file at path %s", relativePath)
+	}
+
+	licenseKindsPropertyName := "android_license_kinds"
+	licenseFilesPropertyName := "android_license_files"
+
+	androidBpFileErr := "// Error: No Android.bp file is found at path\n" +
+		"// %s\n" +
+		"// Please add one there with the needed license module first.\n" +
+		"// Then reset the default_applicable_licenses property below with the license module name.\n"
+	licenseModuleErr := "// Error: Cannot get the name of the license module in the\n" +
+		"// %s file.\n" +
+		"// If no such license module exists, please add one there first.\n" +
+		"// Then reset the default_applicable_licenses property below with the license module name.\n"
+
+	defaultApplicableLicense := "Android-Apache-2.0"
+	var licenseModuleName, licensePatch string
+	var hasFileInParentDir bool
+
+	// when LOCAL_NOTICE_FILE is not empty
+	if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) {
+		hasFileInParentDir = hasValueStartWithTwoDotsLiteralList(mod, licenseFilesPropertyName)
+		// if have LOCAL_NOTICE_FILE outside the current directory, need to find and refer to the license
+		// module in the LOCAL_NOTICE_FILE location directly and no new license module needs to be created
+		if hasFileInParentDir {
+			bpPath, ok := getPathFromProperty(mod, licenseFilesPropertyName, fs, relativePath)
+			if !ok {
+				bpDir, err := getDirFromProperty(mod, licenseFilesPropertyName, fs, relativePath)
+				if err != nil {
+					return err
+				}
+				licensePatch += fmt.Sprintf(androidBpFileErr, bpDir)
+				defaultApplicableLicense = ""
+			} else {
+				licenseModuleName, _ = getModuleName(bpPath, "license", fs)
+				if len(licenseModuleName) == 0 {
+					licensePatch += fmt.Sprintf(licenseModuleErr, bpPath)
+				}
+				defaultApplicableLicense = licenseModuleName
+			}
+		} else {
+			// if have LOCAL_NOTICE_FILE in the current directory, need to create a new license module
+			if len(relativePath) == 0 {
+				return fmt.Errorf("Cannot obtain the relative path of the Android.mk file")
+			}
+			licenseModuleName = strings.Replace(relativePath, "/", "_", -1) + "_license"
+			defaultApplicableLicense = licenseModuleName
+		}
+	}
+
+	//add the package module
+	if hasNonEmptyLiteralListProperty(mod, licenseKindsPropertyName) {
+		licensePatch += "package {\n" +
+			"    // See: http://go/android-license-faq\n" +
+			"    default_applicable_licenses: [\n" +
+			"         \"" + defaultApplicableLicense + "\",\n" +
+			"    ],\n" +
+			"}\n" +
+			"\n"
+	}
+
+	// append the license module when necessary
+	// when LOCAL_NOTICE_FILE is not empty and in the current directory, create a new license module
+	// otherwise, use the above default license directly
+	if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) && !hasFileInParentDir {
+		licenseKinds, err := mergeLiteralListPropertyValue(mod, licenseKindsPropertyName)
+		if err != nil {
+			return err
+		}
+		licenseFiles, err := mergeLiteralListPropertyValue(mod, licenseFilesPropertyName)
+		if err != nil {
+			return err
+		}
+		licensePatch += "license {\n" +
+			"    name: \"" + licenseModuleName + "\",\n" +
+			"    visibility: [\":__subpackages__\"],\n" +
+			"    license_kinds: [\n" +
+			licenseKinds +
+			"    ],\n" +
+			"    license_text: [\n" +
+			licenseFiles +
+			"    ],\n" +
+			"}\n" +
+			"\n"
+	}
+
+	// add to the patchList
+	pos := mod.Pos().Offset
+	err := patchList.Add(pos, pos, licensePatch)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// merge the string vaules in a list property of a module into one string with expected format
+func mergeLiteralListPropertyValue(mod *parser.Module, property string) (s string, err error) {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if !ok {
+		// if do not find
+		return "", fmt.Errorf("Cannot retrieve the %s.%s field", mod.Type, property)
+	}
+	for i := 0; i < len(listValue); i++ {
+		s += "         \"" + listValue[i] + "\",\n"
+	}
+	return s, nil
+}
+
+// check whether a string list property has any value starting with `../`
+func hasValueStartWithTwoDotsLiteralList(mod *parser.Module, property string) bool {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if ok {
+		for i := 0; i < len(listValue); i++ {
+			if strings.HasPrefix(listValue[i], "../") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// get the relative path from ANDROID_BUILD_TOP to the Android.mk file to be converted
+func getModuleRelativePath() string {
+	// get the absolute path of the top of the tree
+	rootPath := os.Getenv("ANDROID_BUILD_TOP")
+	// get the absolute path of the `Android.mk` file to be converted
+	absPath := getModuleAbsolutePath()
+	// get the relative path of the `Android.mk` file to top of the tree
+	relModulePath, err := filepath.Rel(rootPath, absPath)
+	if err != nil {
+		return ""
+	}
+	return relModulePath
+}
+
+// get the absolute path of the Android.mk file to be converted
+func getModuleAbsolutePath() string {
+	// get the absolute path at where the `androidmk` commend is executed
+	curAbsPath, err := filepath.Abs(".")
+	if err != nil {
+		return ""
+	}
+	// the argument for the `androidmk` command could be
+	// 1. "./a/b/c/Android.mk"; 2. "a/b/c/Android.mk"; 3. "Android.mk"
+	argPath := flag.Arg(0)
+	if strings.HasPrefix(argPath, "./") {
+		argPath = strings.TrimPrefix(argPath, ".")
+	}
+	argPath = strings.TrimSuffix(argPath, "Android.mk")
+	if strings.HasSuffix(argPath, "/") {
+		argPath = strings.TrimSuffix(argPath, "/")
+	}
+	if len(argPath) > 0 && !strings.HasPrefix(argPath, "/") {
+		argPath = "/" + argPath
+	}
+	// get the absolute path of the `Android.mk` file to be converted
+	absPath := curAbsPath + argPath
+	return absPath
+}
+
+// check whether a file exists in a filesystem
+func hasFile(path string, fs pathtools.FileSystem) bool {
+	ok, _, _ := fs.Exists(path)
+	return ok
+}
+
+// get the directory where an `Android.bp` file and the property files are expected to locate
+func getDirFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, error) {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if !ok {
+		// if do not find
+		return "", fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property)
+	}
+	if len(listValue) == 0 {
+		// if empty
+		return "", fmt.Errorf("Cannot find the value of the %s.%s property", mod.Type, property)
+	}
+	_, isDir, _ := fs.Exists(relativePath)
+	if !isDir {
+		return "", fmt.Errorf("Cannot find the path %s", relativePath)
+	}
+	path := relativePath
+	for {
+		if !strings.HasPrefix(listValue[0], "../") {
+			break
+		}
+		path = filepath.Dir(path)
+		listValue[0] = strings.TrimPrefix(listValue[0], "../")
+	}
+	_, isDir, _ = fs.Exists(path)
+	if !isDir {
+		return "", fmt.Errorf("Cannot find the path %s", path)
+	}
+	return path, nil
+}
+
+// get the path of the `Android.bp` file at the expected location where the property files locate
+func getPathFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, bool) {
+	dir, err := getDirFromProperty(mod, property, fs, relativePath)
+	if err != nil {
+		return "", false
+	}
+	ok := hasFile(dir+"/Android.bp", fs)
+	if !ok {
+		return "", false
+	}
+	return dir + "/Android.bp", true
+}
+
+// parse an Android.bp file to get the name of the first module with type of moduleType
+func getModuleName(path string, moduleType string, fs pathtools.FileSystem) (string, error) {
+	tree, err := parserPath(path, fs)
+	if err != nil {
+		return "", err
+	}
+	for _, def := range tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok || mod.Type != moduleType {
+			continue
+		}
+		prop, ok := mod.GetProperty("name")
+		if !ok {
+			return "", fmt.Errorf("Cannot get the %s."+"name property", mod.Type)
+		}
+		propVal, ok := prop.Value.(*parser.String)
+		if ok {
+			return propVal.Value, nil
+		}
+	}
+	return "", fmt.Errorf("Cannot find the value of the %s."+"name property", moduleType)
+}
+
+// parse an Android.bp file with the specific path
+func parserPath(path string, fs pathtools.FileSystem) (tree *parser.File, err error) {
+	f, err := fs.Open(path)
+	if err != nil {
+		return tree, err
+	}
+	defer f.Close()
+	fileContent, _ := ioutil.ReadAll(f)
+	tree, err = parse(path, bytes.NewBufferString(string(fileContent)))
+	if err != nil {
+		return tree, err
+	}
+	return tree, nil
+}
+
+// remove the incorrect property that Soong does not support
+func removeIncorrectProperties(propName string) patchListModFunction {
+	return removeObsoleteProperty(propName)
+}
+
+// the modules on the same Android.mk file are expected to have the same license
+func haveSameLicense(f *Fixer) error {
+	androidLicenseProperties := []string{
+		"android_license_kinds",
+		"android_license_conditions",
+		"android_license_files",
+	}
+
+	var prevModuleName string
+	var prevLicenseKindsVals, prevLicenseConditionsVals, prevLicenseFilesVals []string
+	prevLicenseVals := [][]string{
+		prevLicenseKindsVals,
+		prevLicenseConditionsVals,
+		prevLicenseFilesVals,
+	}
+
+	for _, def := range f.tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+		for idx, property := range androidLicenseProperties {
+			curModuleName, ok := getLiteralStringPropertyValue(mod, "name")
+			// some modules in the existing test cases in the androidmk_test.go do not have name property
+			hasNameProperty := hasProperty(mod, "name")
+			if hasNameProperty && (!ok || len(curModuleName) == 0) {
+				return fmt.Errorf("Cannot retrieve the name property of a module of %s type.", mod.Type)
+			}
+			curVals, ok := getLiteralListPropertyValue(mod, property)
+			// some modules in the existing test cases in the androidmk_test.go do not have license-related property
+			hasLicenseProperty := hasProperty(mod, property)
+			if hasLicenseProperty && (!ok || len(curVals) == 0) {
+				// if do not find the property, or no value is found for the property
+				return fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property)
+			}
+			if len(prevLicenseVals[idx]) > 0 {
+				if !reflect.DeepEqual(prevLicenseVals[idx], curVals) {
+					return fmt.Errorf("Modules %s and %s are expected to have the same %s property.",
+						prevModuleName, curModuleName, property)
+				}
+			}
+			sort.Strings(curVals)
+			prevLicenseVals[idx] = curVals
+			prevModuleName = curModuleName
+		}
+	}
+	return nil
+}
+
+func hasProperty(mod *parser.Module, propName string) bool {
+	_, ok := mod.GetProperty(propName)
+	return ok
+}
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index d8772c1..221df45 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -25,6 +25,7 @@
 	"reflect"
 
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
 )
 
 // TODO(jeffrygaston) remove this when position is removed from ParseNode (in b/38325146) and we can directly do reflect.DeepEqual
@@ -125,34 +126,103 @@
 	implFilterListTest(t, []string{}, []string{}, []string{})
 }
 
-func runPass(t *testing.T, in, out string, innerTest func(*Fixer) error) {
-	expected, err := Reformat(out)
+func checkError(t *testing.T, in, expectedErr string, innerTest func(*Fixer) error) {
+	expected := preProcessOutErr(expectedErr)
+	runTestOnce(t, in, expected, innerTest)
+}
+
+func runTestOnce(t *testing.T, in, expected string, innerTest func(*Fixer) error) {
+	fixer, err := preProcessIn(in)
 	if err != nil {
 		t.Fatal(err)
 	}
 
+	out, err := runFixerOnce(fixer, innerTest)
+	if err != nil {
+		out = err.Error()
+	}
+
+	compareResult := compareOutExpected(in, out, expected)
+	if len(compareResult) > 0 {
+		t.Errorf(compareResult)
+	}
+}
+
+func preProcessOutErr(expectedErr string) string {
+	expected := strings.TrimSpace(expectedErr)
+	return expected
+}
+
+func preProcessOut(out string) (expected string, err error) {
+	expected, err = Reformat(out)
+	if err != nil {
+		return expected, err
+	}
+	return expected, nil
+}
+
+func preProcessIn(in string) (fixer *Fixer, err error) {
 	in, err = Reformat(in)
 	if err != nil {
-		t.Fatal(err)
+		return fixer, err
 	}
 
 	tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil))
 	if errs != nil {
-		t.Fatal(errs)
+		return fixer, err
 	}
 
-	fixer := NewFixer(tree)
+	fixer = NewFixer(tree)
+
+	return fixer, nil
+}
+
+func runFixerOnce(fixer *Fixer, innerTest func(*Fixer) error) (string, error) {
+	err := innerTest(fixer)
+	if err != nil {
+		return "", err
+	}
+
+	out, err := parser.Print(fixer.tree)
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
+
+func compareOutExpected(in, out, expected string) string {
+	if out != expected {
+		return fmt.Sprintf("output didn't match:\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
+			in, expected, out)
+	}
+	return ""
+}
+
+func runPassOnce(t *testing.T, in, out string, innerTest func(*Fixer) error) {
+	expected, err := preProcessOut(out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	runTestOnce(t, in, expected, innerTest)
+}
+
+func runPass(t *testing.T, in, out string, innerTest func(*Fixer) error) {
+	expected, err := preProcessOut(out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fixer, err := preProcessIn(in)
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	got := ""
 	prev := "foo"
 	passes := 0
 	for got != prev && passes < 10 {
-		err := innerTest(fixer)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		out, err := parser.Print(fixer.tree)
+		out, err = runFixerOnce(fixer, innerTest)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -162,9 +232,9 @@
 		passes++
 	}
 
-	if got != expected {
-		t.Errorf("output didn't match:\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
-			in, expected, got)
+	compareResult := compareOutExpected(in, out, expected)
+	if len(compareResult) > 0 {
+		t.Errorf(compareResult)
 	}
 }
 
@@ -1608,3 +1678,348 @@
 		})
 	}
 }
+
+func TestRewriteLicenseProperty(t *testing.T) {
+	mockFs := pathtools.MockFs(map[string][]byte{
+		"a/b/c/d/Android.mk": []byte("this is not important."),
+		"a/b/LicenseFile1":   []byte("LicenseFile1"),
+		"a/b/LicenseFile2":   []byte("LicenseFile2"),
+		"a/b/Android.bp":     []byte("license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+	})
+	relativePath := "a/b/c/d"
+	relativePathErr := "a/b/c"
+	tests := []struct {
+		name string
+		in   string
+		fs   pathtools.FileSystem
+		path string
+		out  string
+	}{
+		{
+			name: "license rewriting with one module",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+			out: `
+				package {
+					// See: http://go/android-license-faq
+					default_applicable_licenses: [
+						"Android-Apache-2.0",
+					],
+				}
+
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+		},
+		{
+			name: "license rewriting with two modules",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+			out: `
+				package {
+					// See: http://go/android-license-faq
+					default_applicable_licenses: [
+						"Android-Apache-2.0",
+					],
+				}
+
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+		},
+		{
+			name: "license rewriting with license files in the current directory",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["LicenseFile1", "LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePath,
+			out: `
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"a_b_c_d_license",
+				],
+			}
+
+			license {
+				name: "a_b_c_d_license",
+				visibility: [":__subpackages__"],
+				license_kinds: [
+					"license_kind",
+				],
+				license_text: [
+					"LicenseFile1",
+					"LicenseFile2",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"LicenseFile1",
+					"LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with license files outside the current directory",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePath,
+			out: `
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"reuse_a_b_license",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with no Android.bp file in the expected location",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs: pathtools.MockFs(map[string][]byte{
+				"a/b/c/d/Android.mk": []byte("this is not important."),
+				"a/b/LicenseFile1":   []byte("LicenseFile1"),
+				"a/b/LicenseFile2":   []byte("LicenseFile2"),
+				"a/Android.bp":       []byte("license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+			}),
+			path: relativePath,
+			out: `
+			// Error: No Android.bp file is found at path
+			// a/b
+			// Please add one there with the needed license module first.
+			// Then reset the default_applicable_licenses property below with the license module name.
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with an Android.bp file without a license module",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs: pathtools.MockFs(map[string][]byte{
+				"a/b/c/d/Android.mk": []byte("this is not important."),
+				"a/b/LicenseFile1":   []byte("LicenseFile1"),
+				"a/b/LicenseFile2":   []byte("LicenseFile2"),
+				"a/b/Android.bp":     []byte("non_license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+			}),
+			path: relativePath,
+			out: `
+			// Error: Cannot get the name of the license module in the
+			// a/b/Android.bp file.
+			// If no such license module exists, please add one there first.
+			// Then reset the default_applicable_licenses property below with the license module name.
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPassOnce(t, test.in, test.out, runPatchListMod(rewriteLicenseProperty(test.fs, test.path)))
+		})
+	}
+
+	testErrs := []struct {
+		name        string
+		in          string
+		fs          pathtools.FileSystem
+		path        string
+		expectedErr string
+	}{
+		{
+			name: "license rewriting with a wrong path",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePathErr,
+			expectedErr: `
+				Cannot find an Android.mk file at path a/b/c
+			`,
+		},
+	}
+	for _, test := range testErrs {
+		t.Run(test.name, func(t *testing.T) {
+			checkError(t, test.in, test.expectedErr, runPatchListMod(rewriteLicenseProperty(test.fs, test.path)))
+		})
+	}
+}
+
+func TestHaveSameLicense(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "two modules with the same license",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+			out: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPassOnce(t, test.in, test.out, func(fixer *Fixer) error {
+				return haveSameLicense(fixer)
+			})
+		})
+	}
+	testErrs := []struct {
+		name        string
+		in          string
+		expectedErr string
+	}{
+		{
+			name: "two modules will different licenses",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+			expectedErr: `
+				Modules foo1 and foo2 are expected to have the same android_license_kinds property.
+			`,
+		},
+	}
+	for _, test := range testErrs {
+		t.Run(test.name, func(t *testing.T) {
+			checkError(t, test.in, test.expectedErr, func(fixer *Fixer) error {
+				return haveSameLicense(fixer)
+			})
+		})
+	}
+}
diff --git a/build_kzip.bash b/build_kzip.bash
index 5655067..aff2d6d 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -61,5 +61,5 @@
 # Pack
 # TODO(asmundak): this should be done by soong.
 declare -r allkzip="$KZIP_NAME.kzip"
-"$out/soong/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
+"$out/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
 
diff --git a/build_test.bash b/build_test.bash
index b039285..b6d00e2 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -50,7 +50,9 @@
 
 echo
 echo "Free disk space:"
-df -h
+# Ignore df errors because it errors out on gvfsd file systems
+# but still displays most of the useful info we need
+df -h || true
 
 echo
 echo "Running Bazel smoke test..."
diff --git a/cc/Android.bp b/cc/Android.bp
index bff2761..07aa7cb 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -60,9 +60,11 @@
         "binary.go",
         "binary_sdk_member.go",
         "fuzz.go",
+        "image_sdk_traits.go",
         "library.go",
         "library_headers.go",
         "library_sdk_member.go",
+        "native_bridge_sdk_trait.go",
         "object.go",
         "test.go",
         "toolchain_library.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index cd52363..800b58f 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -80,7 +80,7 @@
 		// to be installed. And this is breaking some older devices (like marlin)
 		// where system.img is small.
 		Required: c.Properties.AndroidMkRuntimeLibs,
-		Include:  "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:  "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
@@ -263,9 +263,10 @@
 		library.androidMkWriteExportedFlags(entries)
 		library.androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries)
 
-		_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
-
-		entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+		if entries.OutputFile.Valid() {
+			_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
+			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+		}
 
 		if library.coverageOutputFile.Valid() {
 			entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputFile.String())
@@ -294,9 +295,6 @@
 			if library.buildStubs() {
 				entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
 			}
-			if library.apiListCoverageXmlPath.String() != "" {
-				entries.SetString("SOONG_CC_API_XML", "$(SOONG_CC_API_XML) "+library.apiListCoverageXmlPath.String())
-			}
 		})
 	}
 	// If a library providing a stub is included in an APEX, the private APIs of the library
@@ -394,6 +392,8 @@
 		if Bool(test.Properties.Test_options.Unit_test) {
 			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
 		}
+
+		entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(test.Properties.Per_testcase_directory))
 	})
 
 	AndroidMkWriteTestData(test.data, entries)
@@ -451,14 +451,9 @@
 	if installer.path == (android.InstallPath{}) {
 		return
 	}
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if ctx.Target().Os.Class == android.Host {
-		entries.OutputFile = android.OptionalPathForPath(installer.path)
-	}
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		path, file := filepath.Split(installer.path.ToMakePath().String())
+		path, file := filepath.Split(installer.path.String())
 		stem, suffix, _ := android.SplitFileExt(file)
 		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 		entries.SetString("LOCAL_MODULE_PATH", path)
@@ -537,7 +532,7 @@
 		c.libraryDecorator.androidMkWriteExportedFlags(entries)
 
 		if c.shared() || c.static() {
-			path, file := filepath.Split(c.path.ToMakePath().String())
+			path, file := filepath.Split(c.path.String())
 			stem, suffix, ext := android.SplitFileExt(file)
 			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
@@ -588,8 +583,8 @@
 		if p.properties.Check_elf_files != nil {
 			entries.SetBool("LOCAL_CHECK_ELF_FILES", *p.properties.Check_elf_files)
 		} else {
-			// soong_cc_prebuilt.mk does not include check_elf_file.mk by default
-			// because cc_library_shared and cc_binary use soong_cc_prebuilt.mk as well.
+			// soong_cc_rust_prebuilt.mk does not include check_elf_file.mk by default
+			// because cc_library_shared and cc_binary use soong_cc_rust_prebuilt.mk as well.
 			// In order to turn on prebuilt ABI checker, set `LOCAL_CHECK_ELF_FILES` to
 			// true if `p.properties.Check_elf_files` is not specified.
 			entries.SetBool("LOCAL_CHECK_ELF_FILES", true)
diff --git a/cc/binary.go b/cc/binary.go
index b423c50..50175d9 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -18,8 +18,10 @@
 	"path/filepath"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 )
 
 type BinaryLinkerProperties struct {
@@ -62,18 +64,19 @@
 
 func RegisterBinaryBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("cc_binary", BinaryFactory)
-	ctx.RegisterModuleType("cc_binary_host", binaryHostFactory)
+	ctx.RegisterModuleType("cc_binary_host", BinaryHostFactory)
 }
 
 // cc_binary produces a binary that is runnable on a device.
 func BinaryFactory() android.Module {
-	module, _ := NewBinary(android.HostAndDeviceSupported)
+	module, _ := newBinary(android.HostAndDeviceSupported, true)
 	return module.Init()
 }
 
 // cc_binary_host produces a binary that is runnable on a host.
-func binaryHostFactory() android.Module {
-	module, _ := NewBinary(android.HostSupported)
+func BinaryHostFactory() android.Module {
+	module, _ := newBinary(android.HostSupported, true)
+	module.bazelable = true
 	return module.Init()
 }
 
@@ -191,6 +194,10 @@
 // Individual module implementations which comprise a C++ binary should call this function,
 // set some fields on the result, and then call the Init function.
 func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
+	return newBinary(hod, true)
+}
+
+func newBinary(hod android.HostOrDeviceSupported, bazelable bool) (*Module, *binaryDecorator) {
 	module := newModule(hod, android.MultilibFirst)
 	binary := &binaryDecorator{
 		baseLinker:    NewBaseLinker(module.sanitize),
@@ -199,6 +206,7 @@
 	module.compiler = NewBaseCompiler()
 	module.linker = binary
 	module.installer = binary
+	module.bazelable = bazelable
 
 	// Allow module to be added as member of an sdk/module_exports.
 	module.sdkMemberTypes = []android.SdkMemberType{
@@ -343,6 +351,12 @@
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-dynamic-linker")
 	}
 
+	if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() {
+		fatOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName)
+		transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path())
+	}
+
 	builderFlags := flagsToBuilderFlags(flags)
 	stripFlags := flagsToStripFlags(flags)
 	if binary.stripper.NeedsStrip(ctx) {
@@ -387,7 +401,7 @@
 		}
 	}
 
-	var validations android.WritablePaths
+	var validations android.Paths
 
 	// Handle host bionic linker symbols.
 	if ctx.Os() == android.LinuxBionic && !binary.static() {
@@ -410,9 +424,10 @@
 		linkerDeps = append(linkerDeps, deps.EarlySharedLibsDeps...)
 		linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 		linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
+		linkerDeps = append(linkerDeps, ndkSharedLibDeps(ctx)...)
 	}
 
-	linkerDeps = append(linkerDeps, objs.tidyFiles...)
+	validations = append(validations, objs.tidyFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
 	// Register link action.
@@ -541,3 +556,105 @@
 		},
 	})
 }
+
+func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
+	var compatibleWith bazel.StringListAttribute
+	if typ == "cc_binary_host" {
+		//incompatible with android OS
+		compatibleWith.SetSelectValue(bazel.OsConfigurationAxis, android.Android.Name, []string{"@platforms//:incompatible"})
+		compatibleWith.SetSelectValue(bazel.OsConfigurationAxis, bazel.ConditionsDefaultConfigKey, []string{})
+	}
+
+	baseAttrs := bp2BuildParseBaseProps(ctx, m)
+	binaryLinkerAttrs := bp2buildBinaryLinkerProps(ctx, m)
+
+	if proptools.BoolDefault(binaryLinkerAttrs.Linkshared, true) {
+		baseAttrs.implementationDynamicDeps.Add(baseAttrs.protoDependency)
+	} else {
+		baseAttrs.implementationDeps.Add(baseAttrs.protoDependency)
+	}
+
+	attrs := &binaryAttributes{
+		binaryLinkerAttrs: binaryLinkerAttrs,
+
+		Srcs:    baseAttrs.srcs,
+		Srcs_c:  baseAttrs.cSrcs,
+		Srcs_as: baseAttrs.asSrcs,
+
+		Copts:      baseAttrs.copts,
+		Cppflags:   baseAttrs.cppFlags,
+		Conlyflags: baseAttrs.conlyFlags,
+		Asflags:    baseAttrs.asFlags,
+
+		Deps:               baseAttrs.implementationDeps,
+		Dynamic_deps:       baseAttrs.implementationDynamicDeps,
+		Whole_archive_deps: baseAttrs.wholeArchiveDeps,
+		System_deps:        baseAttrs.systemDynamicDeps,
+
+		Local_includes:    baseAttrs.localIncludes,
+		Absolute_includes: baseAttrs.absoluteIncludes,
+		Linkopts:          baseAttrs.linkopts,
+		Link_crt:          baseAttrs.linkCrt,
+		Use_libcrt:        baseAttrs.useLibcrt,
+		Rtti:              baseAttrs.rtti,
+		Stl:               baseAttrs.stl,
+		Cpp_std:           baseAttrs.cppStd,
+
+		Additional_linker_inputs: baseAttrs.additionalLinkerInputs,
+
+		Strip: stripAttributes{
+			Keep_symbols:                 baseAttrs.stripKeepSymbols,
+			Keep_symbols_and_debug_frame: baseAttrs.stripKeepSymbolsAndDebugFrame,
+			Keep_symbols_list:            baseAttrs.stripKeepSymbolsList,
+			All:                          baseAttrs.stripAll,
+			None:                         baseAttrs.stripNone,
+		},
+
+		Target_compatible_with: compatibleWith,
+		Features:               baseAttrs.features,
+	}
+
+	ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_binary",
+		Bzl_load_location: "//build/bazel/rules:cc_binary.bzl",
+	},
+		android.CommonAttributes{Name: m.Name()},
+		attrs)
+}
+
+// binaryAttributes contains Bazel attributes corresponding to a cc binary
+type binaryAttributes struct {
+	binaryLinkerAttrs
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+
+	Copts      bazel.StringListAttribute
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Deps               bazel.LabelListAttribute
+	Dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps bazel.LabelListAttribute
+	System_deps        bazel.LabelListAttribute
+
+	Local_includes    bazel.StringListAttribute
+	Absolute_includes bazel.StringListAttribute
+
+	Linkopts                 bazel.StringListAttribute
+	Additional_linker_inputs bazel.LabelListAttribute
+
+	Link_crt   bazel.BoolAttribute
+	Use_libcrt bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+
+	Strip stripAttributes
+
+	Features bazel.StringListAttribute
+
+	Target_compatible_with bazel.StringListAttribute
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 537f01c..fad40be 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -16,131 +16,165 @@
 import (
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"android/soong/android"
 	"android/soong/bazel"
 
+	"github.com/google/blueprint"
+
 	"github.com/google/blueprint/proptools"
 )
 
+const (
+	cSrcPartition     = "c"
+	asSrcPartition    = "as"
+	cppSrcPartition   = "cpp"
+	protoSrcPartition = "proto"
+)
+
+var (
+	// ignoring case, checks for proto or protos as an independent word in the name, whether at the
+	// beginning, end, or middle. e.g. "proto.foo", "bar-protos", "baz_proto_srcs" would all match
+	filegroupLikelyProtoPattern = regexp.MustCompile("(?i)(^|[^a-z])proto(s)?([^a-z]|$)")
+)
+
 // staticOrSharedAttributes are the Bazel-ified versions of StaticOrSharedProperties --
 // properties which apply to either the shared or static version of a cc_library module.
 type staticOrSharedAttributes struct {
 	Srcs    bazel.LabelListAttribute
 	Srcs_c  bazel.LabelListAttribute
 	Srcs_as bazel.LabelListAttribute
+	Hdrs    bazel.LabelListAttribute
 	Copts   bazel.StringListAttribute
 
-	Static_deps        bazel.LabelListAttribute
-	Dynamic_deps       bazel.LabelListAttribute
-	Whole_archive_deps bazel.LabelListAttribute
+	Deps                              bazel.LabelListAttribute
+	Implementation_deps               bazel.LabelListAttribute
+	Dynamic_deps                      bazel.LabelListAttribute
+	Implementation_dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps                bazel.LabelListAttribute
+	Implementation_whole_archive_deps bazel.LabelListAttribute
 
 	System_dynamic_deps bazel.LabelListAttribute
 }
 
-func groupSrcsByExtension(ctx android.TopDownMutatorContext, srcs bazel.LabelListAttribute) (cppSrcs, cSrcs, asSrcs bazel.LabelListAttribute) {
-	// Branch srcs into three language-specific groups.
-	// C++ is the "catch-all" group, and comprises generated sources because we don't
-	// know the language of these sources until the genrule is executed.
+func groupSrcsByExtension(ctx android.BazelConversionPathContext, srcs bazel.LabelListAttribute) bazel.PartitionToLabelListAttribute {
+	// Check that a module is a filegroup type
+	isFilegroup := func(m blueprint.Module) bool {
+		return ctx.OtherModuleType(m) == "filegroup"
+	}
+
+	// Convert filegroup dependencies into extension-specific filegroups filtered in the filegroup.bzl
+	// macro.
+	addSuffixForFilegroup := func(suffix string) bazel.LabelMapper {
+		return func(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
+			m, exists := ctx.ModuleFromName(label.OriginalModuleName)
+			labelStr := label.Label
+			if !exists || !isFilegroup(m) {
+				return labelStr, false
+			}
+			return labelStr + suffix, true
+		}
+	}
+
+	isProtoFilegroup := func(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
+		m, exists := ctx.ModuleFromName(label.OriginalModuleName)
+		labelStr := label.Label
+		if !exists || !isFilegroup(m) {
+			return labelStr, false
+		}
+		likelyProtos := filegroupLikelyProtoPattern.MatchString(label.OriginalModuleName)
+		return labelStr, likelyProtos
+	}
+
 	// TODO(b/190006308): Handle language detection of sources in a Bazel rule.
-	isCSrcOrFilegroup := func(s string) bool {
-		return strings.HasSuffix(s, ".c") || strings.HasSuffix(s, "_c_srcs")
-	}
+	partitioned := bazel.PartitionLabelListAttribute(ctx, &srcs, bazel.LabelPartitions{
+		protoSrcPartition: bazel.LabelPartition{Extensions: []string{".proto"}, LabelMapper: isProtoFilegroup},
+		cSrcPartition:     bazel.LabelPartition{Extensions: []string{".c"}, LabelMapper: addSuffixForFilegroup("_c_srcs")},
+		asSrcPartition:    bazel.LabelPartition{Extensions: []string{".s", ".S"}, LabelMapper: addSuffixForFilegroup("_as_srcs")},
+		// C++ is the "catch-all" group, and comprises generated sources because we don't
+		// know the language of these sources until the genrule is executed.
+		cppSrcPartition: bazel.LabelPartition{Extensions: []string{".cpp", ".cc", ".cxx", ".mm"}, LabelMapper: addSuffixForFilegroup("_cpp_srcs"), Keep_remainder: true},
+	})
 
-	isAsmSrcOrFilegroup := func(s string) bool {
-		return strings.HasSuffix(s, ".S") || strings.HasSuffix(s, ".s") || strings.HasSuffix(s, "_as_srcs")
-	}
+	return partitioned
+}
 
-	// Check that a module is a filegroup type named <label>.
-	isFilegroupNamed := func(m android.Module, fullLabel string) bool {
-		if ctx.OtherModuleType(m) != "filegroup" {
-			return false
-		}
-		labelParts := strings.Split(fullLabel, ":")
-		if len(labelParts) > 2 {
-			// There should not be more than one colon in a label.
-			panic(fmt.Errorf("%s is not a valid Bazel label for a filegroup", fullLabel))
-		} else {
-			return m.Name() == labelParts[len(labelParts)-1]
-		}
+// bp2BuildParseLibProps returns the attributes for a variant of a cc_library.
+func bp2BuildParseLibProps(ctx android.BazelConversionPathContext, module *Module, isStatic bool) staticOrSharedAttributes {
+	lib, ok := module.compiler.(*libraryDecorator)
+	if !ok {
+		return staticOrSharedAttributes{}
 	}
-
-	// Convert the filegroup dependencies into the extension-specific filegroups
-	// filtered in the filegroup.bzl macro.
-	cppFilegroup := func(label string) string {
-		m, exists := ctx.ModuleFromName(label)
-		if exists {
-			aModule, _ := m.(android.Module)
-			if isFilegroupNamed(aModule, label) {
-				label = label + "_cpp_srcs"
-			}
-		}
-		return label
-	}
-	cFilegroup := func(label string) string {
-		m, exists := ctx.ModuleFromName(label)
-		if exists {
-			aModule, _ := m.(android.Module)
-			if isFilegroupNamed(aModule, label) {
-				label = label + "_c_srcs"
-			}
-		}
-		return label
-	}
-	asFilegroup := func(label string) string {
-		m, exists := ctx.ModuleFromName(label)
-		if exists {
-			aModule, _ := m.(android.Module)
-			if isFilegroupNamed(aModule, label) {
-				label = label + "_as_srcs"
-			}
-		}
-		return label
-	}
-
-	cSrcs = bazel.MapLabelListAttribute(srcs, cFilegroup)
-	cSrcs = bazel.FilterLabelListAttribute(cSrcs, isCSrcOrFilegroup)
-
-	asSrcs = bazel.MapLabelListAttribute(srcs, asFilegroup)
-	asSrcs = bazel.FilterLabelListAttribute(asSrcs, isAsmSrcOrFilegroup)
-
-	cppSrcs = bazel.MapLabelListAttribute(srcs, cppFilegroup)
-	cppSrcs = bazel.SubtractBazelLabelListAttribute(cppSrcs, cSrcs)
-	cppSrcs = bazel.SubtractBazelLabelListAttribute(cppSrcs, asSrcs)
-	return
+	return bp2buildParseStaticOrSharedProps(ctx, module, lib, isStatic)
 }
 
 // bp2buildParseSharedProps returns the attributes for the shared variant of a cc_library.
-func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
-	lib, ok := module.compiler.(*libraryDecorator)
-	if !ok {
-		return staticOrSharedAttributes{}
-	}
-
-	return bp2buildParseStaticOrSharedProps(ctx, module, lib, false)
+func bp2BuildParseSharedProps(ctx android.BazelConversionPathContext, module *Module) staticOrSharedAttributes {
+	return bp2BuildParseLibProps(ctx, module, false)
 }
 
 // bp2buildParseStaticProps returns the attributes for the static variant of a cc_library.
-func bp2BuildParseStaticProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
-	lib, ok := module.compiler.(*libraryDecorator)
-	if !ok {
-		return staticOrSharedAttributes{}
-	}
-
-	return bp2buildParseStaticOrSharedProps(ctx, module, lib, true)
+func bp2BuildParseStaticProps(ctx android.BazelConversionPathContext, module *Module) staticOrSharedAttributes {
+	return bp2BuildParseLibProps(ctx, module, true)
 }
 
-func bp2buildParseStaticOrSharedProps(ctx android.TopDownMutatorContext, module *Module, lib *libraryDecorator, isStatic bool) staticOrSharedAttributes {
+type depsPartition struct {
+	export         bazel.LabelList
+	implementation bazel.LabelList
+}
+
+type bazelLabelForDepsFn func(android.BazelConversionPathContext, []string) bazel.LabelList
+
+func maybePartitionExportedAndImplementationsDeps(ctx android.BazelConversionPathContext, exportsDeps bool, allDeps, exportedDeps []string, fn bazelLabelForDepsFn) depsPartition {
+	if !exportsDeps {
+		return depsPartition{
+			implementation: fn(ctx, allDeps),
+		}
+	}
+
+	implementation, export := android.FilterList(allDeps, exportedDeps)
+
+	return depsPartition{
+		export:         fn(ctx, export),
+		implementation: fn(ctx, implementation),
+	}
+}
+
+type bazelLabelForDepsExcludesFn func(android.BazelConversionPathContext, []string, []string) bazel.LabelList
+
+func maybePartitionExportedAndImplementationsDepsExcludes(ctx android.BazelConversionPathContext, exportsDeps bool, allDeps, excludes, exportedDeps []string, fn bazelLabelForDepsExcludesFn) depsPartition {
+	if !exportsDeps {
+		return depsPartition{
+			implementation: fn(ctx, allDeps, excludes),
+		}
+	}
+	implementation, export := android.FilterList(allDeps, exportedDeps)
+
+	return depsPartition{
+		export:         fn(ctx, export, excludes),
+		implementation: fn(ctx, implementation, excludes),
+	}
+}
+
+func bp2buildParseStaticOrSharedProps(ctx android.BazelConversionPathContext, module *Module, lib *libraryDecorator, isStatic bool) staticOrSharedAttributes {
 	attrs := staticOrSharedAttributes{}
 
 	setAttrs := func(axis bazel.ConfigurationAxis, config string, props StaticOrSharedProperties) {
 		attrs.Copts.SetSelectValue(axis, config, props.Cflags)
 		attrs.Srcs.SetSelectValue(axis, config, android.BazelLabelForModuleSrc(ctx, props.Srcs))
-		attrs.Static_deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, props.Static_libs))
-		attrs.Dynamic_deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, props.Shared_libs))
-		attrs.Whole_archive_deps.SetSelectValue(axis, config, android.BazelLabelForModuleWholeDeps(ctx, props.Whole_static_libs))
-		attrs.System_dynamic_deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, props.System_shared_libs))
+		attrs.System_dynamic_deps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, props.System_shared_libs))
+
+		staticDeps := maybePartitionExportedAndImplementationsDeps(ctx, true, props.Static_libs, props.Export_static_lib_headers, bazelLabelForStaticDeps)
+		attrs.Deps.SetSelectValue(axis, config, staticDeps.export)
+		attrs.Implementation_deps.SetSelectValue(axis, config, staticDeps.implementation)
+
+		sharedDeps := maybePartitionExportedAndImplementationsDeps(ctx, true, props.Shared_libs, props.Export_shared_lib_headers, bazelLabelForSharedDeps)
+		attrs.Dynamic_deps.SetSelectValue(axis, config, sharedDeps.export)
+		attrs.Implementation_dynamic_deps.SetSelectValue(axis, config, sharedDeps.implementation)
+
+		attrs.Whole_archive_deps.SetSelectValue(axis, config, bazelLabelForWholeDeps(ctx, props.Whole_static_libs))
 	}
 	// system_dynamic_deps distinguishes between nil/empty list behavior:
 	//    nil -> use default values
@@ -165,10 +199,15 @@
 		}
 	}
 
-	cppSrcs, cSrcs, asSrcs := groupSrcsByExtension(ctx, attrs.Srcs)
-	attrs.Srcs = cppSrcs
-	attrs.Srcs_c = cSrcs
-	attrs.Srcs_as = asSrcs
+	partitionedSrcs := groupSrcsByExtension(ctx, attrs.Srcs)
+	attrs.Srcs = partitionedSrcs[cppSrcPartition]
+	attrs.Srcs_c = partitionedSrcs[cSrcPartition]
+	attrs.Srcs_as = partitionedSrcs[asSrcPartition]
+
+	if !partitionedSrcs[protoSrcPartition].IsEmpty() {
+		// TODO(b/208815215): determine whether this is used and add support if necessary
+		ctx.ModuleErrorf("Migrating static/shared only proto srcs is not currently supported")
+	}
 
 	return attrs
 }
@@ -178,7 +217,8 @@
 	Src bazel.LabelAttribute
 }
 
-func Bp2BuildParsePrebuiltLibraryProps(ctx android.TopDownMutatorContext, module *Module) prebuiltAttributes {
+// NOTE: Used outside of Soong repo project, in the clangprebuilts.go bootstrap_go_package
+func Bp2BuildParsePrebuiltLibraryProps(ctx android.BazelConversionPathContext, module *Module) prebuiltAttributes {
 	var srcLabelAttribute bazel.LabelAttribute
 
 	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltLinkerProperties{}) {
@@ -201,6 +241,13 @@
 	}
 }
 
+type baseAttributes struct {
+	compilerAttributes
+	linkerAttributes
+
+	protoDependency *bazel.LabelAttribute
+}
+
 // Convenience struct to hold all attributes parsed from compiler properties.
 type compilerAttributes struct {
 	// Options for all languages
@@ -215,225 +262,391 @@
 	cppFlags bazel.StringListAttribute
 	srcs     bazel.LabelListAttribute
 
+	hdrs bazel.LabelListAttribute
+
 	rtti bazel.BoolAttribute
+
+	// Not affected by arch variants
+	stl    *string
+	cStd   *string
+	cppStd *string
+
+	localIncludes    bazel.StringListAttribute
+	absoluteIncludes bazel.StringListAttribute
+
+	includes BazelIncludes
+
+	protoSrcs bazel.LabelListAttribute
 }
 
-// bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
-func bp2BuildParseCompilerProps(ctx android.TopDownMutatorContext, module *Module) compilerAttributes {
-	var srcs bazel.LabelListAttribute
-	var copts bazel.StringListAttribute
-	var asFlags bazel.StringListAttribute
-	var conlyFlags bazel.StringListAttribute
-	var cppFlags bazel.StringListAttribute
-	var rtti bazel.BoolAttribute
+func parseCommandLineFlags(soongFlags []string) []string {
+	var result []string
+	for _, flag := range soongFlags {
+		// Soong's cflags can contain spaces, like `-include header.h`. For
+		// Bazel's copts, split them up to be compatible with the
+		// no_copts_tokenization feature.
+		result = append(result, strings.Split(flag, " ")...)
+	}
+	return result
+}
 
-	// Creates the -I flags for a directory, while making the directory relative
-	// to the exec root for Bazel to work.
-	includeFlags := func(dir string) []string {
-		// filepath.Join canonicalizes the path, i.e. it takes care of . or .. elements.
-		moduleDirRootedPath := filepath.Join(ctx.ModuleDir(), dir)
-		return []string{
-			"-I" + moduleDirRootedPath,
-			// Include the bindir-rooted path (using make variable substitution). This most
-			// closely matches Bazel's native include path handling, which allows for dependency
-			// on generated headers in these directories.
-			// TODO(b/188084383): Handle local include directories in Bazel.
-			"-I$(BINDIR)/" + moduleDirRootedPath,
+func (ca *compilerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversionPathContext, axis bazel.ConfigurationAxis, config string, props *BaseCompilerProperties) {
+	// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
+	// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
+	if srcsList, ok := parseSrcs(ctx, props); ok {
+		ca.srcs.SetSelectValue(axis, config, srcsList)
+	}
+
+	localIncludeDirs := props.Local_include_dirs
+	if axis == bazel.NoConfigAxis {
+		ca.cStd, ca.cppStd = bp2buildResolveCppStdValue(props.C_std, props.Cpp_std, props.Gnu_extensions)
+		if includeBuildDirectory(props.Include_build_directory) {
+			localIncludeDirs = append(localIncludeDirs, ".")
 		}
 	}
 
-	// Parse the list of module-relative include directories (-I).
-	parseLocalIncludeDirs := func(baseCompilerProps *BaseCompilerProperties) []string {
-		// include_dirs are root-relative, not module-relative.
-		includeDirs := bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
-		return append(includeDirs, baseCompilerProps.Local_include_dirs...)
-	}
+	ca.absoluteIncludes.SetSelectValue(axis, config, props.Include_dirs)
+	ca.localIncludes.SetSelectValue(axis, config, localIncludeDirs)
 
-	parseCommandLineFlags := func(soongFlags []string) []string {
-		var result []string
-		for _, flag := range soongFlags {
-			// Soong's cflags can contain spaces, like `-include header.h`. For
-			// Bazel's copts, split them up to be compatible with the
-			// no_copts_tokenization feature.
-			result = append(result, strings.Split(flag, " ")...)
-		}
-		return result
-	}
+	ca.copts.SetSelectValue(axis, config, parseCommandLineFlags(props.Cflags))
+	ca.asFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Asflags))
+	ca.conlyFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Conlyflags))
+	ca.cppFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Cppflags))
+	ca.rtti.SetSelectValue(axis, config, props.Rtti)
+}
 
-	// Parse srcs from an arch or OS's props value.
-	parseSrcs := func(baseCompilerProps *BaseCompilerProperties) bazel.LabelList {
-		// Add srcs-like dependencies such as generated files.
-		// First create a LabelList containing these dependencies, then merge the values with srcs.
-		generatedHdrsAndSrcs := baseCompilerProps.Generated_headers
-		generatedHdrsAndSrcs = append(generatedHdrsAndSrcs, baseCompilerProps.Generated_sources...)
-		generatedHdrsAndSrcsLabelList := android.BazelLabelForModuleDeps(ctx, generatedHdrsAndSrcs)
-
-		allSrcsLabelList := android.BazelLabelForModuleSrcExcludes(ctx, baseCompilerProps.Srcs, baseCompilerProps.Exclude_srcs)
-		return bazel.AppendBazelLabelLists(allSrcsLabelList, generatedHdrsAndSrcsLabelList)
-	}
-
-	archVariantCompilerProps := module.GetArchVariantProperties(ctx, &BaseCompilerProperties{})
-	for axis, configToProps := range archVariantCompilerProps {
-		for config, props := range configToProps {
-			if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-				// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
-				// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
-				if len(baseCompilerProps.Srcs) > 0 || len(baseCompilerProps.Exclude_srcs) > 0 {
-					srcsList := parseSrcs(baseCompilerProps)
-					srcs.SetSelectValue(axis, config, srcsList)
+func (ca *compilerAttributes) convertStlProps(ctx android.ArchVariantContext, module *Module) {
+	stlPropsByArch := module.GetArchVariantProperties(ctx, &StlProperties{})
+	for _, configToProps := range stlPropsByArch {
+		for _, props := range configToProps {
+			if stlProps, ok := props.(*StlProperties); ok {
+				if stlProps.Stl == nil {
+					continue
 				}
-
-				archVariantCopts := parseCommandLineFlags(baseCompilerProps.Cflags)
-				archVariantAsflags := parseCommandLineFlags(baseCompilerProps.Asflags)
-				for _, dir := range parseLocalIncludeDirs(baseCompilerProps) {
-					archVariantCopts = append(archVariantCopts, includeFlags(dir)...)
-					archVariantAsflags = append(archVariantAsflags, includeFlags(dir)...)
+				if ca.stl == nil {
+					ca.stl = stlProps.Stl
+				} else if ca.stl != stlProps.Stl {
+					ctx.ModuleErrorf("Unsupported conversion: module with different stl for different variants: %s and %s", *ca.stl, stlProps.Stl)
 				}
-
-				if axis == bazel.NoConfigAxis {
-					if includeBuildDirectory(baseCompilerProps.Include_build_directory) {
-						flags := includeFlags(".")
-						archVariantCopts = append(archVariantCopts, flags...)
-						archVariantAsflags = append(archVariantAsflags, flags...)
-					}
-				}
-
-				copts.SetSelectValue(axis, config, archVariantCopts)
-				asFlags.SetSelectValue(axis, config, archVariantAsflags)
-				conlyFlags.SetSelectValue(axis, config, parseCommandLineFlags(baseCompilerProps.Conlyflags))
-				cppFlags.SetSelectValue(axis, config, parseCommandLineFlags(baseCompilerProps.Cppflags))
-				rtti.SetSelectValue(axis, config, baseCompilerProps.Rtti)
 			}
 		}
 	}
+}
 
-	srcs.ResolveExcludes()
-
+func (ca *compilerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
 	productVarPropNameToAttribute := map[string]*bazel.StringListAttribute{
-		"Cflags":   &copts,
-		"Asflags":  &asFlags,
-		"CppFlags": &cppFlags,
+		"Cflags":   &ca.copts,
+		"Asflags":  &ca.asFlags,
+		"CppFlags": &ca.cppFlags,
 	}
-	productVariableProps := android.ProductVariableProperties(ctx)
 	for propName, attr := range productVarPropNameToAttribute {
-		if props, exists := productVariableProps[propName]; exists {
-			for _, prop := range props {
-				flags, ok := prop.Property.([]string)
+		if productConfigProps, exists := productVariableProps[propName]; exists {
+			for productConfigProp, prop := range productConfigProps {
+				flags, ok := prop.([]string)
 				if !ok {
 					ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
 				}
-				newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
-				attr.SetSelectValue(bazel.ProductVariableConfigurationAxis(prop.FullConfig), prop.FullConfig, newFlags)
+				newFlags, _ := bazel.TryVariableSubstitutions(flags, productConfigProp.Name)
+				attr.SetSelectValue(productConfigProp.ConfigurationAxis(), productConfigProp.SelectKey(), newFlags)
 			}
 		}
 	}
+}
 
-	srcs, cSrcs, asSrcs := groupSrcsByExtension(ctx, srcs)
+func (ca *compilerAttributes) finalize(ctx android.BazelConversionPathContext, implementationHdrs bazel.LabelListAttribute) {
+	ca.srcs.ResolveExcludes()
+	partitionedSrcs := groupSrcsByExtension(ctx, ca.srcs)
 
-	return compilerAttributes{
-		copts:      copts,
-		srcs:       srcs,
-		asFlags:    asFlags,
-		asSrcs:     asSrcs,
-		cSrcs:      cSrcs,
-		conlyFlags: conlyFlags,
-		cppFlags:   cppFlags,
-		rtti:       rtti,
+	ca.protoSrcs = partitionedSrcs[protoSrcPartition]
+
+	for p, lla := range partitionedSrcs {
+		// if there are no sources, there is no need for headers
+		if lla.IsEmpty() {
+			continue
+		}
+		lla.Append(implementationHdrs)
+		partitionedSrcs[p] = lla
+	}
+
+	ca.srcs = partitionedSrcs[cppSrcPartition]
+	ca.cSrcs = partitionedSrcs[cSrcPartition]
+	ca.asSrcs = partitionedSrcs[asSrcPartition]
+
+	ca.absoluteIncludes.DeduplicateAxesFromBase()
+	ca.localIncludes.DeduplicateAxesFromBase()
+}
+
+// Parse srcs from an arch or OS's props value.
+func parseSrcs(ctx android.BazelConversionPathContext, props *BaseCompilerProperties) (bazel.LabelList, bool) {
+	anySrcs := false
+	// Add srcs-like dependencies such as generated files.
+	// First create a LabelList containing these dependencies, then merge the values with srcs.
+	generatedSrcsLabelList := android.BazelLabelForModuleDepsExcludes(ctx, props.Generated_sources, props.Exclude_generated_sources)
+	if len(props.Generated_sources) > 0 || len(props.Exclude_generated_sources) > 0 {
+		anySrcs = true
+	}
+
+	allSrcsLabelList := android.BazelLabelForModuleSrcExcludes(ctx, props.Srcs, props.Exclude_srcs)
+	if len(props.Srcs) > 0 || len(props.Exclude_srcs) > 0 {
+		anySrcs = true
+	}
+	return bazel.AppendBazelLabelLists(allSrcsLabelList, generatedSrcsLabelList), anySrcs
+}
+
+func bp2buildResolveCppStdValue(c_std *string, cpp_std *string, gnu_extensions *bool) (*string, *string) {
+	var cStdVal, cppStdVal string
+	// If c{,pp}std properties are not specified, don't generate them in the BUILD file.
+	// Defaults are handled by the toolchain definition.
+	// However, if gnu_extensions is false, then the default gnu-to-c version must be specified.
+	if cpp_std != nil {
+		cppStdVal = parseCppStd(cpp_std)
+	} else if gnu_extensions != nil && !*gnu_extensions {
+		cppStdVal = "c++17"
+	}
+	if c_std != nil {
+		cStdVal = parseCStd(c_std)
+	} else if gnu_extensions != nil && !*gnu_extensions {
+		cStdVal = "c99"
+	}
+
+	cStdVal, cppStdVal = maybeReplaceGnuToC(gnu_extensions, cStdVal, cppStdVal)
+	var c_std_prop, cpp_std_prop *string
+	if cStdVal != "" {
+		c_std_prop = &cStdVal
+	}
+	if cppStdVal != "" {
+		cpp_std_prop = &cppStdVal
+	}
+
+	return c_std_prop, cpp_std_prop
+}
+
+// packageFromLabel extracts package from a fully-qualified or relative Label and whether the label
+// is fully-qualified.
+// e.g. fully-qualified "//a/b:foo" -> "a/b", true, relative: ":bar" -> ".", false
+func packageFromLabel(label string) (string, bool) {
+	split := strings.Split(label, ":")
+	if len(split) != 2 {
+		return "", false
+	}
+	if split[0] == "" {
+		return ".", false
+	}
+	// remove leading "//"
+	return split[0][2:], true
+}
+
+// includesFromLabelList extracts relative/absolute includes from a bazel.LabelList>
+func includesFromLabelList(labelList bazel.LabelList) (relative, absolute []string) {
+	for _, hdr := range labelList.Includes {
+		if pkg, hasPkg := packageFromLabel(hdr.Label); hasPkg {
+			absolute = append(absolute, pkg)
+		} else if pkg != "" {
+			relative = append(relative, pkg)
+		}
+	}
+	return relative, absolute
+}
+
+// bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
+func bp2BuildParseBaseProps(ctx android.Bp2buildMutatorContext, module *Module) baseAttributes {
+	archVariantCompilerProps := module.GetArchVariantProperties(ctx, &BaseCompilerProperties{})
+	archVariantLinkerProps := module.GetArchVariantProperties(ctx, &BaseLinkerProperties{})
+
+	var implementationHdrs bazel.LabelListAttribute
+
+	axisToConfigs := map[bazel.ConfigurationAxis]map[string]bool{}
+	allAxesAndConfigs := func(cp android.ConfigurationAxisToArchVariantProperties) {
+		for axis, configMap := range cp {
+			if _, ok := axisToConfigs[axis]; !ok {
+				axisToConfigs[axis] = map[string]bool{}
+			}
+			for config, _ := range configMap {
+				axisToConfigs[axis][config] = true
+			}
+		}
+	}
+	allAxesAndConfigs(archVariantCompilerProps)
+	allAxesAndConfigs(archVariantLinkerProps)
+
+	compilerAttrs := compilerAttributes{}
+	linkerAttrs := linkerAttributes{}
+
+	for axis, configs := range axisToConfigs {
+		for config, _ := range configs {
+			var allHdrs []string
+			if baseCompilerProps, ok := archVariantCompilerProps[axis][config].(*BaseCompilerProperties); ok {
+				allHdrs = baseCompilerProps.Generated_headers
+
+				(&compilerAttrs).bp2buildForAxisAndConfig(ctx, axis, config, baseCompilerProps)
+			}
+
+			var exportHdrs []string
+
+			if baseLinkerProps, ok := archVariantLinkerProps[axis][config].(*BaseLinkerProperties); ok {
+				exportHdrs = baseLinkerProps.Export_generated_headers
+
+				(&linkerAttrs).bp2buildForAxisAndConfig(ctx, module.Binary(), axis, config, baseLinkerProps)
+			}
+			headers := maybePartitionExportedAndImplementationsDeps(ctx, !module.Binary(), allHdrs, exportHdrs, android.BazelLabelForModuleDeps)
+			implementationHdrs.SetSelectValue(axis, config, headers.implementation)
+			compilerAttrs.hdrs.SetSelectValue(axis, config, headers.export)
+
+			exportIncludes, exportAbsoluteIncludes := includesFromLabelList(headers.export)
+			compilerAttrs.includes.Includes.SetSelectValue(axis, config, exportIncludes)
+			compilerAttrs.includes.AbsoluteIncludes.SetSelectValue(axis, config, exportAbsoluteIncludes)
+
+			includes, absoluteIncludes := includesFromLabelList(headers.implementation)
+			currAbsoluteIncludes := compilerAttrs.absoluteIncludes.SelectValue(axis, config)
+			currAbsoluteIncludes = android.FirstUniqueStrings(append(currAbsoluteIncludes, absoluteIncludes...))
+			compilerAttrs.absoluteIncludes.SetSelectValue(axis, config, currAbsoluteIncludes)
+			currIncludes := compilerAttrs.localIncludes.SelectValue(axis, config)
+			currIncludes = android.FirstUniqueStrings(append(currIncludes, includes...))
+			compilerAttrs.localIncludes.SetSelectValue(axis, config, currIncludes)
+		}
+	}
+
+	compilerAttrs.convertStlProps(ctx, module)
+	(&linkerAttrs).convertStripProps(ctx, module)
+
+	productVariableProps := android.ProductVariableProperties(ctx)
+
+	(&compilerAttrs).convertProductVariables(ctx, productVariableProps)
+	(&linkerAttrs).convertProductVariables(ctx, productVariableProps)
+
+	(&compilerAttrs).finalize(ctx, implementationHdrs)
+	(&linkerAttrs).finalize()
+
+	protoDep := bp2buildProto(ctx, module, compilerAttrs.protoSrcs)
+
+	// bp2buildProto will only set wholeStaticLib or implementationWholeStaticLib, but we don't know
+	// which. This will add the newly generated proto library to the appropriate attribute and nothing
+	// to the other
+	(&linkerAttrs).wholeArchiveDeps.Add(protoDep.wholeStaticLib)
+	(&linkerAttrs).implementationWholeArchiveDeps.Add(protoDep.implementationWholeStaticLib)
+
+	return baseAttributes{
+		compilerAttrs,
+		linkerAttrs,
+		protoDep.protoDep,
 	}
 }
 
 // Convenience struct to hold all attributes parsed from linker properties.
 type linkerAttributes struct {
-	deps                          bazel.LabelListAttribute
-	dynamicDeps                   bazel.LabelListAttribute
-	systemDynamicDeps             bazel.LabelListAttribute
-	wholeArchiveDeps              bazel.LabelListAttribute
-	exportedDeps                  bazel.LabelListAttribute
+	deps                           bazel.LabelListAttribute
+	implementationDeps             bazel.LabelListAttribute
+	dynamicDeps                    bazel.LabelListAttribute
+	implementationDynamicDeps      bazel.LabelListAttribute
+	wholeArchiveDeps               bazel.LabelListAttribute
+	implementationWholeArchiveDeps bazel.LabelListAttribute
+	systemDynamicDeps              bazel.LabelListAttribute
+
+	linkCrt                       bazel.BoolAttribute
 	useLibcrt                     bazel.BoolAttribute
+	useVersionLib                 bazel.BoolAttribute
 	linkopts                      bazel.StringListAttribute
-	versionScript                 bazel.LabelAttribute
+	additionalLinkerInputs        bazel.LabelListAttribute
 	stripKeepSymbols              bazel.BoolAttribute
 	stripKeepSymbolsAndDebugFrame bazel.BoolAttribute
 	stripKeepSymbolsList          bazel.StringListAttribute
 	stripAll                      bazel.BoolAttribute
 	stripNone                     bazel.BoolAttribute
+	features                      bazel.StringListAttribute
 }
 
-// FIXME(b/187655838): Use the existing linkerFlags() function instead of duplicating logic here
-func getBp2BuildLinkerFlags(linkerProperties *BaseLinkerProperties) []string {
-	flags := linkerProperties.Ldflags
-	if !BoolDefault(linkerProperties.Pack_relocations, true) {
-		flags = append(flags, "-Wl,--pack-dyn-relocs=none")
+func (la *linkerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversionPathContext, isBinary bool, axis bazel.ConfigurationAxis, config string, props *BaseLinkerProperties) {
+	// Use a single variable to capture usage of nocrt in arch variants, so there's only 1 error message for this module
+	var axisFeatures []string
+
+	// Excludes to parallel Soong:
+	// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=247-249;drc=088b53577dde6e40085ffd737a1ae96ad82fc4b0
+	staticLibs := android.FirstUniqueStrings(props.Static_libs)
+	staticDeps := maybePartitionExportedAndImplementationsDepsExcludes(ctx, !isBinary, staticLibs, props.Exclude_static_libs, props.Export_static_lib_headers, bazelLabelForStaticDepsExcludes)
+
+	headerLibs := android.FirstUniqueStrings(props.Header_libs)
+	hDeps := maybePartitionExportedAndImplementationsDeps(ctx, !isBinary, headerLibs, props.Export_header_lib_headers, bazelLabelForHeaderDeps)
+
+	(&hDeps.export).Append(staticDeps.export)
+	la.deps.SetSelectValue(axis, config, hDeps.export)
+
+	(&hDeps.implementation).Append(staticDeps.implementation)
+	la.implementationDeps.SetSelectValue(axis, config, hDeps.implementation)
+
+	wholeStaticLibs := android.FirstUniqueStrings(props.Whole_static_libs)
+	la.wholeArchiveDeps.SetSelectValue(axis, config, bazelLabelForWholeDepsExcludes(ctx, wholeStaticLibs, props.Exclude_static_libs))
+
+	systemSharedLibs := props.System_shared_libs
+	// systemSharedLibs distinguishes between nil/empty list behavior:
+	//    nil -> use default values
+	//    empty list -> no values specified
+	if len(systemSharedLibs) > 0 {
+		systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
 	}
-	return flags
+	la.systemDynamicDeps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, systemSharedLibs))
+
+	sharedLibs := android.FirstUniqueStrings(props.Shared_libs)
+	sharedDeps := maybePartitionExportedAndImplementationsDepsExcludes(ctx, !isBinary, sharedLibs, props.Exclude_shared_libs, props.Export_shared_lib_headers, bazelLabelForSharedDepsExcludes)
+	la.dynamicDeps.SetSelectValue(axis, config, sharedDeps.export)
+	la.implementationDynamicDeps.SetSelectValue(axis, config, sharedDeps.implementation)
+
+	if !BoolDefault(props.Pack_relocations, packRelocationsDefault) {
+		axisFeatures = append(axisFeatures, "disable_pack_relocations")
+	}
+
+	if Bool(props.Allow_undefined_symbols) {
+		axisFeatures = append(axisFeatures, "-no_undefined_symbols")
+	}
+
+	var linkerFlags []string
+	if len(props.Ldflags) > 0 {
+		linkerFlags = append(linkerFlags, props.Ldflags...)
+		// binaries remove static flag if -shared is in the linker flags
+		if isBinary && android.InList("-shared", linkerFlags) {
+			axisFeatures = append(axisFeatures, "-static_flag")
+		}
+	}
+	if props.Version_script != nil {
+		label := android.BazelLabelForModuleSrcSingle(ctx, *props.Version_script)
+		la.additionalLinkerInputs.SetSelectValue(axis, config, bazel.LabelList{Includes: []bazel.Label{label}})
+		linkerFlags = append(linkerFlags, fmt.Sprintf("-Wl,--version-script,$(location %s)", label.Label))
+	}
+	la.linkopts.SetSelectValue(axis, config, linkerFlags)
+	la.useLibcrt.SetSelectValue(axis, config, props.libCrt())
+
+	if axis == bazel.NoConfigAxis {
+		la.useVersionLib.SetSelectValue(axis, config, props.Use_version_lib)
+	}
+
+	// it's very unlikely for nocrt to be arch variant, so bp2build doesn't support it.
+	if props.crt() != nil {
+		if axis == bazel.NoConfigAxis {
+			la.linkCrt.SetSelectValue(axis, config, props.crt())
+		} else if axis == bazel.ArchConfigurationAxis {
+			ctx.ModuleErrorf("nocrt is not supported for arch variants")
+		}
+	}
+
+	if axisFeatures != nil {
+		la.features.SetSelectValue(axis, config, axisFeatures)
+	}
 }
 
-// bp2BuildParseLinkerProps parses the linker properties of a module, including
-// configurable attribute values.
-func bp2BuildParseLinkerProps(ctx android.TopDownMutatorContext, module *Module) linkerAttributes {
-	var headerDeps bazel.LabelListAttribute
-	var staticDeps bazel.LabelListAttribute
-	var exportedDeps bazel.LabelListAttribute
-	var dynamicDeps bazel.LabelListAttribute
-	var wholeArchiveDeps bazel.LabelListAttribute
-	systemSharedDeps := bazel.LabelListAttribute{ForceSpecifyEmptyList: true}
-	var linkopts bazel.StringListAttribute
-	var versionScript bazel.LabelAttribute
-	var useLibcrt bazel.BoolAttribute
-
-	var stripKeepSymbols bazel.BoolAttribute
-	var stripKeepSymbolsAndDebugFrame bazel.BoolAttribute
-	var stripKeepSymbolsList bazel.StringListAttribute
-	var stripAll bazel.BoolAttribute
-	var stripNone bazel.BoolAttribute
-
+func (la *linkerAttributes) convertStripProps(ctx android.BazelConversionPathContext, module *Module) {
 	for axis, configToProps := range module.GetArchVariantProperties(ctx, &StripProperties{}) {
 		for config, props := range configToProps {
 			if stripProperties, ok := props.(*StripProperties); ok {
-				stripKeepSymbols.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols)
-				stripKeepSymbolsList.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_list)
-				stripKeepSymbolsAndDebugFrame.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_and_debug_frame)
-				stripAll.SetSelectValue(axis, config, stripProperties.Strip.All)
-				stripNone.SetSelectValue(axis, config, stripProperties.Strip.None)
+				la.stripKeepSymbols.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols)
+				la.stripKeepSymbolsList.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_list)
+				la.stripKeepSymbolsAndDebugFrame.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_and_debug_frame)
+				la.stripAll.SetSelectValue(axis, config, stripProperties.Strip.All)
+				la.stripNone.SetSelectValue(axis, config, stripProperties.Strip.None)
 			}
 		}
 	}
+}
 
-	for axis, configToProps := range module.GetArchVariantProperties(ctx, &BaseLinkerProperties{}) {
-		for config, props := range configToProps {
-			if baseLinkerProps, ok := props.(*BaseLinkerProperties); ok {
-				// Excludes to parallel Soong:
-				// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=247-249;drc=088b53577dde6e40085ffd737a1ae96ad82fc4b0
-				staticLibs := android.FirstUniqueStrings(baseLinkerProps.Static_libs)
-				staticDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDepsExcludes(ctx, staticLibs, baseLinkerProps.Exclude_static_libs))
-				wholeArchiveLibs := android.FirstUniqueStrings(baseLinkerProps.Whole_static_libs)
-				wholeArchiveDeps.SetSelectValue(axis, config, android.BazelLabelForModuleWholeDepsExcludes(ctx, wholeArchiveLibs, baseLinkerProps.Exclude_static_libs))
-
-				systemSharedLibs := baseLinkerProps.System_shared_libs
-				// systemSharedLibs distinguishes between nil/empty list behavior:
-				//    nil -> use default values
-				//    empty list -> no values specified
-				if len(systemSharedLibs) > 0 {
-					systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
-				}
-				systemSharedDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, systemSharedLibs))
-
-				sharedLibs := android.FirstUniqueStrings(baseLinkerProps.Shared_libs)
-				dynamicDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDepsExcludes(ctx, sharedLibs, baseLinkerProps.Exclude_shared_libs))
-
-				headerLibs := android.FirstUniqueStrings(baseLinkerProps.Header_libs)
-				headerDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, headerLibs))
-				exportedLibs := android.FirstUniqueStrings(baseLinkerProps.Export_header_lib_headers)
-				exportedDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, exportedLibs))
-
-				linkopts.SetSelectValue(axis, config, getBp2BuildLinkerFlags(baseLinkerProps))
-				if baseLinkerProps.Version_script != nil {
-					versionScript.SetSelectValue(axis, config, android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script))
-				}
-				useLibcrt.SetSelectValue(axis, config, baseLinkerProps.libCrt())
-			}
-		}
-	}
+func (la *linkerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
 
 	type productVarDep struct {
 		// the name of the corresponding excludes field, if one exists
@@ -446,12 +659,11 @@
 
 	productVarToDepFields := map[string]productVarDep{
 		// product variables do not support exclude_shared_libs
-		"Shared_libs":       productVarDep{attribute: &dynamicDeps, depResolutionFunc: android.BazelLabelForModuleDepsExcludes},
-		"Static_libs":       productVarDep{"Exclude_static_libs", &staticDeps, android.BazelLabelForModuleDepsExcludes},
-		"Whole_static_libs": productVarDep{"Exclude_static_libs", &wholeArchiveDeps, android.BazelLabelForModuleWholeDepsExcludes},
+		"Shared_libs":       {attribute: &la.implementationDynamicDeps, depResolutionFunc: bazelLabelForSharedDepsExcludes},
+		"Static_libs":       {"Exclude_static_libs", &la.implementationDeps, bazelLabelForStaticDepsExcludes},
+		"Whole_static_libs": {"Exclude_static_libs", &la.wholeArchiveDeps, bazelLabelForWholeDepsExcludes},
 	}
 
-	productVariableProps := android.ProductVariableProperties(ctx)
 	for name, dep := range productVarToDepFields {
 		props, exists := productVariableProps[name]
 		excludeProps, excludesExists := productVariableProps[dep.excludesField]
@@ -459,57 +671,47 @@
 		if !exists && !excludesExists {
 			continue
 		}
-		// collect all the configurations that an include or exclude property exists for.
-		// we want to iterate all configurations rather than either the include or exclude because for a
-		// particular configuration we may have only and include or only an exclude to handle
-		configs := make(map[string]bool, len(props)+len(excludeProps))
-		for config := range props {
-			configs[config] = true
+		// Collect all the configurations that an include or exclude property exists for.
+		// We want to iterate all configurations rather than either the include or exclude because, for a
+		// particular configuration, we may have either only an include or an exclude to handle.
+		productConfigProps := make(map[android.ProductConfigProperty]bool, len(props)+len(excludeProps))
+		for p := range props {
+			productConfigProps[p] = true
 		}
-		for config := range excludeProps {
-			configs[config] = true
+		for p := range excludeProps {
+			productConfigProps[p] = true
 		}
 
-		for config := range configs {
-			prop, includesExists := props[config]
-			excludesProp, excludesExists := excludeProps[config]
+		for productConfigProp := range productConfigProps {
+			prop, includesExists := props[productConfigProp]
+			excludesProp, excludesExists := excludeProps[productConfigProp]
 			var includes, excludes []string
 			var ok bool
 			// if there was no includes/excludes property, casting fails and that's expected
-			if includes, ok = prop.Property.([]string); includesExists && !ok {
+			if includes, ok = prop.([]string); includesExists && !ok {
 				ctx.ModuleErrorf("Could not convert product variable %s property", name)
 			}
-			if excludes, ok = excludesProp.Property.([]string); excludesExists && !ok {
+			if excludes, ok = excludesProp.([]string); excludesExists && !ok {
 				ctx.ModuleErrorf("Could not convert product variable %s property", dep.excludesField)
 			}
 
-			dep.attribute.SetSelectValue(bazel.ProductVariableConfigurationAxis(config), config, dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes))
+			dep.attribute.EmitEmptyList = productConfigProp.AlwaysEmit()
+			dep.attribute.SetSelectValue(
+				productConfigProp.ConfigurationAxis(),
+				productConfigProp.SelectKey(),
+				dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes),
+			)
 		}
 	}
+}
 
-	staticDeps.ResolveExcludes()
-	dynamicDeps.ResolveExcludes()
-	wholeArchiveDeps.ResolveExcludes()
-
-	headerDeps.Append(staticDeps)
-
-	return linkerAttributes{
-		deps:              headerDeps,
-		exportedDeps:      exportedDeps,
-		dynamicDeps:       dynamicDeps,
-		systemDynamicDeps: systemSharedDeps,
-		wholeArchiveDeps:  wholeArchiveDeps,
-		linkopts:          linkopts,
-		useLibcrt:         useLibcrt,
-		versionScript:     versionScript,
-
-		// Strip properties
-		stripKeepSymbols:              stripKeepSymbols,
-		stripKeepSymbolsAndDebugFrame: stripKeepSymbolsAndDebugFrame,
-		stripKeepSymbolsList:          stripKeepSymbolsList,
-		stripAll:                      stripAll,
-		stripNone:                     stripNone,
-	}
+func (la *linkerAttributes) finalize() {
+	la.deps.ResolveExcludes()
+	la.implementationDeps.ResolveExcludes()
+	la.dynamicDeps.ResolveExcludes()
+	la.implementationDynamicDeps.ResolveExcludes()
+	la.wholeArchiveDeps.ResolveExcludes()
+	la.systemDynamicDeps.ForceSpecifyEmptyList = true
 }
 
 // Relativize a list of root-relative paths with respect to the module's
@@ -535,49 +737,133 @@
 	return relativePaths
 }
 
-func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
-	libraryDecorator := module.linker.(*libraryDecorator)
-	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+// BazelIncludes contains information about -I and -isystem paths from a module converted to Bazel
+// attributes.
+type BazelIncludes struct {
+	AbsoluteIncludes bazel.StringListAttribute
+	Includes         bazel.StringListAttribute
+	SystemIncludes   bazel.StringListAttribute
 }
 
-func Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
+func bp2BuildParseExportedIncludes(ctx android.BazelConversionPathContext, module *Module, existingIncludes BazelIncludes) BazelIncludes {
+	libraryDecorator := module.linker.(*libraryDecorator)
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator, &existingIncludes)
+}
+
+// Bp2buildParseExportedIncludesForPrebuiltLibrary returns a BazelIncludes with Bazel-ified values
+// to export includes from the underlying module's properties.
+func Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx android.BazelConversionPathContext, module *Module) BazelIncludes {
 	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
 	libraryDecorator := prebuiltLibraryLinker.libraryDecorator
-	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator, nil)
 }
 
 // bp2BuildParseExportedIncludes creates a string list attribute contains the
 // exported included directories of a module.
-func bp2BuildParseExportedIncludesHelper(ctx android.TopDownMutatorContext, module *Module, libraryDecorator *libraryDecorator) bazel.StringListAttribute {
-	// Export_system_include_dirs and export_include_dirs are already module dir
-	// relative, so they don't need to be relativized like include_dirs, which
-	// are root-relative.
-	includeDirs := libraryDecorator.flagExporter.Properties.Export_system_include_dirs
-	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
-	var includeDirsAttribute bazel.StringListAttribute
-
-	getVariantIncludeDirs := func(includeDirs []string, flagExporterProperties *FlagExporterProperties, subtract bool) []string {
-		variantIncludeDirs := flagExporterProperties.Export_system_include_dirs
-		variantIncludeDirs = append(variantIncludeDirs, flagExporterProperties.Export_include_dirs...)
-
-		if subtract {
-			// To avoid duplicate includes when base includes + arch includes are combined
-			// TODO: Add something similar to ResolveExcludes() in bazel/properties.go
-			variantIncludeDirs = bazel.SubtractStrings(variantIncludeDirs, includeDirs)
-		}
-		return variantIncludeDirs
+func bp2BuildParseExportedIncludesHelper(ctx android.BazelConversionPathContext, module *Module, libraryDecorator *libraryDecorator, includes *BazelIncludes) BazelIncludes {
+	var exported BazelIncludes
+	if includes != nil {
+		exported = *includes
+	} else {
+		exported = BazelIncludes{}
 	}
-
 	for axis, configToProps := range module.GetArchVariantProperties(ctx, &FlagExporterProperties{}) {
 		for config, props := range configToProps {
 			if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-				archVariantIncludeDirs := getVariantIncludeDirs(includeDirs, flagExporterProperties, axis != bazel.NoConfigAxis)
-				if len(archVariantIncludeDirs) > 0 {
-					includeDirsAttribute.SetSelectValue(axis, config, archVariantIncludeDirs)
+				if len(flagExporterProperties.Export_include_dirs) > 0 {
+					exported.Includes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.Includes.SelectValue(axis, config), flagExporterProperties.Export_include_dirs...)))
+				}
+				if len(flagExporterProperties.Export_system_include_dirs) > 0 {
+					exported.SystemIncludes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.SystemIncludes.SelectValue(axis, config), flagExporterProperties.Export_system_include_dirs...)))
 				}
 			}
 		}
 	}
+	exported.AbsoluteIncludes.DeduplicateAxesFromBase()
+	exported.Includes.DeduplicateAxesFromBase()
+	exported.SystemIncludes.DeduplicateAxesFromBase()
 
-	return includeDirsAttribute
+	return exported
+}
+
+func bazelLabelForStaticModule(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	label := android.BazelModuleLabel(ctx, m)
+	if aModule, ok := m.(android.Module); ok {
+		if ctx.OtherModuleType(aModule) == "cc_library" && !android.GenerateCcLibraryStaticOnly(m.Name()) {
+			label += "_bp2build_cc_library_static"
+		}
+	}
+	return label
+}
+
+func bazelLabelForSharedModule(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	// cc_library, at it's root name, propagates the shared library, which depends on the static
+	// library.
+	return android.BazelModuleLabel(ctx, m)
+}
+
+func bazelLabelForStaticWholeModuleDeps(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	label := bazelLabelForStaticModule(ctx, m)
+	if aModule, ok := m.(android.Module); ok {
+		if android.IsModulePrebuilt(aModule) {
+			label += "_alwayslink"
+		}
+	}
+	return label
+}
+
+func bazelLabelForWholeDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForStaticWholeModuleDeps)
+}
+
+func bazelLabelForWholeDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForStaticWholeModuleDeps)
+}
+
+func bazelLabelForStaticDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForStaticModule)
+}
+
+func bazelLabelForStaticDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForStaticModule)
+}
+
+func bazelLabelForSharedDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForSharedModule)
+}
+
+func bazelLabelForHeaderDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	// This is not elegant, but bp2build's shared library targets only propagate
+	// their header information as part of the normal C++ provider.
+	return bazelLabelForSharedDeps(ctx, modules)
+}
+
+func bazelLabelForSharedDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForSharedModule)
+}
+
+type binaryLinkerAttrs struct {
+	Linkshared *bool
+}
+
+func bp2buildBinaryLinkerProps(ctx android.BazelConversionPathContext, m *Module) binaryLinkerAttrs {
+	attrs := binaryLinkerAttrs{}
+	archVariantProps := m.GetArchVariantProperties(ctx, &BinaryLinkerProperties{})
+	for axis, configToProps := range archVariantProps {
+		for _, p := range configToProps {
+			props := p.(*BinaryLinkerProperties)
+			staticExecutable := props.Static_executable
+			if axis == bazel.NoConfigAxis {
+				if linkBinaryShared := !proptools.Bool(staticExecutable); !linkBinaryShared {
+					attrs.Linkshared = &linkBinaryShared
+				}
+			} else if staticExecutable != nil {
+				// TODO(b/202876379): Static_executable is arch-variant; however, linkshared is a
+				// nonconfigurable attribute. Only 4 AOSP modules use this feature, defer handling
+				ctx.ModuleErrorf("bp2build cannot migrate a module with arch/target-specific static_executable values")
+			}
+		}
+	}
+
+	return attrs
 }
diff --git a/cc/builder.go b/cc/builder.go
index 748377b..fa7f7a3 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -21,6 +21,7 @@
 import (
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -164,6 +165,12 @@
 		}
 	}()
 
+	darwinLipo = pctx.AndroidStaticRule("darwinLipo",
+		blueprint.RuleParams{
+			Command:     "${config.MacLipoPath} -create -output $out $in",
+			CommandDeps: []string{"${config.MacLipoPath}"},
+		})
+
 	_ = pctx.SourcePathVariable("archiveRepackPath", "build/soong/scripts/archive_repack.sh")
 
 	// Rule to repack an archive (.a) file with a subset of object files.
@@ -198,13 +205,24 @@
 	// Rule for invoking clang-tidy (a clang-based linter).
 	clangTidy, clangTidyRE = pctx.RemoteStaticRules("clangTidy",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $in -- $cFlags && touch $out",
-			CommandDeps: []string{"${config.ClangBin}/clang-tidy"},
+			Depfile: "${out}.d",
+			Deps:    blueprint.DepsGCC,
+			// Pick bash because some machines with old /bin/sh cannot handle arrays.
+			// All $cFlags and $tidyFlags should have single quotes escaped.
+			// Assume no single quotes in other parameters like $in, $out, $ccCmd.
+			Command: "/bin/bash -c 'SRCF=$in; TIDYF=$out; CLANGFLAGS=($cFlags); " +
+				"rm -f $$TIDYF $${TIDYF}.d && " +
+				"${config.CcWrapper}$ccCmd \"$${CLANGFLAGS[@]}\" -E -o /dev/null $$SRCF " +
+				"-MQ $$TIDYF -MD -MF $${TIDYF}.d && " +
+				"$tidyVars $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $$SRCF " +
+				"-- \"$${CLANGFLAGS[@]}\" && touch $$TIDYF'",
+			CommandDeps: []string{"${config.ClangBin}/clang-tidy", "$ccCmd"},
 		},
 		&remoteexec.REParams{
-			Labels:       map[string]string{"type": "lint", "tool": "clang-tidy", "lang": "cpp"},
-			ExecStrategy: "${config.REClangTidyExecStrategy}",
-			Inputs:       []string{"$in"},
+			Labels:               map[string]string{"type": "lint", "tool": "clang-tidy", "lang": "cpp"},
+			ExecStrategy:         "${config.REClangTidyExecStrategy}",
+			Inputs:               []string{"$in"},
+			EnvironmentVariables: []string{"TIDY_TIMEOUT"},
 			// Although clang-tidy has an option to "fix" source files, that feature is hardly useable
 			// under parallel compilation and RBE. So we assume no OutputFiles here.
 			// The clang-tidy fix option is best run locally in single thread.
@@ -212,7 +230,7 @@
 			// (1) New timestamps trigger clang and clang-tidy compilations again.
 			// (2) Changing source files caused concurrent clang or clang-tidy jobs to crash.
 			Platform: map[string]string{remoteexec.PoolKey: "${config.REClangTidyPool}"},
-		}, []string{"cFlags", "tidyFlags"}, []string{})
+		}, []string{"ccCmd", "cFlags", "tidyFlags", "tidyVars"}, []string{})
 
 	_ = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
 
@@ -226,14 +244,6 @@
 		},
 		"asFlags")
 
-	// Rule to invoke windres, for interaction with Windows resources.
-	windres = pctx.AndroidStaticRule("windres",
-		blueprint.RuleParams{
-			Command:     "$windresCmd $flags -I$$(dirname $in) -i $in -o $out --preprocessor \"${config.ClangBin}/clang -E -xc-header -DRC_INVOKED\"",
-			CommandDeps: []string{"$windresCmd"},
-		},
-		"windresCmd", "flags")
-
 	_ = pctx.SourcePathVariable("sAbiDumper", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-dumper")
 
 	// -w has been added since header-abi-dumper does not need to produce any sort of diagnostic information.
@@ -435,15 +445,30 @@
 	}
 }
 
+func escapeSingleQuotes(s string) string {
+	// Replace single quotes to work when embedded in a single quoted string for bash.
+	// Relying on string concatenation of bash to get A'B from quoted 'A'\''B'.
+	return strings.Replace(s, `'`, `'\''`, -1)
+}
+
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
-func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles android.Paths,
+func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles, noTidySrcs android.Paths,
 	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
 
 	// Source files are one-to-one with tidy, coverage, or kythe files, if enabled.
 	objFiles := make(android.Paths, len(srcFiles))
 	var tidyFiles android.Paths
+	noTidySrcsMap := make(map[android.Path]bool)
+	var tidyVars string
 	if flags.tidy {
 		tidyFiles = make(android.Paths, 0, len(srcFiles))
+		for _, path := range noTidySrcs {
+			noTidySrcsMap[path] = true
+		}
+		tidyTimeout := ctx.Config().Getenv("TIDY_TIMEOUT")
+		if len(tidyTimeout) > 0 {
+			tidyVars += "TIDY_TIMEOUT=" + tidyTimeout
+		}
 	}
 	var coverageFiles android.Paths
 	if flags.gcovCoverage {
@@ -504,6 +529,40 @@
 	cppflags += " ${config.NoOverrideGlobalCflags}"
 	toolingCppflags += " ${config.NoOverrideGlobalCflags}"
 
+	modulePath := android.PathForModuleSrc(ctx).String()
+	if android.IsThirdPartyPath(modulePath) {
+		cflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCflags += " ${config.NoOverrideExternalGlobalCflags}"
+		cppflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCppflags += " ${config.NoOverrideExternalGlobalCflags}"
+	}
+
+	// Multiple source files have build rules usually share the same cFlags or tidyFlags.
+	// Define only one version in this module and share it in multiple build rules.
+	// To simplify the code, the shared variables are all named as $flags<nnn>.
+	numSharedFlags := 0
+	flagsMap := make(map[string]string)
+
+	// Share flags only when there are multiple files or tidy rules.
+	var hasMultipleRules = len(srcFiles) > 1 || flags.tidy
+
+	var shareFlags = func(kind string, flags string) string {
+		if !hasMultipleRules || len(flags) < 60 {
+			// Modules have long names and so do the module variables.
+			// It does not save space by replacing a short name with a long one.
+			return flags
+		}
+		mapKey := kind + flags
+		n, ok := flagsMap[mapKey]
+		if !ok {
+			numSharedFlags += 1
+			n = strconv.Itoa(numSharedFlags)
+			flagsMap[mapKey] = n
+			ctx.Variable(pctx, kind+n, flags)
+		}
+		return "$" + kind + n
+	}
+
 	for i, srcFile := range srcFiles {
 		objFile := android.ObjPathWithExt(ctx, subdir, srcFile, "o")
 
@@ -520,21 +579,7 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"asFlags": flags.globalYasmFlags + " " + flags.localYasmFlags,
-				},
-			})
-			continue
-		case ".rc":
-			ctx.Build(pctx, android.BuildParams{
-				Rule:        windres,
-				Description: "windres " + srcFile.Rel(),
-				Output:      objFile,
-				Input:       srcFile,
-				Implicits:   cFlagsDeps,
-				OrderOnly:   pathDeps,
-				Args: map[string]string{
-					"windresCmd": mingwCmd(flags.toolchain, "windres"),
-					"flags":      flags.toolchain.WindresFlags(),
+					"asFlags": shareFlags("asFlags", flags.globalYasmFlags+" "+flags.localYasmFlags),
 				},
 			})
 			continue
@@ -602,8 +647,8 @@
 			Implicits:       cFlagsDeps,
 			OrderOnly:       pathDeps,
 			Args: map[string]string{
-				"cFlags": moduleFlags,
-				"ccCmd":  ccCmd,
+				"cFlags": shareFlags("cFlags", moduleFlags),
+				"ccCmd":  ccCmd, // short and not shared
 			},
 		})
 
@@ -618,13 +663,14 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags": moduleFlags,
+					"cFlags": shareFlags("cFlags", moduleFlags),
 				},
 			})
 			kytheFiles = append(kytheFiles, kytheFile)
 		}
 
-		if tidy {
+		//  Even with tidy, some src file could be skipped by noTidySrcsMap.
+		if tidy && !noTidySrcsMap[srcFile] {
 			tidyFile := android.ObjPathWithExt(ctx, subdir, srcFile, "tidy")
 			tidyFiles = append(tidyFiles, tidyFile)
 
@@ -638,14 +684,13 @@
 				Description: "clang-tidy " + srcFile.Rel(),
 				Output:      tidyFile,
 				Input:       srcFile,
-				// We must depend on objFile, since clang-tidy doesn't
-				// support exporting dependencies.
-				Implicit:  objFile,
-				Implicits: cFlagsDeps,
-				OrderOnly: pathDeps,
+				Implicits:   cFlagsDeps,
+				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags":    moduleToolingFlags,
-					"tidyFlags": config.TidyFlagsForSrcFile(srcFile, flags.tidyFlags),
+					"ccCmd":     ccCmd,
+					"cFlags":    shareFlags("cFlags", escapeSingleQuotes(moduleToolingFlags)),
+					"tidyFlags": shareFlags("tidyFlags", escapeSingleQuotes(config.TidyFlagsForSrcFile(srcFile, flags.tidyFlags))),
+					"tidyVars":  tidyVars, // short and not shared
 				},
 			})
 		}
@@ -667,8 +712,8 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags":     moduleToolingFlags,
-					"exportDirs": flags.sAbiFlags,
+					"cFlags":     shareFlags("cFlags", moduleToolingFlags),
+					"exportDirs": shareFlags("exportDirs", flags.sAbiFlags),
 				},
 			})
 		}
@@ -687,7 +732,7 @@
 // Generate a rule for compiling multiple .o files to a static library (.a)
 func transformObjToStaticLib(ctx android.ModuleContext,
 	objFiles android.Paths, wholeStaticLibs android.Paths,
-	flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
+	flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths, validations android.Paths) {
 
 	arCmd := "${config.ClangBin}/llvm-ar"
 	arFlags := ""
@@ -702,6 +747,7 @@
 			Output:      outputFile,
 			Inputs:      objFiles,
 			Implicits:   deps,
+			Validations: validations,
 			Args: map[string]string{
 				"arFlags": "crsPD" + arFlags,
 				"arCmd":   arCmd,
@@ -731,7 +777,7 @@
 func transformObjToDynamicBinary(ctx android.ModuleContext,
 	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps, crtBegin, crtEnd android.Paths,
 	groupLate bool, flags builderFlags, outputFile android.WritablePath,
-	implicitOutputs android.WritablePaths, validations android.WritablePaths) {
+	implicitOutputs android.WritablePaths, validations android.Paths) {
 
 	ldCmd := "${config.ClangBin}/clang++"
 
@@ -798,7 +844,7 @@
 		Inputs:          objFiles,
 		Implicits:       deps,
 		OrderOnly:       sharedLibs,
-		Validations:     validations.Paths(),
+		Validations:     validations,
 		Args:            args,
 	})
 }
@@ -916,8 +962,7 @@
 }
 
 // Generate a rule for extracting a table of contents from a shared library (.so)
-func transformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path,
-	outputFile android.WritablePath, flags builderFlags) {
+func TransformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path, outputFile android.WritablePath) {
 
 	var format string
 	if ctx.Darwin() {
@@ -1028,6 +1073,15 @@
 	})
 }
 
+func transformDarwinUniversalBinary(ctx android.ModuleContext, outputFile android.WritablePath, inputFiles ...android.Path) {
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        darwinLipo,
+		Description: "lipo " + outputFile.Base(),
+		Output:      outputFile,
+		Inputs:      inputFiles,
+	})
+}
+
 // Registers build statement to zip one or more coverage files.
 func transformCoverageFilesToZip(ctx android.ModuleContext,
 	inputs Objects, baseName string) android.OptionalPath {
diff --git a/cc/cc.go b/cc/cc.go
index b0c0299..22baf30 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -31,6 +31,7 @@
 	"android/soong/cc/config"
 	"android/soong/fuzz"
 	"android/soong/genrule"
+	"android/soong/snapshot"
 )
 
 func init() {
@@ -94,6 +95,7 @@
 
 	// Used for data dependencies adjacent to tests
 	DataLibs []string
+	DataBins []string
 
 	// Used by DepsMutator to pass system_shared_libs information to check_elf_file.py.
 	SystemSharedLibs []string
@@ -165,6 +167,10 @@
 
 	// Path to the dynamic linker binary
 	DynamicLinker android.OptionalPath
+
+	// For Darwin builds, the path to the second architecture's output that should
+	// be combined with this architectures's output into a FAT MachO file.
+	DarwinSecondArchOutput android.OptionalPath
 }
 
 // LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module
@@ -323,7 +329,7 @@
 	SnapshotStaticLibs  []string `blueprint:"mutated"`
 	SnapshotRuntimeLibs []string `blueprint:"mutated"`
 
-	Installable *bool
+	Installable *bool `android:"arch_variant"`
 
 	// Set by factories of module types that can only be referenced from variants compiled against
 	// the SDK.
@@ -680,6 +686,15 @@
 	return d.Kind == staticLibraryDependency
 }
 
+func (d libraryDependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.shared() {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	}
+	return nil
+}
+
+var _ android.LicenseAnnotationsDependencyTag = libraryDependencyTag{}
+
 // InstallDepNeeded returns true for shared libraries so that shared library dependencies of
 // binaries or other shared libraries are installed as dependencies.
 func (d libraryDependencyTag) InstallDepNeeded() bool {
@@ -717,6 +732,7 @@
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
 	dataLibDepTag         = dependencyTag{name: "data lib"}
+	dataBinDepTag         = dependencyTag{name: "data bin"}
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
@@ -770,8 +786,9 @@
 	Properties       BaseProperties
 
 	// initialize before calling Init
-	hod      android.HostOrDeviceSupported
-	multilib android.Multilib
+	hod       android.HostOrDeviceSupported
+	multilib  android.Multilib
+	bazelable bool
 
 	// Allowable SdkMemberTypes of this module type.
 	sdkMemberTypes []android.SdkMemberType
@@ -812,6 +829,10 @@
 	makeLinkType string
 	// Kythe (source file indexer) paths for this compilation module
 	kytheFiles android.Paths
+	// Object .o file output paths for this compilation module
+	objFiles android.Paths
+	// Tidy .tidy file output paths for this compilation module
+	tidyFiles android.Paths
 
 	// For apex variants, this is set as apex.min_sdk_version
 	apexSdkVersion android.ApiLevel
@@ -1130,7 +1151,9 @@
 	}
 
 	android.InitAndroidArchModule(c, c.hod, c.multilib)
-	android.InitBazelModule(c)
+	if c.bazelable {
+		android.InitBazelModule(c)
+	}
 	android.InitApexModule(c)
 	android.InitSdkAwareModule(c)
 	android.InitDefaultableModule(c)
@@ -1708,8 +1731,18 @@
 
 // Returns true if Bazel was successfully used for the analysis of this module.
 func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
-	bazelModuleLabel := c.GetBazelLabel(actx, c)
+	var bazelModuleLabel string
+	if actx.ModuleType() == "cc_library" && c.static() {
+		// cc_library is a special case in bp2build; two targets are generated -- one for each
+		// of the shared and static variants. The shared variant keeps the module name, but the
+		// static variant uses a different suffixed name.
+		bazelModuleLabel = bazelLabelForStaticModule(actx, c)
+	} else {
+		bazelModuleLabel = c.GetBazelLabel(actx, c)
+	}
 	bazelActionsUsed := false
+	// Mixed builds mode is disabled for modules outside of device OS.
+	// TODO(b/200841190): Support non-device OS in mixed builds.
 	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
 		bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
 	}
@@ -1813,15 +1846,6 @@
 
 	flags.AssemblerWithCpp = inList("-xassembler-with-cpp", flags.Local.AsFlags)
 
-	// Optimization to reduce size of build.ninja
-	// Replace the long list of flags for each file with a module-local variable
-	ctx.Variable(pctx, "cflags", strings.Join(flags.Local.CFlags, " "))
-	ctx.Variable(pctx, "cppflags", strings.Join(flags.Local.CppFlags, " "))
-	ctx.Variable(pctx, "asflags", strings.Join(flags.Local.AsFlags, " "))
-	flags.Local.CFlags = []string{"$cflags"}
-	flags.Local.CppFlags = []string{"$cppflags"}
-	flags.Local.AsFlags = []string{"$asflags"}
-
 	var objs Objects
 	if c.compiler != nil {
 		objs = c.compiler.compile(ctx, flags, deps)
@@ -1829,6 +1853,8 @@
 			return
 		}
 		c.kytheFiles = objs.kytheFiles
+		c.objFiles = objs.objFiles
+		c.tidyFiles = objs.tidyFiles
 	}
 
 	if c.linker != nil {
@@ -1869,7 +1895,7 @@
 }
 
 func (c *Module) maybeInstall(ctx ModuleContext, apexInfo android.ApexInfo) {
-	if !proptools.BoolDefault(c.Properties.Installable, true) {
+	if !proptools.BoolDefault(c.Installable(), true) {
 		// If the module has been specifically configure to not be installed then
 		// hide from make as otherwise it will break when running inside make
 		// as the output path to install will not be specified. Not all uninstallable
@@ -2280,6 +2306,8 @@
 		{Mutator: "link", Variation: "shared"},
 	}, dataLibDepTag, deps.DataLibs...)
 
+	actx.AddVariationDependencies(nil, dataBinDepTag, deps.DataBins...)
+
 	actx.AddVariationDependencies([]blueprint.Variation{
 		{Mutator: "link", Variation: "shared"},
 	}, runtimeDepTag, deps.RuntimeLibs...)
@@ -2570,6 +2598,11 @@
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
 
+		if depTag == android.DarwinUniversalVariantTag {
+			depPaths.DarwinSecondArchOutput = dep.(*Module).OutputFile()
+			return
+		}
+
 		ccDep, ok := dep.(LinkableInterface)
 		if !ok {
 
@@ -3153,6 +3186,24 @@
 	return false
 }
 
+func (c *Module) benchmarkBinary() bool {
+	if b, ok := c.linker.(interface {
+		benchmarkBinary() bool
+	}); ok {
+		return b.benchmarkBinary()
+	}
+	return false
+}
+
+func (c *Module) fuzzBinary() bool {
+	if f, ok := c.linker.(interface {
+		fuzzBinary() bool
+	}); ok {
+		return f.fuzzBinary()
+	}
+	return false
+}
+
 // Header returns true if the module is a header-only variant. (See cc/library.go header()).
 func (c *Module) Header() bool {
 	if h, ok := c.linker.(interface {
@@ -3251,16 +3302,6 @@
 	return c.Properties.Test_for
 }
 
-func (c *Module) UniqueApexVariations() bool {
-	if u, ok := c.compiler.(interface {
-		uniqueApexVariations() bool
-	}); ok {
-		return u.uniqueApexVariations()
-	} else {
-		return false
-	}
-}
-
 func (c *Module) EverInstallable() bool {
 	return c.installer != nil &&
 		// Check to see whether the module is actually ever installable.
@@ -3272,6 +3313,11 @@
 }
 
 func (c *Module) Installable() *bool {
+	if c.library != nil {
+		if i := c.library.installable(); i != nil {
+			return i
+		}
+	}
 	return c.Properties.Installable
 }
 
@@ -3401,16 +3447,53 @@
 	return c.IsStubs() || c.Target().NativeBridge == android.NativeBridgeEnabled
 }
 
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
+
+// ConvertWithBp2build converts Module to Bazel for bp2build.
+func (c *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	prebuilt := c.IsPrebuilt()
+	if c.Binary() {
+		if !prebuilt {
+			binaryBp2build(ctx, c, ctx.ModuleType())
+		}
+	} else if c.Object() {
+		if !prebuilt {
+			objectBp2Build(ctx, c)
+		}
+	} else if c.CcLibrary() {
+		static := c.BuildStaticVariant()
+		shared := c.BuildSharedVariant()
+
+		if static && shared {
+			if !prebuilt {
+				libraryBp2Build(ctx, c)
+			}
+		} else if !static && !shared {
+			if !prebuilt {
+				libraryHeadersBp2Build(ctx, c)
+			}
+		} else if static {
+			if prebuilt {
+				prebuiltLibraryStaticBp2Build(ctx, c)
+			} else {
+				sharedOrStaticLibraryBp2Build(ctx, c, true)
+			}
+		} else if shared {
+			if prebuilt {
+				prebuiltLibrarySharedBp2Build(ctx, c)
+			} else {
+				sharedOrStaticLibraryBp2Build(ctx, c, false)
+			}
+		}
+	}
+}
+
 //
 // Defaults
 //
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
-	// target. This is primarily useful for modules that were architecture specific and instead are
-	// handled in Bazel as a select().
-	android.BazelModuleBase
 	android.ApexModuleBase
 }
 
@@ -3455,10 +3538,9 @@
 		&android.ProtoProperties{},
 		// RustBindgenProperties is included here so that cc_defaults can be used for rust_bindgen modules.
 		&RustBindgenClangProperties{},
+		&prebuiltLinkerProperties{},
 	)
 
-	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
-	android.InitBazelModule(module)
 	android.InitDefaultsModule(module)
 
 	return module
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 84c3a86..bcc6fcd 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -346,7 +346,7 @@
 
 func checkVndkLibrariesOutput(t *testing.T, ctx *android.TestContext, module string, expected []string) {
 	t.Helper()
-	got := ctx.ModuleForTests(module, "").Module().(*vndkLibrariesTxt).fileNames
+	got := ctx.ModuleForTests(module, "android_common").Module().(*vndkLibrariesTxt).fileNames
 	assertArrayString(t, got, expected)
 }
 
@@ -532,11 +532,11 @@
 	CheckSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLib2ndPath, variant2nd)
 
 	snapshotConfigsPath := filepath.Join(snapshotVariantPath, "configs")
-	CheckSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "")
-	CheckSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "")
-	CheckSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "")
-	CheckSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "")
-	CheckSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "android_common")
 
 	checkVndkOutput(t, ctx, "vndk/vndk.libraries.txt", []string{
 		"LLNDK: libc.so",
@@ -614,7 +614,7 @@
 	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
-	module := ctx.ModuleForTests("llndk.libraries.txt", "")
+	module := ctx.ModuleForTests("llndk.libraries.txt", "android_common")
 	entries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
 	assertArrayString(t, entries.EntryMap["LOCAL_MODULE_STEM"], []string{"llndk.libraries.29.txt"})
 }
@@ -730,9 +730,16 @@
 			gtest: false,
 		}
 
+		cc_binary {
+			name: "test_bin",
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "both",
+		}
+
 		cc_test {
 			name: "main_test",
 			data_libs: ["test_lib"],
+			data_bins: ["test_bin"],
 			gtest: false,
 		}
  `
@@ -750,10 +757,10 @@
 		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
 	}
 	if len(outputFiles) != 1 {
-		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
-	if len(testBinary.dataPaths()) != 1 {
-		t.Errorf("expected exactly one test data file. test data files: [%s]", testBinary.dataPaths())
+	if len(testBinary.dataPaths()) != 2 {
+		t.Fatalf("expected exactly one test data file. test data files: [%s]", testBinary.dataPaths())
 	}
 
 	outputPath := outputFiles[0].String()
@@ -766,6 +773,10 @@
 		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
 	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":test_bin:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_bin:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
+	}
 }
 
 func TestVndkWhenVndkVersionIsNotSet(t *testing.T) {
@@ -3585,6 +3596,58 @@
 	}
 }
 
+func TestAidlFlagsWithMinSdkVersion(t *testing.T) {
+	for _, tc := range []struct {
+		name       string
+		sdkVersion string
+		variant    string
+		expected   string
+	}{
+		{
+			name:       "default is current",
+			sdkVersion: "",
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "platform_apis",
+		},
+		{
+			name:       "use sdk_version",
+			sdkVersion: `sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "platform_apis",
+		},
+		{
+			name:       "use sdk_version(sdk variant)",
+			sdkVersion: `sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_sdk_static",
+			expected:   "29",
+		},
+		{
+			name:       "use min_sdk_version",
+			sdkVersion: `min_sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "29",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := testCc(t, `
+				cc_library {
+					name: "libfoo",
+					stl: "none",
+					srcs: ["a/Foo.aidl"],
+					`+tc.sdkVersion+`
+				}
+			`)
+			libfoo := ctx.ModuleForTests("libfoo", tc.variant)
+			manifest := android.RuleBuilderSboxProtoForTests(t, libfoo.Output("aidl.sbox.textproto"))
+			aidlCommand := manifest.Commands[0].GetCommand()
+			expectedAidlFlag := "--min_sdk_version=" + tc.expected
+			if !strings.Contains(aidlCommand, expectedAidlFlag) {
+				t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+			}
+		})
+	}
+}
+
 func TestMinSdkVersionInClangTriple(t *testing.T) {
 	ctx := testCc(t, `
 		cc_library_shared {
@@ -3869,10 +3932,99 @@
 }
 
 func TestIncludeDirectoryOrdering(t *testing.T) {
-	bp := `
+	baseExpectedFlags := []string{
+		"${config.ArmThumbCflags}",
+		"${config.ArmCflags}",
+		"${config.CommonGlobalCflags}",
+		"${config.DeviceGlobalCflags}",
+		"${config.ExternalCflags}",
+		"${config.ArmToolchainCflags}",
+		"${config.ArmArmv7ANeonCflags}",
+		"${config.ArmGenericCflags}",
+		"-target",
+		"armv7a-linux-androideabi20",
+		"-B${config.ArmGccRoot}/arm-linux-androideabi/bin",
+	}
+
+	expectedIncludes := []string{
+		"external/foo/android_arm_export_include_dirs",
+		"external/foo/lib32_export_include_dirs",
+		"external/foo/arm_export_include_dirs",
+		"external/foo/android_export_include_dirs",
+		"external/foo/linux_export_include_dirs",
+		"external/foo/export_include_dirs",
+		"external/foo/android_arm_local_include_dirs",
+		"external/foo/lib32_local_include_dirs",
+		"external/foo/arm_local_include_dirs",
+		"external/foo/android_local_include_dirs",
+		"external/foo/linux_local_include_dirs",
+		"external/foo/local_include_dirs",
+		"external/foo",
+		"external/foo/libheader1",
+		"external/foo/libheader2",
+		"external/foo/libwhole1",
+		"external/foo/libwhole2",
+		"external/foo/libstatic1",
+		"external/foo/libstatic2",
+		"external/foo/libshared1",
+		"external/foo/libshared2",
+		"external/foo/liblinux",
+		"external/foo/libandroid",
+		"external/foo/libarm",
+		"external/foo/lib32",
+		"external/foo/libandroid_arm",
+		"defaults/cc/common/ndk_libc++_shared",
+		"defaults/cc/common/ndk_libandroid_support",
+	}
+
+	conly := []string{"-fPIC", "${config.CommonGlobalConlyflags}"}
+	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
+
+	cflags := []string{"-Wall", "-Werror"}
+	cstd := []string{"-std=gnu99"}
+	cppstd := []string{"-std=gnu++17", "-fno-rtti"}
+
+	lastIncludes := []string{
+		"out/soong/ndk/sysroot/usr/include",
+		"out/soong/ndk/sysroot/usr/include/arm-linux-androideabi",
+	}
+
+	combineSlices := func(slices ...[]string) []string {
+		var ret []string
+		for _, s := range slices {
+			ret = append(ret, s...)
+		}
+		return ret
+	}
+
+	testCases := []struct {
+		name     string
+		src      string
+		expected []string
+	}{
+		{
+			name:     "c",
+			src:      "foo.c",
+			expected: combineSlices(baseExpectedFlags, conly, expectedIncludes, cflags, cstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+		},
+		{
+			name:     "cc",
+			src:      "foo.cc",
+			expected: combineSlices(baseExpectedFlags, cppOnly, expectedIncludes, cflags, cppstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+		},
+		{
+			name:     "assemble",
+			src:      "foo.s",
+			expected: combineSlices(baseExpectedFlags, []string{"-D__ASSEMBLY__"}, expectedIncludes, lastIncludes),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			bp := fmt.Sprintf(`
 		cc_library {
 			name: "libfoo",
-			srcs: ["foo.c"],
+			srcs: ["%s"],
 			local_include_dirs: ["local_include_dirs"],
 			export_include_dirs: ["export_include_dirs"],
 			export_system_include_dirs: ["export_system_include_dirs"],
@@ -3928,24 +4080,24 @@
 			sdk_version: "20",
 			stl: "none",
 		}
-	`
+	`, tc.src)
 
-	libs := []string{
-		"libstatic1",
-		"libstatic2",
-		"libwhole1",
-		"libwhole2",
-		"libshared1",
-		"libshared2",
-		"libandroid",
-		"libandroid_arm",
-		"liblinux",
-		"lib32",
-		"libarm",
-	}
+			libs := []string{
+				"libstatic1",
+				"libstatic2",
+				"libwhole1",
+				"libwhole2",
+				"libshared1",
+				"libshared2",
+				"libandroid",
+				"libandroid_arm",
+				"liblinux",
+				"lib32",
+				"libarm",
+			}
 
-	for _, lib := range libs {
-		bp += fmt.Sprintf(`
+			for _, lib := range libs {
+				bp += fmt.Sprintf(`
 			cc_library {
 				name: "%s",
 				export_include_dirs: ["%s"],
@@ -3953,69 +4105,30 @@
 				stl: "none",
 			}
 		`, lib, lib)
+			}
+
+			ctx := android.GroupFixturePreparers(
+				PrepareForIntegrationTestWithCc,
+				android.FixtureAddTextFile("external/foo/Android.bp", bp),
+			).RunTest(t)
+			// Use the arm variant instead of the arm64 variant so that it gets headers from
+			// ndk_libandroid_support to test LateStaticLibs.
+			cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
+
+			var includes []string
+			flags := strings.Split(cflags, " ")
+			for _, flag := range flags {
+				if strings.HasPrefix(flag, "-I") {
+					includes = append(includes, strings.TrimPrefix(flag, "-I"))
+				} else if flag == "-isystem" {
+					// skip isystem, include next
+				} else if len(flag) > 0 {
+					includes = append(includes, flag)
+				}
+			}
+
+			android.AssertArrayString(t, "includes", tc.expected, includes)
+		})
 	}
 
-	ctx := android.GroupFixturePreparers(
-		PrepareForIntegrationTestWithCc,
-		android.FixtureAddTextFile("external/foo/Android.bp", bp),
-	).RunTest(t)
-	// Use the arm variant instead of the arm64 variant so that it gets headers from
-	// ndk_libandroid_support to test LateStaticLibs.
-	cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
-
-	var includes []string
-	flags := strings.Split(cflags, " ")
-	for i, flag := range flags {
-		if strings.Contains(flag, "Cflags") {
-			includes = append(includes, flag)
-		} else if strings.HasPrefix(flag, "-I") {
-			includes = append(includes, strings.TrimPrefix(flag, "-I"))
-		} else if flag == "-isystem" {
-			includes = append(includes, flags[i+1])
-		}
-	}
-
-	want := []string{
-		"${config.ArmThumbCflags}",
-		"${config.ArmCflags}",
-		"${config.CommonGlobalCflags}",
-		"${config.DeviceGlobalCflags}",
-		"${config.ExternalCflags}",
-		"${config.ArmToolchainCflags}",
-		"${config.ArmArmv7ANeonCflags}",
-		"${config.ArmGenericCflags}",
-		"external/foo/android_arm_export_include_dirs",
-		"external/foo/lib32_export_include_dirs",
-		"external/foo/arm_export_include_dirs",
-		"external/foo/android_export_include_dirs",
-		"external/foo/linux_export_include_dirs",
-		"external/foo/export_include_dirs",
-		"external/foo/android_arm_local_include_dirs",
-		"external/foo/lib32_local_include_dirs",
-		"external/foo/arm_local_include_dirs",
-		"external/foo/android_local_include_dirs",
-		"external/foo/linux_local_include_dirs",
-		"external/foo/local_include_dirs",
-		"external/foo",
-		"external/foo/libheader1",
-		"external/foo/libheader2",
-		"external/foo/libwhole1",
-		"external/foo/libwhole2",
-		"external/foo/libstatic1",
-		"external/foo/libstatic2",
-		"external/foo/libshared1",
-		"external/foo/libshared2",
-		"external/foo/liblinux",
-		"external/foo/libandroid",
-		"external/foo/libarm",
-		"external/foo/lib32",
-		"external/foo/libandroid_arm",
-		"defaults/cc/common/ndk_libc++_shared",
-		"defaults/cc/common/ndk_libandroid_support",
-		"out/soong/ndk/sysroot/usr/include",
-		"out/soong/ndk/sysroot/usr/include/arm-linux-androideabi",
-		"${config.NoOverrideGlobalCflags}",
-	}
-
-	android.AssertArrayString(t, "includes", want, includes)
 }
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index b96d8b0..75e1faf 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -44,11 +44,9 @@
 var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil)
 
 const (
-	// Environment variables used to control the behavior of this singleton.
-	envVariableCollectCCDeps = "SOONG_COLLECT_CC_DEPS"
-	ccdepsJsonFileName       = "module_bp_cc_deps.json"
-	cClang                   = "clang"
-	cppClang                 = "clang++"
+	ccdepsJsonFileName = "module_bp_cc_deps.json"
+	cClang             = "clang"
+	cppClang           = "clang++"
 )
 
 type ccIdeInfo struct {
@@ -83,10 +81,7 @@
 }
 
 func (c *ccdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !ctx.Config().IsEnvTrue(envVariableCollectCCDeps) {
-		return
-	}
-
+	// (b/204397180) Generate module_bp_cc_deps.json by default.
 	moduleDeps := ccDeps{}
 	moduleInfos := map[string]ccIdeInfo{}
 
diff --git a/cc/compiler.go b/cc/compiler.go
index 34d68a1..8adc3ab 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -39,6 +39,9 @@
 	// or filegroup using the syntax ":module".
 	Srcs []string `android:"path,arch_variant"`
 
+	// list of source files that should not be compiled with clang-tidy.
+	Tidy_disabled_srcs []string `android:"path,arch_variant"`
+
 	// list of source files that should not be used to build the C/C++ module.
 	// This is most useful in the arch/multilib variants to remove non-common files
 	Exclude_srcs []string `android:"path,arch_variant"`
@@ -206,15 +209,6 @@
 
 	// Build and link with OpenMP
 	Openmp *bool `android:"arch_variant"`
-
-	// Deprecated.
-	// Adds __ANDROID_APEX_<APEX_MODULE_NAME>__ macro defined for apex variants in addition to __ANDROID_APEX__
-	Use_apex_name_macro *bool
-
-	// Adds two macros for apex variants in addition to __ANDROID_APEX__
-	// * __ANDROID_APEX_COM_ANDROID_FOO__
-	// * __ANDROID_APEX_NAME__="com.android.foo"
-	UseApexNameMacro bool `blueprint:"mutated"`
 }
 
 func NewBaseCompiler() *baseCompiler {
@@ -288,10 +282,6 @@
 	return deps
 }
 
-func (compiler *baseCompiler) useApexNameMacro() bool {
-	return Bool(compiler.Properties.Use_apex_name_macro) || compiler.Properties.UseApexNameMacro
-}
-
 // Return true if the module is in the WarningAllowedProjects.
 func warningsAreAllowed(subdir string) bool {
 	subdir += "/"
@@ -302,6 +292,38 @@
 	getNamedMapForConfig(ctx.Config(), key).Store(module, true)
 }
 
+func maybeReplaceGnuToC(gnuExtensions *bool, cStd string, cppStd string) (string, string) {
+	if gnuExtensions != nil && *gnuExtensions == false {
+		cStd = gnuToCReplacer.Replace(cStd)
+		cppStd = gnuToCReplacer.Replace(cppStd)
+	}
+	return cStd, cppStd
+}
+
+func parseCppStd(cppStdPtr *string) string {
+	cppStd := String(cppStdPtr)
+	switch cppStd {
+	case "":
+		return config.CppStdVersion
+	case "experimental":
+		return config.ExperimentalCppStdVersion
+	default:
+		return cppStd
+	}
+}
+
+func parseCStd(cStdPtr *string) string {
+	cStd := String(cStdPtr)
+	switch cStd {
+	case "":
+		return config.CStdVersion
+	case "experimental":
+		return config.ExperimentalCStdVersion
+	default:
+		return cStd
+	}
+}
+
 // Create a Flags struct that collects the compile flags from global values,
 // per-target values, module type values, and per-module Blueprints properties
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
@@ -383,10 +405,6 @@
 
 	if ctx.apexVariationName() != "" {
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX__")
-		if compiler.useApexNameMacro() {
-			flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX_"+makeDefineString(ctx.apexVariationName())+"__")
-			flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX_NAME__='\""+ctx.apexVariationName()+"\"'")
-		}
 		if ctx.Device() {
 			flags.Global.CommonFlags = append(flags.Global.CommonFlags,
 				fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
@@ -474,25 +492,10 @@
 
 	flags.Global.CommonFlags = append(flags.Global.CommonFlags, tc.ToolchainCflags())
 
-	cStd := config.CStdVersion
-	if String(compiler.Properties.C_std) == "experimental" {
-		cStd = config.ExperimentalCStdVersion
-	} else if String(compiler.Properties.C_std) != "" {
-		cStd = String(compiler.Properties.C_std)
-	}
+	cStd := parseCStd(compiler.Properties.C_std)
+	cppStd := parseCppStd(compiler.Properties.Cpp_std)
 
-	cppStd := String(compiler.Properties.Cpp_std)
-	switch String(compiler.Properties.Cpp_std) {
-	case "":
-		cppStd = config.CppStdVersion
-	case "experimental":
-		cppStd = config.ExperimentalCppStdVersion
-	}
-
-	if compiler.Properties.Gnu_extensions != nil && *compiler.Properties.Gnu_extensions == false {
-		cStd = gnuToCReplacer.Replace(cStd)
-		cppStd = gnuToCReplacer.Replace(cppStd)
-	}
+	cStd, cppStd = maybeReplaceGnuToC(compiler.Properties.Gnu_extensions, cStd, cppStd)
 
 	flags.Local.ConlyFlags = append([]string{"-std=" + cStd}, flags.Local.ConlyFlags...)
 	flags.Local.CppFlags = append([]string{"-std=" + cppStd}, flags.Local.CppFlags...)
@@ -541,11 +544,6 @@
 			"-I"+android.PathForModuleGen(ctx, "yacc", ctx.ModuleDir()).String())
 	}
 
-	if compiler.hasSrcExt(".mc") {
-		flags.Local.CommonFlags = append(flags.Local.CommonFlags,
-			"-I"+android.PathForModuleGen(ctx, "windmc", ctx.ModuleDir()).String())
-	}
-
 	if compiler.hasSrcExt(".aidl") {
 		flags.aidlFlags = append(flags.aidlFlags, compiler.Properties.Aidl.Flags...)
 		if len(compiler.Properties.Aidl.Local_include_dirs) > 0 {
@@ -561,6 +559,12 @@
 			flags.aidlFlags = append(flags.aidlFlags, "-t")
 		}
 
+		aidlMinSdkVersion := ctx.minSdkVersion()
+		if aidlMinSdkVersion == "" {
+			aidlMinSdkVersion = "platform_apis"
+		}
+		flags.aidlFlags = append(flags.aidlFlags, "--min_sdk_version="+aidlMinSdkVersion)
+
 		flags.Local.CommonFlags = append(flags.Local.CommonFlags,
 			"-I"+android.PathForModuleGen(ctx, "aidl").String())
 	}
@@ -621,10 +625,6 @@
 	return false
 }
 
-func (compiler *baseCompiler) uniqueApexVariations() bool {
-	return compiler.useApexNameMacro()
-}
-
 var invalidDefineCharRegex = regexp.MustCompile("[^a-zA-Z0-9_]")
 
 // makeDefineString transforms a name of an APEX module into a value to be used as value for C define
@@ -637,9 +637,9 @@
 
 func ndkPathDeps(ctx ModuleContext) android.Paths {
 	if ctx.Module().(*Module).IsSdkVariant() {
-		// The NDK sysroot timestamp file depends on all the NDK sysroot files
-		// (headers and libraries).
-		return android.Paths{getNdkBaseTimestampFile(ctx)}
+		// The NDK sysroot timestamp file depends on all the NDK sysroot header files
+		// for compiling src to obj files.
+		return android.Paths{getNdkHeadersTimestampFile(ctx)}
 	}
 	return nil
 }
@@ -663,7 +663,9 @@
 	compiler.srcs = srcs
 
 	// Compile files listed in c.Properties.Srcs into objects
-	objs := compileObjs(ctx, buildFlags, "", srcs, pathDeps, compiler.cFlagsDeps)
+	objs := compileObjs(ctx, buildFlags, "", srcs,
+		android.PathsForModuleSrc(ctx, compiler.Properties.Tidy_disabled_srcs),
+		pathDeps, compiler.cFlagsDeps)
 
 	if ctx.Failed() {
 		return Objects{}
@@ -673,10 +675,10 @@
 }
 
 // Compile a list of source files into objects a specified subdirectory
-func compileObjs(ctx android.ModuleContext, flags builderFlags,
-	subdir string, srcFiles, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
+func compileObjs(ctx android.ModuleContext, flags builderFlags, subdir string,
+	srcFiles, noTidySrcs, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
 
-	return transformSourceToObj(ctx, subdir, srcFiles, flags, pathDeps, cFlagsDeps)
+	return transformSourceToObj(ctx, subdir, srcFiles, noTidySrcs, flags, pathDeps, cFlagsDeps)
 }
 
 // Properties for rust_bindgen related to generating rust bindings.
diff --git a/cc/config/Android.bp b/cc/config/Android.bp
index 3e8ee48..7b7ee28 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -24,7 +24,7 @@
         "x86_device.go",
         "x86_64_device.go",
 
-        "x86_darwin_host.go",
+        "darwin_host.go",
         "x86_linux_host.go",
         "x86_linux_bionic_host.go",
         "x86_windows_host.go",
diff --git a/cc/config/OWNERS b/cc/config/OWNERS
index 701db92..580f215 100644
--- a/cc/config/OWNERS
+++ b/cc/config/OWNERS
@@ -1,3 +1,3 @@
 per-file vndk.go = smoreland@google.com, victoryang@google.com
-per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
+per-file clang.go,global.go,tidy.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
 
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
index d19f5ac..4797acc 100644
--- a/cc/config/bp2build.go
+++ b/cc/config/bp2build.go
@@ -16,9 +16,13 @@
 
 import (
 	"fmt"
+	"reflect"
 	"regexp"
 	"sort"
 	"strings"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
 )
 
 const (
@@ -26,18 +30,27 @@
 )
 
 type bazelVarExporter interface {
-	asBazel(exportedStringVariables, exportedStringListVariables) []bazelConstant
+	asBazel(android.Config, exportedStringVariables, exportedStringListVariables, exportedConfigDependingVariables) []bazelConstant
 }
 
 // Helpers for exporting cc configuration information to Bazel.
 var (
-	// Map containing toolchain variables that are independent of the
+	// Maps containing toolchain variables that are independent of the
 	// environment variables of the build.
 	exportedStringListVars     = exportedStringListVariables{}
 	exportedStringVars         = exportedStringVariables{}
 	exportedStringListDictVars = exportedStringListDictVariables{}
+
+	/// Maps containing variables that are dependent on the build config.
+	exportedConfigDependingVars = exportedConfigDependingVariables{}
 )
 
+type exportedConfigDependingVariables map[string]interface{}
+
+func (m exportedConfigDependingVariables) Set(k string, v interface{}) {
+	m[k] = v
+}
+
 // Ensure that string s has no invalid characters to be generated into the bzl file.
 func validateCharacters(s string) string {
 	for _, c := range []string{`\n`, `"`, `\`} {
@@ -74,10 +87,14 @@
 	return strings.Join(list, "\n")
 }
 
-func (m exportedStringVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []bazelConstant {
+func (m exportedStringVariables) asBazel(config android.Config,
+	stringVars exportedStringVariables, stringListVars exportedStringListVariables, cfgDepVars exportedConfigDependingVariables) []bazelConstant {
 	ret := make([]bazelConstant, 0, len(m))
 	for k, variableValue := range m {
-		expandedVar := expandVar(variableValue, exportedStringVars, exportedStringListVars)
+		expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
+		if err != nil {
+			panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
+		}
 		if len(expandedVar) > 1 {
 			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
 		}
@@ -101,7 +118,9 @@
 	m[k] = v
 }
 
-func (m exportedStringListVariables) asBazel(stringScope exportedStringVariables, stringListScope exportedStringListVariables) []bazelConstant {
+func (m exportedStringListVariables) asBazel(config android.Config,
+	stringScope exportedStringVariables, stringListScope exportedStringListVariables,
+	exportedVars exportedConfigDependingVariables) []bazelConstant {
 	ret := make([]bazelConstant, 0, len(m))
 	// For each exported variable, recursively expand elements in the variableValue
 	// list to ensure that interpolated variables are expanded according to their values
@@ -109,7 +128,11 @@
 	for k, variableValue := range m {
 		var expandedVars []string
 		for _, v := range variableValue {
-			expandedVars = append(expandedVars, expandVar(v, stringScope, stringListScope)...)
+			expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
+			if err != nil {
+				panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
+			}
+			expandedVars = append(expandedVars, expandedVar...)
 		}
 		// Assign the list as a bzl-private variable; this variable will be exported
 		// out through a constants struct later.
@@ -121,6 +144,18 @@
 	return ret
 }
 
+// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
+func exportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
+	exportedConfigDependingVars.Set(name, method)
+	return pctx.VariableConfigMethod(name, method)
+}
+
+// Convenience function to declare a static "source path" variable and export it to Bazel's cc_toolchain.
+func exportSourcePathVariable(name string, value string) {
+	pctx.SourcePathVariable(name, value)
+	exportedStringVars.Set(name, value)
+}
+
 // Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
 func exportStringListStaticVariable(name string, value []string) {
 	pctx.StaticVariable(name, strings.Join(value, " "))
@@ -145,7 +180,8 @@
 }
 
 // Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
-func (m exportedStringListDictVariables) asBazel(_ exportedStringVariables, _ exportedStringListVariables) []bazelConstant {
+func (m exportedStringListDictVariables) asBazel(_ android.Config, _ exportedStringVariables,
+	_ exportedStringListVariables, _ exportedConfigDependingVariables) []bazelConstant {
 	ret := make([]bazelConstant, 0, len(m))
 	for k, dict := range m {
 		ret = append(ret, bazelConstant{
@@ -158,19 +194,20 @@
 
 // BazelCcToolchainVars generates bzl file content containing variables for
 // Bazel's cc_toolchain configuration.
-func BazelCcToolchainVars() string {
+func BazelCcToolchainVars(config android.Config) string {
 	return bazelToolchainVars(
+		config,
 		exportedStringListDictVars,
 		exportedStringListVars,
 		exportedStringVars)
 }
 
-func bazelToolchainVars(vars ...bazelVarExporter) string {
+func bazelToolchainVars(config android.Config, vars ...bazelVarExporter) string {
 	ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
 
 	results := []bazelConstant{}
 	for _, v := range vars {
-		results = append(results, v.asBazel(exportedStringVars, exportedStringListVars)...)
+		results = append(results, v.asBazel(config, exportedStringVars, exportedStringListVars, exportedConfigDependingVars)...)
 	}
 
 	sort.Slice(results, func(i, j int) bool { return results[i].variableName < results[j].variableName })
@@ -201,34 +238,35 @@
 // string slice than to handle a pass-by-referenced map, which would make it
 // quite complex to track depth-first interpolations. It's also unlikely the
 // interpolation stacks are deep (n > 1).
-func expandVar(toExpand string, stringScope exportedStringVariables, stringListScope exportedStringListVariables) []string {
+func expandVar(config android.Config, toExpand string, stringScope exportedStringVariables,
+	stringListScope exportedStringListVariables, exportedVars exportedConfigDependingVariables) ([]string, error) {
 	// e.g. "${ExternalCflags}"
 	r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
 
 	// Internal recursive function.
-	var expandVarInternal func(string, map[string]bool) []string
-	expandVarInternal = func(toExpand string, seenVars map[string]bool) []string {
-		var ret []string
-		for _, v := range strings.Split(toExpand, " ") {
-			matches := r.FindStringSubmatch(v)
+	var expandVarInternal func(string, map[string]bool) (string, error)
+	expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
+		var ret string
+		remainingString := toExpand
+		for len(remainingString) > 0 {
+			matches := r.FindStringSubmatch(remainingString)
 			if len(matches) == 0 {
-				return []string{v}
+				return ret + remainingString, nil
 			}
-
 			if len(matches) != 2 {
-				panic(fmt.Errorf(
-					"Expected to only match 1 subexpression in %s, got %d",
-					v,
-					len(matches)-1))
+				panic(fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", remainingString, len(matches)-1))
 			}
+			matchIndex := strings.Index(remainingString, matches[0])
+			ret += remainingString[:matchIndex]
+			remainingString = remainingString[matchIndex+len(matches[0]):]
 
 			// Index 1 of FindStringSubmatch contains the subexpression match
 			// (variable name) of the capture group.
 			variable := matches[1]
 			// toExpand contains a variable.
 			if _, ok := seenVars[variable]; ok {
-				panic(fmt.Errorf(
-					"Unbounded recursive interpolation of variable: %s", variable))
+				return ret, fmt.Errorf(
+					"Unbounded recursive interpolation of variable: %s", variable)
 			}
 			// A map is passed-by-reference. Create a new map for
 			// this scope to prevent variables seen in one depth-first expansion
@@ -239,15 +277,65 @@
 			}
 			newSeenVars[variable] = true
 			if unexpandedVars, ok := stringListScope[variable]; ok {
+				expandedVars := []string{}
 				for _, unexpandedVar := range unexpandedVars {
-					ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
+					expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+					if err != nil {
+						return ret, err
+					}
+					expandedVars = append(expandedVars, expandedVar)
 				}
+				ret += strings.Join(expandedVars, " ")
 			} else if unexpandedVar, ok := stringScope[variable]; ok {
-				ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
+				expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else if unevaluatedVar, ok := exportedVars[variable]; ok {
+				evalFunc := reflect.ValueOf(unevaluatedVar)
+				validateVariableMethod(variable, evalFunc)
+				evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
+				evaluatedValue := evaluatedResult[0].Interface().(string)
+				expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else {
+				return "", fmt.Errorf("Unbound config variable %s", variable)
 			}
 		}
-		return ret
+		return ret, nil
+	}
+	var ret []string
+	for _, v := range strings.Split(toExpand, " ") {
+		val, err := expandVarInternal(v, map[string]bool{})
+		if err != nil {
+			return ret, err
+		}
+		ret = append(ret, val)
 	}
 
-	return expandVarInternal(toExpand, map[string]bool{})
+	return ret, nil
+}
+
+func validateVariableMethod(name string, methodValue reflect.Value) {
+	methodType := methodValue.Type()
+	if methodType.Kind() != reflect.Func {
+		panic(fmt.Errorf("method given for variable %s is not a function",
+			name))
+	}
+	if n := methodType.NumIn(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
+			name, n))
+	}
+	if n := methodType.NumOut(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
+			name, n))
+	}
+	if kind := methodType.Out(0).Kind(); kind != reflect.String {
+		panic(fmt.Errorf("method for variable %s does not return a string",
+			name))
+	}
 }
diff --git a/cc/config/bp2build_test.go b/cc/config/bp2build_test.go
index 883597a..3118df1 100644
--- a/cc/config/bp2build_test.go
+++ b/cc/config/bp2build_test.go
@@ -16,13 +16,21 @@
 
 import (
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestExpandVars(t *testing.T) {
+	android_arm64_config := android.TestConfig("out", nil, "", nil)
+	android_arm64_config.BuildOS = android.Android
+	android_arm64_config.BuildArch = android.Arm64
+
 	testCases := []struct {
 		description     string
+		config          android.Config
 		stringScope     exportedStringVariables
 		stringListScope exportedStringListVariables
+		configVars      exportedConfigDependingVariables
 		toExpand        string
 		expectedValues  []string
 	}{
@@ -57,7 +65,7 @@
 				"bar": []string{"baz", "${qux}"},
 			},
 			toExpand:       "${foo}",
-			expectedValues: []string{"baz", "hello"},
+			expectedValues: []string{"baz hello"},
 		},
 		{
 			description: "double level expansion",
@@ -75,7 +83,7 @@
 				"b": []string{"d"},
 			},
 			toExpand:       "${a}",
-			expectedValues: []string{"d", "c"},
+			expectedValues: []string{"d c"},
 		},
 		{
 			description: "double level expansion, with two variables in a string",
@@ -85,7 +93,7 @@
 				"c": []string{"e"},
 			},
 			toExpand:       "${a}",
-			expectedValues: []string{"d", "e"},
+			expectedValues: []string{"d e"},
 		},
 		{
 			description: "triple level expansion with two variables in a string",
@@ -96,13 +104,38 @@
 				"d": []string{"foo"},
 			},
 			toExpand:       "${a}",
-			expectedValues: []string{"foo", "foo", "foo"},
+			expectedValues: []string{"foo foo foo"},
+		},
+		{
+			description: "expansion with config depending vars",
+			configVars: exportedConfigDependingVariables{
+				"a": func(c android.Config) string { return c.BuildOS.String() },
+				"b": func(c android.Config) string { return c.BuildArch.String() },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${a}-${b}",
+			expectedValues: []string{"android-arm64"},
+		},
+		{
+			description: "double level multi type expansion",
+			stringListScope: exportedStringListVariables{
+				"platform": []string{"${os}-${arch}"},
+				"const":    []string{"const"},
+			},
+			configVars: exportedConfigDependingVariables{
+				"os":   func(c android.Config) string { return c.BuildOS.String() },
+				"arch": func(c android.Config) string { return c.BuildArch.String() },
+				"foo":  func(c android.Config) string { return "foo" },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${const}/${platform}/${foo}",
+			expectedValues: []string{"const/android-arm64/foo"},
 		},
 	}
 
 	for _, testCase := range testCases {
 		t.Run(testCase.description, func(t *testing.T) {
-			output := expandVar(testCase.toExpand, testCase.stringScope, testCase.stringListScope)
+			output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
 			if len(output) != len(testCase.expectedValues) {
 				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
 			}
@@ -119,6 +152,7 @@
 func TestBazelToolchainVars(t *testing.T) {
 	testCases := []struct {
 		name        string
+		config      android.Config
 		vars        []bazelVarExporter
 		expectedOut string
 	}{
@@ -248,7 +282,7 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			out := bazelToolchainVars(tc.vars...)
+			out := bazelToolchainVars(tc.config, tc.vars...)
 			if out != tc.expectedOut {
 				t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
 			}
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 53a7306..3caa688 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -31,23 +31,15 @@
 	"-fno-tree-sra",
 	"-fprefetch-loop-arrays",
 	"-funswitch-loops",
-	"-Werror=unused-but-set-parameter",
-	"-Werror=unused-but-set-variable",
 	"-Wmaybe-uninitialized",
 	"-Wno-error=clobbered",
 	"-Wno-error=maybe-uninitialized",
-	"-Wno-error=unused-but-set-parameter",
-	"-Wno-error=unused-but-set-variable",
 	"-Wno-extended-offsetof",
 	"-Wno-free-nonheap-object",
 	"-Wno-literal-suffix",
 	"-Wno-maybe-uninitialized",
 	"-Wno-old-style-declaration",
-	"-Wno-unused-but-set-parameter",
-	"-Wno-unused-but-set-variable",
 	"-Wno-unused-local-typedefs",
-	"-Wunused-but-set-parameter",
-	"-Wunused-but-set-variable",
 	"-fdiagnostics-color",
 	// http://b/153759688
 	"-fuse-init-array",
diff --git a/cc/config/x86_darwin_host.go b/cc/config/darwin_host.go
similarity index 68%
rename from cc/config/x86_darwin_host.go
rename to cc/config/darwin_host.go
index 0bb1a81..206bec1 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/darwin_host.go
@@ -53,12 +53,8 @@
 	}
 
 	darwinSupportedSdkVersions = []string{
-		"10.13",
-		"10.14",
-		"10.15",
-		"11.0",
-		"11.1",
-		"11.3",
+		"11",
+		"12",
 	}
 
 	darwinAvailableLibraries = append(
@@ -92,6 +88,10 @@
 		return getMacTools(ctx).arPath
 	})
 
+	pctx.VariableFunc("MacLipoPath", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).lipoPath
+	})
+
 	pctx.VariableFunc("MacStripPath", func(ctx android.PackageVarContext) string {
 		return getMacTools(ctx).stripPath
 	})
@@ -113,19 +113,24 @@
 	pctx.StaticVariable("DarwinYasmFlags", "-f macho -m amd64")
 }
 
+func MacStripPath(ctx android.PathContext) string {
+	return getMacTools(ctx).stripPath
+}
+
 type macPlatformTools struct {
 	once sync.Once
 	err  error
 
 	sdkRoot   string
 	arPath    string
+	lipoPath  string
 	stripPath string
 	toolPath  string
 }
 
 var macTools = &macPlatformTools{}
 
-func getMacTools(ctx android.PackageVarContext) *macPlatformTools {
+func getMacTools(ctx android.PathContext) *macPlatformTools {
 	macTools.once.Do(func() {
 		xcrunTool := "/usr/bin/xcrun"
 
@@ -134,7 +139,7 @@
 				return ""
 			}
 
-			bytes, err := exec.Command(xcrunTool, args...).Output()
+			bytes, err := exec.Command(xcrunTool, append([]string{"--sdk", "macosx"}, args...)...).Output()
 			if err != nil {
 				macTools.err = fmt.Errorf("xcrun %q failed with: %q", args, err)
 				return ""
@@ -143,34 +148,27 @@
 			return strings.TrimSpace(string(bytes))
 		}
 
-		xcrunSdk := func(arg string) string {
-			if selected := ctx.Config().Getenv("MAC_SDK_VERSION"); selected != "" {
-				if !inList(selected, darwinSupportedSdkVersions) {
-					macTools.err = fmt.Errorf("MAC_SDK_VERSION %s isn't supported: %q", selected, darwinSupportedSdkVersions)
-					return ""
-				}
-
-				return xcrun("--sdk", "macosx"+selected, arg)
+		sdkVersion := xcrun("--show-sdk-version")
+		sdkVersionSupported := false
+		for _, version := range darwinSupportedSdkVersions {
+			if version == sdkVersion || strings.HasPrefix(sdkVersion, version+".") {
+				sdkVersionSupported = true
 			}
-
-			for _, sdk := range darwinSupportedSdkVersions {
-				bytes, err := exec.Command(xcrunTool, "--sdk", "macosx"+sdk, arg).Output()
-				if err == nil {
-					return strings.TrimSpace(string(bytes))
-				}
-			}
-			macTools.err = fmt.Errorf("Could not find a supported mac sdk: %q", darwinSupportedSdkVersions)
-			return ""
+		}
+		if !sdkVersionSupported {
+			macTools.err = fmt.Errorf("Unsupported macOS SDK version %q not in %v", sdkVersion, darwinSupportedSdkVersions)
+			return
 		}
 
-		macTools.sdkRoot = xcrunSdk("--show-sdk-path")
+		macTools.sdkRoot = xcrun("--show-sdk-path")
 
 		macTools.arPath = xcrun("--find", "ar")
+		macTools.lipoPath = xcrun("--find", "lipo")
 		macTools.stripPath = xcrun("--find", "strip")
 		macTools.toolPath = filepath.Dir(xcrun("--find", "ld"))
 	})
 	if macTools.err != nil {
-		ctx.Errorf("%q", macTools.err)
+		android.ReportPathErrorf(ctx, "%q", macTools.err)
 	}
 	return macTools
 }
@@ -180,19 +178,43 @@
 	toolchain64Bit
 }
 
-func (t *toolchainDarwin) Name() string {
+type toolchainDarwinX86 struct {
+	toolchainDarwin
+}
+
+type toolchainDarwinArm struct {
+	toolchainDarwin
+}
+
+func (t *toolchainDarwinArm) Name() string {
+	return "arm64"
+}
+
+func (t *toolchainDarwinX86) Name() string {
 	return "x86_64"
 }
 
-func (t *toolchainDarwin) GccRoot() string {
+func (t *toolchainDarwinArm) GccRoot() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinArm) GccTriple() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinArm) GccVersion() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinX86) GccRoot() string {
 	return "${config.DarwinGccRoot}"
 }
 
-func (t *toolchainDarwin) GccTriple() string {
+func (t *toolchainDarwinX86) GccTriple() string {
 	return "${config.DarwinGccTriple}"
 }
 
-func (t *toolchainDarwin) GccVersion() string {
+func (t *toolchainDarwinX86) GccVersion() string {
 	return darwinGccVersion
 }
 
@@ -200,7 +222,11 @@
 	return ""
 }
 
-func (t *toolchainDarwin) ClangTriple() string {
+func (t *toolchainDarwinArm) ClangTriple() string {
+	return "aarch64-apple-darwin"
+}
+
+func (t *toolchainDarwinX86) ClangTriple() string {
 	return "x86_64-apple-darwin"
 }
 
@@ -236,12 +262,18 @@
 	return "${config.MacToolPath}"
 }
 
-var toolchainDarwinSingleton Toolchain = &toolchainDarwin{}
+var toolchainDarwinArmSingleton Toolchain = &toolchainDarwinArm{}
+var toolchainDarwinX86Singleton Toolchain = &toolchainDarwinX86{}
 
-func darwinToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainDarwinSingleton
+func darwinArmToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinArmSingleton
+}
+
+func darwinX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinX86Singleton
 }
 
 func init() {
-	registerToolchainFactory(android.Darwin, android.X86_64, darwinToolchainFactory)
+	registerToolchainFactory(android.Darwin, android.Arm64, darwinArmToolchainFactory)
+	registerToolchainFactory(android.Darwin, android.X86_64, darwinX86ToolchainFactory)
 }
diff --git a/cc/config/global.go b/cc/config/global.go
index 9773345..7f2c23e 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -46,7 +46,6 @@
 
 		"-O2",
 		"-g",
-		"-fdebug-info-for-profiling",
 
 		"-fno-strict-aliasing",
 
@@ -77,10 +76,6 @@
 		// TODO: can we remove this now?
 		"-Wno-reserved-id-macro",
 
-		// Workaround for ccache with clang.
-		// See http://petereisentraut.blogspot.com/2011/05/ccache-and-clang.html.
-		"-Wno-unused-command-line-argument",
-
 		// Force clang to always output color diagnostics. Ninja will strip the ANSI
 		// color codes if it is not running in a terminal.
 		"-fcolor-diagnostics",
@@ -129,6 +124,9 @@
 		"-Werror=sequence-point",
 		"-Werror=format-security",
 		"-nostdlibinc",
+
+		// Emit additional debug info for AutoFDO
+		"-fdebug-info-for-profiling",
 	}
 
 	deviceGlobalCppflags = []string{
@@ -226,10 +224,17 @@
 		"-Wno-pessimizing-move",                     // http://b/154270751
 		// New warnings to be fixed after clang-r399163
 		"-Wno-non-c-typedef-for-linkage", // http://b/161304145
-		// New warnings to be fixed after clang-r407598
-		"-Wno-string-concatenation", // http://b/175068488
 		// New warnings to be fixed after clang-r428724
 		"-Wno-align-mismatch", // http://b/193679946
+		// New warnings to be fixed after clang-r433403
+		"-Wno-error=unused-but-set-variable",  // http://b/197240255
+		"-Wno-error=unused-but-set-parameter", // http://b/197240255
+	}
+
+	noOverrideExternalGlobalCflags = []string{
+		// http://b/197240255
+		"-Wno-unused-but-set-variable",
+		"-Wno-unused-but-set-parameter",
 	}
 
 	// Extra cflags for external third-party projects to disable warnings that
@@ -255,6 +260,12 @@
 
 		// http://b/165945989
 		"-Wno-psabi",
+
+		// http://b/199369603
+		"-Wno-null-pointer-subtraction",
+
+		// http://b/175068488
+		"-Wno-string-concatenation",
 	}
 
 	IllegalFlags = []string{
@@ -268,8 +279,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r428724"
-	ClangDefaultShortVersion = "13.0.1"
+	ClangDefaultVersion      = "clang-r437112"
+	ClangDefaultShortVersion = "14.0.0"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
@@ -323,6 +334,12 @@
 			// Default to zero initialization.
 			flags = append(flags, "-ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang")
 		}
+
+		// Workaround for ccache with clang.
+		// See http://petereisentraut.blogspot.com/2011/05/ccache-and-clang.html.
+		if ctx.Config().IsEnvTrue("USE_CCACHE") {
+			flags = append(flags, "-Wno-unused-command-line-argument")
+		}
 		return strings.Join(flags, " ")
 	})
 
@@ -336,6 +353,7 @@
 
 	exportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
 	exportStringListStaticVariable("NoOverrideGlobalCflags", noOverrideGlobalCflags)
+	exportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
 	exportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
 	exportStringListStaticVariable("ExternalCflags", extraExternalCflags)
 
@@ -358,28 +376,12 @@
 	exportStringStaticVariable("CLANG_DEFAULT_VERSION", ClangDefaultVersion)
 	exportStringStaticVariable("CLANG_DEFAULT_SHORT_VERSION", ClangDefaultShortVersion)
 
-	pctx.SourcePathVariable("ClangDefaultBase", ClangDefaultBase)
-	pctx.VariableFunc("ClangBase", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_PREBUILTS_BASE"); override != "" {
-			return override
-		}
-		return "${ClangDefaultBase}"
-	})
-	pctx.VariableFunc("ClangVersion", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_PREBUILTS_VERSION"); override != "" {
-			return override
-		}
-		return ClangDefaultVersion
-	})
+	pctx.StaticVariableWithEnvOverride("ClangBase", "LLVM_PREBUILTS_BASE", ClangDefaultBase)
+	pctx.StaticVariableWithEnvOverride("ClangVersion", "LLVM_PREBUILTS_VERSION", ClangDefaultVersion)
 	pctx.StaticVariable("ClangPath", "${ClangBase}/${HostPrebuiltTag}/${ClangVersion}")
 	pctx.StaticVariable("ClangBin", "${ClangPath}/bin")
 
-	pctx.VariableFunc("ClangShortVersion", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_RELEASE_VERSION"); override != "" {
-			return override
-		}
-		return ClangDefaultShortVersion
-	})
+	pctx.StaticVariableWithEnvOverride("ClangShortVersion", "LLVM_RELEASE_VERSION", ClangDefaultShortVersion)
 	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib64/clang/${ClangShortVersion}/lib/linux")
 
 	// These are tied to the version of LLVM directly in external/llvm, so they might trail the host prebuilts
@@ -412,4 +414,30 @@
 	pctx.StaticVariableWithEnvOverride("REAbiLinkerExecStrategy", "RBE_ABI_LINKER_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
 }
 
-var HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
+var HostPrebuiltTag = exportVariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
+
+func ClangPath(ctx android.PathContext, file string) android.SourcePath {
+	type clangToolKey string
+
+	key := android.NewCustomOnceKey(clangToolKey(file))
+
+	return ctx.Config().OnceSourcePath(key, func() android.SourcePath {
+		return clangPath(ctx).Join(ctx, file)
+	})
+}
+
+var clangPathKey = android.NewOnceKey("clangPath")
+
+func clangPath(ctx android.PathContext) android.SourcePath {
+	return ctx.Config().OnceSourcePath(clangPathKey, func() android.SourcePath {
+		clangBase := ClangDefaultBase
+		if override := ctx.Config().Getenv("LLVM_PREBUILTS_BASE"); override != "" {
+			clangBase = override
+		}
+		clangVersion := ClangDefaultVersion
+		if override := ctx.Config().Getenv("LLVM_PREBUILTS_VERSION"); override != "" {
+			clangVersion = override
+		}
+		return android.PathForSource(ctx, clangBase, ctx.Config().PrebuiltOS(), clangVersion)
+	})
+}
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index cf13503..fdc246c 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -39,6 +39,7 @@
 			"misc-*",
 			"performance-*",
 			"portability-*",
+			"-bugprone-easily-swappable-parameters",
 			"-bugprone-narrowing-conversions",
 			"-google-readability*",
 			"-google-runtime-references",
@@ -115,6 +116,7 @@
 	{"external/", tidyExternalVendor},
 	{"external/google", tidyDefault},
 	{"external/webrtc", tidyDefault},
+	{"external/googletest/", tidyExternalVendor},
 	{"frameworks/compile/mclinker/", tidyExternalVendor},
 	{"hardware/qcom", tidyExternalVendor},
 	{"vendor/", tidyExternalVendor},
@@ -133,6 +135,7 @@
 }
 
 func TidyChecksForDir(dir string) string {
+	dir = dir + "/"
 	for _, pathCheck := range reversedDefaultLocalTidyChecks {
 		if strings.HasPrefix(dir, pathCheck.PathPrefix) {
 			return pathCheck.Checks
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 20384a8..6320dbb 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -95,8 +95,6 @@
 
 	YasmFlags() string
 
-	WindresFlags() string
-
 	Is64Bit() bool
 
 	ShlibSuffix() string
@@ -169,10 +167,6 @@
 	return ""
 }
 
-func (toolchainBase) WindresFlags() string {
-	return ""
-}
-
 func (toolchainBase) LibclangRuntimeLibraryArch() string {
 	return ""
 }
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 2e53b63..492cd98 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -25,12 +25,20 @@
 	"android.hardware.authsecret-unstable-ndk_platform",
 	"android.hardware.automotive.occupant_awareness-V1-ndk",
 	"android.hardware.automotive.occupant_awareness-V1-ndk_platform",
+	"android.hardware.automotive.occupant_awareness-ndk_platform",
+	"android.hardware.gnss-V1-ndk",
+	"android.hardware.gnss-V1-ndk_platform",
+	"android.hardware.gnss-ndk_platform",
+	"android.hardware.gnss-unstable-ndk_platform",
+	"android.hardware.health-V1-ndk",
+	"android.hardware.health-ndk",
 	"android.hardware.health.storage-V1-ndk",
 	"android.hardware.health.storage-V1-ndk_platform",
 	"android.hardware.health.storage-ndk_platform",
 	"android.hardware.health.storage-unstable-ndk_platform",
-	"android.hardware.identity-V2-ndk",
 	"android.hardware.identity-V2-ndk_platform",
+	"android.hardware.identity-V3-ndk",
+	"android.hardware.identity-V3-ndk_platform",
 	"android.hardware.identity-ndk_platform",
 	"android.hardware.light-V1-ndk",
 	"android.hardware.light-V1-ndk_platform",
@@ -44,16 +52,33 @@
 	"android.hardware.oemlock-V1-ndk_platform",
 	"android.hardware.oemlock-ndk_platform",
 	"android.hardware.oemlock-unstable-ndk_platform",
-	"android.hardware.power-V1-ndk",
 	"android.hardware.power-V1-ndk_platform",
+	"android.hardware.power-V2-ndk",
+	"android.hardware.power-V2-ndk_platform",
 	"android.hardware.power-ndk_platform",
-	"android.hardware.rebootescrow-V1-ndk",
-	"android.hardware.rebootescrow-V1-ndk_platform",
 	"android.hardware.power.stats-V1-ndk",
 	"android.hardware.power.stats-V1-ndk_platform",
 	"android.hardware.power.stats-ndk_platform",
 	"android.hardware.power.stats-unstable-ndk_platform",
+	"android.hardware.rebootescrow-V1-ndk",
+	"android.hardware.rebootescrow-V1-ndk_platform",
 	"android.hardware.rebootescrow-ndk_platform",
+	"android.hardware.radio-V1-ndk",
+	"android.hardware.radio-V1-ndk_platform",
+	"android.hardware.radio.config-V1-ndk",
+	"android.hardware.radio.config-V1-ndk_platform",
+	"android.hardware.radio.data-V1-ndk",
+	"android.hardware.radio.data-V1-ndk_platform",
+	"android.hardware.radio.messaging-V1-ndk",
+	"android.hardware.radio.messaging-V1-ndk_platform",
+	"android.hardware.radio.modem-V1-ndk",
+	"android.hardware.radio.modem-V1-ndk_platform",
+	"android.hardware.radio.network-V1-ndk",
+	"android.hardware.radio.network-V1-ndk_platform",
+	"android.hardware.radio.sim-V1-ndk",
+	"android.hardware.radio.sim-V1-ndk_platform",
+	"android.hardware.radio.voice-V1-ndk",
+	"android.hardware.radio.voice-V1-ndk_platform",
 	"android.hardware.security.keymint-V1-ndk",
 	"android.hardware.security.keymint-V1-ndk_platform",
 	"android.hardware.security.keymint-ndk_platform",
@@ -66,20 +91,25 @@
 	"android.hardware.security.sharedsecret-V1-ndk_platform",
 	"android.hardware.security.sharedsecret-ndk_platform",
 	"android.hardware.security.sharedsecret-unstable-ndk_platform",
-	"android.hardware.vibrator-V1-ndk",
 	"android.hardware.vibrator-V1-ndk_platform",
+	"android.hardware.vibrator-V2-ndk",
+	"android.hardware.vibrator-V2-ndk_platform",
 	"android.hardware.vibrator-ndk_platform",
 	"android.hardware.weaver-V1-ndk",
 	"android.hardware.weaver-V1-ndk_platform",
 	"android.hardware.weaver-ndk_platform",
 	"android.hardware.weaver-unstable-ndk_platform",
+	"android.system.suspend-V1-ndk",
 	"android.system.keystore2-V1-ndk",
+	"android.se.omapi-V1-ndk_platform",
+	"android.se.omapi-ndk_platform",
+	"android.se.omapi-unstable-ndk_platform",
 	"android.hardware.wifi.hostapd-V1-ndk",
 	"android.hardware.wifi.hostapd-V1-ndk_platform",
+	"android.hardware.wifi.supplicant-V1-ndk",
 	"android.system.keystore2-V1-ndk_platform",
 	"android.system.keystore2-ndk_platform",
 	"android.system.keystore2-unstable-ndk_platform",
-	"android.system.suspend-V1-ndk",
 	"android.system.suspend-V1-ndk_platform",
 	"libbinder",
 	"libcrypto",
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index ac5d5f7..43333fa 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -120,40 +120,40 @@
 )
 
 func init() {
-	pctx.StaticVariable("LinuxGccVersion", linuxGccVersion)
-	pctx.StaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
+	exportStringStaticVariable("LinuxGccVersion", linuxGccVersion)
+	exportStringStaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
+
 	// Most places use the full GCC version. A few only use up to the first two numbers.
 	if p := strings.Split(linuxGccVersion, "."); len(p) > 2 {
-		pctx.StaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
+		exportStringStaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
 	} else {
-		pctx.StaticVariable("ShortLinuxGccVersion", linuxGccVersion)
+		exportStringStaticVariable("ShortLinuxGccVersion", linuxGccVersion)
 	}
 
-	pctx.SourcePathVariable("LinuxGccRoot",
-		"prebuilts/gcc/${HostPrebuiltTag}/host/x86_64-linux-glibc${LinuxGlibcVersion}-${ShortLinuxGccVersion}")
+	exportSourcePathVariable("LinuxGccRoot",
+		"prebuilts/gcc/linux-x86/host/x86_64-linux-glibc${LinuxGlibcVersion}-${ShortLinuxGccVersion}")
 
-	pctx.StaticVariable("LinuxGccTriple", "x86_64-linux")
+	exportStringListStaticVariable("LinuxGccTriple", []string{"x86_64-linux"})
 
-	pctx.StaticVariable("LinuxCflags", strings.Join(linuxCflags, " "))
-	pctx.StaticVariable("LinuxLdflags", strings.Join(linuxLdflags, " "))
-	pctx.StaticVariable("LinuxLldflags", strings.Join(linuxLdflags, " "))
+	exportStringListStaticVariable("LinuxCflags", linuxCflags)
+	exportStringListStaticVariable("LinuxLdflags", linuxLdflags)
+	exportStringListStaticVariable("LinuxLldflags", linuxLdflags)
+	exportStringListStaticVariable("LinuxGlibcCflags", linuxGlibcCflags)
+	exportStringListStaticVariable("LinuxGlibcLdflags", linuxGlibcLdflags)
+	exportStringListStaticVariable("LinuxGlibcLldflags", linuxGlibcLdflags)
+	exportStringListStaticVariable("LinuxMuslCflags", linuxMuslCflags)
+	exportStringListStaticVariable("LinuxMuslLdflags", linuxMuslLdflags)
+	exportStringListStaticVariable("LinuxMuslLldflags", linuxMuslLdflags)
 
-	pctx.StaticVariable("LinuxX86Cflags", strings.Join(linuxX86Cflags, " "))
-	pctx.StaticVariable("LinuxX8664Cflags", strings.Join(linuxX8664Cflags, " "))
-	pctx.StaticVariable("LinuxX86Ldflags", strings.Join(linuxX86Ldflags, " "))
-	pctx.StaticVariable("LinuxX86Lldflags", strings.Join(linuxX86Ldflags, " "))
-	pctx.StaticVariable("LinuxX8664Ldflags", strings.Join(linuxX8664Ldflags, " "))
-	pctx.StaticVariable("LinuxX8664Lldflags", strings.Join(linuxX8664Ldflags, " "))
+	exportStringListStaticVariable("LinuxX86Cflags", linuxX86Cflags)
+	exportStringListStaticVariable("LinuxX8664Cflags", linuxX8664Cflags)
+	exportStringListStaticVariable("LinuxX86Ldflags", linuxX86Ldflags)
+	exportStringListStaticVariable("LinuxX86Lldflags", linuxX86Ldflags)
+	exportStringListStaticVariable("LinuxX8664Ldflags", linuxX8664Ldflags)
+	exportStringListStaticVariable("LinuxX8664Lldflags", linuxX8664Ldflags)
 	// Yasm flags
-	pctx.StaticVariable("LinuxX86YasmFlags", "-f elf32 -m x86")
-	pctx.StaticVariable("LinuxX8664YasmFlags", "-f elf64 -m amd64")
-
-	pctx.StaticVariable("LinuxGlibcCflags", strings.Join(linuxGlibcCflags, " "))
-	pctx.StaticVariable("LinuxGlibcLdflags", strings.Join(linuxGlibcLdflags, " "))
-	pctx.StaticVariable("LinuxGlibcLldflags", strings.Join(linuxGlibcLdflags, " "))
-	pctx.StaticVariable("LinuxMuslCflags", strings.Join(linuxMuslCflags, " "))
-	pctx.StaticVariable("LinuxMuslLdflags", strings.Join(linuxMuslLdflags, " "))
-	pctx.StaticVariable("LinuxMuslLldflags", strings.Join(linuxMuslLdflags, " "))
+	exportStringListStaticVariable("LinuxX86YasmFlags", []string{"-f elf32 -m x86"})
+	exportStringListStaticVariable("LinuxX8664YasmFlags", []string{"-f elf64 -m amd64"})
 }
 
 type toolchainLinux struct {
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index d9a7537..9daf40f 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -188,14 +188,6 @@
 	return "${config.WindowsIncludeFlags}"
 }
 
-func (t *toolchainWindowsX86) WindresFlags() string {
-	return "-F pe-i386"
-}
-
-func (t *toolchainWindowsX8664) WindresFlags() string {
-	return "-F pe-x86-64"
-}
-
 func (t *toolchainWindowsX86) ClangTriple() string {
 	return "i686-windows-gnu"
 }
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 83f0037..23d81d6 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -52,6 +52,10 @@
 	installedSharedDeps []string
 }
 
+func (fuzz *fuzzBinary) fuzzBinary() bool {
+	return true
+}
+
 func (fuzz *fuzzBinary) linkerProps() []interface{} {
 	props := fuzz.binaryDecorator.linkerProps()
 	props = append(props, &fuzz.fuzzPackagedModule.FuzzProperties)
@@ -79,54 +83,21 @@
 	return flags
 }
 
-// This function performs a breadth-first search over the provided module's
-// dependencies using `visitDirectDeps` to enumerate all shared library
-// dependencies. We require breadth-first expansion, as otherwise we may
-// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
-// from a dependency. This may cause issues when dependencies have explicit
-// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
-func collectAllSharedDependencies(ctx android.SingletonContext, module android.Module) android.Paths {
-	var fringe []android.Module
-
-	seen := make(map[string]bool)
-
-	// Enumerate the first level of dependencies, as we discard all non-library
-	// modules in the BFS loop below.
-	ctx.VisitDirectDeps(module, func(dep android.Module) {
-		if isValidSharedDependency(dep) {
-			fringe = append(fringe, dep)
-		}
-	})
-
-	var sharedLibraries android.Paths
-
-	for i := 0; i < len(fringe); i++ {
-		module := fringe[i]
-		if seen[module.Name()] {
-			continue
-		}
-		seen[module.Name()] = true
-
-		ccModule := module.(*Module)
-		sharedLibraries = append(sharedLibraries, ccModule.UnstrippedOutputFile())
-		ctx.VisitDirectDeps(module, func(dep android.Module) {
-			if isValidSharedDependency(dep) && !seen[dep.Name()] {
-				fringe = append(fringe, dep)
-			}
-		})
+func UnstrippedOutputFile(module android.Module) android.Path {
+	if mod, ok := module.(LinkableInterface); ok {
+		return mod.UnstrippedOutputFile()
 	}
-
-	return sharedLibraries
+	panic("UnstrippedOutputFile called on non-LinkableInterface module: " + module.Name())
 }
 
-// This function takes a module and determines if it is a unique shared library
+// IsValidSharedDependency takes a module and determines if it is a unique shared library
 // that should be installed in the fuzz target output directories. This function
 // returns true, unless:
 //  - The module is not an installable shared library, or
 //  - The module is a header or stub, or
 //  - The module is a prebuilt and its source is available, or
 //  - The module is a versioned member of an SDK snapshot.
-func isValidSharedDependency(dependency android.Module) bool {
+func IsValidSharedDependency(dependency android.Module) bool {
 	// TODO(b/144090547): We should be parsing these modules using
 	// ModuleDependencyTag instead of the current brute-force checking.
 
@@ -156,7 +127,7 @@
 		}
 		// Discard installable:false libraries because they are expected to be absent
 		// in runtime.
-		if !proptools.BoolDefault(ccLibrary.Properties.Installable, true) {
+		if !proptools.BoolDefault(ccLibrary.Installable(), true) {
 			return false
 		}
 	}
@@ -246,7 +217,7 @@
 		}
 		seen[child.Name()] = true
 
-		if isValidSharedDependency(child) {
+		if IsValidSharedDependency(child) {
 			sharedLibraries = append(sharedLibraries, child.(*Module).UnstrippedOutputFile())
 			return true
 		}
@@ -267,7 +238,7 @@
 }
 
 func NewFuzz(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 
 	binary.baseInstaller = NewFuzzInstaller()
 	module.sanitize.SetSanitizer(Fuzzer, true)
@@ -304,7 +275,6 @@
 // their architecture & target/host specific zip file.
 type ccFuzzPackager struct {
 	fuzz.FuzzPackager
-	sharedLibInstallStrings []string
 }
 
 func fuzzPackagingFactory() android.Singleton {
@@ -317,14 +287,14 @@
 	// archive}).
 	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
 
-	// 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)
-
 	// List of individual fuzz targets, so that 'make fuzz' also installs the targets
 	// to the correct output directories as well.
 	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) {
 		ccModule, ok := module.(*Module)
 		if !ok || ccModule.Properties.PreventInstall {
@@ -351,7 +321,7 @@
 		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
 
 		// Grab the list of required shared libraries.
-		sharedLibraries := collectAllSharedDependencies(ctx, module)
+		sharedLibraries := fuzz.CollectAllSharedDependencies(ctx, module, UnstrippedOutputFile, IsValidSharedDependency)
 
 		var files []fuzz.FileToZip
 		builder := android.NewRuleBuilder(pctx, ctx)
@@ -359,39 +329,8 @@
 		// Package the corpus, data, dict and config into a zipfile.
 		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
 
-		// Find and mark all the transiently-dependent shared libraries for
-		// packaging.
-		for _, library := range sharedLibraries {
-			files = append(files, fuzz.FileToZip{library, "lib"})
-
-			// 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(
-				library, ccModule.Host(), archString)
-			if sharedLibraryInstalled[installDestination] {
-				continue
-			}
-			sharedLibraryInstalled[installDestination] = true
-
-			// Escape all the variables, as the install destination here will be called
-			// via. $(eval) in Make.
-			installDestination = strings.ReplaceAll(
-				installDestination, "$", "$$")
-			s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
-				library.String()+":"+installDestination)
-
-			// Ensure that on device, the library is also reinstalled to the /symbols/
-			// dir. Symbolized DSO's are always installed to the device when fuzzing, but
-			// we want symbolization tools (like `stack`) to be able to find the symbols
-			// in $ANDROID_PRODUCT_OUT/symbols automagically.
-			if !ccModule.Host() {
-				symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, archString)
-				symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
-				s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
-					library.String()+":"+symbolsInstallDestination)
-			}
-		}
+		// Package shared libraries
+		files = append(files, GetSharedLibsToZip(sharedLibraries, ccModule, &s.FuzzPackager, archString, &sharedLibraryInstalled)...)
 
 		// The executable.
 		files = append(files, fuzz.FileToZip{ccModule.UnstrippedOutputFile(), ""})
@@ -409,15 +348,54 @@
 func (s *ccFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
 	packages := s.Packages.Strings()
 	sort.Strings(packages)
-	sort.Strings(s.sharedLibInstallStrings)
+	sort.Strings(s.FuzzPackager.SharedLibInstallStrings)
 	// TODO(mitchp): Migrate this to use MakeVarsContext::DistForGoal() when it's
 	// ready to handle phony targets created in Soong. In the meantime, this
 	// exports the phony 'fuzz' target and dependencies on packages to
 	// core/main.mk so that we can use dist-for-goals.
 	ctx.Strict("SOONG_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
 	ctx.Strict("FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS",
-		strings.Join(s.sharedLibInstallStrings, " "))
+		strings.Join(s.FuzzPackager.SharedLibInstallStrings, " "))
 
 	// Preallocate the slice of fuzz targets to minimise memory allocations.
 	s.PreallocateSlice(ctx, "ALL_FUZZ_TARGETS")
 }
+
+// GetSharedLibsToZip finds and marks all the transiently-dependent shared libraries for
+// packaging.
+func GetSharedLibsToZip(sharedLibraries android.Paths, module LinkableInterface, s *fuzz.FuzzPackager, archString string, sharedLibraryInstalled *map[string]bool) []fuzz.FileToZip {
+	var files []fuzz.FileToZip
+
+	for _, library := range sharedLibraries {
+		files = append(files, fuzz.FileToZip{library, "lib"})
+
+		// 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(
+			library, module.Host(), archString)
+		if (*sharedLibraryInstalled)[installDestination] {
+			continue
+		}
+		(*sharedLibraryInstalled)[installDestination] = true
+
+		// Escape all the variables, as the install destination here will be called
+		// via. $(eval) in Make.
+		installDestination = strings.ReplaceAll(
+			installDestination, "$", "$$")
+		s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
+			library.String()+":"+installDestination)
+
+		// Ensure that on device, the library is also reinstalled to the /symbols/
+		// dir. Symbolized DSO's are always installed to the device when fuzzing, but
+		// 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, archString)
+			symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
+			s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
+				library.String()+":"+symbolsInstallDestination)
+		}
+	}
+	return files
+}
diff --git a/cc/gen.go b/cc/gen.go
index 3a1a0e2..8f62363 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -45,13 +45,6 @@
 			CommandDeps: []string{"$syspropCmd"},
 		},
 		"headerOutDir", "publicOutDir", "srcOutDir", "includeName")
-
-	windmc = pctx.AndroidStaticRule("windmc",
-		blueprint.RuleParams{
-			Command:     "$windmcCmd -r$$(dirname $out) -h$$(dirname $out) $in",
-			CommandDeps: []string{"$windmcCmd"},
-		},
-		"windmcCmd")
 )
 
 type YaccProperties struct {
@@ -200,26 +193,6 @@
 	return cppFile, headers.Paths()
 }
 
-func genWinMsg(ctx android.ModuleContext, srcFile android.Path, flags builderFlags) (android.Path, android.Path) {
-	headerFile := android.GenPathWithExt(ctx, "windmc", srcFile, "h")
-	rcFile := android.GenPathWithExt(ctx, "windmc", srcFile, "rc")
-
-	windmcCmd := mingwCmd(flags.toolchain, "windmc")
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:           windmc,
-		Description:    "windmc " + srcFile.Rel(),
-		Output:         rcFile,
-		ImplicitOutput: headerFile,
-		Input:          srcFile,
-		Args: map[string]string{
-			"windmcCmd": windmcCmd,
-		},
-	})
-
-	return rcFile, headerFile
-}
-
 // Used to communicate information from the genSources method back to the library code that uses
 // it.
 type generatedSourceInfo struct {
@@ -305,10 +278,6 @@
 			cppFile := rsGeneratedCppFile(ctx, srcFile)
 			rsFiles = append(rsFiles, srcFiles[i])
 			srcFiles[i] = cppFile
-		case ".mc":
-			rcFile, headerFile := genWinMsg(ctx, srcFile, buildFlags)
-			srcFiles[i] = rcFile
-			deps = append(deps, headerFile)
 		case ".sysprop":
 			cppFile, headerFiles := genSysprop(ctx, srcFile)
 			srcFiles[i] = cppFile
diff --git a/cc/genrule.go b/cc/genrule.go
index 0ca901e..239064f 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -15,13 +15,15 @@
 package cc
 
 import (
+	"fmt"
+
 	"android/soong/android"
 	"android/soong/genrule"
 	"android/soong/snapshot"
 )
 
 func init() {
-	android.RegisterModuleType("cc_genrule", genRuleFactory)
+	android.RegisterModuleType("cc_genrule", GenRuleFactory)
 }
 
 type GenruleExtraProperties struct {
@@ -36,22 +38,40 @@
 
 // cc_genrule is a genrule that can depend on other cc_* objects.
 // The cmd may be run multiple times, once for each of the different arch/etc
-// variations.
-func genRuleFactory() android.Module {
+// variations.  The following environment variables will be set when the command
+// execute:
+//
+//   CC_ARCH           the name of the architecture the command is being executed for
+//
+//   CC_MULTILIB       "lib32" if the architecture the command is being executed for is 32-bit,
+//                     "lib64" if it is 64-bit.
+//
+//   CC_NATIVE_BRIDGE  the name of the subdirectory that native bridge libraries are stored in if
+//                     the architecture has native bridge enabled, empty if it is disabled.
+func GenRuleFactory() android.Module {
 	module := genrule.NewGenRule()
 
 	extra := &GenruleExtraProperties{}
 	module.Extra = extra
 	module.ImageInterface = extra
+	module.CmdModifier = genruleCmdModifier
 	module.AddProperties(module.Extra)
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibBoth)
 
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
 
+func genruleCmdModifier(ctx android.ModuleContext, cmd string) string {
+	target := ctx.Target()
+	arch := target.Arch.ArchType
+	return fmt.Sprintf("CC_ARCH=%s CC_NATIVE_BRIDGE=%s CC_MULTILIB=%s && %s",
+		arch.Name, target.NativeBridgeRelativePath, arch.Multilib, cmd)
+}
+
 var _ android.ImageInterface = (*GenruleExtraProperties)(nil)
 
 func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index 45b343b..f25f704 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -23,7 +23,7 @@
 
 func testGenruleContext(config android.Config) *android.TestContext {
 	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
+	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
 	ctx.Register()
 
 	return ctx
@@ -115,3 +115,75 @@
 		t.Errorf(`want inputs %v, got %v`, expected, got)
 	}
 }
+
+func TestCmdPrefix(t *testing.T) {
+	bp := `
+		cc_genrule {
+			name: "gen",
+			cmd: "echo foo",
+			out: ["out"],
+			native_bridge_supported: true,
+		}
+		`
+
+	testCases := []struct {
+		name     string
+		variant  string
+		preparer android.FixturePreparer
+
+		arch         string
+		nativeBridge string
+		multilib     string
+	}{
+		{
+			name:     "arm",
+			variant:  "android_arm_armv7-a-neon",
+			arch:     "arm",
+			multilib: "lib32",
+		},
+		{
+			name:     "arm64",
+			variant:  "android_arm64_armv8-a",
+			arch:     "arm64",
+			multilib: "lib64",
+		},
+		{
+			name:    "nativebridge",
+			variant: "android_native_bridge_arm_armv7-a-neon",
+			preparer: android.FixtureModifyConfig(func(config android.Config) {
+				config.Targets[android.Android] = []android.Target{
+					{
+						Os:           android.Android,
+						Arch:         android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
+						NativeBridge: android.NativeBridgeDisabled,
+					},
+					{
+						Os:                       android.Android,
+						Arch:                     android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
+						NativeBridge:             android.NativeBridgeEnabled,
+						NativeBridgeHostArchName: "x86",
+						NativeBridgeRelativePath: "arm",
+					},
+				}
+			}),
+			arch:         "arm",
+			multilib:     "lib32",
+			nativeBridge: "arm",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				PrepareForIntegrationTestWithCc,
+				android.OptionalFixturePreparer(tt.preparer),
+			).RunTestWithBp(t, bp)
+			gen := result.ModuleForTests("gen", tt.variant)
+			sboxProto := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+			cmd := *sboxProto.Commands[0].Command
+			android.AssertStringDoesContain(t, "incorrect CC_ARCH", cmd, "CC_ARCH="+tt.arch+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_NATIVE_BRIDGE", cmd, "CC_NATIVE_BRIDGE="+tt.nativeBridge+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_MULTILIB", cmd, "CC_MULTILIB="+tt.multilib+" ")
+		})
+	}
+}
diff --git a/cc/image_sdk_traits.go b/cc/image_sdk_traits.go
new file mode 100644
index 0000000..1d28230
--- /dev/null
+++ b/cc/image_sdk_traits.go
@@ -0,0 +1,40 @@
+// Copyright 2021 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 cc
+
+import "android/soong/android"
+
+// This file contains support for the image variant sdk traits.
+
+func init() {
+	android.RegisterSdkMemberTrait(ramdiskImageRequiredSdkTrait)
+	android.RegisterSdkMemberTrait(recoveryImageRequiredSdkTrait)
+}
+
+type imageSdkTraitStruct struct {
+	android.SdkMemberTraitBase
+}
+
+var ramdiskImageRequiredSdkTrait android.SdkMemberTrait = &imageSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "ramdisk_image_required",
+	},
+}
+
+var recoveryImageRequiredSdkTrait android.SdkMemberTrait = &imageSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "recovery_image_required",
+	},
+}
diff --git a/cc/installer.go b/cc/installer.go
index f95b493..2522610 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -29,6 +29,9 @@
 	// Install output directly in {partition}/, not in any subdir.  This is only intended for use by
 	// init_first_stage.
 	Install_in_root *bool `android:"arch_variant"`
+
+	// Install output directly in {partition}/xbin
+	Install_in_xbin *bool `android:"arch_vvariant"`
 }
 
 type installLocation int
@@ -73,6 +76,8 @@
 
 	if installer.installInRoot() {
 		dir = ""
+	} else if installer.installInXbin() {
+		dir = "xbin"
 	}
 
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
@@ -123,3 +128,7 @@
 func (installer *baseInstaller) installInRoot() bool {
 	return Bool(installer.Properties.Install_in_root)
 }
+
+func (installer *baseInstaller) installInXbin() bool {
+	return Bool(installer.Properties.Install_in_xbin)
+}
diff --git a/cc/libbuildversion/Android.bp b/cc/libbuildversion/Android.bp
index 4debb1c..b105a30 100644
--- a/cc/libbuildversion/Android.bp
+++ b/cc/libbuildversion/Android.bp
@@ -14,8 +14,10 @@
             enabled: true,
         },
     },
+    min_sdk_version: "26",
     apex_available: [
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    vendor_available: true,
 }
diff --git a/cc/libbuildversion/libbuildversion.cpp b/cc/libbuildversion/libbuildversion.cpp
index 5242025..1e01c11 100644
--- a/cc/libbuildversion/libbuildversion.cpp
+++ b/cc/libbuildversion/libbuildversion.cpp
@@ -36,7 +36,11 @@
     return soong_build_number;
   }
 
+#ifdef __ANDROID_VENDOR__
+  const prop_info* pi = __system_property_find("ro.vendor.build.version.incremental");
+#else
   const prop_info* pi = __system_property_find("ro.build.version.incremental");
+#endif
   if (pi == nullptr) return "";
 
   std::string property_value;
diff --git a/cc/library.go b/cc/library.go
index 196116b..216c124 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -143,6 +143,8 @@
 type StaticOrSharedProperties struct {
 	Srcs []string `android:"path,arch_variant"`
 
+	Tidy_disabled_srcs []string `android:"path,arch_variant"`
+
 	Sanitized Sanitized `android:"arch_variant"`
 
 	Cflags []string `android:"arch_variant"`
@@ -157,6 +159,8 @@
 	Export_static_lib_headers []string `android:"arch_variant"`
 
 	Apex_available []string `android:"arch_variant"`
+
+	Installable *bool `android:"arch_variant"`
 }
 
 type LibraryMutatedProperties struct {
@@ -203,9 +207,6 @@
 
 func init() {
 	RegisterLibraryBuildComponents(android.InitRegistrationContext)
-
-	android.RegisterBp2BuildMutator("cc_library_static", CcLibraryStaticBp2Build)
-	android.RegisterBp2BuildMutator("cc_library", CcLibraryBp2Build)
 }
 
 func RegisterLibraryBuildComponents(ctx android.RegistrationContext) {
@@ -216,6 +217,7 @@
 	ctx.RegisterModuleType("cc_library_host_shared", LibraryHostSharedFactory)
 }
 
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
 // For bp2build conversion.
 type bazelCcLibraryAttributes struct {
 	// Attributes pertaining to both static and shared variants.
@@ -228,25 +230,39 @@
 	Conlyflags bazel.StringListAttribute
 	Asflags    bazel.StringListAttribute
 
-	Hdrs                bazel.LabelListAttribute
-	Deps                bazel.LabelListAttribute
-	Implementation_deps bazel.LabelListAttribute
-	Dynamic_deps        bazel.LabelListAttribute
-	Whole_archive_deps  bazel.LabelListAttribute
-	System_dynamic_deps bazel.LabelListAttribute
-	Includes            bazel.StringListAttribute
-	Linkopts            bazel.StringListAttribute
-	Use_libcrt          bazel.BoolAttribute
-	Rtti                bazel.BoolAttribute
+	Hdrs bazel.LabelListAttribute
+
+	Deps                              bazel.LabelListAttribute
+	Implementation_deps               bazel.LabelListAttribute
+	Dynamic_deps                      bazel.LabelListAttribute
+	Implementation_dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps                bazel.LabelListAttribute
+	Implementation_whole_archive_deps bazel.LabelListAttribute
+	System_dynamic_deps               bazel.LabelListAttribute
+
+	Export_includes        bazel.StringListAttribute
+	Export_system_includes bazel.StringListAttribute
+	Local_includes         bazel.StringListAttribute
+	Absolute_includes      bazel.StringListAttribute
+	Linkopts               bazel.StringListAttribute
+	Use_libcrt             bazel.BoolAttribute
+	Rtti                   bazel.BoolAttribute
+
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
 
 	// This is shared only.
-	Version_script bazel.LabelAttribute
+	Link_crt                 bazel.BoolAttribute
+	Additional_linker_inputs bazel.LabelListAttribute
 
 	// Common properties shared between both shared and static variants.
 	Shared staticOrSharedAttributes
 	Static staticOrSharedAttributes
 
 	Strip stripAttributes
+
+	Features bazel.StringListAttribute
 }
 
 type stripAttributes struct {
@@ -257,59 +273,105 @@
 	None                         bazel.BoolAttribute
 }
 
-func CcLibraryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "cc_library" {
-		return
-	}
-
+func libraryBp2Build(ctx android.TopDownMutatorContext, m *Module) {
 	// For some cc_library modules, their static variants are ready to be
 	// converted, but not their shared variants. For these modules, delegate to
 	// the cc_library_static bp2build converter temporarily instead.
-	if android.GenerateCcLibraryStaticOnly(ctx) {
-		ccLibraryStaticBp2BuildInternal(ctx, m)
+	if android.GenerateCcLibraryStaticOnly(ctx.Module().Name()) {
+		sharedOrStaticLibraryBp2Build(ctx, m, true)
 		return
 	}
 
 	sharedAttrs := bp2BuildParseSharedProps(ctx, m)
 	staticAttrs := bp2BuildParseStaticProps(ctx, m)
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, m)
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, m)
+	baseAttributes := bp2BuildParseBaseProps(ctx, m)
+	compilerAttrs := baseAttributes.compilerAttributes
+	linkerAttrs := baseAttributes.linkerAttributes
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, m, compilerAttrs.includes)
 
 	srcs := compilerAttrs.srcs
 
+	sharedAttrs.Dynamic_deps.Add(baseAttributes.protoDependency)
+	staticAttrs.Deps.Add(baseAttributes.protoDependency)
+
 	asFlags := compilerAttrs.asFlags
 	if compilerAttrs.asSrcs.IsEmpty() && sharedAttrs.Srcs_as.IsEmpty() && staticAttrs.Srcs_as.IsEmpty() {
 		// Skip asflags for BUILD file simplicity if there are no assembly sources.
 		asFlags = bazel.MakeStringListAttribute(nil)
 	}
 
-	attrs := &bazelCcLibraryAttributes{
-		Srcs:    srcs,
-		Srcs_c:  compilerAttrs.cSrcs,
-		Srcs_as: compilerAttrs.asSrcs,
+	staticCommonAttrs := staticOrSharedAttributes{
+		Srcs:    *srcs.Clone().Append(staticAttrs.Srcs),
+		Srcs_c:  *compilerAttrs.cSrcs.Clone().Append(staticAttrs.Srcs_c),
+		Srcs_as: *compilerAttrs.asSrcs.Clone().Append(staticAttrs.Srcs_as),
+		Copts:   *compilerAttrs.copts.Clone().Append(staticAttrs.Copts),
+		Hdrs:    *compilerAttrs.hdrs.Clone().Append(staticAttrs.Hdrs),
 
-		Copts:      compilerAttrs.copts,
+		Deps:                              *linkerAttrs.deps.Clone().Append(staticAttrs.Deps),
+		Implementation_deps:               *linkerAttrs.implementationDeps.Clone().Append(staticAttrs.Implementation_deps),
+		Dynamic_deps:                      *linkerAttrs.dynamicDeps.Clone().Append(staticAttrs.Dynamic_deps),
+		Implementation_dynamic_deps:       *linkerAttrs.implementationDynamicDeps.Clone().Append(staticAttrs.Implementation_dynamic_deps),
+		Implementation_whole_archive_deps: linkerAttrs.implementationWholeArchiveDeps,
+		Whole_archive_deps:                *linkerAttrs.wholeArchiveDeps.Clone().Append(staticAttrs.Whole_archive_deps),
+		System_dynamic_deps:               *linkerAttrs.systemDynamicDeps.Clone().Append(staticAttrs.System_dynamic_deps),
+	}
+
+	sharedCommonAttrs := staticOrSharedAttributes{
+		Srcs:    *srcs.Clone().Append(sharedAttrs.Srcs),
+		Srcs_c:  *compilerAttrs.cSrcs.Clone().Append(sharedAttrs.Srcs_c),
+		Srcs_as: *compilerAttrs.asSrcs.Clone().Append(sharedAttrs.Srcs_as),
+		Copts:   *compilerAttrs.copts.Clone().Append(sharedAttrs.Copts),
+		Hdrs:    *compilerAttrs.hdrs.Clone().Append(sharedAttrs.Hdrs),
+
+		Deps:                        *linkerAttrs.deps.Clone().Append(sharedAttrs.Deps),
+		Implementation_deps:         *linkerAttrs.implementationDeps.Clone().Append(sharedAttrs.Implementation_deps),
+		Dynamic_deps:                *linkerAttrs.dynamicDeps.Clone().Append(sharedAttrs.Dynamic_deps),
+		Implementation_dynamic_deps: *linkerAttrs.implementationDynamicDeps.Clone().Append(sharedAttrs.Implementation_dynamic_deps),
+		Whole_archive_deps:          *linkerAttrs.wholeArchiveDeps.Clone().Append(sharedAttrs.Whole_archive_deps),
+		System_dynamic_deps:         *linkerAttrs.systemDynamicDeps.Clone().Append(sharedAttrs.System_dynamic_deps),
+	}
+
+	staticTargetAttrs := &bazelCcLibraryStaticAttributes{
+		staticOrSharedAttributes: staticCommonAttrs,
+
 		Cppflags:   compilerAttrs.cppFlags,
 		Conlyflags: compilerAttrs.conlyFlags,
 		Asflags:    asFlags,
 
-		Implementation_deps: linkerAttrs.deps,
-		Deps:                linkerAttrs.exportedDeps,
-		Dynamic_deps:        linkerAttrs.dynamicDeps,
-		Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
-		System_dynamic_deps: linkerAttrs.systemDynamicDeps,
-		Includes:            exportedIncludes,
-		Linkopts:            linkerAttrs.linkopts,
-		Use_libcrt:          linkerAttrs.useLibcrt,
-		Rtti:                compilerAttrs.rtti,
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Local_includes:           compilerAttrs.localIncludes,
+		Absolute_includes:        compilerAttrs.absoluteIncludes,
+		Use_libcrt:               linkerAttrs.useLibcrt,
+		Rtti:                     compilerAttrs.rtti,
+		Stl:                      compilerAttrs.stl,
+		Cpp_std:                  compilerAttrs.cppStd,
+		C_std:                    compilerAttrs.cStd,
 
-		Version_script: linkerAttrs.versionScript,
+		Features: linkerAttrs.features,
+	}
+
+	sharedTargetAttrs := &bazelCcLibrarySharedAttributes{
+		staticOrSharedAttributes: sharedCommonAttrs,
+		Cppflags:                 compilerAttrs.cppFlags,
+		Conlyflags:               compilerAttrs.conlyFlags,
+		Asflags:                  asFlags,
+
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Local_includes:           compilerAttrs.localIncludes,
+		Absolute_includes:        compilerAttrs.absoluteIncludes,
+		Linkopts:                 linkerAttrs.linkopts,
+		Link_crt:                 linkerAttrs.linkCrt,
+		Use_libcrt:               linkerAttrs.useLibcrt,
+		Rtti:                     compilerAttrs.rtti,
+		Stl:                      compilerAttrs.stl,
+		Cpp_std:                  compilerAttrs.cppStd,
+		C_std:                    compilerAttrs.cStd,
+
+		Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
 
 		Strip: stripAttributes{
 			Keep_symbols:                 linkerAttrs.stripKeepSymbols,
@@ -318,18 +380,20 @@
 			All:                          linkerAttrs.stripAll,
 			None:                         linkerAttrs.stripNone,
 		},
-
-		Shared: sharedAttrs,
-
-		Static: staticAttrs,
+		Features: linkerAttrs.features,
 	}
 
-	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "cc_library",
-		Bzl_load_location: "//build/bazel/rules:full_cc_library.bzl",
+	staticProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library_static",
+		Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl",
+	}
+	sharedProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library_shared",
+		Bzl_load_location: "//build/bazel/rules:cc_library_shared.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(staticProps, android.CommonAttributes{Name: m.Name() + "_bp2build_cc_library_static"}, staticTargetAttrs)
+	ctx.CreateBazelTargetModule(sharedProps, android.CommonAttributes{Name: m.Name()}, sharedTargetAttrs)
 }
 
 // cc_library creates both static and/or shared libraries for a device and/or
@@ -344,6 +408,7 @@
 		staticLibrarySdkMemberType,
 		staticAndSharedLibrarySdkMemberType,
 	}
+	module.bazelable = true
 	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
@@ -353,6 +418,7 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyStatic()
 	module.sdkMemberTypes = []android.SdkMemberType{staticLibrarySdkMemberType}
+	module.bazelable = true
 	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
@@ -362,6 +428,8 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyShared()
 	module.sdkMemberTypes = []android.SdkMemberType{sharedLibrarySdkMemberType}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -379,6 +447,8 @@
 	module, library := NewLibrary(android.HostSupported)
 	library.BuildOnlyShared()
 	module.sdkMemberTypes = []android.SdkMemberType{sharedLibrarySdkMemberType}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -595,7 +665,10 @@
 
 	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = outputFilePath
 
-	tocFile := getTocFile(ctx, label, ccInfo.OutputFiles)
+	var tocFile android.OptionalPath
+	if len(ccInfo.TocFile) > 0 {
+		tocFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, ccInfo.TocFile))
+	}
 	handler.module.linker.(*libraryDecorator).tocFile = tocFile
 
 	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
@@ -608,28 +681,9 @@
 	return true
 }
 
-// getTocFile looks for the .so.toc file in the target's output files, if any. The .so.toc file
-// contains the table of contents of all symbols of a shared object.
-func getTocFile(ctx android.ModuleContext, label string, outputFiles []string) android.OptionalPath {
-	var tocFile string
-	for _, file := range outputFiles {
-		if strings.HasSuffix(file, ".so.toc") {
-			if tocFile != "" {
-				ctx.ModuleErrorf("The %s target cannot produce more than 1 .toc file.", label)
-			}
-			tocFile = file
-			// Don't break to validate that there are no multiple .toc files per .so.
-		}
-	}
-	if tocFile == "" {
-		return android.OptionalPath{}
-	}
-	return android.OptionalPathForPath(android.PathForBazelOut(ctx, tocFile))
-}
-
 func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
 		return false
@@ -955,7 +1009,7 @@
 			nativeAbiResult.versionScript)
 
 		// Parse symbol file to get API list for coverage
-		if library.stubsVersion() == "current" && ctx.PrimaryArch() {
+		if library.stubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
 			library.apiListCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
 		}
 
@@ -996,12 +1050,14 @@
 
 	if library.static() {
 		srcs := android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Srcs)
-		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary,
-			srcs, library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary, srcs,
+			android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Tidy_disabled_srcs),
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
 	} else if library.shared() {
 		srcs := android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Srcs)
-		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary,
-			srcs, library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary, srcs,
+			android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Tidy_disabled_srcs),
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
 	}
 
 	return objs
@@ -1032,6 +1088,10 @@
 	androidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer)
 
 	availableFor(string) bool
+
+	getAPIListCoverageXMLPath() android.ModuleOutPath
+
+	installable() *bool
 }
 
 type versionedInterface interface {
@@ -1274,7 +1334,7 @@
 		}
 	}
 
-	transformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, objs.tidyFiles)
+	transformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, nil, objs.tidyFiles)
 
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
 
@@ -1300,11 +1360,21 @@
 	return outputFile
 }
 
+func ndkSharedLibDeps(ctx ModuleContext) android.Paths {
+	if ctx.Module().(*Module).IsSdkVariant() {
+		// The NDK sysroot timestamp file depends on all the NDK
+		// sysroot header and shared library files.
+		return android.Paths{getNdkBaseTimestampFile(ctx)}
+	}
+	return nil
+}
+
 func (library *libraryDecorator) linkShared(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
 	var linkerDeps android.Paths
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
+	linkerDeps = append(linkerDeps, ndkSharedLibDeps(ctx)...)
 
 	unexportedSymbols := ctx.ExpandOptionalSource(library.Properties.Unexported_symbols_list, "unexported_symbols_list")
 	forceNotWeakSymbols := ctx.ExpandOptionalSource(library.Properties.Force_symbols_not_weak_list, "force_symbols_not_weak_list")
@@ -1353,11 +1423,17 @@
 
 	builderFlags := flagsToBuilderFlags(flags)
 
+	if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() {
+		fatOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName)
+		transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path())
+	}
+
 	// Optimize out relinking against shared libraries whose interface hasn't changed by
 	// depending on a table of contents file instead of the library itself.
 	tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.ShlibSuffix()[1:]+".toc")
 	library.tocFile = android.OptionalPathForPath(tocFile)
-	transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+	TransformSharedObjectToToc(ctx, outputFile, tocFile)
 
 	stripFlags := flagsToStripFlags(flags)
 	needsStrip := library.stripper.NeedsStrip(ctx)
@@ -1403,10 +1479,9 @@
 	linkerDeps = append(linkerDeps, deps.EarlySharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
-	linkerDeps = append(linkerDeps, objs.tidyFiles...)
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
 		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, nil)
+		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, objs.tidyFiles)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -1957,6 +2032,15 @@
 	return android.CheckAvailableForApex(what, list)
 }
 
+func (library *libraryDecorator) installable() *bool {
+	if library.static() {
+		return library.StaticProperties.Static.Installable
+	} else if library.shared() {
+		return library.SharedProperties.Shared.Installable
+	}
+	return nil
+}
+
 func (library *libraryDecorator) makeUninstallable(mod *Module) {
 	if library.static() && library.buildStatic() && !library.buildStubs() {
 		// If we're asked to make a static library uninstallable we don't do
@@ -1968,6 +2052,10 @@
 	mod.ModuleBase.MakeUninstallable()
 }
 
+func (library *libraryDecorator) getAPIListCoverageXMLPath() android.ModuleOutPath {
+	return library.apiListCoverageXmlPath
+}
+
 var versioningMacroNamesListKey = android.NewOnceKey("versioningMacroNamesList")
 
 // versioningMacroNamesList returns a singleton map, where keys are "version macro names",
@@ -2317,46 +2405,28 @@
 	return outputFile
 }
 
-type bazelCcLibraryStaticAttributes struct {
-	Copts               bazel.StringListAttribute
-	Srcs                bazel.LabelListAttribute
-	Implementation_deps bazel.LabelListAttribute
-	Deps                bazel.LabelListAttribute
-	Whole_archive_deps  bazel.LabelListAttribute
-	Dynamic_deps        bazel.LabelListAttribute
-	System_dynamic_deps bazel.LabelListAttribute
-	Linkopts            bazel.StringListAttribute
-	Linkstatic          bool
-	Use_libcrt          bazel.BoolAttribute
-	Rtti                bazel.BoolAttribute
-	Includes            bazel.StringListAttribute
-	Hdrs                bazel.LabelListAttribute
+func sharedOrStaticLibraryBp2Build(ctx android.TopDownMutatorContext, module *Module, isStatic bool) {
+	baseAttributes := bp2BuildParseBaseProps(ctx, module)
+	compilerAttrs := baseAttributes.compilerAttributes
+	linkerAttrs := baseAttributes.linkerAttributes
 
-	Cppflags   bazel.StringListAttribute
-	Srcs_c     bazel.LabelListAttribute
-	Conlyflags bazel.StringListAttribute
-	Srcs_as    bazel.LabelListAttribute
-	Asflags    bazel.StringListAttribute
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module, compilerAttrs.includes)
 
-	Static staticOrSharedAttributes
-}
+	// Append shared/static{} stanza properties. These won't be specified on
+	// cc_library_* itself, but may be specified in cc_defaults that this module
+	// depends on.
+	libSharedOrStaticAttrs := bp2BuildParseLibProps(ctx, module, isStatic)
 
-type bazelCcLibraryStatic struct {
-	android.BazelTargetModuleBase
-	bazelCcLibraryStaticAttributes
-}
+	compilerAttrs.srcs.Append(libSharedOrStaticAttrs.Srcs)
+	compilerAttrs.cSrcs.Append(libSharedOrStaticAttrs.Srcs_c)
+	compilerAttrs.asSrcs.Append(libSharedOrStaticAttrs.Srcs_as)
+	compilerAttrs.copts.Append(libSharedOrStaticAttrs.Copts)
 
-func BazelCcLibraryStaticFactory() android.Module {
-	module := &bazelCcLibraryStatic{}
-	module.AddProperties(&module.bazelCcLibraryStaticAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func ccLibraryStaticBp2BuildInternal(ctx android.TopDownMutatorContext, module *Module) {
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
+	linkerAttrs.deps.Append(libSharedOrStaticAttrs.Deps)
+	linkerAttrs.implementationDeps.Append(libSharedOrStaticAttrs.Implementation_deps)
+	linkerAttrs.dynamicDeps.Append(libSharedOrStaticAttrs.Dynamic_deps)
+	linkerAttrs.implementationDynamicDeps.Append(libSharedOrStaticAttrs.Implementation_dynamic_deps)
+	linkerAttrs.systemDynamicDeps.Append(libSharedOrStaticAttrs.System_dynamic_deps)
 
 	asFlags := compilerAttrs.asFlags
 	if compilerAttrs.asSrcs.IsEmpty() {
@@ -2364,68 +2434,155 @@
 		asFlags = bazel.MakeStringListAttribute(nil)
 	}
 
-	// Append static{} stanza properties. These won't be specified on
-	// cc_library_static itself, but may be specified in cc_defaults that this module
-	// depends on.
-	staticAttrs := bp2BuildParseStaticProps(ctx, module)
+	commonAttrs := staticOrSharedAttributes{
+		Srcs:    compilerAttrs.srcs,
+		Srcs_c:  compilerAttrs.cSrcs,
+		Srcs_as: compilerAttrs.asSrcs,
+		Copts:   compilerAttrs.copts,
+		Hdrs:    compilerAttrs.hdrs,
 
-	compilerAttrs.srcs.Append(staticAttrs.Srcs)
-	compilerAttrs.cSrcs.Append(staticAttrs.Srcs_c)
-	compilerAttrs.asSrcs.Append(staticAttrs.Srcs_as)
-	compilerAttrs.copts.Append(staticAttrs.Copts)
-	linkerAttrs.exportedDeps.Append(staticAttrs.Static_deps)
-	linkerAttrs.dynamicDeps.Append(staticAttrs.Dynamic_deps)
-	linkerAttrs.wholeArchiveDeps.Append(staticAttrs.Whole_archive_deps)
-	linkerAttrs.systemDynamicDeps.Append(staticAttrs.System_dynamic_deps)
-
-	attrs := &bazelCcLibraryStaticAttributes{
-		Copts:               compilerAttrs.copts,
-		Srcs:                compilerAttrs.srcs,
-		Implementation_deps: linkerAttrs.deps,
-		Deps:                linkerAttrs.exportedDeps,
-		Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
-		Dynamic_deps:        linkerAttrs.dynamicDeps,
-		System_dynamic_deps: linkerAttrs.systemDynamicDeps,
-
-		Linkopts:   linkerAttrs.linkopts,
-		Linkstatic: true,
-		Use_libcrt: linkerAttrs.useLibcrt,
-		Rtti:       compilerAttrs.rtti,
-		Includes:   exportedIncludes,
-
-		Cppflags:   compilerAttrs.cppFlags,
-		Srcs_c:     compilerAttrs.cSrcs,
-		Conlyflags: compilerAttrs.conlyFlags,
-		Srcs_as:    compilerAttrs.asSrcs,
-		Asflags:    asFlags,
+		Deps:                              linkerAttrs.deps,
+		Implementation_deps:               linkerAttrs.implementationDeps,
+		Dynamic_deps:                      linkerAttrs.dynamicDeps,
+		Implementation_dynamic_deps:       linkerAttrs.implementationDynamicDeps,
+		Whole_archive_deps:                linkerAttrs.wholeArchiveDeps,
+		Implementation_whole_archive_deps: linkerAttrs.implementationWholeArchiveDeps,
+		System_dynamic_deps:               linkerAttrs.systemDynamicDeps,
 	}
 
+	var attrs interface{}
+	if isStatic {
+		commonAttrs.Deps.Add(baseAttributes.protoDependency)
+		attrs = &bazelCcLibraryStaticAttributes{
+			staticOrSharedAttributes: commonAttrs,
+
+			Use_libcrt:      linkerAttrs.useLibcrt,
+			Use_version_lib: linkerAttrs.useVersionLib,
+
+			Rtti:    compilerAttrs.rtti,
+			Stl:     compilerAttrs.stl,
+			Cpp_std: compilerAttrs.cppStd,
+			C_std:   compilerAttrs.cStd,
+
+			Export_includes:          exportedIncludes.Includes,
+			Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+			Export_system_includes:   exportedIncludes.SystemIncludes,
+			Local_includes:           compilerAttrs.localIncludes,
+			Absolute_includes:        compilerAttrs.absoluteIncludes,
+
+			Cppflags:   compilerAttrs.cppFlags,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Asflags:    asFlags,
+
+			Features: linkerAttrs.features,
+		}
+	} else {
+		commonAttrs.Dynamic_deps.Add(baseAttributes.protoDependency)
+
+		attrs = &bazelCcLibrarySharedAttributes{
+			staticOrSharedAttributes: commonAttrs,
+
+			Cppflags:   compilerAttrs.cppFlags,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Asflags:    asFlags,
+
+			Linkopts:        linkerAttrs.linkopts,
+			Link_crt:        linkerAttrs.linkCrt,
+			Use_libcrt:      linkerAttrs.useLibcrt,
+			Use_version_lib: linkerAttrs.useVersionLib,
+
+			Rtti:    compilerAttrs.rtti,
+			Stl:     compilerAttrs.stl,
+			Cpp_std: compilerAttrs.cppStd,
+			C_std:   compilerAttrs.cStd,
+
+			Export_includes:          exportedIncludes.Includes,
+			Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+			Export_system_includes:   exportedIncludes.SystemIncludes,
+			Local_includes:           compilerAttrs.localIncludes,
+			Absolute_includes:        compilerAttrs.absoluteIncludes,
+			Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
+
+			Strip: stripAttributes{
+				Keep_symbols:                 linkerAttrs.stripKeepSymbols,
+				Keep_symbols_and_debug_frame: linkerAttrs.stripKeepSymbolsAndDebugFrame,
+				Keep_symbols_list:            linkerAttrs.stripKeepSymbolsList,
+				All:                          linkerAttrs.stripAll,
+				None:                         linkerAttrs.stripNone,
+			},
+
+			Features: linkerAttrs.features,
+		}
+	}
+
+	var modType string
+	if isStatic {
+		modType = "cc_library_static"
+	} else {
+		modType = "cc_library_shared"
+	}
 	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "cc_library_static",
-		Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl",
+		Rule_class:        modType,
+		Bzl_load_location: fmt.Sprintf("//build/bazel/rules:%s.bzl", modType),
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
 
-func CcLibraryStaticBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*Module)
-	if !ok {
-		// Not a cc module
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-	if ctx.ModuleType() != "cc_library_static" {
-		return
-	}
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
+type bazelCcLibraryStaticAttributes struct {
+	staticOrSharedAttributes
 
-	ccLibraryStaticBp2BuildInternal(ctx, module)
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
+
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Local_includes           bazel.StringListAttribute
+	Absolute_includes        bazel.StringListAttribute
+	Hdrs                     bazel.LabelListAttribute
+
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Features bazel.StringListAttribute
 }
 
-func (m *bazelCcLibraryStatic) Name() string {
-	return m.BaseModuleName()
-}
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
+type bazelCcLibrarySharedAttributes struct {
+	staticOrSharedAttributes
 
-func (m *bazelCcLibraryStatic) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+	Linkopts bazel.StringListAttribute
+	Link_crt bazel.BoolAttribute // Only for linking shared library (and cc_binary)
+
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
+
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Local_includes           bazel.StringListAttribute
+	Absolute_includes        bazel.StringListAttribute
+	Hdrs                     bazel.LabelListAttribute
+
+	Strip                    stripAttributes
+	Additional_linker_inputs bazel.LabelListAttribute
+
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Features bazel.StringListAttribute
+}
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 44a7a71..70e4715 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -25,7 +25,6 @@
 	// Register sdk member types.
 	android.RegisterSdkMemberType(headersLibrarySdkMemberType)
 
-	android.RegisterBp2BuildMutator("cc_library_headers", CcLibraryHeadersBp2Build)
 }
 
 var headersLibrarySdkMemberType = &librarySdkMemberType{
@@ -33,6 +32,11 @@
 		PropertyName:    "native_header_libs",
 		SupportsSdk:     true,
 		HostOsDependent: true,
+		Traits: []android.SdkMemberTrait{
+			nativeBridgeSdkTrait,
+			ramdiskImageRequiredSdkTrait,
+			recoveryImageRequiredSdkTrait,
+		},
 	},
 	prebuiltModuleType: "cc_prebuilt_library_headers",
 	noOutputFiles:      true,
@@ -52,7 +56,7 @@
 
 func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
 		return false
@@ -91,51 +95,41 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.HeaderOnly()
 	module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
+	module.bazelable = true
 	module.bazelHandler = &libraryHeaderBazelHander{module: module, library: library}
 	return module.Init()
 }
 
 // cc_prebuilt_library_headers is a prebuilt version of cc_library_headers
 func prebuiltLibraryHeaderFactory() android.Module {
-	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported, "")
 	library.HeaderOnly()
 	return module.Init()
 }
 
 type bazelCcLibraryHeadersAttributes struct {
-	Copts               bazel.StringListAttribute
-	Hdrs                bazel.LabelListAttribute
-	Includes            bazel.StringListAttribute
-	Deps                bazel.LabelListAttribute
-	Implementation_deps bazel.LabelListAttribute
-	System_dynamic_deps bazel.LabelListAttribute
+	Hdrs                     bazel.LabelListAttribute
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Deps                     bazel.LabelListAttribute
+	Implementation_deps      bazel.LabelListAttribute
+	System_dynamic_deps      bazel.LabelListAttribute
 }
 
-func CcLibraryHeadersBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*Module)
-	if !ok {
-		// Not a cc module
-		return
-	}
-
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "cc_library_headers" {
-		return
-	}
-
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
+func libraryHeadersBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	baseAttributes := bp2BuildParseBaseProps(ctx, module)
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module, baseAttributes.includes)
+	linkerAttrs := baseAttributes.linkerAttributes
 
 	attrs := &bazelCcLibraryHeadersAttributes{
-		Copts:               compilerAttrs.copts,
-		Includes:            exportedIncludes,
-		Implementation_deps: linkerAttrs.deps,
-		Deps:                linkerAttrs.exportedDeps,
-		System_dynamic_deps: linkerAttrs.systemDynamicDeps,
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Implementation_deps:      linkerAttrs.implementationDeps,
+		Deps:                     linkerAttrs.deps,
+		System_dynamic_deps:      linkerAttrs.systemDynamicDeps,
+		Hdrs:                     baseAttributes.hdrs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
@@ -143,5 +137,5 @@
 		Bzl_load_location: "//build/bazel/rules:cc_library_headers.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/cc/library_headers_test.go b/cc/library_headers_test.go
index 564ef61..3e448ba 100644
--- a/cc/library_headers_test.go
+++ b/cc/library_headers_test.go
@@ -15,48 +15,85 @@
 package cc
 
 import (
-	"strings"
+	"fmt"
 	"testing"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
 )
 
 func TestLibraryHeaders(t *testing.T) {
-	ctx := testCc(t, `
-	cc_library_headers {
-		name: "headers",
-		export_include_dirs: ["my_include"],
-	}
-	cc_library_static {
-		name: "lib",
-		srcs: ["foo.c"],
-		header_libs: ["headers"],
-	}
-	`)
+	bp := `
+		%s {
+			name: "headers",
+			export_include_dirs: ["my_include"],
+		}
+		cc_library_static {
+			name: "lib",
+			srcs: ["foo.c"],
+			header_libs: ["headers"],
+		}
+	`
 
-	// test if header search paths are correctly added
-	cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
-	cflags := cc.Args["cFlags"]
-	if !strings.Contains(cflags, " -Imy_include ") {
-		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
+	for _, headerModule := range []string{"cc_library_headers", "cc_prebuilt_library_headers"} {
+		t.Run(headerModule, func(t *testing.T) {
+			ctx := testCc(t, fmt.Sprintf(bp, headerModule))
+
+			// test if header search paths are correctly added
+			cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
+			android.AssertStringDoesContain(t, "cFlags for lib module", cc.Args["cFlags"], " -Imy_include ")
+
+			// Test that there's a valid AndroidMk entry.
+			headers := ctx.ModuleForTests("headers", "android_arm64_armv8-a").Module()
+			e := android.AndroidMkEntriesForTest(t, ctx, headers)[0]
+
+			// This duplicates the tests done in AndroidMkEntries.write. It would be
+			// better to test its output, but there are no test functions that capture that.
+			android.AssertBoolEquals(t, "AndroidMkEntries.Disabled", false, e.Disabled)
+			android.AssertBoolEquals(t, "AndroidMkEntries.OutputFile.Valid()", true, e.OutputFile.Valid())
+
+			android.AssertStringListContains(t, "LOCAL_EXPORT_CFLAGS for headers module", e.EntryMap["LOCAL_EXPORT_CFLAGS"], "-Imy_include")
+		})
 	}
 }
 
-func TestPrebuiltLibraryHeaders(t *testing.T) {
-	ctx := testCc(t, `
-	cc_prebuilt_library_headers {
-		name: "headers",
-		export_include_dirs: ["my_include"],
-	}
-	cc_library_static {
-		name: "lib",
-		srcs: ["foo.c"],
-		header_libs: ["headers"],
-	}
-	`)
+func TestPrebuiltLibraryHeadersPreferred(t *testing.T) {
+	bp := `
+		cc_library_headers {
+			name: "headers",
+			export_include_dirs: ["my_include"],
+		}
+		cc_prebuilt_library_headers {
+			name: "headers",
+			prefer: %t,
+			export_include_dirs: ["my_include"],
+		}
+		cc_library_static {
+			name: "lib",
+			srcs: ["foo.c"],
+			header_libs: ["headers"],
+		}
+	`
 
-	// test if header search paths are correctly added
-	cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
-	cflags := cc.Args["cFlags"]
-	if !strings.Contains(cflags, " -Imy_include ") {
-		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
+	for _, prebuiltPreferred := range []bool{false, true} {
+		t.Run(fmt.Sprintf("prebuilt prefer %t", prebuiltPreferred), func(t *testing.T) {
+			ctx := testCc(t, fmt.Sprintf(bp, prebuiltPreferred))
+			lib := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static")
+			sourceDep := ctx.ModuleForTests("headers", "android_arm64_armv8-a")
+			prebuiltDep := ctx.ModuleForTests("prebuilt_headers", "android_arm64_armv8-a")
+			hasSourceDep := false
+			hasPrebuiltDep := false
+			ctx.VisitDirectDeps(lib.Module(), func(dep blueprint.Module) {
+				if dep == sourceDep.Module() {
+					hasSourceDep = true
+				}
+				if dep == prebuiltDep.Module() {
+					hasPrebuiltDep = true
+				}
+			})
+			android.AssertBoolEquals(t, "depends on source headers", !prebuiltPreferred, hasSourceDep)
+			android.AssertBoolEquals(t, "depends on prebuilt headers", prebuiltPreferred, hasPrebuiltDep)
+		})
 	}
 }
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 1866ff3..8988de2 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -75,29 +75,112 @@
 }
 
 func (mt *librarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
-	targets := ctx.MultiTargets()
+	// The base set of targets which does not include native bridge targets.
+	defaultTargets := ctx.MultiTargets()
+
+	// The lazily created list of native bridge targets.
+	var includeNativeBridgeTargets []android.Target
+
 	for _, lib := range names {
-		for _, target := range targets {
-			name, version := StubsLibNameAndVersion(lib)
-			if version == "" {
-				version = "latest"
-			}
-			variations := target.Variations()
-			if ctx.Device() {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "image", Variation: android.CoreVariation})
-			}
-			if mt.linkTypes == nil {
-				ctx.AddFarVariationDependencies(variations, dependencyTag, name)
-			} else {
-				for _, linkType := range mt.linkTypes {
-					libVariations := append(variations,
-						blueprint.Variation{Mutator: "link", Variation: linkType})
-					if ctx.Device() && linkType == "shared" {
-						libVariations = append(libVariations,
-							blueprint.Variation{Mutator: "version", Variation: version})
+		targets := defaultTargets
+
+		// If native bridge support is required in the sdk snapshot then add native bridge targets to
+		// the basic list of targets that are required.
+		nativeBridgeSupport := ctx.RequiresTrait(lib, nativeBridgeSdkTrait)
+		if nativeBridgeSupport && ctx.Device() {
+			// If not already computed then compute the list of native bridge targets.
+			if includeNativeBridgeTargets == nil {
+				includeNativeBridgeTargets = append([]android.Target{}, defaultTargets...)
+				allAndroidTargets := ctx.Config().Targets[android.Android]
+				for _, possibleNativeBridgeTarget := range allAndroidTargets {
+					if possibleNativeBridgeTarget.NativeBridge == android.NativeBridgeEnabled {
+						includeNativeBridgeTargets = append(includeNativeBridgeTargets, possibleNativeBridgeTarget)
 					}
-					ctx.AddFarVariationDependencies(libVariations, dependencyTag, name)
+				}
+			}
+
+			// Include the native bridge targets as well.
+			targets = includeNativeBridgeTargets
+		}
+
+		// memberDependency encapsulates information about the dependencies to add for this member.
+		type memberDependency struct {
+			// The targets to depend upon.
+			targets []android.Target
+
+			// Additional image variations to depend upon, is either nil for no image variation or
+			// contains a single image variation.
+			imageVariations []blueprint.Variation
+		}
+
+		// Extract the name and version from the module name.
+		name, version := StubsLibNameAndVersion(lib)
+		if version == "" {
+			version = "latest"
+		}
+
+		// Compute the set of dependencies to add.
+		var memberDependencies []memberDependency
+		if ctx.Host() {
+			// Host does not support image variations so add a dependency without any.
+			memberDependencies = append(memberDependencies, memberDependency{
+				targets: targets,
+			})
+		} else {
+			// Otherwise, this is targeting the device so add a dependency on the core image variation
+			// (image:"").
+			memberDependencies = append(memberDependencies, memberDependency{
+				imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.CoreVariation}},
+				targets:         targets,
+			})
+
+			// If required add additional dependencies on the image:ramdisk variants.
+			if ctx.RequiresTrait(lib, ramdiskImageRequiredSdkTrait) {
+				memberDependencies = append(memberDependencies, memberDependency{
+					imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.RamdiskVariation}},
+					// Only add a dependency on the first target as that is the only one which will have an
+					// image:ramdisk variant.
+					targets: targets[:1],
+				})
+			}
+
+			// If required add additional dependencies on the image:recovery variants.
+			if ctx.RequiresTrait(lib, recoveryImageRequiredSdkTrait) {
+				memberDependencies = append(memberDependencies, memberDependency{
+					imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.RecoveryVariation}},
+					// Only add a dependency on the first target as that is the only one which will have an
+					// image:recovery variant.
+					targets: targets[:1],
+				})
+			}
+		}
+
+		// For each dependency in the list add dependencies on the targets with the correct variations.
+		for _, dependency := range memberDependencies {
+			// For each target add a dependency on the target with any additional dependencies.
+			for _, target := range dependency.targets {
+				// Get the variations for the target.
+				variations := target.Variations()
+
+				// Add any additional dependencies needed.
+				variations = append(variations, dependency.imageVariations...)
+
+				if mt.linkTypes == nil {
+					// No link types are supported so add a dependency directly.
+					ctx.AddFarVariationDependencies(variations, dependencyTag, name)
+				} else {
+					// Otherwise, add a dependency on each supported link type in turn.
+					for _, linkType := range mt.linkTypes {
+						libVariations := append(variations,
+							blueprint.Variation{Mutator: "link", Variation: linkType})
+						// If this is for the device and a shared link type then add a dependency onto the
+						// appropriate version specific variant of the module.
+						if ctx.Device() && linkType == "shared" {
+							libVariations = append(libVariations,
+								blueprint.Variation{Mutator: "version", Variation: version})
+						}
+						ctx.AddFarVariationDependencies(libVariations, dependencyTag, name)
+					}
 				}
 			}
 		}
@@ -122,7 +205,15 @@
 
 	ccModule := member.Variants()[0].(*Module)
 
-	if proptools.Bool(ccModule.Properties.Recovery_available) {
+	if ctx.RequiresTrait(nativeBridgeSdkTrait) {
+		pbm.AddProperty("native_bridge_supported", true)
+	}
+
+	if ctx.RequiresTrait(ramdiskImageRequiredSdkTrait) {
+		pbm.AddProperty("ramdisk_available", true)
+	}
+
+	if ctx.RequiresTrait(recoveryImageRequiredSdkTrait) {
 		pbm.AddProperty("recovery_available", true)
 	}
 
@@ -265,8 +356,8 @@
 	// values where necessary.
 	for _, propertyInfo := range includeDirProperties {
 		// Calculate the base directory in the snapshot into which the files will be copied.
-		// lib.archType is "" for common properties.
-		targetDir := filepath.Join(libInfo.OsPrefix(), libInfo.archType, propertyInfo.snapshotDir)
+		// lib.archSubDir is "" for common properties.
+		targetDir := filepath.Join(libInfo.OsPrefix(), libInfo.archSubDir, propertyInfo.snapshotDir)
 
 		propertyName := propertyInfo.propertyName
 
@@ -334,7 +425,7 @@
 
 // path to the native library. Relative to <sdk_root>/<api_dir>
 func nativeLibraryPathFor(lib *nativeLibInfoProperties) string {
-	return filepath.Join(lib.OsPrefix(), lib.archType,
+	return filepath.Join(lib.OsPrefix(), lib.archSubDir,
 		nativeStubDir, lib.outputFile.Base())
 }
 
@@ -347,9 +438,12 @@
 
 	memberType *librarySdkMemberType
 
-	// archType is not exported as if set (to a non default value) it is always arch specific.
-	// This is "" for common properties.
-	archType string
+	// archSubDir is the subdirectory within the OS directory in the sdk snapshot into which arch
+	// specific files will be copied.
+	//
+	// It is not exported since any value other than "" is always going to be arch specific.
+	// This is "" for non-arch specific common properties.
+	archSubDir string
 
 	// The list of possibly common exported include dirs.
 	//
@@ -433,7 +527,11 @@
 	exportedIncludeDirs, exportedGeneratedIncludeDirs := android.FilterPathListPredicate(
 		exportedInfo.IncludeDirs, isGeneratedHeaderDirectory)
 
-	p.archType = ccModule.Target().Arch.ArchType.String()
+	target := ccModule.Target()
+	p.archSubDir = target.Arch.ArchType.String()
+	if target.NativeBridge == android.NativeBridgeEnabled {
+		p.archSubDir += "_native_bridge"
+	}
 
 	// Make sure that the include directories are unique.
 	p.ExportedIncludeDirs = android.FirstUniquePaths(exportedIncludeDirs)
diff --git a/cc/library_test.go b/cc/library_test.go
index 6b349b6..d220e19 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -257,9 +257,16 @@
 				CcObjectFiles:        []string{"foo.o"},
 				Includes:             []string{"include"},
 				SystemIncludes:       []string{"system_include"},
-				RootStaticArchives:   []string{"foo.a"},
+				Headers:              []string{"foo.h"},
 				RootDynamicLibraries: []string{"foo.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"},
+			},
 		},
 	}
 	ctx := testCcWithConfig(t, config)
@@ -273,18 +280,25 @@
 	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo.a"}
 	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
 
+	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_object outputfiles %s", err)
+		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())
 
-	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)
+	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) {
@@ -320,3 +334,48 @@
 		libfoo.Args["ldFlags"], "-Wl,--dynamic-list,foo.dynamic.txt")
 
 }
+
+func TestCcLibrarySharedWithBazel(t *testing.T) {
+	bp := `
+cc_library_shared {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	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",
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	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{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	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)
+}
diff --git a/cc/linkable.go b/cc/linkable.go
index b510508..02d7047 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -110,6 +110,7 @@
 	BaseModuleName() string
 
 	OutputFile() android.OptionalPath
+	UnstrippedOutputFile() android.Path
 	CoverageFiles() android.Paths
 
 	NonCcVariants() bool
@@ -383,9 +384,13 @@
 
 	includes := android.PathsForBazelOut(ctx, ccInfo.Includes)
 	systemIncludes := android.PathsForBazelOut(ctx, ccInfo.SystemIncludes)
+	headers := android.PathsForBazelOut(ctx, ccInfo.Headers)
 
 	return FlagExporterInfo{
 		IncludeDirs:       android.FirstUniquePaths(includes),
 		SystemIncludeDirs: android.FirstUniquePaths(systemIncludes),
+		GeneratedHeaders:  headers,
+		// necessary to ensure generated headers are considered implicit deps of dependent actions
+		Deps: headers,
 	}
 }
diff --git a/cc/linker.go b/cc/linker.go
index 0d612b5..aaaca7a 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -27,6 +27,10 @@
 // This file contains the basic functionality for linking against static libraries and shared
 // libraries.  Final linking into libraries or executables is handled in library.go, binary.go, etc.
 
+const (
+	packRelocationsDefault = true
+)
+
 type BaseLinkerProperties struct {
 	// list of modules whose object files should be linked into this module
 	// in their entirety.  For static library modules, all of the .o files from the intermediate
@@ -238,6 +242,19 @@
 	return &ret
 }
 
+func (blp *BaseLinkerProperties) crt() *bool {
+	val := invertBoolPtr(blp.Nocrt)
+	if val != nil && *val {
+		// == True
+		//
+		// Since crt is enabled for almost every module compiling against the Bionic runtime,
+		// use `nil` when it's enabled, and rely on the Starlark macro to set it to True by default.
+		// This keeps the BUILD files clean.
+		return nil
+	}
+	return val // can be False or nil
+}
+
 func (blp *BaseLinkerProperties) libCrt() *bool {
 	return invertBoolPtr(blp.No_libcrt)
 }
@@ -458,7 +475,7 @@
 
 	if linker.useClangLld(ctx) {
 		flags.Global.LdFlags = append(flags.Global.LdFlags, fmt.Sprintf("${config.%sGlobalLldflags}", hod))
-		if !BoolDefault(linker.Properties.Pack_relocations, true) {
+		if !BoolDefault(linker.Properties.Pack_relocations, packRelocationsDefault) {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=none")
 		} else if ctx.Device() {
 			// SHT_RELR relocations are only supported at API level >= 30.
diff --git a/cc/lto.go b/cc/lto.go
index d9a0118..6d55579 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -49,8 +49,9 @@
 
 	// Dep properties indicate that this module needs to be built with LTO
 	// since it is an object dependency of an LTO module.
-	FullDep bool `blueprint:"mutated"`
-	ThinDep bool `blueprint:"mutated"`
+	FullDep  bool `blueprint:"mutated"`
+	ThinDep  bool `blueprint:"mutated"`
+	NoLtoDep bool `blueprint:"mutated"`
 
 	// Use clang lld instead of gnu ld.
 	Use_clang_lld *bool
@@ -70,15 +71,6 @@
 func (lto *lto) begin(ctx BaseModuleContext) {
 	if ctx.Config().IsEnvTrue("DISABLE_LTO") {
 		lto.Properties.Lto.Never = proptools.BoolPtr(true)
-	} else if ctx.Config().IsEnvTrue("GLOBAL_THINLTO") {
-		staticLib := ctx.static() && !ctx.staticBinary()
-		hostBin := ctx.Host()
-		vndk := ctx.isVndk() // b/169217596
-		if !staticLib && !hostBin && !vndk {
-			if !lto.Never() && !lto.FullLTO() {
-				lto.Properties.Lto.Thin = proptools.BoolPtr(true)
-			}
-		}
 	}
 }
 
@@ -96,22 +88,27 @@
 		return flags
 	}
 
-	if lto.LTO() {
-		var ltoFlag string
+	if lto.LTO(ctx) {
+		var ltoCFlag string
+		var ltoLdFlag string
 		if lto.ThinLTO() {
-			ltoFlag = "-flto=thin -fsplit-lto-unit"
+			ltoCFlag = "-flto=thin -fsplit-lto-unit"
+		} else if lto.FullLTO() {
+			ltoCFlag = "-flto"
 		} else {
-			ltoFlag = "-flto"
+			ltoCFlag = "-flto=thin -fsplit-lto-unit"
+			ltoLdFlag = "-Wl,--lto-O0"
 		}
 
-		flags.Local.CFlags = append(flags.Local.CFlags, ltoFlag)
-		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoFlag)
+		flags.Local.CFlags = append(flags.Local.CFlags, ltoCFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoCFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoLdFlag)
 
 		if Bool(lto.Properties.Whole_program_vtables) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fwhole-program-vtables")
 		}
 
-		if lto.ThinLTO() && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) {
+		if (lto.DefaultThinLTO(ctx) || lto.ThinLTO()) && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) {
 			// Set appropriate ThinLTO cache policy
 			cacheDirFormat := "-Wl,--thinlto-cache-dir="
 			cacheDir := android.PathForOutput(ctx, "thinlto-cache").String()
@@ -134,33 +131,40 @@
 	return flags
 }
 
-// Can be called with a null receiver
-func (lto *lto) LTO() bool {
-	if lto == nil || lto.Never() {
-		return false
-	}
+func (lto *lto) LTO(ctx BaseModuleContext) bool {
+	return lto.ThinLTO() || lto.FullLTO() || lto.DefaultThinLTO(ctx)
+}
 
-	return lto.FullLTO() || lto.ThinLTO()
+func (lto *lto) DefaultThinLTO(ctx BaseModuleContext) bool {
+	host := ctx.Host()
+	vndk := ctx.isVndk() // b/169217596
+	return GlobalThinLTO(ctx) && !lto.Never() && !host && !vndk
 }
 
 func (lto *lto) FullLTO() bool {
-	return Bool(lto.Properties.Lto.Full)
+	return lto != nil && Bool(lto.Properties.Lto.Full)
 }
 
 func (lto *lto) ThinLTO() bool {
-	return Bool(lto.Properties.Lto.Thin)
+	return lto != nil && Bool(lto.Properties.Lto.Thin)
 }
 
-// Is lto.never explicitly set to true?
 func (lto *lto) Never() bool {
-	return Bool(lto.Properties.Lto.Never)
+	return lto != nil && Bool(lto.Properties.Lto.Never)
+}
+
+func GlobalThinLTO(ctx android.BaseModuleContext) bool {
+	return ctx.Config().IsEnvTrue("GLOBAL_THINLTO")
 }
 
 // Propagate lto requirements down from binaries
 func ltoDepsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.lto.LTO() {
+	globalThinLTO := GlobalThinLTO(mctx)
+
+	if m, ok := mctx.Module().(*Module); ok {
 		full := m.lto.FullLTO()
 		thin := m.lto.ThinLTO()
+		never := m.lto.Never()
 		if full && thin {
 			mctx.PropertyErrorf("LTO", "FullLTO and ThinLTO are mutually exclusive")
 		}
@@ -180,14 +184,16 @@
 				}
 			}
 
-			if dep, ok := dep.(*Module); ok && dep.lto != nil &&
-				!dep.lto.Never() {
+			if dep, ok := dep.(*Module); ok {
 				if full && !dep.lto.FullLTO() {
 					dep.lto.Properties.FullDep = true
 				}
-				if thin && !dep.lto.ThinLTO() {
+				if !globalThinLTO && thin && !dep.lto.ThinLTO() {
 					dep.lto.Properties.ThinDep = true
 				}
+				if globalThinLTO && never && !dep.lto.Never() {
+					dep.lto.Properties.NoLtoDep = true
+				}
 			}
 
 			// Recursively walk static dependencies
@@ -198,6 +204,8 @@
 
 // Create lto variants for modules that need them
 func ltoMutator(mctx android.BottomUpMutatorContext) {
+	globalThinLTO := GlobalThinLTO(mctx)
+
 	if m, ok := mctx.Module().(*Module); ok && m.lto != nil {
 		// Create variations for LTO types required as static
 		// dependencies
@@ -205,18 +213,25 @@
 		if m.lto.Properties.FullDep && !m.lto.FullLTO() {
 			variationNames = append(variationNames, "lto-full")
 		}
-		if m.lto.Properties.ThinDep && !m.lto.ThinLTO() {
+		if !globalThinLTO && m.lto.Properties.ThinDep && !m.lto.ThinLTO() {
 			variationNames = append(variationNames, "lto-thin")
 		}
+		if globalThinLTO && m.lto.Properties.NoLtoDep && !m.lto.Never() {
+			variationNames = append(variationNames, "lto-none")
+		}
 
 		// Use correct dependencies if LTO property is explicitly set
 		// (mutually exclusive)
 		if m.lto.FullLTO() {
 			mctx.SetDependencyVariation("lto-full")
 		}
-		if m.lto.ThinLTO() {
+		if !globalThinLTO && m.lto.ThinLTO() {
 			mctx.SetDependencyVariation("lto-thin")
 		}
+		// Never must be the last, it overrides Thin or Full.
+		if globalThinLTO && m.lto.Never() {
+			mctx.SetDependencyVariation("lto-none")
+		}
 
 		if len(variationNames) > 1 {
 			modules := mctx.CreateVariations(variationNames...)
@@ -232,16 +247,18 @@
 				// LTO properties for dependencies
 				if name == "lto-full" {
 					variation.lto.Properties.Lto.Full = proptools.BoolPtr(true)
-					variation.lto.Properties.Lto.Thin = proptools.BoolPtr(false)
 				}
 				if name == "lto-thin" {
-					variation.lto.Properties.Lto.Full = proptools.BoolPtr(false)
 					variation.lto.Properties.Lto.Thin = proptools.BoolPtr(true)
 				}
+				if name == "lto-none" {
+					variation.lto.Properties.Lto.Never = proptools.BoolPtr(true)
+				}
 				variation.Properties.PreventInstall = true
 				variation.Properties.HideFromMake = true
 				variation.lto.Properties.FullDep = false
 				variation.lto.Properties.ThinDep = false
+				variation.lto.Properties.NoLtoDep = false
 			}
 		}
 	}
diff --git a/cc/makevars.go b/cc/makevars.go
index 8d7a163..b7aaaad 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -95,6 +95,7 @@
 	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ExternalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE", "")
+	ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
 
 	ctx.Strict("BOARD_VNDK_VERSION", ctx.DeviceConfig().VndkVersion())
 	ctx.Strict("RECOVERY_SNAPSHOT_VERSION", ctx.DeviceConfig().RecoverySnapshotVersion())
diff --git a/cc/native_bridge_sdk_trait.go b/cc/native_bridge_sdk_trait.go
new file mode 100644
index 0000000..1326d57
--- /dev/null
+++ b/cc/native_bridge_sdk_trait.go
@@ -0,0 +1,33 @@
+// Copyright 2021 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 cc
+
+import "android/soong/android"
+
+// This file contains support for the native bridge sdk trait.
+
+func init() {
+	android.RegisterSdkMemberTrait(nativeBridgeSdkTrait)
+}
+
+type nativeBridgeSdkTraitStruct struct {
+	android.SdkMemberTraitBase
+}
+
+var nativeBridgeSdkTrait android.SdkMemberTrait = &nativeBridgeSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "native_bridge_support",
+	},
+}
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 704b03a..7879a7d 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -272,7 +272,7 @@
 
 func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
 	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
-		android.Paths{src}, nil, nil)
+		android.Paths{src}, nil, nil, nil)
 }
 
 func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index 2726b1a..6c200f5 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -33,7 +33,7 @@
 // Component 3: Stub Libraries
 // The shared libraries in the NDK are not the actual shared libraries they
 // refer to (to prevent people from accidentally loading them), but stub
-// libraries with dummy implementations of everything for use at build time
+// libraries with placeholder implementations of everything for use at build time
 // only.
 //
 // Since we don't actually need to know anything about the stub libraries aside
@@ -82,6 +82,12 @@
 	return android.PathForOutput(ctx, "ndk_base.timestamp")
 }
 
+// The headers timestamp file depends only on the NDK headers.
+// This is used mainly for .tidy files that do not need any stub libraries.
+func getNdkHeadersTimestampFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForOutput(ctx, "ndk_headers.timestamp")
+}
+
 // The full timestamp file depends on the base timestamp *and* the static
 // libraries.
 func getNdkFullTimestampFile(ctx android.PathContext) android.WritablePath {
@@ -96,6 +102,7 @@
 
 func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var staticLibInstallPaths android.Paths
+	var headerPaths android.Paths
 	var installPaths android.Paths
 	var licensePaths android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
@@ -104,16 +111,19 @@
 		}
 
 		if m, ok := module.(*headerModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*versionedHeaderModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*preprocessedHeadersModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
@@ -153,6 +163,12 @@
 		Validation: getNdkAbiDiffTimestampFile(ctx),
 	})
 
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    getNdkHeadersTimestampFile(ctx),
+		Implicits: headerPaths,
+	})
+
 	fullDepPaths := append(staticLibInstallPaths, getNdkBaseTimestampFile(ctx))
 
 	// There's a phony "ndk" rule defined in core/main.mk that depends on this.
diff --git a/cc/object.go b/cc/object.go
index 606e368..bd43e36 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -29,7 +29,6 @@
 	android.RegisterModuleType("cc_object", ObjectFactory)
 	android.RegisterSdkMemberType(ccObjectSdkMemberType)
 
-	android.RegisterBp2BuildMutator("cc_object", ObjectBp2Build)
 }
 
 var ccObjectSdkMemberType = &librarySdkMemberType{
@@ -54,7 +53,7 @@
 
 func (handler *objectBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	objPaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
+	objPaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
 	if ok {
 		if len(objPaths) != 1 {
 			ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
@@ -117,46 +116,57 @@
 
 	module.sdkMemberTypes = []android.SdkMemberType{ccObjectSdkMemberType}
 
+	module.bazelable = true
 	return module.Init()
 }
 
 // For bp2build conversion.
 type bazelObjectAttributes struct {
-	Srcs    bazel.LabelListAttribute
-	Srcs_as bazel.LabelListAttribute
-	Hdrs    bazel.LabelListAttribute
-	Deps    bazel.LabelListAttribute
-	Copts   bazel.StringListAttribute
-	Asflags bazel.StringListAttribute
+	Srcs                bazel.LabelListAttribute
+	Srcs_as             bazel.LabelListAttribute
+	Hdrs                bazel.LabelListAttribute
+	Deps                bazel.LabelListAttribute
+	System_dynamic_deps bazel.LabelListAttribute
+	Copts               bazel.StringListAttribute
+	Asflags             bazel.StringListAttribute
+	Local_includes      bazel.StringListAttribute
+	Absolute_includes   bazel.StringListAttribute
+	Stl                 *string
+	Linker_script       bazel.LabelAttribute
 }
 
-// ObjectBp2Build is the bp2build converter from cc_object modules to the
+// objectBp2Build is the bp2build converter from cc_object modules to the
 // Bazel equivalent target, plus any necessary include deps for the cc_object.
-func ObjectBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	// a Module can be something other than a cc_object.
-	if ctx.ModuleType() != "cc_object" {
-		return
-	}
-
+func objectBp2Build(ctx android.TopDownMutatorContext, m *Module) {
 	if m.compiler == nil {
 		// a cc_object must have access to the compiler decorator for its props.
 		ctx.ModuleErrorf("compiler must not be nil for a cc_object module")
 	}
 
 	// Set arch-specific configurable attributes
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
+	baseAttributes := bp2BuildParseBaseProps(ctx, m)
+	compilerAttrs := baseAttributes.compilerAttributes
 	var deps bazel.LabelListAttribute
-	for _, props := range m.linker.linkerProps() {
-		if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
-			deps = bazel.MakeLabelListAttribute(
-				android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
+	systemDynamicDeps := bazel.LabelListAttribute{ForceSpecifyEmptyList: true}
+
+	var linkerScript bazel.LabelAttribute
+
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &ObjectLinkerProperties{}) {
+		for config, props := range configToProps {
+			if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
+				if objectLinkerProps.Linker_script != nil {
+					linkerScript.SetSelectValue(axis, config, android.BazelLabelForModuleSrcSingle(ctx, *objectLinkerProps.Linker_script))
+				}
+				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
+				systemSharedLibs := objectLinkerProps.System_shared_libs
+				if len(systemSharedLibs) > 0 {
+					systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
+				}
+				systemDynamicDeps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, systemSharedLibs))
+			}
 		}
 	}
+	deps.ResolveExcludes()
 
 	// Don't split cc_object srcs across languages. Doing so would add complexity,
 	// and this isn't typically done for cc_object.
@@ -170,11 +180,16 @@
 	}
 
 	attrs := &bazelObjectAttributes{
-		Srcs:    srcs,
-		Srcs_as: compilerAttrs.asSrcs,
-		Deps:    deps,
-		Copts:   compilerAttrs.copts,
-		Asflags: asFlags,
+		Srcs:                srcs,
+		Srcs_as:             compilerAttrs.asSrcs,
+		Deps:                deps,
+		System_dynamic_deps: systemDynamicDeps,
+		Copts:               compilerAttrs.copts,
+		Asflags:             asFlags,
+		Local_includes:      compilerAttrs.localIncludes,
+		Absolute_includes:   compilerAttrs.absoluteIncludes,
+		Stl:                 compilerAttrs.stl,
+		Linker_script:       linkerScript,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
@@ -182,7 +197,7 @@
 		Bzl_load_location: "//build/bazel/rules:cc_object.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 func (object *objectLinker) appendLdflags(flags []string) {
diff --git a/cc/pgo.go b/cc/pgo.go
index e78549e..cd017c4 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -96,10 +96,6 @@
 	flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrumentFlag)
 	return flags
 }
-func (props *PgoProperties) addSamplingProfileGatherFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...)
-	return flags
-}
 
 func (props *PgoProperties) getPgoProfileFile(ctx BaseModuleContext) android.OptionalPath {
 	profileFile := *props.Pgo.Profile_file
@@ -313,10 +309,6 @@
 	if (props.ShouldProfileModule && props.isInstrumentation()) || props.PgoInstrLink {
 		// Instrumentation PGO use and gather flags cannot coexist.
 		return props.addInstrumentationProfileGatherFlags(ctx, flags)
-	} else if props.ShouldProfileModule && props.isSampling() {
-		flags = props.addSamplingProfileGatherFlags(ctx, flags)
-	} else if ctx.DeviceConfig().SamplingPGO() {
-		flags = props.addSamplingProfileGatherFlags(ctx, flags)
 	}
 
 	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index d324241..feae812 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -16,9 +16,9 @@
 
 import (
 	"path/filepath"
-	"strings"
 
 	"android/soong/android"
+	"android/soong/bazel"
 )
 
 func init() {
@@ -112,8 +112,6 @@
 	// TODO(ccross): verify shared library dependencies
 	srcs := p.prebuiltSrcs(ctx)
 	if len(srcs) > 0 {
-		builderFlags := flagsToBuilderFlags(flags)
-
 		if len(srcs) > 1 {
 			ctx.PropertyErrorf("srcs", "multiple prebuilt source files")
 			return nil
@@ -150,7 +148,7 @@
 			// depending on a table of contents file instead of the library itself.
 			tocFile := android.PathForModuleOut(ctx, libName+".toc")
 			p.tocFile = android.OptionalPathForPath(tocFile)
-			transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+			TransformSharedObjectToToc(ctx, outputFile, tocFile)
 
 			if ctx.Windows() && p.properties.Windows_import_lib != nil {
 				// Consumers of this library actually links to the import library in build
@@ -197,7 +195,13 @@
 	if p.header() {
 		ctx.SetProvider(HeaderLibraryInfoProvider, HeaderLibraryInfo{})
 
-		return nil
+		// Need to return an output path so that the AndroidMk logic doesn't skip
+		// the prebuilt header. For compatibility, in case Android.mk files use a
+		// header lib in LOCAL_STATIC_LIBRARIES, create an empty ar file as
+		// placeholder, just like non-prebuilt header modules do in linkStatic().
+		ph := android.PathForModuleOut(ctx, ctx.ModuleName()+staticLibraryExtension)
+		transformObjToStaticLib(ctx, nil, nil, builderFlags{}, ph, nil, nil)
+		return ph
 	}
 
 	return nil
@@ -232,10 +236,10 @@
 
 // Implements versionedInterface
 func (p *prebuiltLibraryLinker) implementationModuleName(name string) string {
-	return strings.TrimPrefix(name, "prebuilt_")
+	return android.RemoveOptionalPrebuiltPrefix(name)
 }
 
-func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
+func NewPrebuiltLibrary(hod android.HostOrDeviceSupported, srcsProperty string) (*Module, *libraryDecorator) {
 	module, library := NewLibrary(hod)
 	module.compiler = nil
 
@@ -247,11 +251,15 @@
 
 	module.AddProperties(&prebuilt.properties)
 
-	srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
-		return prebuilt.prebuiltSrcs(ctx)
-	}
+	if srcsProperty == "" {
+		android.InitPrebuiltModuleWithoutSrcs(module)
+	} else {
+		srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
+			return prebuilt.prebuiltSrcs(ctx)
+		}
 
-	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "srcs")
+		android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, srcsProperty)
+	}
 
 	// Prebuilt libraries can be used in SDKs.
 	android.InitSdkAwareModule(module)
@@ -261,7 +269,7 @@
 // cc_prebuilt_library installs a precompiled shared library that are
 // listed in the srcs property in the device's directory.
 func PrebuiltLibraryFactory() android.Module {
-	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported, "srcs")
 
 	// Prebuilt shared libraries can be included in APEXes
 	android.InitApexModule(module)
@@ -280,15 +288,16 @@
 // to be used as a data dependency of a test-related module (such as cc_test, or
 // cc_test_library).
 func PrebuiltSharedTestLibraryFactory() android.Module {
-	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported, "srcs")
 	library.BuildOnlyShared()
 	library.baseInstaller = NewTestInstaller()
 	return module.Init()
 }
 
 func NewPrebuiltSharedLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
-	module, library := NewPrebuiltLibrary(hod)
+	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyShared()
+	module.bazelable = true
 
 	// Prebuilt shared libraries can be included in APEXes
 	android.InitApexModule(module)
@@ -304,12 +313,58 @@
 }
 
 func NewPrebuiltStaticLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
-	module, library := NewPrebuiltLibrary(hod)
+	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyStatic()
+	module.bazelable = true
 	module.bazelHandler = &prebuiltStaticLibraryBazelHandler{module: module, library: library}
 	return module, library
 }
 
+type bazelPrebuiltLibraryStaticAttributes struct {
+	Static_library         bazel.LabelAttribute
+	Export_includes        bazel.StringListAttribute
+	Export_system_includes bazel.StringListAttribute
+}
+
+func prebuiltLibraryStaticBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module)
+	exportedIncludes := Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx, module)
+
+	attrs := &bazelPrebuiltLibraryStaticAttributes{
+		Static_library:         prebuiltAttrs.Src,
+		Export_includes:        exportedIncludes.Includes,
+		Export_system_includes: exportedIncludes.SystemIncludes,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "prebuilt_library_static",
+		Bzl_load_location: "//build/bazel/rules:prebuilt_library_static.bzl",
+	}
+
+	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+}
+
+type bazelPrebuiltLibrarySharedAttributes struct {
+	Shared_library bazel.LabelAttribute
+}
+
+func prebuiltLibrarySharedBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module)
+
+	attrs := &bazelPrebuiltLibrarySharedAttributes{
+		Shared_library: prebuiltAttrs.Src,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "prebuilt_library_shared",
+		Bzl_load_location: "//build/bazel/rules:prebuilt_library_shared.bzl",
+	}
+
+	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+}
+
 type prebuiltObjectProperties struct {
 	Srcs []string `android:"path,arch_variant"`
 }
@@ -330,7 +385,7 @@
 
 func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
 	}
@@ -497,7 +552,7 @@
 }
 
 func NewPrebuiltBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.compiler = nil
 
 	prebuilt := &prebuiltBinaryLinker{
diff --git a/cc/proto.go b/cc/proto.go
index 4466144..f3410bc 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -16,8 +16,14 @@
 
 import (
 	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
+)
+
+const (
+	protoTypeDefault = "lite"
 )
 
 // genProto creates a rule to convert a .proto file to generated .pb.cc and .pb.h files and returns
@@ -63,7 +69,7 @@
 	var lib string
 
 	if String(p.Proto.Plugin) == "" {
-		switch String(p.Proto.Type) {
+		switch proptools.StringDefault(p.Proto.Type, protoTypeDefault) {
 		case "full":
 			if ctx.useSdk() {
 				lib = "libprotobuf-cpp-full-ndk"
@@ -71,7 +77,7 @@
 			} else {
 				lib = "libprotobuf-cpp-full"
 			}
-		case "lite", "":
+		case "lite":
 			if ctx.useSdk() {
 				lib = "libprotobuf-cpp-lite-ndk"
 				static = true
@@ -157,3 +163,69 @@
 
 	return flags
 }
+
+type protoAttributes struct {
+	Deps bazel.LabelListAttribute
+}
+
+type bp2buildProtoDeps struct {
+	wholeStaticLib               *bazel.LabelAttribute
+	implementationWholeStaticLib *bazel.LabelAttribute
+	protoDep                     *bazel.LabelAttribute
+}
+
+func bp2buildProto(ctx android.Bp2buildMutatorContext, m *Module, protoSrcs bazel.LabelListAttribute) bp2buildProtoDeps {
+	var ret bp2buildProtoDeps
+
+	protoInfo, ok := android.Bp2buildProtoProperties(ctx, m, protoSrcs)
+	if !ok {
+		return ret
+	}
+
+	var depName string
+	typ := proptools.StringDefault(protoInfo.Type, protoTypeDefault)
+	var rule_class string
+	suffix := "_cc_proto"
+	switch typ {
+	case "lite":
+		suffix += "_lite"
+		rule_class = "cc_lite_proto_library"
+		depName = "libprotobuf-cpp-lite"
+	case "full":
+		rule_class = "cc_proto_library"
+		depName = "libprotobuf-cpp-full"
+	default:
+		ctx.PropertyErrorf("proto.type", "cannot handle conversion at this time: %q", typ)
+	}
+
+	dep := android.BazelLabelForModuleDepSingle(ctx, depName)
+	ret.protoDep = &bazel.LabelAttribute{Value: &dep}
+
+	protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
+	var protoAttrs protoAttributes
+	protoAttrs.Deps.SetValue(bazel.LabelList{Includes: []bazel.Label{protoLabel}})
+
+	name := m.Name() + suffix
+
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{
+			Rule_class:        rule_class,
+			Bzl_load_location: "//build/bazel/rules:cc_proto.bzl",
+		},
+		android.CommonAttributes{Name: name},
+		&protoAttrs)
+
+	var privateHdrs bool
+	if lib, ok := m.linker.(*libraryDecorator); ok {
+		privateHdrs = !proptools.Bool(lib.Properties.Proto.Export_proto_headers)
+	}
+
+	labelAttr := &bazel.LabelAttribute{Value: &bazel.Label{Label: ":" + name}}
+	if privateHdrs {
+		ret.implementationWholeStaticLib = labelAttr
+	} else {
+		ret.wholeStaticLib = labelAttr
+	}
+
+	return ret
+}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index f6a9d5b..d7b1ade 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -35,12 +35,18 @@
 
 	asanCflags = []string{
 		"-fno-omit-frame-pointer",
-		"-fno-experimental-new-pass-manager",
 	}
 	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
 
-	hwasanCflags = []string{"-fno-omit-frame-pointer", "-Wno-frame-larger-than=",
+	hwasanCflags = []string{
+		"-fno-omit-frame-pointer",
+		"-Wno-frame-larger-than=",
 		"-fsanitize-hwaddress-abi=platform",
+	}
+
+	// ThinLTO performs codegen during link time, thus these flags need to
+	// passed to both CFLAGS and LDFLAGS.
+	hwasanCommonflags = []string{
 		// The following improves debug location information
 		// availability at the cost of its accuracy. It increases
 		// the likelihood of a stack variable's frame offset
@@ -48,11 +54,11 @@
 		// for the quality of hwasan reports. The downside is a
 		// higher number of "optimized out" stack variables.
 		// b/112437883.
-		"-mllvm", "-instcombine-lower-dbg-declare=0",
+		"-instcombine-lower-dbg-declare=0",
 		// TODO(b/159343917): HWASan and GlobalISel don't play nicely, and
 		// GlobalISel is the default at -O0 on aarch64.
-		"-mllvm", "--aarch64-enable-global-isel-at-O=-1",
-		"-mllvm", "-fast-isel=false",
+		"--aarch64-enable-global-isel-at-O=-1",
+		"-fast-isel=false",
 	}
 
 	cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso",
@@ -81,7 +87,7 @@
 	intOverflow
 	scs
 	Fuzzer
-	memtag_heap
+	Memtag_heap
 	cfi // cfi is last to prevent it running before incompatible mutators
 )
 
@@ -92,7 +98,7 @@
 	intOverflow,
 	scs,
 	Fuzzer,
-	memtag_heap,
+	Memtag_heap,
 	cfi, // cfi is last to prevent it running before incompatible mutators
 }
 
@@ -111,7 +117,7 @@
 		return "cfi"
 	case scs:
 		return "scs"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case Fuzzer:
 		return "fuzzer"
@@ -127,7 +133,7 @@
 		return "address"
 	case Hwasan:
 		return "hwaddress"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case tsan:
 		return "thread"
@@ -149,7 +155,7 @@
 	case Asan, Hwasan, Fuzzer, scs, tsan, cfi:
 		ctx.TopDown(t.variationName()+"_deps", sanitizerDepsMutator(t))
 		ctx.BottomUp(t.variationName(), sanitizerMutator(t))
-	case memtag_heap, intOverflow:
+	case Memtag_heap, intOverflow:
 		// do nothing
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
@@ -172,6 +178,8 @@
 		return true
 	case Fuzzer:
 		return true
+	case Memtag_heap:
+		return true
 	default:
 		return false
 	}
@@ -460,7 +468,7 @@
 		s.Scs = nil
 	}
 
-	// memtag_heap is only implemented on AArch64.
+	// Memtag_heap is only implemented on AArch64.
 	if ctx.Arch().ArchType != android.Arm64 {
 		s.Memtag_heap = nil
 	}
@@ -629,6 +637,14 @@
 
 	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
 		flags.Local.CFlags = append(flags.Local.CFlags, hwasanCflags...)
+
+		for _, flag := range hwasanCommonflags {
+			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", flag)
+		}
+		for _, flag := range hwasanCommonflags {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
+		}
+
 		if Bool(sanitize.Properties.Sanitize.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-hwasan-instrument-reads=0")
 		}
@@ -649,9 +665,6 @@
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-coverage=stack-depth")
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-coverage=stack-depth")
 
-		// TODO(b/133876586): Experimental PM breaks sanitizer coverage.
-		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-experimental-new-pass-manager")
-
 		// Disable fortify for fuzzing builds. Generally, we'll be building with
 		// UBSan or ASan here and the fortify checks pollute the stack traces.
 		flags.Local.CFlags = append(flags.Local.CFlags, "-U_FORTIFY_SOURCE")
@@ -798,7 +811,7 @@
 		return sanitize.Properties.Sanitize.Cfi
 	case scs:
 		return sanitize.Properties.Sanitize.Scs
-	case memtag_heap:
+	case Memtag_heap:
 		return sanitize.Properties.Sanitize.Memtag_heap
 	case Fuzzer:
 		return sanitize.Properties.Sanitize.Fuzzer
@@ -814,7 +827,7 @@
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(cfi) &&
 		!sanitize.isSanitizerEnabled(scs) &&
-		!sanitize.isSanitizerEnabled(memtag_heap) &&
+		!sanitize.isSanitizerEnabled(Memtag_heap) &&
 		!sanitize.isSanitizerEnabled(Fuzzer)
 }
 
@@ -844,7 +857,7 @@
 		sanitize.Properties.Sanitize.Cfi = bPtr
 	case scs:
 		sanitize.Properties.Sanitize.Scs = bPtr
-	case memtag_heap:
+	case Memtag_heap:
 		sanitize.Properties.Sanitize.Memtag_heap = bPtr
 	case Fuzzer:
 		sanitize.Properties.Sanitize.Fuzzer = bPtr
@@ -1133,7 +1146,7 @@
 			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
 				noteDep = lib
 			}
-			depTag := libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: true}
+			depTag := StaticDepTag(true)
 			variations := append(mctx.Target().Variations(),
 				blueprint.Variation{Mutator: "link", Variation: "static"})
 			if c.Device() {
@@ -1303,6 +1316,10 @@
 func sanitizerMutator(t SanitizerType) func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
 		if c, ok := mctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
+
+			// Make sure we're not setting CFI to any value if it's not supported.
+			cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
+
 			if c.Binary() && c.IsSanitizerEnabled(t) {
 				modules := mctx.CreateVariations(t.variationName())
 				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
@@ -1323,7 +1340,6 @@
 					// is redirected to the sanitized variant of the dependent module.
 					defaultVariation := t.variationName()
 					// Not all PlatformSanitizeable modules support the CFI sanitizer
-					cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
 					mctx.SetDefaultDependencyVariation(&defaultVariation)
 
 					modules := mctx.CreateVariations("", t.variationName())
@@ -1370,7 +1386,7 @@
 						modules[0].(PlatformSanitizeable).SetInSanitizerDir()
 					}
 
-					if mctx.Device() && t.incompatibleWithCfi() {
+					if mctx.Device() && t.incompatibleWithCfi() && cfiSupported {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
 						// are incompatible with cfi
 						modules[0].(PlatformSanitizeable).SetSanitizer(cfi, false)
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index 9570664..253a11b 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -66,7 +66,7 @@
 
 // Override existing vendor and recovery snapshot for cc module specific extra functions
 var VendorSnapshotImageSingleton vendorSnapshotImage = vendorSnapshotImage{&snapshot.VendorSnapshotImageSingleton}
-var recoverySnapshotImageSingleton recoverySnapshotImage = recoverySnapshotImage{&snapshot.RecoverySnapshotImageSingleton}
+var RecoverySnapshotImageSingleton recoverySnapshotImage = recoverySnapshotImage{&snapshot.RecoverySnapshotImageSingleton}
 
 func RegisterVendorSnapshotModules(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("vendor_snapshot", vendorSnapshotFactory)
@@ -231,7 +231,7 @@
 }
 
 func recoverySnapshotFactory() android.Module {
-	return snapshotFactory(recoverySnapshotImageSingleton)
+	return snapshotFactory(RecoverySnapshotImageSingleton)
 }
 
 func snapshotFactory(image SnapshotImage) android.Module {
@@ -326,7 +326,7 @@
 		return
 	}
 
-	images := []SnapshotImage{VendorSnapshotImageSingleton, recoverySnapshotImageSingleton}
+	images := []SnapshotImage{VendorSnapshotImageSingleton, RecoverySnapshotImageSingleton}
 
 	for _, image := range images {
 		if p.Image == image {
@@ -476,13 +476,12 @@
 
 	if p.shared() {
 		libName := in.Base()
-		builderFlags := flagsToBuilderFlags(flags)
 
 		// Optimize out relinking against shared libraries whose interface hasn't changed by
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		TransformSharedObjectToToc(ctx, in, tocFile)
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
 			SharedLibrary: in,
@@ -584,7 +583,7 @@
 // overrides the recovery variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, SnapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, SnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -604,7 +603,7 @@
 // overrides the recovery variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, SnapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, SnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -624,7 +623,7 @@
 // overrides the recovery variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -699,7 +698,7 @@
 // development/vendor_snapshot/update.py. As a part of recovery snapshot, recovery_snapshot_binary
 // overrides the recovery variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func RecoverySnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(recoverySnapshotImageSingleton, snapshotBinarySuffix)
+	return snapshotBinaryFactory(RecoverySnapshotImageSingleton, snapshotBinarySuffix)
 }
 
 func snapshotBinaryFactory(image SnapshotImage, moduleSuffix string) android.Module {
@@ -801,7 +800,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.Init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
+	prebuilt.Init(module, RecoverySnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index 24abcce..de50ef5 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -113,7 +113,7 @@
 		return ctx.Config().VndkSnapshotBuildArtifacts()
 	}
 
-	for _, image := range []SnapshotImage{VendorSnapshotImageSingleton, recoverySnapshotImageSingleton} {
+	for _, image := range []SnapshotImage{VendorSnapshotImageSingleton, RecoverySnapshotImageSingleton} {
 		if isSnapshotAware(ctx.DeviceConfig(), m, image.IsProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()), apexInfo, image) {
 			return true
 		}
diff --git a/cc/stub_library.go b/cc/stub_library.go
index 1722c80..76da782 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"sort"
 	"strings"
 
 	"android/soong/android"
@@ -27,6 +28,8 @@
 
 type stubLibraries struct {
 	stubLibraryMap map[string]bool
+
+	apiListCoverageXmlPaths []string
 }
 
 // Check if the module defines stub, or itself is stub
@@ -53,6 +56,11 @@
 					s.stubLibraryMap[name] = true
 				}
 			}
+			if m.library != nil {
+				if p := m.library.getAPIListCoverageXMLPath().String(); p != "" {
+					s.apiListCoverageXmlPaths = append(s.apiListCoverageXmlPaths, p)
+				}
+			}
 		}
 	})
 }
@@ -66,4 +74,8 @@
 func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
 	// Convert stub library file names into Makefile variable.
 	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+
+	// Export the list of API XML files to Make.
+	sort.Strings(s.apiListCoverageXmlPaths)
+	ctx.Strict("SOONG_CC_API_XML", strings.Join(s.apiListCoverageXmlPaths, " "))
 }
diff --git a/cc/test.go b/cc/test.go
index 047a69e..d8b7833 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -80,6 +80,9 @@
 	// list of shared library modules that should be installed alongside the test
 	Data_libs []string `android:"arch_variant"`
 
+	// list of binary modules that should be installed alongside the test
+	Data_bins []string `android:"arch_variant"`
+
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
@@ -102,11 +105,6 @@
 	// Add RunCommandTargetPreparer to stop framework before the test and start it after the test.
 	Disable_framework *bool
 
-	// Add ShippingApiLevelModuleController to auto generated test config. If the device properties
-	// for the shipping api level is less than the test_min_api_level, skip this module.
-	// Deprecated (b/187258404). Use test_options.min_shipping_api_level instead.
-	Test_min_api_level *int64
-
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -115,6 +113,9 @@
 	// Add parameterized mainline modules to auto generated test config. The options will be
 	// handled by TradeFed to download and install the specified modules on the device.
 	Test_mainline_modules []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 func init() {
@@ -347,6 +348,7 @@
 	deps = test.testDecorator.linkerDeps(ctx, deps)
 	deps = test.binaryDecorator.linkerDeps(ctx, deps)
 	deps.DataLibs = append(deps.DataLibs, test.Properties.Data_libs...)
+	deps.DataBins = append(deps.DataBins, test.Properties.Data_bins...)
 	return deps
 }
 
@@ -371,19 +373,26 @@
 
 	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
-		ccDep, ok := dep.(LinkableInterface)
-
+		linkableDep, ok := dep.(LinkableInterface)
 		if !ok {
-			ctx.ModuleErrorf("data_lib %q is not a linkable cc module", depName)
+			ctx.ModuleErrorf("data_lib %q is not a LinkableInterface module", depName)
 		}
-		ccModule, ok := dep.(*Module)
-		if !ok {
-			ctx.ModuleErrorf("data_lib %q is not a cc module", depName)
-		}
-		if ccDep.OutputFile().Valid() {
+		if linkableDep.OutputFile().Valid() {
 			test.data = append(test.data,
-				android.DataPath{SrcPath: ccDep.OutputFile().Path(),
-					RelativeInstallPath: ccModule.installer.relativeInstallPath()})
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_bin %q is not a LinkableInterface module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
 		}
 	})
 
@@ -418,14 +427,6 @@
 		var options []tradefed.Option
 		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_shipping_api_level), 10)})
 		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
-	} else if test.Properties.Test_min_api_level != nil {
-		// TODO: (b/187258404) Remove test.Properties.Test_min_api_level
-		if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
-			ctx.PropertyErrorf("test_min_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
-		}
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
 	}
 	if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
 		var options []tradefed.Option
@@ -460,7 +461,7 @@
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.multilib = android.MultilibBoth
 	binary.baseInstaller = NewTestInstaller()
 
@@ -550,6 +551,10 @@
 	testConfig android.Path
 }
 
+func (benchmark *benchmarkDecorator) benchmarkBinary() bool {
+	return true
+}
+
 func (benchmark *benchmarkDecorator) linkerInit(ctx BaseModuleContext) {
 	runpath := "../../lib"
 	if ctx.toolchain().Is64Bit() {
@@ -587,7 +592,7 @@
 }
 
 func NewBenchmark(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.multilib = android.MultilibBoth
 	binary.baseInstaller = NewBaseInstaller("benchmarktest", "benchmarktest64", InstallInData)
 
diff --git a/cc/testing.go b/cc/testing.go
index d0dca6b..3bf936d 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -33,7 +33,7 @@
 	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
-	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
+	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_static_stl", NdkPrebuiltStaticStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_object", NdkPrebuiltObjectFactory)
@@ -763,3 +763,11 @@
 	}
 	return paths
 }
+
+func AssertExcludeFromRecoverySnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool, variant string) {
+	t.Helper()
+	m := ctx.ModuleForTests(name, variant).Module().(LinkableInterface)
+	if m.ExcludeFromRecoverySnapshot() != expected {
+		t.Errorf("expected %q ExcludeFromRecoverySnapshot to be %t", m.String(), expected)
+	}
+}
diff --git a/cc/tidy.go b/cc/tidy.go
index fefa7f0..78a791f 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"path/filepath"
 	"regexp"
 	"strings"
 
@@ -148,6 +149,9 @@
 	tidyChecks = tidyChecks + ",-bugprone-branch-clone"
 	// http://b/193716442
 	tidyChecks = tidyChecks + ",-bugprone-implicit-widening-of-multiplication-result"
+	// Too many existing functions trigger this rule, and fixing it requires large code
+	// refactoring. The cost of maintaining this tidy rule outweighs the benefit it brings.
+	tidyChecks = tidyChecks + ",-bugprone-easily-swappable-parameters"
 	flags.TidyFlags = append(flags.TidyFlags, tidyChecks)
 
 	if ctx.Config().IsEnvTrue("WITH_TIDY") {
@@ -180,3 +184,154 @@
 	}
 	return flags
 }
+
+func init() {
+	android.RegisterSingletonType("tidy_phony_targets", TidyPhonySingleton)
+}
+
+// This TidyPhonySingleton generates both tidy-* and obj-* phony targets for C/C++ files.
+func TidyPhonySingleton() android.Singleton {
+	return &tidyPhonySingleton{}
+}
+
+type tidyPhonySingleton struct{}
+
+// Given a final module, add its tidy/obj phony targets to tidy/objModulesInDirGroup.
+func collectTidyObjModuleTargets(ctx android.SingletonContext, module android.Module,
+	tidyModulesInDirGroup, objModulesInDirGroup map[string]map[string]android.Paths) {
+	allObjFileGroups := make(map[string]android.Paths)     // variant group name => obj file Paths
+	allTidyFileGroups := make(map[string]android.Paths)    // variant group name => tidy file Paths
+	subsetObjFileGroups := make(map[string]android.Paths)  // subset group name => obj file Paths
+	subsetTidyFileGroups := make(map[string]android.Paths) // subset group name => tidy file Paths
+
+	// (1) Collect all obj/tidy files into OS-specific groups.
+	ctx.VisitAllModuleVariants(module, func(variant android.Module) {
+		if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(variant) {
+			return
+		}
+		if m, ok := variant.(*Module); ok {
+			osName := variant.Target().Os.Name
+			addToOSGroup(osName, m.objFiles, allObjFileGroups, subsetObjFileGroups)
+			addToOSGroup(osName, m.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
+		}
+	})
+
+	// (2) Add an all-OS group, with "" or "subset" name, to include all os-specific phony targets.
+	addAllOSGroup(ctx, module, allObjFileGroups, "", "obj")
+	addAllOSGroup(ctx, module, allTidyFileGroups, "", "tidy")
+	addAllOSGroup(ctx, module, subsetObjFileGroups, "subset", "obj")
+	addAllOSGroup(ctx, module, subsetTidyFileGroups, "subset", "tidy")
+
+	tidyTargetGroups := make(map[string]android.Path)
+	objTargetGroups := make(map[string]android.Path)
+	genObjTidyPhonyTargets(ctx, module, "obj", allObjFileGroups, objTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "obj", subsetObjFileGroups, objTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "tidy", allTidyFileGroups, tidyTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "tidy", subsetTidyFileGroups, tidyTargetGroups)
+
+	moduleDir := ctx.ModuleDir(module)
+	appendToModulesInDirGroup(tidyTargetGroups, moduleDir, tidyModulesInDirGroup)
+	appendToModulesInDirGroup(objTargetGroups, moduleDir, objModulesInDirGroup)
+}
+
+func (m *tidyPhonySingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// For tidy-* directory phony targets, there are different variant groups.
+	// tidyModulesInDirGroup[G][D] is for group G, directory D, with Paths
+	// of all phony targets to be included into direct dependents of tidy-D_G.
+	tidyModulesInDirGroup := make(map[string]map[string]android.Paths)
+	// Also for obj-* directory phony targets.
+	objModulesInDirGroup := make(map[string]map[string]android.Paths)
+
+	// Collect tidy/obj targets from the 'final' modules.
+	ctx.VisitAllModules(func(module android.Module) {
+		if module == ctx.FinalModule(module) {
+			collectTidyObjModuleTargets(ctx, module, tidyModulesInDirGroup, objModulesInDirGroup)
+		}
+	})
+
+	suffix := ""
+	if ctx.Config().KatiEnabled() {
+		suffix = "-soong"
+	}
+	generateObjTidyPhonyTargets(ctx, suffix, "obj", objModulesInDirGroup)
+	generateObjTidyPhonyTargets(ctx, suffix, "tidy", tidyModulesInDirGroup)
+}
+
+// The name for an obj/tidy module variant group phony target is Name_group-obj/tidy,
+func objTidyModuleGroupName(module android.Module, group string, suffix string) string {
+	if group == "" {
+		return module.Name() + "-" + suffix
+	}
+	return module.Name() + "_" + group + "-" + suffix
+}
+
+// Generate obj-* or tidy-* phony targets.
+func generateObjTidyPhonyTargets(ctx android.SingletonContext, suffix string, prefix string, objTidyModulesInDirGroup map[string]map[string]android.Paths) {
+	// For each variant group, create a <prefix>-<directory>_group target that
+	// depends on all subdirectories and modules in the directory.
+	for group, modulesInDir := range objTidyModulesInDirGroup {
+		groupSuffix := ""
+		if group != "" {
+			groupSuffix = "_" + group
+		}
+		mmTarget := func(dir string) string {
+			return prefix + "-" + strings.Replace(filepath.Clean(dir), "/", "-", -1) + groupSuffix
+		}
+		dirs, topDirs := android.AddAncestors(ctx, modulesInDir, mmTarget)
+		// Create a <prefix>-soong_group target that depends on all <prefix>-dir_group of top level dirs.
+		var topDirPaths android.Paths
+		for _, dir := range topDirs {
+			topDirPaths = append(topDirPaths, android.PathForPhony(ctx, mmTarget(dir)))
+		}
+		ctx.Phony(prefix+suffix+groupSuffix, topDirPaths...)
+		// Create a <prefix>-dir_group target that depends on all targets in modulesInDir[dir]
+		for _, dir := range dirs {
+			if dir != "." && dir != "" {
+				ctx.Phony(mmTarget(dir), modulesInDir[dir]...)
+			}
+		}
+	}
+}
+
+// Append (obj|tidy)TargetGroups[group] into (obj|tidy)ModulesInDirGroups[group][moduleDir].
+func appendToModulesInDirGroup(targetGroups map[string]android.Path, moduleDir string, modulesInDirGroup map[string]map[string]android.Paths) {
+	for group, phonyPath := range targetGroups {
+		if _, found := modulesInDirGroup[group]; !found {
+			modulesInDirGroup[group] = make(map[string]android.Paths)
+		}
+		modulesInDirGroup[group][moduleDir] = append(modulesInDirGroup[group][moduleDir], phonyPath)
+	}
+}
+
+// Add given files to the OS group and subset group.
+func addToOSGroup(osName string, files android.Paths, allGroups, subsetGroups map[string]android.Paths) {
+	if len(files) > 0 {
+		subsetName := osName + "_subset"
+		allGroups[osName] = append(allGroups[osName], files...)
+		// Now include only the first variant in the subsetGroups.
+		// If clang and clang-tidy get faster, we might include more variants.
+		if _, found := subsetGroups[subsetName]; !found {
+			subsetGroups[subsetName] = files
+		}
+	}
+}
+
+// Add an all-OS group, with groupName, to include all os-specific phony targets.
+func addAllOSGroup(ctx android.SingletonContext, module android.Module, phonyTargetGroups map[string]android.Paths, groupName string, objTidyName string) {
+	if len(phonyTargetGroups) > 0 {
+		var targets android.Paths
+		for group, _ := range phonyTargetGroups {
+			targets = append(targets, android.PathForPhony(ctx, objTidyModuleGroupName(module, group, objTidyName)))
+		}
+		phonyTargetGroups[groupName] = targets
+	}
+}
+
+// Create one phony targets for each group and add them to the targetGroups.
+func genObjTidyPhonyTargets(ctx android.SingletonContext, module android.Module, objTidyName string, fileGroups map[string]android.Paths, targetGroups map[string]android.Path) {
+	for group, files := range fileGroups {
+		groupName := objTidyModuleGroupName(module, group, objTidyName)
+		ctx.Phony(groupName, files...)
+		targetGroups[group] = android.PathForPhony(ctx, groupName)
+	}
+}
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index ba4d79f..8a17e2e 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -132,12 +132,9 @@
 	return false
 }
 
-// This is to be saved as .json files, which is for development/vendor_snapshot/update.py.
-// These flags become Android.bp snapshot module properties.
+// Extend the snapshot.SnapshotJsonFlags to include cc specific fields.
 type snapshotJsonFlags struct {
-	ModuleName          string `json:",omitempty"`
-	RelativeInstallPath string `json:",omitempty"`
-
+	snapshot.SnapshotJsonFlags
 	// library flags
 	ExportedDirs       []string `json:",omitempty"`
 	ExportedSystemDirs []string `json:",omitempty"`
@@ -154,7 +151,6 @@
 	SharedLibs  []string `json:",omitempty"`
 	StaticLibs  []string `json:",omitempty"`
 	RuntimeLibs []string `json:",omitempty"`
-	Required    []string `json:",omitempty"`
 
 	// extra config files
 	InitRc         []string `json:",omitempty"`
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index ca2f569..b5022c8 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -1020,14 +1020,6 @@
 	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
 }
 
-func assertExcludeFromRecoverySnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool) {
-	t.Helper()
-	m := ctx.ModuleForTests(name, recoveryVariant).Module().(*Module)
-	if m.ExcludeFromRecoverySnapshot() != expected {
-		t.Errorf("expected %q ExcludeFromRecoverySnapshot to be %t", m.String(), expected)
-	}
-}
-
 func TestVendorSnapshotExclude(t *testing.T) {
 
 	// This test verifies that the exclude_from_vendor_snapshot property
@@ -1371,13 +1363,13 @@
 	android.FailIfErrored(t, errs)
 
 	// Test an include and exclude framework module.
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false)
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true)
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false, recoveryVariant)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true, recoveryVariant)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true, recoveryVariant)
 
 	// A recovery module is excluded, but by its path, not the
 	// exclude_from_recovery_snapshot property.
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false, recoveryVariant)
 
 	// Verify the content of the recovery snapshot.
 
diff --git a/cc/vndk.go b/cc/vndk.go
index 1ae79de..c9c9f2c 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -495,7 +495,7 @@
 		filterOutFromMakeVar: filter,
 	}
 	m.AddProperties(&m.properties)
-	android.InitAndroidModule(m)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
 
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index da34f36..31b6d10 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -144,7 +144,6 @@
 		// current VNDK prebuilts are only shared libs.
 
 		in := p.singleSourcePath(ctx)
-		builderFlags := flagsToBuilderFlags(flags)
 		p.unstrippedOutputFile = in
 		libName := in.Base()
 		if p.stripper.NeedsStrip(ctx) {
@@ -158,7 +157,7 @@
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		TransformSharedObjectToToc(ctx, in, tocFile)
 
 		p.androidMkSuffix = p.NameSuffix()
 
diff --git a/cmd/extract_apks/main.go b/cmd/extract_apks/main.go
index 6e51a28..1cf64de 100644
--- a/cmd/extract_apks/main.go
+++ b/cmd/extract_apks/main.go
@@ -356,7 +356,7 @@
 
 // Writes out selected entries, renaming them as needed
 func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
-	writer Zip2ZipWriter, partition string) ([]string, error) {
+	outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
 	// Renaming rules:
 	//  splits/MODULE-master.apk to STEM.apk
 	// else
@@ -406,8 +406,14 @@
 				origin, inName, outName)
 		}
 		entryOrigin[outName] = inName
-		if err := writer.CopyFrom(apkFile, outName); err != nil {
-			return nil, err
+		if outName == config.stem+".apk" {
+			if err := writeZipEntryToFile(outFile, apkFile); err != nil {
+				return nil, err
+			}
+		} else {
+			if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
+				return nil, err
+			}
 		}
 		if partition != "" {
 			apkcerts = append(apkcerts, fmt.Sprintf(
@@ -426,14 +432,13 @@
 	if !ok {
 		return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
 	}
-	inputReader, _ := apk.Open()
-	_, err := io.Copy(outFile, inputReader)
-	return err
+	return writeZipEntryToFile(outFile, apk)
 }
 
 // Arguments parsing
 var (
-	outputFile   = flag.String("o", "", "output file containing extracted entries")
+	outputFile   = flag.String("o", "", "output file for primary entry")
+	zipFile      = flag.String("zip", "", "output file containing additional extracted entries")
 	targetConfig = TargetConfig{
 		screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
 		abis:      map[android_bundle_proto.Abi_AbiAlias]int{},
@@ -494,7 +499,8 @@
 
 func processArgs() {
 	flag.Usage = func() {
-		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> -sdk-version value -abis value `+
+		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
+			`-sdk-version value -abis value `+
 			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
 			`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
 		flag.PrintDefaults()
@@ -510,7 +516,8 @@
 	flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
 	flag.Parse()
 	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
-		(targetConfig.stem == "" && !*extractSingle) || (*apkcertsOutput != "" && *partition == "") {
+		((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
+		(*apkcertsOutput != "" && *partition == "") {
 		flag.Usage()
 	}
 	targetConfig.sdkVersion = int32(*version)
@@ -542,13 +549,20 @@
 	if *extractSingle {
 		err = apkSet.extractAndCopySingle(sel, outFile)
 	} else {
-		writer := zip.NewWriter(outFile)
+		zipOutputFile, err := os.Create(*zipFile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer zipOutputFile.Close()
+
+		zipWriter := zip.NewWriter(zipOutputFile)
 		defer func() {
-			if err := writer.Close(); err != nil {
+			if err := zipWriter.Close(); err != nil {
 				log.Fatal(err)
 			}
 		}()
-		apkcerts, err := apkSet.writeApks(sel, targetConfig, writer, *partition)
+
+		apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
 		if err == nil && *apkcertsOutput != "" {
 			apkcertsFile, err := os.Create(*apkcertsOutput)
 			if err != nil {
@@ -567,3 +581,13 @@
 		log.Fatal(err)
 	}
 }
+
+func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
+	reader, err := zipEntry.Open()
+	if err != nil {
+		return err
+	}
+	defer reader.Close()
+	_, err = io.Copy(outFile, reader)
+	return err
+}
diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go
index 9fcf324..f5e4046 100644
--- a/cmd/extract_apks/main_test.go
+++ b/cmd/extract_apks/main_test.go
@@ -15,6 +15,7 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"reflect"
 	"testing"
@@ -437,8 +438,8 @@
 	stem       string
 	partition  string
 	// what we write from what
-	expectedZipEntries map[string]string
-	expectedApkcerts   []string
+	zipEntries       map[string]string
+	expectedApkcerts []string
 }
 
 func TestWriteApks(t *testing.T) {
@@ -448,7 +449,7 @@
 			moduleName: "mybase",
 			stem:       "Foo",
 			partition:  "system",
-			expectedZipEntries: map[string]string{
+			zipEntries: map[string]string{
 				"Foo.apk":       "splits/mybase-master.apk",
 				"Foo-xhdpi.apk": "splits/mybase-xhdpi.apk",
 			},
@@ -462,7 +463,7 @@
 			moduleName: "base",
 			stem:       "Bar",
 			partition:  "product",
-			expectedZipEntries: map[string]string{
+			zipEntries: map[string]string{
 				"Bar.apk": "universal.apk",
 			},
 			expectedApkcerts: []string{
@@ -471,23 +472,46 @@
 		},
 	}
 	for _, testCase := range testCases {
-		apkSet := ApkSet{entries: make(map[string]*zip.File)}
-		sel := SelectionResult{moduleName: testCase.moduleName}
-		for _, in := range testCase.expectedZipEntries {
-			apkSet.entries[in] = &zip.File{FileHeader: zip.FileHeader{Name: in}}
-			sel.entries = append(sel.entries, in)
-		}
-		writer := testZip2ZipWriter{make(map[string]string)}
-		config := TargetConfig{stem: testCase.stem}
-		apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition)
-		if err != nil {
-			t.Error(err)
-		}
-		if !reflect.DeepEqual(testCase.expectedZipEntries, writer.entries) {
-			t.Errorf("expected zip entries %v, got %v", testCase.expectedZipEntries, writer.entries)
-		}
-		if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) {
-			t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts)
-		}
+		t.Run(testCase.name, func(t *testing.T) {
+			testZipBuf := &bytes.Buffer{}
+			testZip := zip.NewWriter(testZipBuf)
+			for _, in := range testCase.zipEntries {
+				f, _ := testZip.Create(in)
+				f.Write([]byte(in))
+			}
+			testZip.Close()
+
+			zipReader, _ := zip.NewReader(bytes.NewReader(testZipBuf.Bytes()), int64(testZipBuf.Len()))
+
+			apkSet := ApkSet{entries: make(map[string]*zip.File)}
+			sel := SelectionResult{moduleName: testCase.moduleName}
+			for _, f := range zipReader.File {
+				apkSet.entries[f.Name] = f
+				sel.entries = append(sel.entries, f.Name)
+			}
+
+			zipWriter := testZip2ZipWriter{make(map[string]string)}
+			outWriter := &bytes.Buffer{}
+			config := TargetConfig{stem: testCase.stem}
+			apkcerts, err := apkSet.writeApks(sel, config, outWriter, zipWriter, testCase.partition)
+			if err != nil {
+				t.Error(err)
+			}
+			expectedZipEntries := make(map[string]string)
+			for k, v := range testCase.zipEntries {
+				if k != testCase.stem+".apk" {
+					expectedZipEntries[k] = v
+				}
+			}
+			if !reflect.DeepEqual(expectedZipEntries, zipWriter.entries) {
+				t.Errorf("expected zip entries %v, got %v", testCase.zipEntries, zipWriter.entries)
+			}
+			if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) {
+				t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts)
+			}
+			if g, w := outWriter.String(), testCase.zipEntries[testCase.stem+".apk"]; !reflect.DeepEqual(g, w) {
+				t.Errorf("expected output file contents %q, got %q", testCase.stem+".apk", outWriter.String())
+			}
+		})
 	}
 }
diff --git a/cmd/multiproduct_kati/Android.bp b/cmd/multiproduct_kati/Android.bp
index e5be6c0..20ca2a3 100644
--- a/cmd/multiproduct_kati/Android.bp
+++ b/cmd/multiproduct_kati/Android.bp
@@ -31,4 +31,14 @@
     testSrcs: [
         "main_test.go",
     ],
+    linux: {
+        srcs: [
+            "main_linux.go",
+        ],
+    },
+    darwin: {
+        srcs: [
+            "main_darwin.go",
+        ],
+    },
 }
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index fa63b46..0577c86 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -166,15 +166,6 @@
 	MainLogsDir string
 }
 
-func detectTotalRAM() uint64 {
-	var info syscall.Sysinfo_t
-	err := syscall.Sysinfo(&info)
-	if err != nil {
-		panic(err)
-	}
-	return info.Totalram * uint64(info.Unit)
-}
-
 func findNamedProducts(soongUi string, log logger.Logger) []string {
 	cmd := exec.Command(soongUi, "--dumpvars-mode", "--vars=all_named_products")
 	output, err := cmd.Output()
@@ -218,10 +209,16 @@
 	}
 }
 
+func forceAnsiOutput() bool {
+	value := os.Getenv("SOONG_UI_ANSI_OUTPUT")
+	return value == "1" || value == "y" || value == "yes" || value == "on" || value == "true"
+}
+
 func main() {
 	stdio := terminal.StdioImpl{}
 
-	output := terminal.NewStatusOutput(stdio.Stdout(), "", false, false)
+	output := terminal.NewStatusOutput(stdio.Stdout(), "", false, false,
+		forceAnsiOutput())
 	log := logger.New(output)
 	defer log.Cleanup()
 
@@ -295,7 +292,7 @@
 		jobs = runtime.NumCPU() / 4
 
 		ramGb := int(detectTotalRAM() / (1024 * 1024 * 1024))
-		if ramJobs := ramGb / 25; ramGb > 0 && jobs > ramJobs {
+		if ramJobs := ramGb / 30; ramGb > 0 && jobs > ramJobs {
 			jobs = ramJobs
 		}
 
diff --git a/cmd/multiproduct_kati/main_darwin.go b/cmd/multiproduct_kati/main_darwin.go
new file mode 100644
index 0000000..3d1b12a
--- /dev/null
+++ b/cmd/multiproduct_kati/main_darwin.go
@@ -0,0 +1,20 @@
+// 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 main
+
+func detectTotalRAM() uint64 {
+	// unimplemented stub on darwin
+	return 0
+}
diff --git a/cmd/multiproduct_kati/main_linux.go b/cmd/multiproduct_kati/main_linux.go
new file mode 100644
index 0000000..db74496
--- /dev/null
+++ b/cmd/multiproduct_kati/main_linux.go
@@ -0,0 +1,28 @@
+// 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 main
+
+import (
+	"syscall"
+)
+
+func detectTotalRAM() uint64 {
+	var info syscall.Sysinfo_t
+	err := syscall.Sysinfo(&info)
+	if err != nil {
+		panic(err)
+	}
+	return info.Totalram * uint64(info.Unit)
+}
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index d31489e..f8844fc 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -24,6 +24,7 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -93,6 +94,8 @@
 
 var extraLibs = make(ExtraDeps)
 
+var optionalUsesLibs = make(ExtraDeps)
+
 type Exclude map[string]bool
 
 func (e Exclude) String() string {
@@ -162,7 +165,8 @@
 type Dependency struct {
 	XMLName xml.Name `xml:"dependency"`
 
-	BpTarget string `xml:"-"`
+	BpTarget    string `xml:"-"`
+	BazelTarget string `xml:"-"`
 
 	GroupId    string `xml:"groupId"`
 	ArtifactId string `xml:"artifactId"`
@@ -228,6 +232,14 @@
 	}
 }
 
+func (p Pom) BazelTargetType() string {
+	if p.IsAar() {
+		return "android_library"
+	} else {
+		return "java_library"
+	}
+}
+
 func (p Pom) ImportModuleType() string {
 	if p.IsAar() {
 		return "android_library_import"
@@ -238,6 +250,14 @@
 	}
 }
 
+func (p Pom) BazelImportTargetType() string {
+	if p.IsAar() {
+		return "aar_import"
+	} else {
+		return "java_import"
+	}
+}
+
 func (p Pom) ImportProperty() string {
 	if p.IsAar() {
 		return "aars"
@@ -246,6 +266,14 @@
 	}
 }
 
+func (p Pom) BazelImportProperty() string {
+	if p.IsAar() {
+		return "aar"
+	} else {
+		return "jars"
+	}
+}
+
 func (p Pom) BpName() string {
 	if p.BpTarget == "" {
 		p.BpTarget = rewriteNames.MavenToBp(p.GroupId, p.ArtifactId)
@@ -261,6 +289,14 @@
 	return p.BpDeps("aar", []string{"compile", "runtime"})
 }
 
+func (p Pom) BazelJarDeps() []string {
+	return p.BazelDeps("jar", []string{"compile", "runtime"})
+}
+
+func (p Pom) BazelAarDeps() []string {
+	return p.BazelDeps("aar", []string{"compile", "runtime"})
+}
+
 func (p Pom) BpExtraStaticLibs() []string {
 	return extraStaticLibs[p.BpName()]
 }
@@ -269,6 +305,10 @@
 	return extraLibs[p.BpName()]
 }
 
+func (p Pom) BpOptionalUsesLibs() []string {
+	return optionalUsesLibs[p.BpName()]
+}
+
 // BpDeps obtains dependencies filtered by type and scope. The results of this
 // method are formatted as Android.bp targets, e.g. run through MavenToBp rules.
 func (p Pom) BpDeps(typeExt string, scopes []string) []string {
@@ -283,6 +323,91 @@
 	return ret
 }
 
+// BazelDeps obtains dependencies filtered by type and scope. The results of this
+// method are formatted as Bazel BUILD targets.
+func (p Pom) BazelDeps(typeExt string, scopes []string) []string {
+	var ret []string
+	for _, d := range p.Dependencies {
+		if d.Type != typeExt || !InList(d.Scope, scopes) {
+			continue
+		}
+		ret = append(ret, d.BazelTarget)
+	}
+	return ret
+}
+
+func PathModVars() (string, string, string) {
+	cmd := "/bin/bash"
+	androidTop := os.Getenv("ANDROID_BUILD_TOP")
+	envSetupSh := path.Join(androidTop, "build/envsetup.sh")
+	return cmd, androidTop, envSetupSh
+}
+
+func InitRefreshMod(poms []*Pom) error {
+	cmd, _, envSetupSh := PathModVars()
+	// refreshmod is expensive, so if pathmod is already working we can skip it.
+	_, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && pathmod "+poms[0].BpName()).Output()
+	if exitErr, _ := err.(*exec.ExitError); exitErr != nil || err != nil {
+		_, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && refreshmod").Output()
+		if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+			return fmt.Errorf("failed to run %s\n%s\ntry running lunch.", cmd, string(exitErr.Stderr))
+		} else if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func BazelifyExtraDeps(extraDeps ExtraDeps, modules map[string]*Pom) error {
+	for _, deps := range extraDeps {
+		for _, dep := range deps {
+			bazelName, err := BpNameToBazelTarget(dep, modules)
+			if err != nil {
+				return err
+			}
+			dep = bazelName
+		}
+
+	}
+	return nil
+}
+
+func (p *Pom) GetBazelDepNames(modules map[string]*Pom) error {
+	for _, d := range p.Dependencies {
+		bazelName, err := BpNameToBazelTarget(d.BpName(), modules)
+		if err != nil {
+			return err
+		}
+		d.BazelTarget = bazelName
+	}
+	return nil
+}
+
+func BpNameToBazelTarget(bpName string, modules map[string]*Pom) (string, error) {
+	cmd, androidTop, envSetupSh := PathModVars()
+
+	if _, ok := modules[bpName]; ok {
+		// We've seen the POM for this dependency, it will be local to the output BUILD file
+		return ":" + bpName, nil
+	} else {
+		// we don't have the POM for this artifact, find and use the fully qualified target name.
+		output, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && pathmod "+bpName).Output()
+		if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+			return "", fmt.Errorf("failed to run %s %s\n%s", cmd, bpName, string(exitErr.Stderr))
+		} else if err != nil {
+			return "", err
+		}
+		relPath := ""
+		for _, line := range strings.Fields(string(output)) {
+			if strings.Contains(line, androidTop) {
+				relPath = strings.TrimPrefix(line, androidTop)
+				relPath = strings.TrimLeft(relPath, "/")
+			}
+		}
+		return "//" + relPath + ":" + bpName, nil
+	}
+}
+
 func (p Pom) SdkVersion() string {
 	return sdkVersion
 }
@@ -401,6 +526,13 @@
         {{- end}}
     ],
     {{- end}}
+    {{- if .BpOptionalUsesLibs}}
+    optional_uses_libs: [
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
     {{- else if not .IsHostOnly}}
     min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
@@ -488,10 +620,69 @@
         {{- end}}
     ],
     {{- end}}
+    {{- if .BpOptionalUsesLibs}}
+    optional_uses_libs: [
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
     java_version: "1.7",
 }
 `))
 
+var bazelTemplate = template.Must(template.New("bp").Parse(`
+{{.BazelImportTargetType}} (
+    name = "{{.BpName}}",
+    {{.BazelImportProperty}}: {{- if not .IsAar}}[{{- end}}"{{.ArtifactFile}}"{{- if not .IsAar}}]{{- end}},
+    visibility = ["//visibility:public"],
+    {{- if .IsAar}}
+    deps = [
+        {{- range .BazelJarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BazelAarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraStaticLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+)
+`))
+
+var bazelDepsTemplate = template.Must(template.New("bp").Parse(`
+{{.BazelImportTargetType}} (
+    name = "{{.BpName}}",
+    {{.BazelImportProperty}} = {{- if not .IsAar}}[{{- end}}"{{.ArtifactFile}}"{{- if not .IsAar}}]{{- end}},
+    visibility = ["//visibility:public"],
+    exports = [
+        {{- range .BazelJarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BazelAarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraStaticLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+)
+`))
+
 func parse(filename string) (*Pom, error) {
 	data, err := ioutil.ReadFile(filename)
 	if err != nil {
@@ -539,12 +730,14 @@
 
 	// Extract the old args from the file
 	line := scanner.Text()
-	if strings.HasPrefix(line, "// pom2bp ") {
+	if strings.HasPrefix(line, "// pom2bp ") { // .bp file
 		line = strings.TrimPrefix(line, "// pom2bp ")
-	} else if strings.HasPrefix(line, "// pom2mk ") {
+	} else if strings.HasPrefix(line, "// pom2mk ") { // .bp file converted from .mk file
 		line = strings.TrimPrefix(line, "// pom2mk ")
-	} else if strings.HasPrefix(line, "# pom2mk ") {
+	} else if strings.HasPrefix(line, "# pom2mk ") { // .mk file
 		line = strings.TrimPrefix(line, "# pom2mk ")
+	} else if strings.HasPrefix(line, "# pom2bp ") { // Bazel BUILD file
+		line = strings.TrimPrefix(line, "# pom2bp ")
 	} else {
 		return fmt.Errorf("unexpected second line: %q", line)
 	}
@@ -587,7 +780,7 @@
 The tool will extract the necessary information from *.pom files to create an Android.bp whose
 aar libraries can be linked against when using AAPT2.
 
-Usage: %s [--rewrite <regex>=<replace>] [-exclude <module>] [--extra-static-libs <module>=<module>[,<module>]] [--extra-libs <module>=<module>[,<module>]] [<dir>] [-regen <file>]
+Usage: %s [--rewrite <regex>=<replace>] [--exclude <module>] [--extra-static-libs <module>=<module>[,<module>]] [--extra-libs <module>=<module>[,<module>]] [--optional-uses-libs <module>=<module>[,<module>]] [<dir>] [-regen <file>]
 
   -rewrite <regex>=<replace>
      rewrite can be used to specify mappings between Maven projects and Android.bp modules. The -rewrite
@@ -605,6 +798,11 @@
      Some Android.bp modules have transitive runtime dependencies that must be specified when they
      are depended upon (like androidx.test.rules requires android.test.base).
      This may be specified multiple times to declare these dependencies.
+  -optional-uses-libs <module>=<module>[,<module>]
+     Some Android.bp modules have optional dependencies (typically specified with <uses-library> in
+     the module's AndroidManifest.xml) that must be specified when they are depended upon (like
+     androidx.window:window optionally requires androidx.window:window-extensions).
+     This may be specified multiple times to declare these dependencies.
   -sdk-version <version>
      Sets sdk_version: "<version>" for all modules.
   -default-min-sdk-version
@@ -625,10 +823,12 @@
 	}
 
 	var regen string
+	var pom2build bool
 
 	flag.Var(&excludes, "exclude", "Exclude module")
 	flag.Var(&extraStaticLibs, "extra-static-libs", "Extra static dependencies needed when depending on a module")
 	flag.Var(&extraLibs, "extra-libs", "Extra runtime dependencies needed when depending on a module")
+	flag.Var(&optionalUsesLibs, "optional-uses-libs", "Extra optional dependencies needed when depending on a module")
 	flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
 	flag.Var(&hostModuleNames, "host", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is a host module")
 	flag.Var(&hostAndDeviceModuleNames, "host-and-device", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is both a host and device module.")
@@ -638,6 +838,7 @@
 	flag.BoolVar(&staticDeps, "static-deps", false, "Statically include direct dependencies")
 	flag.BoolVar(&jetifier, "jetifier", false, "Sets jetifier: true on all modules")
 	flag.StringVar(&regen, "regen", "", "Rewrite specified file")
+	flag.BoolVar(&pom2build, "pom2build", false, "If true, will generate a Bazel BUILD file *instead* of a .bp file")
 	flag.Parse()
 
 	if regen != "" {
@@ -732,6 +933,16 @@
 		os.Exit(1)
 	}
 
+	if pom2build {
+		if err := InitRefreshMod(poms); err != nil {
+			fmt.Fprintf(os.Stderr, "Error in refreshmod: %s", err)
+			os.Exit(1)
+		}
+		BazelifyExtraDeps(extraStaticLibs, modules)
+		BazelifyExtraDeps(extraLibs, modules)
+		BazelifyExtraDeps(optionalUsesLibs, modules)
+	}
+
 	for _, pom := range poms {
 		if pom.IsAar() {
 			err := pom.ExtractMinSdkVersion()
@@ -741,19 +952,32 @@
 			}
 		}
 		pom.FixDeps(modules)
+		if pom2build {
+			pom.GetBazelDepNames(modules)
+		}
 	}
 
 	buf := &bytes.Buffer{}
+	commentString := "//"
+	if pom2build {
+		commentString = "#"
+	}
+	fmt.Fprintln(buf, commentString, "Automatically generated with:")
+	fmt.Fprintln(buf, commentString, "pom2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
 
-	fmt.Fprintln(buf, "// Automatically generated with:")
-	fmt.Fprintln(buf, "// pom2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
+	depsTemplate := bpDepsTemplate
+	template := bpTemplate
+	if pom2build {
+		depsTemplate = bazelDepsTemplate
+		template = bazelTemplate
+	}
 
 	for _, pom := range poms {
 		var err error
 		if staticDeps {
-			err = bpDepsTemplate.Execute(buf, pom)
+			err = depsTemplate.Execute(buf, pom)
 		} else {
-			err = bpTemplate.Execute(buf, pom)
+			err = template.Execute(buf, pom)
 		}
 		if err != nil {
 			fmt.Fprintln(os.Stderr, "Error writing", pom.PomFile, pom.BpName(), err)
@@ -761,11 +985,15 @@
 		}
 	}
 
-	out, err := bpfix.Reformat(buf.String())
-	if err != nil {
-		fmt.Fprintln(os.Stderr, "Error formatting output", err)
-		os.Exit(1)
+	if pom2build {
+		os.Stdout.WriteString(buf.String())
+	} else {
+		out, err := bpfix.Reformat(buf.String())
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Error formatting output", err)
+			os.Exit(1)
+		}
+		os.Stdout.WriteString(out)
 	}
 
-	os.Stdout.WriteString(out)
 }
diff --git a/cmd/run_with_timeout/run_with_timeout_test.go b/cmd/run_with_timeout/run_with_timeout_test.go
index ed6ec11..6729e61 100644
--- a/cmd/run_with_timeout/run_with_timeout_test.go
+++ b/cmd/run_with_timeout/run_with_timeout_test.go
@@ -50,7 +50,7 @@
 			args: args{
 				command: "echo",
 				args:    []string{"foo"},
-				timeout: 1 * time.Second,
+				timeout: 10 * time.Second,
 			},
 			wantStdout: "foo\n",
 		},
@@ -58,7 +58,7 @@
 			name: "timed out",
 			args: args{
 				command: "sh",
-				args:    []string{"-c", "sleep 1 && echo foo"},
+				args:    []string{"-c", "sleep 10 && echo foo"},
 				timeout: 1 * time.Millisecond,
 			},
 			wantStderr: ".*: process timed out after .*\n",
@@ -68,7 +68,7 @@
 			name: "on_timeout command",
 			args: args{
 				command:      "sh",
-				args:         []string{"-c", "sleep 1 && echo foo"},
+				args:         []string{"-c", "sleep 10 && echo foo"},
 				timeout:      1 * time.Millisecond,
 				onTimeoutCmd: "echo bar",
 			},
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index c7f3f6a..4fa7486 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -164,7 +164,7 @@
 		if useSubDir {
 			localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
 		}
-		depFile, err := runCommand(command, localTempDir)
+		depFile, err := runCommand(command, localTempDir, i)
 		if err != nil {
 			// Running the command failed, keep the temporary output directory around in
 			// case a user wants to inspect it for debugging purposes.  Soong will delete
@@ -194,6 +194,28 @@
 	return nil
 }
 
+// createCommandScript will create and return an exec.Cmd that runs rawCommand.
+//
+// rawCommand is executed via a script in the sandbox.
+// tempDir is the temporary where the script is created.
+// toDirInSandBox is the path containing the script in the sbox environment.
+// toDirInSandBox is the path containing the script in the sbox environment.
+// seed is a unique integer used to distinguish different scripts that might be at location.
+//
+// returns an exec.Cmd that can be ran from within sbox context if no error, or nil if error.
+// caller must ensure script is cleaned up if function succeeds.
+//
+func createCommandScript(rawCommand string, tempDir, toDirInSandbox string, seed int) (*exec.Cmd, error) {
+	scriptName := fmt.Sprintf("sbox_command.%d.bash", seed)
+	scriptPathAndName := joinPath(tempDir, scriptName)
+	err := os.WriteFile(scriptPathAndName, []byte(rawCommand), 0644)
+	if err != nil {
+		return nil, fmt.Errorf("failed to write command %s... to %s",
+			rawCommand[0:40], scriptPathAndName)
+	}
+	return exec.Command("bash", joinPath(toDirInSandbox, filepath.Base(scriptName))), nil
+}
+
 // readManifest reads an sbox manifest from a textproto file.
 func readManifest(file string) (*sbox_proto.Manifest, error) {
 	manifestData, err := ioutil.ReadFile(file)
@@ -213,7 +235,7 @@
 
 // runCommand runs a single command from a manifest.  If the command references the
 // __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
-func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
+func runCommand(command *sbox_proto.Command, tempDir string, commandIndex int) (depFile string, err error) {
 	rawCommand := command.GetCommand()
 	if rawCommand == "" {
 		return "", fmt.Errorf("command is required")
@@ -255,7 +277,11 @@
 		return "", err
 	}
 
-	cmd := exec.Command("bash", "-c", rawCommand)
+	cmd, err := createCommandScript(rawCommand, tempDir, pathToTempDirInSbox, commandIndex)
+	if err != nil {
+		return "", err
+	}
+
 	buf := &bytes.Buffer{}
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = buf
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 703a875..e85163e 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -22,6 +22,7 @@
         "blueprint",
         "blueprint-bootstrap",
         "golang-protobuf-proto",
+        "golang-protobuf-android",
         "soong",
         "soong-android",
         "soong-bp2build",
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 2cad785..f07eafa 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -23,22 +23,25 @@
 	"strings"
 	"time"
 
+	"android/soong/android"
 	"android/soong/bp2build"
 	"android/soong/shared"
 
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/pathtools"
-
-	"android/soong/android"
+	androidProtobuf "google.golang.org/protobuf/android"
 )
 
 var (
 	topDir           string
+	outDir           string
 	soongOutDir      string
 	availableEnvFile string
 	usedEnvFile      string
 
+	runGoTests bool
+
 	globFile    string
 	globListDir string
 	delveListen string
@@ -60,7 +63,7 @@
 	flag.StringVar(&usedEnvFile, "used_env", "", "File containing used environment variables")
 	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
 	flag.StringVar(&globListDir, "globListDir", "", "the directory containing the glob list files")
-	flag.StringVar(&cmdlineArgs.OutDir, "out", "", "the ninja builddir directory")
+	flag.StringVar(&outDir, "out", "", "the ninja builddir directory")
 	flag.StringVar(&cmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
 
 	// Debug flags
@@ -81,8 +84,13 @@
 
 	// Flags that probably shouldn't be flags of soong_build but we haven't found
 	// the time to remove them yet
-	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
-	flag.BoolVar(&cmdlineArgs.UseValidations, "use-validations", false, "use validations to depend on go tests")
+	flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
+
+	// Disable deterministic randomization in the protobuf package, so incremental
+	// builds with unrelated Soong changes don't trigger large rebuilds (since we
+	// write out text protos in command lines, and command line changes trigger
+	// rebuilds).
+	androidProtobuf.DisableRand()
 }
 
 func newNameResolver(config android.Config) *android.NameResolver {
@@ -101,19 +109,16 @@
 	return android.NewNameResolver(exportFilter)
 }
 
-func newContext(configuration android.Config, prepareBuildActions bool) *android.Context {
+func newContext(configuration android.Config) *android.Context {
 	ctx := android.NewContext(configuration)
 	ctx.Register()
-	if !prepareBuildActions {
-		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
-	}
 	ctx.SetNameInterface(newNameResolver(configuration))
 	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
 	return ctx
 }
 
-func newConfig(cmdlineArgs bootstrap.Args, outDir string, availableEnv map[string]string) android.Config {
-	configuration, err := android.NewConfig(cmdlineArgs, outDir, availableEnv)
+func newConfig(availableEnv map[string]string) android.Config {
+	configuration, err := android.NewConfig(cmdlineArgs.ModuleListFile, runGoTests, outDir, soongOutDir, availableEnv)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -127,11 +132,7 @@
 // TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
 // the incorrect results from the first pass, and file I/O is expensive.
 func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
-	var firstArgs, secondArgs bootstrap.Args
-
-	firstArgs = cmdlineArgs
-	configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
-	bootstrap.RunBlueprint(firstArgs, firstCtx.Context, configuration)
+	bootstrap.RunBlueprint(cmdlineArgs, bootstrap.StopBeforeWriteNinja, firstCtx.Context, configuration)
 
 	// Invoke bazel commands and save results for second pass.
 	if err := configuration.BazelContext.InvokeBazel(); err != nil {
@@ -139,20 +140,19 @@
 		os.Exit(1)
 	}
 	// Second pass: Full analysis, using the bazel command results. Output ninja file.
-	secondArgs = cmdlineArgs
-	secondConfig, err := android.ConfigForAdditionalRun(secondArgs, configuration)
+	secondConfig, err := android.ConfigForAdditionalRun(configuration)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
-	secondCtx := newContext(secondConfig, true)
-	ninjaDeps := bootstrap.RunBlueprint(secondArgs, secondCtx.Context, secondConfig)
+	secondCtx := newContext(secondConfig)
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, secondCtx.Context, secondConfig)
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
 	globListFiles := writeBuildGlobsNinjaFile(secondCtx.SrcDir(), configuration.SoongOutDir(), secondCtx.Globs, configuration)
 	ninjaDeps = append(ninjaDeps, globListFiles...)
 
-	writeDepFile(secondArgs.OutFile, ninjaDeps)
+	writeDepFile(cmdlineArgs.OutFile, ninjaDeps)
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
@@ -167,16 +167,6 @@
 	touch(shared.JoinPath(topDir, queryviewMarker))
 }
 
-func runSoongDocs(configuration android.Config) {
-	ctx := newContext(configuration, false)
-	soongDocsArgs := cmdlineArgs
-	bootstrap.RunBlueprint(soongDocsArgs, ctx.Context, configuration)
-	if err := writeDocs(ctx, configuration, docFile); err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
-	}
-}
-
 func writeMetrics(configuration android.Config) {
 	metricsFile := filepath.Join(configuration.SoongOutDir(), "soong_build_metrics.pb")
 	err := android.WriteMetrics(configuration, metricsFile)
@@ -221,24 +211,37 @@
 // or the actual Soong build for the build.ninja file. Returns the top level
 // output file of the specific activity.
 func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
-	bazelConversionRequested := bp2buildMarker != ""
 	mixedModeBuild := configuration.BazelContext.BazelEnabled()
+	generateBazelWorkspace := bp2buildMarker != ""
 	generateQueryView := bazelQueryViewDir != ""
+	generateModuleGraphFile := moduleGraphFile != ""
+	generateDocFile := docFile != ""
 
-	blueprintArgs := cmdlineArgs
-	prepareBuildActions := !generateQueryView && moduleGraphFile == ""
-	if bazelConversionRequested {
+	if generateBazelWorkspace {
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
 		runBp2Build(configuration, extraNinjaDeps)
 		return bp2buildMarker
 	}
 
-	ctx := newContext(configuration, prepareBuildActions)
+	blueprintArgs := cmdlineArgs
+
+	var stopBefore bootstrap.StopBefore
+	if generateModuleGraphFile {
+		stopBefore = bootstrap.StopBeforeWriteNinja
+	} else if generateQueryView {
+		stopBefore = bootstrap.StopBeforePrepareBuildActions
+	} else if generateDocFile {
+		stopBefore = bootstrap.StopBeforePrepareBuildActions
+	} else {
+		stopBefore = bootstrap.DoEverything
+	}
+
+	ctx := newContext(configuration)
 	if mixedModeBuild {
 		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
 	} else {
-		ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, ctx.Context, configuration)
+		ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, stopBefore, ctx.Context, configuration)
 		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
 		globListFiles := writeBuildGlobsNinjaFile(ctx.SrcDir(), configuration.SoongOutDir(), ctx.Globs, configuration)
@@ -250,10 +253,20 @@
 			runQueryView(bazelQueryViewDir, queryviewMarkerFile, configuration, ctx)
 			writeDepFile(queryviewMarkerFile, ninjaDeps)
 			return queryviewMarkerFile
-		} else if moduleGraphFile != "" {
+		} else if generateModuleGraphFile {
 			writeJsonModuleGraph(ctx, moduleGraphFile)
 			writeDepFile(moduleGraphFile, ninjaDeps)
 			return moduleGraphFile
+		} else if generateDocFile {
+			// TODO: we could make writeDocs() return the list of documentation files
+			// written and add them to the .d file. Then soong_docs would be re-run
+			// whenever one is deleted.
+			if err := writeDocs(ctx, shared.JoinPath(topDir, docFile)); err != nil {
+				fmt.Fprintf(os.Stderr, "error building Soong documentation: %s\n", err)
+				os.Exit(1)
+			}
+			writeDepFile(docFile, ninjaDeps)
+			return docFile
 		} else {
 			// The actual output (build.ninja) was written in the RunBlueprint() call
 			// above
@@ -301,7 +314,7 @@
 
 	availableEnv := parseAvailableEnv()
 
-	configuration := newConfig(cmdlineArgs, soongOutDir, availableEnv)
+	configuration := newConfig(availableEnv)
 	extraNinjaDeps := []string{
 		configuration.ProductVariablesFileName,
 		usedEnvFile,
@@ -317,16 +330,6 @@
 		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.SoongOutDir(), "always_rerun_for_delve"))
 	}
 
-	if docFile != "" {
-		// We don't write an used variables file when generating documentation
-		// because that is done from within the actual builds as a Ninja action and
-		// thus it would overwrite the actual used variables file so this is
-		// special-cased.
-		// TODO: Fix this by not passing --used_env to the soong_docs invocation
-		runSoongDocs(configuration)
-		return
-	}
-
 	finalOutputFile := doChosenActivity(configuration, extraNinjaDeps)
 	writeUsedEnvironmentFile(configuration, finalOutputFile)
 }
@@ -383,7 +386,7 @@
 // - won't be overwritten by corresponding bp2build generated files
 //
 // And return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
-func getPathsToIgnoredBuildFiles(topDir string, generatedRoot string, srcDirBazelFiles []string) []string {
+func getPathsToIgnoredBuildFiles(topDir string, generatedRoot string, srcDirBazelFiles []string, verbose bool) []string {
 	paths := make([]string, 0)
 
 	for _, srcDirBazelFileRelativePath := range srcDirBazelFiles {
@@ -413,7 +416,9 @@
 			// BUILD file clash resolution happens later in the symlink forest creation
 			continue
 		}
-		fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", srcDirBazelFileRelativePath)
+		if verbose {
+			fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", srcDirBazelFileRelativePath)
+		}
 		paths = append(paths, srcDirBazelFileRelativePath)
 	}
 
@@ -461,6 +466,11 @@
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
 
+	// Soong internals like LoadHooks behave differently when running as
+	// bp2build. This is the bit to differentiate between Soong-as-Soong and
+	// Soong-as-bp2build.
+	bp2buildCtx.SetRunningAsBp2build()
+
 	// Propagate "allow misssing dependencies" bit. This is normally set in
 	// newContext(), but we create bp2buildCtx without calling that method.
 	bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
@@ -479,14 +489,11 @@
 
 	extraNinjaDeps = append(extraNinjaDeps, modulePaths...)
 
-	// No need to generate Ninja build rules/statements from Modules and Singletons.
-	configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
-
 	// Run the loading and analysis pipeline to prepare the graph of regular
 	// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 	// from the regular Modules.
 	blueprintArgs := cmdlineArgs
-	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bp2buildCtx.Context, configuration)
+	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.StopBeforePrepareBuildActions, bp2buildCtx.Context, configuration)
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
 	globListFiles := writeBuildGlobsNinjaFile(bp2buildCtx.SrcDir(), configuration.SoongOutDir(), bp2buildCtx.Globs, configuration)
@@ -508,8 +515,8 @@
 		"bazel-" + filepath.Base(topDir),
 	}
 
-	if cmdlineArgs.OutDir[0] != '/' {
-		excludes = append(excludes, cmdlineArgs.OutDir)
+	if outDir[0] != '/' {
+		excludes = append(excludes, outDir)
 	}
 
 	existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
@@ -518,7 +525,7 @@
 		os.Exit(1)
 	}
 
-	pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(topDir, generatedRoot, existingBazelRelatedFiles)
+	pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(topDir, generatedRoot, existingBazelRelatedFiles, configuration.IsEnvTrue("BP2BUILD_VERBOSE"))
 	excludes = append(excludes, pathsToIgnoredBuildFiles...)
 
 	excludes = append(excludes, getTemporaryExcludes()...)
@@ -530,6 +537,7 @@
 	// for queryview, since that's a total repo-wide conversion and there's a
 	// 1:1 mapping for each module.
 	metrics.Print()
+	writeBp2BuildMetrics(&metrics, configuration)
 
 	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
 	ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
@@ -539,3 +547,13 @@
 	// Create an empty bp2build marker file.
 	touch(shared.JoinPath(topDir, bp2buildMarker))
 }
+
+// Write Bp2Build metrics into $LOG_DIR
+func writeBp2BuildMetrics(metrics *bp2build.CodegenMetrics, configuration android.Config) {
+	metricsDir := configuration.Getenv("LOG_DIR")
+	if len(metricsDir) < 1 {
+		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating bp2build metrics: LOG_DIR\n")
+		os.Exit(1)
+	}
+	metrics.Write(metricsDir)
+}
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 98e27c6..d63ded5 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -27,12 +27,12 @@
 	os.RemoveAll(bazelQueryViewDir)
 	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
 
-	// Ignore metrics reporting and compat layers for queryview, since queryview
-	// is already a full-repo conversion and can use data from bazel query
-	// directly.
-	buildToTargets, _, _ := bp2build.GenerateBazelTargets(ctx, true)
+	res, err := bp2build.GenerateBazelTargets(ctx, true)
+	if err != nil {
+		panic(err)
+	}
 
-	filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets, bp2build.QueryView)
+	filesToWrite := bp2build.CreateBazelFiles(ruleShims, res.BuildDirToTargets(), bp2build.QueryView)
 	for _, f := range filesToWrite {
 		if err := writeReadOnlyFile(bazelQueryViewDir, f); err != nil {
 			return err
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index b7c260c..d2fbed4 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -15,13 +15,14 @@
 package main
 
 import (
-	"android/soong/android"
 	"bytes"
 	"html/template"
 	"io/ioutil"
 	"path/filepath"
 	"sort"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/bootstrap/bpdoc"
 )
@@ -95,13 +96,13 @@
 	return result
 }
 
-func getPackages(ctx *android.Context, config interface{}) ([]*bpdoc.Package, error) {
+func getPackages(ctx *android.Context) ([]*bpdoc.Package, error) {
 	moduleTypeFactories := android.ModuleTypeFactoriesForDocs()
-	return bootstrap.ModuleTypeDocs(ctx.Context, config, moduleTypeFactories)
+	return bootstrap.ModuleTypeDocs(ctx.Context, moduleTypeFactories)
 }
 
-func writeDocs(ctx *android.Context, config interface{}, filename string) error {
-	packages, err := getPackages(ctx, config)
+func writeDocs(ctx *android.Context, filename string) error {
+	packages, err := getPackages(ctx)
 	if err != nil {
 		return err
 	}
@@ -371,6 +372,7 @@
     {{if .Properties -}}
       <div class="accordion"  id="{{getModule}}.{{.Name}}">
         <span class="fixed">&#x2295</span><b>{{.Name}}</b>
+        <i>{{.Type}}</i>
         {{- range .OtherNames -}}, {{.}}{{- end -}}
       </div>
       <div class="collapsible">
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index d709787..9ee373e 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -164,7 +164,8 @@
 
 	// Create a terminal output that mimics Ninja's.
 	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput,
-		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
+		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"),
+		build.OsEnvironment().IsEnvTrue("SOONG_UI_ANSI_OUTPUT"))
 
 	// Attach a new logger instance to the terminal output.
 	log := logger.New(output)
diff --git a/compliance/build_license_metadata/Android.bp b/compliance/build_license_metadata/Android.bp
new file mode 100644
index 0000000..4826526
--- /dev/null
+++ b/compliance/build_license_metadata/Android.bp
@@ -0,0 +1,30 @@
+// Copyright 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "build_license_metadata",
+    srcs: [
+        "build_license_metadata.go",
+    ],
+    deps: [
+        "license_metadata_proto",
+        "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
+        "soong-response",
+    ],
+}
diff --git a/compliance/build_license_metadata/build_license_metadata.go b/compliance/build_license_metadata/build_license_metadata.go
new file mode 100644
index 0000000..53d2407
--- /dev/null
+++ b/compliance/build_license_metadata/build_license_metadata.go
@@ -0,0 +1,196 @@
+// Copyright 2021 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 main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+
+	"android/soong/compliance/license_metadata_proto"
+	"android/soong/response"
+)
+
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	packageName := flags.String("p", "", "license package name")
+	moduleType := newMultiString(flags, "mt", "module type")
+	kinds := newMultiString(flags, "k", "license kinds")
+	moduleClass := newMultiString(flags, "mc", "module class")
+	conditions := newMultiString(flags, "c", "license conditions")
+	notices := newMultiString(flags, "n", "license notice file")
+	deps := newMultiString(flags, "d", "license metadata file dependency")
+	sources := newMultiString(flags, "s", "source (input) dependency")
+	built := newMultiString(flags, "t", "built targets")
+	installed := newMultiString(flags, "i", "installed targets")
+	roots := newMultiString(flags, "r", "root directory of project")
+	installedMap := newMultiString(flags, "m", "map dependent targets to their installed names")
+	isContainer := flags.Bool("is_container", false, "preserved dependent target name when given")
+	outFile := flags.String("o", "", "output file")
+
+	flags.Parse(expandedArgs)
+
+	metadata := license_metadata_proto.LicenseMetadata{}
+	metadata.PackageName = proto.String(*packageName)
+	metadata.ModuleTypes = *moduleType
+	metadata.ModuleClasses = *moduleClass
+	metadata.IsContainer = proto.Bool(*isContainer)
+	metadata.Projects = findGitRoots(*roots)
+	metadata.LicenseKinds = *kinds
+	metadata.LicenseConditions = *conditions
+	metadata.LicenseTexts = *notices
+	metadata.Built = *built
+	metadata.Installed = *installed
+	metadata.InstallMap = convertInstalledMap(*installedMap)
+	metadata.Sources = *sources
+	metadata.Deps = convertDependencies(*deps)
+
+	err := writeMetadata(*outFile, &metadata)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
+		os.Exit(2)
+	}
+}
+
+func findGitRoots(dirs []string) []string {
+	ret := make([]string, len(dirs))
+	for i, dir := range dirs {
+		ret[i] = findGitRoot(dir)
+	}
+	return ret
+}
+
+// findGitRoot finds the directory at or above dir that contains a ".git" directory.  This isn't
+// guaranteed to exist, for example during remote execution, when sandboxed, when building from
+// infrastructure that doesn't use git, or when the .git directory has been removed to save space,
+// but it should be good enough for local builds.  If no .git directory is found the original value
+// is returned.
+func findGitRoot(dir string) string {
+	orig := dir
+	for dir != "" && dir != "." && dir != "/" {
+		_, err := os.Stat(filepath.Join(dir, ".git"))
+		if err == nil {
+			// Found dir/.git, return dir.
+			return dir
+		} else if !os.IsNotExist(err) {
+			// Error finding .git, return original input.
+			return orig
+		}
+		dir, _ = filepath.Split(dir)
+		dir = strings.TrimSuffix(dir, "/")
+	}
+	return orig
+}
+
+// convertInstalledMap converts a list of colon-separated from:to pairs into InstallMap proto
+// messages.
+func convertInstalledMap(installMaps []string) []*license_metadata_proto.InstallMap {
+	var ret []*license_metadata_proto.InstallMap
+
+	for _, installMap := range installMaps {
+		components := strings.Split(installMap, ":")
+		if len(components) != 2 {
+			panic(fmt.Errorf("install map entry %q contains %d colons, expected 1", installMap, len(components)-1))
+		}
+		ret = append(ret, &license_metadata_proto.InstallMap{
+			FromPath:      proto.String(components[0]),
+			ContainerPath: proto.String(components[1]),
+		})
+	}
+
+	return ret
+}
+
+// convertDependencies converts a colon-separated tuple of dependency:annotation:annotation...
+// into AnnotatedDependency proto messages.
+func convertDependencies(deps []string) []*license_metadata_proto.AnnotatedDependency {
+	var ret []*license_metadata_proto.AnnotatedDependency
+
+	for _, d := range deps {
+		components := strings.Split(d, ":")
+		dep := components[0]
+		components = components[1:]
+		ad := &license_metadata_proto.AnnotatedDependency{
+			File:        proto.String(dep),
+			Annotations: make([]string, 0, len(components)),
+		}
+		for _, ann := range components {
+			if len(ann) == 0 {
+				continue
+			}
+			ad.Annotations = append(ad.Annotations, ann)
+		}
+		ret = append(ret, ad)
+	}
+
+	return ret
+}
+
+func writeMetadata(file string, metadata *license_metadata_proto.LicenseMetadata) error {
+	buf, err := prototext.MarshalOptions{Multiline: true}.Marshal(metadata)
+	if err != nil {
+		return fmt.Errorf("error marshalling textproto: %w", err)
+	}
+
+	if file != "" {
+		err = ioutil.WriteFile(file, buf, 0666)
+		if err != nil {
+			return fmt.Errorf("error writing textproto %q: %w", file, err)
+		}
+	} else {
+		_, _ = os.Stdout.Write(buf)
+	}
+
+	return nil
+}
diff --git a/compliance/license_metadata_proto/Android.bp b/compliance/license_metadata_proto/Android.bp
new file mode 100644
index 0000000..3c041e4
--- /dev/null
+++ b/compliance/license_metadata_proto/Android.bp
@@ -0,0 +1,27 @@
+// Copyright 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "license_metadata_proto",
+    pkgPath: "android/soong/compliance/license_metadata_proto",
+    srcs: ["license_metadata.pb.go"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+}
diff --git a/compliance/license_metadata_proto/license_metadata.pb.go b/compliance/license_metadata_proto/license_metadata.pb.go
new file mode 100644
index 0000000..44dbc78
--- /dev/null
+++ b/compliance/license_metadata_proto/license_metadata.pb.go
@@ -0,0 +1,451 @@
+// Copyright 2021 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.19.0
+// source: license_metadata.proto
+
+package license_metadata_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type LicenseMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// package_name identifies the source package. License texts are named relative to the package name.
+	PackageName   *string  `protobuf:"bytes,1,opt,name=package_name,json=packageName" json:"package_name,omitempty"`
+	ModuleTypes   []string `protobuf:"bytes,2,rep,name=module_types,json=moduleTypes" json:"module_types,omitempty"`
+	ModuleClasses []string `protobuf:"bytes,3,rep,name=module_classes,json=moduleClasses" json:"module_classes,omitempty"`
+	// projects identifies the git project(s) containing the associated source code.
+	Projects []string `protobuf:"bytes,4,rep,name=projects" json:"projects,omitempty"`
+	// license_kinds lists the kinds of licenses. e.g. SPDX-license-identifier-Apache-2.0 or legacy_notice.
+	LicenseKinds []string `protobuf:"bytes,5,rep,name=license_kinds,json=licenseKinds" json:"license_kinds,omitempty"`
+	// license_conditions lists the conditions that apply to the license kinds. e.g. notice or restricted.
+	LicenseConditions []string `protobuf:"bytes,6,rep,name=license_conditions,json=licenseConditions" json:"license_conditions,omitempty"`
+	// license_texts lists the filenames of the associated license text(s).
+	LicenseTexts []string `protobuf:"bytes,7,rep,name=license_texts,json=licenseTexts" json:"license_texts,omitempty"`
+	// is_container is true for target types that merely aggregate. e.g. .img or .zip files.
+	IsContainer *bool `protobuf:"varint,8,opt,name=is_container,json=isContainer" json:"is_container,omitempty"`
+	// built lists the built targets.
+	Built []string `protobuf:"bytes,9,rep,name=built" json:"built,omitempty"`
+	// installed lists the installed targets.
+	Installed []string `protobuf:"bytes,10,rep,name=installed" json:"installed,omitempty"`
+	// installMap identifies the substitutions to make to path names when moving into installed location.
+	InstallMap []*InstallMap `protobuf:"bytes,11,rep,name=install_map,json=installMap" json:"install_map,omitempty"`
+	// sources lists the targets depended on.
+	Sources []string `protobuf:"bytes,12,rep,name=sources" json:"sources,omitempty"`
+	// deps lists the license metadata files depended on.
+	Deps []*AnnotatedDependency `protobuf:"bytes,13,rep,name=deps" json:"deps,omitempty"`
+}
+
+func (x *LicenseMetadata) Reset() {
+	*x = LicenseMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LicenseMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LicenseMetadata) ProtoMessage() {}
+
+func (x *LicenseMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use LicenseMetadata.ProtoReflect.Descriptor instead.
+func (*LicenseMetadata) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *LicenseMetadata) GetPackageName() string {
+	if x != nil && x.PackageName != nil {
+		return *x.PackageName
+	}
+	return ""
+}
+
+func (x *LicenseMetadata) GetModuleTypes() []string {
+	if x != nil {
+		return x.ModuleTypes
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetModuleClasses() []string {
+	if x != nil {
+		return x.ModuleClasses
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetProjects() []string {
+	if x != nil {
+		return x.Projects
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseKinds() []string {
+	if x != nil {
+		return x.LicenseKinds
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseConditions() []string {
+	if x != nil {
+		return x.LicenseConditions
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseTexts() []string {
+	if x != nil {
+		return x.LicenseTexts
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetIsContainer() bool {
+	if x != nil && x.IsContainer != nil {
+		return *x.IsContainer
+	}
+	return false
+}
+
+func (x *LicenseMetadata) GetBuilt() []string {
+	if x != nil {
+		return x.Built
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetInstalled() []string {
+	if x != nil {
+		return x.Installed
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetInstallMap() []*InstallMap {
+	if x != nil {
+		return x.InstallMap
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetSources() []string {
+	if x != nil {
+		return x.Sources
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetDeps() []*AnnotatedDependency {
+	if x != nil {
+		return x.Deps
+	}
+	return nil
+}
+
+// InstallMap messages describe the mapping from an input filesystem file to the path to the file
+// in a container.
+type InstallMap struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The input path on the filesystem.
+	FromPath *string `protobuf:"bytes,1,opt,name=from_path,json=fromPath" json:"from_path,omitempty"`
+	// The path to the file inside the container or installed location.
+	ContainerPath *string `protobuf:"bytes,2,opt,name=container_path,json=containerPath" json:"container_path,omitempty"`
+}
+
+func (x *InstallMap) Reset() {
+	*x = InstallMap{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *InstallMap) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*InstallMap) ProtoMessage() {}
+
+func (x *InstallMap) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use InstallMap.ProtoReflect.Descriptor instead.
+func (*InstallMap) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *InstallMap) GetFromPath() string {
+	if x != nil && x.FromPath != nil {
+		return *x.FromPath
+	}
+	return ""
+}
+
+func (x *InstallMap) GetContainerPath() string {
+	if x != nil && x.ContainerPath != nil {
+		return *x.ContainerPath
+	}
+	return ""
+}
+
+// AnnotateDepencency messages describe edges in the build graph.
+type AnnotatedDependency struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The path to the dependency license metadata file.
+	File *string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"`
+	// The annotations attached to the dependency.
+	Annotations []string `protobuf:"bytes,2,rep,name=annotations" json:"annotations,omitempty"`
+}
+
+func (x *AnnotatedDependency) Reset() {
+	*x = AnnotatedDependency{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AnnotatedDependency) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AnnotatedDependency) ProtoMessage() {}
+
+func (x *AnnotatedDependency) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AnnotatedDependency.ProtoReflect.Descriptor instead.
+func (*AnnotatedDependency) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *AnnotatedDependency) GetFile() string {
+	if x != nil && x.File != nil {
+		return *x.File
+	}
+	return ""
+}
+
+func (x *AnnotatedDependency) GetAnnotations() []string {
+	if x != nil {
+		return x.Annotations
+	}
+	return nil
+}
+
+var File_license_metadata_proto protoreflect.FileDescriptor
+
+var file_license_metadata_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x22, 0x8a, 0x04, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b,
+	0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65,
+	0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x0a,
+	0x0d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x18, 0x05,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x69, 0x6e,
+	0x64, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f,
+	0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11,
+	0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x78,
+	0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73,
+	0x65, 0x54, 0x65, 0x78, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e,
+	0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73,
+	0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69,
+	0x6c, 0x74, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a,
+	0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0b, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e,
+	0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x49, 0x6e, 0x73, 0x74,
+	0x61, 0x6c, 0x6c, 0x4d, 0x61, 0x70, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x4d,
+	0x61, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x0c, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x04,
+	0x64, 0x65, 0x70, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x44, 0x65, 0x70,
+	0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73, 0x22, 0x50, 0x0a,
+	0x0a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x4d, 0x61, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x66,
+	0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+	0x66, 0x72, 0x6f, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
+	0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x22,
+	0x4b, 0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x44, 0x65, 0x70, 0x65,
+	0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6e,
+	0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x31, 0x5a, 0x2f,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6f,
+	0x6d, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65,
+	0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	file_license_metadata_proto_rawDescOnce sync.Once
+	file_license_metadata_proto_rawDescData = file_license_metadata_proto_rawDesc
+)
+
+func file_license_metadata_proto_rawDescGZIP() []byte {
+	file_license_metadata_proto_rawDescOnce.Do(func() {
+		file_license_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_license_metadata_proto_rawDescData)
+	})
+	return file_license_metadata_proto_rawDescData
+}
+
+var file_license_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_license_metadata_proto_goTypes = []interface{}{
+	(*LicenseMetadata)(nil),     // 0: build_license_metadata.LicenseMetadata
+	(*InstallMap)(nil),          // 1: build_license_metadata.InstallMap
+	(*AnnotatedDependency)(nil), // 2: build_license_metadata.AnnotatedDependency
+}
+var file_license_metadata_proto_depIdxs = []int32{
+	1, // 0: build_license_metadata.LicenseMetadata.install_map:type_name -> build_license_metadata.InstallMap
+	2, // 1: build_license_metadata.LicenseMetadata.deps:type_name -> build_license_metadata.AnnotatedDependency
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_license_metadata_proto_init() }
+func file_license_metadata_proto_init() {
+	if File_license_metadata_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_license_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LicenseMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_license_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*InstallMap); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_license_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AnnotatedDependency); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_license_metadata_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_license_metadata_proto_goTypes,
+		DependencyIndexes: file_license_metadata_proto_depIdxs,
+		MessageInfos:      file_license_metadata_proto_msgTypes,
+	}.Build()
+	File_license_metadata_proto = out.File
+	file_license_metadata_proto_rawDesc = nil
+	file_license_metadata_proto_goTypes = nil
+	file_license_metadata_proto_depIdxs = nil
+}
diff --git a/compliance/license_metadata_proto/license_metadata.proto b/compliance/license_metadata_proto/license_metadata.proto
new file mode 100644
index 0000000..1b4f34f
--- /dev/null
+++ b/compliance/license_metadata_proto/license_metadata.proto
@@ -0,0 +1,76 @@
+// Copyright 2021 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.
+
+syntax = "proto2";
+
+package build_license_metadata;
+option go_package = "android/soong/compliance/license_metadata_proto";
+
+message LicenseMetadata {
+  // package_name identifies the source package. License texts are named relative to the package name.
+  optional string package_name = 1;
+
+  repeated string module_types = 2;
+
+  repeated string module_classes = 3;
+
+  // projects identifies the git project(s) containing the associated source code.
+  repeated string projects = 4;
+
+  // license_kinds lists the kinds of licenses. e.g. SPDX-license-identifier-Apache-2.0 or legacy_notice.
+  repeated string license_kinds = 5;
+
+  // license_conditions lists the conditions that apply to the license kinds. e.g. notice or restricted.
+  repeated string license_conditions = 6;
+
+  // license_texts lists the filenames of the associated license text(s).
+  repeated string license_texts = 7;
+
+  // is_container is true for target types that merely aggregate. e.g. .img or .zip files.
+  optional bool is_container = 8;
+
+  // built lists the built targets.
+  repeated string built = 9;
+
+  // installed lists the installed targets.
+  repeated string installed = 10;
+
+  // installMap identifies the substitutions to make to path names when moving into installed location.
+  repeated InstallMap install_map = 11;
+
+  // sources lists the targets depended on.
+  repeated string sources = 12;
+
+  // deps lists the license metadata files depended on.
+  repeated AnnotatedDependency deps = 13;
+}
+
+// InstallMap messages describe the mapping from an input filesystem file to the path to the file
+// in a container.
+message InstallMap {
+  // The input path on the filesystem.
+  optional string from_path = 1;
+
+  // The path to the file inside the container or installed location.
+  optional string container_path = 2;
+}
+
+// AnnotateDepencency messages describe edges in the build graph.
+message AnnotatedDependency {
+  // The path to the dependency license metadata file.
+  optional string file = 1;
+
+  // The annotations attached to the dependency.
+  repeated string annotations = 2;
+}
diff --git a/cuj/cuj.go b/cuj/cuj.go
index 413f423..869e0f7 100644
--- a/cuj/cuj.go
+++ b/cuj/cuj.go
@@ -48,7 +48,7 @@
 
 // Run runs a single build command.  It emulates the "m" command line by calling into Soong UI directly.
 func (t *Test) Run(logsDir string) {
-	output := terminal.NewStatusOutput(os.Stdout, "", false, false)
+	output := terminal.NewStatusOutput(os.Stdout, "", false, false, false)
 
 	log := logger.New(output)
 	defer log.Cleanup()
@@ -138,6 +138,8 @@
 
 	cujDir := filepath.Join(outDir, "cuj_tests")
 
+	wd, _ := os.Getwd()
+	os.Setenv("TOP", wd)
 	// Use a subdirectory for the out directory for the tests to keep them isolated.
 	os.Setenv("OUT_DIR", filepath.Join(cujDir, "out"))
 
diff --git a/cuj/run_cuj_tests.sh b/cuj/run_cuj_tests.sh
index b4f9f88..a746bd5 100755
--- a/cuj/run_cuj_tests.sh
+++ b/cuj/run_cuj_tests.sh
@@ -18,11 +18,10 @@
 cd "$ANDROID_TOP"
 
 export OUT_DIR="${OUT_DIR:-out}"
-readonly SOONG_OUT="${OUT_DIR}/soong"
 
-build/soong/soong_ui.bash --make-mode "${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests"
+build/soong/soong_ui.bash --make-mode "${OUT_DIR}/host/${OS}-x86/bin/cuj_tests"
 
-"${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests" || true
+"${OUT_DIR}/host/${OS}-x86/bin/cuj_tests" || true
 
 if [ -n "${DIST_DIR}" ]; then
   cp -r "${OUT_DIR}/cuj_tests/logs" "${DIST_DIR}"
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 1bdd040..658e8e2 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -598,11 +598,18 @@
 func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) []*jsonClassLoaderContext {
 	jClcs := make([]*jsonClassLoaderContext, len(clcs))
 	for i, clc := range clcs {
+		var host string
+		if clc.Host == nil {
+			// Defer build failure to when this CLC is actually used.
+			host = fmt.Sprintf("implementation-jar-for-%s-is-not-available.jar", clc.Name)
+		} else {
+			host = clc.Host.String()
+		}
 		jClcs[i] = &jsonClassLoaderContext{
 			Name:        clc.Name,
 			Optional:    clc.Optional,
 			Implicit:    clc.Implicit,
-			Host:        clc.Host.String(),
+			Host:        host,
 			Device:      clc.Device,
 			Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts),
 		}
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index d81ac2c..4a3d390 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -115,16 +115,16 @@
 	// Test that class loader context structure is correct.
 	t.Run("string", func(t *testing.T) {
 		wantStr := " --host-context-for-sdk 29 " +
-			"PCL[out/" + AndroidHidlManager + ".jar]#" +
-			"PCL[out/" + AndroidHidlBase + ".jar]" +
+			"PCL[out/soong/" + AndroidHidlManager + ".jar]#" +
+			"PCL[out/soong/" + AndroidHidlBase + ".jar]" +
 			" --target-context-for-sdk 29 " +
 			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
 			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
 			" --host-context-for-sdk any " +
-			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]" +
-			"{PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]" +
-			"{PCL[out/a1.jar]#PCL[out/b1.jar]}}#" +
-			"PCL[out/f.jar]#PCL[out/a3.jar]#PCL[out/b3.jar]" +
+			"PCL[out/soong/a.jar]#PCL[out/soong/b.jar]#PCL[out/soong/c.jar]#PCL[out/soong/d.jar]" +
+			"{PCL[out/soong/a2.jar]#PCL[out/soong/b2.jar]#PCL[out/soong/c2.jar]" +
+			"{PCL[out/soong/a1.jar]#PCL[out/soong/b1.jar]}}#" +
+			"PCL[out/soong/f.jar]#PCL[out/soong/a3.jar]#PCL[out/soong/b3.jar]" +
 			" --target-context-for-sdk any " +
 			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
 			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
@@ -138,11 +138,11 @@
 	// Test that all expected build paths are gathered.
 	t.Run("paths", func(t *testing.T) {
 		wantPaths := []string{
-			"out/android.hidl.manager-V1.0-java.jar", "out/android.hidl.base-V1.0-java.jar",
-			"out/a.jar", "out/b.jar", "out/c.jar", "out/d.jar",
-			"out/a2.jar", "out/b2.jar", "out/c2.jar",
-			"out/a1.jar", "out/b1.jar",
-			"out/f.jar", "out/a3.jar", "out/b3.jar",
+			"out/soong/android.hidl.manager-V1.0-java.jar", "out/soong/android.hidl.base-V1.0-java.jar",
+			"out/soong/a.jar", "out/soong/b.jar", "out/soong/c.jar", "out/soong/d.jar",
+			"out/soong/a2.jar", "out/soong/b2.jar", "out/soong/c2.jar",
+			"out/soong/a1.jar", "out/soong/b1.jar",
+			"out/soong/f.jar", "out/soong/a3.jar", "out/soong/b3.jar",
 		}
 		if !reflect.DeepEqual(wantPaths, havePaths.Strings()) {
 			t.Errorf("\nwant paths: %s\nhave paths: %s", wantPaths, havePaths)
@@ -270,13 +270,13 @@
 
 	// Test that class loader context structure is correct.
 	t.Run("string", func(t *testing.T) {
-		wantStr := " --host-context-for-sdk 30 PCL[out/c.jar]" +
+		wantStr := " --host-context-for-sdk 30 PCL[out/soong/c.jar]" +
 			" --target-context-for-sdk 30 PCL[/system/c.jar]" +
-			" --host-context-for-sdk 29 PCL[out/b.jar]" +
+			" --host-context-for-sdk 29 PCL[out/soong/b.jar]" +
 			" --target-context-for-sdk 29 PCL[/system/b.jar]" +
-			" --host-context-for-sdk 28 PCL[out/a.jar]" +
+			" --host-context-for-sdk 28 PCL[out/soong/a.jar]" +
 			" --target-context-for-sdk 28 PCL[/system/a.jar]" +
-			" --host-context-for-sdk any PCL[out/d.jar]" +
+			" --host-context-for-sdk any PCL[out/soong/d.jar]" +
 			" --target-context-for-sdk any PCL[/system/d.jar]"
 		if wantStr != haveStr {
 			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index de3666a..1f99a96 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -49,10 +49,12 @@
 
 	ArtApexJars android.ConfiguredJarList // modules for jars that are in the ART APEX
 
-	SystemServerJars     android.ConfiguredJarList // jars that form the system server
-	SystemServerApps     []string                  // apps that are loaded into system server
-	ApexSystemServerJars android.ConfiguredJarList // jars within apex that are loaded into system server
-	SpeedApps            []string                  // apps that should be speed optimized
+	SystemServerJars               android.ConfiguredJarList // system_server classpath jars on the platform
+	SystemServerApps               []string                  // apps that are loaded into system server
+	ApexSystemServerJars           android.ConfiguredJarList // system_server classpath jars delivered via apex
+	StandaloneSystemServerJars     android.ConfiguredJarList // jars on the platform that system_server loads dynamically using separate classloaders
+	ApexStandaloneSystemServerJars android.ConfiguredJarList // jars delivered via apex that system_server loads dynamically using separate classloaders
+	SpeedApps                      []string                  // apps that should be speed optimized
 
 	BrokenSuboptimalOrderOfSystemServerJars bool // if true, sub-optimal order does not cause a build error
 
@@ -98,6 +100,48 @@
 	RelaxUsesLibraryCheck bool
 }
 
+var allPlatformSystemServerJarsKey = android.NewOnceKey("allPlatformSystemServerJars")
+
+// Returns all jars on the platform that system_server loads, including those on classpath and those
+// loaded dynamically.
+func (g *GlobalConfig) AllPlatformSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allPlatformSystemServerJarsKey, func() interface{} {
+		res := g.SystemServerJars.AppendList(&g.StandaloneSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allApexSystemServerJarsKey = android.NewOnceKey("allApexSystemServerJars")
+
+// Returns all jars delivered via apex that system_server loads, including those on classpath and
+// those loaded dynamically.
+func (g *GlobalConfig) AllApexSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allApexSystemServerJarsKey, func() interface{} {
+		res := g.ApexSystemServerJars.AppendList(&g.ApexStandaloneSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allSystemServerClasspathJarsKey = android.NewOnceKey("allSystemServerClasspathJars")
+
+// Returns all system_server classpath jars.
+func (g *GlobalConfig) AllSystemServerClasspathJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allSystemServerClasspathJarsKey, func() interface{} {
+		res := g.SystemServerJars.AppendList(&g.ApexSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allSystemServerJarsKey = android.NewOnceKey("allSystemServerJars")
+
+// Returns all jars that system_server loads.
+func (g *GlobalConfig) AllSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allSystemServerJarsKey, func() interface{} {
+		res := g.AllPlatformSystemServerJars(ctx).AppendList(g.AllApexSystemServerJars(ctx))
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
 // GlobalSoongConfig contains the global config that is generated from Soong,
 // stored in dexpreopt_soong.config.
 type GlobalSoongConfig struct {
@@ -619,6 +663,8 @@
 		SystemServerJars:                   android.EmptyConfiguredJarList(),
 		SystemServerApps:                   nil,
 		ApexSystemServerJars:               android.EmptyConfiguredJarList(),
+		StandaloneSystemServerJars:         android.EmptyConfiguredJarList(),
+		ApexStandaloneSystemServerJars:     android.EmptyConfiguredJarList(),
 		SpeedApps:                          nil,
 		PreoptFlags:                        nil,
 		DefaultCompilerFilter:              "",
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 1401c75..de139c4 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -110,17 +110,12 @@
 		return true
 	}
 
-	// Don't preopt system server jars that are updatable.
-	if global.ApexSystemServerJars.ContainsJar(module.Name) {
-		return true
-	}
-
 	// If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip
 	// Also preopt system server jars since selinux prevents system server from loading anything from
 	// /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
 	// or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options.
 	if global.OnlyPreoptBootImageAndSystemServer && !global.BootJars.ContainsJar(module.Name) &&
-		!global.SystemServerJars.ContainsJar(module.Name) && !module.PreoptExtractedApk {
+		!global.AllSystemServerJars(ctx).ContainsJar(module.Name) && !module.PreoptExtractedApk {
 		return true
 	}
 
@@ -201,6 +196,25 @@
 	return profilePath
 }
 
+// Returns the dex location of a system server java library.
+func GetSystemServerDexLocation(ctx android.PathContext, global *GlobalConfig, lib string) string {
+	if apex := global.AllApexSystemServerJars(ctx).ApexOfJar(lib); apex != "" {
+		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apex, lib)
+	}
+	return fmt.Sprintf("/system/framework/%s.jar", lib)
+}
+
+// Returns the location to the odex file for the dex file at `path`.
+func ToOdexPath(path string, arch android.ArchType) string {
+	if strings.HasPrefix(path, "/apex/") {
+		return filepath.Join("/system/framework/oat", arch.String(),
+			strings.ReplaceAll(path[1:], "/", "@")+"@classes.odex")
+	}
+
+	return filepath.Join(filepath.Dir(path), "oat", arch.String(),
+		pathtools.ReplaceExtension(filepath.Base(path), "odex"))
+}
+
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
 	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
 	appImage bool, generateDM bool) {
@@ -215,16 +229,8 @@
 		base = "package.apk"
 	}
 
-	toOdexPath := func(path string) string {
-		return filepath.Join(
-			filepath.Dir(path),
-			"oat",
-			arch.String(),
-			pathtools.ReplaceExtension(filepath.Base(path), "odex"))
-	}
-
 	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
-	odexInstallPath := toOdexPath(module.DexLocation)
+	odexInstallPath := ToOdexPath(module.DexLocation, arch)
 	if odexOnSystemOther(module, global) {
 		odexInstallPath = filepath.Join(SystemOtherPartition, odexInstallPath)
 	}
@@ -234,33 +240,58 @@
 
 	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
 
-	systemServerJars := NonApexSystemServerJars(ctx, global)
+	systemServerJars := global.AllSystemServerJars(ctx)
+	systemServerClasspathJars := global.AllSystemServerClasspathJars(ctx)
 
 	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 
-	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
+	if jarIndex := systemServerJars.IndexOfJar(module.Name); jarIndex >= 0 {
 		// System server jars should be dexpreopted together: class loader context of each jar
 		// should include all preceding jars on the system server classpath.
 
 		var clcHost android.Paths
 		var clcTarget []string
-		for _, lib := range systemServerJars[:jarIndex] {
+		endIndex := systemServerClasspathJars.IndexOfJar(module.Name)
+		if endIndex < 0 {
+			// The jar is a standalone one. Use the full classpath as the class loader context.
+			endIndex = systemServerClasspathJars.Len()
+		}
+		for i := 0; i < endIndex; i++ {
+			lib := systemServerClasspathJars.Jar(i)
 			clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib))
-			clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar"))
+			clcTarget = append(clcTarget, GetSystemServerDexLocation(ctx, global, lib))
 		}
 
-		// Copy the system server jar to a predefined location where dex2oat will find it.
-		dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
-		rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
-		rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost)
+		if DexpreoptRunningInSoong {
+			// Copy the system server jar to a predefined location where dex2oat will find it.
+			dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
+			rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
+			rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost)
+		} else {
+			// For Make modules the copy rule is generated in the makefiles, not in dexpreopt.sh.
+			// This is necessary to expose the rule to Ninja, otherwise it has rules that depend on
+			// the jar (namely, dexpreopt commands for all subsequent system server jars that have
+			// this one in their class loader context), but no rule that creates it (because Ninja
+			// cannot see the rule in the generated dexpreopt.sh script).
+		}
 
-		checkSystemServerOrder(ctx, jarIndex)
+		clcHostString := "PCL[" + strings.Join(clcHost.Strings(), ":") + "]"
+		clcTargetString := "PCL[" + strings.Join(clcTarget, ":") + "]"
+
+		if systemServerClasspathJars.ContainsJar(module.Name) {
+			checkSystemServerOrder(ctx, jarIndex)
+		} else {
+			// Standalone jars are loaded by separate class loaders with SYSTEMSERVERCLASSPATH as the
+			// parent.
+			clcHostString = "PCL[];" + clcHostString
+			clcTargetString = "PCL[];" + clcTargetString
+		}
 
 		rule.Command().
-			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clcHost.Strings(), ":") + "]").
+			Text(`class_loader_context_arg=--class-loader-context="` + clcHostString + `"`).
 			Implicits(clcHost).
-			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]")
+			Text(`stored_class_loader_context_arg=--stored-class-loader-context="` + clcTargetString + `"`)
 
 	} else {
 		// There are three categories of Java modules handled here:
@@ -362,7 +393,7 @@
 
 	if !android.PrefixInList(preoptFlags, "--compiler-filter=") {
 		var compilerFilter string
-		if global.SystemServerJars.ContainsJar(module.Name) {
+		if systemServerJars.ContainsJar(module.Name) {
 			// Jars of system server, use the product option if it is set, speed otherwise.
 			if global.SystemServerCompilerFilter != "" {
 				compilerFilter = global.SystemServerCompilerFilter
@@ -416,7 +447,7 @@
 
 	// PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
 	// PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
-	if global.SystemServerJars.ContainsJar(module.Name) {
+	if systemServerJars.ContainsJar(module.Name) {
 		if global.AlwaysSystemServerDebugInfo {
 			debugInfo = true
 		} else if global.NeverSystemServerDebugInfo {
@@ -518,16 +549,6 @@
 	}
 }
 
-var nonApexSystemServerJarsKey = android.NewOnceKey("nonApexSystemServerJars")
-
-// TODO: eliminate the superficial global config parameter by moving global config definition
-// from java subpackage to dexpreopt.
-func NonApexSystemServerJars(ctx android.PathContext, global *GlobalConfig) []string {
-	return ctx.Config().Once(nonApexSystemServerJarsKey, func() interface{} {
-		return android.RemoveListFromList(global.SystemServerJars.CopyOfJars(), global.ApexSystemServerJars.CopyOfJars())
-	}).([]string)
-}
-
 // A predefined location for the system server dex jars. This is needed in order to generate
 // class loader context for dex2oat, as the path to the jar in the Soong module may be unknown
 // at that time (Soong processes the jars in dependency order, which may be different from the
@@ -551,12 +572,12 @@
 	mctx, isModule := ctx.(android.ModuleContext)
 	if isModule {
 		config := GetGlobalConfig(ctx)
-		jars := NonApexSystemServerJars(ctx, config)
+		jars := config.AllSystemServerClasspathJars(ctx)
 		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			depIndex := android.IndexList(dep.Name(), jars)
+			depIndex := jars.IndexOfJar(dep.Name())
 			if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars {
-				jar := jars[jarIndex]
-				dep := jars[depIndex]
+				jar := jars.Jar(jarIndex)
+				dep := jars.Jar(depIndex)
 				mctx.ModuleErrorf("non-optimal order of jars on the system server classpath:"+
 					" '%s' precedes its dependency '%s', so dexpreopt is unable to resolve any"+
 					" references from '%s' to '%s'.\n", jar, dep, jar, dep)
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 4ee61b6..07e4fad 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -33,17 +33,44 @@
 }
 
 func testModuleConfig(ctx android.PathContext, name, partition string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func testApexModuleConfig(ctx android.PathContext, name, apexName string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func testPlatformSystemServerModuleConfig(ctx android.PathContext, name string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/system/framework/%s.jar", name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func createTestModuleConfig(name, dexLocation string, buildPath, dexPath, enforceUsesLibrariesStatusFile android.OutputPath) *ModuleConfig {
 	return &ModuleConfig{
 		Name:                            name,
-		DexLocation:                     fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
-		BuildPath:                       android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
-		DexPath:                         android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		DexLocation:                     dexLocation,
+		BuildPath:                       buildPath,
+		DexPath:                         dexPath,
 		UncompressedDex:                 false,
 		HasApkLibraries:                 false,
 		PreoptFlags:                     nil,
 		ProfileClassListing:             android.OptionalPath{},
 		ProfileIsTextListing:            false,
-		EnforceUsesLibrariesStatusFile:  android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)),
+		EnforceUsesLibrariesStatusFile:  enforceUsesLibrariesStatusFile,
 		EnforceUsesLibraries:            false,
 		ClassLoaderContexts:             nil,
 		Archs:                           []android.ArchType{android.Arm},
@@ -140,6 +167,75 @@
 
 }
 
+func TestDexPreoptApexSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+
+	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
+func TestDexPreoptStandaloneSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testPlatformSystemServerModuleConfig(ctx, "service-A")
+
+	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"platform:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/service-A.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/service-A.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
+func TestDexPreoptApexStandaloneSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+
+	global.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
 func TestDexPreoptProfile(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index 8f5c315..47ae494 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -85,12 +85,12 @@
 // Prepares a test fixture by enabling dexpreopt, registering the fake_tool_binary module type and
 // using that to define the `dex2oatd` module.
 var PrepareForTestByEnablingDexpreopt = android.GroupFixturePreparers(
-	FixtureModifyGlobalConfig(func(*GlobalConfig) {}),
+	FixtureModifyGlobalConfig(func(android.PathContext, *GlobalConfig) {}),
 )
 
 // FixtureModifyGlobalConfig enables dexpreopt (unless modified by the mutator) and modifies the
 // configuration.
-func FixtureModifyGlobalConfig(configModifier func(dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
+func FixtureModifyGlobalConfig(configModifier func(ctx android.PathContext, dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
 	return android.FixtureModifyConfig(func(config android.Config) {
 		// Initialize the dexpreopt GlobalConfig to an empty structure. This has no effect if it has
 		// already been set.
@@ -100,48 +100,77 @@
 
 		// Retrieve the existing configuration and modify it.
 		dexpreoptConfig = GetGlobalConfig(pathCtx)
-		configModifier(dexpreoptConfig)
+		configModifier(pathCtx, dexpreoptConfig)
 	})
 }
 
 // FixtureSetArtBootJars enables dexpreopt and sets the ArtApexJars property.
 func FixtureSetArtBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.ArtApexJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
 
 // FixtureSetBootJars enables dexpreopt and sets the BootJars property.
 func FixtureSetBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
 
 // FixtureSetApexBootJars sets the ApexBootJars property in the global config.
 func FixtureSetApexBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.ApexBootJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
 
+// FixtureSetStandaloneSystemServerJars sets the StandaloneSystemServerJars property.
+func FixtureSetStandaloneSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(jars)
+	})
+}
+
 // FixtureSetSystemServerJars sets the SystemServerJars property.
 func FixtureSetSystemServerJars(jars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.SystemServerJars = android.CreateTestConfiguredJarList(jars)
 	})
 }
 
 // FixtureSetApexSystemServerJars sets the ApexSystemServerJars property in the global config.
 func FixtureSetApexSystemServerJars(jars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.ApexSystemServerJars = android.CreateTestConfiguredJarList(jars)
 	})
 }
 
+// FixtureSetApexStandaloneSystemServerJars sets the ApexStandaloneSystemServerJars property in the
+// global config.
+func FixtureSetApexStandaloneSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(jars)
+	})
+}
+
 // FixtureSetPreoptWithUpdatableBcp sets the PreoptWithUpdatableBcp property in the global config.
 func FixtureSetPreoptWithUpdatableBcp(value bool) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.PreoptWithUpdatableBcp = value
 	})
 }
+
+// FixtureSetBootImageProfiles sets the BootImageProfiles property in the global config.
+func FixtureSetBootImageProfiles(profiles ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(ctx android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.BootImageProfiles = android.PathsForSource(ctx, profiles)
+	})
+}
+
+// FixtureDisableGenerateProfile sets the DisableGenerateProfile property in the global config.
+func FixtureDisableGenerateProfile(disable bool) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.DisableGenerateProfile = disable
+	})
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 3213e5c..c2866ab 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -63,7 +63,6 @@
 
 	ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
 
-	android.RegisterBp2BuildMutator("prebuilt_etc", PrebuiltEtcBp2Build)
 }
 
 var PrepareForTestWithPrebuiltEtc = android.FixtureRegisterWithContext(RegisterPrebuiltEtcBuildComponents)
@@ -377,7 +376,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
@@ -439,6 +438,7 @@
 	InitPrebuiltEtcModule(module, "etc")
 	// This module is host-only
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -449,6 +449,7 @@
 	InitPrebuiltRootModule(module)
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -459,6 +460,7 @@
 	InitPrebuiltEtcModule(module, "usr/share")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -469,6 +471,7 @@
 	InitPrebuiltEtcModule(module, "usr/share")
 	// This module is host-only
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -478,6 +481,7 @@
 	InitPrebuiltEtcModule(module, "fonts")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -491,6 +495,7 @@
 	InitPrebuiltEtcModule(module, "etc/firmware")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -503,6 +508,7 @@
 	InitPrebuiltEtcModule(module, "etc/dsp")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -516,16 +522,10 @@
 	InitPrebuiltEtcModule(module, "lib/rfsa")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
-// Flags to be included in the snapshot
-type snapshotJsonFlags struct {
-	ModuleName          string `json:",omitempty"`
-	Filename            string `json:",omitempty"`
-	RelativeInstallPath string `json:",omitempty"`
-}
-
 // Copy file into the snapshot
 func copyFile(ctx android.SingletonContext, path android.Path, out string, fake bool) android.OutputPath {
 	if fake {
@@ -612,7 +612,7 @@
 		snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "etc", m.BaseModuleName())
 		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, m.OutputFile(), snapshotLibOut, s.Fake))
 
-		prop := snapshotJsonFlags{}
+		prop := snapshot.SnapshotJsonFlags{}
 		propOut := snapshotLibOut + ".json"
 		prop.ModuleName = m.BaseModuleName()
 		if m.subdirProperties.Relative_install_path != nil {
@@ -662,26 +662,28 @@
 	Installable bazel.BoolAttribute
 }
 
-func PrebuiltEtcBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*PrebuiltEtc)
-	if !ok {
-		// Not an prebuilt_etc
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
+// ConvertWithBp2build performs bp2build conversion of PrebuiltEtc
+func (p *PrebuiltEtc) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	// All prebuilt_* modules are PrebuiltEtc, but at this time, we only convert prebuilt_etc modules.
 	if ctx.ModuleType() != "prebuilt_etc" {
 		return
 	}
 
-	prebuiltEtcBp2BuildInternal(ctx, module)
+	prebuiltEtcBp2BuildInternal(ctx, p)
 }
 
 func prebuiltEtcBp2BuildInternal(ctx android.TopDownMutatorContext, module *PrebuiltEtc) {
 	var srcLabelAttribute bazel.LabelAttribute
-	if module.properties.Src != nil {
-		srcLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.Src))
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltEtcProperties{}) {
+		for config, p := range configToProps {
+			props, ok := p.(*prebuiltEtcProperties)
+			if !ok {
+				continue
+			}
+			if props.Src != nil {
+				srcLabelAttribute.SetSelectValue(axis, config, android.BazelLabelForModuleSrcSingle(ctx, *props.Src))
+			}
+		}
 	}
 
 	var filename string
@@ -711,5 +713,5 @@
 		Bzl_load_location: "//build/bazel/rules:prebuilt_etc.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/etc/snapshot_etc.go b/etc/snapshot_etc.go
index 9a25d5a..b54a8a6 100644
--- a/etc/snapshot_etc.go
+++ b/etc/snapshot_etc.go
@@ -128,7 +128,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
 			},
 		},
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 73d807d..fc973a4 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -267,7 +267,7 @@
 		OutputFile: android.OptionalPathForPath(b.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", b.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", b.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
 			},
 		},
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index b2caa51..0796258 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -394,7 +394,7 @@
 		OutputFile: android.OptionalPathForPath(f.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
 			},
 		},
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 739e609..e2f7d7b 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -215,7 +215,7 @@
 		OutputFile: android.OptionalPathForPath(l.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.installFileName())
 			},
 		},
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 3f16c0d..63e0aba 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -247,7 +247,7 @@
 		OutputFile: android.OptionalPathForPath(v.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", v.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
 			},
 		},
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index ccadc0f..8861d1b 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -42,8 +42,9 @@
 }
 
 type FuzzPackager struct {
-	Packages    android.Paths
-	FuzzTargets map[string]bool
+	Packages                android.Paths
+	FuzzTargets             map[string]bool
+	SharedLibInstallStrings []string
 }
 
 type FileToZip struct {
@@ -251,3 +252,42 @@
 	sort.Strings(fuzzTargets)
 	ctx.Strict(targets, strings.Join(fuzzTargets, " "))
 }
+
+// CollectAllSharedDependencies performs a breadth-first search over the provided module's
+// dependencies using `visitDirectDeps` to enumerate all shared library
+// dependencies. We require breadth-first expansion, as otherwise we may
+// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
+// from a dependency. This may cause issues when dependencies have explicit
+// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
+func CollectAllSharedDependencies(ctx android.SingletonContext, module android.Module, unstrippedOutputFile func(module android.Module) android.Path, isValidSharedDependency func(dependency android.Module) bool) android.Paths {
+	var fringe []android.Module
+
+	seen := make(map[string]bool)
+
+	// Enumerate the first level of dependencies, as we discard all non-library
+	// modules in the BFS loop below.
+	ctx.VisitDirectDeps(module, func(dep android.Module) {
+		if isValidSharedDependency(dep) {
+			fringe = append(fringe, dep)
+		}
+	})
+
+	var sharedLibraries android.Paths
+
+	for i := 0; i < len(fringe); i++ {
+		module := fringe[i]
+		if seen[module.Name()] {
+			continue
+		}
+		seen[module.Name()] = true
+
+		sharedLibraries = append(sharedLibraries, unstrippedOutputFile(module))
+		ctx.VisitDirectDeps(module, func(dep android.Module) {
+			if isValidSharedDependency(dep) && !seen[dep.Name()] {
+				fringe = append(fringe, dep)
+			}
+		})
+	}
+
+	return sharedLibraries
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index bde6e97..6a91e01 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -67,12 +67,6 @@
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
 	})
-
-	android.RegisterBp2BuildMutator("genrule", GenruleBp2Build)
-}
-
-func RegisterGenruleBp2BuildDeps(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("genrule_tool_deps", toolDepsMutator)
 }
 
 var (
@@ -109,6 +103,7 @@
 
 type hostToolDependencyTag struct {
 	blueprint.BaseDependencyTag
+	android.LicenseAnnotationToolchainDependencyTag
 	label string
 }
 type generatorProperties struct {
@@ -155,6 +150,11 @@
 	// For other packages to make their own genrules with extra
 	// properties
 	Extra interface{}
+
+	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
+	// prefix environment variables to it.
+	CmdModifier func(ctx android.ModuleContext, cmd string) string
+
 	android.ImageInterface
 
 	properties generatorProperties
@@ -242,7 +242,7 @@
 // Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
 func (c *Module) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
+	filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
 	if ok {
 		var bazelOutputFiles android.Paths
 		exportIncludeDirs := map[string]bool{}
@@ -330,14 +330,9 @@
 					}
 				case bootstrap.GoBinaryTool:
 					// A GoBinaryTool provides the install path to a tool, which will be copied.
-					if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil {
-						toolPath := android.PathForOutput(ctx, s)
-						tools = append(tools, toolPath)
-						addLocationLabel(tag.label, toolLocation{android.Paths{toolPath}})
-					} else {
-						ctx.ModuleErrorf("cannot find path for %q: %v", tool, err)
-						return
-					}
+					p := android.PathForGoBinary(ctx, t)
+					tools = append(tools, p)
+					addLocationLabel(tag.label, toolLocation{android.Paths{p}})
 				default:
 					ctx.ModuleErrorf("%q is not a host tool provider", tool)
 					return
@@ -397,8 +392,13 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	cmd := String(g.properties.Cmd)
+	if g.CmdModifier != nil {
+		cmd = g.CmdModifier(ctx, cmd)
+	}
+
 	// Generate tasks, either from genrule or gensrcs.
-	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
+	for _, task := range g.taskGenerator(ctx, cmd, srcFiles) {
 		if len(task.out) == 0 {
 			ctx.ModuleErrorf("must have at least one output file")
 			return
@@ -826,17 +826,8 @@
 	Cmd   string
 }
 
-func GenruleBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "genrule" {
-		// Not a regular genrule. Could be a cc_genrule or java_genrule.
-		return
-	}
-
+// ConvertWithBp2build converts a Soong module -> Bazel target.
+func (m *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	// Bazel only has the "tools" attribute.
 	tools_prop := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
 	tool_files_prop := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
@@ -854,7 +845,11 @@
 	if m.properties.Cmd != nil {
 		cmd = strings.Replace(*m.properties.Cmd, "$(in)", "$(SRCS)", -1)
 		cmd = strings.Replace(cmd, "$(out)", "$(OUTS)", -1)
-		cmd = strings.Replace(cmd, "$(genDir)", "$(GENDIR)", -1)
+		genDir := "$(GENDIR)"
+		if ctx.ModuleType() == "cc_genrule" {
+			genDir = "$(RULEDIR)"
+		}
+		cmd = strings.Replace(cmd, "$(genDir)", genDir, -1)
 		if len(tools.Value.Includes) > 0 {
 			cmd = strings.Replace(cmd, "$(location)", fmt.Sprintf("$(location %s)", tools.Value.Includes[0].Label), -1)
 			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
@@ -892,7 +887,7 @@
 	}
 
 	// Create the BazelTargetModule.
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 var Bool = proptools.Bool
diff --git a/java/Android.bp b/java/Android.bp
index 9ffa123..8835b44 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -40,6 +40,7 @@
         "dex.go",
         "dexpreopt.go",
         "dexpreopt_bootjars.go",
+        "dexpreopt_check.go",
         "dexpreopt_config.go",
         "droiddoc.go",
         "droidstubs.go",
@@ -92,6 +93,7 @@
         "platform_bootclasspath_test.go",
         "platform_compat_config_test.go",
         "plugin_test.go",
+        "prebuilt_apis_test.go",
         "rro_test.go",
         "sdk_test.go",
         "sdk_library_test.go",
diff --git a/java/aar.go b/java/aar.go
index afbaea2..13390db 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -486,6 +486,18 @@
 	exportedStaticPackages    android.Paths
 }
 
+var _ android.OutputFileProducer = (*AndroidLibrary)(nil)
+
+// For OutputFileProducer interface
+func (a *AndroidLibrary) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case ".aar":
+		return []android.Path{a.aarFile}, nil
+	default:
+		return a.Library.OutputFiles(tag)
+	}
+}
+
 func (a *AndroidLibrary) ExportedProguardFlagFiles() android.Paths {
 	return a.exportedProguardFlagFiles
 }
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 38065f1..f29d8ad 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -42,6 +43,21 @@
 	},
 	"args", "libs")
 
+// targetSdkVersion for manifest_fixer
+// When TARGET_BUILD_APPS is not empty, this method returns 10000 for modules targeting an unreleased SDK
+// This enables release builds (that run with TARGET_BUILD_APPS=[val...]) to target APIs that have not yet been finalized as part of an SDK
+func targetSdkVersionForManifestFixer(ctx android.ModuleContext, sdkContext android.SdkContext) string {
+	targetSdkVersionSpec := sdkContext.TargetSdkVersion(ctx)
+	if ctx.Config().UnbundledBuildApps() && targetSdkVersionSpec.ApiLevel.IsPreview() {
+		return strconv.Itoa(android.FutureApiLevel.FinalOrFutureInt())
+	}
+	targetSdkVersion, err := targetSdkVersionSpec.EffectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
+	}
+	return targetSdkVersion
+}
+
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
 func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext android.SdkContext,
 	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
@@ -89,10 +105,8 @@
 		args = append(args, "--logging-parent", loggingParent)
 	}
 	var deps android.Paths
-	targetSdkVersion, err := sdkContext.TargetSdkVersion(ctx).EffectiveVersionString(ctx)
-	if err != nil {
-		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
-	}
+	targetSdkVersion := targetSdkVersionForManifestFixer(ctx, sdkContext)
+
 	if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
 		targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
 		deps = append(deps, ApiFingerprintPath(ctx))
diff --git a/java/android_resources.go b/java/android_resources.go
index 6864ebb..8c5908f 100644
--- a/java/android_resources.go
+++ b/java/android_resources.go
@@ -43,7 +43,7 @@
 
 // androidResourceGlob returns the list of files in the given directory, using the standard
 // exclusion patterns for Android resources.
-func androidResourceGlob(ctx android.ModuleContext, dir android.Path) android.Paths {
+func androidResourceGlob(ctx android.EarlyModuleContext, dir android.Path) android.Paths {
 	return ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), androidResourceIgnoreFilenames)
 }
 
diff --git a/java/androidmk.go b/java/androidmk.go
index 68ccd82..19fe7e2 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -29,8 +29,8 @@
 
 	if hostDexNeeded {
 		var output android.Path
-		if library.dexJarFile != nil {
-			output = library.dexJarFile
+		if library.dexJarFile.IsSet() {
+			output = library.dexJarFile.Path()
 		} else {
 			output = library.implementationAndResourcesJar
 		}
@@ -44,9 +44,10 @@
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetBool("LOCAL_IS_HOST_MODULE", true)
 					entries.SetPath("LOCAL_PREBUILT_MODULE_FILE", output)
-					if library.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile)
+					if library.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile.Path())
 					}
+					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", library.hostdexInstallFile)
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem()+"-hostdex")
@@ -60,28 +61,23 @@
 func (library *Library) AndroidMkEntries() []android.AndroidMkEntries {
 	var entriesList []android.AndroidMkEntries
 
+	if library.Os() == android.Windows {
+		// Make does not support Windows Java modules
+		return nil
+	}
+
 	if library.hideApexVariantFromMake {
-		// For a java library built for an APEX we don't need Make module
+		// For a java library built for an APEX, we don't need a Make module for itself. Otherwise, it
+		// will conflict with the platform variant because they have the same module name in the
+		// makefile. However, we need to add its dexpreopt outputs as sub-modules, if it is preopted.
+		dexpreoptEntries := library.dexpreopter.AndroidMkEntriesForApex()
+		if len(dexpreoptEntries) > 0 {
+			entriesList = append(entriesList, dexpreoptEntries...)
+		}
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
-		// May still need to add some additional dependencies.
-		checkedModulePaths := library.additionalCheckedModules
-		if len(checkedModulePaths) != 0 {
-			entriesList = append(entriesList, android.AndroidMkEntries{
-				Class: "FAKE",
-				// Need at least one output file in order for this to take effect.
-				OutputFile: android.OptionalPathForPath(checkedModulePaths[0]),
-				Include:    "$(BUILD_PHONY_PACKAGE)",
-				ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-					func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", checkedModulePaths.Strings()...)
-					},
-				},
-			})
-		} else {
-			entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
-		}
+		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
@@ -100,8 +96,8 @@
 					if library.installFile == nil {
 						entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
 					}
-					if library.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile)
+					if library.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile.Path())
 					}
 					if len(library.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
@@ -117,10 +113,6 @@
 					requiredUsesLibs, optionalUsesLibs := library.classLoaderContexts.UsesLibs()
 					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", append(requiredUsesLibs, optionalUsesLibs...)...)
 
-					if len(library.additionalCheckedModules) != 0 {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
-					}
-
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_DICT", library.dexer.proguardDictionary)
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_USAGE_ZIP", library.dexer.proguardUsageZip)
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
@@ -141,20 +133,21 @@
 }
 
 // Called for modules that are a component of a test suite.
-func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
+func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string, perTestcaseDirectory bool) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
 		entries.AddCompatibilityTestSuites(test_suites...)
 	} else {
 		entries.AddCompatibilityTestSuites("null-suite")
 	}
+	entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", perTestcaseDirectory)
 }
 
 func (j *Test) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testProperties.Test_suites)
+		testSuiteComponent(entries, j.testProperties.Test_suites, Bool(j.testProperties.Per_testcase_directory))
 		if j.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", j.testConfig)
 		}
@@ -182,14 +175,21 @@
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites)
+		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites, Bool(j.testHelperLibraryProperties.Per_testcase_directory))
 	})
 
 	return entriesList
 }
 
 func (prebuilt *Import) AndroidMkEntries() []android.AndroidMkEntries {
-	if prebuilt.hideApexVariantFromMake || !prebuilt.ContainingSdk().Unversioned() {
+	if prebuilt.hideApexVariantFromMake {
+		// For a library imported from a prebuilt APEX, we don't need a Make module for itself, as we
+		// don't need to install it. However, we need to add its dexpreopt outputs as sub-modules, if it
+		// is preopted.
+		dexpreoptEntries := prebuilt.dexpreopter.AndroidMkEntriesForApex()
+		return append(dexpreoptEntries, android.AndroidMkEntries{Disabled: true})
+	}
+	if !prebuilt.ContainingSdk().Unversioned() {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Disabled: true,
 		}}
@@ -201,8 +201,8 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable))
-				if prebuilt.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile)
+				if prebuilt.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile.Path())
 				}
 				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile)
 				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile)
@@ -221,12 +221,12 @@
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile),
+		OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile.Path()),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				if prebuilt.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile)
+				if prebuilt.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile.Path())
 				}
 				if len(prebuilt.dexpreopter.builtInstalled) > 0 {
 					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", prebuilt.dexpreopter.builtInstalled)
@@ -263,6 +263,10 @@
 }
 
 func (binary *Binary) AndroidMkEntries() []android.AndroidMkEntries {
+	if binary.Os() == android.Windows {
+		// Make does not support Windows Java modules
+		return nil
+	}
 
 	if !binary.isWrapperVariant {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
@@ -273,8 +277,8 @@
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar)
-					if binary.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile)
+					if binary.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile.Path())
 					}
 					if len(binary.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
@@ -289,11 +293,6 @@
 		}}
 	} else {
 		outputFile := binary.wrapperFile
-		// Have Make installation trigger Soong installation by using Soong's install path as
-		// the output file.
-		if binary.Host() {
-			outputFile = binary.binaryFile
-		}
 
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Class:      "EXECUTABLES",
@@ -330,8 +329,8 @@
 				entries.SetString("LOCAL_MODULE", app.installApkName)
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", app.appProperties.PreventInstall)
 				entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", app.exportPackage)
-				if app.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile)
+				if app.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile.Path())
 				}
 				if app.implementationAndResourcesJar != nil {
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", app.implementationAndResourcesJar)
@@ -444,7 +443,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		if a.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", a.testConfig)
 		}
@@ -460,7 +459,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites, Bool(a.appTestHelperAppProperties.Per_testcase_directory))
 		// introduce a flag variable to control the generation of the .config file
 		entries.SetString("LOCAL_DISABLE_TEST_CONFIG", "true")
 	})
@@ -671,7 +670,7 @@
 	entriesList := a.AndroidAppImport.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		androidMkWriteTestData(a.data, entries)
 	})
 	return entriesList
@@ -693,7 +692,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_CERTIFICATE", r.certificate.AndroidMkString())
-				entries.SetPath("LOCAL_MODULE_PATH", r.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", r.installDir)
 				entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", r.properties.Overrides...)
 			},
 		},
@@ -704,12 +703,12 @@
 	return []android.AndroidMkEntries{
 		android.AndroidMkEntries{
 			Class:      "APPS",
-			OutputFile: android.OptionalPathForPath(apkSet.packedOutput),
+			OutputFile: android.OptionalPathForPath(apkSet.primaryOutput),
 			Include:    "$(BUILD_SYSTEM)/soong_android_app_set.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", apkSet.Privileged())
-					entries.SetString("LOCAL_APK_SET_INSTALL_FILE", apkSet.InstallFile())
+					entries.SetPath("LOCAL_APK_SET_INSTALL_FILE", apkSet.PackedAdditionalOutputs())
 					entries.SetPath("LOCAL_APKCERTS_FILE", apkSet.apkcertsFile)
 					entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", apkSet.properties.Overrides...)
 				},
diff --git a/java/app.go b/java/app.go
index 5104f07..1c69aeb 100755
--- a/java/app.go
+++ b/java/app.go
@@ -43,8 +43,6 @@
 	ctx.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory)
 	ctx.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory)
 	ctx.RegisterModuleType("override_android_test", OverrideAndroidTestModuleFactory)
-
-	android.RegisterBp2BuildMutator("android_app_certificate", AndroidAppCertificateBp2Build)
 }
 
 // AndroidManifest.xml merging
@@ -139,6 +137,7 @@
 }
 
 type AndroidApp struct {
+	android.BazelModuleBase
 	Library
 	aapt
 	android.OverridableModuleBase
@@ -291,7 +290,7 @@
 
 		if minSdkVersion, err := a.MinSdkVersion(ctx).EffectiveVersion(ctx); err == nil {
 			a.checkJniLibsSdkVersion(ctx, minSdkVersion)
-			android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+			android.CheckMinSdkVersion(ctx, minSdkVersion, a.WalkPayloadDeps)
 		} else {
 			ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		}
@@ -471,12 +470,13 @@
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
 	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
+	a.dexpreopter.preventInstall = a.appProperties.PreventInstall
 
 	if ctx.ModuleName() != "framework-res" {
 		a.Module.compile(ctx, a.aaptSrcJar)
 	}
 
-	return a.dexJarFile
+	return a.dexJarFile.PathOrNil()
 }
 
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath {
@@ -485,7 +485,7 @@
 		a.jniLibs = jniLibs
 		if a.shouldEmbedJnis(ctx) {
 			jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip")
-			a.installPathForJNISymbols = a.installPath(ctx).ToMakePath()
+			a.installPathForJNISymbols = a.installPath(ctx)
 			TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.useEmbeddedNativeLibs(ctx))
 			for _, jni := range jniLibs {
 				if jni.coverageFile.Valid() {
@@ -720,11 +720,15 @@
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 
 	// Install the app package.
-	if (Bool(a.Module.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() {
-		ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile)
+	if (Bool(a.Module.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() &&
+		!a.appProperties.PreventInstall {
+
+		var extraInstalledPaths android.Paths
 		for _, extra := range a.extraOutputFiles {
-			ctx.InstallFile(a.installDir, extra.Base(), extra)
+			installed := ctx.InstallFile(a.installDir, extra.Base(), extra)
+			extraInstalledPaths = append(extraInstalledPaths, installed)
 		}
+		ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile, extraInstalledPaths...)
 	}
 
 	a.buildAppDependencyInfo(ctx)
@@ -760,18 +764,18 @@
 				}
 
 				lib := dep.OutputFile()
-				path := lib.Path()
-				if seenModulePaths[path.String()] {
-					return false
-				}
-				seenModulePaths[path.String()] = true
-
-				if checkNativeSdkVersion && dep.SdkVersion() == "" {
-					ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
-						otherName)
-				}
-
 				if lib.Valid() {
+					path := lib.Path()
+					if seenModulePaths[path.String()] {
+						return false
+					}
+					seenModulePaths[path.String()] = true
+
+					if checkNativeSdkVersion && dep.SdkVersion() == "" {
+						ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
+							otherName)
+					}
+
 					jniLibs = append(jniLibs, jniLib{
 						name:           ctx.OtherModuleName(module),
 						path:           path,
@@ -938,6 +942,7 @@
 	android.InitDefaultableModule(module)
 	android.InitOverridableModule(module, &module.appProperties.Overrides)
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
@@ -1070,6 +1075,9 @@
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
 	Auto_gen_config *bool
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type AndroidTestHelperApp struct {
@@ -1305,7 +1313,8 @@
 				replaceInList(u.usesLibraryProperties.Optional_uses_libs, dep, libName)
 			}
 			clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional, tag.implicit,
-				lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
+				lib.DexJarBuildPath().PathOrNil(), lib.DexJarInstallPath(),
+				lib.ClassLoaderContexts())
 		} else if ctx.Config().AllowMissingDependencies() {
 			ctx.AddMissingDependencies([]string{dep})
 		} else {
@@ -1396,23 +1405,11 @@
 	Certificate string
 }
 
-func AndroidAppCertificateBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*AndroidAppCertificate)
-	if !ok {
-		// Not an Android app certificate
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-	if ctx.ModuleType() != "android_app_certificate" {
-		return
-	}
-
-	androidAppCertificateBp2BuildInternal(ctx, module)
+func (m *AndroidAppCertificate) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	androidAppCertificateBp2Build(ctx, m)
 }
 
-func androidAppCertificateBp2BuildInternal(ctx android.TopDownMutatorContext, module *AndroidAppCertificate) {
+func androidAppCertificateBp2Build(ctx android.TopDownMutatorContext, module *AndroidAppCertificate) {
 	var certificate string
 	if module.properties.Certificate != nil {
 		certificate = *module.properties.Certificate
@@ -1427,5 +1424,41 @@
 		Bzl_load_location: "//build/bazel/rules:android_app_certificate.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
+}
+
+type bazelAndroidAppAttributes struct {
+	Srcs           bazel.LabelListAttribute
+	Manifest       bazel.Label
+	Custom_package *string
+	Resource_files bazel.LabelListAttribute
+}
+
+// ConvertWithBp2build is used to convert android_app to Bazel.
+func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	//TODO(b/209577426): Support multiple arch variants
+	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, a.properties.Srcs, a.properties.Exclude_srcs))
+
+	manifest := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
+
+	resourceFiles := bazel.LabelList{
+		Includes: []bazel.Label{},
+	}
+	for _, dir := range android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res") {
+		files := android.RootToModuleRelativePaths(ctx, androidResourceGlob(ctx, dir))
+		resourceFiles.Includes = append(resourceFiles.Includes, files...)
+	}
+
+	attrs := &bazelAndroidAppAttributes{
+		Srcs:     srcs,
+		Manifest: android.BazelLabelForModuleSrcSingle(ctx, manifest),
+		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
+		Custom_package: a.overridableAppProperties.Package_name,
+		Resource_files: bazel.MakeLabelListAttribute(resourceFiles),
+	}
+	props := bazel.BazelTargetModuleProperties{Rule_class: "android_binary",
+		Bzl_load_location: "@rules_android//rules:rules.bzl"}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+
 }
diff --git a/java/app_set.go b/java/app_set.go
index 6b25638..694b167 100644
--- a/java/app_set.go
+++ b/java/app_set.go
@@ -55,10 +55,10 @@
 	android.DefaultableModuleBase
 	prebuilt android.Prebuilt
 
-	properties   AndroidAppSetProperties
-	packedOutput android.WritablePath
-	installFile  string
-	apkcertsFile android.ModuleOutPath
+	properties    AndroidAppSetProperties
+	packedOutput  android.WritablePath
+	primaryOutput android.WritablePath
+	apkcertsFile  android.ModuleOutPath
 }
 
 func (as *AndroidAppSet) Name() string {
@@ -78,11 +78,11 @@
 }
 
 func (as *AndroidAppSet) OutputFile() android.Path {
-	return as.packedOutput
+	return as.primaryOutput
 }
 
-func (as *AndroidAppSet) InstallFile() string {
-	return as.installFile
+func (as *AndroidAppSet) PackedAdditionalOutputs() android.Path {
+	return as.packedOutput
 }
 
 func (as *AndroidAppSet) APKCertsFile() android.Path {
@@ -114,11 +114,11 @@
 
 func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+	as.primaryOutput = android.PathForModuleOut(ctx, as.BaseModuleName()+".apk")
 	as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt")
 	// We are assuming here that the install file in the APK
 	// set has `.apk` suffix. If it doesn't the build will fail.
 	// APK sets containing APEX files are handled elsewhere.
-	as.installFile = as.BaseModuleName() + ".apk"
 	screenDensities := "all"
 	if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 {
 		screenDensities = strings.ToUpper(strings.Join(dpis, ","))
@@ -127,11 +127,11 @@
 	// TODO(asmundak): do we support device features
 	ctx.Build(pctx,
 		android.BuildParams{
-			Rule:           extractMatchingApks,
-			Description:    "Extract APKs from APK set",
-			Output:         as.packedOutput,
-			ImplicitOutput: as.apkcertsFile,
-			Inputs:         android.Paths{as.prebuilt.SingleSourcePath(ctx)},
+			Rule:            extractMatchingApks,
+			Description:     "Extract APKs from APK set",
+			Output:          as.primaryOutput,
+			ImplicitOutputs: android.WritablePaths{as.packedOutput, as.apkcertsFile},
+			Inputs:          android.Paths{as.prebuilt.SingleSourcePath(ctx)},
 			Args: map[string]string{
 				"abis":              strings.Join(SupportedAbis(ctx), ","),
 				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
@@ -140,10 +140,21 @@
 				"stem":              as.BaseModuleName(),
 				"apkcerts":          as.apkcertsFile.String(),
 				"partition":         as.PartitionTag(ctx.DeviceConfig()),
+				"zip":               as.packedOutput.String(),
 			},
 		})
+
+	var installDir android.InstallPath
+	if as.Privileged() {
+		installDir = android.PathForModuleInstall(ctx, "priv-app", as.BaseModuleName())
+	} else {
+		installDir = android.PathForModuleInstall(ctx, "app", as.BaseModuleName())
+	}
+	ctx.InstallFileWithExtraFilesZip(installDir, as.BaseModuleName()+".apk", as.primaryOutput, as.packedOutput)
 }
 
+func (as *AndroidAppSet) InstallBypassMake() bool { return true }
+
 // android_app_set extracts a set of APKs based on the target device
 // configuration and installs this set as "split APKs".
 // The extracted set always contains an APK whose name is
diff --git a/java/app_set_test.go b/java/app_set_test.go
index adaf71b..03eb667 100644
--- a/java/app_set_test.go
+++ b/java/app_set_test.go
@@ -17,19 +17,20 @@
 import (
 	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 
 	"android/soong/android"
 )
 
 func TestAndroidAppSet(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app_set {
 			name: "foo",
 			set: "prebuilts/apks/app.apks",
 			prerelease: true,
 		}`)
-	module := ctx.ModuleForTests("foo", "android_common")
+	module := result.ModuleForTests("foo", "android_common")
 	const packedSplitApks = "foo.zip"
 	params := module.Output(packedSplitApks)
 	if params.Rule == nil {
@@ -41,9 +42,22 @@
 	if s := params.Args["partition"]; s != "system" {
 		t.Errorf("wrong partition value: '%s', expected 'system'", s)
 	}
-	mkEntries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
+
+	android.AssertPathRelativeToTopEquals(t, "incorrect output path",
+		"out/soong/.intermediates/foo/android_common/foo.apk", params.Output)
+
+	android.AssertPathsRelativeToTopEquals(t, "incorrect implicit output paths",
+		[]string{
+			"out/soong/.intermediates/foo/android_common/foo.zip",
+			"out/soong/.intermediates/foo/android_common/apkcerts.txt",
+		},
+		params.ImplicitOutputs.Paths())
+
+	mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0]
 	actualInstallFile := mkEntries.EntryMap["LOCAL_APK_SET_INSTALL_FILE"]
-	expectedInstallFile := []string{"foo.apk"}
+	expectedInstallFile := []string{
+		strings.Replace(params.ImplicitOutputs[0].String(), android.OutSoongDir, result.Config.SoongOutDir(), 1),
+	}
 	if !reflect.DeepEqual(actualInstallFile, expectedInstallFile) {
 		t.Errorf("Unexpected LOCAL_APK_SET_INSTALL_FILE value: '%s', expected: '%s',",
 			actualInstallFile, expectedInstallFile)
diff --git a/java/app_test.go b/java/app_test.go
index 07439fc..4da7c3d 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -144,14 +144,14 @@
 		}
 	`)
 
-	testJavaError(t, "platform_apis must be true when sdk_version is empty.", `
+	testJavaError(t, "This module has conflicting settings. sdk_version is empty, which means that this module is build against platform APIs. However platform_apis is not set to true", `
 		android_app {
 			name: "bar",
 			srcs: ["b.java"],
 		}
 	`)
 
-	testJavaError(t, "platform_apis must be false when sdk_version is not empty.", `
+	testJavaError(t, "This module has conflicting settings. sdk_version is not empty, which means this module cannot use platform APIs. However platform_apis is set to true.", `
 		android_app {
 			name: "bar",
 			srcs: ["b.java"],
@@ -2873,3 +2873,76 @@
 		t.Errorf("App does not use library proguard config")
 	}
 }
+
+func TestTargetSdkVersionManifestFixer(t *testing.T) {
+	platform_sdk_codename := "Tiramisu"
+	testCases := []struct {
+		name                     string
+		targetSdkVersionInBp     string
+		targetSdkVersionExpected string
+		unbundledBuild           bool
+	}{
+		{
+			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion",
+			targetSdkVersionInBp:     "30",
+			targetSdkVersionExpected: "30",
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has targetSdkVersion",
+			targetSdkVersionInBp:     "30",
+			targetSdkVersionExpected: "30",
+			unbundledBuild:           true,
+		},
+		{
+			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
+			targetSdkVersionInBp:     platform_sdk_codename,
+			targetSdkVersionExpected: platform_sdk_codename,
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
+			targetSdkVersionInBp:     platform_sdk_codename,
+			targetSdkVersionExpected: "10000",
+			unbundledBuild:           true,
+		},
+
+		{
+			name:                     "Non-Unbundled build: Android.bp has no targetSdkVersion",
+			targetSdkVersionExpected: platform_sdk_codename,
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has no targetSdkVersion",
+			targetSdkVersionExpected: "10000",
+			unbundledBuild:           true,
+		},
+	}
+	for _, testCase := range testCases {
+		bp := fmt.Sprintf(`
+			android_app {
+				name: "foo",
+				sdk_version: "current",
+				target_sdk_version: "%v",
+			}
+			`, testCase.targetSdkVersionInBp)
+		fixture := android.GroupFixturePreparers(
+			prepareForJavaTest,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				// explicitly set platform_sdk_codename to make the test deterministic
+				variables.Platform_sdk_codename = &platform_sdk_codename
+				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
+				// create a non-empty list if unbundledBuild==true
+				if testCase.unbundledBuild {
+					variables.Unbundled_build_apps = []string{"apex_a", "apex_b"}
+				}
+			}),
+		)
+
+		result := fixture.RunTestWithBp(t, bp)
+		foo := result.ModuleForTests("foo", "android_common")
+
+		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args
+		android.AssertStringEquals(t, testCase.name, testCase.targetSdkVersionExpected, manifestFixerArgs["targetSdkVersion"])
+	}
+}
diff --git a/java/base.go b/java/base.go
index 86022c3..7cd71a2 100644
--- a/java/base.go
+++ b/java/base.go
@@ -122,6 +122,14 @@
 		Javacflags []string
 	}
 
+	Openjdk11 struct {
+		// List of source files that should only be used when passing -source 1.9 or higher
+		Srcs []string `android:"path"`
+
+		// List of javac flags that should only be used when passing -source 1.9 or higher
+		Javacflags []string
+	}
+
 	// When compiling language level 9+ .java code in packages that are part of
 	// a system module, patch_module names the module that your sources and
 	// dependencies should be patched into. The Android runtime currently
@@ -184,16 +192,25 @@
 // Properties that are specific to device modules. Host module factories should not add these when
 // constructing a new module.
 type DeviceProperties struct {
-	// if not blank, set to the version of the sdk to compile against.
+	// If not blank, set to the version of the sdk to compile against.
 	// Defaults to compiling against the current platform.
+	// Values are of one of the following forms:
+	// 1) numerical API level or "current"
+	// 2) An SDK kind with an API level: "<sdk kind>_<API level>". See
+	// build/soong/android/sdk_version.go for the complete and up to date list of
+	// SDK kinds. If the SDK kind value is empty, it will be set to public.
 	Sdk_version *string
 
 	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
-	// Defaults to sdk_version if not set.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
 	Min_sdk_version *string
 
+	// if not blank, set the maximum version of the sdk that the compiled artifacts will run against.
+	// Defaults to empty string "". See sdk_version for possible values.
+	Max_sdk_version *string
+
 	// if not blank, set the targetSdkVersion in the AndroidManifest.xml.
-	// Defaults to sdk_version if not set.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
 	Target_sdk_version *string
 
 	// Whether to compile against the platform APIs instead of an SDK.
@@ -276,12 +293,94 @@
 	return false
 }
 
+// OptionalDexJarPath can be either unset, hold a valid path to a dex jar file,
+// or an invalid path describing the reason it is invalid.
+//
+// It is unset if a dex jar isn't applicable, i.e. no build rule has been
+// requested to create one.
+//
+// If a dex jar has been requested to be built then it is set, and it may be
+// either a valid android.Path, or invalid with a reason message. The latter
+// happens if the source that should produce the dex file isn't able to.
+//
+// E.g. it is invalid with a reason message if there is a prebuilt APEX that
+// could produce the dex jar through a deapexer module, but the APEX isn't
+// installable so doing so wouldn't be safe.
+type OptionalDexJarPath struct {
+	isSet bool
+	path  android.OptionalPath
+}
+
+// IsSet returns true if a path has been set, either invalid or valid.
+func (o OptionalDexJarPath) IsSet() bool {
+	return o.isSet
+}
+
+// Valid returns true if there is a path that is valid.
+func (o OptionalDexJarPath) Valid() bool {
+	return o.isSet && o.path.Valid()
+}
+
+// Path returns the valid path, or panics if it's either not set or is invalid.
+func (o OptionalDexJarPath) Path() android.Path {
+	if !o.isSet {
+		panic("path isn't set")
+	}
+	return o.path.Path()
+}
+
+// PathOrNil returns the path if it's set and valid, or else nil.
+func (o OptionalDexJarPath) PathOrNil() android.Path {
+	if o.Valid() {
+		return o.Path()
+	}
+	return nil
+}
+
+// InvalidReason returns the reason for an invalid path, which is never "". It
+// returns "" for an unset or valid path.
+func (o OptionalDexJarPath) InvalidReason() string {
+	if !o.isSet {
+		return ""
+	}
+	return o.path.InvalidReason()
+}
+
+func (o OptionalDexJarPath) String() string {
+	if !o.isSet {
+		return "<unset>"
+	}
+	return o.path.String()
+}
+
+// makeUnsetDexJarPath returns an unset OptionalDexJarPath.
+func makeUnsetDexJarPath() OptionalDexJarPath {
+	return OptionalDexJarPath{isSet: false}
+}
+
+// makeDexJarPathFromOptionalPath returns an OptionalDexJarPath that is set with
+// the given OptionalPath, which may be valid or invalid.
+func makeDexJarPathFromOptionalPath(path android.OptionalPath) OptionalDexJarPath {
+	return OptionalDexJarPath{isSet: true, path: path}
+}
+
+// makeDexJarPathFromPath returns an OptionalDexJarPath that is set with the
+// valid given path. It returns an unset OptionalDexJarPath if the given path is
+// nil.
+func makeDexJarPathFromPath(path android.Path) OptionalDexJarPath {
+	if path == nil {
+		return makeUnsetDexJarPath()
+	}
+	return makeDexJarPathFromOptionalPath(android.OptionalPathForPath(path))
+}
+
 // Module contains the properties and members used by all java module types
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	android.SdkBase
+	android.BazelModuleBase
 
 	// Functionality common to Module and Import.
 	embeddableInModuleAndImport
@@ -310,7 +409,7 @@
 	implementationAndResourcesJar android.Path
 
 	// output file containing classes.dex and resources
-	dexJarFile android.Path
+	dexJarFile OptionalDexJarPath
 
 	// output file containing uninstrumented classes that will be instrumented by jacoco
 	jacocoReportClassesFile android.Path
@@ -326,6 +425,9 @@
 	// installed file for binary dependency
 	installFile android.Path
 
+	// installed file for hostdex copy
+	hostdexInstallFile android.InstallPath
+
 	// list of .java files and srcjars that was passed to javac
 	compiledJavaSrcs android.Paths
 	compiledSrcJars  android.Paths
@@ -352,9 +454,6 @@
 	// expanded Jarjar_rules
 	expandJarjarRules android.Path
 
-	// list of additional targets for checkbuild
-	additionalCheckedModules android.Paths
-
 	// Extra files generated by the module type to be added as java resources.
 	extraResources android.Paths
 
@@ -374,6 +473,7 @@
 
 	sdkVersion    android.SdkSpec
 	minSdkVersion android.SdkSpec
+	maxSdkVersion android.SdkSpec
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -382,7 +482,7 @@
 		return nil
 	}
 	if sdkVersion.Kind == android.SdkCorePlatform {
-		if useLegacyCorePlatformApiByName(j.BaseModuleName()) {
+		if useLegacyCorePlatformApi(ctx, j.BaseModuleName()) {
 			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
 		} else {
 			// Treat stable core platform as stable.
@@ -424,9 +524,9 @@
 		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
 		sdkVersionSpecified := sc.SdkVersion(ctx).Specified()
 		if usePlatformAPI && sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
+			ctx.PropertyErrorf("platform_apis", "This module has conflicting settings. sdk_version is not empty, which means this module cannot use platform APIs. However platform_apis is set to true.")
 		} else if !usePlatformAPI && !sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
+			ctx.PropertyErrorf("platform_apis", "This module has conflicting settings. sdk_version is empty, which means that this module is build against platform APIs. However platform_apis is not set to true")
 		}
 
 	}
@@ -531,6 +631,13 @@
 	return j.SdkVersion(ctx)
 }
 
+func (j *Module) MaxSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	maxSdkVersion := proptools.StringDefault(j.deviceProperties.Max_sdk_version, "")
+	// SdkSpecFrom returns SdkSpecPrivate for this, which may be confusing.
+	// TODO(b/208456999): ideally MaxSdkVersion should be an ApiLevel and not SdkSpec.
+	return android.SdkSpecFrom(ctx, maxSdkVersion)
+}
+
 func (j *Module) MinSdkVersionString() string {
 	return j.minSdkVersion.Raw
 }
@@ -643,6 +750,11 @@
 	} else if j.shouldInstrumentStatic(ctx) {
 		ctx.AddVariationDependencies(nil, staticLibTag, "jacocoagent")
 	}
+
+	if j.useCompose() {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), kotlinPluginTag,
+			"androidx.compose.compiler_compiler-hosted")
+	}
 }
 
 func hasSrcExt(srcs []string, ext string) bool {
@@ -701,6 +813,9 @@
 		flags = append(flags, "--transaction_names")
 	}
 
+	aidlMinSdkVersion := j.MinSdkVersion(ctx).ApiLevel.String()
+	flags = append(flags, "--min_sdk_version="+aidlMinSdkVersion)
+
 	return strings.Join(flags, " "), deps
 }
 
@@ -852,6 +967,10 @@
 	if flags.javaVersion.usesJavaModules() {
 		j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk9.Srcs...)
 	}
+	if ctx.Config().TargetsJava11() {
+		j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk11.Srcs...)
+	}
+
 	srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs)
 	if hasSrcExt(srcFiles.Strings(), ".proto") {
 		flags = protoFlags(ctx, &j.properties, &j.protoProperties, flags)
@@ -911,6 +1030,12 @@
 		if ctx.Device() {
 			kotlincFlags = append(kotlincFlags, "-no-jdk")
 		}
+
+		for _, plugin := range deps.kotlinPlugins {
+			kotlincFlags = append(kotlincFlags, "-Xplugin="+plugin.String())
+		}
+		flags.kotlincDeps = append(flags.kotlincDeps, deps.kotlinPlugins...)
+
 		if len(kotlincFlags) > 0 {
 			// optimization.
 			ctx.Variable(pctx, "kotlincFlags", strings.Join(kotlincFlags, " "))
@@ -966,7 +1091,7 @@
 	j.compiledSrcJars = srcJars
 
 	enableSharding := false
-	var headerJarFileWithoutJarjar android.Path
+	var headerJarFileWithoutDepsOrJarjar android.Path
 	if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !deps.disableTurbine {
 		if j.properties.Javac_shard_size != nil && *(j.properties.Javac_shard_size) > 0 {
 			enableSharding = true
@@ -976,7 +1101,7 @@
 			// allow for the use of annotation processors that do function correctly
 			// with sharding enabled. See: b/77284273.
 		}
-		headerJarFileWithoutJarjar, j.headerJarFile =
+		headerJarFileWithoutDepsOrJarjar, j.headerJarFile =
 			j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinJars)
 		if ctx.Failed() {
 			return
@@ -1005,7 +1130,9 @@
 		}
 
 		if enableSharding {
-			flags.classpath = append(flags.classpath, headerJarFileWithoutJarjar)
+			if headerJarFileWithoutDepsOrJarjar != nil {
+				flags.classpath = append(classpath{headerJarFileWithoutDepsOrJarjar}, flags.classpath...)
+			}
 			shardSize := int(*(j.properties.Javac_shard_size))
 			var shardSrcs []android.Paths
 			if len(uniqueSrcFiles) > 0 {
@@ -1173,10 +1300,25 @@
 
 	// Check package restrictions if necessary.
 	if len(j.properties.Permitted_packages) > 0 {
-		// Check packages and copy to package-checked file.
+		// Time stamp file created by the package check rule.
 		pkgckFile := android.PathForModuleOut(ctx, "package-check.stamp")
+
+		// Create a rule to copy the output jar to another path and add a validate dependency that
+		// will check that the jar only contains the permitted packages. The new location will become
+		// the output file of this module.
+		inputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "package-check", jarName).OutputPath
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  inputFile,
+			Output: outputFile,
+			// Make sure that any dependency on the output file will cause ninja to run the package check
+			// rule.
+			Validation: pkgckFile,
+		})
+
+		// Check packages and create a timestamp file when complete.
 		CheckJarPackages(ctx, pkgckFile, outputFile, j.properties.Permitted_packages)
-		j.additionalCheckedModules = append(j.additionalCheckedModules, pkgckFile)
 
 		if ctx.Failed() {
 			return
@@ -1254,12 +1396,13 @@
 			}
 
 			// Initialize the hiddenapi structure.
-			j.initHiddenAPI(ctx, dexOutputFile, j.implementationJarFile, j.dexProperties.Uncompress_dex)
+
+			j.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), j.implementationJarFile, j.dexProperties.Uncompress_dex)
 
 			// Encode hidden API flags in dex file, if needed.
 			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
 
-			j.dexJarFile = dexOutputFile
+			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 
 			// Dexpreopting
 			j.dexpreopt(ctx, dexOutputFile)
@@ -1269,7 +1412,7 @@
 			// There is no code to compile into a dex jar, make sure the resources are propagated
 			// to the APK if this is an app.
 			outputFile = implementationAndResourcesJar
-			j.dexJarFile = j.resourceJar
+			j.dexJarFile = makeDexJarPathFromPath(j.resourceJar)
 		}
 
 		if ctx.Failed() {
@@ -1325,6 +1468,10 @@
 	j.outputFile = outputFile.WithoutRel()
 }
 
+func (j *Module) useCompose() bool {
+	return android.InList("androidx.compose.runtime_runtime", j.properties.Static_libs)
+}
+
 // Returns a copy of the supplied flags, but with all the errorprone-related
 // fields copied to the regular build's fields.
 func enableErrorproneFlags(flags javaBuilderFlags) javaBuilderFlags {
@@ -1388,7 +1535,7 @@
 
 func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars android.Paths,
 	deps deps, flags javaBuilderFlags, jarName string,
-	extraJars android.Paths) (headerJar, jarjarHeaderJar android.Path) {
+	extraJars android.Paths) (headerJar, jarjarAndDepsHeaderJar android.Path) {
 
 	var jars android.Paths
 	if len(srcFiles) > 0 || len(srcJars) > 0 {
@@ -1399,6 +1546,7 @@
 			return nil, nil
 		}
 		jars = append(jars, turbineJar)
+		headerJar = turbineJar
 	}
 
 	jars = append(jars, extraJars...)
@@ -1412,20 +1560,19 @@
 	combinedJar := android.PathForModuleOut(ctx, "turbine-combined", jarName)
 	TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{},
 		false, nil, []string{"META-INF/TRANSITIVE"})
-	headerJar = combinedJar
-	jarjarHeaderJar = combinedJar
+	jarjarAndDepsHeaderJar = combinedJar
 
 	if j.expandJarjarRules != nil {
 		// Transform classes.jar into classes-jarjar.jar
 		jarjarFile := android.PathForModuleOut(ctx, "turbine-jarjar", jarName)
-		TransformJarJar(ctx, jarjarFile, headerJar, j.expandJarjarRules)
-		jarjarHeaderJar = jarjarFile
+		TransformJarJar(ctx, jarjarFile, jarjarAndDepsHeaderJar, j.expandJarjarRules)
+		jarjarAndDepsHeaderJar = jarjarFile
 		if ctx.Failed() {
 			return nil, nil
 		}
 	}
 
-	return headerJar, jarjarHeaderJar
+	return headerJar, jarjarAndDepsHeaderJar
 }
 
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
@@ -1455,7 +1602,7 @@
 	return android.Paths{j.implementationJarFile}
 }
 
-func (j *Module) DexJarBuildPath() android.Path {
+func (j *Module) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
@@ -1509,8 +1656,7 @@
 }
 
 // Implements android.ApexModule
-func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
-	sdkVersion android.ApiLevel) error {
+func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
 	sdkSpec := j.MinSdkVersion(ctx)
 	if !sdkSpec.Specified() {
 		return fmt.Errorf("min_sdk_version is not specified")
@@ -1755,6 +1901,8 @@
 				deps.kotlinStdlib = append(deps.kotlinStdlib, dep.HeaderJars...)
 			case kotlinAnnotationsTag:
 				deps.kotlinAnnotations = dep.HeaderJars
+			case kotlinPluginTag:
+				deps.kotlinPlugins = append(deps.kotlinPlugins, dep.ImplementationAndResourcesJars...)
 			case syspropPublicStubDepTag:
 				// This is a sysprop implementation library, forward the JavaInfoProvider from
 				// the corresponding sysprop public stub library as SyspropPublicStubInfoProvider.
@@ -1817,3 +1965,17 @@
 }
 
 var _ ModuleWithStem = (*Module)(nil)
+
+func (j *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	switch ctx.ModuleType() {
+	case "java_library", "java_library_host":
+		if lib, ok := ctx.Module().(*Library); ok {
+			javaLibraryBp2Build(ctx, lib)
+		}
+	case "java_binary_host":
+		if binary, ok := ctx.Module().(*Binary); ok {
+			javaBinaryHostBp2Build(ctx, binary)
+		}
+	}
+
+}
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 4108770..52ce77d 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -95,15 +95,6 @@
 	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
 		ctx.AddVariationDependencies(variations, tag, prebuiltName)
 		addedDep = true
-	} else if ctx.Config().AlwaysUsePrebuiltSdks() && len(variations) > 0 {
-		// TODO(b/179354495): Remove this code path once the Android build has been fully migrated to
-		//  use bootclasspath_fragment properly.
-		// Some prebuilt java_sdk_library modules do not yet have an APEX variations so try and add a
-		// dependency on the non-APEX variant.
-		if ctx.OtherModuleDependencyVariantExists(nil, prebuiltName) {
-			ctx.AddVariationDependencies(nil, tag, prebuiltName)
-			addedDep = true
-		}
 	}
 
 	// If no appropriate variant existing for this, so no dependency could be added, then it is an
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index f7561b4..bfe895c 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -89,7 +89,7 @@
 
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
 var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
-var _ android.SdkMemberTypeDependencyTag = bootclasspathFragmentContentDepTag
+var _ android.SdkMemberDependencyTag = bootclasspathFragmentContentDepTag
 var _ android.CopyDirectlyInAnyApexTag = bootclasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = bootclasspathFragmentContentDepTag
 
@@ -139,6 +139,74 @@
 	BootclasspathFragmentsDepsProperties
 }
 
+type SourceOnlyBootclasspathProperties struct {
+	Hidden_api struct {
+		// Contains prefixes of a package hierarchy that is provided solely by this
+		// bootclasspath_fragment.
+		//
+		// This affects the signature patterns file that is used to select the subset of monolithic
+		// hidden API flags. See split_packages property for more details.
+		Package_prefixes []string
+
+		// The list of split packages provided by this bootclasspath_fragment.
+		//
+		// A split package is one that contains classes which are provided by multiple
+		// bootclasspath_fragment modules.
+		//
+		// This defaults to "*" - which treats all packages as being split. A module that has no split
+		// packages must specify an empty list.
+		//
+		// This affects the signature patterns file that is generated by a bootclasspath_fragment and
+		// used to select the subset of monolithic hidden API flags against which the flags generated
+		// by the bootclasspath_fragment are compared.
+		//
+		// The signature patterns file selects the subset of monolithic hidden API flags using a number
+		// of patterns, i.e.:
+		// * The qualified name (including package) of an outermost class, e.g. java/lang/Character.
+		//   This selects all the flags for all the members of this class and any nested classes.
+		// * A package wildcard, e.g. java/lang/*. This selects all the flags for all the members of all
+		//   the classes in this package (but not in sub-packages).
+		// * A recursive package wildcard, e.g. java/**. This selects all the flags for all the members
+		//   of all the classes in this package and sub-packages.
+		//
+		// The signature patterns file is constructed as follows:
+		// * All the signatures are retrieved from the all-flags.csv file.
+		// * The member and inner class names are removed.
+		// * If a class is in a split package then that is kept, otherwise the class part is removed
+		//   and replaced with a wildcard, i.e. *.
+		// * If a package matches a package prefix then the package is removed.
+		// * All the package prefixes are added with a recursive wildcard appended to each, i.e. **.
+		// * The resulting patterns are sorted.
+		//
+		// So, by default (i.e. without specifying any package_prefixes or split_packages) the signature
+		// patterns is a list of class names, because there are no package packages and all packages are
+		// assumed to be split.
+		//
+		// If any split packages are specified then only those packages are treated as split and all
+		// other packages are treated as belonging solely to the bootclasspath_fragment and so they use
+		// wildcard package patterns.
+		//
+		// So, if an empty list of split packages is specified then the signature patterns file just
+		// includes a wildcard package pattern for every package provided by the bootclasspath_fragment.
+		//
+		// If split_packages are specified and a package that is split is not listed then it could lead
+		// to build failures as it will select monolithic flags that are generated by another
+		// bootclasspath_fragment to compare against the flags provided by this fragment. The latter
+		// will obviously not contain those flags and that can cause the comparison and build to fail.
+		//
+		// If any package prefixes are specified then any matching packages are removed from the
+		// signature patterns and replaced with a single recursive package pattern.
+		//
+		// It is not strictly necessary to specify either package_prefixes or split_packages as the
+		// defaults will produce a valid set of signature patterns. However, those patterns may include
+		// implementation details, e.g. names of implementation classes or packages, which will be
+		// exported to the sdk snapshot in the signature patterns file. That is something that should be
+		// avoided where possible. Specifying package_prefixes and split_packages allows those
+		// implementation details to be excluded from the snapshot.
+		Split_packages []string
+	}
+}
+
 type BootclasspathFragmentModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
@@ -147,6 +215,8 @@
 
 	properties bootclasspathFragmentProperties
 
+	sourceOnlyProperties SourceOnlyBootclasspathProperties
+
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
 }
@@ -180,7 +250,7 @@
 
 func bootclasspathFragmentFactory() android.Module {
 	m := &BootclasspathFragmentModule{}
-	m.AddProperties(&m.properties)
+	m.AddProperties(&m.properties, &m.sourceOnlyProperties)
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, BOOTCLASSPATH)
@@ -320,6 +390,13 @@
 	// Map from the base module name (without prebuilt_ prefix) of a fragment's contents module to the
 	// hidden API encoded dex jar path.
 	contentModuleDexJarPaths bootDexJarByModule
+
+	// Path to the image profile file on host (or empty, if profile is not generated).
+	profilePathOnHost android.Path
+
+	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
+	// needed.
+	profileInstallPathInApex string
 }
 
 func (i BootclasspathFragmentApexContentInfo) Modules() android.ConfiguredJarList {
@@ -348,6 +425,14 @@
 	}
 }
 
+func (i BootclasspathFragmentApexContentInfo) ProfilePathOnHost() android.Path {
+	return i.profilePathOnHost
+}
+
+func (i BootclasspathFragmentApexContentInfo) ProfileInstallPathInApex() string {
+	return i.profileInstallPathInApex
+}
+
 func (b *BootclasspathFragmentModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
 	if IsBootclasspathFragmentContentDepTag(tag) {
@@ -509,6 +594,11 @@
 
 	if imageConfig != nil {
 		info.modules = imageConfig.modules
+		global := dexpreopt.GetGlobalConfig(ctx)
+		if !global.DisableGenerateProfile {
+			info.profilePathOnHost = imageConfig.profilePathOnHost
+			info.profileInstallPathInApex = imageConfig.profileInstallPathInApex
+		}
 	}
 
 	info.bootImageFilesByArch = bootImageFilesByArch
@@ -546,6 +636,8 @@
 	// This is an exception to support end-to-end test for SdkExtensions, until such support exists.
 	if android.InList("test_framework-sdkextensions", possibleUpdatableModules) {
 		jars = jars.Append("com.android.sdkext", "test_framework-sdkextensions")
+	} else if android.InList("test_framework-apexd", possibleUpdatableModules) {
+		jars = jars.Append("com.android.apex.test_package", "test_framework-apexd")
 	} else if global.ApexBootJars.Len() != 0 && !android.IsModuleInVersionedSdk(ctx.Module()) {
 		unknown = android.RemoveListFromList(unknown, b.properties.Coverage.Contents)
 		_, unknown = android.RemoveFromList("core-icu4j", unknown)
@@ -590,7 +682,7 @@
 	// TODO(b/192868581): Remove once the source and prebuilts provide a signature patterns file of
 	//  their own.
 	if output.SignaturePatternsPath == nil {
-		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath)
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath, []string{"*"}, nil)
 	}
 
 	// Initialize a HiddenAPIInfo structure.
@@ -659,7 +751,20 @@
 func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
 	// Generate the rules to create the hidden API flags and update the supplied hiddenAPIInfo with the
 	// paths to the created files.
-	return hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+	output := hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+
+	// If the module specifies split_packages or package_prefixes then use those to generate the
+	// signature patterns.
+	splitPackages := b.sourceOnlyProperties.Hidden_api.Split_packages
+	packagePrefixes := b.sourceOnlyProperties.Hidden_api.Package_prefixes
+	if splitPackages != nil || packagePrefixes != nil {
+		if splitPackages == nil {
+			splitPackages = []string{"*"}
+		}
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath, splitPackages, packagePrefixes)
+	}
+
+	return output
 }
 
 // produceBootImageFiles builds the boot image files from the source if it is required.
@@ -767,14 +872,20 @@
 	// The path to the generated index.csv file.
 	Index_path android.OptionalPath
 
-	// The path to the generated signature-patterns.csv file.
-	Signature_patterns_path android.OptionalPath
-
 	// The path to the generated stub-flags.csv file.
-	Stub_flags_path android.OptionalPath
+	Stub_flags_path android.OptionalPath `supported_build_releases:"S"`
 
 	// The path to the generated all-flags.csv file.
-	All_flags_path android.OptionalPath
+	All_flags_path android.OptionalPath `supported_build_releases:"S"`
+
+	// The path to the generated signature-patterns.csv file.
+	Signature_patterns_path android.OptionalPath `supported_build_releases:"T+"`
+
+	// The path to the generated filtered-stub-flags.csv file.
+	Filtered_stub_flags_path android.OptionalPath `supported_build_releases:"T+"`
+
+	// The path to the generated filtered-flags.csv file.
+	Filtered_flags_path android.OptionalPath `supported_build_releases:"T+"`
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -793,10 +904,13 @@
 	b.Metadata_path = android.OptionalPathForPath(hiddenAPIInfo.MetadataPath)
 	b.Index_path = android.OptionalPathForPath(hiddenAPIInfo.IndexPath)
 
-	b.Signature_patterns_path = android.OptionalPathForPath(hiddenAPIInfo.SignaturePatternsPath)
 	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
 	b.All_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AllFlagsPath)
 
+	b.Signature_patterns_path = android.OptionalPathForPath(hiddenAPIInfo.SignaturePatternsPath)
+	b.Filtered_stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredStubFlagsPath)
+	b.Filtered_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredFlagsPath)
+
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
 	b.Core_platform_stub_libs = module.properties.Core_platform_api.Stub_libs
@@ -861,9 +975,13 @@
 	copyOptionalPath(b.Annotation_flags_path, "annotation_flags")
 	copyOptionalPath(b.Metadata_path, "metadata")
 	copyOptionalPath(b.Index_path, "index")
-	copyOptionalPath(b.Signature_patterns_path, "signature_patterns")
+
 	copyOptionalPath(b.Stub_flags_path, "stub_flags")
 	copyOptionalPath(b.All_flags_path, "all_flags")
+
+	copyOptionalPath(b.Signature_patterns_path, "signature_patterns")
+	copyOptionalPath(b.Filtered_stub_flags_path, "filtered_stub_flags")
+	copyOptionalPath(b.Filtered_flags_path, "filtered_flags")
 }
 
 var _ android.SdkMemberType = (*bootclasspathFragmentMemberType)(nil)
@@ -889,6 +1007,12 @@
 
 		// The path to the all-flags.csv file created by the bootclasspath_fragment.
 		All_flags *string `android:"path"`
+
+		// The path to the filtered-stub-flags.csv file created by the bootclasspath_fragment.
+		Filtered_stub_flags *string `android:"path"`
+
+		// The path to the filtered-flags.csv file created by the bootclasspath_fragment.
+		Filtered_flags *string `android:"path"`
 	}
 }
 
@@ -915,9 +1039,9 @@
 
 // produceHiddenAPIOutput returns a path to the prebuilt all-flags.csv or nil if none is specified.
 func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
-	pathForOptionalSrc := func(src *string) android.Path {
+	pathForOptionalSrc := func(src *string, defaultPath android.Path) android.Path {
 		if src == nil {
-			return nil
+			return defaultPath
 		}
 		return android.PathForModuleSrc(ctx, *src)
 	}
@@ -938,13 +1062,19 @@
 			AnnotationFlagsPath:   pathForSrc("hidden_api.annotation_flags", module.prebuiltProperties.Hidden_api.Annotation_flags),
 			MetadataPath:          pathForSrc("hidden_api.metadata", module.prebuiltProperties.Hidden_api.Metadata),
 			IndexPath:             pathForSrc("hidden_api.index", module.prebuiltProperties.Hidden_api.Index),
-			SignaturePatternsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Signature_patterns),
-			StubFlagsPath:         pathForSrc("hidden_api.stub_flags", module.prebuiltProperties.Hidden_api.Stub_flags),
-			AllFlagsPath:          pathForSrc("hidden_api.all_flags", module.prebuiltProperties.Hidden_api.All_flags),
+			SignaturePatternsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Signature_patterns, nil),
+			// TODO: Temporarily handle stub_flags/all_flags properties until prebuilts have been updated.
+			StubFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags, nil),
+			AllFlagsPath:  pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags, nil),
 		},
+
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
 
+	// TODO: Temporarily fallback to stub_flags/all_flags properties until prebuilts have been updated.
+	output.FilteredStubFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_stub_flags, output.StubFlagsPath)
+	output.FilteredFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_flags, output.AllFlagsPath)
+
 	return &output
 }
 
@@ -954,23 +1084,11 @@
 		return nil
 	}
 
-	var deapexerModule android.Module
-	ctx.VisitDirectDeps(func(module android.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			deapexerModule = module
-		}
-	})
-
-	if deapexerModule == nil {
-		// This should never happen as a variant for a prebuilt_apex is only created if the
-		// deapexer module has been configured to export the dex implementation jar for this module.
-		ctx.ModuleErrorf("internal error: module does not depend on a `deapexer` module")
-		return nil
+	di := android.FindDeapexerProviderForModule(ctx)
+	if di == nil {
+		return nil // An error has been reported by FindDeapexerProviderForModule.
 	}
 
-	di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
 	files := bootImageFilesByArch{}
 	for _, variant := range imageConfig.apexVariants() {
 		arch := variant.target.Arch.ArchType
diff --git a/java/builder.go b/java/builder.go
index ea011b8..e64a61f 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -120,14 +120,14 @@
 		"extractMatchingApks",
 		blueprint.RuleParams{
 			Command: `rm -rf "$out" && ` +
-				`${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` +
+				`${config.ExtractApksCmd} -o "${out}" -zip "${zip}" -allow-prereleased=${allow-prereleased} ` +
 				`-sdk-version=${sdk-version} -abis=${abis} ` +
 				`--screen-densities=${screen-densities} --stem=${stem} ` +
 				`-apkcerts=${apkcerts} -partition=${partition} ` +
 				`${in}`,
 			CommandDeps: []string{"${config.ExtractApksCmd}"},
 		},
-		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition")
+		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition", "zip")
 
 	turbine, turbineRE = pctx.RemoteStaticRules("turbine",
 		blueprint.RuleParams{
@@ -263,6 +263,7 @@
 
 	kotlincFlags     string
 	kotlincClasspath classpath
+	kotlincDeps      android.Paths
 
 	proto android.ProtoFlags
 }
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index f63d81d..ca27528 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -25,7 +25,7 @@
 	"android/soong/android"
 )
 
-// Build rules and utilities to generate individual packages/modules/SdkExtensions/proto/classpaths.proto
+// Build rules and utilities to generate individual packages/modules/common/proto/classpaths.proto
 // config files based on build configuration to embed into /system and /apex on a device.
 //
 // See `derive_classpath` service that reads the configs at runtime and defines *CLASSPATH variables
@@ -34,14 +34,15 @@
 type classpathType int
 
 const (
-	// Matches definition in packages/modules/SdkExtensions/proto/classpaths.proto
+	// Matches definition in packages/modules/common/proto/classpaths.proto
 	BOOTCLASSPATH classpathType = iota
 	DEX2OATBOOTCLASSPATH
 	SYSTEMSERVERCLASSPATH
+	STANDALONE_SYSTEMSERVER_JARS
 )
 
 func (c classpathType) String() string {
-	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH"}[c]
+	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"}[c]
 }
 
 type classpathFragmentProperties struct {
@@ -84,11 +85,10 @@
 
 // Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto
 type classpathJar struct {
-	path      string
-	classpath classpathType
-	// TODO(satayev): propagate min/max sdk versions for the jars
-	minSdkVersion int32
-	maxSdkVersion int32
+	path          string
+	classpath     classpathType
+	minSdkVersion string
+	maxSdkVersion string
 }
 
 // gatherPossibleApexModuleNamesAndStems returns a set of module and stem names from the
@@ -120,10 +120,32 @@
 	jars := make([]classpathJar, 0, len(paths)*len(classpaths))
 	for i := 0; i < len(paths); i++ {
 		for _, classpathType := range classpaths {
-			jars = append(jars, classpathJar{
+			jar := classpathJar{
 				classpath: classpathType,
 				path:      paths[i],
+			}
+			ctx.VisitDirectDepsIf(func(m android.Module) bool {
+				return m.Name() == configuredJars.Jar(i)
+			}, func(m android.Module) {
+				if s, ok := m.(*SdkLibrary); ok {
+					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
+					if s.minSdkVersion.Specified() {
+						if s.minSdkVersion.ApiLevel.IsCurrent() {
+							jar.minSdkVersion = ctx.Config().LatestPreviewApiLevel().String()
+						} else {
+							jar.minSdkVersion = s.minSdkVersion.ApiLevel.String()
+						}
+					}
+					if s.maxSdkVersion.Specified() {
+						if s.maxSdkVersion.ApiLevel.IsCurrent() {
+							jar.maxSdkVersion = ctx.Config().LatestPreviewApiLevel().String()
+						} else {
+							jar.maxSdkVersion = s.maxSdkVersion.ApiLevel.String()
+						}
+					}
+				}
 			})
+			jars = append(jars, jar)
 		}
 	}
 	return jars
@@ -136,15 +158,15 @@
 		c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
 		c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
 
-		generatedJson := android.PathForModuleOut(ctx, outputFilename+".json")
-		writeClasspathsJson(ctx, generatedJson, jars)
+		generatedTextproto := android.PathForModuleOut(ctx, outputFilename+".textproto")
+		writeClasspathsTextproto(ctx, generatedTextproto, jars)
 
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			BuiltTool("conv_classpaths_proto").
 			Flag("encode").
-			Flag("--format=json").
-			FlagWithInput("--input=", generatedJson).
+			Flag("--format=textproto").
+			FlagWithInput("--input=", generatedTextproto).
 			FlagWithOutput("--output=", c.outputFilepath)
 
 		rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
@@ -159,24 +181,18 @@
 	ctx.SetProvider(ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
 }
 
-func writeClasspathsJson(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
+func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
 	var content strings.Builder
-	fmt.Fprintf(&content, "{\n")
-	fmt.Fprintf(&content, "\"jars\": [\n")
-	for idx, jar := range jars {
-		fmt.Fprintf(&content, "{\n")
 
-		fmt.Fprintf(&content, "\"path\": \"%s\",\n", jar.path)
-		fmt.Fprintf(&content, "\"classpath\": \"%s\"\n", jar.classpath)
-
-		if idx < len(jars)-1 {
-			fmt.Fprintf(&content, "},\n")
-		} else {
-			fmt.Fprintf(&content, "}\n")
-		}
+	for _, jar := range jars {
+		fmt.Fprintf(&content, "jars {\n")
+		fmt.Fprintf(&content, "path: \"%s\"\n", jar.path)
+		fmt.Fprintf(&content, "classpath: %s\n", jar.classpath)
+		fmt.Fprintf(&content, "min_sdk_version: \"%s\"\n", jar.minSdkVersion)
+		fmt.Fprintf(&content, "max_sdk_version: \"%s\"\n", jar.maxSdkVersion)
+		fmt.Fprintf(&content, "}\n")
 	}
-	fmt.Fprintf(&content, "]\n")
-	fmt.Fprintf(&content, "}\n")
+
 	android.WriteFileRule(ctx, output, content.String())
 }
 
@@ -188,7 +204,7 @@
 		OutputFile: android.OptionalPathForPath(c.outputFilepath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
 			},
 		},
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index b198c24..cf39746 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -28,6 +28,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+dist_targets = [
+    "sdk",
+    "win_sdk",
+]
+
 java_library {
     name: "core.current.stubs",
     visibility: ["//visibility:public"],
@@ -40,15 +45,16 @@
     system_modules: "none",
 
     dist: {
-        targets: [
-            "sdk",
-            "win_sdk",
-        ],
+        targets: dist_targets,
     },
 }
 
 // Distributed with the SDK for turning into system modules to compile apps
 // against.
+//
+// Also, produces dist files that are used by the
+// prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
+// directory.
 java_library {
     name: "core-current-stubs-for-system-modules",
     visibility: ["//development/sdk"],
@@ -65,18 +71,22 @@
     ],
     sdk_version: "none",
     system_modules: "none",
-    dist: {
-        dest: "core-for-system-modules.jar",
-        targets: [
-            "sdk",
-            "win_sdk",
-        ],
-    },
+    dists: [
+        {
+            // Legacy dist location for the public file.
+            dest: "core-for-system-modules.jar",
+            targets: dist_targets,
+        },
+        {
+            dest: "system-modules/public/core-for-system-modules.jar",
+            targets: dist_targets,
+        },
+    ],
 }
 
 // Used when compiling higher-level code against core.current.stubs.
 java_system_modules {
-    name: "core-current-stubs-system-modules",
+    name: "core-public-stubs-system-modules",
     visibility: ["//visibility:public"],
     libs: [
         "core-current-stubs-for-system-modules",
@@ -103,10 +113,13 @@
     visibility: ["//visibility:private"],
 }
 
-// Used when compiling higher-level code with sdk_version "module_current"
-java_system_modules {
-    name: "core-module-lib-stubs-system-modules",
-    libs: [
+// Produces a dist file that is used by the
+// prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
+// directory.
+java_library {
+    name: "core-module-lib-stubs-for-system-modules",
+    visibility: ["//visibility:private"],
+    static_libs: [
         "core.module_lib.stubs",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
@@ -117,6 +130,20 @@
         // See http://b/123891440.
         "core-generated-annotation-stubs",
     ],
+    sdk_version: "none",
+    system_modules: "none",
+    dist: {
+        dest: "system-modules/module-lib/core-for-system-modules.jar",
+        targets: dist_targets,
+    },
+}
+
+// Used when compiling higher-level code with sdk_version "module_current"
+java_system_modules {
+    name: "core-module-lib-stubs-system-modules",
+    libs: [
+        "core-module-lib-stubs-for-system-modules",
+    ],
     visibility: ["//visibility:public"],
 }
 
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 39fb04a..4abdcc6 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -118,7 +118,7 @@
 		TransformJarsToJar(ctx, outputFile, "combine", d.implementationAndResourceJars,
 			android.OptionalPath{}, false, nil, nil)
 		d.combinedImplementationJar = outputFile
-	} else {
+	} else if len(d.implementationAndResourceJars) == 1 {
 		d.combinedImplementationJar = d.implementationAndResourceJars[0]
 	}
 
@@ -127,7 +127,7 @@
 		TransformJarsToJar(ctx, outputFile, "turbine combine", d.headerJars,
 			android.OptionalPath{}, false, nil, []string{"META-INF/TRANSITIVE"})
 		d.combinedHeaderJar = outputFile
-	} else {
+	} else if len(d.headerJars) == 1 {
 		d.combinedHeaderJar = d.headerJars[0]
 	}
 
@@ -174,7 +174,9 @@
 	return android.AndroidMkData{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(d.combinedImplementationJar),
-		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		// Make does not support Windows Java modules
+		Disabled: d.Os() == android.Windows,
+		Include:  "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		Extra: []android.AndroidMkExtraFunc{
 			func(w io.Writer, outputFile android.Path) {
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
diff --git a/java/dex.go b/java/dex.go
index 667800f..8045b5c 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -32,6 +32,9 @@
 	// list of module-specific flags that will be used for dex compiles
 	Dxflags []string `android:"arch_variant"`
 
+	// A list of files containing rules that specify the classes to keep in the main dex file.
+	Main_dex_rules []string `android:"path"`
+
 	Optimize struct {
 		// If false, disable all optimization.  Defaults to true for android_app and android_test
 		// modules, false for java_library and java_test modules.
@@ -164,13 +167,20 @@
 	}, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir",
 		"r8Flags", "zipFlags", "tmpJar"}, []string{"implicits"})
 
-func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string {
-	flags := d.dexProperties.Dxflags
+func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
+	minSdkVersion android.SdkSpec) (flags []string, deps android.Paths) {
+
+	flags = d.dexProperties.Dxflags
 	// Translate all the DX flags to D8 ones until all the build files have been migrated
 	// to D8 flags. See: b/69377755
 	flags = android.RemoveListFromList(flags,
 		[]string{"--core-library", "--dex", "--multi-dex"})
 
+	for _, f := range android.PathsForModuleSrc(ctx, d.dexProperties.Main_dex_rules) {
+		flags = append(flags, "--main-dex-rules", f.String())
+		deps = append(deps, f)
+	}
+
 	if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" {
 		flags = append(flags, "--debug")
 	}
@@ -187,7 +197,7 @@
 	}
 
 	flags = append(flags, "--min-api "+strconv.Itoa(effectiveVersion.FinalOrFutureInt()))
-	return flags
+	return flags, deps
 }
 
 func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) {
@@ -286,7 +296,7 @@
 		zipFlags += " -L 0"
 	}
 
-	commonFlags := d.dexCommonFlags(ctx, minSdkVersion)
+	commonFlags, commonDeps := d.dexCommonFlags(ctx, minSdkVersion)
 
 	useR8 := d.effectiveOptimizeEnabled()
 	if useR8 {
@@ -298,6 +308,7 @@
 		proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip")
 		d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
 		r8Flags, r8Deps := d.r8Flags(ctx, flags)
+		r8Deps = append(r8Deps, commonDeps...)
 		rule := r8
 		args := map[string]string{
 			"r8Flags":     strings.Join(append(commonFlags, r8Flags...), " "),
@@ -324,6 +335,7 @@
 		})
 	} else {
 		d8Flags, d8Deps := d8Flags(flags)
+		d8Deps = append(d8Deps, commonDeps...)
 		rule := d8
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") {
 			rule = d8RE
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 0faae36..e9bc518 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -15,13 +15,46 @@
 package java
 
 import (
+	"path/filepath"
+	"strings"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
 
-type dexpreopterInterface interface {
+type DexpreopterInterface interface {
 	IsInstallable() bool // Structs that embed dexpreopter must implement this.
 	dexpreoptDisabled(ctx android.BaseModuleContext) bool
+	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
+	AndroidMkEntriesForApex() []android.AndroidMkEntries
+}
+
+type dexpreopterInstall struct {
+	// A unique name to distinguish an output from others for the same java library module. Usually in
+	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
+	name string
+
+	// The name of the input java module.
+	moduleName string
+
+	// The path to the dexpreopt output on host.
+	outputPathOnHost android.Path
+
+	// The directory on the device for the output to install to.
+	installDirOnDevice android.InstallPath
+
+	// The basename (the last segment of the path) for the output to install as.
+	installFileOnDevice string
+}
+
+// The full module name of the output in the makefile.
+func (install *dexpreopterInstall) FullModuleName() string {
+	return install.moduleName + install.SubModuleName()
+}
+
+// The sub-module name of the output in the makefile (the name excluding the java module name).
+func (install *dexpreopterInstall) SubModuleName() string {
+	return "-dexpreopt-" + install.name
 }
 
 type dexpreopter struct {
@@ -33,13 +66,16 @@
 	isApp               bool
 	isTest              bool
 	isPresignedPrebuilt bool
+	preventInstall      bool
 
 	manifestFile        android.Path
 	statusFile          android.WritablePath
 	enforceUsesLibs     bool
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
-	builtInstalled string
+	// See the `dexpreopt` function for details.
+	builtInstalled        string
+	builtInstalledForApex []dexpreopterInstall
 
 	// The config is used for two purposes:
 	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
@@ -74,14 +110,24 @@
 	dexpreopt.DexpreoptRunningInSoong = true
 }
 
+func isApexVariant(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return !apexInfo.IsForPlatform()
+}
+
+func forPrebuiltApex(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return apexInfo.ForPrebuiltApex
+}
+
+func moduleName(ctx android.BaseModuleContext) string {
+	// Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not
+	// expected by dexpreopter.
+	return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
+}
+
 func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool {
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	if global.DisablePreopt {
-		return true
-	}
-
-	if inList(ctx.ModuleName(), global.DisablePreoptModules) {
+	if !ctx.Device() {
 		return true
 	}
 
@@ -93,36 +139,80 @@
 		return true
 	}
 
-	if !ctx.Module().(dexpreopterInterface).IsInstallable() {
+	// If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be
+	// dexpreopted.
+	if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) {
 		return true
 	}
 
-	if ctx.Host() {
+	if !android.IsModulePreferred(ctx.Module()) {
 		return true
 	}
 
-	// Don't preopt APEX variant module
-	if apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo); !apexInfo.IsForPlatform() {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	if global.DisablePreopt {
 		return true
 	}
 
+	if inList(moduleName(ctx), global.DisablePreoptModules) {
+		return true
+	}
+
+	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() {
+			return true
+		}
+	} else {
+		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
+		if isApexSystemServerJar {
+			return true
+		}
+	}
+
 	// TODO: contains no java code
 
 	return false
 }
 
 func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) {
-	if d, ok := ctx.Module().(dexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
+	if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
 		return
 	}
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
-func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
+	return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+}
+
+// Returns the install path of the dex jar of a module.
+//
+// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
+// than the `name` in the path `/apex/<name>` as suggested in its comment.
+//
+// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a
+// system server jar, which is fine because we currently only preopt system server jars for APEXes.
+func (d *dexpreopter) getInstallPath(
+	ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, moduleName(ctx))
+		return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/"))
+	}
+	if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) &&
+		filepath.Base(defaultInstallPath.PartitionDir()) != "apex" {
+		ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt")
+	}
+	return defaultInstallPath
 }
 
 func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
 	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
 	// the dexpreopter struct hasn't been fully initialized before we're called,
 	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
@@ -133,7 +223,7 @@
 
 	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
 
-	providesUsesLib := ctx.ModuleName()
+	providesUsesLib := moduleName(ctx)
 	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
 		name := ulib.ProvidesUsesLib()
 		if name != nil {
@@ -147,17 +237,14 @@
 		return
 	}
 
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	isSystemServerJar := global.SystemServerJars.ContainsJar(ctx.ModuleName())
+	isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
 
 	bootImage := defaultBootImageConfig(ctx)
 	if global.UseArtImage {
 		bootImage = artBootImageConfig(ctx)
 	}
 
-	// System server jars are an exception: they are dexpreopted without updatable bootclasspath.
-	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp && !isSystemServerJar)
+	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
 
 	targets := ctx.MultiTargets()
 	if len(targets) == 0 {
@@ -199,15 +286,15 @@
 			profileIsTextListing = true
 		} else if global.ProfileDir != "" {
 			profileClassListing = android.ExistentPathForSource(ctx,
-				global.ProfileDir, ctx.ModuleName()+".prof")
+				global.ProfileDir, moduleName(ctx)+".prof")
 		}
 	}
 
 	// Full dexpreopt config, used to create dexpreopt build rules.
 	dexpreoptConfig := &dexpreopt.ModuleConfig{
-		Name:            ctx.ModuleName(),
+		Name:            moduleName(ctx),
 		DexLocation:     dexLocation,
-		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
+		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath,
 		DexPath:         dexJarFile,
 		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
 		UncompressedDex: d.uncompressedDex,
@@ -256,5 +343,59 @@
 
 	dexpreoptRule.Build("dexpreopt", "dexpreopt")
 
-	d.builtInstalled = dexpreoptRule.Installs().String()
+	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+
+	for _, install := range dexpreoptRule.Installs() {
+		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
+		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+		installBase := filepath.Base(install.To)
+		arch := filepath.Base(installDir)
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+
+		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,
+			})
+		} else if !d.preventInstall {
+			ctx.InstallFile(installPath, installBase, install.From)
+		}
+	}
+
+	if !isApexSystemServerJar {
+		d.builtInstalled = dexpreoptRule.Installs().String()
+	}
+}
+
+func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
+	return d.builtInstalledForApex
+}
+
+func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
+	var entries []android.AndroidMkEntries
+	for _, install := range d.builtInstalledForApex {
+		install := install
+		entries = append(entries, android.AndroidMkEntries{
+			Class:      "ETC",
+			SubName:    install.SubModuleName(),
+			OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
+			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+					entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String())
+					entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
+					entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
+				},
+			},
+		})
+	}
+	return entries
 }
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 946092c..c599c4d 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -16,7 +16,6 @@
 
 import (
 	"path/filepath"
-	"sort"
 	"strings"
 
 	"android/soong/android"
@@ -257,6 +256,10 @@
 	// Subdirectory where the image files on device are installed.
 	installDirOnDevice string
 
+	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
+	// needed.
+	profileInstallPathInApex string
+
 	// A list of (location, jar) pairs for the Java modules in this image.
 	modules android.ConfiguredJarList
 
@@ -273,6 +276,9 @@
 	// Rules which should be used in make to install the outputs.
 	profileInstalls android.RuleBuilderInstalls
 
+	// Path to the image profile file on host (or empty, if profile is not generated).
+	profilePathOnHost android.Path
+
 	// Target-dependent fields.
 	variants []*bootImageVariant
 }
@@ -500,8 +506,18 @@
 		dst := dstBootJarsByModule[name]
 
 		if src == nil {
+			// A dex boot jar should be provided by the source java module. It needs to be installable or
+			// have compile_dex=true - cf. assignments to java.Module.dexJarFile.
+			//
+			// However, the source java module may be either replaced or overridden (using prefer:true) by
+			// a prebuilt java module with the same name. In that case the dex boot jar needs to be
+			// provided by the corresponding prebuilt APEX module. That APEX is the one that refers
+			// through a exported_(boot|systemserver)classpath_fragments property to a
+			// prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt
+			// java module in the contents property. If that chain is broken then this dependency will
+			// fail.
 			if !ctx.Config().AllowMissingDependencies() {
-				ctx.ModuleErrorf("module %s does not provide a dex boot jar", name)
+				ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name)
 			} else {
 				ctx.AddMissingDependencies([]string{name})
 			}
@@ -626,7 +642,6 @@
 		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx)
 
 	if profile != nil {
-		cmd.FlagWithArg("--compiler-filter=", "speed-profile")
 		cmd.FlagWithInput("--profile-file=", profile)
 	}
 
@@ -771,11 +786,14 @@
 		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
 		FlagWithOutput("--reference-profile-file=", profile)
 
-	rule.Install(profile, "/system/etc/boot-image.prof")
+	if image == defaultBootImageConfig(ctx) {
+		rule.Install(profile, "/system/etc/boot-image.prof")
+		image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+	}
 
 	rule.Build("bootJarsProfile", "profile boot jars")
 
-	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+	image.profilePathOnHost = profile
 
 	return profile
 }
@@ -812,40 +830,6 @@
 	return profile
 }
 
-// generateUpdatableBcpPackagesRule generates the rule to create the updatable-bcp-packages.txt file
-// and returns a path to the generated file.
-func generateUpdatableBcpPackagesRule(ctx android.ModuleContext, image *bootImageConfig, apexModules []android.Module) android.WritablePath {
-	// Collect `permitted_packages` for updatable boot jars.
-	var updatablePackages []string
-	for _, module := range apexModules {
-		if j, ok := module.(PermittedPackagesForUpdatableBootJars); ok {
-			pp := j.PermittedPackagesForUpdatableBootJars()
-			if len(pp) > 0 {
-				updatablePackages = append(updatablePackages, pp...)
-			} else {
-				ctx.OtherModuleErrorf(module, "Missing permitted_packages")
-			}
-		}
-	}
-
-	// Sort updatable packages to ensure deterministic ordering.
-	sort.Strings(updatablePackages)
-
-	updatableBcpPackagesName := "updatable-bcp-packages.txt"
-	updatableBcpPackages := image.dir.Join(ctx, updatableBcpPackagesName)
-
-	// WriteFileRule automatically adds the last end-of-line.
-	android.WriteFileRule(ctx, updatableBcpPackages, strings.Join(updatablePackages, "\n"))
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Install(updatableBcpPackages, "/system/etc/"+updatableBcpPackagesName)
-	// TODO: Rename `profileInstalls` to `extraInstalls`?
-	// Maybe even move the field out of the bootImageConfig into some higher level type?
-	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
-
-	return updatableBcpPackages
-}
-
 func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) {
 	var allPhonies android.Paths
 	for _, image := range image.variants {
diff --git a/java/dexpreopt_check.go b/java/dexpreopt_check.go
new file mode 100644
index 0000000..149cbb7
--- /dev/null
+++ b/java/dexpreopt_check.go
@@ -0,0 +1,97 @@
+// Copyright 2021 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 java
+
+import (
+	"strings"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/pathtools"
+)
+
+func init() {
+	RegisterDexpreoptCheckBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterDexpreoptCheckBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonModuleType("dexpreopt_systemserver_check", dexpreoptSystemserverCheckFactory)
+}
+
+// A build-time check to verify if all compilation artifacts of system server jars are installed
+// into the system image. When the check fails, it means that dexpreopting is not working for some
+// system server jars and needs to be fixed.
+// This singleton module generates a list of the paths to the artifacts based on
+// PRODUCT_SYSTEM_SERVER_JARS and PRODUCT_APEX_SYSTEM_SERVER_JARS, and passes it to Make via a
+// variable. Make will then do the actual check.
+// Currently, it only checks artifacts of modules defined in Soong. Artifacts of modules defined in
+// Makefile are generated by a script generated by dexpreopt_gen, and their existence is unknown to
+// Make and Ninja.
+type dexpreoptSystemserverCheck struct {
+	android.SingletonModuleBase
+
+	// Mapping from the module name to the install paths to the compilation artifacts.
+	artifactsByModuleName map[string][]string
+
+	// The install paths to the compilation artifacts.
+	artifacts []string
+}
+
+func dexpreoptSystemserverCheckFactory() android.SingletonModule {
+	m := &dexpreoptSystemserverCheck{}
+	m.artifactsByModuleName = make(map[string][]string)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+func getInstallPath(ctx android.ModuleContext, location string) android.InstallPath {
+	return android.PathForModuleInPartitionInstall(
+		ctx, "", strings.TrimPrefix(location, "/"))
+}
+
+func (m *dexpreoptSystemserverCheck) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	targets := ctx.Config().Targets[android.Android]
+
+	// The check should be skipped on unbundled builds because system server jars are not preopted on
+	// unbundled builds since the artifacts are installed into the system image, not the APEXes.
+	if global.DisablePreopt || len(targets) == 0 || ctx.Config().UnbundledBuild() {
+		return
+	}
+
+	// TODO(b/203198541): Check all system server jars.
+	systemServerJars := global.AllSystemServerClasspathJars(ctx)
+	for _, jar := range systemServerJars.CopyOfJars() {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, jar)
+		odexLocation := dexpreopt.ToOdexPath(dexLocation, targets[0].Arch.ArchType)
+		odexPath := getInstallPath(ctx, odexLocation)
+		vdexPath := getInstallPath(ctx, pathtools.ReplaceExtension(odexLocation, "vdex"))
+		m.artifactsByModuleName[jar] = []string{odexPath.String(), vdexPath.String()}
+	}
+}
+
+func (m *dexpreoptSystemserverCheck) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+	// Only keep modules defined in Soong.
+	ctx.VisitAllModules(func(module android.Module) {
+		if artifacts, ok := m.artifactsByModuleName[module.Name()]; ok {
+			m.artifacts = append(m.artifacts, artifacts...)
+		}
+	})
+}
+
+func (m *dexpreoptSystemserverCheck) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("DEXPREOPT_SYSTEMSERVER_ARTIFACTS", strings.Join(m.artifacts, " "))
+}
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 415a1d4..26c1105 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -56,22 +56,20 @@
 		artModules := global.ArtApexJars
 		frameworkModules := global.BootJars.RemoveList(artModules)
 
-		artDirOnHost := "apex/art_boot_images/javalib"
-		artDirOnDevice := "apex/com.android.art/javalib"
-		frameworkSubdir := "system/framework"
-
 		// ART config for the primary boot image in the ART apex.
 		// It includes the Core Libraries.
 		artCfg := bootImageConfig{
-			name:               artBootImageName,
-			stem:               "boot",
-			installDirOnHost:   artDirOnHost,
-			installDirOnDevice: artDirOnDevice,
-			modules:            artModules,
+			name:                     artBootImageName,
+			stem:                     "boot",
+			installDirOnHost:         "apex/art_boot_images/javalib",
+			installDirOnDevice:       "apex/com.android.art/javalib",
+			profileInstallPathInApex: "etc/boot-image.prof",
+			modules:                  artModules,
 		}
 
 		// Framework config for the boot image extension.
 		// It includes framework libraries and depends on the ART config.
+		frameworkSubdir := "system/framework"
 		frameworkCfg := bootImageConfig{
 			extends:            &artCfg,
 			name:               frameworkBootImageName,
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index 8dc7b79..1c1070a 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"runtime"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -24,11 +25,17 @@
 	"android/soong/dexpreopt"
 )
 
+func init() {
+	RegisterFakeRuntimeApexMutator()
+}
+
 func TestDexpreoptEnabled(t *testing.T) {
 	tests := []struct {
-		name    string
-		bp      string
-		enabled bool
+		name        string
+		bp          string
+		moduleName  string
+		apexVariant bool
+		enabled     bool
 	}{
 		{
 			name: "app",
@@ -148,13 +155,81 @@
 				}`,
 			enabled: true,
 		},
+		{
+			name: "apex variant",
+			bp: `
+				java_library {
+					name: "foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			apexVariant: true,
+			enabled:     false,
+		},
+		{
+			name: "apex variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "apex variant of prebuilt apex system server jar",
+			bp: `
+				java_library {
+					name: "prebuilt_service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "prebuilt_service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "platform variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: false,
+			enabled:     false,
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx, _ := testJava(t, test.bp)
+			preparers := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithFakeApexMutator,
+				dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+			)
 
-			dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt")
+			result := preparers.RunTestWithBp(t, test.bp)
+			ctx := result.TestContext
+
+			moduleName := "foo"
+			if test.moduleName != "" {
+				moduleName = test.moduleName
+			}
+
+			variant := "android_common"
+			if test.apexVariant {
+				variant += "_apex1000"
+			}
+
+			dexpreopt := ctx.ModuleForTests(moduleName, variant).MaybeRule("dexpreopt")
 			enabled := dexpreopt.Rule != nil
 
 			if enabled != test.enabled {
@@ -220,3 +295,145 @@
 	testDex2oatToolDep(true, true, true, prebuiltDex2oatPath)
 	testDex2oatToolDep(false, true, false, prebuiltDex2oatPath)
 }
+
+func TestDexpreoptBuiltInstalledForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+	library := module.Module().(*Library)
+
+	installs := library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 2, len(installs))
+
+	android.AssertStringEquals(t, "installs[0] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[0] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].SubModuleName())
+
+	android.AssertStringEquals(t, "installs[1] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[1] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].SubModuleName())
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+	library = module.Module().(*Library)
+
+	installs = library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 0, len(installs))
+}
+
+func filterDexpreoptEntriesList(entriesList []android.AndroidMkEntries) []android.AndroidMkEntries {
+	var results []android.AndroidMkEntries
+	for _, entries := range entriesList {
+		if strings.Contains(entries.EntryMap["LOCAL_MODULE"][0], "-dexpreopt-") {
+			results = append(results, entries)
+		}
+	}
+	return results
+}
+
+func verifyEntries(t *testing.T, message string, expectedModule string,
+	expectedPrebuiltModuleFile string, expectedModulePath string, expectedInstalledModuleStem string,
+	entries android.AndroidMkEntries) {
+	android.AssertStringEquals(t, message+" LOCAL_MODULE", expectedModule,
+		entries.EntryMap["LOCAL_MODULE"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_MODULE_CLASS", "ETC",
+		entries.EntryMap["LOCAL_MODULE_CLASS"][0])
+
+	android.AssertStringDoesContain(t, message+" LOCAL_PREBUILT_MODULE_FILE",
+		entries.EntryMap["LOCAL_PREBUILT_MODULE_FILE"][0], expectedPrebuiltModuleFile)
+
+	android.AssertStringDoesContain(t, message+" LOCAL_MODULE_PATH",
+		entries.EntryMap["LOCAL_MODULE_PATH"][0], expectedModulePath)
+
+	android.AssertStringEquals(t, message+" LOCAL_INSTALLED_MODULE_STEM",
+		expectedInstalledModuleStem, entries.EntryMap["LOCAL_INSTALLED_MODULE_STEM"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_NOT_AVAILABLE_FOR_PLATFORM",
+		"false", entries.EntryMap["LOCAL_NOT_AVAILABLE_FOR_PLATFORM"][0])
+}
+
+func TestAndroidMkEntriesForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+
+	entriesList := android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 2, len(entriesList))
+
+	verifyEntries(t,
+		"entriesList[0]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		"/dexpreopt/oat/arm64/javalib.odex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		entriesList[0])
+
+	verifyEntries(t,
+		"entriesList[1]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		"/dexpreopt/oat/arm64/javalib.vdex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		entriesList[1])
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+
+	entriesList = android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 0, len(entriesList))
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 869a598..c84a15c 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -769,8 +769,8 @@
 
 	d.Javadoc.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
 
-	jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
-	doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
+	jsilver := ctx.Config().HostJavaToolPath(ctx, "jsilver.jar")
+	doclava := ctx.Config().HostJavaToolPath(ctx, "doclava.jar")
 
 	outDir := android.PathForModuleOut(ctx, "out")
 	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
diff --git a/java/droidstubs.go b/java/droidstubs.go
index ec1b04a..7ad316f 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -25,6 +26,9 @@
 	"android/soong/remoteexec"
 )
 
+// The values allowed for Droidstubs' Api_levels_sdk_type
+var allowedApiLevelSdkTypes = []string{"public", "system", "module-lib"}
+
 func init() {
 	RegisterStubsBuildComponents(android.InitRegistrationContext)
 }
@@ -134,7 +138,7 @@
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
-	// the sdk kind which Metalava extracts API levels annotations from. Supports 'public' and 'system' for now; defaults to public.
+	// the sdk kind which Metalava extracts API levels annotations from. Supports 'public', 'system' and 'module-lib' for now; defaults to public.
 	Api_levels_sdk_type *string
 
 	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
@@ -156,6 +160,7 @@
 
 // Provider of information about API stubs, used by java_sdk_library.
 type ApiStubsProvider interface {
+	AnnotationsZip() android.Path
 	ApiFilePath
 	RemovedApiFilePath() android.Path
 
@@ -210,6 +215,10 @@
 	}
 }
 
+func (d *Droidstubs) AnnotationsZip() android.Path {
+	return d.annotationsZip
+}
+
 func (d *Droidstubs) ApiFilePath() android.Path {
 	return d.apiFilePath
 }
@@ -399,19 +408,24 @@
 	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
 	// an actual file present on disk (in the order the patterns were passed). For system APIs for
 	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
-	// for older releases.
-	if sdkType := proptools.StringDefault(d.properties.Api_levels_sdk_type, "public"); sdkType != "public" {
-		if sdkType != "system" {
-			ctx.PropertyErrorf("api_levels_sdk_type", "only 'public' and 'system' are supported")
-		}
-		// If building non public stubs, add all sdkType patterns first...
-		for _, dir := range dirs {
-			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkType, filename))
-		}
+	// for older releases. Similarly, module-lib falls back to system API.
+	var sdkDirs []string
+	switch proptools.StringDefault(d.properties.Api_levels_sdk_type, "public") {
+	case "module-lib":
+		sdkDirs = []string{"module-lib", "system", "public"}
+	case "system":
+		sdkDirs = []string{"system", "public"}
+	case "public":
+		sdkDirs = []string{"public"}
+	default:
+		ctx.PropertyErrorf("api_levels_sdk_type", "needs to be one of %v", allowedApiLevelSdkTypes)
+		return
 	}
-	for _, dir := range dirs {
-		// ... and fallback to public ones, for Metalava to use if needed.
-		cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, "public", filename))
+
+	for _, sdkDir := range sdkDirs {
+		for _, dir := range dirs {
+			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, filename))
+		}
 	}
 }
 
@@ -792,7 +806,7 @@
 
 	properties PrebuiltStubsSourcesProperties
 
-	stubsSrcJar android.ModuleOutPath
+	stubsSrcJar android.Path
 }
 
 func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
@@ -809,35 +823,39 @@
 }
 
 func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
-
 	if len(p.properties.Srcs) != 1 {
-		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
+		ctx.PropertyErrorf("srcs", "must only specify one directory path or srcjar, contains %d paths", len(p.properties.Srcs))
 		return
 	}
 
-	localSrcDir := p.properties.Srcs[0]
-	// Although PathForModuleSrc can return nil if either the path doesn't exist or
-	// the path components are invalid it won't in this case because no components
-	// are specified and the module directory must exist in order to get this far.
-	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
+	src := p.properties.Srcs[0]
+	if filepath.Ext(src) == ".srcjar" {
+		// This is a srcjar. We can use it directly.
+		p.stubsSrcJar = android.PathForModuleSrc(ctx, src)
+	} else {
+		outPath := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
 
-	// Glob the contents of the directory just in case the directory does not exist.
-	srcGlob := localSrcDir + "/**/*"
-	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
+		// This is a directory. Glob the contents just in case the directory does not exist.
+		srcGlob := src + "/**/*"
+		srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
 
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("soong_zip").
-		Flag("-write_if_changed").
-		Flag("-jar").
-		FlagWithOutput("-o ", p.stubsSrcJar).
-		FlagWithArg("-C ", srcDir.String()).
-		FlagWithRspFileInputList("-r ", p.stubsSrcJar.ReplaceExtension(ctx, "rsp"), srcPaths)
+		// Although PathForModuleSrc can return nil if either the path doesn't exist or
+		// the path components are invalid it won't in this case because no components
+		// are specified and the module directory must exist in order to get this far.
+		srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, src)
 
-	rule.Restat()
-
-	rule.Build("zip src", "Create srcjar from prebuilt source")
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			BuiltTool("soong_zip").
+			Flag("-write_if_changed").
+			Flag("-jar").
+			FlagWithOutput("-o ", outPath).
+			FlagWithArg("-C ", srcDir.String()).
+			FlagWithRspFileInputList("-r ", outPath.ReplaceExtension(ctx, "rsp"), srcPaths)
+		rule.Restat()
+		rule.Build("zip src", "Create srcjar from prebuilt source")
+		p.stubsSrcJar = outPath
+	}
 }
 
 func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 60d0bea..10d99f3 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"reflect"
 	"regexp"
 	"strings"
@@ -82,8 +83,10 @@
 	}
 }
 
-func TestSystemDroidstubs(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
+// runs a test for droidstubs with a customizable sdkType argument and returns
+// the list of jar patterns that is passed as `--android-jar-pattern`
+func getAndroidJarPatternsForDroidstubs(t *testing.T, sdkType string) []string {
+	ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
 		droiddoc_exported_dir {
 			name: "some-exported-dir",
 			path: "somedir",
@@ -102,9 +105,9 @@
 				"some-other-exported-dir",
 			],
 			api_levels_annotations_enabled: true,
-            api_levels_sdk_type: "system",
+      api_levels_sdk_type: "%s",
 		}
-		`,
+		`, sdkType),
 		map[string][]byte{
 			"foo-doc/a.java": nil,
 		})
@@ -113,13 +116,40 @@
 	manifest := m.Output("metalava.sbox.textproto")
 	cmd := String(android.RuleBuilderSboxProtoForTests(t, manifest).Commands[0].Command)
 	r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
-	matches := r.FindAllString(cmd, -1)
+	return r.FindAllString(cmd, -1)
+}
+
+func TestPublicDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "public")
+
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, patterns)
+}
+
+func TestSystemDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "system")
+
 	android.AssertArrayString(t, "order of patterns", []string{
 		"--android-jar-pattern somedir/%/system/android.jar",
 		"--android-jar-pattern someotherdir/%/system/android.jar",
 		"--android-jar-pattern somedir/%/public/android.jar",
 		"--android-jar-pattern someotherdir/%/public/android.jar",
-	}, matches)
+	}, patterns)
+}
+
+func TestModuleLibDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib")
+
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/module-lib/android.jar",
+		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
+		"--android-jar-pattern somedir/%/system/android.jar",
+		"--android-jar-pattern someotherdir/%/system/android.jar",
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, patterns)
 }
 
 func TestDroidstubsSandbox(t *testing.T) {
diff --git a/java/genrule.go b/java/genrule.go
index e0a9c8f..16743b3 100644
--- a/java/genrule.go
+++ b/java/genrule.go
@@ -64,6 +64,7 @@
 	module := genrule.NewGenRule()
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 
 	return module
 }
@@ -76,6 +77,7 @@
 	module := genrule.NewGenRule()
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 
 	return module
 }
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 30683da..7c8be1e 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -30,14 +30,14 @@
 	// that information encoded within it.
 	active bool
 
-	// The path to the dex jar that is in the boot class path. If this is nil then the associated
+	// The path to the dex jar that is in the boot class path. If this is unset then the associated
 	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
 	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
 	// themselves.
 	//
 	// This must be the path to the unencoded dex jar as the encoded dex jar indirectly depends on
 	// this file so using the encoded dex jar here would result in a cycle in the ninja rules.
-	bootDexJarPath android.Path
+	bootDexJarPath OptionalDexJarPath
 
 	// The paths to the classes jars that contain classes and class members annotated with
 	// the UnsupportedAppUsage annotation that need to be extracted as part of the hidden API
@@ -49,7 +49,7 @@
 	uncompressDexState *bool
 }
 
-func (h *hiddenAPI) bootDexJar() android.Path {
+func (h *hiddenAPI) bootDexJar() OptionalDexJarPath {
 	return h.bootDexJarPath
 }
 
@@ -68,7 +68,7 @@
 }
 
 type hiddenAPIIntf interface {
-	bootDexJar() android.Path
+	bootDexJar() OptionalDexJarPath
 	classesJars() android.Paths
 	uncompressDex() *bool
 }
@@ -79,7 +79,7 @@
 //
 // uncompressedDexState should be nil when the module is a prebuilt and so does not require hidden
 // API encoding.
-func (h *hiddenAPI) initHiddenAPI(ctx android.ModuleContext, dexJar, classesJar android.Path, uncompressedDexState *bool) {
+func (h *hiddenAPI) initHiddenAPI(ctx android.ModuleContext, dexJar OptionalDexJarPath, classesJar android.Path, uncompressedDexState *bool) {
 
 	// Save the classes jars even if this is not active as they may be used by modular hidden API
 	// processing.
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 8e39f40..0cc960d 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -218,7 +219,7 @@
 var _ android.ExcludeFromVisibilityEnforcementTag = hiddenAPIStubsDependencyTag{}
 var _ android.ReplaceSourceWithPrebuilt = hiddenAPIStubsDependencyTag{}
 var _ android.ExcludeFromApexContentsTag = hiddenAPIStubsDependencyTag{}
-var _ android.SdkMemberTypeDependencyTag = hiddenAPIStubsDependencyTag{}
+var _ android.SdkMemberDependencyTag = hiddenAPIStubsDependencyTag{}
 
 // hiddenAPIComputeMonolithicStubLibModules computes the set of module names that provide stubs
 // needed to produce the hidden API monolithic stub flags file.
@@ -277,7 +278,7 @@
 // hiddenAPIRetrieveDexJarBuildPath retrieves the DexJarBuildPath from the specified module, if
 // available, or reports an error.
 func hiddenAPIRetrieveDexJarBuildPath(ctx android.ModuleContext, module android.Module, kind android.SdkKind) android.Path {
-	var dexJar android.Path
+	var dexJar OptionalDexJarPath
 	if sdkLibrary, ok := module.(SdkLibraryDependency); ok {
 		dexJar = sdkLibrary.SdkApiStubDexJar(ctx, kind)
 	} else if j, ok := module.(UsesLibraryDependency); ok {
@@ -287,10 +288,11 @@
 		return nil
 	}
 
-	if dexJar == nil {
-		ctx.ModuleErrorf("dependency %s does not provide a dex jar, consider setting compile_dex: true", module)
+	if !dexJar.Valid() {
+		ctx.ModuleErrorf("dependency %s does not provide a dex jar: %s", module, dexJar.InvalidReason())
+		return nil
 	}
-	return dexJar
+	return dexJar.Path()
 }
 
 // buildRuleToGenerateHiddenAPIStubFlagsFile creates a rule to create a hidden API stub flags file.
@@ -546,18 +548,18 @@
 	}
 }
 
-// StubFlagSubset returns a SignatureCsvSubset that contains a path to a stub-flags.csv file and a
-// path to a signature-patterns.csv file that defines a subset of the monolithic stub flags file,
-// i.e. out/soong/hiddenapi/hiddenapi-stub-flags.txt, against which it will be compared.
+// StubFlagSubset returns a SignatureCsvSubset that contains a path to a filtered-stub-flags.csv
+// file and a path to a signature-patterns.csv file that defines a subset of the monolithic stub
+// flags file, i.e. out/soong/hiddenapi/hiddenapi-stub-flags.txt, against which it will be compared.
 func (i *HiddenAPIInfo) StubFlagSubset() SignatureCsvSubset {
-	return SignatureCsvSubset{i.StubFlagsPath, i.SignaturePatternsPath}
+	return SignatureCsvSubset{i.FilteredStubFlagsPath, i.SignaturePatternsPath}
 }
 
-// FlagSubset returns a SignatureCsvSubset that contains a path to an all-flags.csv file and a
+// FlagSubset returns a SignatureCsvSubset that contains a path to a filtered-flags.csv file and a
 // path to a signature-patterns.csv file that defines a subset of the monolithic flags file, i.e.
 // out/soong/hiddenapi/hiddenapi-flags.csv, against which it will be compared.
 func (i *HiddenAPIInfo) FlagSubset() SignatureCsvSubset {
-	return SignatureCsvSubset{i.AllFlagsPath, i.SignaturePatternsPath}
+	return SignatureCsvSubset{i.FilteredFlagsPath, i.SignaturePatternsPath}
 }
 
 var HiddenAPIInfoProvider = blueprint.NewProvider(HiddenAPIInfo{})
@@ -782,9 +784,6 @@
 // HiddenAPIFlagOutput contains paths to output files from the hidden API flag generation for a
 // bootclasspath_fragment module.
 type HiddenAPIFlagOutput struct {
-	// The path to the generated stub-flags.csv file.
-	StubFlagsPath android.Path
-
 	// The path to the generated annotation-flags.csv file.
 	AnnotationFlagsPath android.Path
 
@@ -794,12 +793,21 @@
 	// The path to the generated index.csv file.
 	IndexPath android.Path
 
+	// The path to the generated stub-flags.csv file.
+	StubFlagsPath android.Path
+
 	// The path to the generated all-flags.csv file.
 	AllFlagsPath android.Path
 
 	// The path to the generated signature-patterns.txt file which defines the subset of the
 	// monolithic hidden API files provided in this.
 	SignaturePatternsPath android.Path
+
+	// The path to the generated filtered-stub-flags.csv file.
+	FilteredStubFlagsPath android.Path
+
+	// The path to the generated filtered-flags.csv file.
+	FilteredFlagsPath android.Path
 }
 
 // bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
@@ -935,13 +943,22 @@
 
 // buildRuleSignaturePatternsFile creates a rule to generate a file containing the set of signature
 // patterns that will select a subset of the monolithic flags.
-func buildRuleSignaturePatternsFile(ctx android.ModuleContext, flagsPath android.Path) android.Path {
+func buildRuleSignaturePatternsFile(ctx android.ModuleContext, flagsPath android.Path, splitPackages []string, packagePrefixes []string) android.Path {
 	patternsFile := android.PathForModuleOut(ctx, "modular-hiddenapi", "signature-patterns.csv")
 	// Create a rule to validate the output from the following rule.
 	rule := android.NewRuleBuilder(pctx, ctx)
+
+	// Quote any * in the packages to avoid them being expanded by the shell.
+	quotedSplitPackages := []string{}
+	for _, pkg := range splitPackages {
+		quotedSplitPackages = append(quotedSplitPackages, strings.ReplaceAll(pkg, "*", "\\*"))
+	}
+
 	rule.Command().
 		BuiltTool("signature_patterns").
 		FlagWithInput("--flags ", flagsPath).
+		FlagForEachArg("--split-package ", quotedSplitPackages).
+		FlagForEachArg("--package-prefix ", packagePrefixes).
 		FlagWithOutput("--output ", patternsFile)
 	rule.Build("hiddenAPISignaturePatterns", "hidden API signature patterns")
 
@@ -1065,11 +1082,13 @@
 	// Store the paths in the info for use by other modules and sdk snapshot generation.
 	output := HiddenAPIOutput{
 		HiddenAPIFlagOutput: HiddenAPIFlagOutput{
-			StubFlagsPath:       filteredStubFlagsCSV,
-			AnnotationFlagsPath: annotationFlagsCSV,
-			MetadataPath:        metadataCSV,
-			IndexPath:           indexCSV,
-			AllFlagsPath:        filteredFlagsCSV,
+			AnnotationFlagsPath:   annotationFlagsCSV,
+			MetadataPath:          metadataCSV,
+			IndexPath:             indexCSV,
+			StubFlagsPath:         stubFlagsCSV,
+			AllFlagsPath:          allFlagsCSV,
+			FilteredStubFlagsPath: filteredStubFlagsCSV,
+			FilteredFlagsPath:     filteredFlagsCSV,
 		},
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
@@ -1159,18 +1178,17 @@
 
 // retrieveBootDexJarFromHiddenAPIModule retrieves the boot dex jar from the hiddenAPIModule.
 //
-// If the module does not provide a boot dex jar, i.e. the returned boot dex jar is nil, then  that
-// create a fake path and either report an error immediately or defer reporting of the error until
-// the path is actually used.
+// If the module does not provide a boot dex jar, i.e. the returned boot dex jar is unset or
+// invalid, then create a fake path and either report an error immediately or defer reporting of the
+// error until the path is actually used.
 func retrieveBootDexJarFromHiddenAPIModule(ctx android.ModuleContext, module hiddenAPIModule) android.Path {
 	bootDexJar := module.bootDexJar()
-	if bootDexJar == nil {
+	if !bootDexJar.Valid() {
 		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/boot-dex/%s.jar", module.Name()))
-		bootDexJar = fake
-
-		handleMissingDexBootFile(ctx, module, fake)
+		handleMissingDexBootFile(ctx, module, fake, bootDexJar.InvalidReason())
+		return fake
 	}
-	return bootDexJar
+	return bootDexJar.Path()
 }
 
 // extractClassesJarsFromModules extracts the class jars from the supplied modules.
@@ -1194,13 +1212,6 @@
 // deferReportingMissingBootDexJar returns true if a missing boot dex jar should not be reported by
 // Soong but should instead only be reported in ninja if the file is actually built.
 func deferReportingMissingBootDexJar(ctx android.ModuleContext, module android.Module) bool {
-	// TODO(b/179354495): Remove this workaround when it is unnecessary.
-	// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
-	// create a fake one that will cause a build error only if it is used.
-	if ctx.Config().AlwaysUsePrebuiltSdks() {
-		return true
-	}
-
 	// Any missing dependency should be allowed.
 	if ctx.Config().AllowMissingDependencies() {
 		return true
@@ -1271,7 +1282,7 @@
 
 // handleMissingDexBootFile will either log a warning or create an error rule to create the fake
 // file depending on the value returned from deferReportingMissingBootDexJar.
-func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath) {
+func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath, reason string) {
 	if deferReportingMissingBootDexJar(ctx, module) {
 		// Create an error rule that pretends to create the output file but will actually fail if it
 		// is run.
@@ -1279,11 +1290,11 @@
 			Rule:   android.ErrorRule,
 			Output: fake,
 			Args: map[string]string{
-				"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
+				"error": fmt.Sprintf("missing boot dex jar dependency for %s: %s", module, reason),
 			},
 		})
 	} else {
-		ctx.ModuleErrorf("module %s does not provide a dex jar", module)
+		ctx.ModuleErrorf("module %s does not provide a dex jar: %s", module, reason)
 	}
 }
 
@@ -1294,14 +1305,13 @@
 // However, under certain conditions, e.g. errors, or special build configurations it will return
 // a path to a fake file.
 func retrieveEncodedBootDexJarFromModule(ctx android.ModuleContext, module android.Module) android.Path {
-	bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-	if bootDexJar == nil {
+	bootDexJar := module.(interface{ DexJarBuildPath() OptionalDexJarPath }).DexJarBuildPath()
+	if !bootDexJar.Valid() {
 		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/encoded-dex/%s.jar", module.Name()))
-		bootDexJar = fake
-
-		handleMissingDexBootFile(ctx, module, fake)
+		handleMissingDexBootFile(ctx, module, fake, bootDexJar.InvalidReason())
+		return fake
 	}
-	return bootDexJar
+	return bootDexJar.Path()
 }
 
 // extractEncodedDexJarsFromModules extracts the encoded dex jars from the supplied modules.
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index dcd363c..75b7bb7 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -20,6 +20,7 @@
 	"testing"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -306,7 +307,7 @@
 		android.AssertStringEquals(t, "encode embedded java_library", unencodedDexJar, actualUnencodedDexJar.String())
 
 		// Make sure that the encoded dex jar is the exported one.
-		exportedDexJar := moduleForTests.Module().(UsesLibraryDependency).DexJarBuildPath()
+		exportedDexJar := moduleForTests.Module().(UsesLibraryDependency).DexJarBuildPath().Path()
 		android.AssertPathRelativeToTopEquals(t, "encode embedded java_library", encodedDexJar, exportedDexJar)
 	}
 
diff --git a/java/java.go b/java/java.go
index 1a052b4..9b4a005 100644
--- a/java/java.go
+++ b/java/java.go
@@ -21,7 +21,9 @@
 import (
 	"fmt"
 	"path/filepath"
+	"strings"
 
+	"android/soong/bazel"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -73,6 +75,7 @@
 	android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaBootLibsSdkMemberType)
+	android.RegisterSdkMemberType(javaSystemserverLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaTestSdkMemberType)
 }
 
@@ -146,6 +149,37 @@
 		onlyCopyJarToSnapshot,
 	}
 
+	// Supports adding java systemserver libraries to module_exports and sdk.
+	//
+	// The build has some implicit dependencies (via the systemserver jars configuration) on a number
+	// of modules that are part of the java systemserver classpath and which are provided by mainline
+	// modules but which are not otherwise used outside those mainline modules.
+	//
+	// As they are not needed outside the mainline modules adding them to the sdk/module-exports as
+	// either java_libs, or java_header_libs would end up exporting more information than was strictly
+	// necessary. The java_systemserver_libs property to allow those modules to be exported as part of
+	// the sdk/module_exports without exposing any unnecessary information.
+	javaSystemserverLibsSdkMemberType = &librarySdkMemberType{
+		android.SdkMemberTypeBase{
+			PropertyName: "java_systemserver_libs",
+			SupportsSdk:  true,
+		},
+		func(ctx android.SdkMemberContext, j *Library) android.Path {
+			// Java systemserver libs are only provided in the SDK to provide access to their dex
+			// implementation jar for use by dexpreopting. They do not need to provide an actual
+			// implementation jar but the java_import will need a file that exists so just copy an empty
+			// file. Any attempt to use that file as a jar will cause a build error.
+			return ctx.SnapshotBuilder().EmptyFile()
+		},
+		func(osPrefix, name string) string {
+			// Create a special name for the implementation jar to try and provide some useful information
+			// to a developer that attempts to compile against this.
+			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
+			return filepath.Join(osPrefix, "java_systemserver_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
+		},
+		onlyCopyJarToSnapshot,
+	}
+
 	// Supports adding java test libraries to module_exports but not sdk.
 	javaTestSdkMemberType = &testSdkMemberType{
 		SdkMemberTypeBase: android.SdkMemberTypeBase{
@@ -219,7 +253,7 @@
 
 // Provides build path and install path to DEX jars.
 type UsesLibraryDependency interface {
-	DexJarBuildPath() android.Path
+	DexJarBuildPath() OptionalDexJarPath
 	DexJarInstallPath() android.Path
 	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
 }
@@ -236,6 +270,9 @@
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
+
+	// True if the dependency is relinked at runtime.
+	runtimeLinked bool
 }
 
 // installDependencyTag is a dependency tag that is annotated to cause the installed files of the
@@ -246,6 +283,15 @@
 	name string
 }
 
+func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.runtimeLinked {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	}
+	return nil
+}
+
+var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}
+
 type usesLibraryDependencyTag struct {
 	dependencyTag
 
@@ -262,10 +308,13 @@
 
 func makeUsesLibraryDependencyTag(sdkVersion int, optional bool, implicit bool) usesLibraryDependencyTag {
 	return usesLibraryDependencyTag{
-		dependencyTag: dependencyTag{name: fmt.Sprintf("uses-library-%d", sdkVersion)},
-		sdkVersion:    sdkVersion,
-		optional:      optional,
-		implicit:      implicit,
+		dependencyTag: dependencyTag{
+			name:          fmt.Sprintf("uses-library-%d", sdkVersion),
+			runtimeLinked: true,
+		},
+		sdkVersion: sdkVersion,
+		optional:   optional,
+		implicit:   implicit,
 	}
 }
 
@@ -276,21 +325,22 @@
 var (
 	dataNativeBinsTag       = dependencyTag{name: "dataNativeBins"}
 	staticLibTag            = dependencyTag{name: "staticlib"}
-	libTag                  = dependencyTag{name: "javalib"}
-	java9LibTag             = dependencyTag{name: "java9lib"}
+	libTag                  = dependencyTag{name: "javalib", runtimeLinked: true}
+	java9LibTag             = dependencyTag{name: "java9lib", runtimeLinked: true}
 	pluginTag               = dependencyTag{name: "plugin"}
 	errorpronePluginTag     = dependencyTag{name: "errorprone-plugin"}
 	exportedPluginTag       = dependencyTag{name: "exported-plugin"}
-	bootClasspathTag        = dependencyTag{name: "bootclasspath"}
-	systemModulesTag        = dependencyTag{name: "system modules"}
+	bootClasspathTag        = dependencyTag{name: "bootclasspath", runtimeLinked: true}
+	systemModulesTag        = dependencyTag{name: "system modules", runtimeLinked: true}
 	frameworkResTag         = dependencyTag{name: "framework-res"}
-	kotlinStdlibTag         = dependencyTag{name: "kotlin-stdlib"}
-	kotlinAnnotationsTag    = dependencyTag{name: "kotlin-annotations"}
+	kotlinStdlibTag         = dependencyTag{name: "kotlin-stdlib", runtimeLinked: true}
+	kotlinAnnotationsTag    = dependencyTag{name: "kotlin-annotations", runtimeLinked: true}
+	kotlinPluginTag         = dependencyTag{name: "kotlin-plugin"}
 	proguardRaiseTag        = dependencyTag{name: "proguard-raise"}
 	certificateTag          = dependencyTag{name: "certificate"}
 	instrumentationForTag   = dependencyTag{name: "instrumentation_for"}
 	extraLintCheckTag       = dependencyTag{name: "extra-lint-check"}
-	jniLibTag               = dependencyTag{name: "jnilib"}
+	jniLibTag               = dependencyTag{name: "jnilib", runtimeLinked: true}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	jniInstallTag           = installDependencyTag{name: "jni install"}
 	binaryInstallTag        = installDependencyTag{name: "binary install"}
@@ -380,6 +430,7 @@
 	aidlPreprocess          android.OptionalPath
 	kotlinStdlib            android.Paths
 	kotlinAnnotations       android.Paths
+	kotlinPlugins           android.Paths
 
 	disableTurbine bool
 }
@@ -398,6 +449,12 @@
 		return normalizeJavaVersion(ctx, javaVersion)
 	} else if ctx.Device() {
 		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
+	} else if ctx.Config().TargetsJava11() {
+		// Temporary experimental flag to be able to try and build with
+		// java version 11 options.  The flag, if used, just sets Java
+		// 11 as the default version, leaving any components that
+		// target an older version intact.
+		return JAVA_VERSION_11
 	} else {
 		return JAVA_VERSION_9
 	}
@@ -411,6 +468,7 @@
 	JAVA_VERSION_7           = 7
 	JAVA_VERSION_8           = 8
 	JAVA_VERSION_9           = 9
+	JAVA_VERSION_11          = 11
 )
 
 func (v javaVersion) String() string {
@@ -423,6 +481,8 @@
 		return "1.8"
 	case JAVA_VERSION_9:
 		return "1.9"
+	case JAVA_VERSION_11:
+		return "11"
 	default:
 		return "unsupported"
 	}
@@ -443,8 +503,10 @@
 		return JAVA_VERSION_8
 	case "1.9", "9":
 		return JAVA_VERSION_9
-	case "10", "11":
-		ctx.PropertyErrorf("java_version", "Java language levels above 9 are not supported")
+	case "11":
+		return JAVA_VERSION_11
+	case "10":
+		ctx.PropertyErrorf("java_version", "Java language levels 10 is not supported")
 		return JAVA_VERSION_UNSUPPORTED
 	default:
 		ctx.PropertyErrorf("java_version", "Unrecognized Java language level")
@@ -487,7 +549,7 @@
 	}
 
 	// Store uncompressed dex files that are preopted on /system.
-	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !odexOnSystemOther(ctx, dexpreopter.installPath)) {
+	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, dexpreopter.installPath)) {
 		return true
 	}
 	if ctx.Config().UncompressPrivAppDex() &&
@@ -498,9 +560,18 @@
 	return false
 }
 
+// Sets `dexer.dexProperties.Uncompress_dex` to the proper value.
+func setUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter, dexer *dexer) {
+	if dexer.dexProperties.Uncompress_dex == nil {
+		// If the value was not force-set by the user, use reasonable default based on the module.
+		dexer.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, dexpreopter))
+	}
+}
+
 func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
+	j.maxSdkVersion = j.MaxSdkVersion(ctx)
 
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if !apexInfo.IsForPlatform() {
@@ -508,12 +579,10 @@
 	}
 
 	j.checkSdkVersions(ctx)
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
-	if j.dexProperties.Uncompress_dex == nil {
-		// If the value was not force-set by the user, use reasonable default based on the module.
-		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
-	}
+	setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 	j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 	j.compile(ctx, nil)
@@ -527,8 +596,23 @@
 		if j.InstallMixin != nil {
 			extraInstallDeps = j.InstallMixin(ctx, j.outputFile)
 		}
-		j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
-			j.Stem()+".jar", j.outputFile, extraInstallDeps...)
+		hostDexNeeded := Bool(j.deviceProperties.Hostdex) && !ctx.Host()
+		if hostDexNeeded {
+			j.hostdexInstallFile = ctx.InstallFile(
+				android.PathForHostDexInstall(ctx, "framework"),
+				j.Stem()+"-hostdex.jar", j.outputFile)
+		}
+		var installDir android.InstallPath
+		if ctx.InstallInTestcases() {
+			var archDir string
+			if !ctx.Host() {
+				archDir = ctx.DeviceConfig().DeviceArch()
+			}
+			installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
+		} else {
+			installDir = android.PathForModuleInstall(ctx, "framework")
+		}
+		j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
 }
 
@@ -672,6 +756,7 @@
 
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostAndDeviceSupported)
 	return module
 }
@@ -694,6 +779,7 @@
 
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostSupported)
 	return module
 }
@@ -742,6 +828,9 @@
 
 	// Names of modules containing JNI libraries that should be installed alongside the test.
 	Jni_libs []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type hostTestProperties struct {
@@ -753,6 +842,9 @@
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type prebuiltTestProperties struct {
@@ -796,6 +888,20 @@
 	dexJarFile android.Path
 }
 
+func (j *Test) InstallInTestcases() bool {
+	// Host java tests install into $(HOST_OUT_JAVA_LIBRARIES), and then are copied into
+	// testcases by base_rules.mk.
+	return !j.Host()
+}
+
+func (j *TestHelperLibrary) InstallInTestcases() bool {
+	return true
+}
+
+func (j *JavaTestImport) InstallInTestcases() bool {
+	return true
+}
+
 func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if len(j.testHostProperties.Data_native_bins) > 0 {
 		for _, target := range ctx.MultiTargets() {
@@ -1027,14 +1133,14 @@
 
 type binaryProperties struct {
 	// installable script to execute the resulting jar
-	Wrapper *string `android:"path"`
+	Wrapper *string `android:"path,arch_variant"`
 
 	// Name of the class containing main to be inserted into the manifest as Main-Class.
 	Main_class *string
 
 	// Names of modules containing JNI libraries that should be installed alongside the host
 	// variant of the binary.
-	Jni_libs []string
+	Jni_libs []string `android:"arch_variant"`
 }
 
 type Binary struct {
@@ -1072,14 +1178,23 @@
 		if j.binaryProperties.Wrapper != nil {
 			j.wrapperFile = android.PathForModuleSrc(ctx, *j.binaryProperties.Wrapper)
 		} else {
+			if ctx.Windows() {
+				ctx.PropertyErrorf("wrapper", "wrapper is required for Windows")
+			}
+
 			j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
 		}
 
+		ext := ""
+		if ctx.Windows() {
+			ext = ".bat"
+		}
+
 		// The host installation rules make the installed wrapper depend on all the dependencies
 		// of the wrapper variant, which will include the common variant's jar file and any JNI
 		// libraries.  This is verified by TestBinary.
 		j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
-			ctx.ModuleName(), j.wrapperFile)
+			ctx.ModuleName()+ext, j.wrapperFile)
 	}
 }
 
@@ -1115,6 +1230,8 @@
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommonFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
+
 	return module
 }
 
@@ -1132,6 +1249,7 @@
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommonFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -1153,7 +1271,6 @@
 	Installable *bool
 
 	// If not empty, classes are restricted to the specified packages and their sub-packages.
-	// This information is used to generate the updatable-bcp-packages.txt file.
 	Permitted_packages []string
 
 	// List of shared java libs that this module has dependencies to
@@ -1195,7 +1312,7 @@
 	properties ImportProperties
 
 	// output file containing classes.dex and resources
-	dexJarFile        android.Path
+	dexJarFile        OptionalDexJarPath
 	dexJarInstallFile android.Path
 
 	combinedClasspathFile android.Path
@@ -1280,6 +1397,10 @@
 		j.hideApexVariantFromMake = true
 	}
 
+	if ctx.Windows() {
+		j.HideFromMake()
+	}
+
 	jars := android.PathsForModuleSrc(ctx, j.properties.Jars)
 
 	jarName := j.Stem() + ".jar"
@@ -1295,7 +1416,6 @@
 	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
-	var deapexerModule android.Module
 
 	ctx.VisitDirectDeps(func(module android.Module) {
 		tag := ctx.OtherModuleDependencyTag(module)
@@ -1316,16 +1436,20 @@
 		}
 
 		addCLCFromDep(ctx, module, j.classLoaderContexts)
-
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			deapexerModule = module
-		}
 	})
 
 	if Bool(j.properties.Installable) {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
-			jarName, outputFile)
+		var installDir android.InstallPath
+		if ctx.InstallInTestcases() {
+			var archDir string
+			if !ctx.Host() {
+				archDir = ctx.DeviceConfig().DeviceArch()
+			}
+			installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
+		} else {
+			installDir = android.PathForModuleInstall(ctx, "framework")
+		}
+		ctx.InstallFile(installDir, jarName, outputFile)
 	}
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
@@ -1335,26 +1459,28 @@
 		// obtained from the associated deapexer module.
 		ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if ai.ForPrebuiltApex {
-			if deapexerModule == nil {
-				// This should never happen as a variant for a prebuilt_apex is only created if the
-				// deapexer module has been configured to export the dex implementation jar for this module.
-				ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
-					j.Name(), ai.ApexVariationName)
-				return
-			}
-
 			// Get the path of the dex implementation jar from the `deapexer` module.
-			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+			di := android.FindDeapexerProviderForModule(ctx)
+			if di == nil {
+				return // An error has been reported by FindDeapexerProviderForModule.
+			}
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(j.BaseModuleName())); dexOutputPath != nil {
-				j.dexJarFile = dexOutputPath
-				j.dexJarInstallFile = android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(j.BaseModuleName()))
+				dexJarFile := makeDexJarPathFromPath(dexOutputPath)
+				j.dexJarFile = dexJarFile
+				installPath := android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(j.BaseModuleName()))
+				j.dexJarInstallFile = installPath
+
+				j.dexpreopter.installPath = j.dexpreopter.getInstallPath(ctx, installPath)
+				setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
+				j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
+				j.dexpreopt(ctx, dexOutputPath)
 
 				// Initialize the hiddenapi structure.
-				j.initHiddenAPI(ctx, dexOutputPath, outputFile, nil)
+				j.initHiddenAPI(ctx, dexJarFile, outputFile, j.dexProperties.Uncompress_dex)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
-				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt APEX %s", di.ApexModuleName())
 			}
 		} else if Bool(j.dexProperties.Compile_dex) {
 			sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
@@ -1368,11 +1494,9 @@
 
 			// Dex compilation
 
-			j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName)
-			if j.dexProperties.Uncompress_dex == nil {
-				// If the value was not force-set by the user, use reasonable default based on the module.
-				j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
-			}
+			j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+				ctx, android.PathForModuleInstall(ctx, "framework", jarName))
+			setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 
 			var dexOutputFile android.OutputPath
@@ -1382,12 +1506,12 @@
 			}
 
 			// Initialize the hiddenapi structure.
-			j.initHiddenAPI(ctx, dexOutputFile, outputFile, j.dexProperties.Uncompress_dex)
+			j.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), outputFile, j.dexProperties.Uncompress_dex)
 
 			// Encode hidden API flags in dex file.
 			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
 
-			j.dexJarFile = dexOutputFile
+			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 			j.dexJarInstallFile = android.PathForModuleInstall(ctx, "framework", jarName)
 		}
 	}
@@ -1425,7 +1549,7 @@
 	return android.Paths{j.combinedClasspathFile}
 }
 
-func (j *Import) DexJarBuildPath() android.Path {
+func (j *Import) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
@@ -1488,9 +1612,6 @@
 var _ android.IDECustomizedModuleName = (*Import)(nil)
 
 // Collect information for opening IDE project files in java/jdeps.go.
-const (
-	removedPrefix = "prebuilt_"
-)
 
 func (j *Import) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Jars = append(dpInfo.Jars, j.PrebuiltSrcs()...)
@@ -1509,7 +1630,7 @@
 	return Bool(j.properties.Installable)
 }
 
-var _ dexpreopterInterface = (*Import)(nil)
+var _ DexpreopterInterface = (*Import)(nil)
 
 // java_import imports one or more `.jar` files into the build graph as if they were built by a java_library module.
 //
@@ -1570,7 +1691,7 @@
 
 	properties DexImportProperties
 
-	dexJarFile android.Path
+	dexJarFile OptionalDexJarPath
 
 	dexpreopter
 
@@ -1622,7 +1743,8 @@
 		j.hideApexVariantFromMake = true
 	}
 
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter)
 
 	inputJar := ctx.ExpandSource(j.properties.Jars[0], "jars")
@@ -1660,7 +1782,7 @@
 		})
 	}
 
-	j.dexJarFile = dexOutputFile
+	j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 
 	j.dexpreopt(ctx, dexOutputFile)
 
@@ -1670,7 +1792,7 @@
 	}
 }
 
-func (j *DexImport) DexJarBuildPath() android.Path {
+func (j *DexImport) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
@@ -1839,8 +1961,108 @@
 	// from its CLC should be added to the current CLC.
 	if sdkLib != nil {
 		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, false, true,
-			dep.DexJarBuildPath(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
+			dep.DexJarBuildPath().PathOrNil(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
 	} else {
 		clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
 	}
 }
+
+type javaLibraryAttributes struct {
+	Srcs      bazel.LabelListAttribute
+	Deps      bazel.LabelListAttribute
+	Javacopts bazel.StringListAttribute
+}
+
+func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
+	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs))
+	attrs := &javaLibraryAttributes{
+		Srcs: srcs,
+	}
+
+	if m.properties.Javacflags != nil {
+		attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
+	}
+
+	if m.properties.Libs != nil {
+		attrs.Deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "java_library",
+		Bzl_load_location: "//build/bazel/rules/java:library.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+}
+
+type javaBinaryHostAttributes struct {
+	Srcs       bazel.LabelListAttribute
+	Deps       bazel.LabelListAttribute
+	Main_class string
+	Jvm_flags  bazel.StringListAttribute
+}
+
+// JavaBinaryHostBp2Build is for java_binary_host bp2build.
+func javaBinaryHostBp2Build(ctx android.TopDownMutatorContext, m *Binary) {
+	mainClass := ""
+	if m.binaryProperties.Main_class != nil {
+		mainClass = *m.binaryProperties.Main_class
+	}
+	if m.properties.Manifest != nil {
+		mainClassInManifest, err := android.GetMainClassInManifest(ctx.Config(), android.PathForModuleSrc(ctx, *m.properties.Manifest).String())
+		if err != nil {
+			return
+		}
+		mainClass = mainClassInManifest
+	}
+	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs))
+	attrs := &javaBinaryHostAttributes{
+		Srcs:       srcs,
+		Main_class: mainClass,
+	}
+
+	// Attribute deps
+	deps := []string{}
+	if m.properties.Static_libs != nil {
+		deps = append(deps, m.properties.Static_libs...)
+	}
+	if m.binaryProperties.Jni_libs != nil {
+		deps = append(deps, m.binaryProperties.Jni_libs...)
+	}
+	if len(deps) > 0 {
+		attrs.Deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, deps))
+	}
+
+	// Attribute jvm_flags
+	if m.binaryProperties.Jni_libs != nil {
+		jniLibPackages := map[string]bool{}
+		for _, jniLibLabel := range android.BazelLabelForModuleDeps(ctx, m.binaryProperties.Jni_libs).Includes {
+			jniLibPackage := jniLibLabel.Label
+			indexOfColon := strings.Index(jniLibLabel.Label, ":")
+			if indexOfColon > 0 {
+				// JNI lib from other package
+				jniLibPackage = jniLibLabel.Label[2:indexOfColon]
+			} else if indexOfColon == 0 {
+				// JNI lib in the same package of java_binary
+				packageOfCurrentModule := m.GetBazelLabel(ctx, m)
+				jniLibPackage = packageOfCurrentModule[2:strings.Index(packageOfCurrentModule, ":")]
+			}
+			if _, inMap := jniLibPackages[jniLibPackage]; !inMap {
+				jniLibPackages[jniLibPackage] = true
+			}
+		}
+		jniLibPaths := []string{}
+		for jniLibPackage, _ := range jniLibPackages {
+			// See cs/f:.*/third_party/bazel/.*java_stub_template.txt for the use of RUNPATH
+			jniLibPaths = append(jniLibPaths, "$${RUNPATH}"+jniLibPackage)
+		}
+		attrs.Jvm_flags = bazel.MakeStringListAttribute([]string{"-Djava.library.path=" + strings.Join(jniLibPaths, ":")})
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "java_binary",
+	}
+
+	// Create the BazelTargetModule.
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+}
diff --git a/java/java_test.go b/java/java_test.go
index 8bb017f..6e4e673 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -600,8 +600,8 @@
 	}
 
 	barDexJar := barModule.Module().(*Import).DexJarBuildPath()
-	if barDexJar != nil {
-		t.Errorf("bar dex jar build path expected to be nil, got %q", barDexJar)
+	if barDexJar.IsSet() {
+		t.Errorf("bar dex jar build path expected to be set, got %s", barDexJar)
 	}
 
 	if !strings.Contains(javac.Args["classpath"], sdklibStubsJar.String()) {
@@ -612,7 +612,7 @@
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, bazJar.String())
 	}
 
-	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath()
+	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath().Path()
 	expectedDexJar := "out/soong/.intermediates/baz/android_common/dex/baz.jar"
 	android.AssertPathRelativeToTopEquals(t, "baz dex jar build path", expectedDexJar, bazDexJar)
 
@@ -988,11 +988,11 @@
 		}
 		`)
 
-	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
+	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
 	for i := 0; i < 3; i++ {
 		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i))
-		if !strings.Contains(barJavac.Args["classpath"], barHeaderJar) {
-			t.Errorf("bar javac classpath %v does not contain %q", barJavac.Args["classpath"], barHeaderJar)
+		if !strings.HasPrefix(barJavac.Args["classpath"], "-classpath "+barHeaderJar+":") {
+			t.Errorf("bar javac classpath %v does start with %q", barJavac.Args["classpath"], barHeaderJar)
 		}
 	}
 }
@@ -1357,6 +1357,36 @@
 	}
 }
 
+func TestAidlFlagsWithMinSdkVersion(t *testing.T) {
+	fixture := android.GroupFixturePreparers(
+		prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}}))
+
+	for _, tc := range []struct {
+		name       string
+		sdkVersion string
+		expected   string
+	}{
+		{"default is current", "", "current"},
+		{"use sdk_version", `sdk_version: "14"`, "14"},
+		{"system_current", `sdk_version: "system_current"`, "current"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := fixture.RunTestWithBp(t, `
+				java_library {
+					name: "foo",
+					srcs: ["aidl/foo/IFoo.aidl"],
+					`+tc.sdkVersion+`
+				}
+			`)
+			aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+			expectedAidlFlag := "--min_sdk_version=" + tc.expected
+			if !strings.Contains(aidlCommand, expectedAidlFlag) {
+				t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+			}
+		})
+	}
+}
+
 func TestDataNativeBinaries(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_test_host {
diff --git a/java/jdeps.go b/java/jdeps.go
index 0ab2e42..eff9a31 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -40,16 +40,11 @@
 var _ android.SingletonMakeVarsProvider = (*jdepsGeneratorSingleton)(nil)
 
 const (
-	// Environment variables used to modify behavior of this singleton.
-	envVariableCollectJavaDeps = "SOONG_COLLECT_JAVA_DEPS"
-	jdepsJsonFileName          = "module_bp_java_deps.json"
+	jdepsJsonFileName = "module_bp_java_deps.json"
 )
 
 func (j *jdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !ctx.Config().IsEnvTrue(envVariableCollectJavaDeps) {
-		return
-	}
-
+	// (b/204397180) Generate module_bp_java_deps.json by default.
 	moduleInfos := make(map[string]android.IdeInfo)
 
 	ctx.VisitAllModules(func(module android.Module) {
diff --git a/java/kotlin.go b/java/kotlin.go
index 3a6fc0f..e4f1bc1 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -81,6 +81,7 @@
 
 	var deps android.Paths
 	deps = append(deps, flags.kotlincClasspath...)
+	deps = append(deps, flags.kotlincDeps...)
 	deps = append(deps, srcJars...)
 	deps = append(deps, commonSrcFiles...)
 
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index db30696..cac0af3 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -281,3 +281,46 @@
 		})
 	}
 }
+
+func TestKotlinCompose(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "androidx.compose.runtime_runtime",
+		}
+
+		java_library_host {
+			name: "androidx.compose.compiler_compiler-hosted",
+		}
+
+		java_library {
+			name: "withcompose",
+			srcs: ["a.kt"],
+			static_libs: ["androidx.compose.runtime_runtime"],
+		}
+
+		java_library {
+			name: "nocompose",
+			srcs: ["a.kt"],
+		}
+	`)
+
+	buildOS := result.Config.BuildOS.String()
+
+	composeCompiler := result.ModuleForTests("androidx.compose.compiler_compiler-hosted", buildOS+"_common").Rule("combineJar").Output
+	withCompose := result.ModuleForTests("withcompose", "android_common")
+	noCompose := result.ModuleForTests("nocompose", "android_common")
+
+	android.AssertStringListContains(t, "missing compose compiler dependency",
+		withCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String())
+
+	android.AssertStringDoesContain(t, "missing compose compiler plugin",
+		withCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
+
+	android.AssertStringListDoesNotContain(t, "unexpected compose compiler dependency",
+		noCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String())
+
+	android.AssertStringDoesNotContain(t, "unexpected compose compiler plugin",
+		noCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
+}
diff --git a/java/legacy_core_platform_api_usage.go b/java/legacy_core_platform_api_usage.go
index 8c401a7..e3396c1 100644
--- a/java/legacy_core_platform_api_usage.go
+++ b/java/legacy_core_platform_api_usage.go
@@ -20,6 +20,8 @@
 )
 
 var legacyCorePlatformApiModules = []string{
+	"AAECarSystemUI",
+	"AAECarSystemUI-tests",
 	"ArcSettings",
 	"ahat-test-dump",
 	"android.car",
@@ -30,28 +32,33 @@
 	"api-stubs-docs",
 	"art_cts_jvmti_test_library",
 	"art-gtest-jars-MyClassNatives",
+	"BackupEncryption",
 	"BackupFrameworksServicesRoboTests",
 	"backuplib",
 	"BandwidthEnforcementTest",
 	"BlockedNumberProvider",
 	"BluetoothInstrumentationTests",
+	"BluetoothMidiLib",
 	"BluetoothMidiService",
-	"CarDeveloperOptions",
+	"BTTestApp",
+	"CallEnhancement",
+	"CapCtrlInterface",
 	"CarService",
 	"CarServiceTest",
-	"car-apps-common",
 	"car-service-test-lib",
 	"car-service-test-static-lib",
 	"CertInstaller",
+	"com.qti.location.sdk",
 	"com.qti.media.secureprocessor",
 	"ConnectivityManagerTest",
 	"ContactsProvider",
 	"CorePerfTests",
 	"core-tests-support",
+	"cronet_impl_common_java",
+	"cronet_impl_native_java",
+	"cronet_impl_platform_java",
 	"CtsAppExitTestCases",
 	"CtsContentTestCases",
-	"CtsIkeTestCases",
-	"CtsAppExitTestCases",
 	"CtsLibcoreWycheproofBCTestCases",
 	"CtsMediaTestCases",
 	"CtsNetTestCases",
@@ -64,8 +71,10 @@
 	"DeviceInfo",
 	"DiagnosticTools",
 	"DisplayCutoutEmulationEmu01Overlay",
+	"DocumentsUIGoogleTests",
 	"DocumentsUIPerfTests",
 	"DocumentsUITests",
+	"DocumentsUIUnitTests",
 	"DownloadProvider",
 	"DownloadProviderTests",
 	"DownloadProviderUi",
@@ -75,10 +84,12 @@
 	"ethernet-service",
 	"EthernetServiceTests",
 	"ExternalStorageProvider",
-	"ExtServices",
-	"ExtServices-core",
-	"framework-all",
+	"face-V1-0-javalib",
+	"FloralClocks",
+	"framework-jobscheduler",
 	"framework-minus-apex",
+	"framework-minus-apex-intdefs",
+	"FrameworkOverlayG6QU3",
 	"FrameworksCoreTests",
 	"FrameworksIkeTests",
 	"FrameworksNetCommonTests",
@@ -87,29 +98,50 @@
 	"FrameworksServicesTests",
 	"FrameworksMockingServicesTests",
 	"FrameworksUtilTests",
-	"FrameworksWifiTests",
+	"GtsIncrementalInstallTestCases",
+	"GtsIncrementalInstallTriggerApp",
+	"GtsInstallerV2TestCases",
+	"HelloOslo",
 	"hid",
 	"hidl_test_java_java",
 	"hwbinder",
-	"ims",
+	"imssettings",
+	"izat.lib.glue",
 	"KeyChain",
-	"ksoap2",
+	"LocalSettingsLib",
 	"LocalTransport",
 	"lockagent",
 	"mediaframeworktest",
-	"MediaProvider",
+	"mediatek-ims-base",
 	"MmsService",
-	"MtpDocumentsProvider",
+	"ModemTestMode",
+	"MtkCapCtrl",
+	"MtpService",
 	"MultiDisplayProvider",
+	"my.tests.snapdragonsdktest",
+	"NetworkSetting",
 	"NetworkStackIntegrationTestsLib",
 	"NetworkStackNextIntegrationTests",
 	"NetworkStackNextTests",
 	"NetworkStackTests",
 	"NetworkStackTestsLib",
-	"NfcNci",
+	"online-gcm-ref-docs",
+	"online-gts-docs",
+	"PerformanceMode",
 	"platform_library-docs",
+	"PowerStatsService",
 	"PrintSpooler",
+	"pxp-monitor",
+	"QColor",
+	"qcom.fmradio",
+	"QDCMMobileApp",
+	"Qmmi",
+	"QPerformance",
+	"remotesimlockmanagerlibrary",
 	"RollbackTest",
+	"sam",
+	"saminterfacelibrary",
+	"sammanagerlibrary",
 	"service-blobstore",
 	"service-connectivity-pre-jarjar",
 	"service-jobscheduler",
@@ -123,21 +155,50 @@
 	"services.usb",
 	"Settings-core",
 	"SettingsGoogle",
+	"SettingsGoogleOverlayCoral",
+	"SettingsGoogleOverlayFlame",
 	"SettingsLib",
+	"SettingsOverlayG020A",
+	"SettingsOverlayG020B",
+	"SettingsOverlayG020C",
+	"SettingsOverlayG020D",
+	"SettingsOverlayG020E",
+	"SettingsOverlayG020E_VN",
+	"SettingsOverlayG020F",
+	"SettingsOverlayG020F_VN",
+	"SettingsOverlayG020G",
+	"SettingsOverlayG020G_VN",
+	"SettingsOverlayG020H",
+	"SettingsOverlayG020H_VN",
+	"SettingsOverlayG020I",
+	"SettingsOverlayG020I_VN",
+	"SettingsOverlayG020J",
+	"SettingsOverlayG020M",
+	"SettingsOverlayG020N",
+	"SettingsOverlayG020P",
+	"SettingsOverlayG020Q",
+	"SettingsOverlayG025H",
+	"SettingsOverlayG025J",
+	"SettingsOverlayG025M",
+	"SettingsOverlayG025N",
+	"SettingsOverlayG5NZ6",
 	"SettingsProvider",
 	"SettingsProviderTest",
 	"SettingsRoboTests",
 	"Shell",
 	"ShellTests",
+	"SimContact",
+	"SimContacts",
+	"SimSettings",
 	"sl4a.Common",
 	"StatementService",
 	"SystemUI-core",
 	"SystemUISharedLib",
 	"SystemUI-tests",
+	"tcmiface",
 	"Telecom",
 	"TelecomUnitTests",
 	"telephony-common",
-	"TelephonyProvider",
 	"TelephonyProviderTests",
 	"TeleService",
 	"testables",
@@ -147,12 +208,16 @@
 	"time_zone_distro_installer-tests",
 	"time_zone_distro-tests",
 	"time_zone_updater",
+	"TMobilePlanProvider",
 	"TvProvider",
 	"uiautomator-stubs-docs",
+	"uimgbamanagerlibrary",
 	"UsbHostExternalManagementTestApp",
 	"UserDictionaryProvider",
+	"UxPerformance",
 	"WallpaperBackup",
-	"wifi-service",
+	"WallpaperBackupAgentTests",
+	"WfdCommon",
 }
 
 var legacyCorePlatformApiLookup = make(map[string]struct{})
@@ -163,17 +228,27 @@
 	}
 }
 
-func useLegacyCorePlatformApi(ctx android.EarlyModuleContext) bool {
-	return useLegacyCorePlatformApiByName(ctx.ModuleName())
+var legacyCorePlatformApiLookupKey = android.NewOnceKey("legacyCorePlatformApiLookup")
+
+func getLegacyCorePlatformApiLookup(config android.Config) map[string]struct{} {
+	return config.Once(legacyCorePlatformApiLookupKey, func() interface{} {
+		return legacyCorePlatformApiLookup
+	}).(map[string]struct{})
 }
 
-func useLegacyCorePlatformApiByName(name string) bool {
-	_, found := legacyCorePlatformApiLookup[name]
+// useLegacyCorePlatformApi checks to see whether the supplied module name is in the list of modules
+// that are able to use the legacy core platform API and returns true if it does, false otherwise.
+//
+// This method takes the module name separately from the context as this may be being called for a
+// module that is not the target of the supplied context.
+func useLegacyCorePlatformApi(ctx android.EarlyModuleContext, moduleName string) bool {
+	lookup := getLegacyCorePlatformApiLookup(ctx.Config())
+	_, found := lookup[moduleName]
 	return found
 }
 
 func corePlatformSystemModules(ctx android.EarlyModuleContext) string {
-	if useLegacyCorePlatformApi(ctx) {
+	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
 		return config.LegacyCorePlatformSystemModules
 	} else {
 		return config.StableCorePlatformSystemModules
@@ -181,7 +256,7 @@
 }
 
 func corePlatformBootclasspathLibraries(ctx android.EarlyModuleContext) []string {
-	if useLegacyCorePlatformApi(ctx) {
+	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
 		return config.LegacyCorePlatformBootclasspathLibraries
 	} else {
 		return config.StableCorePlatformBootclasspathLibraries
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 36baf7e..1e27238 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -68,7 +68,6 @@
 func platformBootclasspathFactory() android.SingletonModule {
 	m := &platformBootclasspathModule{}
 	m.AddProperties(&m.properties)
-	// TODO(satayev): split apex jars into separate configs.
 	initClasspathFragment(m, BOOTCLASSPATH)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
@@ -424,14 +423,6 @@
 	// Generate the framework profile rule
 	bootFrameworkProfileRule(ctx, imageConfig)
 
-	// If always using prebuilt sdks then do not generate the updatable-bcp-packages.txt file as it
-	// will break because the prebuilts do not yet specify a permitted_packages property.
-	// TODO(b/193889859): Remove when the prebuilts have been updated.
-	if !ctx.Config().AlwaysUsePrebuiltSdks() {
-		// Generate the updatable bootclasspath packages rule.
-		generateUpdatableBcpPackagesRule(ctx, imageConfig, apexModules)
-	}
-
 	// Copy platform module dex jars to their predefined locations.
 	platformBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, platformModules)
 	copyBootJarsToPredefinedLocations(ctx, platformBootDexJarsByModule, imageConfig.dexPathsByModule)
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 0d8ebac..f442ddf 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -115,7 +115,7 @@
 		Include:    "$(BUILD_PREBUILT)",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base())
 			},
 		},
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index c33e6c2..c67e2bd 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -167,30 +167,24 @@
 		localPath := strings.TrimPrefix(f, mydir)
 		module, apiver, scope := parseJarPath(localPath)
 		createImport(mctx, module, scope, apiver, localPath, sdkVersion, compileDex)
+
+		if module == "core-for-system-modules" {
+			createSystemModules(mctx, apiver, scope)
+		}
 	}
 }
 
-func createSystemModules(mctx android.LoadHookContext, apiver string) {
+func createSystemModules(mctx android.LoadHookContext, apiver string, scope string) {
 	props := struct {
 		Name *string
 		Libs []string
 	}{}
-	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", "public", apiver))
-	props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", "public", apiver))
+	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", scope, apiver))
+	props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", scope, apiver))
 
 	mctx.CreateModule(systemModulesImportFactory, &props)
 }
 
-func prebuiltSdkSystemModules(mctx android.LoadHookContext, p *prebuiltApis) {
-	for _, apiver := range p.properties.Api_dirs {
-		jar := android.ExistentPathForSource(mctx,
-			mctx.ModuleDir(), apiver, "public", "core-for-system-modules.jar")
-		if jar.Valid() {
-			createSystemModules(mctx, apiver)
-		}
-	}
-}
-
 func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) {
 	mydir := mctx.ModuleDir() + "/"
 	// <apiver>/<scope>/api/<module>.txt
@@ -273,7 +267,6 @@
 	if p, ok := mctx.Module().(*prebuiltApis); ok {
 		prebuiltApiFiles(mctx, p)
 		prebuiltSdkStubs(mctx, p)
-		prebuiltSdkSystemModules(mctx, p)
 	}
 }
 
diff --git a/java/prebuilt_apis_test.go b/java/prebuilt_apis_test.go
new file mode 100644
index 0000000..79f4225
--- /dev/null
+++ b/java/prebuilt_apis_test.go
@@ -0,0 +1,56 @@
+// Copyright 2021 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 java
+
+import (
+	"sort"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+func TestPrebuiltApis_SystemModulesCreation(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"31":      {},
+			"32":      {},
+			"current": {},
+		}),
+	).RunTest(t)
+
+	sdkSystemModules := []string{}
+	result.VisitAllModules(func(module blueprint.Module) {
+		name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+		if strings.HasPrefix(name, "sdk_") && strings.HasSuffix(name, "_system_modules") {
+			sdkSystemModules = append(sdkSystemModules, name)
+		}
+	})
+	sort.Strings(sdkSystemModules)
+	expected := []string{
+		// 31 only has public system modules.
+		"sdk_public_31_system_modules",
+
+		// 32 and current both have public and module-lib system modules.
+		"sdk_public_32_system_modules",
+		"sdk_module-lib_32_system_modules",
+		"sdk_public_current_system_modules",
+		"sdk_module-lib_current_system_modules",
+	}
+	sort.Strings(expected)
+	android.AssertArrayString(t, "sdk system modules", expected, sdkSystemModules)
+}
diff --git a/java/proto.go b/java/proto.go
index 8731822..ab913d8 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -24,7 +24,7 @@
 func genProto(ctx android.ModuleContext, protoFiles android.Paths, flags android.ProtoFlags) android.Paths {
 	// Shard proto files into groups of 100 to avoid having to recompile all of them if one changes and to avoid
 	// hitting command line length limits.
-	shards := android.ShardPaths(protoFiles, 100)
+	shards := android.ShardPaths(protoFiles, 50)
 
 	srcJarFiles := make(android.Paths, 0, len(shards))
 
@@ -102,6 +102,9 @@
 	if String(p.Proto.Plugin) == "" {
 		var typeToPlugin string
 		switch String(p.Proto.Type) {
+		case "stream":
+			flags.proto.OutTypeFlag = "--javastream_out"
+			typeToPlugin = "javastream"
 		case "micro":
 			flags.proto.OutTypeFlag = "--javamicro_out"
 			typeToPlugin = "javamicro"
diff --git a/java/robolectric.go b/java/robolectric.go
index a0c9c7f..f719521 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -212,7 +212,7 @@
 		installDeps = append(installDeps, installedData)
 	}
 
-	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.combinedJar, installDeps...)
+	r.installFile = ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.combinedJar, installDeps...)
 }
 
 func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath,
@@ -276,6 +276,10 @@
 func (r *robolectricTest) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := r.Library.AndroidMkEntries()
 	entries := &entriesList[0]
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+		})
 
 	entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
 		func(w io.Writer, name, prefix, moduleDir string) {
@@ -348,7 +352,6 @@
 	return module
 }
 
-func (r *robolectricTest) InstallBypassMake() bool  { return true }
 func (r *robolectricTest) InstallInTestcases() bool { return true }
 func (r *robolectricTest) InstallForceOS() (*android.OsType, *android.ArchType) {
 	return &r.forceOSType, &r.forceArchType
@@ -417,16 +420,15 @@
 		}
 		runtimeFromSourceJar := android.OutputFileForModule(ctx, runtimeFromSourceModule, "")
 
-		// TODO(murj) Update this to ctx.Config().PlatformSdkCodename() once the platform
-		// classes like android.os.Build are updated to S.
-		runtimeName := fmt.Sprintf("android-all-%s-robolectric-r0.jar",
-			"R")
+		// "TREE" name is essential here because it hooks into the "TREE" name in
+		// Robolectric's SdkConfig.java that will always correspond to the NEWEST_SDK
+		// in Robolectric configs.
+		runtimeName := "android-all-current-robolectric-r0.jar"
 		installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar)
 		r.runtimes = append(r.runtimes, installedRuntime)
 	}
 }
 
-func (r *robolectricRuntimes) InstallBypassMake() bool  { return true }
 func (r *robolectricRuntimes) InstallInTestcases() bool { return true }
 func (r *robolectricRuntimes) InstallForceOS() (*android.OsType, *android.ArchType) {
 	return &r.forceOSType, &r.forceArchType
diff --git a/java/rro_test.go b/java/rro_test.go
index 27abbe4..be0d7ba 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -62,6 +62,7 @@
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		PrepareForTestWithOverlayBuildComponents,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
 		fs.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
@@ -127,7 +128,10 @@
 }
 
 func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
-	ctx, config := testJava(t, `
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		java_defaults {
 			name: "rro_defaults",
 			theme: "default_theme",
@@ -148,7 +152,7 @@
 	//
 	// RRO module with defaults
 	//
-	m := ctx.ModuleForTests("foo_with_defaults", "android_common")
+	m := result.ModuleForTests("foo_with_defaults", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -159,14 +163,14 @@
 	}
 
 	// Check device location.
-	path := android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	path := android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath := []string{shared.JoinPath("out/target/product/test_device/product/overlay/default_theme")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	//
 	// RRO module without defaults
 	//
-	m = ctx.ModuleForTests("foo_barebones", "android_common")
+	m = result.ModuleForTests("foo_barebones", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -176,9 +180,9 @@
 	}
 
 	// Check device location.
-	path = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	path = android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 }
 
 func TestOverrideRuntimeResourceOverlay(t *testing.T) {
diff --git a/java/sdk.go b/java/sdk.go
index d1b899e..756a24d 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -55,11 +55,41 @@
 		return JAVA_VERSION_7
 	} else if sdk.FinalOrFutureInt() <= 29 {
 		return JAVA_VERSION_8
+	} else if ctx.Config().TargetsJava11() {
+		// Temporary experimental flag to be able to try and build with
+		// java version 11 options. The flag, if used, just sets Java
+		// 11 as the default version, leaving any components that
+		// target an older version intact.
+		return JAVA_VERSION_11
 	} else {
 		return JAVA_VERSION_9
 	}
 }
 
+// systemModuleKind returns the kind of system modules to use for the supplied combination of sdk
+// kind and API level.
+func systemModuleKind(sdkKind android.SdkKind, apiLevel android.ApiLevel) android.SdkKind {
+	systemModuleKind := sdkKind
+	if apiLevel.LessThanOrEqualTo(android.LastWithoutModuleLibCoreSystemModules) {
+		// API levels less than or equal to 31 did not provide a core-for-system-modules.jar
+		// specifically for the module-lib API. So, always use the public system modules for them.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkCore {
+		// Core is by definition what is included in the system module for the public API so should
+		// just use its system modules.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkSystem || systemModuleKind == android.SdkTest {
+		// The core system and test APIs are currently the same as the public API so they should use
+		// its system modules.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkSystemServer {
+		// The core system server API is the same as the core module-lib API.
+		systemModuleKind = android.SdkModule
+	}
+
+	return systemModuleKind
+}
+
 func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext android.SdkContext) sdkDep {
 	sdkVersion := sdkContext.SdkVersion(ctx)
 	if !sdkVersion.Valid() {
@@ -105,7 +135,8 @@
 
 		var systemModules string
 		if defaultJavaLanguageVersion(ctx, sdkVersion).usesJavaModules() {
-			systemModules = "sdk_public_" + sdkVersion.ApiLevel.String() + "_system_modules"
+			systemModuleKind := systemModuleKind(sdkVersion.Kind, sdkVersion.ApiLevel)
+			systemModules = fmt.Sprintf("sdk_%s_%s_system_modules", systemModuleKind, sdkVersion.ApiLevel)
 		}
 
 		return sdkDep{
@@ -116,13 +147,15 @@
 		}
 	}
 
-	toModule := func(modules []string, res string, aidl android.Path) sdkDep {
+	toModule := func(module string, aidl android.Path) sdkDep {
+		// Select the kind of system modules needed for the sdk version.
+		systemModulesKind := systemModuleKind(sdkVersion.Kind, android.FutureApiLevel)
 		return sdkDep{
 			useModule:          true,
-			bootclasspath:      append(modules, config.DefaultLambdaStubsLibrary),
-			systemModules:      "core-current-stubs-system-modules",
-			java9Classpath:     modules,
-			frameworkResModule: res,
+			bootclasspath:      []string{module, config.DefaultLambdaStubsLibrary},
+			systemModules:      fmt.Sprintf("core-%s-stubs-system-modules", systemModulesKind),
+			java9Classpath:     []string{module},
+			frameworkResModule: "framework-res",
 			aidl:               android.OptionalPathForPath(aidl),
 		}
 	}
@@ -161,38 +194,24 @@
 			noFrameworksLibs: true,
 		}
 	case android.SdkPublic:
-		return toModule([]string{"android_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkSystem:
-		return toModule([]string{"android_system_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_system_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkTest:
-		return toModule([]string{"android_test_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_test_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkCore:
 		return sdkDep{
 			useModule:        true,
 			bootclasspath:    []string{"core.current.stubs", config.DefaultLambdaStubsLibrary},
-			systemModules:    "core-current-stubs-system-modules",
+			systemModules:    "core-public-stubs-system-modules",
 			noFrameworksLibs: true,
 		}
 	case android.SdkModule:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return sdkDep{
-			useModule:          true,
-			bootclasspath:      []string{"android_module_lib_stubs_current", config.DefaultLambdaStubsLibrary},
-			systemModules:      "core-module-lib-stubs-system-modules",
-			java9Classpath:     []string{"android_module_lib_stubs_current"},
-			frameworkResModule: "framework-res",
-			aidl:               android.OptionalPathForPath(nonUpdatableFrameworkAidlPath(ctx)),
-		}
+		return toModule("android_module_lib_stubs_current", nonUpdatableFrameworkAidlPath(ctx))
 	case android.SdkSystemServer:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return sdkDep{
-			useModule:          true,
-			bootclasspath:      []string{"android_system_server_stubs_current", config.DefaultLambdaStubsLibrary},
-			systemModules:      "core-module-lib-stubs-system-modules",
-			java9Classpath:     []string{"android_system_server_stubs_current"},
-			frameworkResModule: "framework-res",
-			aidl:               android.OptionalPathForPath(sdkFrameworkAidlPath(ctx)),
-		}
+		return toModule("android_system_server_stubs_current", sdkFrameworkAidlPath(ctx))
 	default:
 		panic(fmt.Errorf("invalid sdk %q", sdkVersion.Raw))
 	}
@@ -370,7 +389,7 @@
 			"frameworks-base-api-current.txt",
 			"frameworks-base-api-system-current.txt",
 			"frameworks-base-api-module-lib-current.txt",
-			"services-system-server-current.txt",
+			"frameworks-base-api-system-server-current.txt",
 		}
 		count := 0
 		ctx.VisitAllModules(func(module android.Module) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index ce8f179..0bc8895 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -21,6 +21,7 @@
 	"reflect"
 	"regexp"
 	"sort"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -32,25 +33,7 @@
 )
 
 const (
-	sdkXmlFileSuffix    = ".xml"
-	permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` +
-		`<!-- Copyright (C) 2018 The Android Open Source Project\n` +
-		`\n` +
-		`    Licensed under the Apache License, Version 2.0 (the \"License\");\n` +
-		`    you may not use this file except in compliance with the License.\n` +
-		`    You may obtain a copy of the License at\n` +
-		`\n` +
-		`        http://www.apache.org/licenses/LICENSE-2.0\n` +
-		`\n` +
-		`    Unless required by applicable law or agreed to in writing, software\n` +
-		`    distributed under the License is distributed on an \"AS IS\" BASIS,\n` +
-		`    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n` +
-		`    See the License for the specific language governing permissions and\n` +
-		`    limitations under the License.\n` +
-		`-->\n` +
-		`<permissions>\n` +
-		`    <library name=\"%s\" file=\"%s\"/>\n` +
-		`</permissions>\n`
+	sdkXmlFileSuffix = ".xml"
 )
 
 // A tag to associated a dependency with a specific api scope.
@@ -361,13 +344,14 @@
 	// The sdk_version to use for building the stubs.
 	//
 	// If not specified then it will use an sdk_version determined as follows:
+	//
 	// 1) If the sdk_version specified on the java_sdk_library is none then this
-	//    will be none. This is used for java_sdk_library instances that are used
-	//    to create stubs that contribute to the core_current sdk version.
-	// 2) Otherwise, it is assumed that this library extends but does not contribute
-	//    directly to a specific sdk_version and so this uses the sdk_version appropriate
-	//    for the api scope. e.g. public will use sdk_version: current, system will use
-	//    sdk_version: system_current, etc.
+	// will be none. This is used for java_sdk_library instances that are used
+	// to create stubs that contribute to the core_current sdk version.
+	// 2) Otherwise, it is assumed that this library extends but does not
+	// contribute directly to a specific sdk_version and so this uses the
+	// sdk_version appropriate for the api scope. e.g. public will use
+	// sdk_version: current, system will use sdk_version: system_current, etc.
 	//
 	// This does not affect the sdk_version used for either generating the stubs source
 	// or the API file. They both have to use the same sdk_version as is used for
@@ -376,6 +360,9 @@
 }
 
 type sdkLibraryProperties struct {
+	// List of source files that are needed to compile the API, but are not part of runtime library.
+	Api_srcs []string `android:"arch_variant"`
+
 	// Visibility for impl library module. If not specified then defaults to the
 	// visibility property.
 	Impl_library_visibility []string
@@ -537,7 +524,7 @@
 	// The dex jar for the stubs.
 	//
 	// This is not the implementation jar, it still only contains stubs.
-	stubsDexJarPath android.Path
+	stubsDexJarPath OptionalDexJarPath
 
 	// The API specification file, e.g. system_current.txt.
 	currentApiFilePath android.OptionalPath
@@ -547,6 +534,9 @@
 
 	// The stubs source jar.
 	stubsSrcJar android.OptionalPath
+
+	// Extracted annotations.
+	annotationsZip android.OptionalPath
 }
 
 func (paths *scopePaths) extractStubsLibraryInfoFromDependency(ctx android.ModuleContext, dep android.Module) error {
@@ -582,6 +572,7 @@
 }
 
 func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider ApiStubsProvider) {
+	paths.annotationsZip = android.OptionalPathForPath(provider.AnnotationsZip())
 	paths.currentApiFilePath = android.OptionalPathForPath(provider.ApiFilePath())
 	paths.removedApiFilePath = android.OptionalPathForPath(provider.RemovedApiFilePath())
 }
@@ -629,6 +620,33 @@
 
 	// Files containing information about supported java doc tags.
 	Doctag_files []string `android:"path"`
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
 }
 
 // commonSdkLibraryAndImportModule defines the interface that must be provided by a module that
@@ -736,6 +754,8 @@
 	apiTxtComponentName = "api.txt"
 
 	removedApiTxtComponentName = "removed-api.txt"
+
+	annotationsComponentName = "annotations.zip"
 )
 
 // A regular expression to match tags that reference a specific stubs component.
@@ -754,7 +774,7 @@
 	scopesRegexp := choice(allScopeNames...)
 
 	// Regular expression to match one of the components.
-	componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName)
+	componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName, annotationsComponentName)
 
 	// Regular expression to match any combination of one scope and one component.
 	return regexp.MustCompile(fmt.Sprintf(`^\.(%s)\.(%s)$`, scopesRegexp, componentsRegexp))
@@ -762,9 +782,7 @@
 
 // For OutputFileProducer interface
 //
-// .<scope>.stubs.source
-// .<scope>.api.txt
-// .<scope>.removed-api.txt
+// .<scope>.<component name>, for all ComponentNames (for example: .public.removed-api.txt)
 func (c *commonToSdkLibraryAndImport) commonOutputFiles(tag string) (android.Paths, error) {
 	if groups := tagSplitter.FindStringSubmatch(tag); groups != nil {
 		scopeName := groups[1]
@@ -791,6 +809,11 @@
 				if paths.removedApiFilePath.Valid() {
 					return android.Paths{paths.removedApiFilePath.Path()}, nil
 				}
+
+			case annotationsComponentName:
+				if paths.annotationsZip.Valid() {
+					return android.Paths{paths.annotationsZip.Path()}, nil
+				}
 			}
 
 			return nil, fmt.Errorf("%s not available for api scope %s", component, scopeName)
@@ -903,10 +926,10 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path {
+func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) OptionalDexJarPath {
 	paths := c.selectScopePaths(ctx, kind)
 	if paths == nil {
-		return nil
+		return makeUnsetDexJarPath()
 	}
 
 	return paths.stubsDexJarPath
@@ -1032,7 +1055,7 @@
 
 	// SdkApiStubDexJar returns the dex jar for the stubs. It is needed by the hiddenapi processing
 	// tool which processes dex files.
-	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path
+	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) OptionalDexJarPath
 
 	// SdkRemovedTxtFile returns the optional path to the removed.txt file for the specified sdk kind.
 	SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath
@@ -1106,6 +1129,22 @@
 	return generatedScopes
 }
 
+var _ android.ModuleWithMinSdkVersionCheck = (*SdkLibrary)(nil)
+
+func (module *SdkLibrary) CheckMinSdkVersion(ctx android.ModuleContext) {
+	android.CheckMinSdkVersion(ctx, module.MinSdkVersion(ctx).ApiLevel, func(c android.ModuleContext, do android.PayloadDepsCallback) {
+		ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
+			isExternal := !module.depIsInSameApex(ctx, child)
+			if am, ok := child.(android.ApexModule); ok {
+				if !do(ctx, parent, am, isExternal) {
+					return false
+				}
+			}
+			return !isExternal
+		})
+	})
+}
+
 type sdkLibraryComponentTag struct {
 	blueprint.BaseDependencyTag
 	name string
@@ -1183,14 +1222,23 @@
 
 func (module *SdkLibrary) OutputFiles(tag string) (android.Paths, error) {
 	paths, err := module.commonOutputFiles(tag)
-	if paths == nil && err == nil {
-		return module.Library.OutputFiles(tag)
-	} else {
+	if paths != nil || err != nil {
 		return paths, err
 	}
+	if module.requiresRuntimeImplementationLibrary() {
+		return module.Library.OutputFiles(tag)
+	}
+	if tag == "" {
+		return nil, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 }
 
 func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if proptools.String(module.deviceProperties.Min_sdk_version) != "" {
+		module.CheckMinSdkVersion(ctx)
+	}
+
 	module.generateCommonBuildActions(ctx)
 
 	// Only build an implementation library if required.
@@ -1344,6 +1392,10 @@
 			Srcs       []string
 			Javacflags []string
 		}
+		Openjdk11 struct {
+			Srcs       []string
+			Javacflags []string
+		}
 		Dist struct {
 			Targets []string
 			Dest    *string
@@ -1370,6 +1422,8 @@
 	}
 	props.Openjdk9.Srcs = module.properties.Openjdk9.Srcs
 	props.Openjdk9.Javacflags = module.properties.Openjdk9.Javacflags
+	props.Openjdk11.Srcs = module.properties.Openjdk11.Srcs
+	props.Openjdk11.Javacflags = module.properties.Openjdk11.Javacflags
 	// We compile the stubs for 1.8 in line with the main android.jar stubs, and potential
 	// interop with older developer tools that don't support 1.9.
 	props.Java_version = proptools.StringPtr("1.8")
@@ -1438,6 +1492,7 @@
 	props.Name = proptools.StringPtr(name)
 	props.Visibility = childModuleVisibility(module.sdkLibraryProperties.Stubs_source_visibility)
 	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.System_modules = module.deviceProperties.System_modules
 	props.Installable = proptools.BoolPtr(false)
@@ -1566,14 +1621,29 @@
 
 // Creates the xml file that publicizes the runtime library
 func (module *SdkLibrary) createXmlFile(mctx android.DefaultableHookContext) {
+	moduleMinApiLevel := module.Library.MinSdkVersion(mctx).ApiLevel
+	var moduleMinApiLevelStr = moduleMinApiLevel.String()
+	if moduleMinApiLevel == android.NoneApiLevel {
+		moduleMinApiLevelStr = "current"
+	}
 	props := struct {
-		Name           *string
-		Lib_name       *string
-		Apex_available []string
+		Name                      *string
+		Lib_name                  *string
+		Apex_available            []string
+		On_bootclasspath_since    *string
+		On_bootclasspath_before   *string
+		Min_device_sdk            *string
+		Max_device_sdk            *string
+		Sdk_library_min_api_level *string
 	}{
-		Name:           proptools.StringPtr(module.xmlPermissionsModuleName()),
-		Lib_name:       proptools.StringPtr(module.BaseModuleName()),
-		Apex_available: module.ApexProperties.Apex_available,
+		Name:                      proptools.StringPtr(module.xmlPermissionsModuleName()),
+		Lib_name:                  proptools.StringPtr(module.BaseModuleName()),
+		Apex_available:            module.ApexProperties.Apex_available,
+		On_bootclasspath_since:    module.commonSdkLibraryProperties.On_bootclasspath_since,
+		On_bootclasspath_before:   module.commonSdkLibraryProperties.On_bootclasspath_before,
+		Min_device_sdk:            module.commonSdkLibraryProperties.Min_device_sdk,
+		Max_device_sdk:            module.commonSdkLibraryProperties.Max_device_sdk,
+		Sdk_library_min_api_level: &moduleMinApiLevelStr,
 	}
 
 	mctx.CreateModule(sdkLibraryXmlFactory, &props)
@@ -1884,6 +1954,9 @@
 
 	// The removed.txt
 	Removed_api *string `android:"path"`
+
+	// Annotation zip
+	Annotations *string `android:"path"`
 }
 
 type sdkLibraryImportProperties struct {
@@ -1895,7 +1968,6 @@
 	Compile_dex *bool
 
 	// If not empty, classes are restricted to the specified packages and their sub-packages.
-	// This information is used to generate the updatable-bcp-packages.txt file.
 	Permitted_packages []string
 }
 
@@ -1907,6 +1979,7 @@
 	android.SdkBase
 
 	hiddenAPI
+	dexpreopter
 
 	properties sdkLibraryImportProperties
 
@@ -1924,7 +1997,7 @@
 	xmlPermissionsFileModule *sdkLibraryXml
 
 	// Build path to the dex implementation jar obtained from the prebuilt_apex, if any.
-	dexJarFile android.Path
+	dexJarFile OptionalDexJarPath
 
 	// Expected install file path of the source module(sdk_library)
 	// or dex implementation jar obtained from the prebuilt_apex, if any.
@@ -2111,6 +2184,14 @@
 	}
 }
 
+func (module *SdkLibraryImport) AndroidMkEntries() []android.AndroidMkEntries {
+	// For an SDK library imported from a prebuilt APEX, we don't need a Make module for itself, as we
+	// don't need to install it. However, we need to add its dexpreopt outputs as sub-modules, if it
+	// is preopted.
+	dexpreoptEntries := module.dexpreopter.AndroidMkEntriesForApex()
+	return append(dexpreoptEntries, android.AndroidMkEntries{Disabled: true})
+}
+
 var _ android.ApexModule = (*SdkLibraryImport)(nil)
 
 // Implements android.ApexModule
@@ -2144,8 +2225,6 @@
 func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	module.generateCommonBuildActions(ctx)
 
-	var deapexerModule android.Module
-
 	// Assume that source module(sdk_library) is installed in /<sdk_library partition>/framework
 	module.installFile = android.PathForModuleInstall(ctx, "framework", module.Stem()+".jar")
 
@@ -2174,11 +2253,6 @@
 				ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to)
 			}
 		}
-
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			deapexerModule = to
-		}
 	})
 
 	// Populate the scope paths with information from the properties.
@@ -2188,6 +2262,7 @@
 		}
 
 		paths := module.getScopePathsCreateIfNeeded(apiScope)
+		paths.annotationsZip = android.OptionalPathForModuleSrc(ctx, scopeProperties.Annotations)
 		paths.currentApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Current_api)
 		paths.removedApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Removed_api)
 	}
@@ -2197,23 +2272,28 @@
 		// obtained from the associated deapexer module.
 		ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if ai.ForPrebuiltApex {
-			if deapexerModule == nil {
-				// This should never happen as a variant for a prebuilt_apex is only created if the
-				// deapxer module has been configured to export the dex implementation jar for this module.
-				ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
-					module.Name(), ai.ApexVariationName)
-			}
-
 			// Get the path of the dex implementation jar from the `deapexer` module.
-			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+			di := android.FindDeapexerProviderForModule(ctx)
+			if di == nil {
+				return // An error has been reported by FindDeapexerProviderForModule.
+			}
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(module.BaseModuleName())); dexOutputPath != nil {
-				module.dexJarFile = dexOutputPath
-				module.installFile = android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(module.BaseModuleName()))
-				module.initHiddenAPI(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
+				dexJarFile := makeDexJarPathFromPath(dexOutputPath)
+				module.dexJarFile = dexJarFile
+				installPath := android.PathForModuleInPartitionInstall(
+					ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(module.BaseModuleName()))
+				module.installFile = installPath
+				module.initHiddenAPI(ctx, dexJarFile, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
+
+				// Dexpreopting.
+				module.dexpreopter.installPath = module.dexpreopter.getInstallPath(ctx, installPath)
+				module.dexpreopter.isSDKLibrary = true
+				module.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &module.dexpreopter)
+				module.dexpreopt(ctx, dexOutputPath)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
-				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt APEX %s", di.ApexModuleName())
 			}
 		}
 	}
@@ -2248,14 +2328,14 @@
 }
 
 // to satisfy UsesLibraryDependency interface
-func (module *SdkLibraryImport) DexJarBuildPath() android.Path {
+func (module *SdkLibraryImport) DexJarBuildPath() OptionalDexJarPath {
 	// The dex implementation jar extracted from the .apex file should be used in preference to the
 	// source.
-	if module.dexJarFile != nil {
+	if module.dexJarFile.IsSet() {
 		return module.dexJarFile
 	}
 	if module.implLibraryModule == nil {
-		return nil
+		return makeUnsetDexJarPath()
 	} else {
 		return module.implLibraryModule.DexJarBuildPath()
 	}
@@ -2328,6 +2408,11 @@
 	}
 }
 
+// to satisfy java.DexpreopterInterface interface
+func (module *SdkLibraryImport) IsInstallable() bool {
+	return true
+}
+
 var _ android.RequiredFilesFromPrebuiltApex = (*SdkLibraryImport)(nil)
 
 func (module *SdkLibraryImport) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
@@ -2354,6 +2439,38 @@
 type sdkLibraryXmlProperties struct {
 	// canonical name of the lib
 	Lib_name *string
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
+
+	// The SdkLibrary's min api level as a string
+	//
+	// This value comes from the ApiLevel of the MinSdkVersion property.
+	Sdk_library_min_api_level *string
 }
 
 // java_sdk_library_xml builds the permission xml file for a java_sdk_library.
@@ -2430,11 +2547,81 @@
 	return "/" + partition + "/framework/" + implName + ".jar"
 }
 
+func formattedOptionalSdkLevelAttribute(ctx android.ModuleContext, attrName string, value *string) string {
+	if value == nil {
+		return ""
+	}
+	apiLevel, err := android.ApiLevelFromUser(ctx, *value)
+	if err != nil {
+		// attributes in bp files have underscores but in the xml have dashes.
+		ctx.PropertyErrorf(strings.ReplaceAll(attrName, "-", "_"), err.Error())
+		return ""
+	}
+	intStr := strconv.Itoa(apiLevel.FinalOrPreviewInt())
+	return formattedOptionalAttribute(attrName, &intStr)
+}
+
+// formats an attribute for the xml permissions file if the value is not null
+// returns empty string otherwise
+func formattedOptionalAttribute(attrName string, value *string) string {
+	if value == nil {
+		return ""
+	}
+	return fmt.Sprintf(`        %s=\"%s\"\n`, attrName, *value)
+}
+
+func (module *sdkLibraryXml) permissionsContents(ctx android.ModuleContext) string {
+	libName := proptools.String(module.properties.Lib_name)
+	libNameAttr := formattedOptionalAttribute("name", &libName)
+	filePath := module.implPath(ctx)
+	filePathAttr := formattedOptionalAttribute("file", &filePath)
+	implicitFromAttr := formattedOptionalSdkLevelAttribute(ctx, "on-bootclasspath-since", module.properties.On_bootclasspath_since)
+	implicitUntilAttr := formattedOptionalSdkLevelAttribute(ctx, "on-bootclasspath-before", module.properties.On_bootclasspath_before)
+	minSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "min-device-sdk", module.properties.Min_device_sdk)
+	maxSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "max-device-sdk", module.properties.Max_device_sdk)
+	// <library> is understood in all android versions whereas <updatable-library> is only understood from API T (and ignored before that).
+	// similarly, min_device_sdk is only understood from T. So if a library is using that, we need to use the updatable-library to make sure this library is not loaded before T
+	var libraryTag string
+	if module.properties.Min_device_sdk != nil {
+		libraryTag = `    <updatable-library\n`
+	} else {
+		libraryTag = `    <library\n`
+	}
+
+	return strings.Join([]string{
+		`<?xml version=\"1.0\" encoding=\"utf-8\"?>\n`,
+		`<!-- Copyright (C) 2018 The Android Open Source Project\n`,
+		`\n`,
+		`    Licensed under the Apache License, Version 2.0 (the \"License\");\n`,
+		`    you may not use this file except in compliance with the License.\n`,
+		`    You may obtain a copy of the License at\n`,
+		`\n`,
+		`        http://www.apache.org/licenses/LICENSE-2.0\n`,
+		`\n`,
+		`    Unless required by applicable law or agreed to in writing, software\n`,
+		`    distributed under the License is distributed on an \"AS IS\" BASIS,\n`,
+		`    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n`,
+		`    See the License for the specific language governing permissions and\n`,
+		`    limitations under the License.\n`,
+		`-->\n`,
+		`<permissions>\n`,
+		libraryTag,
+		libNameAttr,
+		filePathAttr,
+		implicitFromAttr,
+		implicitUntilAttr,
+		minSdkAttr,
+		maxSdkAttr,
+		`    />\n`,
+		`</permissions>\n`}, "")
+}
+
 func (module *sdkLibraryXml) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	module.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
 	libName := proptools.String(module.properties.Lib_name)
-	xmlContent := fmt.Sprintf(permissionsTemplate, libName, module.implPath(ctx))
+	module.selfValidate(ctx)
+	xmlContent := module.permissionsContents(ctx)
 
 	module.outputFilePath = android.PathForModuleOut(ctx, libName+".xml").OutputPath
 	rule := android.NewRuleBuilder(pctx, ctx)
@@ -2449,24 +2636,99 @@
 
 func (module *sdkLibraryXml) AndroidMkEntries() []android.AndroidMkEntries {
 	if module.hideApexVariantFromMake {
-		return []android.AndroidMkEntries{android.AndroidMkEntries{
+		return []android.AndroidMkEntries{{
 			Disabled: true,
 		}}
 	}
 
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
+	return []android.AndroidMkEntries{{
 		Class:      "ETC",
 		OutputFile: android.OptionalPathForPath(module.outputFilePath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", module.outputFilePath.Base())
 			},
 		},
 	}}
 }
 
+func (module *sdkLibraryXml) selfValidate(ctx android.ModuleContext) {
+	module.validateAtLeastTAttributes(ctx)
+	module.validateMinAndMaxDeviceSdk(ctx)
+	module.validateMinMaxDeviceSdkAndModuleMinSdk(ctx)
+	module.validateOnBootclasspathBeforeRequirements(ctx)
+}
+
+func (module *sdkLibraryXml) validateAtLeastTAttributes(ctx android.ModuleContext) {
+	t := android.ApiLevelOrPanic(ctx, "Tiramisu")
+	module.attrAtLeastT(ctx, t, module.properties.Min_device_sdk, "min_device_sdk")
+	module.attrAtLeastT(ctx, t, module.properties.Max_device_sdk, "max_device_sdk")
+	module.attrAtLeastT(ctx, t, module.properties.On_bootclasspath_before, "on_bootclasspath_before")
+	module.attrAtLeastT(ctx, t, module.properties.On_bootclasspath_since, "on_bootclasspath_since")
+}
+
+func (module *sdkLibraryXml) attrAtLeastT(ctx android.ModuleContext, t android.ApiLevel, attr *string, attrName string) {
+	if attr != nil {
+		if level, err := android.ApiLevelFromUser(ctx, *attr); err == nil {
+			// we will inform the user of invalid inputs when we try to write the
+			// permissions xml file so we don't need to do it here
+			if t.GreaterThan(level) {
+				ctx.PropertyErrorf(attrName, "Attribute value needs to be at least T")
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateMinAndMaxDeviceSdk(ctx android.ModuleContext) {
+	if module.properties.Min_device_sdk != nil && module.properties.Max_device_sdk != nil {
+		min, minErr := android.ApiLevelFromUser(ctx, *module.properties.Min_device_sdk)
+		max, maxErr := android.ApiLevelFromUser(ctx, *module.properties.Max_device_sdk)
+		if minErr == nil && maxErr == nil {
+			// we will inform the user of invalid inputs when we try to write the
+			// permissions xml file so we don't need to do it here
+			if min.GreaterThan(max) {
+				ctx.ModuleErrorf("min_device_sdk can't be greater than max_device_sdk")
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateMinMaxDeviceSdkAndModuleMinSdk(ctx android.ModuleContext) {
+	moduleMinApi := android.ApiLevelOrPanic(ctx, *module.properties.Sdk_library_min_api_level)
+	if module.properties.Min_device_sdk != nil {
+		api, err := android.ApiLevelFromUser(ctx, *module.properties.Min_device_sdk)
+		if err == nil {
+			if moduleMinApi.GreaterThan(api) {
+				ctx.PropertyErrorf("min_device_sdk", "Can't be less than module's min sdk (%s)", moduleMinApi)
+			}
+		}
+	}
+	if module.properties.Max_device_sdk != nil {
+		api, err := android.ApiLevelFromUser(ctx, *module.properties.Max_device_sdk)
+		if err == nil {
+			if moduleMinApi.GreaterThan(api) {
+				ctx.PropertyErrorf("max_device_sdk", "Can't be less than module's min sdk (%s)", moduleMinApi)
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateOnBootclasspathBeforeRequirements(ctx android.ModuleContext) {
+	moduleMinApi := android.ApiLevelOrPanic(ctx, *module.properties.Sdk_library_min_api_level)
+	if module.properties.On_bootclasspath_before != nil {
+		t := android.ApiLevelOrPanic(ctx, "Tiramisu")
+		// if we use the attribute, then we need to do this validation
+		if moduleMinApi.LessThan(t) {
+			// if minAPi is < T, then we need to have min_device_sdk (which only accepts T+)
+			if module.properties.Min_device_sdk == nil {
+				ctx.PropertyErrorf("on_bootclasspath_before", "Using this property requires that the module's min_sdk_version or the shared library's min_device_sdk is at least T")
+			}
+		}
+	}
+}
+
 type sdkLibrarySdkMemberType struct {
 	android.SdkMemberTypeBase
 }
@@ -2518,6 +2780,33 @@
 	Doctag_paths android.Paths
 
 	Permitted_packages []string
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
 }
 
 type scopeProperties struct {
@@ -2525,6 +2814,7 @@
 	StubsSrcJar    android.Path
 	CurrentApiFile android.Path
 	RemovedApiFile android.Path
+	AnnotationsZip android.Path
 	SdkVersion     string
 }
 
@@ -2550,6 +2840,10 @@
 			if paths.removedApiFilePath.Valid() {
 				properties.RemovedApiFile = paths.removedApiFilePath.Path()
 			}
+			// The annotations zip is only available for modules that set annotations_enabled: true.
+			if paths.annotationsZip.Valid() {
+				properties.AnnotationsZip = paths.annotationsZip.Path()
+			}
 			s.Scopes[apiScope] = properties
 		}
 	}
@@ -2559,6 +2853,10 @@
 	s.Compile_dex = sdk.dexProperties.Compile_dex
 	s.Doctag_paths = sdk.doctagPaths
 	s.Permitted_packages = sdk.PermittedPackagesForUpdatableBootJars()
+	s.On_bootclasspath_since = sdk.commonSdkLibraryProperties.On_bootclasspath_since
+	s.On_bootclasspath_before = sdk.commonSdkLibraryProperties.On_bootclasspath_before
+	s.Min_device_sdk = sdk.commonSdkLibraryProperties.Min_device_sdk
+	s.Max_device_sdk = sdk.commonSdkLibraryProperties.Max_device_sdk
 }
 
 func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
@@ -2614,6 +2912,12 @@
 				scopeSet.AddProperty("removed_api", removedApiSnapshotPath)
 			}
 
+			if properties.AnnotationsZip != nil {
+				annotationsSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"_annotations.zip")
+				ctx.SnapshotBuilder().CopyToSnapshot(properties.AnnotationsZip, annotationsSnapshotPath)
+				scopeSet.AddProperty("annotations", annotationsSnapshotPath)
+			}
+
 			if properties.SdkVersion != "" {
 				scopeSet.AddProperty("sdk_version", properties.SdkVersion)
 			}
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 938bb28..f3a19e9 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -15,12 +15,13 @@
 package java
 
 import (
-	"android/soong/android"
 	"fmt"
 	"path/filepath"
 	"regexp"
 	"testing"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -107,7 +108,7 @@
 			libs: ["foo"],
 			sdk_version: "module_30",
 		}
-		`)
+	`)
 
 	// check the existence of the internal modules
 	foo := result.ModuleForTests("foo", "android_common")
@@ -162,6 +163,185 @@
 	}
 }
 
+func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+			"29": {"foo"},
+			"30": {"foo", "fooUpdatable", "fooUpdatableErr"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W"}
+		}),
+	).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "fooUpdatable",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "U",
+			on_bootclasspath_before: "V",
+			min_device_sdk: "W",
+			max_device_sdk: "current",
+			min_sdk_version: "S",
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+		}
+`)
+	// test that updatability attributes are passed on correctly
+	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-since=\"9001\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-before=\"9002\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `min-device-sdk=\"9003\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `max-device-sdk=\"10000\"`)
+
+	// double check that updatability attributes are not written if they don't exist in the bp file
+	// the permissions file for the foo library defined above
+	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-since`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-before`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `min-device-sdk`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `max-device-sdk`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"30": {"fooUpdatable", "fooUpdatableErr"},
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			`on_bootclasspath_since: "aaa" could not be parsed as an integer and is not a recognized codename`,
+			`on_bootclasspath_before: "bbc" could not be parsed as an integer and is not a recognized codename`,
+			`min_device_sdk: "ccc" could not be parsed as an integer and is not a recognized codename`,
+			`max_device_sdk: "ddd" could not be parsed as an integer and is not a recognized codename`,
+		})).RunTestWithBp(t,
+		`
+	java_sdk_library {
+			name: "fooUpdatableErr",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "aaa",
+			on_bootclasspath_before: "bbc",
+			min_device_sdk: "ccc",
+			max_device_sdk: "ddd",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_AtLeastTAttributes(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			"on_bootclasspath_since: Attribute value needs to be at least T",
+			"on_bootclasspath_before: Attribute value needs to be at least T",
+			"min_device_sdk: Attribute value needs to be at least T",
+			"max_device_sdk: Attribute value needs to be at least T",
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "S",
+			on_bootclasspath_before: "S",
+			min_device_sdk: "S",
+			max_device_sdk: "S",
+			min_sdk_version: "S",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdk(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			"min_device_sdk can't be greater than max_device_sdk",
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			min_device_sdk: "V",
+			max_device_sdk: "U",
+			min_sdk_version: "S",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdkAndModuleMinSdk(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			regexp.QuoteMeta("min_device_sdk: Can't be less than module's min sdk (V)"),
+			regexp.QuoteMeta("max_device_sdk: Can't be less than module's min sdk (V)"),
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			min_device_sdk: "U",
+			max_device_sdk: "U",
+			min_sdk_version: "V",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_usesNewTag(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"30": {"foo"},
+		}),
+	).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			min_device_sdk: "Tiramisu",
+			min_sdk_version: "S",
+		}
+`)
+	// test that updatability attributes are passed on correctly
+	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<updatable-library`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<library`)
+}
+
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
@@ -248,7 +428,7 @@
 	}
 }
 
-func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
+func TestJavaSdkLibrary_AccessOutputFiles(t *testing.T) {
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -258,6 +438,31 @@
 			name: "foo",
 			srcs: ["a.java"],
 			api_packages: ["foo"],
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.public.stubs.source}"],
+			java_resources: [":foo{.public.annotations.zip}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_AccessOutputFiles_NoAnnotations(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": path dependency ":foo{.public.annotations.zip}": annotations.zip not available for api scope public`)).
+		RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
 			public: {
 				enabled: true,
 			},
@@ -266,6 +471,7 @@
 		java_library {
 			name: "bar",
 			srcs: ["b.java", ":foo{.public.stubs.source}"],
+			java_resources: [":foo{.public.annotations.zip}"],
 		}
 		`)
 }
@@ -329,6 +535,7 @@
 				stub_srcs: ["a.java"],
 				current_api: "api/current.txt",
 				removed_api: "api/removed.txt",
+				annotations: "x/annotations.zip",
 			},
 		}
 
@@ -338,6 +545,7 @@
 			java_resources: [
 				":foo{.public.api.txt}",
 				":foo{.public.removed-api.txt}",
+				":foo{.public.annotations.zip}",
 			],
 		}
 		`)
@@ -598,6 +806,7 @@
 	}
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
 		`prebuilt_sdklib.stubs.system`,
@@ -674,7 +883,6 @@
 		`)
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
@@ -683,6 +891,7 @@
 	})
 
 	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		`sdklib.xml`,
@@ -931,3 +1140,87 @@
 		})
 	}
 }
+
+func TestSdkLibrary_CheckMinSdkVersion(t *testing.T) {
+	preparer := android.GroupFixturePreparers(
+		PrepareForTestWithJavaBuildComponents,
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithJavaSdkLibraryFiles,
+	)
+
+	preparer.RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+            srcs: ["a.java"],
+            static_libs: ["util"],
+            min_sdk_version: "30",
+			unsafe_ignore_missing_latest_api: true,
+        }
+
+		java_library {
+			name: "util",
+			srcs: ["a.java"],
+			min_sdk_version: "30",
+		}
+	`)
+
+	preparer.
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				libs: ["util"],
+				impl_only_libs: ["util"],
+				stub_only_libs: ["util"],
+				stub_only_static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+			}
+		`)
+
+	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "util".*should support min_sdk_version\(30\)`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+				min_sdk_version: "31",
+			}
+		`)
+
+	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "another_util".*should support min_sdk_version\(30\)`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+				static_libs: ["another_util"],
+				min_sdk_version: "30",
+			}
+
+			java_library {
+				name: "another_util",
+				srcs: ["a.java"],
+				min_sdk_version: "31",
+			}
+		`)
+}
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 6d62130..9e8ba6e 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -25,27 +25,36 @@
 	"android/soong/java/config"
 )
 
+type classpathTestCase struct {
+	name       string
+	unbundled  bool
+	moduleType string
+	host       android.OsClass
+	properties string
+
+	// for java 8
+	bootclasspath  []string
+	java8classpath []string
+
+	// for java 9
+	system         string
+	java9classpath []string
+
+	forces8 bool // if set, javac will always be called with java 8 arguments
+
+	aidl string
+
+	// Indicates how this test case is affected by the setting of Always_use_prebuilt_sdks.
+	//
+	// If this is nil then the test case is unaffected by the setting of Always_use_prebuilt_sdks.
+	// Otherwise, the test case can only be used when
+	// Always_use_prebuilt_sdks=*forAlwaysUsePrebuiltSdks.
+	forAlwaysUsePrebuiltSdks *bool
+}
+
 func TestClasspath(t *testing.T) {
 	const frameworkAidl = "-I" + defaultJavaDir + "/framework/aidl"
-	var classpathTestcases = []struct {
-		name       string
-		unbundled  bool
-		moduleType string
-		host       android.OsClass
-		properties string
-
-		// for java 8
-		bootclasspath  []string
-		java8classpath []string
-
-		// for java 9
-		system         string
-		java9classpath []string
-
-		forces8 bool // if set, javac will always be called with java 8 arguments
-
-		aidl string
-	}{
+	var classpathTestcases = []classpathTestCase{
 		{
 			name:           "default",
 			bootclasspath:  config.StableCorePlatformBootclasspathLibraries,
@@ -91,25 +100,52 @@
 			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "current",
 			properties:     `sdk_version: "current",`,
 			bootclasspath:  []string{"android_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "current",
+			properties:     `sdk_version: "current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "system_current",
 			properties:     `sdk_version: "system_current",`,
 			bootclasspath:  []string{"android_system_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_system_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
 
+			name:           "system_current",
+			properties:     `sdk_version: "system_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
 			name:           "system_29",
 			properties:     `sdk_version: "system_29",`,
 			bootclasspath:  []string{`""`},
@@ -118,7 +154,6 @@
 			aidl:           "-pprebuilts/sdk/29/public/framework.aidl",
 		},
 		{
-
 			name:           "system_30",
 			properties:     `sdk_version: "system_30",`,
 			bootclasspath:  []string{`""`},
@@ -128,20 +163,57 @@
 			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "test_current",
 			properties:     `sdk_version: "test_current",`,
 			bootclasspath:  []string{"android_test_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_test_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "test_current",
+			properties:     `sdk_version: "test_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "test_30",
+			properties:     `sdk_version: "test_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:          "core_current",
 			properties:    `sdk_version: "core_current",`,
 			bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"},
-			system:        "core-current-stubs-system-modules",
+			system:        "core-public-stubs-system-modules",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "core_current",
+			properties:     `sdk_version: "core_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/core/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/core/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
 		},
 		{
 
@@ -214,8 +286,10 @@
 			java9classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
 			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
 		},
-
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
+
 			name:           "module_current",
 			properties:     `sdk_version: "module_current",`,
 			bootclasspath:  []string{"android_module_lib_stubs_current", "core-lambda-stubs"},
@@ -224,6 +298,48 @@
 			aidl:           "-pout/soong/framework_non_updatable.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "module_current",
+			properties:     `sdk_version: "module_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "module_30",
+			properties:     `sdk_version: "module_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			name:           "module_31",
+			properties:     `sdk_version: "module_31",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_31_system_modules",
+			java8classpath: []string{"prebuilts/sdk/31/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/31/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/31/public/framework.aidl",
+		},
+		{
+			name:           "module_32",
+			properties:     `sdk_version: "module_32",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_32_system_modules",
+			java8classpath: []string{"prebuilts/sdk/32/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/32/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/32/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
+
 			name:           "system_server_current",
 			properties:     `sdk_version: "system_server_current",`,
 			bootclasspath:  []string{"android_system_server_stubs_current", "core-lambda-stubs"},
@@ -231,9 +347,62 @@
 			java9classpath: []string{"android_system_server_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "system_server_current",
+			properties:     `sdk_version: "system_server_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "system_server_30",
+			properties:     `sdk_version: "system_server_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			name:           "system_server_31",
+			properties:     `sdk_version: "system_server_31",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_31_system_modules",
+			java8classpath: []string{"prebuilts/sdk/31/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/31/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/31/public/framework.aidl",
+		},
+		{
+			name:           "system_server_32",
+			properties:     `sdk_version: "system_server_32",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_32_system_modules",
+			java8classpath: []string{"prebuilts/sdk/32/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/32/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/32/public/framework.aidl",
+		},
 	}
 
+	t.Run("basic", func(t *testing.T) {
+		testClasspathTestCases(t, classpathTestcases, false)
+	})
+
+	t.Run("Always_use_prebuilt_sdks=true", func(t *testing.T) {
+		testClasspathTestCases(t, classpathTestcases, true)
+	})
+}
+
+func testClasspathTestCases(t *testing.T, classpathTestcases []classpathTestCase, alwaysUsePrebuiltSdks bool) {
 	for _, testcase := range classpathTestcases {
+		if testcase.forAlwaysUsePrebuiltSdks != nil && *testcase.forAlwaysUsePrebuiltSdks != alwaysUsePrebuiltSdks {
+			continue
+		}
+
 		t.Run(testcase.name, func(t *testing.T) {
 			moduleType := "java_library"
 			if testcase.moduleType != "" {
@@ -299,7 +468,9 @@
 				system = "--system=none"
 			} else if testcase.system != "" {
 				dir := ""
-				if strings.HasPrefix(testcase.system, "sdk_public_") {
+				// If the system modules name starts with sdk_ then it is a prebuilt module and so comes
+				// from the prebuilt directory.
+				if strings.HasPrefix(testcase.system, "sdk_") {
 					dir = "prebuilts/sdk"
 				} else {
 					dir = defaultJavaDir
@@ -351,11 +522,20 @@
 				android.AssertPathsRelativeToTopEquals(t, "implicits", deps, javac.Implicits)
 			}
 
+			preparer := android.NullFixturePreparer
+			if alwaysUsePrebuiltSdks {
+				preparer = android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
+				})
+			}
+
 			fixtureFactory := android.GroupFixturePreparers(
 				prepareForJavaTest,
 				FixtureWithPrebuiltApis(map[string][]string{
 					"29":      {},
 					"30":      {},
+					"31":      {},
+					"32":      {},
 					"current": {},
 				}),
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -369,6 +549,7 @@
 						env["ANDROID_JAVA8_HOME"] = "jdk8"
 					}
 				}),
+				preparer,
 			)
 
 			// Test with legacy javac -source 1.8 -target 1.8
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 5311f62..fa61ea6 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -23,11 +23,19 @@
 
 func init() {
 	registerSystemserverClasspathBuildComponents(android.InitRegistrationContext)
+
+	android.RegisterSdkMemberType(&systemServerClasspathFragmentMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "systemserverclasspath_fragments",
+			SupportsSdk:  true,
+		},
+	})
 }
 
 func registerSystemserverClasspathBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("platform_systemserverclasspath", platformSystemServerClasspathFactory)
 	ctx.RegisterModuleType("systemserverclasspath_fragment", systemServerClasspathFactory)
+	ctx.RegisterModuleType("prebuilt_systemserverclasspath_fragment", prebuiltSystemServerClasspathModuleFactory)
 }
 
 type platformSystemServerClasspathModule struct {
@@ -50,6 +58,10 @@
 func (p *platformSystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	configuredJars := p.configuredJars(ctx)
 	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, p.classpathType)
+	standaloneConfiguredJars := p.standaloneConfiguredJars(ctx)
+	standaloneClasspathJars := configuredJarListToClasspathJars(ctx, standaloneConfiguredJars, STANDALONE_SYSTEMSERVER_JARS)
+	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
+	classpathJars = append(classpathJars, standaloneClasspathJars...)
 	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
@@ -58,9 +70,14 @@
 	return dexpreopt.GetGlobalConfig(ctx).SystemServerJars
 }
 
+func (p *platformSystemServerClasspathModule) standaloneConfiguredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	return dexpreopt.GetGlobalConfig(ctx).StandaloneSystemServerJars
+}
+
 type SystemServerClasspathModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
+	android.SdkBase
 
 	ClasspathFragmentBase
 
@@ -75,28 +92,38 @@
 }
 
 type systemServerClasspathFragmentProperties struct {
-	// The contents of this systemserverclasspath_fragment, could be either java_library, or java_sdk_library.
+	// List of system_server classpath jars, could be either java_library, or java_sdk_library.
 	//
 	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
 	Contents []string
+
+	// List of jars that system_server loads dynamically using separate classloaders.
+	//
+	// The order does not matter.
+	Standalone_contents []string
 }
 
 func systemServerClasspathFactory() android.Module {
 	m := &SystemServerClasspathModule{}
 	m.AddProperties(&m.properties)
 	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, SYSTEMSERVERCLASSPATH)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
 
 func (s *SystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if len(s.properties.Contents) == 0 {
-		ctx.PropertyErrorf("contents", "empty contents are not allowed")
+	if len(s.properties.Contents) == 0 && len(s.properties.Standalone_contents) == 0 {
+		ctx.PropertyErrorf("contents", "Either contents or standalone_contents needs to be non-empty")
 	}
 
 	configuredJars := s.configuredJars(ctx)
 	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, s.classpathType)
+	standaloneConfiguredJars := s.standaloneConfiguredJars(ctx)
+	standaloneClasspathJars := configuredJarListToClasspathJars(ctx, standaloneConfiguredJars, STANDALONE_SYSTEMSERVER_JARS)
+	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
+	classpathJars = append(classpathJars, standaloneClasspathJars...)
 	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -110,24 +137,78 @@
 	jars, unknown := global.ApexSystemServerJars.Filter(possibleUpdatableModules)
 	// TODO(satayev): remove geotz ssc_fragment, since geotz is not part of SSCP anymore.
 	_, unknown = android.RemoveFromList("geotz", unknown)
+	// This module only exists in car products.
+	// So ignore it even if it is not in PRODUCT_APEX_SYSTEM_SERVER_JARS.
+	// TODO(b/203233647): Add better mechanism to make it optional.
+	_, unknown = android.RemoveFromList("car-frameworks-service-module", unknown)
 
-	// For non test apexes, make sure that all contents are actually declared in make.
-	if global.ApexSystemServerJars.Len() > 0 && len(unknown) > 0 {
-		ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS", unknown)
+	// This module is optional, so it is not present in all products.
+	// (See PRODUCT_ISOLATED_COMPILATION_ENABLED.)
+	// So ignore it even if it is not in PRODUCT_APEX_SYSTEM_SERVER_JARS.
+	// TODO(b/203233647): Add better mechanism to make it optional.
+	_, unknown = android.RemoveFromList("service-compos", unknown)
+
+	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
+	// config. However, any test specific jars would not be present in ApexSystemServerJars. Instead,
+	// we should check if we are creating a config for apex_test via ApexInfo and amend the values.
+	// This is an exception to support end-to-end test for ApexdUnitTests, until such support exists.
+	if android.InList("test_service-apexd", possibleUpdatableModules) {
+		jars = jars.Append("com.android.apex.test_package", "test_service-apexd")
+	} else if global.ApexSystemServerJars.Len() > 0 && len(unknown) > 0 && !android.IsModuleInVersionedSdk(ctx.Module()) {
+		// For non test apexes, make sure that all contents are actually declared in make.
+		ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS", unknown)
 	}
 
 	return jars
 }
 
+func (s *SystemServerClasspathModule) standaloneConfiguredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Standalone_contents, systemServerClasspathFragmentContentDepTag)
+	jars, _ := global.ApexStandaloneSystemServerJars.Filter(possibleUpdatableModules)
+
+	// TODO(jiakaiz): add a check to ensure that the contents are declared in make.
+
+	return jars
+}
+
 type systemServerClasspathFragmentContentDependencyTag struct {
 	blueprint.BaseDependencyTag
 }
 
+// The systemserverclasspath_fragment contents must never depend on prebuilts.
+func (systemServerClasspathFragmentContentDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
+// SdkMemberType causes dependencies added with this tag to be automatically added to the sdk as if
+// they were specified using java_systemserver_libs or java_sdk_libs.
+func (b systemServerClasspathFragmentContentDependencyTag) SdkMemberType(child android.Module) android.SdkMemberType {
+	// If the module is a java_sdk_library then treat it as if it was specified in the java_sdk_libs
+	// property, otherwise treat if it was specified in the java_systemserver_libs property.
+	if javaSdkLibrarySdkMemberType.IsInstance(child) {
+		return javaSdkLibrarySdkMemberType
+	}
+
+	return javaSystemserverLibsSdkMemberType
+}
+
+func (b systemServerClasspathFragmentContentDependencyTag) ExportMember() bool {
+	return true
+}
+
 // Contents of system server fragments in an apex are considered to be directly in the apex, as if
 // they were listed in java_libs.
 func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
 
+// Contents of system server fragments require files from prebuilt apex files.
+func (systemServerClasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
+
+var _ android.ReplaceSourceWithPrebuilt = systemServerClasspathFragmentContentDepTag
+var _ android.SdkMemberDependencyTag = systemServerClasspathFragmentContentDepTag
 var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
+var _ android.RequiresFilesFromPrebuiltApexTag = systemServerClasspathFragmentContentDepTag
 
 // The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
 var systemServerClasspathFragmentContentDepTag = systemServerClasspathFragmentContentDependencyTag{}
@@ -138,8 +219,17 @@
 
 func (s *SystemServerClasspathModule) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	module := ctx.Module()
+	_, isSourceModule := module.(*SystemServerClasspathModule)
+	var deps []string
+	deps = append(deps, s.properties.Contents...)
+	deps = append(deps, s.properties.Standalone_contents...)
 
-	for _, name := range s.properties.Contents {
+	for _, name := range deps {
+		// A systemserverclasspath_fragment must depend only on other source modules, while the
+		// prebuilt_systemserverclasspath_fragment_fragment must only depend on other prebuilt modules.
+		if !isSourceModule {
+			name = android.PrebuiltNameFromSource(name)
+		}
 		ctx.AddDependency(module, systemServerClasspathFragmentContentDepTag, name)
 	}
 }
@@ -147,5 +237,95 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 func (s *SystemServerClasspathModule) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Deps = append(dpInfo.Deps, s.properties.Contents...)
+	dpInfo.Deps = append(dpInfo.Deps, s.properties.Standalone_contents...)
 	dpInfo.Paths = append(dpInfo.Paths, s.modulePaths...)
 }
+
+type systemServerClasspathFragmentMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (s *systemServerClasspathFragmentMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
+}
+
+func (s *systemServerClasspathFragmentMemberType) IsInstance(module android.Module) bool {
+	_, ok := module.(*SystemServerClasspathModule)
+	return ok
+}
+
+func (s *systemServerClasspathFragmentMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_systemserverclasspath_fragment")
+}
+
+func (s *systemServerClasspathFragmentMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	return &systemServerClasspathFragmentSdkMemberProperties{}
+}
+
+type systemServerClasspathFragmentSdkMemberProperties struct {
+	android.SdkMemberPropertiesBase
+
+	// List of system_server classpath jars, could be either java_library, or java_sdk_library.
+	//
+	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
+	Contents []string
+
+	// List of jars that system_server loads dynamically using separate classloaders.
+	//
+	// The order does not matter.
+	Standalone_contents []string
+}
+
+func (s *systemServerClasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
+	module := variant.(*SystemServerClasspathModule)
+
+	s.Contents = module.properties.Contents
+	s.Standalone_contents = module.properties.Standalone_contents
+}
+
+func (s *systemServerClasspathFragmentSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
+	builder := ctx.SnapshotBuilder()
+	requiredMemberDependency := builder.SdkMemberReferencePropertyTag(true)
+
+	if len(s.Contents) > 0 {
+		propertySet.AddPropertyWithTag("contents", s.Contents, requiredMemberDependency)
+	}
+
+	if len(s.Standalone_contents) > 0 {
+		propertySet.AddPropertyWithTag("standalone_contents", s.Standalone_contents, requiredMemberDependency)
+	}
+}
+
+var _ android.SdkMemberType = (*systemServerClasspathFragmentMemberType)(nil)
+
+// A prebuilt version of the systemserverclasspath_fragment module.
+type prebuiltSystemServerClasspathModule struct {
+	SystemServerClasspathModule
+	prebuilt android.Prebuilt
+}
+
+func (module *prebuiltSystemServerClasspathModule) Prebuilt() *android.Prebuilt {
+	return &module.prebuilt
+}
+
+func (module *prebuiltSystemServerClasspathModule) Name() string {
+	return module.prebuilt.Name(module.ModuleBase.Name())
+}
+
+func (module *prebuiltSystemServerClasspathModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
+	return nil
+}
+
+var _ android.RequiredFilesFromPrebuiltApex = (*prebuiltSystemServerClasspathModule)(nil)
+
+func prebuiltSystemServerClasspathModuleFactory() android.Module {
+	m := &prebuiltSystemServerClasspathModule{}
+	m.AddProperties(&m.properties)
+	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
+	// array.
+	android.InitPrebuiltModule(m, &[]string{"placeholder"})
+	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
diff --git a/java/systemserver_classpath_fragment_test.go b/java/systemserver_classpath_fragment_test.go
index 9ad50dd..ba328e7 100644
--- a/java/systemserver_classpath_fragment_test.go
+++ b/java/systemserver_classpath_fragment_test.go
@@ -99,7 +99,7 @@
 func TestSystemServerClasspathFragmentWithoutContents(t *testing.T) {
 	prepareForTestWithSystemServerClasspath.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qempty contents are not allowed\E`)).
+			`\QEither contents or standalone_contents needs to be non-empty\E`)).
 		RunTestWithBp(t, `
 			systemserverclasspath_fragment {
 				name: "systemserverclasspath-fragment",
diff --git a/java/testing.go b/java/testing.go
index 8860b45..7441e44 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -159,8 +159,7 @@
 		`, strings.Join(android.SortedStringKeys(release2Modules), `", "`))
 
 	for release, modules := range release2Modules {
-		libs := append([]string{"android", "core-for-system-modules"}, modules...)
-		mockFS.Merge(prebuiltApisFilesForLibs([]string{release}, libs))
+		mockFS.Merge(prebuiltApisFilesForModules([]string{release}, modules))
 	}
 	return android.GroupFixturePreparers(
 		android.FixtureAddTextFile(path, bp),
@@ -168,19 +167,32 @@
 	)
 }
 
-func prebuiltApisFilesForLibs(apiLevels []string, sdkLibs []string) map[string][]byte {
+func prebuiltApisFilesForModules(apiLevels []string, modules []string) map[string][]byte {
+	libs := append([]string{"android"}, modules...)
+
 	fs := make(map[string][]byte)
 	for _, level := range apiLevels {
-		for _, lib := range sdkLibs {
-			for _, scope := range []string{"public", "system", "module-lib", "system-server", "test"} {
-				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/%s.jar", level, scope, lib)] = nil
+		apiLevel := android.ApiLevelForTest(level)
+		for _, sdkKind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkModule, android.SdkSystemServer, android.SdkTest} {
+			// A core-for-system-modules file must only be created for the sdk kind that supports it.
+			if sdkKind == systemModuleKind(sdkKind, apiLevel) {
+				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/core-for-system-modules.jar", level, sdkKind)] = nil
+			}
+
+			for _, lib := range libs {
+				// Create a jar file for every library.
+				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/%s.jar", level, sdkKind, lib)] = nil
+
 				// No finalized API files for "current"
 				if level != "current" {
-					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s.txt", level, scope, lib)] = nil
-					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s-removed.txt", level, scope, lib)] = nil
+					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s.txt", level, sdkKind, lib)] = nil
+					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s-removed.txt", level, sdkKind, lib)] = nil
 				}
 			}
 		}
+		if level == "current" {
+			fs["prebuilts/sdk/current/core/android.jar"] = nil
+		}
 		fs[fmt.Sprintf("prebuilts/sdk/%s/public/framework.aidl", level)] = nil
 	}
 	return fs
@@ -229,6 +241,26 @@
 	)
 }
 
+// FixtureUseLegacyCorePlatformApi prepares the fixture by setting the exception list of those
+// modules that are allowed to use the legacy core platform API to be the ones supplied.
+func FixtureUseLegacyCorePlatformApi(moduleNames ...string) android.FixturePreparer {
+	lookup := make(map[string]struct{})
+	for _, moduleName := range moduleNames {
+		lookup[moduleName] = struct{}{}
+	}
+	return android.FixtureModifyConfig(func(config android.Config) {
+		// Try and set the legacyCorePlatformApiLookup in the config, the returned value will be the
+		// actual value that is set.
+		cached := config.Once(legacyCorePlatformApiLookupKey, func() interface{} {
+			return lookup
+		})
+		// Make sure that the cached value is the one we need.
+		if !reflect.DeepEqual(cached, lookup) {
+			panic(fmt.Errorf("attempting to set legacyCorePlatformApiLookupKey to %q but it has already been set to %q", lookup, cached))
+		}
+	})
+}
+
 // registerRequiredBuildComponentsForTest registers the build components used by
 // PrepareForTestWithJavaDefaultModules.
 //
@@ -280,6 +312,7 @@
 		"kotlin-stdlib-jdk7",
 		"kotlin-stdlib-jdk8",
 		"kotlin-annotations",
+		"stub-annotations",
 	}
 
 	for _, extra := range extraModules {
@@ -311,7 +344,7 @@
 		}`
 
 	systemModules := []string{
-		"core-current-stubs-system-modules",
+		"core-public-stubs-system-modules",
 		"core-module-lib-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
 		"stable-core-platform-api-stubs-system-modules",
@@ -431,3 +464,45 @@
 	output := sourceGlobalCompatConfig.Output(allOutputs[0])
 	android.AssertPathsRelativeToTopEquals(t, message+": inputs", expectedPaths, output.Implicits)
 }
+
+// Register the fake APEX mutator to `android.InitRegistrationContext` as if the real mutator exists
+// at runtime. This must be called in `init()` of a test if the test is going to use the fake APEX
+// mutator. Otherwise, we will be missing the runtime mutator because "soong-apex" is not a
+// dependency, which will cause an inconsistency between testing and runtime mutators.
+func RegisterFakeRuntimeApexMutator() {
+	registerFakeApexMutator(android.InitRegistrationContext)
+}
+
+var PrepareForTestWithFakeApexMutator = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerFakeApexMutator),
+)
+
+func registerFakeApexMutator(ctx android.RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("apex", fakeApexMutator).Parallel()
+	})
+}
+
+type apexModuleBase interface {
+	ApexAvailable() []string
+}
+
+var _ apexModuleBase = (*Library)(nil)
+var _ apexModuleBase = (*SdkLibrary)(nil)
+
+// A fake APEX mutator that creates a platform variant and an APEX variant for modules with
+// `apex_available`. It helps us avoid a dependency on the real mutator defined in "soong-apex",
+// which will cause a cyclic dependency, and it provides an easy way to create an APEX variant for
+// testing without dealing with all the complexities in the real mutator.
+func fakeApexMutator(mctx android.BottomUpMutatorContext) {
+	switch mctx.Module().(type) {
+	case *Library, *SdkLibrary:
+		if len(mctx.Module().(apexModuleBase).ApexAvailable()) > 0 {
+			modules := mctx.CreateVariations("", "apex1000")
+			apexInfo := android.ApexInfo{
+				ApexVariationName: "apex1000",
+			}
+			mctx.SetVariationProvider(modules[1], android.ApexInfoProvider, apexInfo)
+		}
+	}
+}
diff --git a/licenses/Android.bp b/licenses/Android.bp
index a983b5b..5b764dc 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -492,36 +492,36 @@
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-1.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-1.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-2.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-2.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-2.5",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-2.5.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-3.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-3.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-4.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-4.0.html",
 }
 
@@ -562,7 +562,10 @@
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-SA-ND",
-    conditions: ["restricted"],
+    conditions: [
+        "restricted",
+        "by_exception_only",
+    ],
 }
 
 license_kind {
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 8d0ad7c..dbc112e 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -155,7 +155,7 @@
 		OutputFile: android.OptionalPathForPath(l.outputFilePath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
 				entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index 72525c4..d9b4e86 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -46,21 +46,21 @@
 	dryRun   = flag.Bool("dry_run", false, "dry run")
 	recurse  = flag.Bool("convert_dependents", false, "convert all dependent files")
 	mode     = flag.String("mode", "", `"backup" to back up existing files, "write" to overwrite them`)
-	warn     = flag.Bool("warnings", false, "warn about partially failed conversions")
-	verbose  = flag.Bool("v", false, "print summary")
 	errstat  = flag.Bool("error_stat", false, "print error statistics")
 	traceVar = flag.String("trace", "", "comma-separated list of variables to trace")
 	// TODO(asmundak): this option is for debugging
 	allInSource           = flag.Bool("all", false, "convert all product config makefiles in the tree under //")
 	outputTop             = flag.String("outdir", "", "write output files into this directory hierarchy")
-	launcher              = flag.String("launcher", "", "generated launcher path. If set, the non-flag argument is _product_name_")
+	launcher              = flag.String("launcher", "", "generated launcher path.")
+	boardlauncher         = flag.String("boardlauncher", "", "generated board configuration launcher path.")
 	printProductConfigMap = flag.Bool("print_product_config_map", false, "print product config map and exit")
 	cpuProfile            = flag.String("cpu_profile", "", "write cpu profile to file")
 	traceCalls            = flag.Bool("trace_calls", false, "trace function calls")
+	inputVariables        = flag.String("input_variables", "", "starlark file containing product config and global variables")
 )
 
 func init() {
-	// Poor man's flag aliasing: works, but the usage string is ugly and
+	// Simplistic flag aliasing: works, but the usage string is ugly and
 	// both flag and its alias can be present on the command line
 	flagAlias := func(target string, alias string) {
 		if f := flag.Lookup(target); f != nil {
@@ -73,21 +73,19 @@
 	flagAlias("root", "d")
 	flagAlias("dry_run", "n")
 	flagAlias("convert_dependents", "r")
-	flagAlias("warnings", "w")
 	flagAlias("error_stat", "e")
 }
 
 var backupSuffix string
 var tracedVariables []string
-var errorLogger = errorsByType{data: make(map[string]datum)}
+var errorLogger = errorSink{data: make(map[string]datum)}
 var makefileFinder = &LinuxMakefileFinder{}
 
 func main() {
 	flag.Usage = func() {
 		cmd := filepath.Base(os.Args[0])
 		fmt.Fprintf(flag.CommandLine.Output(),
-			"Usage: %[1]s flags file...\n"+
-				"or:    %[1]s flags --launcher=PATH PRODUCT\n", cmd)
+			"Usage: %[1]s flags file...\n", cmd)
 		flag.PrintDefaults()
 	}
 	flag.Parse()
@@ -153,41 +151,57 @@
 	}
 
 	// Convert!
+	files := flag.Args()
+	if *allInSource {
+		productConfigMap := buildProductConfigMap()
+		for _, path := range productConfigMap {
+			files = append(files, path)
+		}
+	}
 	ok := true
+	for _, mkFile := range files {
+		ok = convertOne(mkFile) && ok
+	}
+
 	if *launcher != "" {
-		if len(flag.Args()) != 1 {
+		if len(files) != 1 {
 			quit(fmt.Errorf("a launcher can be generated only for a single product"))
 		}
-		product := flag.Args()[0]
-		productConfigMap := buildProductConfigMap()
-		path, found := productConfigMap[product]
-		if !found {
-			quit(fmt.Errorf("cannot generate configuration launcher for %s, it is not a known product",
-				product))
+		if *inputVariables == "" {
+			quit(fmt.Errorf("the product launcher requires an input variables file"))
 		}
-		ok = convertOne(path) && ok
-		err := writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(path), mk2rbc.MakePath2ModuleName(path)))
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s:%s", path, err)
-			ok = false
+		if !convertOne(*inputVariables) {
+			quit(fmt.Errorf("the product launcher input variables file failed to convert"))
 		}
 
-	} else {
-		files := flag.Args()
-		if *allInSource {
-			productConfigMap := buildProductConfigMap()
-			for _, path := range productConfigMap {
-				files = append(files, path)
-			}
+		err := writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(files[0]), outputFilePath(*inputVariables),
+			mk2rbc.MakePath2ModuleName(files[0])))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
+			ok = false
 		}
-		for _, mkFile := range files {
-			ok = convertOne(mkFile) && ok
+	}
+	if *boardlauncher != "" {
+		if len(files) != 1 {
+			quit(fmt.Errorf("a launcher can be generated only for a single product"))
+		}
+		if *inputVariables == "" {
+			quit(fmt.Errorf("the board launcher requires an input variables file"))
+		}
+		if !convertOne(*inputVariables) {
+			quit(fmt.Errorf("the board launcher input variables file failed to convert"))
+		}
+		err := writeGenerated(*boardlauncher, mk2rbc.BoardLauncher(
+			outputFilePath(files[0]), outputFilePath(*inputVariables)))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
+			ok = false
 		}
 	}
 
-	printStats()
 	if *errstat {
 		errorLogger.printStatistics()
+		printStats()
 	}
 	if !ok {
 		os.Exit(1)
@@ -202,8 +216,7 @@
 func buildProductConfigMap() map[string]string {
 	const androidProductsMk = "AndroidProducts.mk"
 	// Build the list of AndroidProducts.mk files: it's
-	// build/make/target/product/AndroidProducts.mk plus
-	// device/**/AndroidProducts.mk
+	// build/make/target/product/AndroidProducts.mk + device/**/AndroidProducts.mk plus + vendor/**/AndroidProducts.mk
 	targetAndroidProductsFile := filepath.Join(*rootDir, "build", "make", "target", "product", androidProductsMk)
 	if _, err := os.Stat(targetAndroidProductsFile); err != nil {
 		fmt.Fprintf(os.Stderr, "%s: %s\n(hint: %s is not a source tree root)\n",
@@ -213,17 +226,19 @@
 	if err := mk2rbc.UpdateProductConfigMap(productConfigMap, targetAndroidProductsFile); err != nil {
 		fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
 	}
-	_ = filepath.Walk(filepath.Join(*rootDir, "device"),
-		func(path string, info os.FileInfo, err error) error {
-			if info.IsDir() || filepath.Base(path) != androidProductsMk {
+	for _, t := range []string{"device", "vendor"} {
+		_ = filepath.WalkDir(filepath.Join(*rootDir, t),
+			func(path string, d os.DirEntry, err error) error {
+				if err != nil || d.IsDir() || filepath.Base(path) != androidProductsMk {
+					return nil
+				}
+				if err2 := mk2rbc.UpdateProductConfigMap(productConfigMap, path); err2 != nil {
+					fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
+					// Keep going, we want to find all such errors in a single run
+				}
 				return nil
-			}
-			if err2 := mk2rbc.UpdateProductConfigMap(productConfigMap, path); err2 != nil {
-				fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
-				// Keep going, we want to find all such errors in a single run
-			}
-			return nil
-		})
+			})
+	}
 	return productConfigMap
 }
 
@@ -297,19 +312,16 @@
 	}()
 
 	mk2starRequest := mk2rbc.Request{
-		MkFile:             mkFile,
-		Reader:             nil,
-		RootDir:            *rootDir,
-		OutputDir:          *outputTop,
-		OutputSuffix:       *suffix,
-		TracedVariables:    tracedVariables,
-		TraceCalls:         *traceCalls,
-		WarnPartialSuccess: *warn,
-		SourceFS:           os.DirFS(*rootDir),
-		MakefileFinder:     makefileFinder,
-	}
-	if *errstat {
-		mk2starRequest.ErrorLogger = errorLogger
+		MkFile:          mkFile,
+		Reader:          nil,
+		RootDir:         *rootDir,
+		OutputDir:       *outputTop,
+		OutputSuffix:    *suffix,
+		TracedVariables: tracedVariables,
+		TraceCalls:      *traceCalls,
+		SourceFS:        os.DirFS(*rootDir),
+		MakefileFinder:  makefileFinder,
+		ErrorLogger:     errorLogger,
 	}
 	ss, err := mk2rbc.Convert(mk2starRequest)
 	if err != nil {
@@ -387,9 +399,6 @@
 
 func printStats() {
 	var sortedFiles []string
-	if !*warn && !*verbose {
-		return
-	}
 	for p := range converted {
 		sortedFiles = append(sortedFiles, p)
 	}
@@ -405,29 +414,22 @@
 			nOk++
 		}
 	}
-	if *warn {
-		if nPartial > 0 {
-			fmt.Fprintf(os.Stderr, "Conversion was partially successful for:\n")
-			for _, f := range sortedFiles {
-				if ss := converted[f]; ss != nil && ss.HasErrors() {
-					fmt.Fprintln(os.Stderr, "  ", f)
-				}
-			}
-		}
-
-		if nFailed > 0 {
-			fmt.Fprintf(os.Stderr, "Conversion failed for files:\n")
-			for _, f := range sortedFiles {
-				if converted[f] == nil {
-					fmt.Fprintln(os.Stderr, "  ", f)
-				}
+	if nPartial > 0 {
+		fmt.Fprintf(os.Stderr, "Conversion was partially successful for:\n")
+		for _, f := range sortedFiles {
+			if ss := converted[f]; ss != nil && ss.HasErrors() {
+				fmt.Fprintln(os.Stderr, "  ", f)
 			}
 		}
 	}
-	if *verbose {
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Succeeded:", nOk)
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Partial:", nPartial)
-		fmt.Fprintf(os.Stderr, "%-16s%5d\n", "Failed:", nFailed)
+
+	if nFailed > 0 {
+		fmt.Fprintf(os.Stderr, "Conversion failed for files:\n")
+		for _, f := range sortedFiles {
+			if converted[f] == nil {
+				fmt.Fprintln(os.Stderr, "  ", f)
+			}
+		}
 	}
 }
 
@@ -436,11 +438,18 @@
 	formattingArgs []string
 }
 
-type errorsByType struct {
+type errorSink struct {
 	data map[string]datum
 }
 
-func (ebt errorsByType) NewError(message string, node parser.Node, args ...interface{}) {
+func (ebt errorSink) NewError(el mk2rbc.ErrorLocation, node parser.Node, message string, args ...interface{}) {
+	fmt.Fprint(os.Stderr, el, ": ")
+	fmt.Fprintf(os.Stderr, message, args...)
+	fmt.Fprintln(os.Stderr)
+	if !*errstat {
+		return
+	}
+
 	v, exists := ebt.data[message]
 	if exists {
 		v.count++
@@ -465,7 +474,7 @@
 	ebt.data[message] = v
 }
 
-func (ebt errorsByType) printStatistics() {
+func (ebt errorSink) printStatistics() {
 	if len(ebt.data) > 0 {
 		fmt.Fprintln(os.Stderr, "Error counts:")
 	}
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 0bb8b95..7cd4899 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -16,23 +16,22 @@
 
 import (
 	"fmt"
-	"strconv"
 	"strings"
-
-	mkparser "android/soong/androidmk/parser"
 )
 
-// Represents an expression in the Starlark code. An expression has
-// a type, and it can be evaluated.
+// Represents an expression in the Starlark code. An expression has a type.
 type starlarkExpr interface {
 	starlarkNode
 	typ() starlarkType
-	// Try to substitute variable values. Return substitution result
-	// and whether it is the same as the original expression.
-	eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool)
 	// Emit the code to copy the expression, otherwise we will end up
 	// with source and target pointing to the same list.
 	emitListVarCopy(gctx *generationContext)
+	// Return the expression, calling the transformer func for
+	// every expression in the tree. If the transformer func returns non-nil,
+	// its result is used in place of the expression it was called with in the
+	// resulting expression. The resulting starlarkExpr will contain as many
+	// of the same objects from the original expression as possible.
+	transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr
 }
 
 func maybeString(expr starlarkExpr) (string, bool) {
@@ -46,12 +45,6 @@
 	literal string
 }
 
-func (s *stringLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = s
-	same = true
-	return
-}
-
 func (s *stringLiteralExpr) emit(gctx *generationContext) {
 	gctx.writef("%q", s.literal)
 }
@@ -64,17 +57,19 @@
 	s.emit(gctx)
 }
 
+func (s *stringLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
 // Integer literal
 type intLiteralExpr struct {
 	literal int
 }
 
-func (s *intLiteralExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = s
-	same = true
-	return
-}
-
 func (s *intLiteralExpr) emit(gctx *generationContext) {
 	gctx.writef("%d", s.literal)
 }
@@ -87,6 +82,43 @@
 	s.emit(gctx)
 }
 
+func (s *intLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
+// Boolean literal
+type boolLiteralExpr struct {
+	literal bool
+}
+
+func (b *boolLiteralExpr) emit(gctx *generationContext) {
+	if b.literal {
+		gctx.write("True")
+	} else {
+		gctx.write("False")
+	}
+}
+
+func (_ *boolLiteralExpr) typ() starlarkType {
+	return starlarkTypeBool
+}
+
+func (b *boolLiteralExpr) emitListVarCopy(gctx *generationContext) {
+	b.emit(gctx)
+}
+
+func (b *boolLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
 // interpolateExpr represents Starlark's interpolation operator <string> % list
 // we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y)
 // will have chunks = ["first", "second", "third"] and args = [X, Y]
@@ -95,6 +127,35 @@
 	args   []starlarkExpr
 }
 
+func NewInterpolateExpr(parts []starlarkExpr) starlarkExpr {
+	result := &interpolateExpr{}
+	needString := true
+	for _, part := range parts {
+		if needString {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks = append(result.chunks, strLit.literal)
+			} else {
+				result.chunks = append(result.chunks, "")
+			}
+			needString = false
+		} else {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks[len(result.chunks)-1] += strLit.literal
+			} else {
+				result.args = append(result.args, part)
+				needString = true
+			}
+		}
+	}
+	if len(result.chunks) == len(result.args) {
+		result.chunks = append(result.chunks, "")
+	}
+	if len(result.args) == 0 {
+		return &stringLiteralExpr{literal: strings.Join(result.chunks, "")}
+	}
+	return result
+}
+
 func (xi *interpolateExpr) emit(gctx *generationContext) {
 	if len(xi.chunks) != len(xi.args)+1 {
 		panic(fmt.Errorf("malformed interpolateExpr: #chunks(%d) != #args(%d)+1",
@@ -106,7 +167,7 @@
 		format += "%s" + strings.ReplaceAll(chunk, "%", "%%")
 	}
 	gctx.writef("%q %% ", format)
-	emitarg := func(arg starlarkExpr) {
+	emitArg := func(arg starlarkExpr) {
 		if arg.typ() == starlarkTypeList {
 			gctx.write(`" ".join(`)
 			arg.emit(gctx)
@@ -116,49 +177,18 @@
 		}
 	}
 	if len(xi.args) == 1 {
-		emitarg(xi.args[0])
+		emitArg(xi.args[0])
 	} else {
 		sep := "("
 		for _, arg := range xi.args {
 			gctx.write(sep)
-			emitarg(arg)
+			emitArg(arg)
 			sep = ", "
 		}
 		gctx.write(")")
 	}
 }
 
-func (xi *interpolateExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	same = true
-	newChunks := []string{xi.chunks[0]}
-	var newArgs []starlarkExpr
-	for i, arg := range xi.args {
-		newArg, sameArg := arg.eval(valueMap)
-		same = same && sameArg
-		switch x := newArg.(type) {
-		case *stringLiteralExpr:
-			newChunks[len(newChunks)-1] += x.literal + xi.chunks[i+1]
-			same = false
-			continue
-		case *intLiteralExpr:
-			newChunks[len(newChunks)-1] += strconv.Itoa(x.literal) + xi.chunks[i+1]
-			same = false
-			continue
-		default:
-			newChunks = append(newChunks, xi.chunks[i+1])
-			newArgs = append(newArgs, newArg)
-		}
-	}
-	if same {
-		res = xi
-	} else if len(newChunks) == 1 {
-		res = &stringLiteralExpr{newChunks[0]}
-	} else {
-		res = &interpolateExpr{chunks: newChunks, args: newArgs}
-	}
-	return
-}
-
 func (_ *interpolateExpr) typ() starlarkType {
 	return starlarkTypeString
 }
@@ -167,19 +197,29 @@
 	xi.emit(gctx)
 }
 
+func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	argsCopy := make([]starlarkExpr, len(xi.args))
+	for i, arg := range xi.args {
+		argsCopy[i] = arg.transform(transformer)
+	}
+	xi.args = argsCopy
+	if replacement := transformer(xi); replacement != nil {
+		return replacement
+	} else {
+		return xi
+	}
+}
+
 type variableRefExpr struct {
 	ref       variable
 	isDefined bool
 }
 
-func (v *variableRefExpr) eval(map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	predefined, ok := v.ref.(*predefinedVariable)
-	if same = !ok; same {
-		res = v
-	} else {
-		res = predefined.value
+func NewVariableRefExpr(ref variable, isDefined bool) starlarkExpr {
+	if predefined, ok := ref.(*predefinedVariable); ok {
+		return predefined.value
 	}
-	return
+	return &variableRefExpr{ref, isDefined}
 }
 
 func (v *variableRefExpr) emit(gctx *generationContext) {
@@ -197,17 +237,61 @@
 	}
 }
 
-type notExpr struct {
+func (v *variableRefExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(v); replacement != nil {
+		return replacement
+	} else {
+		return v
+	}
+}
+
+type toStringExpr struct {
 	expr starlarkExpr
 }
 
-func (n *notExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	if x, same := n.expr.eval(valueMap); same {
-		res = n
-	} else {
-		res = &notExpr{expr: x}
+func (s *toStringExpr) emit(ctx *generationContext) {
+	switch s.expr.typ() {
+	case starlarkTypeString, starlarkTypeUnknown:
+		// Assume unknown types are strings already.
+		s.expr.emit(ctx)
+	case starlarkTypeList:
+		ctx.write(`" ".join(`)
+		s.expr.emit(ctx)
+		ctx.write(")")
+	case starlarkTypeInt:
+		ctx.write(`("%d" % (`)
+		s.expr.emit(ctx)
+		ctx.write("))")
+	case starlarkTypeBool:
+		ctx.write(`("true" if (`)
+		s.expr.emit(ctx)
+		ctx.write(`) else "")`)
+	case starlarkTypeVoid:
+		ctx.write(`""`)
+	default:
+		panic("Unknown starlark type!")
 	}
-	return
+}
+
+func (s *toStringExpr) typ() starlarkType {
+	return starlarkTypeString
+}
+
+func (s *toStringExpr) emitListVarCopy(gctx *generationContext) {
+	s.emit(gctx)
+}
+
+func (s *toStringExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	s.expr = s.expr.transform(transformer)
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
+type notExpr struct {
+	expr starlarkExpr
 }
 
 func (n *notExpr) emit(ctx *generationContext) {
@@ -223,38 +307,55 @@
 	n.emit(gctx)
 }
 
+func (n *notExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	n.expr = n.expr.transform(transformer)
+	if replacement := transformer(n); replacement != nil {
+		return replacement
+	} else {
+		return n
+	}
+}
+
 type eqExpr struct {
 	left, right starlarkExpr
 	isEq        bool // if false, it's !=
 }
 
-func (eq *eqExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	xLeft, sameLeft := eq.left.eval(valueMap)
-	xRight, sameRight := eq.right.eval(valueMap)
-	if same = sameLeft && sameRight; same {
-		res = eq
-	} else {
-		res = &eqExpr{left: xLeft, right: xRight, isEq: eq.isEq}
-	}
-	return
-}
-
 func (eq *eqExpr) emit(gctx *generationContext) {
-	emitSimple := func(expr starlarkExpr) {
-		if eq.isEq {
-			gctx.write("not ")
-		}
-		expr.emit(gctx)
+	var stringOperand string
+	var otherOperand starlarkExpr
+	if s, ok := maybeString(eq.left); ok {
+		stringOperand = s
+		otherOperand = eq.right
+	} else if s, ok := maybeString(eq.right); ok {
+		stringOperand = s
+		otherOperand = eq.left
 	}
-	// Are we checking that a variable is empty?
-	if isEmptyString(eq.left) {
-		emitSimple(eq.right)
-		return
-	} else if isEmptyString(eq.right) {
-		emitSimple(eq.left)
-		return
 
+	// If we've identified one of the operands as being a string literal, check
+	// for some special cases we can do to simplify the resulting expression.
+	if otherOperand != nil {
+		if stringOperand == "" {
+			if eq.isEq {
+				gctx.write("not ")
+			}
+			otherOperand.emit(gctx)
+			return
+		}
+		if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
+			if !eq.isEq {
+				gctx.write("not ")
+			}
+			otherOperand.emit(gctx)
+			return
+		}
 	}
+
+	if eq.left.typ() != eq.right.typ() {
+		eq.left = &toStringExpr{expr: eq.left}
+		eq.right = &toStringExpr{expr: eq.right}
+	}
+
 	// General case
 	eq.left.emit(gctx)
 	if eq.isEq {
@@ -273,18 +374,21 @@
 	eq.emit(gctx)
 }
 
+func (eq *eqExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	eq.left = eq.left.transform(transformer)
+	eq.right = eq.right.transform(transformer)
+	if replacement := transformer(eq); replacement != nil {
+		return replacement
+	} else {
+		return eq
+	}
+}
+
 // variableDefinedExpr corresponds to Make's ifdef VAR
 type variableDefinedExpr struct {
 	v variable
 }
 
-func (v *variableDefinedExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = v
-	same = true
-	return
-
-}
-
 func (v *variableDefinedExpr) emit(gctx *generationContext) {
 	if v.v != nil {
 		v.v.emitDefined(gctx)
@@ -301,24 +405,13 @@
 	v.emit(gctx)
 }
 
-type listExpr struct {
-	items []starlarkExpr
+func (v *variableDefinedExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	// TODO: VariableDefinedExpr isn't really an expression?
+	return v
 }
 
-func (l *listExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newItems := make([]starlarkExpr, len(l.items))
-	same = true
-	for i, item := range l.items {
-		var sameItem bool
-		newItems[i], sameItem = item.eval(valueMap)
-		same = same && sameItem
-	}
-	if same {
-		res = l
-	} else {
-		res = &listExpr{newItems}
-	}
-	return
+type listExpr struct {
+	items []starlarkExpr
 }
 
 func (l *listExpr) emit(gctx *generationContext) {
@@ -355,6 +448,19 @@
 	l.emit(gctx)
 }
 
+func (l *listExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(l.items))
+	for i, item := range l.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	l.items = itemsCopy
+	if replacement := transformer(l); replacement != nil {
+		return replacement
+	} else {
+		return l
+	}
+}
+
 func newStringListExpr(items []string) *listExpr {
 	v := listExpr{}
 	for _, item := range items {
@@ -363,7 +469,7 @@
 	return &v
 }
 
-// concatExpr generates epxr1 + expr2 + ... + exprN in Starlark.
+// concatExpr generates expr1 + expr2 + ... + exprN in Starlark.
 type concatExpr struct {
 	items []starlarkExpr
 }
@@ -394,22 +500,6 @@
 	gctx.indentLevel -= 2
 }
 
-func (c *concatExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	same = true
-	xConcat := &concatExpr{items: make([]starlarkExpr, len(c.items))}
-	for i, item := range c.items {
-		var sameItem bool
-		xConcat.items[i], sameItem = item.eval(valueMap)
-		same = same && sameItem
-	}
-	if same {
-		res = c
-	} else {
-		res = xConcat
-	}
-	return
-}
-
 func (_ *concatExpr) typ() starlarkType {
 	return starlarkTypeList
 }
@@ -418,6 +508,19 @@
 	c.emit(gctx)
 }
 
+func (c *concatExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(c.items))
+	for i, item := range c.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	c.items = itemsCopy
+	if replacement := transformer(c); replacement != nil {
+		return replacement
+	} else {
+		return c
+	}
+}
+
 // inExpr generates <expr> [not] in <list>
 type inExpr struct {
 	expr  starlarkExpr
@@ -425,19 +528,6 @@
 	isNot bool
 }
 
-func (i *inExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	x := &inExpr{isNot: i.isNot}
-	var sameExpr, sameList bool
-	x.expr, sameExpr = i.expr.eval(valueMap)
-	x.list, sameList = i.list.eval(valueMap)
-	if same = sameExpr && sameList; same {
-		res = i
-	} else {
-		res = x
-	}
-	return
-}
-
 func (i *inExpr) emit(gctx *generationContext) {
 	i.expr.emit(gctx)
 	if i.isNot {
@@ -456,35 +546,44 @@
 	i.emit(gctx)
 }
 
+func (i *inExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.expr = i.expr.transform(transformer)
+	i.list = i.list.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
 type indexExpr struct {
 	array starlarkExpr
 	index starlarkExpr
 }
 
-func (ix indexExpr) emit(gctx *generationContext) {
+func (ix *indexExpr) emit(gctx *generationContext) {
 	ix.array.emit(gctx)
 	gctx.write("[")
 	ix.index.emit(gctx)
 	gctx.write("]")
 }
 
-func (ix indexExpr) typ() starlarkType {
+func (ix *indexExpr) typ() starlarkType {
 	return starlarkTypeString
 }
 
-func (ix indexExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newArray, isSameArray := ix.array.eval(valueMap)
-	newIndex, isSameIndex := ix.index.eval(valueMap)
-	if same = isSameArray && isSameIndex; same {
-		res = ix
-	} else {
-		res = &indexExpr{newArray, newIndex}
-	}
-	return
+func (ix *indexExpr) emitListVarCopy(gctx *generationContext) {
+	ix.emit(gctx)
 }
 
-func (ix indexExpr) emitListVarCopy(gctx *generationContext) {
-	ix.emit(gctx)
+func (ix *indexExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	ix.array = ix.array.transform(transformer)
+	ix.index = ix.index.transform(transformer)
+	if replacement := transformer(ix); replacement != nil {
+		return replacement
+	} else {
+		return ix
+	}
 }
 
 type callExpr struct {
@@ -494,27 +593,6 @@
 	returnType starlarkType
 }
 
-func (cx *callExpr) eval(valueMap map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	newCallExpr := &callExpr{name: cx.name, args: make([]starlarkExpr, len(cx.args)),
-		returnType: cx.returnType}
-	if cx.object != nil {
-		newCallExpr.object, same = cx.object.eval(valueMap)
-	} else {
-		same = true
-	}
-	for i, args := range cx.args {
-		var s bool
-		newCallExpr.args[i], s = args.eval(valueMap)
-		same = same && s
-	}
-	if same {
-		res = cx
-	} else {
-		res = newCallExpr
-	}
-	return
-}
-
 func (cx *callExpr) emit(gctx *generationContext) {
 	sep := ""
 	if cx.object != nil {
@@ -555,29 +633,146 @@
 	cx.emit(gctx)
 }
 
+func (cx *callExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if cx.object != nil {
+		cx.object = cx.object.transform(transformer)
+	}
+	argsCopy := make([]starlarkExpr, len(cx.args))
+	for i, arg := range cx.args {
+		argsCopy[i] = arg.transform(transformer)
+	}
+	if replacement := transformer(cx); replacement != nil {
+		return replacement
+	} else {
+		return cx
+	}
+}
+
+type ifExpr struct {
+	condition starlarkExpr
+	ifTrue    starlarkExpr
+	ifFalse   starlarkExpr
+}
+
+func (i *ifExpr) emit(gctx *generationContext) {
+	gctx.write("(")
+	i.ifTrue.emit(gctx)
+	gctx.write(" if ")
+	i.condition.emit(gctx)
+	gctx.write(" else ")
+	i.ifFalse.emit(gctx)
+	gctx.write(")")
+}
+
+func (i *ifExpr) typ() starlarkType {
+	tType := i.ifTrue.typ()
+	fType := i.ifFalse.typ()
+	if tType != fType && tType != starlarkTypeUnknown && fType != starlarkTypeUnknown {
+		panic("Conflicting types in if expression")
+	}
+	if tType != starlarkTypeUnknown {
+		return tType
+	} else {
+		return fType
+	}
+}
+
+func (i *ifExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *ifExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.condition = i.condition.transform(transformer)
+	i.ifTrue = i.ifTrue.transform(transformer)
+	i.ifFalse = i.ifFalse.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type identifierExpr struct {
+	name string
+}
+
+func (i *identifierExpr) emit(gctx *generationContext) {
+	gctx.write(i.name)
+}
+
+func (i *identifierExpr) typ() starlarkType {
+	return starlarkTypeUnknown
+}
+
+func (i *identifierExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *identifierExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type foreachExpr struct {
+	varName string
+	list    starlarkExpr
+	action  starlarkExpr
+}
+
+func (f *foreachExpr) emit(gctx *generationContext) {
+	gctx.write("[")
+	f.action.emit(gctx)
+	gctx.write(" for " + f.varName + " in ")
+	f.list.emit(gctx)
+	gctx.write("]")
+}
+
+func (f *foreachExpr) typ() starlarkType {
+	return starlarkTypeList
+}
+
+func (f *foreachExpr) emitListVarCopy(gctx *generationContext) {
+	f.emit(gctx)
+}
+
+func (f *foreachExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	f.list = f.list.transform(transformer)
+	f.action = f.action.transform(transformer)
+	if replacement := transformer(f); replacement != nil {
+		return replacement
+	} else {
+		return f
+	}
+}
+
 type badExpr struct {
-	node    mkparser.Node
-	message string
+	errorLocation ErrorLocation
+	message       string
 }
 
-func (b *badExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
-	res = b
-	same = true
-	return
-}
-
-func (b *badExpr) emit(_ *generationContext) {
-	panic("implement me")
+func (b *badExpr) emit(gctx *generationContext) {
+	gctx.emitConversionError(b.errorLocation, b.message)
 }
 
 func (_ *badExpr) typ() starlarkType {
 	return starlarkTypeUnknown
 }
 
-func (b *badExpr) emitListVarCopy(gctx *generationContext) {
+func (_ *badExpr) emitListVarCopy(_ *generationContext) {
 	panic("implement me")
 }
 
+func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
 func maybeConvertToStringList(expr starlarkExpr) starlarkExpr {
 	if xString, ok := expr.(*stringLiteralExpr); ok {
 		return newStringListExpr(strings.Fields(xString.literal))
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index b05d340..024311e 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -40,16 +40,21 @@
 )
 
 const (
-	baseUri = "//build/make/core:product_config.rbc"
+	annotationCommentPrefix = "RBC#"
+	baseUri                 = "//build/make/core:product_config.rbc"
 	// The name of the struct exported by the product_config.rbc
 	// that contains the functions and variables available to
 	// product configuration Starlark files.
 	baseName = "rblf"
 
+	soongNsPrefix = "SOONG_CONFIG_"
+
 	// And here are the functions and variables:
 	cfnGetCfg          = baseName + ".cfg"
 	cfnMain            = baseName + ".product_configuration"
+	cfnBoardMain       = baseName + ".board_configuration"
 	cfnPrintVars       = baseName + ".printvars"
+	cfnPrintGlobals    = baseName + ".printglobals"
 	cfnWarning         = baseName + ".warning"
 	cfnLocalAppend     = baseName + ".local_append"
 	cfnLocalSetDefault = baseName + ".local_set_default"
@@ -60,10 +65,15 @@
 const (
 	// Phony makefile functions, they are eventually rewritten
 	// according to knownFunctions map
-	addSoongNamespace      = "add_soong_config_namespace"
-	addSoongConfigVarValue = "add_soong_config_var_value"
-	fileExistsPhony        = "$file_exists"
-	wildcardExistsPhony    = "$wildcard_exists"
+	fileExistsPhony = "$file_exists"
+	// The following two macros are obsolete, and will we deleted once
+	// there are deleted from the makefiles:
+	soongConfigNamespaceOld = "add_soong_config_namespace"
+	soongConfigVarSetOld    = "add_soong_config_var_value"
+	soongConfigAppend       = "soong_config_append"
+	soongConfigAssign       = "soong_config_set"
+	soongConfigGet          = "soong_config_get"
+	wildcardExistsPhony     = "$wildcard_exists"
 )
 
 const (
@@ -82,33 +92,42 @@
 	"abspath":                             {baseName + ".abspath", starlarkTypeString, hiddenArgNone},
 	fileExistsPhony:                       {baseName + ".file_exists", starlarkTypeBool, hiddenArgNone},
 	wildcardExistsPhony:                   {baseName + ".file_wildcard_exists", starlarkTypeBool, hiddenArgNone},
-	addSoongNamespace:                     {baseName + ".add_soong_config_namespace", starlarkTypeVoid, hiddenArgGlobal},
-	addSoongConfigVarValue:                {baseName + ".add_soong_config_var_value", starlarkTypeVoid, hiddenArgGlobal},
+	soongConfigNamespaceOld:               {baseName + ".soong_config_namespace", starlarkTypeVoid, hiddenArgGlobal},
+	soongConfigVarSetOld:                  {baseName + ".soong_config_set", starlarkTypeVoid, hiddenArgGlobal},
+	soongConfigAssign:                     {baseName + ".soong_config_set", starlarkTypeVoid, hiddenArgGlobal},
+	soongConfigAppend:                     {baseName + ".soong_config_append", starlarkTypeVoid, hiddenArgGlobal},
+	soongConfigGet:                        {baseName + ".soong_config_get", starlarkTypeString, hiddenArgGlobal},
 	"add-to-product-copy-files-if-exists": {baseName + ".copy_if_exists", starlarkTypeList, hiddenArgNone},
 	"addprefix":                           {baseName + ".addprefix", starlarkTypeList, hiddenArgNone},
 	"addsuffix":                           {baseName + ".addsuffix", starlarkTypeList, hiddenArgNone},
 	"copy-files":                          {baseName + ".copy_files", starlarkTypeList, hiddenArgNone},
 	"dir":                                 {baseName + ".dir", starlarkTypeList, hiddenArgNone},
+	"dist-for-goals":                      {baseName + ".mkdist_for_goals", starlarkTypeVoid, hiddenArgGlobal},
 	"enforce-product-packages-exist":      {baseName + ".enforce_product_packages_exist", starlarkTypeVoid, hiddenArgNone},
 	"error":                               {baseName + ".mkerror", starlarkTypeVoid, hiddenArgNone},
-	"findstring":                          {"!findstring", starlarkTypeInt, hiddenArgNone},
+	"findstring":                          {baseName + ".findstring", starlarkTypeString, hiddenArgNone},
 	"find-copy-subdir-files":              {baseName + ".find_and_copy", starlarkTypeList, hiddenArgNone},
 	"find-word-in-list":                   {"!find-word-in-list", starlarkTypeUnknown, hiddenArgNone}, // internal macro
 	"filter":                              {baseName + ".filter", starlarkTypeList, hiddenArgNone},
 	"filter-out":                          {baseName + ".filter_out", starlarkTypeList, hiddenArgNone},
 	"firstword":                           {"!firstword", starlarkTypeString, hiddenArgNone},
+	"foreach":                             {"!foreach", starlarkTypeList, hiddenArgNone},
 	"get-vendor-board-platforms":          {"!get-vendor-board-platforms", starlarkTypeList, hiddenArgNone}, // internal macro, used by is-board-platform, etc.
+	"if":                                  {"!if", starlarkTypeUnknown, hiddenArgNone},
 	"info":                                {baseName + ".mkinfo", starlarkTypeVoid, hiddenArgNone},
 	"is-android-codename":                 {"!is-android-codename", starlarkTypeBool, hiddenArgNone},         // unused by product config
 	"is-android-codename-in-list":         {"!is-android-codename-in-list", starlarkTypeBool, hiddenArgNone}, // unused by product config
 	"is-board-platform":                   {"!is-board-platform", starlarkTypeBool, hiddenArgNone},
+	"is-board-platform2":                  {baseName + ".board_platform_is", starlarkTypeBool, hiddenArgGlobal},
 	"is-board-platform-in-list":           {"!is-board-platform-in-list", starlarkTypeBool, hiddenArgNone},
+	"is-board-platform-in-list2":          {baseName + ".board_platform_in", starlarkTypeBool, hiddenArgGlobal},
 	"is-chipset-in-board-platform":        {"!is-chipset-in-board-platform", starlarkTypeUnknown, hiddenArgNone},     // unused by product config
 	"is-chipset-prefix-in-board-platform": {"!is-chipset-prefix-in-board-platform", starlarkTypeBool, hiddenArgNone}, // unused by product config
 	"is-not-board-platform":               {"!is-not-board-platform", starlarkTypeBool, hiddenArgNone},               // defined but never used
 	"is-platform-sdk-version-at-least":    {"!is-platform-sdk-version-at-least", starlarkTypeBool, hiddenArgNone},    // unused by product config
 	"is-product-in-list":                  {"!is-product-in-list", starlarkTypeBool, hiddenArgNone},
 	"is-vendor-board-platform":            {"!is-vendor-board-platform", starlarkTypeBool, hiddenArgNone},
+	"is-vendor-board-qcom":                {"!is-vendor-board-qcom", starlarkTypeBool, hiddenArgNone},
 	callLoadAlways:                        {"!inherit-product", starlarkTypeVoid, hiddenArgNone},
 	callLoadIf:                            {"!inherit-product-if-exists", starlarkTypeVoid, hiddenArgNone},
 	"lastword":                            {"!lastword", starlarkTypeString, hiddenArgNone},
@@ -129,34 +148,38 @@
 	"warning":    {baseName + ".mkwarning", starlarkTypeVoid, hiddenArgNone},
 	"word":       {baseName + "!word", starlarkTypeString, hiddenArgNone},
 	"wildcard":   {baseName + ".expand_wildcard", starlarkTypeList, hiddenArgNone},
+	"words":      {baseName + ".words", starlarkTypeList, hiddenArgNone},
 }
 
-var builtinFuncRex = regexp.MustCompile(
-	"^(addprefix|addsuffix|abspath|and|basename|call|dir|error|eval" +
-		"|flavor|foreach|file|filter|filter-out|findstring|firstword|guile" +
-		"|if|info|join|lastword|notdir|or|origin|patsubst|realpath" +
-		"|shell|sort|strip|subst|suffix|value|warning|word|wordlist|words" +
-		"|wildcard)")
+var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
 
 // Conversion request parameters
 type Request struct {
-	MkFile             string    // file to convert
-	Reader             io.Reader // if set, read input from this stream instead
-	RootDir            string    // root directory path used to resolve included files
-	OutputSuffix       string    // generated Starlark files suffix
-	OutputDir          string    // if set, root of the output hierarchy
-	ErrorLogger        ErrorMonitorCB
-	TracedVariables    []string // trace assignment to these variables
-	TraceCalls         bool
-	WarnPartialSuccess bool
-	SourceFS           fs.FS
-	MakefileFinder     MakefileFinder
+	MkFile          string    // file to convert
+	Reader          io.Reader // if set, read input from this stream instead
+	RootDir         string    // root directory path used to resolve included files
+	OutputSuffix    string    // generated Starlark files suffix
+	OutputDir       string    // if set, root of the output hierarchy
+	ErrorLogger     ErrorLogger
+	TracedVariables []string // trace assignment to these variables
+	TraceCalls      bool
+	SourceFS        fs.FS
+	MakefileFinder  MakefileFinder
 }
 
-// An error sink allowing to gather error statistics.
-// NewError is called on every error encountered during processing.
-type ErrorMonitorCB interface {
-	NewError(s string, node mkparser.Node, args ...interface{})
+// ErrorLogger prints errors and gathers error statistics.
+// Its NewError function is called on every error encountered during the conversion.
+type ErrorLogger interface {
+	NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
+}
+
+type ErrorLocation struct {
+	MkFile string
+	MkLine int
+}
+
+func (el ErrorLocation) String() string {
+	return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
 }
 
 // Derives module name for a given file. It is base name
@@ -232,10 +255,6 @@
 		node.emit(gctx)
 	}
 
-	if ss.hasErrors && ss.warnPartialSuccess {
-		gctx.newLine()
-		gctx.writef("%s(%q, %q)", cfnWarning, filepath.Base(ss.mkFile), "partially successful conversion")
-	}
 	if gctx.starScript.traceCalls {
 		gctx.newLine()
 		gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
@@ -290,6 +309,10 @@
 	gctx.writef("%*s", 2*gctx.indentLevel, "")
 }
 
+func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
+	gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
+}
+
 type knownVariable struct {
 	name      string
 	class     varClass
@@ -354,17 +377,17 @@
 
 // Information about the generated Starlark script.
 type StarlarkScript struct {
-	mkFile             string
-	moduleName         string
-	mkPos              scanner.Position
-	nodes              []starlarkNode
-	inherited          []*moduleInfo
-	hasErrors          bool
-	topDir             string
-	traceCalls         bool // print enter/exit each init function
-	warnPartialSuccess bool
-	sourceFS           fs.FS
-	makefileFinder     MakefileFinder
+	mkFile         string
+	moduleName     string
+	mkPos          scanner.Position
+	nodes          []starlarkNode
+	inherited      []*moduleInfo
+	hasErrors      bool
+	topDir         string
+	traceCalls     bool // print enter/exit each init function
+	sourceFS       fs.FS
+	makefileFinder MakefileFinder
+	nodeLocator    func(pos mkparser.Pos) int
 }
 
 func (ss *StarlarkScript) newNode(node starlarkNode) {
@@ -388,9 +411,8 @@
 	ifNestLevel      int
 	moduleNameCount  map[string]int // count of imported modules with given basename
 	fatalError       error
-	builtinMakeVars  map[string]starlarkExpr
 	outputSuffix     string
-	errorLogger      ErrorMonitorCB
+	errorLogger      ErrorLogger
 	tracedVariables  map[string]bool // variables to be traced in the generated script
 	variables        map[string]variable
 	varAssignments   *varAssignmentScope
@@ -399,6 +421,7 @@
 	outputDir        string
 	dependentModules map[string]*moduleInfo
 	soongNamespaces  map[string]map[string]bool
+	includeTops      []string
 }
 
 func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
@@ -440,10 +463,10 @@
 		currentNodeIndex: 0,
 		ifNestLevel:      0,
 		moduleNameCount:  make(map[string]int),
-		builtinMakeVars:  map[string]starlarkExpr{},
 		variables:        make(map[string]variable),
 		dependentModules: make(map[string]*moduleInfo),
 		soongNamespaces:  make(map[string]map[string]bool),
+		includeTops:      []string{"vendor/google-devices"},
 	}
 	ctx.pushVarAssignments()
 	for _, item := range predefined {
@@ -522,8 +545,15 @@
 		return
 	}
 	name := a.Name.Strings[0]
-	const soongNsPrefix = "SOONG_CONFIG_"
-	// Soong confuguration
+	// The `override` directive
+	//      override FOO :=
+	// is parsed as an assignment to a variable named `override FOO`.
+	// There are very few places where `override` is used, just flag it.
+	if strings.HasPrefix(name, "override ") {
+		ctx.errorf(a, "cannot handle override directive")
+	}
+
+	// Soong configuration
 	if strings.HasPrefix(name, soongNsPrefix) {
 		ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
 		return
@@ -534,7 +564,7 @@
 		return
 	}
 	_, isTraced := ctx.tracedVariables[name]
-	asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced}
+	asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
 	if lhs.valueType() == starlarkTypeUnknown {
 		// Try to divine variable type from the RHS
 		asgn.value = ctx.parseMakeString(a, a.Value)
@@ -568,9 +598,6 @@
 		}
 	}
 
-	// TODO(asmundak): move evaluation to a separate pass
-	asgn.value, _ = asgn.value.eval(ctx.builtinMakeVars)
-
 	asgn.previous = ctx.lastAssignment(name)
 	ctx.setLastAssignment(name, asgn)
 	switch a.Type {
@@ -597,7 +624,6 @@
 		ctx.wrapBadExpr(xBad)
 		return
 	}
-	val, _ = val.eval(ctx.builtinMakeVars)
 
 	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
 	// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
@@ -615,7 +641,7 @@
 		for _, ns := range strings.Fields(s) {
 			ctx.addSoongNamespace(ns)
 			ctx.receiver.newNode(&exprNode{&callExpr{
-				name:       addSoongNamespace,
+				name:       soongConfigNamespaceOld,
 				args:       []starlarkExpr{&stringLiteralExpr{ns}},
 				returnType: starlarkTypeVoid,
 			}})
@@ -624,7 +650,7 @@
 		// Upon seeing
 		//      SOONG_CONFIG_x_y = v
 		// find a namespace called `x` and act as if we encountered
-		//      $(call add_config_var_value(x,y,v)
+		//      $(call soong_config_set,x,y,v)
 		// or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
 		// it.
 		// Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
@@ -665,8 +691,12 @@
 			ctx.errorf(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)
 			return
 		}
+		fname := soongConfigAssign
+		if asgn.Type == "+=" {
+			fname = soongConfigAppend
+		}
 		ctx.receiver.newNode(&exprNode{&callExpr{
-			name:       addSoongConfigVarValue,
+			name:       fname,
 			args:       []starlarkExpr{&stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
 			returnType: starlarkTypeVoid,
 		}})
@@ -726,7 +756,7 @@
 func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
 	modulePath := ctx.loadedModulePath(path)
 	if mi, ok := ctx.dependentModules[modulePath]; ok {
-		mi.optional = mi.optional || optional
+		mi.optional = mi.optional && optional
 		return mi
 	}
 	moduleName := moduleNameForFile(path)
@@ -749,20 +779,24 @@
 
 func (ctx *parseContext) handleSubConfig(
 	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
-	pathExpr, _ = pathExpr.eval(ctx.builtinMakeVars)
 
 	// In a simple case, the name of a module to inherit/include is known statically.
 	if path, ok := maybeString(pathExpr); ok {
+		// Note that even if this directive loads a module unconditionally, a module may be
+		// absent without causing any harm if this directive is inside an if/else block.
+		moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
 		if strings.Contains(path, "*") {
 			if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
 				for _, p := range paths {
-					processModule(inheritedStaticModule{ctx.newDependentModule(p, !loadAlways), loadAlways})
+					mi := ctx.newDependentModule(p, !moduleShouldExist)
+					processModule(inheritedStaticModule{mi, loadAlways})
 				}
 			} else {
 				ctx.errorf(v, "cannot glob wildcard argument")
 			}
 		} else {
-			processModule(inheritedStaticModule{ctx.newDependentModule(path, !loadAlways), loadAlways})
+			mi := ctx.newDependentModule(path, !moduleShouldExist)
+			processModule(inheritedStaticModule{mi, loadAlways})
 		}
 		return
 	}
@@ -794,21 +828,15 @@
 			pathPattern = append(pathPattern, chunk)
 		}
 	}
-	if pathPattern[0] != "" {
-		matchingPaths = ctx.findMatchingPaths(pathPattern)
-	} else {
-		// Heuristics -- if pattern starts from top, restrict it to the directories where
-		// we know inherit-product uses dynamically calculated path. Restrict it even further
-		// for certain path which would yield too many useless matches
-		if len(varPath.chunks) == 2 && varPath.chunks[1] == "/BoardConfigVendor.mk" {
-			pathPattern[0] = "vendor/google_devices"
-			matchingPaths = ctx.findMatchingPaths(pathPattern)
-		} else {
-			for _, t := range []string{"vendor/qcom", "vendor/google_devices"} {
-				pathPattern[0] = t
-				matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
-			}
+	if pathPattern[0] == "" {
+		// If pattern starts from the top. restrict it to the directories where
+		// we know inherit-product uses dynamically calculated path.
+		for _, p := range ctx.includeTops {
+			pathPattern[0] = p
+			matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
 		}
+	} else {
+		matchingPaths = ctx.findMatchingPaths(pathPattern)
 	}
 	// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
 	const maxMatchingFiles = 150
@@ -852,13 +880,13 @@
 
 func (ctx *parseContext) handleInheritModule(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
 	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
-		ctx.receiver.newNode(&inheritNode{im})
+		ctx.receiver.newNode(&inheritNode{im, loadAlways})
 	})
 }
 
 func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
 	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
-		ctx.receiver.newNode(&includeNode{im})
+		ctx.receiver.newNode(&includeNode{im, loadAlways})
 	})
 }
 
@@ -945,25 +973,16 @@
 	ctx.pushReceiver(&block)
 	for ctx.hasNodes() {
 		node := ctx.getNode()
-		if ctx.handleSimpleStatement(node) {
-			continue
-		}
-		switch d := node.(type) {
-		case *mkparser.Directive:
+		if d, ok := node.(*mkparser.Directive); ok {
 			switch d.Name {
 			case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
 				ctx.popReceiver()
 				ctx.receiver.newNode(&block)
 				ctx.backNode()
 				return
-			case "ifdef", "ifndef", "ifeq", "ifneq":
-				ctx.handleIfBlock(d)
-			default:
-				ctx.errorf(d, "unexpected directive %s", d.Name)
 			}
-		default:
-			ctx.errorf(node, "unexpected statement")
 		}
+		ctx.handleSimpleStatement(node)
 	}
 	ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
 	ctx.popReceiver()
@@ -1003,10 +1022,10 @@
 func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
 	message := fmt.Sprintf(text, args...)
 	if ctx.errorLogger != nil {
-		ctx.errorLogger.NewError(text, node, args)
+		ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
 	}
 	ctx.script.hasErrors = true
-	return &badExpr{node, message}
+	return &badExpr{errorLocation: ctx.errorLocation(node), message: message}
 }
 
 func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
@@ -1024,48 +1043,54 @@
 	args[1].TrimLeftSpaces()
 
 	isEq := !strings.HasSuffix(cond.Name, "neq")
-	switch xLeft := ctx.parseMakeString(cond, args[0]).(type) {
-	case *stringLiteralExpr, *variableRefExpr:
-		switch xRight := ctx.parseMakeString(cond, args[1]).(type) {
-		case *stringLiteralExpr, *variableRefExpr:
-			return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
-		case *badExpr:
-			return xRight
-		default:
-			expr, ok := ctx.parseCheckFunctionCallResult(cond, xLeft, args[1])
-			if ok {
-				return expr
-			}
-			return ctx.newBadExpr(cond, "right operand is too complex: %s", args[1].Dump())
-		}
-	case *badExpr:
-		return xLeft
-	default:
-		switch xRight := ctx.parseMakeString(cond, args[1]).(type) {
-		case *stringLiteralExpr, *variableRefExpr:
-			expr, ok := ctx.parseCheckFunctionCallResult(cond, xRight, args[0])
-			if ok {
-				return expr
-			}
-			return ctx.newBadExpr(cond, "left operand is too complex: %s", args[0].Dump())
-		case *badExpr:
-			return xRight
-		default:
-			return ctx.newBadExpr(cond, "operands are too complex: (%s,%s)", args[0].Dump(), args[1].Dump())
-		}
+	xLeft := ctx.parseMakeString(cond, args[0])
+	xRight := ctx.parseMakeString(cond, args[1])
+	if bad, ok := xLeft.(*badExpr); ok {
+		return bad
 	}
+	if bad, ok := xRight.(*badExpr); ok {
+		return bad
+	}
+
+	if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
+		return expr
+	}
+
+	return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
 }
 
-func (ctx *parseContext) parseCheckFunctionCallResult(directive *mkparser.Directive, xValue starlarkExpr,
-	varArg *mkparser.MakeString) (starlarkExpr, bool) {
-	mkSingleVar, ok := varArg.SingleVariable()
-	if !ok {
+// Given an if statement's directive and the left/right starlarkExprs,
+// check if the starlarkExprs are one of a few hardcoded special cases
+// that can be converted to a simpler equalify expression than simply comparing
+// the two.
+func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
+	right starlarkExpr) (starlarkExpr, bool) {
+	isEq := !strings.HasSuffix(directive.Name, "neq")
+
+	// All the special cases require a call on one side and a
+	// string literal/variable on the other. Turn the left/right variables into
+	// call/value variables, and return false if that's not possible.
+	var value starlarkExpr = nil
+	call, ok := left.(*callExpr)
+	if ok {
+		switch right.(type) {
+		case *stringLiteralExpr, *variableRefExpr:
+			value = right
+		}
+	} else {
+		call, _ = right.(*callExpr)
+		switch left.(type) {
+		case *stringLiteralExpr, *variableRefExpr:
+			value = left
+		}
+	}
+
+	if call == nil || value == nil {
 		return nil, false
 	}
-	expr := ctx.parseReference(directive, mkSingleVar)
-	negate := strings.HasSuffix(directive.Name, "neq")
+
 	checkIsSomethingFunction := func(xCall *callExpr) starlarkExpr {
-		s, ok := maybeString(xValue)
+		s, ok := maybeString(value)
 		if !ok || s != "true" {
 			return ctx.newBadExpr(directive,
 				fmt.Sprintf("the result of %s can be compared only to 'true'", xCall.name))
@@ -1075,67 +1100,88 @@
 		}
 		return nil
 	}
-	switch x := expr.(type) {
-	case *callExpr:
-		switch x.name {
-		case "filter":
-			return ctx.parseCompareFilterFuncResult(directive, x, xValue, !negate), true
-		case "filter-out":
-			return ctx.parseCompareFilterFuncResult(directive, x, xValue, negate), true
-		case "wildcard":
-			return ctx.parseCompareWildcardFuncResult(directive, x, xValue, negate), true
-		case "findstring":
-			return ctx.parseCheckFindstringFuncResult(directive, x, xValue, negate), true
-		case "strip":
-			return ctx.parseCompareStripFuncResult(directive, x, xValue, negate), true
-		case "is-board-platform":
-			if xBad := checkIsSomethingFunction(x); xBad != nil {
-				return xBad, true
-			}
-			return &eqExpr{
-				left:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-				right: x.args[0],
-				isEq:  !negate,
-			}, true
-		case "is-board-platform-in-list":
-			if xBad := checkIsSomethingFunction(x); xBad != nil {
-				return xBad, true
-			}
-			return &inExpr{
-				expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-				list:  maybeConvertToStringList(x.args[0]),
-				isNot: negate,
-			}, true
-		case "is-product-in-list":
-			if xBad := checkIsSomethingFunction(x); xBad != nil {
-				return xBad, true
-			}
-			return &inExpr{
-				expr:  &variableRefExpr{ctx.addVariable("TARGET_PRODUCT"), true},
-				list:  maybeConvertToStringList(x.args[0]),
-				isNot: negate,
-			}, true
-		case "is-vendor-board-platform":
-			if xBad := checkIsSomethingFunction(x); xBad != nil {
-				return xBad, true
-			}
-			s, ok := maybeString(x.args[0])
-			if !ok {
-				return ctx.newBadExpr(directive, "cannot handle non-constant argument to is-vendor-board-platform"), true
-			}
-			return &inExpr{
-				expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-				list:  &variableRefExpr{ctx.addVariable(s + "_BOARD_PLATFORMS"), true},
-				isNot: negate,
-			}, true
-		default:
-			return ctx.newBadExpr(directive, "Unknown function in ifeq: %s", x.name), true
+
+	switch call.name {
+	case "filter", "filter-out":
+		return ctx.parseCompareFilterFuncResult(directive, call, value, isEq), true
+	case "wildcard":
+		return ctx.parseCompareWildcardFuncResult(directive, call, value, !isEq), true
+	case "findstring":
+		return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
+	case "strip":
+		return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
+	case "is-board-platform":
+		if xBad := checkIsSomethingFunction(call); xBad != nil {
+			return xBad, true
 		}
-	case *badExpr:
-		return x, true
-	default:
-		return nil, false
+		return &eqExpr{
+			left:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			right: call.args[0],
+			isEq:  isEq,
+		}, true
+	case "is-board-platform-in-list":
+		if xBad := checkIsSomethingFunction(call); xBad != nil {
+			return xBad, true
+		}
+		return &inExpr{
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			list:  maybeConvertToStringList(call.args[0]),
+			isNot: !isEq,
+		}, true
+	case "is-product-in-list":
+		if xBad := checkIsSomethingFunction(call); xBad != nil {
+			return xBad, true
+		}
+		return &inExpr{
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT"), true),
+			list:  maybeConvertToStringList(call.args[0]),
+			isNot: !isEq,
+		}, true
+	case "is-vendor-board-platform":
+		if xBad := checkIsSomethingFunction(call); xBad != nil {
+			return xBad, true
+		}
+		s, ok := maybeString(call.args[0])
+		if !ok {
+			return ctx.newBadExpr(directive, "cannot handle non-constant argument to is-vendor-board-platform"), true
+		}
+		return &inExpr{
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			list:  NewVariableRefExpr(ctx.addVariable(s+"_BOARD_PLATFORMS"), true),
+			isNot: !isEq,
+		}, true
+
+	case "is-board-platform2", "is-board-platform-in-list2":
+		if s, ok := maybeString(value); !ok || s != "" {
+			return ctx.newBadExpr(directive,
+				fmt.Sprintf("the result of %s can be compared only to empty", call.name)), true
+		}
+		if len(call.args) != 1 {
+			return ctx.newBadExpr(directive, "%s requires an argument", call.name), true
+		}
+		cc := &callExpr{
+			name:       call.name,
+			args:       []starlarkExpr{call.args[0]},
+			returnType: starlarkTypeBool,
+		}
+		if isEq {
+			return &notExpr{cc}, true
+		}
+		return cc, true
+	case "is-vendor-board-qcom":
+		if s, ok := maybeString(value); !ok || s != "" {
+			return ctx.newBadExpr(directive,
+				fmt.Sprintf("the result of %s can be compared only to empty", call.name)), true
+		}
+		// if the expression is ifneq (,$(call is-vendor-board-platform,...)), negate==true,
+		// so we should set inExpr.isNot to false
+		return &inExpr{
+			expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM"), false),
+			list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS"), true),
+			isNot: isEq,
+		}, true
 	}
+	return nil, false
 }
 
 func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
@@ -1157,17 +1203,21 @@
 		}
 		// Either pattern or text should be const, and the
 		// non-const one should be varRefExpr
-		if xInList, ok = xPattern.(*stringLiteralExpr); ok {
+		if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
 			expr = xText
 		} else if xInList, ok = xText.(*stringLiteralExpr); ok {
 			expr = xPattern
 		} else {
-			return &callExpr{
+			expr = &callExpr{
 				object:     nil,
 				name:       filterFuncCall.name,
 				args:       filterFuncCall.args,
 				returnType: starlarkTypeBool,
 			}
+			if negate {
+				expr = &notExpr{expr: expr}
+			}
+			return expr
 		}
 	case *variableRefExpr:
 		if v, ok := xPattern.(*variableRefExpr); ok {
@@ -1228,8 +1278,21 @@
 			right: &intLiteralExpr{-1},
 			isEq:  !negate,
 		}
+	} else if s, ok := maybeString(xValue); ok {
+		if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
+			return &eqExpr{
+				left: &callExpr{
+					object:     xCall.args[1],
+					name:       "find",
+					args:       []starlarkExpr{xCall.args[0]},
+					returnType: starlarkTypeInt,
+				},
+				right: &intLiteralExpr{-1},
+				isEq:  negate,
+			}
+		}
 	}
-	return ctx.newBadExpr(directive, "findstring result can be compared only to empty: %s", xValue)
+	return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
 }
 
 func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
@@ -1268,8 +1331,40 @@
 				returnType: starlarkTypeUnknown,
 			}
 		}
+		if strings.HasPrefix(refDump, soongNsPrefix) {
+			// TODO (asmundak): if we find many, maybe handle them.
+			return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
+		}
+		// Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
+		if strings.Contains(refDump, ":") {
+			parts := strings.SplitN(refDump, ":", 2)
+			substParts := strings.SplitN(parts[1], "=", 2)
+			if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
+				return ctx.newBadExpr(node, "Invalid substitution reference")
+			}
+			if !strings.Contains(substParts[0], "%") {
+				if strings.Contains(substParts[1], "%") {
+					return ctx.newBadExpr(node, "A substitution reference must have a %% in the \"before\" part of the substitution if it has one in the \"after\" part.")
+				}
+				substParts[0] = "%" + substParts[0]
+				substParts[1] = "%" + substParts[1]
+			}
+			v := ctx.addVariable(parts[0])
+			if v == nil {
+				return ctx.newBadExpr(node, "unknown variable %s", refDump)
+			}
+			return &callExpr{
+				name:       "patsubst",
+				returnType: knownFunctions["patsubst"].returnType,
+				args: []starlarkExpr{
+					&stringLiteralExpr{literal: substParts[0]},
+					&stringLiteralExpr{literal: substParts[1]},
+					NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil),
+				},
+			}
+		}
 		if v := ctx.addVariable(refDump); v != nil {
-			return &variableRefExpr{v, ctx.lastAssignment(v.name()) != nil}
+			return NewVariableRefExpr(v, ctx.lastAssignment(v.name()) != nil)
 		}
 		return ctx.newBadExpr(node, "unknown variable %s", refDump)
 	}
@@ -1305,12 +1400,16 @@
 		return ctx.newBadExpr(node, "cannot handle invoking %s", expr.name)
 	}
 	switch expr.name {
+	case "if":
+		return ctx.parseIfFunc(node, args)
+	case "foreach":
+		return ctx.parseForeachFunc(node, args)
 	case "word":
 		return ctx.parseWordFunc(node, args)
 	case "firstword", "lastword":
 		return ctx.parseFirstOrLastwordFunc(node, expr.name, args)
 	case "my-dir":
-		return &variableRefExpr{ctx.addVariable("LOCAL_PATH"), true}
+		return NewVariableRefExpr(ctx.addVariable("LOCAL_PATH"), true)
 	case "subst", "patsubst":
 		return ctx.parseSubstFunc(node, expr.name, args)
 	default:
@@ -1332,11 +1431,14 @@
 	if len(words) != 3 {
 		return ctx.newBadExpr(node, "%s function should have 3 arguments", fname)
 	}
-	if !words[0].Const() || !words[1].Const() {
-		return ctx.newBadExpr(node, "%s function's from and to arguments should be constant", fname)
+	from := ctx.parseMakeString(node, words[0])
+	if xBad, ok := from.(*badExpr); ok {
+		return xBad
 	}
-	from := words[0].Strings[0]
-	to := words[1].Strings[0]
+	to := ctx.parseMakeString(node, words[1])
+	if xBad, ok := to.(*badExpr); ok {
+		return xBad
+	}
 	words[2].TrimLeftSpaces()
 	words[2].TrimRightSpaces()
 	obj := ctx.parseMakeString(node, words[2])
@@ -1346,17 +1448,78 @@
 		return &callExpr{
 			object:     obj,
 			name:       "replace",
-			args:       []starlarkExpr{&stringLiteralExpr{from}, &stringLiteralExpr{to}},
+			args:       []starlarkExpr{from, to},
 			returnType: typ,
 		}
 	}
 	return &callExpr{
 		name:       fname,
-		args:       []starlarkExpr{&stringLiteralExpr{from}, &stringLiteralExpr{to}, obj},
+		args:       []starlarkExpr{from, to, obj},
 		returnType: obj.typ(),
 	}
 }
 
+func (ctx *parseContext) parseIfFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 2 && len(words) != 3 {
+		return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
+	}
+	condition := ctx.parseMakeString(node, words[0])
+	ifTrue := ctx.parseMakeString(node, words[1])
+	var ifFalse starlarkExpr
+	if len(words) == 3 {
+		ifFalse = ctx.parseMakeString(node, words[2])
+	} else {
+		switch ifTrue.typ() {
+		case starlarkTypeList:
+			ifFalse = &listExpr{items: []starlarkExpr{}}
+		case starlarkTypeInt:
+			ifFalse = &intLiteralExpr{literal: 0}
+		case starlarkTypeBool:
+			ifFalse = &boolLiteralExpr{literal: false}
+		default:
+			ifFalse = &stringLiteralExpr{literal: ""}
+		}
+	}
+	return &ifExpr{
+		condition,
+		ifTrue,
+		ifFalse,
+	}
+}
+
+func (ctx *parseContext) parseForeachFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 3 {
+		return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
+	}
+	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+		return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
+	}
+	loopVarName := words[0].Strings[0]
+	list := ctx.parseMakeString(node, words[1])
+	action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
+		if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+			return &identifierExpr{loopVarName}
+		}
+		return nil
+	})
+
+	if list.typ() != starlarkTypeList {
+		list = &callExpr{
+			name:       "words",
+			returnType: knownFunctions["words"].returnType,
+			args:       []starlarkExpr{list},
+		}
+	}
+
+	return &foreachExpr{
+		varName: loopVarName,
+		list:    list,
+		action:  action,
+	}
+}
+
 func (ctx *parseContext) parseWordFunc(node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
 	words := args.Split(",")
 	if len(words) != 2 {
@@ -1378,7 +1541,7 @@
 	if array.typ() != starlarkTypeList {
 		array = &callExpr{object: array, name: "split", returnType: starlarkTypeList}
 	}
-	return indexExpr{array, &intLiteralExpr{int(index - 1)}}
+	return &indexExpr{array, &intLiteralExpr{int(index - 1)}}
 }
 
 func (ctx *parseContext) parseFirstOrLastwordFunc(node mkparser.Node, name string, args *mkparser.MakeString) starlarkExpr {
@@ -1409,26 +1572,27 @@
 	// If we reached here, it's neither string literal nor a simple variable,
 	// we need a full-blown interpolation node that will generate
 	// "a%b%c" % (X, Y) for a$(X)b$(Y)c
-	xInterp := &interpolateExpr{args: make([]starlarkExpr, len(mk.Variables))}
-	for i, ref := range mk.Variables {
-		arg := ctx.parseReference(node, ref.Name)
-		if x, ok := arg.(*badExpr); ok {
-			return x
+	parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
+	for i := 0; i < len(parts); i++ {
+		if i%2 == 0 {
+			parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
+		} else {
+			parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
+			if x, ok := parts[i].(*badExpr); ok {
+				return x
+			}
 		}
-		xInterp.args[i] = arg
 	}
-	xInterp.chunks = append(xInterp.chunks, mk.Strings...)
-	return xInterp
+	return NewInterpolateExpr(parts)
 }
 
 // Handles the statements whose treatment is the same in all contexts: comment,
 // assignment, variable (which is a macro call in reality) and all constructs that
 // do not handle in any context ('define directive and any unrecognized stuff).
-// Return true if we handled it.
-func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) bool {
-	handled := true
+func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) {
 	switch x := node.(type) {
 	case *mkparser.Comment:
+		ctx.maybeHandleAnnotation(x)
 		ctx.insertComment("#" + x.Comment)
 	case *mkparser.Assignment:
 		ctx.handleAssignment(x)
@@ -1440,13 +1604,36 @@
 			ctx.handleDefine(x)
 		case "include", "-include":
 			ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+		case "ifeq", "ifneq", "ifdef", "ifndef":
+			ctx.handleIfBlock(x)
 		default:
-			handled = false
+			ctx.errorf(x, "unexpected directive %s", x.Name)
 		}
 	default:
-		ctx.errorf(x, "unsupported line %s", x.Dump())
+		ctx.errorf(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))
 	}
-	return handled
+}
+
+// Processes annotation. An annotation is a comment that starts with #RBC# and provides
+// a conversion hint -- say, where to look for the dynamically calculated inherit/include
+// paths.
+func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) {
+	maybeTrim := func(s, prefix string) (string, bool) {
+		if strings.HasPrefix(s, prefix) {
+			return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
+		}
+		return s, false
+	}
+	annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
+	if !ok {
+		return
+	}
+	if p, ok := maybeTrim(annotation, "include_top"); ok {
+		ctx.includeTops = append(ctx.includeTops, p)
+		return
+	}
+	ctx.errorf(cnode, "unsupported annotation %s", cnode.Comment)
+
 }
 
 func (ctx *parseContext) insertComment(s string) {
@@ -1462,17 +1649,14 @@
 // records that the given node failed to be converted and includes an explanatory message
 func (ctx *parseContext) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
 	if ctx.errorLogger != nil {
-		ctx.errorLogger.NewError(message, failedNode, args...)
+		ctx.errorLogger.NewError(ctx.errorLocation(failedNode), failedNode, message, args...)
 	}
-	message = fmt.Sprintf(message, args...)
-	ctx.insertComment(fmt.Sprintf("# MK2RBC TRANSLATION ERROR: %s", message))
-	ctx.carryAsComment(failedNode)
+	ctx.receiver.newNode(&exprNode{ctx.newBadExpr(failedNode, message, args...)})
 	ctx.script.hasErrors = true
 }
 
 func (ctx *parseContext) wrapBadExpr(xBad *badExpr) {
-	ctx.insertComment(fmt.Sprintf("# MK2RBC TRANSLATION ERROR: %s", xBad.message))
-	ctx.carryAsComment(xBad.node)
+	ctx.receiver.newNode(&exprNode{xBad})
 }
 
 func (ctx *parseContext) loadedModulePath(path string) string {
@@ -1538,6 +1722,10 @@
 	return ok
 }
 
+func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
+	return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
+}
+
 func (ss *StarlarkScript) String() string {
 	return NewGenerateContext(ss).emit()
 }
@@ -1576,13 +1764,13 @@
 		return nil, fmt.Errorf("bad makefile %s", req.MkFile)
 	}
 	starScript := &StarlarkScript{
-		moduleName:         moduleNameForFile(req.MkFile),
-		mkFile:             req.MkFile,
-		topDir:             req.RootDir,
-		traceCalls:         req.TraceCalls,
-		warnPartialSuccess: req.WarnPartialSuccess,
-		sourceFS:           req.SourceFS,
-		makefileFinder:     req.MakefileFinder,
+		moduleName:     moduleNameForFile(req.MkFile),
+		mkFile:         req.MkFile,
+		topDir:         req.RootDir,
+		traceCalls:     req.TraceCalls,
+		sourceFS:       req.SourceFS,
+		makefileFinder: req.MakefileFinder,
+		nodeLocator:    func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
 	}
 	ctx := newParseContext(starScript, nodes)
 	ctx.outputSuffix = req.OutputSuffix
@@ -1596,21 +1784,7 @@
 	}
 	ctx.pushReceiver(starScript)
 	for ctx.hasNodes() && ctx.fatalError == nil {
-		node := ctx.getNode()
-		if ctx.handleSimpleStatement(node) {
-			continue
-		}
-		switch x := node.(type) {
-		case *mkparser.Directive:
-			switch x.Name {
-			case "ifeq", "ifneq", "ifdef", "ifndef":
-				ctx.handleIfBlock(x)
-			default:
-				ctx.errorf(x, "unexpected directive %s", x.Name)
-			}
-		default:
-			ctx.errorf(x, "unsupported line")
-		}
+		ctx.handleSimpleStatement(ctx.getNode())
 	}
 	if ctx.fatalError != nil {
 		return nil, ctx.fatalError
@@ -1618,12 +1792,23 @@
 	return starScript, nil
 }
 
-func Launcher(path, name string) string {
+func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
 	var buf bytes.Buffer
 	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
-	fmt.Fprintf(&buf, "load(%q, \"init\")\n", path)
-	fmt.Fprintf(&buf, "g, config = %s(%q, init)\n", cfnMain, name)
-	fmt.Fprintf(&buf, "%s(g, config)\n", cfnPrintVars)
+	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
+	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
+	fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
+	return buf.String()
+}
+
+func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
+	var buf bytes.Buffer
+	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
+	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
+	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
+	fmt.Fprintf(&buf, "globals, cfg, globals_base = %s(init, input_variables_init)\n", cfnBoardMain)
+	fmt.Fprintf(&buf, "# TODO: Some product config variables need to be printed, but most are readonly so we can't just print cfg here.\n")
+	fmt.Fprintf(&buf, "%s((globals, cfg, globals_base))\n", cfnPrintVars)
 	return buf.String()
 }
 
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index ca7fe6f..94c4fe6 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -105,37 +105,37 @@
 PRODUCT_NAME := $(call foo1, bar)
 PRODUCT_NAME := $(call foo0)
 `,
-		expected: `# MK2RBC TRANSLATION ERROR: cannot handle invoking foo1
-# PRODUCT_NAME := $(call foo1, bar)
-# MK2RBC TRANSLATION ERROR: cannot handle invoking foo0
-# PRODUCT_NAME := $(call foo0)
-load("//build/make/core:product_config.rbc", "rblf")
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.warning("product.mk", "partially successful conversion")
+  rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
+  rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
 `,
 	},
 	{
 		desc:   "Inherit configuration always",
 		mkname: "product.mk",
 		in: `
-ifdef PRODUCT_NAME
 $(call inherit-product, part.mk)
+ifdef PRODUCT_NAME
+$(call inherit-product, part1.mk)
 else # Comment
-$(call inherit-product, $(LOCAL_PATH)/part.mk)
+$(call inherit-product, $(LOCAL_PATH)/part1.mk)
 endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 load(":part.star", _part_init = "init")
+load(":part1.star|init", _part1_init = "init")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "part", _part_init)
   if g.get("PRODUCT_NAME") != None:
-    rblf.inherit(handle, "part", _part_init)
+    rblf.inherit(handle, "part1", _part1_init)
   else:
     # Comment
-    rblf.inherit(handle, "part", _part_init)
+    rblf.inherit(handle, "part1", _part1_init)
 `,
 	},
 	{
@@ -158,22 +158,25 @@
 		desc:   "Include configuration",
 		mkname: "product.mk",
 		in: `
-ifdef PRODUCT_NAME
 include part.mk
+ifdef PRODUCT_NAME
+include part1.mk
 else
--include $(LOCAL_PATH)/part.mk)
+-include $(LOCAL_PATH)/part1.mk)
 endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
-load(":part.star|init", _part_init = "init")
+load(":part.star", _part_init = "init")
+load(":part1.star|init", _part1_init = "init")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
+  _part_init(g, handle)
   if g.get("PRODUCT_NAME") != None:
-    _part_init(g, handle)
+    _part1_init(g, handle)
   else:
-    if _part_init != None:
-      _part_init(g, handle)
+    if _part1_init != None:
+      _part1_init(g, handle)
 `,
 	},
 
@@ -201,15 +204,11 @@
     $(info foo)
 endef
 `,
-		expected: `# MK2RBC TRANSLATION ERROR: define is not supported: some-macro
-# define  some-macro
-#     $(info foo)
-# endef
-load("//build/make/core:product_config.rbc", "rblf")
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.warning("product.mk", "partially successful conversion")
+  rblf.mk2rbc_error("product.mk:2", "define is not supported: some-macro")
 `,
 	},
 	{
@@ -220,6 +219,9 @@
   PRODUCT_NAME = gizmo
 else
 endif
+local_var :=
+ifdef local_var
+endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
@@ -229,6 +231,9 @@
     cfg["PRODUCT_NAME"] = "gizmo"
   else:
     pass
+  _local_var = ""
+  if _local_var:
+    pass
 `,
 	},
 	{
@@ -270,9 +275,7 @@
     # Comment
     pass
   else:
-    # MK2RBC TRANSLATION ERROR: cannot set predefined variable TARGET_COPY_OUT_RECOVERY to "foo", its value should be "recovery"
-    pass
-  rblf.warning("product.mk", "partially successful conversion")
+    rblf.mk2rbc_error("product.mk:5", "cannot set predefined variable TARGET_COPY_OUT_RECOVERY to \"foo\", its value should be \"recovery\"")
 `,
 	},
 	{
@@ -341,6 +344,36 @@
 `,
 	},
 	{
+		desc:   "ifeq with soong_config_get",
+		mkname: "product.mk",
+		in: `
+ifeq (true,$(call soong_config_get,art_module,source_build))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "true" == rblf.soong_config_get(g, "art_module", "source_build"):
+    pass
+`,
+	},
+	{
+		desc:   "ifeq with $(NATIVE_COVERAGE)",
+		mkname: "product.mk",
+		in: `
+ifeq ($(NATIVE_COVERAGE),true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if g.get("NATIVE_COVERAGE", False):
+    pass
+`,
+	},
+	{
 		desc:   "Check filter result",
 		mkname: "product.mk",
 		in: `
@@ -354,20 +387,31 @@
 endif
 ifneq (,$(filter true, $(v1)$(v2)))
 endif
+ifeq (,$(filter barbet coral%,$(TARGET_PRODUCT)))
+else ifneq (,$(filter barbet%,$(TARGET_PRODUCT)))
+endif
+ifeq (,$(filter-out sunfish_kasan, $(TARGET_PRODUCT)))
+endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if g["TARGET_BUILD_VARIANT"] not in ["userdebug", "eng"]:
+  if not rblf.filter("userdebug eng", g["TARGET_BUILD_VARIANT"]):
     pass
-  if g["TARGET_BUILD_VARIANT"] == "userdebug":
+  if rblf.filter("userdebug", g["TARGET_BUILD_VARIANT"]):
     pass
   if "plaf" in g.get("PLATFORM_LIST", []):
     pass
   if g["TARGET_BUILD_VARIANT"] in ["userdebug", "eng"]:
     pass
-  if "%s%s" % (_v1, _v2) == "true":
+  if rblf.filter("true", "%s%s" % (_v1, _v2)):
+    pass
+  if not rblf.filter("barbet coral%", g["TARGET_PRODUCT"]):
+    pass
+  elif rblf.filter("barbet%", g["TARGET_PRODUCT"]):
+    pass
+  if not rblf.filter_out("sunfish_kasan", g["TARGET_PRODUCT"]):
     pass
 `,
 	},
@@ -496,6 +540,21 @@
 `,
 	},
 	{
+		desc:   "if with interpolation",
+		mkname: "product.mk",
+		in: `
+ifeq ($(VARIABLE1)text$(VARIABLE2),true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "%stext%s" % (g.get("VARIABLE1", ""), g.get("VARIABLE2", "")) == "true":
+    pass
+`,
+	},
+	{
 		desc:   "ifneq $(X),true",
 		mkname: "product.mk",
 		in: `
@@ -550,18 +609,66 @@
 `,
 	},
 	{
-		desc:   "findstring call",
+		desc:   "new is-board calls",
 		mkname: "product.mk",
 		in: `
-ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+ifneq (,$(call is-board-platform-in-list2,msm8998 $(X))
+else ifeq (,$(call is-board-platform2,copper)
+else ifneq (,$(call is-vendor-board-qcom))
 endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
+  if rblf.board_platform_in(g, "msm8998 %s" % g.get("X", "")):
+    pass
+  elif not rblf.board_platform_is(g, "copper"):
+    pass
+  elif g.get("TARGET_BOARD_PLATFORM", "") in g["QCOM_BOARD_PLATFORMS"]:
+    pass
+`,
+	},
+	{
+		desc:   "findstring call",
+		mkname: "product.mk",
+		in: `
+result := $(findstring a,a b c)
+result := $(findstring b,x y z)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _result = rblf.findstring("a", "a b c")
+  _result = rblf.findstring("b", "x y z")
+`,
+	},
+	{
+		desc:   "findstring in if statement",
+		mkname: "product.mk",
+		in: `
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+endif
+ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+endif
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
+ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
   if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
     pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
+    pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
 `,
 	},
 	{
@@ -619,6 +726,7 @@
 $(call enforce-product-packages-exist, foo)
 $(call require-artifacts-in-path, foo, bar)
 $(call require-artifacts-in-path-relaxed, foo, bar)
+$(call dist-for-goals, goal, from:to)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
@@ -628,6 +736,7 @@
   rblf.enforce_product_packages_exist("foo")
   rblf.require_artifacts_in_path("foo", "bar")
   rblf.require_artifacts_in_path_relaxed("foo", "bar")
+  rblf.mkdist_for_goals(g, "goal", "from:to")
 `,
 	},
 	{
@@ -657,7 +766,7 @@
 PRODUCT_COPY_FILES := $(addprefix pfx-,a b c)
 PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
 PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
-$(info $(patsubst %.pub,%,$(PRODUCT_ADB_KEYS)))
+$(info $(patsubst %.pub,$(PRODUCT_NAME)%,$(PRODUCT_ADB_KEYS)))
 $(info $(dir foo/bar))
 $(info $(firstword $(PRODUCT_COPY_FILES)))
 $(info $(lastword $(PRODUCT_COPY_FILES)))
@@ -668,6 +777,8 @@
 $(info $(notdir foo/bar))
 $(call add_soong_config_namespace,snsconfig)
 $(call add_soong_config_var_value,snsconfig,imagetype,odm_image)
+$(call soong_config_set, snsconfig, foo, foo_value)
+$(call soong_config_append, snsconfig, bar, bar_value)
 PRODUCT_COPY_FILES := $(call copy-files,$(wildcard foo*.mk),etc)
 PRODUCT_COPY_FILES := $(call product-copy-files-by-pattern,from/%,to/%,a b c)
 `,
@@ -678,7 +789,7 @@
   cfg["PRODUCT_COPY_FILES"] = rblf.addprefix("pfx-", "a b c")
   cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
   cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
-  rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%", g.get("PRODUCT_ADB_KEYS", "")))
+  rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%s%%" % cfg["PRODUCT_NAME"], g.get("PRODUCT_ADB_KEYS", "")))
   rblf.mkinfo("product.mk", rblf.dir("foo/bar"))
   rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][0])
   rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][-1])
@@ -687,8 +798,10 @@
   rblf.mkinfo("product.mk", rblf.dir((_foobar).split()[-1]))
   rblf.mkinfo("product.mk", rblf.abspath("foo/bar"))
   rblf.mkinfo("product.mk", rblf.notdir("foo/bar"))
-  rblf.add_soong_config_namespace(g, "snsconfig")
-  rblf.add_soong_config_var_value(g, "snsconfig", "imagetype", "odm_image")
+  rblf.soong_config_namespace(g, "snsconfig")
+  rblf.soong_config_set(g, "snsconfig", "imagetype", "odm_image")
+  rblf.soong_config_set(g, "snsconfig", "foo", "foo_value")
+  rblf.soong_config_append(g, "snsconfig", "bar", "bar_value")
   cfg["PRODUCT_COPY_FILES"] = rblf.copy_files(rblf.expand_wildcard("foo*.mk"), "etc")
   cfg["PRODUCT_COPY_FILES"] = rblf.product_copy_files_by_pattern("from/%", "to/%", "a b c")
 `,
@@ -771,17 +884,39 @@
 		in: `
 SOONG_CONFIG_NAMESPACES += cvd
 SOONG_CONFIG_cvd += launch_configs
-SOONG_CONFIG_cvd_launch_configs += cvd_config_auto.json
+SOONG_CONFIG_cvd_launch_configs = cvd_config_auto.json
 SOONG_CONFIG_cvd += grub_config
 SOONG_CONFIG_cvd_grub_config += grub.cfg
+x := $(SOONG_CONFIG_cvd_grub_config)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.add_soong_config_namespace(g, "cvd")
-  rblf.add_soong_config_var_value(g, "cvd", "launch_configs", "cvd_config_auto.json")
-  rblf.add_soong_config_var_value(g, "cvd", "grub_config", "grub.cfg")
+  rblf.soong_config_namespace(g, "cvd")
+  rblf.soong_config_set(g, "cvd", "launch_configs", "cvd_config_auto.json")
+  rblf.soong_config_append(g, "cvd", "grub_config", "grub.cfg")
+  rblf.mk2rbc_error("product.mk:7", "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config")
+`,
+	}, {
+		desc:   "soong namespace accesses",
+		mkname: "product.mk",
+		in: `
+SOONG_CONFIG_NAMESPACES += cvd
+SOONG_CONFIG_cvd += launch_configs
+SOONG_CONFIG_cvd_launch_configs = cvd_config_auto.json
+SOONG_CONFIG_cvd += grub_config
+SOONG_CONFIG_cvd_grub_config += grub.cfg
+x := $(call soong_config_get,cvd,grub_config)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.soong_config_namespace(g, "cvd")
+  rblf.soong_config_set(g, "cvd", "launch_configs", "cvd_config_auto.json")
+  rblf.soong_config_append(g, "cvd", "grub_config", "grub.cfg")
+  _x = rblf.soong_config_get(g, "cvd", "grub_config")
 `,
 	},
 	{
@@ -898,7 +1033,7 @@
 		desc:   "Dynamic inherit path",
 		mkname: "product.mk",
 		in: `
-MY_PATH=foo
+MY_PATH:=foo
 $(call inherit-product,vendor/$(MY_PATH)/cfg.mk)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -918,6 +1053,133 @@
   rblf.inherit(handle, _varmod, _varmod_init)
 `,
 	},
+	{
+		desc:   "Dynamic inherit with hint",
+		mkname: "product.mk",
+		in: `
+MY_PATH:=foo
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_PATH)/cfg.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_PATH"] = "foo"
+  #RBC# include_top vendor/foo1
+  _entry = {
+    "vendor/foo1/cfg.mk": ("_cfg", _cfg_init),
+  }.get("%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("cannot")
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
+	{
+		desc:   "Ignore make rules",
+		mkname: "product.mk",
+		in: `
+foo: foo.c
+	gcc -o $@ $*`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "unsupported line rule:       foo: foo.c\n#gcc -o $@ $*")
+`,
+	},
+	{
+		desc:   "Flag override",
+		mkname: "product.mk",
+		in: `
+override FOO:=`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "cannot handle override directive")
+  g["override FOO"] = ""
+`,
+	},
+	{
+		desc:   "Bad expression",
+		mkname: "build/product.mk",
+		in: `
+ifeq (,$(call foobar))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.mk2rbc_error("build/product.mk:2", "cannot handle invoking foobar"):
+    pass
+`,
+	},
+	{
+		desc:   "if expression",
+		mkname: "product.mk",
+		in: `
+TEST_VAR := foo
+TEST_VAR_LIST := foo
+TEST_VAR_LIST += bar
+TEST_VAR_2 := $(if $(TEST_VAR),bar)
+TEST_VAR_3 := $(if $(TEST_VAR),bar,baz)
+TEST_VAR_3 := $(if $(TEST_VAR),$(TEST_VAR_LIST))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["TEST_VAR"] = "foo"
+  g["TEST_VAR_LIST"] = ["foo"]
+  g["TEST_VAR_LIST"] += ["bar"]
+  g["TEST_VAR_2"] = ("bar" if g["TEST_VAR"] else "")
+  g["TEST_VAR_3"] = ("bar" if g["TEST_VAR"] else "baz")
+  g["TEST_VAR_3"] = (g["TEST_VAR_LIST"] if g["TEST_VAR"] else [])
+`,
+	},
+	{
+		desc:   "substitution references",
+		mkname: "product.mk",
+		in: `
+SOURCES := foo.c bar.c
+OBJECTS := $(SOURCES:.c=.o)
+OBJECTS2 := $(SOURCES:%.c=%.o)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["SOURCES"] = "foo.c bar.c"
+  g["OBJECTS"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"])
+  g["OBJECTS2"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"])
+`,
+	},
+	{
+		desc:   "foreach expressions",
+		mkname: "product.mk",
+		in: `
+BOOT_KERNEL_MODULES := foo.ko bar.ko
+BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))
+BOOT_KERNEL_MODULES_LIST := foo.ko
+BOOT_KERNEL_MODULES_LIST += bar.ko
+BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["BOOT_KERNEL_MODULES"] = "foo.ko bar.ko"
+  g["BOOT_KERNEL_MODULES_FILTER"] = ["%%/%s" % m for m in rblf.words(g["BOOT_KERNEL_MODULES"])]
+  g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
+  g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
+  g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+`,
+	},
 }
 
 var known_variables = []struct {
@@ -925,6 +1187,7 @@
 	class varClass
 	starlarkType
 }{
+	{"NATIVE_COVERAGE", VarClassSoong, starlarkTypeBool},
 	{"PRODUCT_NAME", VarClassConfig, starlarkTypeString},
 	{"PRODUCT_MODEL", VarClassConfig, starlarkTypeString},
 	{"PRODUCT_PACKAGES", VarClassConfig, starlarkTypeList},
@@ -986,13 +1249,12 @@
 		t.Run(test.desc,
 			func(t *testing.T) {
 				ss, err := Convert(Request{
-					MkFile:             test.mkname,
-					Reader:             bytes.NewBufferString(test.in),
-					RootDir:            ".",
-					OutputSuffix:       ".star",
-					WarnPartialSuccess: true,
-					SourceFS:           fs,
-					MakefileFinder:     &testMakefileFinder{fs: fs},
+					MkFile:         test.mkname,
+					Reader:         bytes.NewBufferString(test.in),
+					RootDir:        ".",
+					OutputSuffix:   ".star",
+					SourceFS:       fs,
+					MakefileFinder: &testMakefileFinder{fs: fs},
 				})
 				if err != nil {
 					t.Error(err)
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index 8efe207..d38299d 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -57,7 +57,7 @@
 	name() string
 	entryName() string
 	emitSelect(gctx *generationContext)
-	isLoadAlways() bool
+	shouldExist() bool
 }
 
 type inheritedStaticModule struct {
@@ -72,7 +72,7 @@
 func (im inheritedStaticModule) emitSelect(_ *generationContext) {
 }
 
-func (im inheritedStaticModule) isLoadAlways() bool {
+func (im inheritedStaticModule) shouldExist() bool {
 	return im.loadAlways
 }
 
@@ -115,12 +115,13 @@
 	}
 }
 
-func (i inheritedDynamicModule) isLoadAlways() bool {
+func (i inheritedDynamicModule) shouldExist() bool {
 	return i.loadAlways
 }
 
 type inheritNode struct {
-	module inheritedModule
+	module     inheritedModule
+	loadAlways bool
 }
 
 func (inn *inheritNode) emit(gctx *generationContext) {
@@ -134,7 +135,7 @@
 	name := inn.module.name()
 	entry := inn.module.entryName()
 	gctx.newLine()
-	if inn.module.isLoadAlways() {
+	if inn.loadAlways {
 		gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
 		return
 	}
@@ -147,14 +148,15 @@
 }
 
 type includeNode struct {
-	module inheritedModule
+	module     inheritedModule
+	loadAlways bool
 }
 
 func (inn *includeNode) emit(gctx *generationContext) {
 	inn.module.emitSelect(gctx)
 	entry := inn.module.entryName()
 	gctx.newLine()
-	if inn.module.isLoadAlways() {
+	if inn.loadAlways {
 		gctx.writef("%s(g, handle)", entry)
 		return
 	}
@@ -181,6 +183,7 @@
 	value    starlarkExpr
 	mkValue  *mkparser.MakeString
 	flavor   assignmentFlavor
+	location ErrorLocation
 	isTraced bool
 	previous *assignmentNode
 }
@@ -221,18 +224,6 @@
 	}
 
 	gctx.newLine()
-	if bad, ok := in.expr.(*badExpr); ok {
-		gctx.write("# MK2STAR ERROR converting:")
-		gctx.newLine()
-		gctx.writef("#   %s", bad.node.Dump())
-		gctx.newLine()
-		gctx.writef("# %s", bad.message)
-		gctx.newLine()
-		// The init function emits a warning if the conversion was not
-		// fullly successful, so here we (arbitrarily) take the false path.
-		gctx.writef("%sFalse:", ifElif)
-		return
-	}
 	gctx.write(ifElif)
 	in.expr.emit(gctx)
 	gctx.write(":")
diff --git a/mk2rbc/test/version_defaults.mk.test b/mk2rbc/test/version_defaults.mk.test
new file mode 100644
index 0000000..1666392
--- /dev/null
+++ b/mk2rbc/test/version_defaults.mk.test
@@ -0,0 +1,22 @@
+INTERNAL_BUILD_ID_MAKEFILE := $(wildcard $(BUILD_SYSTEM)/build_id.mk)
+ifdef INTERNAL_BUILD_ID_MAKEFILE
+  include $(INTERNAL_BUILD_ID_MAKEFILE)
+endif
+
+DEFAULT_PLATFORM_VERSION := TP1A
+.KATI_READONLY := DEFAULT_PLATFORM_VERSION
+MIN_PLATFORM_VERSION := TP1A
+MAX_PLATFORM_VERSION := TP1A
+PLATFORM_VERSION_LAST_STABLE := 12
+PLATFORM_VERSION_CODENAME.SP2A := Sv2
+PLATFORM_VERSION_CODENAME.TP1A := Tiramisu
+ifndef PLATFORM_SDK_VERSION
+  PLATFORM_SDK_VERSION := 31
+endif
+.KATI_READONLY := PLATFORM_SDK_VERSION
+PLATFORM_SDK_EXTENSION_VERSION := 1
+PLATFORM_BASE_SDK_EXTENSION_VERSION := 0
+ifndef PLATFORM_SECURITY_PATCH
+    PLATFORM_SECURITY_PATCH := 2021-10-05
+endif
+include $(BUILD_SYSTEM)/version_util.mk
diff --git a/mk2rbc/types.go b/mk2rbc/types.go
index ebd52d8..46c6aa9 100644
--- a/mk2rbc/types.go
+++ b/mk2rbc/types.go
@@ -53,7 +53,7 @@
 	NewVariable(name string, varClass varClass, valueType starlarkType)
 }
 
-// ScopeBase is a dummy implementation of the mkparser.Scope.
+// ScopeBase is a placeholder implementation of the mkparser.Scope.
 // All our scopes are read-only and resolve only simple variables.
 type ScopeBase struct{}
 
diff --git a/mk2rbc/variable.go b/mk2rbc/variable.go
index 4bb9ed5..6b67a7c 100644
--- a/mk2rbc/variable.go
+++ b/mk2rbc/variable.go
@@ -177,8 +177,8 @@
 	baseVariable
 }
 
-func (lv localVariable) emitDefined(_ *generationContext) {
-	panic("implement me")
+func (lv localVariable) emitDefined(gctx *generationContext) {
+	gctx.writef(lv.String())
 }
 
 func (lv localVariable) String() string {
@@ -228,10 +228,9 @@
 			if actualValue == expectedValue {
 				return
 			}
-			gctx.writef("# MK2RBC TRANSLATION ERROR: cannot set predefined variable %s to %q, its value should be %q",
-				pv.name(), actualValue, expectedValue)
-			gctx.newLine()
-			gctx.write("pass")
+			gctx.emitConversionError(asgn.location,
+				fmt.Sprintf("cannot set predefined variable %s to %q, its value should be %q",
+					pv.name(), actualValue, expectedValue))
 			gctx.starScript.hasErrors = true
 			return
 		}
diff --git a/python/androidmk.go b/python/androidmk.go
index 13b4172..233d867 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -75,16 +75,10 @@
 }
 
 func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if base.Target().Os.Class == android.Host {
-		entries.OutputFile = android.OptionalPathForPath(installer.path)
-	}
-
 	entries.Required = append(entries.Required, "libc++")
 	entries.ExtraEntries = append(entries.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			path, file := filepath.Split(installer.path.ToMakePath().String())
+			path, file := filepath.Split(installer.path.String())
 			stem := strings.TrimSuffix(file, filepath.Ext(file))
 
 			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
diff --git a/python/binary.go b/python/binary.go
index bc2768c..99c6259 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -27,7 +27,6 @@
 
 func init() {
 	registerPythonBinaryComponents(android.InitRegistrationContext)
-	android.RegisterBp2BuildMutator("python_binary_host", PythonBinaryBp2Build)
 }
 
 func registerPythonBinaryComponents(ctx android.RegistrationContext) {
@@ -35,60 +34,46 @@
 }
 
 type bazelPythonBinaryAttributes struct {
-	Main           string
+	Main           *string
 	Srcs           bazel.LabelListAttribute
-	Data           bazel.LabelListAttribute
 	Deps           bazel.LabelListAttribute
-	Python_version string
+	Python_version *string
 }
 
-func PythonBinaryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	// a Module can be something other than a python_binary_host
-	if ctx.ModuleType() != "python_binary_host" {
-		return
-	}
-
-	var main string
+func pythonBinaryBp2Build(ctx android.TopDownMutatorContext, m *Module) {
+	var main *string
 	for _, propIntf := range m.GetProperties() {
 		if props, ok := propIntf.(*BinaryProperties); ok {
 			// main is optional.
 			if props.Main != nil {
-				main = *props.Main
+				main = props.Main
 				break
 			}
 		}
 	}
+
 	// 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
+	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 = "PY2"
+		python_version = &pyVersion2
 	} else {
 		// do nothing, since python_version defaults to PY3.
 	}
 
-	srcs := android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
-	data := android.BazelLabelForModuleSrc(ctx, m.properties.Data)
-	deps := android.BazelLabelForModuleDeps(ctx, m.properties.Libs)
-
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
 	attrs := &bazelPythonBinaryAttributes{
 		Main:           main,
-		Srcs:           bazel.MakeLabelListAttribute(srcs),
-		Data:           bazel.MakeLabelListAttribute(data),
-		Deps:           bazel.MakeLabelListAttribute(deps),
+		Srcs:           baseAttrs.Srcs,
+		Deps:           baseAttrs.Deps,
 		Python_version: python_version,
 	}
 
@@ -97,7 +82,10 @@
 		Rule_class: "py_binary",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
 }
 
 type BinaryProperties struct {
diff --git a/python/library.go b/python/library.go
index a132216..d026c13 100644
--- a/python/library.go
+++ b/python/library.go
@@ -17,17 +17,14 @@
 // This file contains the module types for building Python library.
 
 import (
-	"fmt"
-
 	"android/soong/android"
 	"android/soong/bazel"
+
 	"github.com/google/blueprint/proptools"
 )
 
 func init() {
 	registerPythonLibraryComponents(android.InitRegistrationContext)
-	android.RegisterBp2BuildMutator("python_library_host", PythonLibraryHostBp2Build)
-	android.RegisterBp2BuildMutator("python_library", PythonLibraryBp2Build)
 }
 
 func registerPythonLibraryComponents(ctx android.RegistrationContext) {
@@ -45,66 +42,44 @@
 
 type bazelPythonLibraryAttributes struct {
 	Srcs         bazel.LabelListAttribute
-	Data         bazel.LabelListAttribute
 	Deps         bazel.LabelListAttribute
-	Srcs_version string
+	Srcs_version *string
 }
 
-func PythonLibraryHostBp2Build(ctx android.TopDownMutatorContext) {
-	pythonLibBp2Build(ctx, "python_library_host")
-}
-
-func PythonLibraryBp2Build(ctx android.TopDownMutatorContext) {
-	pythonLibBp2Build(ctx, "python_library")
-}
-
-func pythonLibBp2Build(ctx android.TopDownMutatorContext, modType string) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	// a Module can be something other than a `modType`
-	if ctx.ModuleType() != modType {
-		return
-	}
-
+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
+	var python_version *string
 	if py2Enabled && !py3Enabled {
-		python_version = "PY2"
+		python_version = &pyVersion2
 	} else if !py2Enabled && py3Enabled {
-		python_version = "PY3"
+		python_version = &pyVersion3
 	} else if !py2Enabled && !py3Enabled {
-		panic(fmt.Errorf(
-			"error for '%s' module: bp2build's %s converter doesn't understand having "+
-				"neither py2 nor py3 enabled", m.Name(), modType))
+		ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
 	} else {
 		// do nothing, since python_version defaults to PY2ANDPY3
 	}
 
-	srcs := android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
-	data := android.BazelLabelForModuleSrc(ctx, m.properties.Data)
-	deps := android.BazelLabelForModuleDeps(ctx, m.properties.Libs)
-
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
 	attrs := &bazelPythonLibraryAttributes{
-		Srcs:         bazel.MakeLabelListAttribute(srcs),
-		Data:         bazel.MakeLabelListAttribute(data),
-		Deps:         bazel.MakeLabelListAttribute(deps),
+		Srcs:         baseAttrs.Srcs,
+		Deps:         baseAttrs.Deps,
 		Srcs_version: python_version,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
-		// Use the native py_library rule.
-		Rule_class: "py_library",
+		Rule_class:        "py_library",
+		Bzl_load_location: "//build/bazel/rules/python:library.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
 }
 
 func PythonLibraryFactory() android.Module {
diff --git a/python/python.go b/python/python.go
index 83844e6..734ac57 100644
--- a/python/python.go
+++ b/python/python.go
@@ -22,6 +22,8 @@
 	"regexp"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -45,7 +47,7 @@
 type VersionProperties struct {
 	// whether the module is required to be built with this version.
 	// Defaults to true for Python 3, and false otherwise.
-	Enabled *bool `android:"arch_variant"`
+	Enabled *bool
 
 	// list of source files specific to this Python version.
 	// Using the syntax ":module", srcs may reference the outputs of other modules that produce source files,
@@ -60,7 +62,7 @@
 	Libs []string `android:"arch_variant"`
 
 	// whether the binary is required to be built with embedded launcher for this version, defaults to false.
-	Embedded_launcher *bool `android:"arch_variant"` // TODO(b/174041232): Remove this property
+	Embedded_launcher *bool // TODO(b/174041232): Remove this property
 }
 
 // properties that apply to all python modules
@@ -70,10 +72,10 @@
 	// eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
 	// (from a.b.c import ...) statement.
 	// if left unspecified, all the source/data files path is unchanged within zip file.
-	Pkg_path *string `android:"arch_variant"`
+	Pkg_path *string
 
 	// true, if the Python module is used internally, eg, Python std libs.
-	Is_internal *bool `android:"arch_variant"`
+	Is_internal *bool
 
 	// list of source (.py) files compatible both with Python2 and Python3 used to compile the
 	// Python module.
@@ -120,6 +122,18 @@
 	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
+}
+
 // Used to store files of current module after expanding dependencies
 type pathMapping struct {
 	dest string
@@ -177,6 +191,25 @@
 	}
 }
 
+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)
+			}
+		}
+	}
+	return attrs
+}
+
 // bootstrapper interface should be implemented for runnable modules, e.g. binary and test
 type bootstrapper interface {
 	bootstrapperProps() []interface{}
@@ -310,13 +343,16 @@
 // 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 {
-		// python_library is just meta module, and doesn't have any installer.
-		return 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)
+		}
 	}
-	// TODO: This should only be set when building host binaries -- tests built for device would be
-	// setting this incorrectly.
-	return android.OptionalPathForPath(p.installer.(*binaryDecorator).path)
+
+	return android.OptionalPath{}
+
 }
 
 // OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
@@ -632,18 +668,25 @@
 }
 
 // isPythonLibModule returns whether the given module is a Python library Module or not
-// This is distinguished by the fact that Python libraries are not installable, while other Python
-// modules are.
 func isPythonLibModule(module blueprint.Module) bool {
 	if m, ok := module.(*Module); ok {
-		// Python library has no bootstrapper or installer
-		if m.bootstrapper == nil && m.installer == nil {
-			return true
-		}
+		return m.isLibrary()
 	}
 	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.
@@ -717,6 +760,14 @@
 	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/rust/Android.bp b/rust/Android.bp
index 221014e..5e14da8 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -38,6 +38,7 @@
         "strip.go",
         "test.go",
         "testing.go",
+        "toolchain_library.go",
     ],
     testSrcs: [
         "benchmark_test.go",
@@ -50,9 +51,11 @@
         "fuzz_test.go",
         "image_test.go",
         "library_test.go",
+        "proc_macro_test.go",
         "project_json_test.go",
         "protobuf_test.go",
         "rust_test.go",
+        "sanitize_test.go",
         "source_provider_test.go",
         "test_test.go",
         "vendor_snapshot_test.go",
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 630805a..4e58632 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -50,8 +50,8 @@
 	}
 
 	ret := android.AndroidMkEntries{
-		OutputFile: mod.unstrippedOutputFile,
-		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
+		OutputFile: android.OptionalPathForPath(mod.UnstrippedOutputFile()),
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
@@ -137,10 +137,15 @@
 	} else if library.shared() {
 		ret.Class = "SHARED_LIBRARIES"
 	}
-
 	if library.distFile.Valid() {
 		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
 	}
+	ret.ExtraEntries = append(ret.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			if library.tocFile.Valid() {
+				entries.SetString("LOCAL_SOONG_TOC", library.tocFile.String())
+			}
+		})
 }
 
 func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
@@ -181,19 +186,14 @@
 		return
 	}
 
-	var unstrippedOutputFile android.OptionalPath
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if ctx.Target().Os.Class == android.Host {
-		ret.OutputFile = android.OptionalPathForPath(compiler.path)
-	} else if compiler.strippedOutputFile.Valid() {
-		unstrippedOutputFile = ret.OutputFile
+	if compiler.strippedOutputFile.Valid() {
 		ret.OutputFile = compiler.strippedOutputFile
 	}
+
 	ret.ExtraEntries = append(ret.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
-			path, file := filepath.Split(compiler.path.ToMakePath().String())
+			entries.SetPath("LOCAL_SOONG_UNSTRIPPED_BINARY", compiler.unstrippedOutputFile)
+			path, file := filepath.Split(compiler.path.String())
 			stem, suffix, _ := android.SplitFileExt(file)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 			entries.SetString("LOCAL_MODULE_PATH", path)
diff --git a/rust/binary.go b/rust/binary.go
index 2c3f548..db91ccb 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -31,6 +31,12 @@
 	Static_executable *bool `android:"arch_variant"`
 }
 
+type binaryInterface interface {
+	binary() bool
+	staticallyLinked() bool
+	testBinary() bool
+}
+
 type binaryDecorator struct {
 	*baseCompiler
 	stripper Stripper
@@ -117,20 +123,24 @@
 	fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix()
 	srcPath, _ := srcPathFromModuleSrcs(ctx, binary.baseCompiler.Properties.Srcs)
 	outputFile := android.PathForModuleOut(ctx, fileName)
+	ret := outputFile
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
 
+	if binary.stripper.NeedsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
+		binary.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
+
+		binary.baseCompiler.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
+	}
+	binary.baseCompiler.unstrippedOutputFile = outputFile
+
 	TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile)
 
-	if binary.stripper.NeedsStrip(ctx) {
-		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
-		binary.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
-		binary.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
-	}
-
-	return outputFile
+	return ret
 }
 
 func (binary *binaryDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
@@ -155,3 +165,15 @@
 	}
 	return binary.baseCompiler.stdLinkage(ctx)
 }
+
+func (binary *binaryDecorator) binary() bool {
+	return true
+}
+
+func (binary *binaryDecorator) staticallyLinked() bool {
+	return Bool(binary.Properties.Static_executable)
+}
+
+func (binary *binaryDecorator) testBinary() bool {
+	return false
+}
diff --git a/rust/binary_test.go b/rust/binary_test.go
index 968c0c1..7dac249 100644
--- a/rust/binary_test.go
+++ b/rust/binary_test.go
@@ -106,7 +106,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Output("fizz-buzz")
+	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Rule("rustc")
 
 	flags := fizzBuzz.Args["rustcFlags"]
 	if strings.Contains(flags, "--test") {
@@ -139,7 +139,7 @@
 			static_executable: true,
 		}`)
 
-	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Output("fizz")
+	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
 	fizzMod := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Module().(*Module)
 
 	flags := fizzOut.Args["rustcFlags"]
@@ -173,7 +173,7 @@
 			name: "libfoo",
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Output("fizz-buzz")
+	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Rule("rustc")
 	linkFlags := fizzBuzz.Args["linkFlags"]
 	if !strings.Contains(linkFlags, "/libfoo.so") {
 		t.Errorf("missing shared dependency 'libfoo.so' in linkFlags: %#v", linkFlags)
@@ -197,15 +197,17 @@
 	`)
 
 	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a")
-	foo.Output("stripped/foo")
+	foo.Output("unstripped/foo")
+	foo.Output("foo")
+
 	// Check that the `cp` rules is using the stripped version as input.
 	cp := foo.Rule("android.Cp")
-	if !strings.HasSuffix(cp.Input.String(), "stripped/foo") {
+	if strings.HasSuffix(cp.Input.String(), "unstripped/foo") {
 		t.Errorf("installed binary not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("bar", "android_arm64_armv8-a").MaybeOutput("stripped/bar")
+	fizzBar := ctx.ModuleForTests("bar", "android_arm64_armv8-a").MaybeOutput("unstripped/bar")
 	if fizzBar.Rule != nil {
-		t.Errorf("stripped version of bar has been generated")
+		t.Errorf("unstripped binary exists, so stripped binary has incorrectly been generated")
 	}
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index be9e71e..5e1b4b7 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -15,6 +15,7 @@
 package rust
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -29,7 +30,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r428724"
+	bindgenClangVersion = "clang-r437112"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -147,6 +148,31 @@
 	cflags = append(cflags, strings.ReplaceAll(ccToolchain.Cflags(), "${config.", "${cc_config."))
 	cflags = append(cflags, strings.ReplaceAll(ccToolchain.ToolchainCflags(), "${config.", "${cc_config."))
 
+	if ctx.RustModule().UseVndk() {
+		cflags = append(cflags, "-D__ANDROID_VNDK__")
+		if ctx.RustModule().InVendor() {
+			cflags = append(cflags, "-D__ANDROID_VENDOR__")
+		} else if ctx.RustModule().InProduct() {
+			cflags = append(cflags, "-D__ANDROID_PRODUCT__")
+		}
+	}
+
+	if ctx.RustModule().InRecovery() {
+		cflags = append(cflags, "-D__ANDROID_RECOVERY__")
+	}
+
+	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 {
+		cflags = append(cflags, "-D__ANDROID_NATIVE_BRIDGE__")
+	}
+
 	// Dependency clang flags and include paths
 	cflags = append(cflags, deps.depClangFlags...)
 	for _, include := range deps.depIncludePaths {
diff --git a/rust/builder.go b/rust/builder.go
index 426a569..a7efc28 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -95,7 +95,7 @@
 
 func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin")
 }
@@ -112,13 +112,13 @@
 
 func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib")
 }
 
 func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib")
 }
 
@@ -185,7 +185,7 @@
 }
 
 func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, crate_type string) buildOutput {
+	outputFile android.WritablePath, crateType string) buildOutput {
 
 	var inputs android.Paths
 	var implicits android.Paths
@@ -204,7 +204,7 @@
 	// Collect rustc flags
 	rustcFlags = append(rustcFlags, flags.GlobalRustFlags...)
 	rustcFlags = append(rustcFlags, flags.RustFlags...)
-	rustcFlags = append(rustcFlags, "--crate-type="+crate_type)
+	rustcFlags = append(rustcFlags, "--crate-type="+crateType)
 	if crateName != "" {
 		rustcFlags = append(rustcFlags, "--crate-name="+crateName)
 	}
@@ -216,6 +216,13 @@
 	// Suppress an implicit sysroot
 	rustcFlags = append(rustcFlags, "--sysroot=/dev/null")
 
+	// Enable incremental compilation if requested by user
+	if ctx.Config().IsEnvTrue("SOONG_RUSTC_INCREMENTAL") {
+		incrementalPath := android.PathForOutput(ctx, "rustc").String()
+
+		rustcFlags = append(rustcFlags, "-C incremental="+incrementalPath)
+	}
+
 	// Collect linker flags
 	linkFlags = append(linkFlags, flags.GlobalLinkFlags...)
 	linkFlags = append(linkFlags, flags.LinkFlags...)
@@ -361,7 +368,7 @@
 		Description: "rustdoc " + main.Rel(),
 		Output:      docTimestampFile,
 		Input:       main,
-		Implicit:    ctx.RustModule().unstrippedOutputFile.Path(),
+		Implicit:    ctx.RustModule().UnstrippedOutputFile(),
 		Args: map[string]string{
 			"rustdocFlags": strings.Join(rustdocFlags, " "),
 			"outDir":       docDir.String(),
diff --git a/rust/compiler.go b/rust/compiler.go
index 7bd9af4..3040e5d 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -181,7 +181,11 @@
 	sanitize *sanitize
 
 	distFile android.OptionalPath
-	// Stripped output file. If Valid(), this file will be installed instead of outputFile.
+
+	// unstripped output file.
+	unstrippedOutputFile android.Path
+
+	// stripped output file.
 	strippedOutputFile android.OptionalPath
 
 	// If a crate has a source-generated dependency, a copy of the source file
@@ -231,6 +235,7 @@
 	for _, cfg := range compiler.Properties.Cfgs {
 		flags = append(flags, "--cfg '"+cfg+"'")
 	}
+
 	return flags
 }
 
@@ -239,6 +244,24 @@
 	for _, feature := range compiler.Properties.Features {
 		flags = append(flags, "--cfg 'feature=\""+feature+"\"'")
 	}
+
+	return flags
+}
+
+func (compiler *baseCompiler) featureFlags(ctx ModuleContext, flags Flags) Flags {
+	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
+
+	return flags
+}
+
+func (compiler *baseCompiler) cfgFlags(ctx ModuleContext, flags Flags) Flags {
+	if ctx.RustModule().UseVndk() {
+		compiler.Properties.Cfgs = append(compiler.Properties.Cfgs, "android_vndk")
+	}
+
+	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
 	return flags
 }
 
@@ -269,10 +292,6 @@
 
 	flags.RustFlags = append(flags.RustFlags, lintFlags)
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
-	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
-	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
 	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
 	flags.RustdocFlags = append(flags.RustdocFlags, "--edition="+compiler.edition())
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
@@ -296,10 +315,6 @@
 		flags.LinkFlags = append(flags.LinkFlags, "-Wl,-rpath,"+rpathPrefix+"../"+rpath)
 	}
 
-	if ctx.RustModule().UseVndk() {
-		flags.RustFlags = append(flags.RustFlags, "--cfg 'android_vndk'")
-	}
-
 	return flags
 }
 
@@ -329,6 +344,10 @@
 	return String(compiler.Properties.Cargo_pkg_version)
 }
 
+func (compiler *baseCompiler) unstrippedOutputFilePath() android.Path {
+	return compiler.unstrippedOutputFile
+}
+
 func (compiler *baseCompiler) strippedOutputFilePath() android.OptionalPath {
 	return compiler.strippedOutputFile
 }
@@ -344,9 +363,9 @@
 
 	if !Bool(compiler.Properties.No_stdlibs) {
 		for _, stdlib := range config.Stdlibs {
-			// If we're building for the primary arch of the build host, use the compiler's stdlibs
+			// If we're building for the build host, use the prebuilt stdlibs
 			if ctx.Target().Os == ctx.Config().BuildOS {
-				stdlib = stdlib + "_" + ctx.toolchain().RustTriple()
+				stdlib = "prebuilt_" + stdlib
 			}
 			deps.Stdlibs = append(deps.Stdlibs, stdlib)
 		}
@@ -438,6 +457,10 @@
 
 // Returns the Path for the main source file along with Paths for generated source files from modules listed in srcs.
 func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) (android.Path, android.Paths) {
+	if len(srcs) == 0 {
+		ctx.PropertyErrorf("srcs", "srcs must not be empty")
+	}
+
 	// The srcs can contain strings with prefix ":".
 	// They are dependent modules of this module, with android.SourceDepTag.
 	// They are not the main source file compiled by rustc.
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index f589b69..ec6829a 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -98,6 +98,41 @@
 		}`)
 }
 
+// Test that we reject _no_ source files.
+func TestEnforceMissingSourceFiles(t *testing.T) {
+
+	singleSrcError := "srcs must not be empty"
+
+	// Test libraries
+	testRustError(t, singleSrcError, `
+		rust_library_host {
+			name: "foo-bar-library",
+			crate_name: "foo",
+		}`)
+
+	// Test binaries
+	testRustError(t, singleSrcError, `
+		rust_binary_host {
+			name: "foo-bar-binary",
+			crate_name: "foo",
+		}`)
+
+	// Test proc_macros
+	testRustError(t, singleSrcError, `
+		rust_proc_macro {
+			name: "foo-bar-proc-macro",
+			crate_name: "foo",
+		}`)
+
+	// Test prebuilts
+	testRustError(t, singleSrcError, `
+		rust_prebuilt_dylib {
+			name: "foo-bar-prebuilt",
+			crate_name: "foo",
+		  host_supported: true,
+		}`)
+}
+
 // Test environment vars for Cargo compat are set.
 func TestCargoCompat(t *testing.T) {
 	ctx := testRust(t, `
diff --git a/rust/config/Android.bp b/rust/config/Android.bp
index 5b121c3..7757c79 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -16,7 +16,7 @@
         "lints.go",
         "toolchain.go",
         "allowed_list.go",
-        "x86_darwin_host.go",
+        "darwin_host.go",
         "x86_linux_bionic_host.go",
         "x86_linux_host.go",
         "x86_device.go",
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 63a8f04..0d0b712 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -11,16 +11,19 @@
 		"external/crosvm",
 		"external/libchromeos-rs",
 		"external/minijail",
+		"external/open-dice",
 		"external/rust",
 		"external/selinux/libselinux",
 		"external/uwb",
 		"external/vm_tools/p9",
 		"frameworks/native/libs/binder/rust",
 		"frameworks/proto_logging/stats",
+		"hardware/interfaces/security",
+		"packages/modules/Bluetooth",
 		"packages/modules/DnsResolver",
+		"packages/modules/Uwb",
 		"packages/modules/Virtualization",
 		"prebuilts/rust",
-		"system/bt",
 		"system/core/libstats/pull_rust",
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
@@ -28,9 +31,11 @@
 		"system/librustutils",
 		"system/logging/liblog",
 		"system/logging/rust",
+		"system/nfc",
 		"system/security",
 		"system/tools/aidl",
 		"tools/security/fuzzing/example_rust_fuzzer",
+		"tools/security/fuzzing/orphans",
 		"vendor/",
 	}
 
diff --git a/rust/config/x86_darwin_host.go b/rust/config/darwin_host.go
similarity index 67%
rename from rust/config/x86_darwin_host.go
rename to rust/config/darwin_host.go
index 8ff0dd4..03bea82 100644
--- a/rust/config/x86_darwin_host.go
+++ b/rust/config/darwin_host.go
@@ -25,41 +25,64 @@
 	DarwinRustLinkFlags = []string{
 		"-B${cc_config.MacToolPath}",
 	}
+	darwinArm64Rustflags = []string{}
+	darwinArm64Linkflags = []string{}
 	darwinX8664Rustflags = []string{}
 	darwinX8664Linkflags = []string{}
 )
 
 func init() {
+	registerToolchainFactory(android.Darwin, android.Arm64, darwinArm64ToolchainFactory)
 	registerToolchainFactory(android.Darwin, android.X86_64, darwinX8664ToolchainFactory)
+
 	pctx.StaticVariable("DarwinToolchainRustFlags", strings.Join(DarwinRustFlags, " "))
 	pctx.StaticVariable("DarwinToolchainLinkFlags", strings.Join(DarwinRustLinkFlags, " "))
+
+	pctx.StaticVariable("DarwinToolchainArm64RustFlags", strings.Join(darwinArm64Rustflags, " "))
+	pctx.StaticVariable("DarwinToolchainArm64LinkFlags", strings.Join(darwinArm64Linkflags, " "))
 	pctx.StaticVariable("DarwinToolchainX8664RustFlags", strings.Join(darwinX8664Rustflags, " "))
 	pctx.StaticVariable("DarwinToolchainX8664LinkFlags", strings.Join(darwinX8664Linkflags, " "))
 
 }
 
 type toolchainDarwin struct {
+	toolchain64Bit
 	toolchainRustFlags string
 	toolchainLinkFlags string
 }
 
-type toolchainDarwinX8664 struct {
-	toolchain64Bit
+type toolchainDarwinArm64 struct {
 	toolchainDarwin
 }
 
+type toolchainDarwinX8664 struct {
+	toolchainDarwin
+}
+
+func (toolchainDarwinArm64) Supported() bool {
+	return true
+}
+
 func (toolchainDarwinX8664) Supported() bool {
 	return true
 }
 
-func (toolchainDarwinX8664) Bionic() bool {
+func (toolchainDarwin) Bionic() bool {
 	return false
 }
 
+func (t *toolchainDarwinArm64) Name() string {
+	return "arm64"
+}
+
 func (t *toolchainDarwinX8664) Name() string {
 	return "x86_64"
 }
 
+func (t *toolchainDarwinArm64) RustTriple() string {
+	return "aarch64-apple-darwin"
+}
+
 func (t *toolchainDarwinX8664) RustTriple() string {
 	return "x86_64-apple-darwin"
 }
@@ -76,6 +99,15 @@
 	return ".dylib"
 }
 
+func (t *toolchainDarwinArm64) ToolchainLinkFlags() string {
+	// Prepend the lld flags from cc_config so we stay in sync with cc
+	return "${cc_config.DarwinLldflags} ${config.DarwinToolchainLinkFlags} ${config.DarwinToolchainArm64LinkFlags}"
+}
+
+func (t *toolchainDarwinArm64) ToolchainRustFlags() string {
+	return "${config.DarwinToolchainRustFlags} ${config.DarwinToolchainArm64RustFlags}"
+}
+
 func (t *toolchainDarwinX8664) ToolchainLinkFlags() string {
 	// Prepend the lld flags from cc_config so we stay in sync with cc
 	return "${cc_config.DarwinLldflags} ${config.DarwinToolchainLinkFlags} ${config.DarwinToolchainX8664LinkFlags}"
@@ -85,8 +117,13 @@
 	return "${config.DarwinToolchainRustFlags} ${config.DarwinToolchainX8664RustFlags}"
 }
 
+func darwinArm64ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinArm64Singleton
+}
+
 func darwinX8664ToolchainFactory(arch android.Arch) Toolchain {
 	return toolchainDarwinX8664Singleton
 }
 
+var toolchainDarwinArm64Singleton Toolchain = &toolchainDarwinArm64{}
 var toolchainDarwinX8664Singleton Toolchain = &toolchainDarwinX8664{}
diff --git a/rust/config/global.go b/rust/config/global.go
index e5b334d..bfb2d1f 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,9 +24,9 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.54.0"
+	RustDefaultVersion = "1.57.0"
 	RustDefaultBase    = "prebuilts/rust/"
-	DefaultEdition     = "2018"
+	DefaultEdition     = "2021"
 	Stdlibs            = []string{
 		"libstd",
 	}
@@ -41,12 +41,13 @@
 	}
 
 	GlobalRustFlags = []string{
-		"--remap-path-prefix $$(pwd)=",
+		"-Z remap-cwd-prefix=.",
 		"-C codegen-units=1",
 		"-C debuginfo=2",
 		"-C opt-level=3",
 		"-C relocation-model=pic",
 		"-C overflow-checks=on",
+		"-C force-unwind-tables=yes",
 		// Use v0 mangling to distinguish from C++ symbols
 		"-Z symbol-mangling-version=v0",
 	}
@@ -54,6 +55,8 @@
 	deviceGlobalRustFlags = []string{
 		"-C panic=abort",
 		"-Z link-native-libraries=no",
+		// Generate additional debug info for AutoFDO
+		"-Z debug-info-for-profiling",
 	}
 
 	deviceGlobalLinkFlags = []string{
diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go
index 0aa534f..c10afd8 100644
--- a/rust/config/x86_linux_host.go
+++ b/rust/config/x86_linux_host.go
@@ -26,6 +26,7 @@
 		"-B${cc_config.ClangBin}",
 		"-fuse-ld=lld",
 		"-Wl,--undefined-version",
+		"--sysroot ${cc_config.LinuxGccRoot}/sysroot",
 	}
 	linuxX86Rustflags   = []string{}
 	linuxX86Linkflags   = []string{}
diff --git a/rust/coverage.go b/rust/coverage.go
index 050b811..8fdfa23 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -57,7 +57,18 @@
 		flags.RustFlags = append(flags.RustFlags,
 			"-Z instrument-coverage", "-g")
 		flags.LinkFlags = append(flags.LinkFlags,
-			profileInstrFlag, "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,open")
+			profileInstrFlag, "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,open",
+			// Upstream LLVM change 6d2d3bd0a6 made
+			// -z,start-stop-gc the default.  It drops metadata
+			// sections like __llvm_prf_data unless they are marked
+			// SHF_GNU_RETAIN.  https://reviews.llvm.org/D97448
+			// marks generated sections, including __llvm_prf_data
+			// as SHF_GNU_RETAIN.  However this change is not in
+			// the Rust toolchain.  Since we link Rust libs with
+			// new lld, we should use nostart-stop-gc until the
+			// Rust toolchain updates past D97448.
+			"-Wl,-z,nostart-stop-gc",
+		)
 		deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile().Path())
 	}
 
diff --git a/rust/fuzz.go b/rust/fuzz.go
index 5fb56ff..55921ba 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -36,7 +36,7 @@
 	fuzzPackagedModule fuzz.FuzzPackagedModule
 }
 
-var _ compiler = (*binaryDecorator)(nil)
+var _ compiler = (*fuzzDecorator)(nil)
 
 // rust_binary produces a binary that is runnable on a device.
 func RustFuzzFactory() android.Module {
@@ -111,6 +111,10 @@
 	// 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)
@@ -143,7 +147,13 @@
 		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
 
 		// The executable.
-		files = append(files, fuzz.FileToZip{rustModule.unstrippedOutputFile.Path(), ""})
+		files = append(files, fuzz.FileToZip{rustModule.UnstrippedOutputFile(), ""})
+
+		// Grab the list of required shared libraries.
+		sharedLibraries := fuzz.CollectAllSharedDependencies(ctx, module, cc.UnstrippedOutputFile, cc.IsValidSharedDependency)
+
+		// Package shared libraries
+		files = append(files, cc.GetSharedLibsToZip(sharedLibraries, rustModule, &s.FuzzPackager, archString, &sharedLibraryInstalled)...)
 
 		archDirs[archOs], ok = s.BuildZipFile(ctx, module, fuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
 		if !ok {
diff --git a/rust/fuzz_test.go b/rust/fuzz_test.go
index 2524f91..98be7c2 100644
--- a/rust/fuzz_test.go
+++ b/rust/fuzz_test.go
@@ -45,7 +45,7 @@
 	}
 
 	// Check that compiler flags are set appropriately .
-	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Output("fuzz_libtest")
+	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
 	if !strings.Contains(fuzz_libtest.Args["rustcFlags"], "-Z sanitizer=hwaddress") ||
 		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "-C passes='sancov'") ||
 		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "--cfg fuzzing") {
diff --git a/rust/library.go b/rust/library.go
index 8c10e29..bb2e83f 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -102,6 +102,9 @@
 	sourceProvider    SourceProvider
 
 	collectedSnapshotHeaders android.Paths
+
+	// table-of-contents file for cdylib crates to optimize out relinking when possible
+	tocFile android.OptionalPath
 }
 
 type libraryInterface interface {
@@ -137,12 +140,18 @@
 	BuildOnlyDylib()
 	BuildOnlyStatic()
 	BuildOnlyShared()
+
+	toc() android.OptionalPath
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
 	return true
 }
 
+func (library *libraryDecorator) toc() android.OptionalPath {
+	return library.tocFile
+}
+
 func (library *libraryDecorator) rlib() bool {
 	return library.MutatedProperties.VariantIsRlib
 }
@@ -430,15 +439,25 @@
 	return library.getStem(ctx) + ctx.toolchain().SharedLibSuffix()
 }
 
-func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
+func (library *libraryDecorator) cfgFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = library.baseCompiler.cfgFlags(ctx, flags)
 	if library.dylib() {
 		// We need to add a dependency on std in order to link crates as dylibs.
 		// The hack to add this dependency is guarded by the following cfg so
 		// that we don't force a dependency when it isn't needed.
 		library.baseCompiler.Properties.Cfgs = append(library.baseCompiler.Properties.Cfgs, "android_dylib")
 	}
+
+	flags.RustFlags = append(flags.RustFlags, library.baseCompiler.cfgsToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, library.baseCompiler.cfgsToFlags()...)
+
+	return flags
+}
+
+func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = library.baseCompiler.compilerFlags(ctx, flags)
+
+	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 	if library.shared() || library.static() {
 		library.includeDirs = append(library.includeDirs, android.PathsForModuleSrc(ctx, library.Properties.Include_dirs)...)
 	}
@@ -450,7 +469,7 @@
 }
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
-	var outputFile android.ModuleOutPath
+	var outputFile, ret android.ModuleOutPath
 	var fileName string
 	srcPath := library.srcPath(ctx, deps)
 
@@ -458,6 +477,34 @@
 		deps.srcProviderFiles = append(deps.srcProviderFiles, library.sourceProvider.Srcs()...)
 	}
 
+	// Calculate output filename
+	if library.rlib() {
+		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.dylib() {
+		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.static() {
+		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.shared() {
+		fileName = library.sharedLibFilename(ctx)
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	}
+
+	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
+		library.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
+
+		library.baseCompiler.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
+	}
+	library.baseCompiler.unstrippedOutputFile = outputFile
+
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
@@ -469,34 +516,17 @@
 		flags.RustFlags = append(flags.RustFlags, "-C prefer-dynamic")
 	}
 
+	// Call the appropriate builder for this library type
 	if library.rlib() {
-		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.dylib() {
-		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.static() {
-		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile)
 	} else if library.shared() {
-		fileName = library.sharedLibFilename(ctx)
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile)
 	}
 
-	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
-		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
-		library.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
-		library.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
-	}
-
 	if library.rlib() || library.dylib() {
 		library.flagExporter.exportLinkDirs(deps.linkDirs...)
 		library.flagExporter.exportLinkObjects(deps.linkObjects...)
@@ -509,9 +539,16 @@
 	}
 
 	if library.shared() {
+		// Optimize out relinking against shared libraries whose interface hasn't changed by
+		// depending on a table of contents file instead of the library itself.
+		tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.SharedLibSuffix()[1:]+".toc")
+		library.tocFile = android.OptionalPathForPath(tocFile)
+		cc.TransformSharedObjectToToc(ctx, outputFile, tocFile)
+
 		ctx.SetProvider(cc.SharedLibraryInfoProvider, cc.SharedLibraryInfo{
-			SharedLibrary: outputFile,
-			Target:        ctx.Target(),
+			TableOfContents: android.OptionalPathForPath(tocFile),
+			SharedLibrary:   outputFile,
+			Target:          ctx.Target(),
 		})
 	}
 
@@ -526,7 +563,7 @@
 
 	library.flagExporter.setProvider(ctx)
 
-	return outputFile
+	return ret
 }
 
 func (library *libraryDecorator) srcPath(ctx ModuleContext, deps PathDeps) android.Path {
@@ -644,6 +681,12 @@
 			}
 
 			variation := v.(*Module).ModuleBase.ImageVariation().Variation
+			if strings.HasPrefix(variation, cc.VendorVariationPrefix) {
+				// TODO(b/204303985)
+				// Disable vendor dylibs until they are supported
+				v.(*Module).Disable()
+			}
+
 			if strings.HasPrefix(variation, cc.VendorVariationPrefix) &&
 				m.HasVendorVariant() &&
 				!snapshot.IsVendorProprietaryModule(mctx) &&
diff --git a/rust/library_test.go b/rust/library_test.go
index cb4ef7e..d78dcdd 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -37,10 +37,10 @@
                 }`)
 
 	// Test all variants are being built.
-	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std").Output("libfoo.rlib")
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
-	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").Output("libfoo.ffi.a")
-	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Output("libfoo.ffi.so")
+	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").Rule("rustc")
+	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Rule("rustc")
 
 	rlibCrateType := "rlib"
 	dylibCrateType := "dylib"
@@ -78,7 +78,7 @@
 			crate_name: "foo",
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "prefer-dynamic") {
 		t.Errorf("missing prefer-dynamic flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
@@ -94,7 +94,7 @@
 			crate_name: "foo",
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "--cfg 'android_dylib'") {
 		t.Errorf("missing android_dylib cfg flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
@@ -148,7 +148,7 @@
 
 	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared")
 
-	libfooOutput := libfoo.Output("libfoo.so")
+	libfooOutput := libfoo.Rule("rustc")
 	if !strings.Contains(libfooOutput.Args["linkFlags"], "-Wl,-soname=libfoo.so") {
 		t.Errorf("missing expected -Wl,-soname linker flag for libfoo shared lib, linkFlags: %#v",
 			libfooOutput.Args["linkFlags"])
@@ -160,6 +160,26 @@
 	}
 }
 
+func TestSharedLibraryToc(t *testing.T) {
+	ctx := testRust(t, `
+		rust_ffi_shared {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+		}
+		cc_binary {
+			name: "fizzbuzz",
+			shared_libs: ["libfoo"],
+		}`)
+
+	fizzbuzz := ctx.ModuleForTests("fizzbuzz", "android_arm64_armv8-a").Rule("ld")
+
+	if !android.SuffixInList(fizzbuzz.Implicits.Strings(), "libfoo.so.toc") {
+		t.Errorf("missing expected libfoo.so.toc implicit dependency, instead found: %#v",
+			fizzbuzz.Implicits.Strings())
+	}
+}
+
 func TestStaticLibraryLinkage(t *testing.T) {
 	ctx := testRust(t, `
 		rust_ffi_static {
@@ -242,16 +262,17 @@
 	`)
 
 	foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib")
-	foo.Output("stripped/libfoo.dylib.so")
+	foo.Output("libfoo.dylib.so")
+	foo.Output("unstripped/libfoo.dylib.so")
 	// Check that the `cp` rule is using the stripped version as input.
 	cp := foo.Rule("android.Cp")
-	if !strings.HasSuffix(cp.Input.String(), "stripped/libfoo.dylib.so") {
-		t.Errorf("installed binary not based on stripped version: %v", cp.Input)
+	if strings.HasSuffix(cp.Input.String(), "unstripped/libfoo.dylib.so") {
+		t.Errorf("installed library not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeOutput("stripped/libbar.dylib.so")
+	fizzBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeOutput("unstripped/libbar.dylib.so")
 	if fizzBar.Rule != nil {
-		t.Errorf("stripped version of bar has been generated")
+		t.Errorf("unstripped library exists, so stripped library has incorrectly been generated")
 	}
 }
 
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 49f3c0f..6f17272 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -32,6 +32,8 @@
 }
 
 type prebuiltLibraryDecorator struct {
+	android.Prebuilt
+
 	*libraryDecorator
 	Properties PrebuiltProperties
 }
@@ -54,6 +56,13 @@
 	return module.Init()
 }
 
+func addSrcSupplier(module android.PrebuiltInterface, prebuilt *prebuiltLibraryDecorator) {
+	srcsSupplier := func(_ android.BaseModuleContext, _ android.Module) []string {
+		return prebuilt.prebuiltSrcs()
+	}
+	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "srcs")
+}
+
 func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
 	module, library := NewRustLibrary(hod)
 	library.BuildOnlyRust()
@@ -62,6 +71,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -73,6 +85,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -84,6 +99,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -100,6 +118,7 @@
 	if len(paths) > 0 {
 		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
 	}
+	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
 	return srcPath
 }
 
@@ -129,3 +148,7 @@
 
 	return srcs
 }
+
+func (prebuilt *prebuiltLibraryDecorator) prebuilt() *android.Prebuilt {
+	return &prebuilt.Prebuilt
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index c217959..974c096 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -63,12 +63,19 @@
 		&procMacro.Properties)
 }
 
+func (procMacro *procMacroDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = procMacro.baseCompiler.compilerFlags(ctx, flags)
+	flags.RustFlags = append(flags.RustFlags, "--extern proc_macro")
+	return flags
+}
+
 func (procMacro *procMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
 	srcPath, _ := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
 	TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
+	procMacro.baseCompiler.unstrippedOutputFile = outputFile
 	return outputFile
 }
 
diff --git a/rust/proc_macro_test.go b/rust/proc_macro_test.go
new file mode 100644
index 0000000..cc81938
--- /dev/null
+++ b/rust/proc_macro_test.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Android Open Source Project
+//
+// 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 rust
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestRustProcMacro(t *testing.T) {
+	ctx := testRust(t, `
+          rust_proc_macro {
+	    name: "libprocmacro",
+	    srcs: ["foo.rs"],
+	    crate_name: "procmacro",
+	  }
+	`)
+
+	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
+
+	if !strings.Contains(libprocmacro.Args["rustcFlags"], "--extern proc_macro") {
+		t.Errorf("--extern proc_macro flag not being passed to rustc for proc macro %#v", libprocmacro.Args["rustcFlags"])
+	}
+}
diff --git a/rust/project_json.go b/rust/project_json.go
index faa7db5..fe259d6 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -51,6 +51,7 @@
 	Deps        []rustProjectDep  `json:"deps"`
 	Cfg         []string          `json:"cfg"`
 	Env         map[string]string `json:"env"`
+	ProcMacro   bool              `json:"is_proc_macro"`
 }
 
 type rustProjectJson struct {
@@ -208,6 +209,10 @@
 		comp = c.baseCompiler
 	case *testDecorator:
 		comp = c.binaryDecorator.baseCompiler
+	case *procMacroDecorator:
+		comp = c.baseCompiler
+	case *toolchainLibraryDecorator:
+		comp = c.baseCompiler
 	default:
 		return nil, nil, false
 	}
@@ -224,6 +229,8 @@
 		return 0, false
 	}
 
+	_, procMacro := rModule.compiler.(*procMacroDecorator)
+
 	crate := rustProjectCrate{
 		DisplayName: rModule.Name(),
 		RootModule:  rootModule,
@@ -231,6 +238,7 @@
 		Deps:        make([]rustProjectDep, 0),
 		Cfg:         make([]string, 0),
 		Env:         make(map[string]string),
+		ProcMacro:   procMacro,
 	}
 
 	if comp.CargoOutDir().Valid() {
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index f7b6681..255b2e5 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -117,6 +117,58 @@
 	validateJsonCrates(t, jsonContent)
 }
 
+func TestProjectJsonProcMacroDep(t *testing.T) {
+	bp := `
+	rust_proc_macro {
+		name: "libproc_macro",
+		srcs: ["a/src/lib.rs"],
+		crate_name: "proc_macro"
+	}
+	rust_library {
+		name: "librust",
+		srcs: ["b/src/lib.rs"],
+		crate_name: "rust",
+		proc_macros: ["libproc_macro"],
+	}
+	`
+	jsonContent := testProjectJson(t, bp)
+	crates := validateJsonCrates(t, jsonContent)
+	libproc_macro_count := 0
+	librust_count := 0
+	for _, c := range crates {
+		crate := validateCrate(t, c)
+		procMacro, ok := crate["is_proc_macro"].(bool)
+		if !ok {
+			t.Fatalf("Unexpected type for is_proc_macro: %v", crate["is_proc_macro"])
+		}
+
+		name, ok := crate["display_name"].(string)
+		if !ok {
+			t.Fatalf("Unexpected type for display_name: %v", crate["display_name"])
+		}
+
+		switch name {
+		case "libproc_macro":
+			libproc_macro_count += 1
+			if !procMacro {
+				t.Fatalf("'libproc_macro' is marked with is_proc_macro=false")
+			}
+		case "librust":
+			librust_count += 1
+			if procMacro {
+				t.Fatalf("'librust' is not a proc macro crate, but is marked with is_proc_macro=true")
+			}
+		default:
+			break
+		}
+	}
+
+	if libproc_macro_count != 1 || librust_count != 1 {
+		t.Fatalf("Unexpected crate counts: libproc_macro_count: %v, librust_count: %v",
+			libproc_macro_count, librust_count)
+	}
+}
+
 func TestProjectJsonFeature(t *testing.T) {
 	bp := `
 	rust_library {
diff --git a/rust/rust.go b/rust/rust.go
index 0cd299d..c2585f2 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -130,9 +130,10 @@
 	// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
 	Min_sdk_version *string
 
-	PreventInstall bool
-	HideFromMake   bool
-	Installable    *bool
+	HideFromMake   bool `blueprint:"mutated"`
+	PreventInstall bool `blueprint:"mutated"`
+
+	Installable *bool
 }
 
 type Module struct {
@@ -155,13 +156,15 @@
 	sourceProvider   SourceProvider
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
-	// Unstripped output. This is usually used when this module is linked to another module
-	// as a library. The stripped output which is used for installation can be found via
-	// compiler.strippedOutputFile if it exists.
-	unstrippedOutputFile android.OptionalPath
-	docTimestampFile     android.OptionalPath
+	// Output file to be installed, may be stripped or unstripped.
+	outputFile android.OptionalPath
+
+	docTimestampFile android.OptionalPath
 
 	hideApexVariantFromMake bool
+
+	// For apex variants, this is set as apex.min_sdk_version
+	apexSdkVersion android.ApiLevel
 }
 
 func (mod *Module) Header() bool {
@@ -177,8 +180,8 @@
 	mod.Properties.HideFromMake = true
 }
 
-func (c *Module) HiddenFromMake() bool {
-	return c.Properties.HideFromMake
+func (mod *Module) HiddenFromMake() bool {
+	return mod.Properties.HideFromMake
 }
 
 func (mod *Module) SanitizePropDefined() bool {
@@ -260,10 +263,8 @@
 }
 
 func (mod *Module) Binary() bool {
-	if mod.compiler != nil {
-		if _, ok := mod.compiler.(*binaryDecorator); ok {
-			return true
-		}
+	if binary, ok := mod.compiler.(binaryInterface); ok {
+		return binary.binary()
 	}
 	return false
 }
@@ -272,7 +273,7 @@
 	if !mod.Binary() {
 		return false
 	}
-	return Bool(mod.compiler.(*binaryDecorator).Properties.Static_executable)
+	return mod.StaticallyLinked()
 }
 
 func (mod *Module) Object() bool {
@@ -282,8 +283,8 @@
 
 func (mod *Module) Toc() android.OptionalPath {
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(libraryInterface); ok {
-			return android.OptionalPath{}
+		if lib, ok := mod.compiler.(libraryInterface); ok {
+			return lib.toc()
 		}
 	}
 	panic(fmt.Errorf("Toc() called on non-library module: %q", mod.BaseModuleName()))
@@ -392,6 +393,10 @@
 	WholeStaticLibs []string
 	HeaderLibs      []string
 
+	// Used for data dependencies adjacent to tests
+	DataLibs []string
+	DataBins []string
+
 	CrtBegin, CrtEnd string
 }
 
@@ -436,6 +441,8 @@
 type compiler interface {
 	initialize(ctx ModuleContext)
 	compilerFlags(ctx ModuleContext, flags Flags) Flags
+	cfgFlags(ctx ModuleContext, flags Flags) Flags
+	featureFlags(ctx ModuleContext, flags Flags) Flags
 	compilerProps() []interface{}
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path
 	compilerDeps(ctx DepsContext, deps Deps) Deps
@@ -464,6 +471,7 @@
 
 	stdLinkage(ctx *depsContext) RustLinkage
 
+	unstrippedOutputFilePath() android.Path
 	strippedOutputFilePath() android.OptionalPath
 }
 
@@ -524,10 +532,6 @@
 	return mod.Properties.PreventInstall
 }
 
-func (mod *Module) HideFromMake() {
-	mod.Properties.HideFromMake = true
-}
-
 func (mod *Module) MarkAsCoverageVariant(coverage bool) {
 	mod.coverage.Properties.IsCoverageVariant = coverage
 }
@@ -577,7 +581,7 @@
 
 func (mod *Module) CcLibrary() bool {
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(*libraryDecorator); ok {
+		if _, ok := mod.compiler.(libraryInterface); ok {
 			return true
 		}
 	}
@@ -595,6 +599,13 @@
 	return false
 }
 
+func (mod *Module) UnstrippedOutputFile() android.Path {
+	if mod.compiler != nil {
+		return mod.compiler.unstrippedOutputFilePath()
+	}
+	return nil
+}
+
 func (mod *Module) IncludeDirs() android.Paths {
 	if mod.compiler != nil {
 		if library, ok := mod.compiler.(*libraryDecorator); ok {
@@ -647,10 +658,7 @@
 }
 
 func (mod *Module) OutputFile() android.OptionalPath {
-	if mod.compiler != nil && mod.compiler.strippedOutputFilePath().Valid() {
-		return mod.compiler.strippedOutputFilePath()
-	}
-	return mod.unstrippedOutputFile
+	return mod.outputFile
 }
 
 func (mod *Module) CoverageFiles() android.Paths {
@@ -661,7 +669,7 @@
 }
 
 func (mod *Module) installable(apexInfo android.ApexInfo) bool {
-	if !mod.EverInstallable() {
+	if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
 		return false
 	}
 
@@ -674,6 +682,10 @@
 	return mod.OutputFile().Valid() && !mod.Properties.PreventInstall
 }
 
+func (ctx moduleContext) apexVariationName() string {
+	return ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).ApexVariationName
+}
+
 var _ cc.LinkableInterface = (*Module)(nil)
 
 func (mod *Module) Init() android.Module {
@@ -847,8 +859,11 @@
 		Toolchain: toolchain,
 	}
 
+	// Calculate rustc flags
 	if mod.compiler != nil {
 		flags = mod.compiler.compilerFlags(ctx, flags)
+		flags = mod.compiler.cfgFlags(ctx, flags)
+		flags = mod.compiler.featureFlags(ctx, flags)
 	}
 	if mod.coverage != nil {
 		flags, deps = mod.coverage.flags(ctx, flags, deps)
@@ -878,9 +893,12 @@
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
-		unstrippedOutputFile := mod.compiler.compile(ctx, flags, deps)
-		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
-		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), mod.unstrippedOutputFile)
+		outputFile := mod.compiler.compile(ctx, flags, deps)
+		if ctx.Failed() {
+			return
+		}
+		mod.outputFile = android.OptionalPathForPath(outputFile)
+		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), android.OptionalPathForPath(mod.compiler.unstrippedOutputFilePath()))
 
 		mod.docTimestampFile = mod.compiler.rustdoc(ctx, flags, deps)
 
@@ -893,8 +911,24 @@
 		}
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-		if mod.installable(apexInfo) {
+		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
+			// If the module has been specifically configure to not be installed then
+			// hide from make as otherwise it will break when running inside make as the
+			// output path to install will not be specified. Not all uninstallable
+			// modules can be hidden from make as some are needed for resolving make
+			// side dependencies.
+			mod.HideFromMake()
+		} else if !mod.installable(apexInfo) {
+			mod.SkipInstall()
+		}
+
+		// Still call install though, the installs will be stored as PackageSpecs to allow
+		// using the outputs in a genrule.
+		if mod.OutputFile().Valid() {
 			mod.compiler.install(ctx)
+			if ctx.Failed() {
+				return
+			}
 		}
 
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
@@ -935,6 +969,7 @@
 	name      string
 	library   bool
 	procMacro bool
+	dynamic   bool
 }
 
 // InstallDepNeeded returns true for rlibs, dylibs, and proc macros so that they or their transitive
@@ -945,13 +980,24 @@
 
 var _ android.InstallNeededDependencyTag = dependencyTag{}
 
+func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.library && d.dynamic {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	}
+	return nil
+}
+
+var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}
+
 var (
 	customBindgenDepTag = dependencyTag{name: "customBindgenTag"}
 	rlibDepTag          = dependencyTag{name: "rlibTag", library: true}
-	dylibDepTag         = dependencyTag{name: "dylib", library: true}
+	dylibDepTag         = dependencyTag{name: "dylib", library: true, dynamic: true}
 	procMacroDepTag     = dependencyTag{name: "procMacro", procMacro: true}
 	testPerSrcDepTag    = dependencyTag{name: "rust_unit_tests"}
 	sourceDepTag        = dependencyTag{name: "source"}
+	dataLibDepTag       = dependencyTag{name: "data lib"}
+	dataBinDepTag       = dependencyTag{name: "data bin"}
 )
 
 func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
@@ -989,6 +1035,13 @@
 	}
 }
 
+func (mod *Module) Prebuilt() *android.Prebuilt {
+	if p, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
+		return p.prebuilt()
+	}
+	return nil
+}
+
 func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps {
 	var depPaths PathDeps
 
@@ -1000,6 +1053,20 @@
 	directSrcProvidersDeps := []*Module{}
 	directSrcDeps := [](android.SourceFileProducer){}
 
+	// For the dependency from platform to apex, use the latest stubs
+	mod.apexSdkVersion = android.FutureApiLevel
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		mod.apexSdkVersion = apexInfo.MinSdkVersion
+	}
+
+	if android.InList("hwaddress", ctx.Config().SanitizeDevice()) {
+		// In hwasan build, we override apexSdkVersion to the FutureApiLevel(10000)
+		// so that even Q(29/Android10) apexes could use the dynamic unwinder by linking the newer stubs(e.g libc(R+)).
+		// (b/144430859)
+		mod.apexSdkVersion = android.FutureApiLevel
+	}
+
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
@@ -1060,13 +1127,8 @@
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
-				linkFile := rustDep.unstrippedOutputFile
-				if !linkFile.Valid() {
-					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q",
-						depName, ctx.ModuleName())
-					return
-				}
-				linkDir := linkPathFromFilePath(linkFile.Path())
+				linkFile := rustDep.UnstrippedOutputFile()
+				linkDir := linkPathFromFilePath(linkFile)
 				if lib, ok := mod.compiler.(exportedFlagsProducer); ok {
 					lib.exportLinkDirs(linkDir)
 				}
@@ -1098,7 +1160,12 @@
 				if cc.IsWholeStaticLib(depTag) {
 					// rustc will bundle static libraries when they're passed with "-lstatic=<lib>". This will fail
 					// if the library is not prefixed by "lib".
-					if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
+					if mod.Binary() {
+						// Binaries may sometimes need to link whole static libraries that don't start with 'lib'.
+						// Since binaries don't need to 'rebundle' these like libraries and only use these for the
+						// final linkage, pass the args directly to the linker to handle these cases.
+						depPaths.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", linkObject.Path().String(), "-Wl,--no-whole-archive"}...)
+					} else if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
 						depPaths.depFlags = append(depPaths.depFlags, "-lstatic="+libName)
 					} else {
 						ctx.ModuleErrorf("'%q' cannot be listed as a whole_static_library in Rust modules unless the output is prefixed by 'lib'", depName, ctx.ModuleName())
@@ -1170,15 +1237,15 @@
 
 	var rlibDepFiles RustLibraries
 	for _, dep := range directRlibDeps {
-		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 	var dylibDepFiles RustLibraries
 	for _, dep := range directDylibDeps {
-		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 	var procMacroDepFiles RustLibraries
 	for _, dep := range directProcMacroDeps {
-		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 
 	var staticLibDepFiles android.Paths
@@ -1373,6 +1440,12 @@
 		}
 	}
 
+	actx.AddVariationDependencies([]blueprint.Variation{
+		{Mutator: "link", Variation: "shared"},
+	}, dataLibDepTag, deps.DataLibs...)
+
+	actx.AddVariationDependencies(nil, dataBinDepTag, deps.DataBins...)
+
 	// proc_macros are compiler plugins, and so we need the host arch variant as a dependendcy.
 	actx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), procMacroDepTag, deps.ProcMacros...)
 }
@@ -1487,7 +1560,7 @@
 // Overrides ApexModule.IsInstallabeToApex()
 func (mod *Module) IsInstallableToApex() bool {
 	if mod.compiler != nil {
-		if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.shared() || lib.dylib()) {
 			return true
 		}
 		if _, ok := mod.compiler.(*binaryDecorator); ok {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 80f693e..97bd541 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -79,8 +79,10 @@
 }
 
 const (
-	sharedVendorVariant = "android_vendor.29_arm64_armv8-a_shared"
-	rlibVendorVariant   = "android_vendor.29_arm64_armv8-a_rlib_rlib-std"
+	sharedVendorVariant   = "android_vendor.29_arm64_armv8-a_shared"
+	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"
 )
 
 func testRustVndkFs(t *testing.T, bp string, fs android.MockFS) *android.TestContext {
@@ -101,7 +103,22 @@
 		),
 	).RunTestWithBp(t, bp)
 	return result.TestContext
+}
 
+func testRustRecoveryFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, vndk_version, recovery_version string) *android.TestContext {
+	skipTestIfOsNotSupported(t)
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		fs.AddToFixture(),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = StringPtr(device_version)
+				variables.RecoverySnapshotVersion = StringPtr(recovery_version)
+				variables.Platform_vndk_version = StringPtr(vndk_version)
+			},
+		),
+	).RunTestWithBp(t, bp)
+	return result.TestContext
 }
 
 // testRustCov returns a TestContext in which a basic environment has been
@@ -439,6 +456,13 @@
 		}`)
 
 	m := ctx.SingletonForTests("file_metrics")
+	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
 	m.Output("libwaldo.dylib.so.bloaty.csv")
-	m.Output("stripped/libwaldo.dylib.so.bloaty.csv")
+}
+
+func assertString(t *testing.T, got, expected string) {
+	t.Helper()
+	if got != expected {
+		t.Errorf("expected %q got %q", expected, got)
+	}
 }
diff --git a/rust/sanitize.go b/rust/sanitize.go
index a4ba4bd..be9dc42 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -15,20 +15,39 @@
 package rust
 
 import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/rust/config"
-	"fmt"
-	"github.com/google/blueprint"
 )
 
+// TODO: When Rust has sanitizer-parity with CC, deduplicate this struct
 type SanitizeProperties struct {
 	// enable AddressSanitizer, HWAddressSanitizer, and others.
 	Sanitize struct {
 		Address   *bool `android:"arch_variant"`
 		Hwaddress *bool `android:"arch_variant"`
-		Fuzzer    *bool `android:"arch_variant"`
-		Never     *bool `android:"arch_variant"`
+
+		// Memory-tagging, only available on arm64
+		// if diag.memtag unset or false, enables async memory tagging
+		Memtag_heap *bool `android:"arch_variant"`
+		Fuzzer      *bool `android:"arch_variant"`
+		Never       *bool `android:"arch_variant"`
+
+		// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
+		// Replaces abort() on error with a human-readable error message.
+		// Address and Thread sanitizers always run in diagnostic mode.
+		Diag struct {
+			// Memory-tagging, only available on arm64
+			// requires sanitizer.memtag: true
+			// if set, enables sync memory tagging
+			Memtag_heap *bool `android:"arch_variant"`
+		}
 	}
 	SanitizerEnabled bool `blueprint:"mutated"`
 	SanitizeDep      bool `blueprint:"mutated"`
@@ -59,9 +78,18 @@
 	"-Z sanitizer=address",
 }
 
+// See cc/sanitize.go's hwasanGlobalOptions for global hwasan options.
 var hwasanFlags = []string{
 	"-Z sanitizer=hwaddress",
 	"-C target-feature=+tagged-globals",
+
+	// Flags from cc/sanitize.go hwasanFlags
+	"-C llvm-args=--aarch64-enable-global-isel-at-O=-1",
+	"-C llvm-args=-fast-isel=false",
+	"-C llvm-args=-instcombine-lower-dbg-declare=0",
+
+	// Additional flags for HWASAN-ified Rust/C interop
+	"-C llvm-args=--hwasan-with-ifunc",
 }
 
 func boolPtr(v bool) *bool {
@@ -79,7 +107,88 @@
 }
 
 func (sanitize *sanitize) begin(ctx BaseModuleContext) {
-	s := sanitize.Properties.Sanitize
+	s := &sanitize.Properties.Sanitize
+
+	// Never always wins.
+	if Bool(s.Never) {
+		return
+	}
+
+	// rust_test targets default to SYNC MemTag unless explicitly set to ASYNC (via diag: {Memtag_heap}).
+	if binary, ok := ctx.RustModule().compiler.(binaryInterface); ok && binary.testBinary() {
+		if s.Memtag_heap == nil {
+			s.Memtag_heap = proptools.BoolPtr(true)
+		}
+		if s.Diag.Memtag_heap == nil {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
+	}
+
+	var globalSanitizers []string
+	var globalSanitizersDiag []string
+
+	if ctx.Host() {
+		if !ctx.Windows() {
+			globalSanitizers = ctx.Config().SanitizeHost()
+		}
+	} else {
+		arches := ctx.Config().SanitizeDeviceArch()
+		if len(arches) == 0 || android.InList(ctx.Arch().ArchType.Name, arches) {
+			globalSanitizers = ctx.Config().SanitizeDevice()
+			globalSanitizersDiag = ctx.Config().SanitizeDeviceDiag()
+		}
+	}
+
+	if len(globalSanitizers) > 0 {
+		var found bool
+
+		// Global Sanitizers
+		if found, globalSanitizers = android.RemoveFromList("hwaddress", globalSanitizers); found && s.Hwaddress == nil {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet.
+			if !ctx.RustModule().StaticExecutable() {
+				s.Hwaddress = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
+			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("address", globalSanitizers); found && s.Address == nil {
+			s.Address = proptools.BoolPtr(true)
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("fuzzer", globalSanitizers); found && s.Fuzzer == nil {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet, and fuzzer enables HWAsan
+			if !ctx.RustModule().StaticExecutable() {
+				s.Fuzzer = proptools.BoolPtr(true)
+			}
+		}
+
+		// Global Diag Sanitizers
+		if found, globalSanitizersDiag = android.RemoveFromList("memtag_heap", globalSanitizersDiag); found &&
+			s.Diag.Memtag_heap == nil && Bool(s.Memtag_heap) {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
+	}
+
+	// Enable Memtag for all components in the include paths (for Aarch64 only)
+	if ctx.Arch().ArchType == android.Arm64 {
+		if ctx.Config().MemtagHeapSyncEnabledForPath(ctx.ModuleDir()) {
+			if s.Memtag_heap == nil {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+			if s.Diag.Memtag_heap == nil {
+				s.Diag.Memtag_heap = proptools.BoolPtr(true)
+			}
+		} else if ctx.Config().MemtagHeapAsyncEnabledForPath(ctx.ModuleDir()) {
+			if s.Memtag_heap == nil {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+		}
+	}
 
 	// TODO:(b/178369775)
 	// For now sanitizing is only supported on devices
@@ -96,7 +205,22 @@
 		s.Hwaddress = nil
 	}
 
-	if ctx.Os() == android.Android && Bool(s.Hwaddress) {
+	// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
+	// Keep libc instrumented so that ramdisk / vendor_ramdisk / recovery can run hwasan-instrumented code if necessary.
+	if (ctx.RustModule().InRamdisk() || ctx.RustModule().InVendorRamdisk() || ctx.RustModule().InRecovery()) && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
+		s.Hwaddress = nil
+	}
+
+	if Bool(s.Hwaddress) {
+		s.Address = nil
+	}
+
+	// Memtag_heap is only implemented on AArch64.
+	if ctx.Arch().ArchType != android.Arm64 {
+		s.Memtag_heap = nil
+	}
+
+	if ctx.Os() == android.Android && (Bool(s.Hwaddress) || Bool(s.Address) || Bool(s.Memtag_heap)) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
 }
@@ -116,12 +240,10 @@
 		} else {
 			flags.RustFlags = append(flags.RustFlags, asanFlags...)
 		}
-	}
-	if Bool(sanitize.Properties.Sanitize.Address) {
-		flags.RustFlags = append(flags.RustFlags, asanFlags...)
-	}
-	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
+	} else if Bool(sanitize.Properties.Sanitize.Hwaddress) {
 		flags.RustFlags = append(flags.RustFlags, hwasanFlags...)
+	} else if Bool(sanitize.Properties.Sanitize.Address) {
+		flags.RustFlags = append(flags.RustFlags, asanFlags...)
 	}
 	return flags, deps
 }
@@ -136,6 +258,26 @@
 			return
 		}
 
+		if Bool(mod.sanitize.Properties.Sanitize.Memtag_heap) && mod.Binary() {
+			noteDep := "note_memtag_heap_async"
+			if Bool(mod.sanitize.Properties.Sanitize.Diag.Memtag_heap) {
+				noteDep = "note_memtag_heap_sync"
+			}
+			// If we're using snapshots, redirect to snapshot whenever possible
+			// TODO(b/178470649): clean manual snapshot redirections
+			snapshot := mctx.Provider(cc.SnapshotInfoProvider).(cc.SnapshotInfo)
+			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
+				noteDep = lib
+			}
+			depTag := cc.StaticDepTag(true)
+			variations := append(mctx.Target().Variations(),
+				blueprint.Variation{Mutator: "link", Variation: "static"})
+			if mod.Device() {
+				variations = append(variations, mod.ImageVariation())
+			}
+			mctx.AddFarVariationDependencies(variations, depTag, noteDep)
+		}
+
 		variations := mctx.Target().Variations()
 		var depTag blueprint.DependencyTag
 		var deps []string
@@ -148,27 +290,24 @@
 			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "asan")}
 		} else if mod.IsSanitizerEnabled(cc.Hwasan) ||
 			(mod.IsSanitizerEnabled(cc.Fuzzer) && mctx.Arch().ArchType == android.Arm64) {
-			// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
-			if binary, ok := mod.compiler.(*binaryDecorator); ok {
-				if Bool(binary.Properties.Static_executable) {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet.
+			if binary, ok := mod.compiler.(binaryInterface); ok {
+				if binary.staticallyLinked() {
 					mctx.ModuleErrorf("HWASan is not supported for static Rust executables yet.")
 				}
 			}
 
-			if mod.StaticallyLinked() {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "static"})
-				depTag = cc.StaticDepTag(false)
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan_static")}
-			} else {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "shared"})
-				depTag = cc.SharedDepTag()
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
-			}
+			// Always link against the shared library -- static binaries will pull in the static
+			// library during final link if necessary
+			variations = append(variations,
+				blueprint.Variation{Mutator: "link", Variation: "shared"})
+			depTag = cc.SharedDepTag()
+			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
 		}
 
-		mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		if len(deps) > 0 {
+			mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		}
 	}
 }
 
@@ -184,6 +323,9 @@
 	case cc.Hwasan:
 		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
 		sanitizerSet = true
+	case cc.Memtag_heap:
+		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+		sanitizerSet = true
 	default:
 		panic(fmt.Errorf("setting unsupported sanitizerType %d", t))
 	}
@@ -243,6 +385,8 @@
 		return sanitize.Properties.Sanitize.Address
 	case cc.Hwasan:
 		return sanitize.Properties.Sanitize.Hwaddress
+	case cc.Memtag_heap:
+		return sanitize.Properties.Sanitize.Memtag_heap
 	default:
 		return nil
 	}
@@ -268,6 +412,12 @@
 	case cc.Asan:
 		return true
 	case cc.Hwasan:
+		// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
+		if mod.StaticExecutable() {
+			return false
+		}
+		return true
+	case cc.Memtag_heap:
 		return true
 	default:
 		return false
@@ -311,8 +461,8 @@
 func (mod *Module) StaticallyLinked() bool {
 	if lib, ok := mod.compiler.(libraryInterface); ok {
 		return lib.rlib() || lib.static()
-	} else if binary, ok := mod.compiler.(*binaryDecorator); ok {
-		return Bool(binary.Properties.Static_executable)
+	} else if binary, ok := mod.compiler.(binaryInterface); ok {
+		return binary.staticallyLinked()
 	}
 	return false
 }
diff --git a/rust/sanitize_test.go b/rust/sanitize_test.go
new file mode 100644
index 0000000..d6a14b2
--- /dev/null
+++ b/rust/sanitize_test.go
@@ -0,0 +1,365 @@
+package rust
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+type MemtagNoteType int
+
+const (
+	None MemtagNoteType = iota + 1
+	Sync
+	Async
+)
+
+func (t MemtagNoteType) str() string {
+	switch t {
+	case None:
+		return "none"
+	case Sync:
+		return "sync"
+	case Async:
+		return "async"
+	default:
+		panic("type_note_invalid")
+	}
+}
+
+func checkHasMemtagNote(t *testing.T, m android.TestingModule, expected MemtagNoteType) {
+	t.Helper()
+	note_async := "note_memtag_heap_async"
+	note_sync := "note_memtag_heap_sync"
+
+	found := None
+	implicits := m.Rule("rustc").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), note_async) {
+			found = Async
+			break
+		} else if strings.Contains(lib.Rel(), note_sync) {
+			found = Sync
+			break
+		}
+	}
+
+	if found != expected {
+		t.Errorf("Wrong Memtag note in target %q: found %q, expected %q", m.Module().(*Module).Name(), found.str(), expected.str())
+	}
+}
+
+var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
+	android.FixtureModifyMockFS(func(fs android.MockFS) {
+		templateBp := `
+		rust_test {
+			name: "unset_test_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_test {
+			name: "no_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_test {
+			name: "set_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_test {
+			name: "set_memtag_set_async_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_test {
+			name: "set_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_test {
+			name: "unset_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_binary_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_binary {
+			name: "no_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_binary {
+			name: "set_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_async_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+		`
+		subdirNoOverrideBp := fmt.Sprintf(templateBp, "no_override")
+		subdirOverrideDefaultDisableBp := fmt.Sprintf(templateBp, "override_default_disable")
+		subdirSyncBp := fmt.Sprintf(templateBp, "override_default_sync")
+		subdirAsyncBp := fmt.Sprintf(templateBp, "override_default_async")
+
+		fs.Merge(android.MockFS{
+			"subdir_no_override/Android.bp":              []byte(subdirNoOverrideBp),
+			"subdir_override_default_disable/Android.bp": []byte(subdirOverrideDefaultDisableBp),
+			"subdir_sync/Android.bp":                     []byte(subdirSyncBp),
+			"subdir_async/Android.bp":                    []byte(subdirAsyncBp),
+		})
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.MemtagHeapExcludePaths = []string{"subdir_override_default_disable"}
+		// "subdir_override_default_disable" is covered by both include and override_default_disable paths. override_default_disable wins.
+		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync", "subdir_override_default_disable"}
+		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async", "subdir_override_default_disable"}
+	}),
+)
+
+func TestSanitizeMemtagHeap(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
diff --git a/rust/snapshot_prebuilt.go b/rust/snapshot_prebuilt.go
index 79eaab3..dfbc1d1 100644
--- a/rust/snapshot_prebuilt.go
+++ b/rust/snapshot_prebuilt.go
@@ -44,6 +44,8 @@
 func registerRustSnapshotModules(ctx android.RegistrationContext) {
 	cc.VendorSnapshotImageSingleton.RegisterAdditionalModule(ctx,
 		"vendor_snapshot_rlib", VendorSnapshotRlibFactory)
+	cc.RecoverySnapshotImageSingleton.RegisterAdditionalModule(ctx,
+		"recovery_snapshot_rlib", RecoverySnapshotRlibFactory)
 }
 
 func snapshotLibraryFactory(image cc.SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
@@ -85,8 +87,9 @@
 	if !library.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
-
-	return android.PathForModuleSrc(ctx, *library.properties.Src)
+	outputFile := android.PathForModuleSrc(ctx, *library.properties.Src)
+	library.unstrippedOutputFile = outputFile
+	return outputFile
 }
 
 func (library *snapshotLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath {
@@ -104,6 +107,13 @@
 	return module.Init()
 }
 
+func RecoverySnapshotRlibFactory() android.Module {
+	module, prebuilt := snapshotLibraryFactory(cc.RecoverySnapshotImageSingleton, cc.SnapshotRlibSuffix)
+	prebuilt.libraryDecorator.BuildOnlyRlib()
+	prebuilt.libraryDecorator.setNoStdlibs()
+	return module.Init()
+}
+
 func (library *snapshotLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
 	if len(arches) == 0 || arches[0].ArchType.String() != library.Arch() {
diff --git a/rust/test.go b/rust/test.go
index e95b47c..250b765 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -18,6 +18,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/tradefed"
 )
 
@@ -49,6 +50,12 @@
 	// the test
 	Data []string `android:"path,arch_variant"`
 
+	// list of shared library modules that should be installed alongside the test
+	Data_libs []string `android:"arch_variant"`
+
+	// list of binary modules that should be installed alongside the test
+	Data_bins []string `android:"arch_variant"`
+
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -59,6 +66,10 @@
 
 	// Test options.
 	Test_options TestOptions
+
+	// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
+	// with root permission.
+	Require_root *bool
 }
 
 // A test module is a binary module with extra --test compiler flag
@@ -109,15 +120,56 @@
 }
 
 func (test *testDecorator) install(ctx ModuleContext) {
+	testInstallBase := "/data/local/tests/unrestricted"
+	if ctx.RustModule().InVendor() || ctx.RustModule().UseVndk() {
+		testInstallBase = "/data/local/tests/vendor"
+	}
+
+	var configs []tradefed.Config
+	if Bool(test.Properties.Require_root) {
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
+	} else {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "force-root", Value: "false"})
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
+	}
+
 	test.testConfig = tradefed.AutoGenRustTestConfig(ctx,
 		test.Properties.Test_config,
 		test.Properties.Test_config_template,
 		test.Properties.Test_suites,
-		nil,
-		test.Properties.Auto_gen_config)
+		configs,
+		test.Properties.Auto_gen_config,
+		testInstallBase)
 
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
 
+	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(cc.LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_lib %q is not a linkable module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+
+	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(cc.LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_bin %q is not a linkable module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+
 	for _, dataSrcPath := range dataSrcPaths {
 		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
 	}
@@ -158,6 +210,12 @@
 
 func RustTestFactory() android.Module {
 	module, _ := NewRustTest(android.HostAndDeviceSupported)
+
+	// NewRustTest will set MultilibBoth true, however the host variant
+	// cannot produce the non-primary target. Therefore, add the
+	// rustTestHostMultilib load hook to set MultilibFirst for the
+	// host target.
+	android.AddLoadHook(module, rustTestHostMultilib)
 	return module.Init()
 }
 
@@ -175,5 +233,25 @@
 
 	deps.Rustlibs = append(deps.Rustlibs, "libtest")
 
+	deps.DataLibs = append(deps.DataLibs, test.Properties.Data_libs...)
+	deps.DataBins = append(deps.DataBins, test.Properties.Data_bins...)
+
 	return deps
 }
+
+func (test *testDecorator) testBinary() bool {
+	return true
+}
+
+func rustTestHostMultilib(ctx android.LoadHookContext) {
+	type props struct {
+		Target struct {
+			Host struct {
+				Compile_multilib *string
+			}
+		}
+	}
+	p := &props{}
+	p.Target.Host.Compile_multilib = proptools.StringPtr("first")
+	ctx.AppendProperties(p)
+}
diff --git a/rust/test_test.go b/rust/test_test.go
index 892761a..1124176 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -74,3 +74,129 @@
 		t.Errorf("Device rust_test module 'my_test' does not link libstd as an rlib")
 	}
 }
+
+func TestDataLibs(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "test_lib",
+			srcs: ["test_lib.cpp"],
+		}
+
+		rust_binary {
+			name: "rusty",
+			srcs: ["foo.rs"],
+			compile_multilib: "both",
+		}
+
+		rust_ffi {
+			name: "librust_test_lib",
+			crate_name: "rust_test_lib",
+			srcs: ["test_lib.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_test {
+			name: "main_test",
+			srcs: ["foo.rs"],
+			data_libs: ["test_lib"],
+			data_bins: ["rusty"],
+		}
+ `
+
+	ctx := testRust(t, bp)
+
+	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testBinary := module.(*Module).compiler.(*testDecorator)
+	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
+	}
+	if len(outputFiles) != 1 {
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
+	}
+	if len(testBinary.dataPaths()) != 2 {
+		t.Fatalf("expected exactly two test data files. test data files: [%s]", testBinary.dataPaths())
+	}
+
+	outputPath := outputFiles[0].String()
+	dataLibraryPath := testBinary.dataPaths()[0].SrcPath.String()
+	dataBinaryPath := testBinary.dataPaths()[1].SrcPath.String()
+
+	if !strings.HasSuffix(outputPath, "/main_test") {
+		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
+	}
+	if !strings.HasSuffix(dataLibraryPath, "/test_lib.so") {
+		t.Errorf("expected test data file to be 'test_lib.so', but was '%s'", dataLibraryPath)
+	}
+	if !strings.HasSuffix(dataBinaryPath, "/rusty") {
+		t.Errorf("expected test data file to be 'test_lib.so', but was '%s'", dataBinaryPath)
+	}
+}
+
+func TestDataLibsRelativeInstallPath(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "test_lib",
+			srcs: ["test_lib.cpp"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_ffi {
+			name: "librust_test_lib",
+			crate_name: "rust_test_lib",
+			srcs: ["test_lib.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_binary {
+			name: "rusty",
+			srcs: ["foo.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_test {
+			name: "main_test",
+			srcs: ["foo.rs"],
+			data_libs: ["test_lib", "librust_test_lib"],
+			data_bins: ["rusty"],
+			compile_multilib: "64",
+		}
+ `
+
+	ctx := testRust(t, bp)
+	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testBinary := module.(*Module).compiler.(*testDecorator)
+	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
+	}
+	if len(outputFiles) != 1 {
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
+	}
+	if len(testBinary.dataPaths()) != 3 {
+		t.Fatalf("expected exactly two test data files. test data files: [%s]", testBinary.dataPaths())
+	}
+
+	outputPath := outputFiles[0].String()
+
+	if !strings.HasSuffix(outputPath, "/main_test") {
+		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
+	}
+	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
+	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
+	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][2], ":rusty:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:rusty:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][2])
+	}
+}
diff --git a/rust/testing.go b/rust/testing.go
index 94cdd9d..1b34dfe 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -53,74 +53,14 @@
 func GatherRequiredDepsForTest() string {
 	bp := `
 		rust_prebuilt_library {
-				name: "libstd_x86_64-unknown-linux-gnu",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_x86_64-unknown-linux-gnu",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libstd_i686-unknown-linux-gnu",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_i686-unknown-linux-gnu",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libstd_x86_64-apple-darwin",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_x86_64-apple-darwin",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
+				name: "libstd",
+				crate_name: "std",
+				rlib: {
+					srcs: ["libstd.rlib"],
+				},
+				dylib: {
+					srcs: ["libstd.so"],
+				},
 				host_supported: true,
 				sysroot: true,
 		}
@@ -135,6 +75,7 @@
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
+			recovery_available: true,
 			llndk: {
 				symbol_file: "liblog.map.txt",
 			},
@@ -151,7 +92,12 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
-			export_include_dirs: ["libprotobuf-cpp-full-includes"],
+		}
+		cc_library {
+			name: "libclang_rt.hwasan_static-aarch64-android",
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
 		}
 		rust_library {
 			name: "libstd",
@@ -161,6 +107,7 @@
 			host_supported: true,
 			vendor_available: true,
 			vendor_ramdisk_available: true,
+			recovery_available: true,
 			native_coverage: false,
 			sysroot: true,
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
@@ -173,6 +120,7 @@
 			host_supported: true,
 			vendor_available: true,
 			vendor_ramdisk_available: true,
+			recovery_available: true,
 			native_coverage: false,
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
@@ -246,5 +194,8 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+	})
 	registerRustSnapshotModules(ctx)
 }
diff --git a/rust/toolchain_library.go b/rust/toolchain_library.go
new file mode 100644
index 0000000..326d529
--- /dev/null
+++ b/rust/toolchain_library.go
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 rust
+
+import (
+	"path"
+
+	"android/soong/android"
+	"android/soong/rust/config"
+)
+
+// This module is used to compile the rust toolchain libraries
+// When RUST_PREBUILTS_VERSION is set, the library will generated
+// from the given Rust version.
+func init() {
+	android.RegisterModuleType("rust_toolchain_library",
+		rustToolchainLibraryFactory)
+	android.RegisterModuleType("rust_toolchain_library_rlib",
+		rustToolchainLibraryRlibFactory)
+	android.RegisterModuleType("rust_toolchain_library_dylib",
+		rustToolchainLibraryDylibFactory)
+}
+
+type toolchainLibraryProperties struct {
+	// path to the toolchain source, relative to the top of the toolchain source
+	Toolchain_src *string `android:"arch_variant"`
+}
+
+type toolchainLibraryDecorator struct {
+	*libraryDecorator
+	Properties toolchainLibraryProperties
+}
+
+// rust_toolchain_library produces all rust variants.
+func rustToolchainLibraryFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyRust()
+
+	return initToolchainLibrary(module, library)
+}
+
+// rust_toolchain_library_dylib produces a dylib.
+func rustToolchainLibraryDylibFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyDylib()
+
+	return initToolchainLibrary(module, library)
+}
+
+// rust_toolchain_library_rlib produces an rlib.
+func rustToolchainLibraryRlibFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyRlib()
+
+	return initToolchainLibrary(module, library)
+}
+
+func initToolchainLibrary(module *Module, library *libraryDecorator) android.Module {
+	toolchainLibrary := &toolchainLibraryDecorator{
+		libraryDecorator: library,
+	}
+	module.compiler = toolchainLibrary
+	module.AddProperties(&toolchainLibrary.Properties)
+	android.AddLoadHook(module, rustSetToolchainSource)
+
+	return module.Init()
+}
+
+func rustSetToolchainSource(ctx android.LoadHookContext) {
+	if toolchainLib, ok := ctx.Module().(*Module).compiler.(*toolchainLibraryDecorator); ok {
+		prefix := "linux-x86/" + GetRustPrebuiltVersion(ctx)
+		newSrcs := []string{path.Join(prefix, android.String(toolchainLib.Properties.Toolchain_src))}
+
+		type props struct {
+			Srcs []string
+		}
+		p := &props{}
+		p.Srcs = newSrcs
+		ctx.AppendProperties(p)
+
+	} else {
+		ctx.ModuleErrorf("Called rustSetToolchainSource on a non-Rust Module.")
+	}
+}
+
+// GetRustPrebuiltVersion returns the RUST_PREBUILTS_VERSION env var, or the default version if it is not defined.
+func GetRustPrebuiltVersion(ctx android.LoadHookContext) string {
+	return ctx.AConfig().GetenvWithDefault("RUST_PREBUILTS_VERSION", config.RustDefaultVersion)
+}
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
index 60ddb65..03bd867 100644
--- a/rust/vendor_snapshot_test.go
+++ b/rust/vendor_snapshot_test.go
@@ -562,6 +562,7 @@
 					"libvendor",
 					"libvndk",
 					"libclang_rt.builtins-aarch64-android",
+					"note_memtag_heap_sync",
 				],
 				shared_libs: [
 					"libvendor_available",
@@ -853,6 +854,20 @@
 		},
 	}
 
+	// Test sanitizers use the snapshot libraries
+	rust_binary {
+		name: "memtag_binary",
+		srcs: ["vendor/bin.rs"],
+		vendor: true,
+		compile_multilib: "64",
+		sanitize: {
+			memtag_heap: true,
+			diag: {
+				memtag_heap: true,
+			}
+		},
+	}
+
 	// old snapshot module which has to be ignored
 	vendor_snapshot_binary {
 		name: "bin",
@@ -880,11 +895,25 @@
 			},
 		},
 	}
+
+	vendor_snapshot_static {
+		name: "note_memtag_heap_sync",
+		vendor: true,
+		target_arch: "arm64",
+		version: "30",
+		arch: {
+			arm64: {
+				src: "note_memtag_heap_sync.a",
+			},
+		},
+	}
+
 `
 
 	mockFS := android.MockFS{
 		"framework/Android.bp":                          []byte(frameworkBp),
 		"framework/bin.rs":                              nil,
+		"note_memtag_heap_sync.a":                       nil,
 		"vendor/Android.bp":                             []byte(vendorProprietaryBp),
 		"vendor/bin":                                    nil,
 		"vendor/bin32":                                  nil,
@@ -993,4 +1022,360 @@
 	if android.InList(binaryVariant, binVariants) {
 		t.Errorf("bin must not have variant %#v, but it does", sharedVariant)
 	}
+
+	memtagStaticLibs := ctx.ModuleForTests("memtag_binary", "android_vendor.30_arm64_armv8-a").Module().(*Module).Properties.AndroidMkStaticLibs
+	if g, w := memtagStaticLibs, []string{"libclang_rt.builtins-aarch64-android.vendor", "note_memtag_heap_sync.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted memtag_binary AndroidMkStaticLibs %q, got %q", w, g)
+	}
+}
+
+func TestRecoverySnapshotCapture(t *testing.T) {
+	bp := `
+	rust_ffi {
+		name: "librecovery",
+		recovery: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery",
+	}
+
+	rust_ffi {
+		name: "librecovery_available",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_available",
+	}
+
+	rust_library_rlib {
+		name: "librecovery_rlib",
+		recovery: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_rlib",
+	}
+
+	rust_library_rlib {
+		name: "librecovery_available_rlib",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_available_rlib",
+	}
+
+	rust_binary {
+		name: "recovery_bin",
+		recovery: true,
+		srcs: ["foo.rs"],
+	}
+
+	rust_binary {
+		name: "recovery_available_bin",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+	}
+
+`
+	// Check Recovery snapshot output.
+
+	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "", "29", "current")
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var jsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, all recovery:true and recovery_available modules are captured.
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "librecovery.so.json"),
+			filepath.Join(sharedDir, "librecovery_available.so.json"))
+
+		// For static libraries, all recovery:true and recovery_available modules are captured.
+		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "librecovery.a.json"),
+			filepath.Join(staticDir, "librecovery_available.a.json"))
+
+		// For rlib libraries, all recovery:true and recovery_available modules are captured.
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librecovery_rlib.rlib.json"),
+			filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))
+
+		// For binary executables, all recovery:true and recovery_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "recovery_bin.json"),
+				filepath.Join(binaryDir, "recovery_available_bin.json"))
+		}
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found", jsonFile)
+		}
+	}
+}
+
+func TestRecoverySnapshotExclude(t *testing.T) {
+	// This test verifies that the exclude_from_recovery_snapshot property
+	// makes its way from the Android.bp source file into the module data
+	// structure. It also verifies that modules are correctly included or
+	// excluded in the recovery snapshot based on their path (framework or
+	// vendor) and the exclude_from_recovery_snapshot property.
+
+	frameworkBp := `
+		rust_ffi_shared {
+			name: "libinclude",
+			srcs: ["src/include.rs"],
+			recovery_available: true,
+			crate_name: "include",
+		}
+		rust_ffi_shared {
+			name: "libexclude",
+			srcs: ["src/exclude.rs"],
+			recovery: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "exclude",
+		}
+		rust_ffi_shared {
+			name: "libavailable_exclude",
+			srcs: ["src/exclude.rs"],
+			recovery_available: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "available_exclude",
+		}
+		rust_library_rlib {
+			name: "libinclude_rlib",
+			srcs: ["src/include.rs"],
+			recovery_available: true,
+			crate_name: "include_rlib",
+		}
+		rust_library_rlib {
+			name: "libexclude_rlib",
+			srcs: ["src/exclude.rs"],
+			recovery: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "exclude_rlib",
+		}
+		rust_library_rlib {
+			name: "libavailable_exclude_rlib",
+			srcs: ["src/exclude.rs"],
+			recovery_available: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "available_exclude_rlib",
+		}
+	`
+
+	vendorProprietaryBp := `
+		rust_ffi_shared {
+			name: "librecovery",
+			srcs: ["recovery.rs"],
+			recovery: true,
+			crate_name: "recovery",
+		}
+		rust_library_rlib {
+			name: "librecovery_rlib",
+			srcs: ["recovery.rs"],
+			recovery: true,
+			crate_name: "recovery_rlib",
+		}
+	`
+
+	mockFS := map[string][]byte{
+		"framework/Android.bp": []byte(frameworkBp),
+		"framework/include.rs": nil,
+		"framework/exclude.rs": nil,
+		"device/Android.bp":    []byte(vendorProprietaryBp),
+		"device/recovery.rs":   nil,
+	}
+
+	ctx := testRustRecoveryFsVersions(t, "", mockFS, "", "29", "current")
+
+	// Test an include and exclude framework module.
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude_rlib", false, rlibRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude_rlib", true, rlibRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude_rlib", true, rlibRecoveryVariant)
+
+	// A recovery module is excluded, but by its path not the exclude_from_recovery_snapshot property
+	// ('device/' and 'vendor/' are default excluded). See snapshot/recovery_snapshot.go for more detail.
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery_rlib", false, rlibRecoveryVariant)
+
+	// Verify the content of the recovery snapshot.
+
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var includeJsonFiles []string
+	var excludeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude_rlib", "libinclude_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "libinclude_rlib.rlib.json"))
+
+		// Excluded modules
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude_rlib", "libexclude_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libexclude_rlib.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude_rlib", "libavailable_exclude_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libavailable_exclude_rlib.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+
+	// Verify that each json file for an excluded module has no rule.
+	for _, jsonFile := range excludeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
+			t.Errorf("exclude json file %q found", jsonFile)
+		}
+	}
+}
+
+func TestRecoverySnapshotDirected(t *testing.T) {
+	bp := `
+	rust_ffi_shared {
+		name: "librecovery",
+		recovery: true,
+		crate_name: "recovery",
+		srcs: ["foo.rs"],
+	}
+
+	rust_ffi_shared {
+		name: "librecovery_available",
+		recovery_available: true,
+		crate_name: "recovery_available",
+		srcs: ["foo.rs"],
+	}
+
+	rust_library_rlib {
+		name: "librecovery_rlib",
+		recovery: true,
+		crate_name: "recovery",
+		srcs: ["foo.rs"],
+	}
+
+	rust_library_rlib {
+		name: "librecovery_available_rlib",
+		recovery_available: true,
+		crate_name: "recovery_available",
+		srcs: ["foo.rs"],
+	}
+
+	/* TODO: Uncomment when Rust supports the "prefer" property for prebuilts
+	rust_library_rlib {
+		name: "libfoo_rlib",
+		recovery: true,
+		crate_name: "foo",
+	}
+
+	rust_prebuilt_rlib {
+		name: "libfoo_rlib",
+		recovery: true,
+		prefer: true,
+		srcs: ["libfoo.rlib"],
+		crate_name: "foo",
+	}
+	*/
+`
+	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "current", "29", "current")
+	ctx.Config().TestProductVariables.RecoverySnapshotModules = make(map[string]bool)
+	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery"] = true
+	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery_rlib"] = true
+	ctx.Config().TestProductVariables.DirectedRecoverySnapshot = true
+
+	// Check recovery snapshot output.
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var includeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))
+
+		// TODO: When Rust supports the "prefer" property for prebuilts, perform this check.
+		/*
+			// Check that snapshot captures "prefer: true" prebuilt
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo_rlib", "libfoo_rlib.rlib", rlibDir, rlibVariant)
+			includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo_rlib.rlib.json"))
+		*/
+
+		// Excluded modules. Modules not included in the directed recovery snapshot
+		// are still included as fake modules.
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery_available.so.json"))
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found, %#v", jsonFile, includeJsonFiles)
+		}
+	}
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 635be10..4c847a1 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -1,5 +1,6 @@
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
+    default_visibility: ["//build/soong:__subpackages__"],
 }
 
 python_binary_host {
@@ -8,14 +9,6 @@
     srcs: [
         "check_boot_jars/check_boot_jars.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -24,14 +17,6 @@
     srcs: [
         "manifest_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -45,11 +30,8 @@
         "manifest_fixer.py",
     ],
     version: {
-        py2: {
-            enabled: true,
-        },
         py3: {
-            enabled: false,
+            embedded_launcher: true,
         },
     },
     libs: [
@@ -65,14 +47,7 @@
     srcs: [
         "manifest.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
+    visibility: ["//system/apex/apexer:__pkg__"],
 }
 
 python_binary_host {
@@ -81,14 +56,6 @@
     srcs: [
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -101,14 +68,6 @@
         "manifest_check_test.py",
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -123,14 +82,6 @@
     srcs: [
         "jsonmodify.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -139,14 +90,6 @@
     srcs: [
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -159,14 +102,6 @@
         "test_config_fixer_test.py",
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -179,14 +114,6 @@
     srcs: [
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -199,14 +126,6 @@
         "construct_context_test.py",
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -253,11 +172,7 @@
         "conv_linker_config.py",
     ],
     version: {
-        py2: {
-            enabled: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },
@@ -272,12 +187,4 @@
     srcs: [
         "get_clang_version.py",
     ],
-    version: {
-        py2: {
-            enabled: false,
-        },
-        py3: {
-            enabled: true,
-        },
-    },
 }
diff --git a/scripts/OWNERS b/scripts/OWNERS
index 1830a18..3f4f9c0 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,6 +1,5 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
-per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file construct_context.py = ngeoffray@google.com,calin@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
-per-file gen_ndk*.sh = sophiez@google.com, allenhair@google.com
+per-file gen_ndk*.sh,gen_java*.sh = sophiez@google.com, allenhair@google.com
\ No newline at end of file
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index 8a5513e..1a16f7c 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -23,10 +23,10 @@
 export OUT_DIR=${OUT_DIR:-out}
 
 if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
-  # If ${OUT_DIR} has been created without --skip-make, Soong will create an
+  # If ${OUT_DIR} has been created without --soong-only, Soong will create an
   # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
   # expected to be supplied by the .mk files, and that might cause errors in
-  # "m --skip-make" below. We therefore default to a different out dir
+  # "m --soong-only" below. We therefore default to a different out dir
   # location in that case.
   AML_OUT_DIR=out/aml
   echo "Avoiding in-make OUT_DIR '${OUT_DIR}' - building in '${AML_OUT_DIR}' instead"
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
deleted file mode 100755
index b05861b..0000000
--- a/scripts/build-mainline-modules.sh
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/bin/bash -e
-
-# Non exhaustive list of modules where we want prebuilts. More can be added as
-# needed.
-MAINLINE_MODULES=(
-  com.android.art
-  com.android.art.debug
-  com.android.art.testing
-  com.android.conscrypt
-  com.android.i18n
-  com.android.os.statsd
-  com.android.runtime
-  com.android.tzdata
-)
-
-# List of SDKs and module exports we know of.
-MODULES_SDK_AND_EXPORTS=(
-  art-module-sdk
-  art-module-test-exports
-  conscrypt-module-host-exports
-  conscrypt-module-sdk
-  conscrypt-module-test-exports
-  i18n-module-host-exports
-  i18n-module-sdk
-  i18n-module-test-exports
-  platform-mainline-sdk
-  platform-mainline-test-exports
-  runtime-module-host-exports
-  runtime-module-sdk
-  statsd-module-sdk
-  tzdata-module-test-exports
-)
-
-# List of libraries installed on the platform that are needed for ART chroot
-# testing.
-PLATFORM_LIBRARIES=(
-  heapprofd_client_api
-  libartpalette-system
-  liblog
-)
-
-# We want to create apex modules for all supported architectures.
-PRODUCTS=(
-  aosp_arm
-  aosp_arm64
-  aosp_x86
-  aosp_x86_64
-)
-
-if [ ! -e "build/make/core/Makefile" ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
-
-echo_and_run() {
-  echo "$*"
-  "$@"
-}
-
-lib_dir() {
-  case $1 in
-    (aosp_arm|aosp_x86) echo "lib";;
-    (aosp_arm64|aosp_x86_64) echo "lib64";;
-  esac
-}
-
-# Make sure this build builds from source, regardless of the default.
-export SOONG_CONFIG_art_module_source_build=true
-
-# This script does not intend to handle compressed APEX
-export OVERRIDE_PRODUCT_COMPRESSED_APEX=false
-
-OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
-DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
-
-for product in "${PRODUCTS[@]}"; do
-  echo_and_run build/soong/soong_ui.bash --make-mode $@ \
-    TARGET_PRODUCT=${product} \
-    ${MAINLINE_MODULES[@]} \
-    ${PLATFORM_LIBRARIES[@]}
-
-  PRODUCT_OUT=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var PRODUCT_OUT)
-  TARGET_ARCH=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var TARGET_ARCH)
-  rm -rf ${DIST_DIR}/${TARGET_ARCH}/
-  mkdir -p ${DIST_DIR}/${TARGET_ARCH}/
-  for module in "${MAINLINE_MODULES[@]}"; do
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/apex/${module}.apex ${DIST_DIR}/${TARGET_ARCH}/
-  done
-  for library in "${PLATFORM_LIBRARIES[@]}"; do
-    libdir=$(lib_dir $product)
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/${libdir}/${library}.so ${DIST_DIR}/${TARGET_ARCH}/
-  done
-done
-
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-# Create multi-archs SDKs in a different out directory. The multi-arch script
-# uses Soong in --skip-make mode which cannot use the same directory as normal
-# mode with make.
-export OUT_DIR=${OUT_DIR}/aml
-echo_and_run build/soong/scripts/build-aml-prebuilts.sh \
-  TARGET_PRODUCT=mainline_sdk ${MODULES_SDK_AND_EXPORTS[@]}
-
-rm -rf ${DIST_DIR}/mainline-sdks
-echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}
diff --git a/scripts/build-ndk-prebuilts.sh b/scripts/build-ndk-prebuilts.sh
index a1fa48d..4783037 100755
--- a/scripts/build-ndk-prebuilts.sh
+++ b/scripts/build-ndk-prebuilts.sh
@@ -64,7 +64,7 @@
     "MissingUsesLibraries": ${MISSING_USES_LIBRARIES}
 }
 EOF
-m --skip-make ${SOONG_OUT}/ndk.timestamp
+m --soong-only --skip-config ${SOONG_OUT}/ndk.timestamp
 
 if [ -n "${DIST_DIR}" ]; then
     mkdir -p ${DIST_DIR} || true
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index c271211..b711f9d 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -1,101 +1,102 @@
 #!/usr/bin/env python
+"""Check boot jars.
 
+Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> \
+<jar2> ...
 """
-Check boot jars.
-
-Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> <jar2> ...
-"""
+from __future__ import print_function
 import logging
-import os.path
 import re
 import subprocess
 import sys
 import xml.etree.ElementTree
 
-
 # The compiled allow list RE.
 allow_list_re = None
 
 
 def LoadAllowList(filename):
-  """ Load and compile allow list regular expressions from filename.
-  """
-  lines = []
-  with open(filename, 'r') as f:
-    for line in f:
-      line = line.strip()
-      if not line or line.startswith('#'):
-        continue
-      lines.append(line)
-  combined_re = r'^(%s)$' % '|'.join(lines)
-  global allow_list_re
-  try:
-    allow_list_re = re.compile(combined_re)
-  except re.error:
-    logging.exception(
-        'Cannot compile package allow list regular expression: %r',
-        combined_re)
-    allow_list_re = None
-    return False
-  return True
+    """ Load and compile allow list regular expressions from filename."""
+    lines = []
+    with open(filename, 'r') as f:
+        for line in f:
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+            lines.append(line)
+    combined_re = r'^(%s)$' % '|'.join(lines)
+    global allow_list_re #pylint: disable=global-statement
+    try:
+        allow_list_re = re.compile(combined_re)
+    except re.error:
+        logging.exception(
+            'Cannot compile package allow list regular expression: %r',
+            combined_re)
+        allow_list_re = None
+        return False
+    return True
 
 def CheckDexJar(dexdump_path, allow_list_path, jar):
-  """Check a dex jar file.
-  """
-  # Use dexdump to generate the XML representation of the dex jar file.
-  p = subprocess.Popen(args='%s -l xml %s' % (dexdump_path, jar),
-      stdout=subprocess.PIPE, shell=True)
-  stdout, _ = p.communicate()
-  if p.returncode != 0:
-    return False
+    """Check a dex jar file."""
+    # Use dexdump to generate the XML representation of the dex jar file.
+    p = subprocess.Popen(
+        args='%s -l xml %s' % (dexdump_path, jar),
+        stdout=subprocess.PIPE,
+        shell=True)
+    stdout, _ = p.communicate()
+    if p.returncode != 0:
+        return False
 
-  packages = 0
-  try:
-    # TODO(b/172063475) - improve performance
-    root = xml.etree.ElementTree.fromstring(stdout)
-  except xml.etree.ElementTree.ParseError as e:
-    print >> sys.stderr, 'Error processing jar %s - %s' % (jar, e)
-    print >> sys.stderr, stdout
-    return False
-  for package_elt in root.iterfind('package'):
-    packages += 1
-    package_name = package_elt.get('name')
-    if not package_name or not allow_list_re.match(package_name):
-      # Report the name of a class in the package as it is easier to navigate to
-      # the source of a concrete class than to a package which is often required
-      # to investigate this failure.
-      class_name = package_elt[0].get('name')
-      if package_name != "":
-        class_name = package_name + "." + class_name
-      print >> sys.stderr, ('Error: %s contains class file %s, whose package name "%s" is empty or'
-                            ' not in the allow list %s of packages allowed on the bootclasspath.'
-                            % (jar, class_name, package_name, allow_list_path))
-      return False
-  if packages == 0:
-    print >> sys.stderr, ('Error: %s does not contain any packages.' % jar)
-    return False
-  return True
-
+    packages = 0
+    try:
+        # TODO(b/172063475) - improve performance
+        root = xml.etree.ElementTree.fromstring(stdout)
+    except xml.etree.ElementTree.ParseError as e:
+        print('Error processing jar %s - %s' % (jar, e), file=sys.stderr)
+        print(stdout, file=sys.stderr)
+        return False
+    for package_elt in root.iterfind('package'):
+        packages += 1
+        package_name = package_elt.get('name')
+        if not package_name or not allow_list_re.match(package_name):
+            # Report the name of a class in the package as it is easier to
+            # navigate to the source of a concrete class than to a package
+            # which is often required to investigate this failure.
+            class_name = package_elt[0].get('name')
+            if package_name:
+                class_name = package_name + '.' + class_name
+            print((
+                'Error: %s contains class file %s, whose package name "%s" is '
+                'empty or not in the allow list %s of packages allowed on the '
+                'bootclasspath.'
+                % (jar, class_name, package_name, allow_list_path)),
+                  file=sys.stderr)
+            return False
+    if packages == 0:
+        print(('Error: %s does not contain any packages.' % jar),
+              file=sys.stderr)
+        return False
+    return True
 
 def main(argv):
-  if len(argv) < 3:
-    print __doc__
-    return 1
-  dexdump_path = argv[0]
-  allow_list_path = argv[1]
+    if len(argv) < 3:
+        print(__doc__)
+        return 1
+    dexdump_path = argv[0]
+    allow_list_path = argv[1]
 
-  if not LoadAllowList(allow_list_path):
-    return 1
+    if not LoadAllowList(allow_list_path):
+        return 1
 
-  passed = True
-  for jar in argv[2:]:
-    if not CheckDexJar(dexdump_path, allow_list_path, jar):
-      passed = False
-  if not passed:
-    return 1
+    passed = True
+    for jar in argv[2:]:
+        if not CheckDexJar(dexdump_path, allow_list_path, jar):
+            passed = False
+    if not passed:
+        return 1
 
-  return 0
+    return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index 942f26a..a02c195 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -69,7 +69,11 @@
 javax\.xml\.transform\.stream
 javax\.xml\.validation
 javax\.xml\.xpath
+jdk\.internal
 jdk\.internal\.math
+jdk\.internal\.misc
+jdk\.internal\.ref
+jdk\.internal\.reflect
 jdk\.internal\.util
 jdk\.internal\.vm\.annotation
 jdk\.net
diff --git a/scripts/diff_build_graphs.sh b/scripts/diff_build_graphs.sh
index 81010f3..8d01124 100755
--- a/scripts/diff_build_graphs.sh
+++ b/scripts/diff_build_graphs.sh
@@ -98,7 +98,7 @@
   # or in case it is affected by some of the changes we're testing
   make blueprint_tools
   # find multiproduct_kati and have it build the ninja files for each product
-  builder="$(echo $OUT_DIR/soong/host/*/bin/multiproduct_kati)"
+  builder="$(echo $OUT_DIR/host/*/bin/multiproduct_kati)"
   BUILD_NUMBER=sample "$builder" $PRODUCTS_ARG --keep --out "$OUT_DIR_TEMP" || true
   echo
 }
diff --git a/scripts/gen_java_usedby_apex.sh b/scripts/gen_java_usedby_apex.sh
new file mode 100755
index 0000000..e398541
--- /dev/null
+++ b/scripts/gen_java_usedby_apex.sh
@@ -0,0 +1,48 @@
+#!/bin/bash -e
+
+# Copyright 2020 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.
+
+printHelp() {
+    echo "**************************** Usage Instructions ****************************"
+    echo "This script is used to generate the Mainline modules used-by Java symbols."
+    echo ""
+    echo "To run this script use: ./gen_java_usedby_apex.sh \$BINARY_DEXDEPS_PATH \$OUTPUT_FILE_PATH \$JAR_AND_APK_LIST"
+    echo "For example: If all jar and apk files are '/myJar.jar /myApk.apk' and output write to /myModule.txt then the command would be:"
+    echo "./gen_java_usedby_apex.sh \$BINARY_DEXDEPS_PATH /myModule.txt /myJar.jar /myApk.apk"
+}
+
+genUsedByList() {
+  dexdeps="$1"
+  shift
+  out="$1"
+  shift
+  rm -f "$out"
+  touch "$out"
+  echo "<externals>" >> "$out"
+  for x in "$@"; do
+    "$dexdeps" "$x" >> "$out" || echo "</external>" >> "$out"
+  done
+  echo "</externals>" >> "$out"
+}
+
+if [[ "$1" == "help" ]]
+then
+  printHelp
+elif [[ "$#" -lt 2 ]]
+then
+  echo "Wrong argument length. Expecting at least 2 argument representing dexdeps path, output path, followed by a list of jar or apk files in the Mainline module."
+else
+  genUsedByList "$@"
+fi
\ No newline at end of file
diff --git a/scripts/gen_ndk_backedby_apex.sh b/scripts/gen_ndk_backedby_apex.sh
index 4abaaba..212362e 100755
--- a/scripts/gen_ndk_backedby_apex.sh
+++ b/scripts/gen_ndk_backedby_apex.sh
@@ -21,52 +21,29 @@
 # After the parse function below "dlopen" would be write to the output file.
 printHelp() {
     echo "**************************** Usage Instructions ****************************"
-    echo "This script is used to generate the Mainline modules backed-by NDK symbols."
+    echo "This script is used to generate the native libraries backed by Mainline modules."
     echo ""
-    echo "To run this script use: ./gen_ndk_backed_by_apex.sh \$OUTPUT_FILE_PATH \$NDK_LIB_NAME_LIST \$MODULE_LIB1 \$MODULE_LIB2..."
+    echo "To run this script use: ./gen_ndk_backed_by_apex.sh \$OUTPUT_FILE_PATH \$MODULE_LIB1 \$MODULE_LIB2..."
     echo "For example: If output write to /backedby.txt then the command would be:"
-    echo "./gen_ndk_backed_by_apex.sh /backedby.txt /ndkLibList.txt lib1.so lib2.so"
+    echo "./gen_ndk_backed_by_apex.sh /backedby.txt lib1.so lib2.so"
     echo "If the module1 is backing lib1 then the backedby.txt would contains: "
-    echo "lib1"
+    echo "lib1.so lib2.so"
 }
 
-contains() {
-  val="$1"
-  shift
-  for x in "$@"; do
-    if [ "$x" = "$val" ]; then
-      return 0
-    fi
-  done
-  return 1
-}
-
-
-genBackedByList() {
+genAllBackedByList() {
   out="$1"
   shift
-  ndk_list="$1"
-  shift
   rm -f "$out"
   touch "$out"
-  while IFS= read -r line
-  do
-    soFileName=$(echo "$line" | sed 's/\(.*so\).*/\1/')
-    if [[ ! -z "$soFileName" && "$soFileName" != *"#"* ]]
-    then
-      if contains "$soFileName" "$@"; then
-        echo "$soFileName" >> "$out"
-      fi
-    fi
-  done < "$ndk_list"
+  echo "$@" >> "$out"
 }
 
 if [[ "$1" == "help" ]]
 then
   printHelp
-elif [[ "$#" -lt 2 ]]
+elif [[ "$#" -lt 1 ]]
 then
-  echo "Wrong argument length. Expecting at least 2 argument representing output path, path to ndk library list, followed by a list of libraries in the Mainline module."
+  echo "Wrong argument length. Expecting at least 1 argument representing output path, followed by a list of libraries in the Mainline module."
 else
-  genBackedByList "$@"
+  genAllBackedByList "$@"
 fi
diff --git a/scripts/generate-notice-files.py b/scripts/generate-notice-files.py
index 49011b2..1b4acfa 100755
--- a/scripts/generate-notice-files.py
+++ b/scripts/generate-notice-files.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2012 The Android Open Source Project
 #
@@ -30,20 +30,18 @@
 import os
 import os.path
 import re
+import struct
 import sys
 
 MD5_BLOCKSIZE = 1024 * 1024
 HTML_ESCAPE_TABLE = {
-    "&": "&amp;",
-    '"': "&quot;",
-    "'": "&apos;",
-    ">": "&gt;",
-    "<": "&lt;",
+    b"&": b"&amp;",
+    b'"': b"&quot;",
+    b"'": b"&apos;",
+    b">": b"&gt;",
+    b"<": b"&lt;",
     }
 
-def hexify(s):
-    return ("%02x"*len(s)) % tuple(map(ord, s))
-
 def md5sum(filename):
     """Calculate an MD5 of the file given by FILENAME,
     and return hex digest as a string.
@@ -57,20 +55,26 @@
             break
         sum.update(block)
     f.close()
-    return hexify(sum.digest())
+    return sum.hexdigest()
 
 
 def html_escape(text):
     """Produce entities within text."""
-    return "".join(HTML_ESCAPE_TABLE.get(c,c) for c in text)
+    # Using for i in text doesn't work since i will be an int, not a byte.
+    # There are multiple ways to solve this, but the most performant way
+    # to iterate over a byte array is to use unpack. Using the
+    # for i in range(len(text)) and using that to get a byte using array
+    # slices is twice as slow as this method.
+    return b"".join(HTML_ESCAPE_TABLE.get(i,i) for i in struct.unpack(str(len(text)) + 'c', text))
 
-HTML_OUTPUT_CSS="""
+HTML_OUTPUT_CSS=b"""
 <style type="text/css">
 body { padding: 0; font-family: sans-serif; }
 .same-license { background-color: #eeeeee; border-top: 20px solid white; padding: 10px; }
 .label { font-weight: bold; }
 .file-list { margin-left: 1em; color: blue; }
 </style>
+
 """
 
 def combine_notice_files_html(file_hash, input_dir, output_filename):
@@ -90,13 +94,13 @@
     # Open the output file, and output the header pieces
     output_file = open(output_filename, "wb")
 
-    print >> output_file, "<html><head>"
-    print >> output_file, HTML_OUTPUT_CSS
-    print >> output_file, '</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">'
+    output_file.write(b"<html><head>\n")
+    output_file.write(HTML_OUTPUT_CSS)
+    output_file.write(b'</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">\n')
 
     # Output our table of contents
-    print >> output_file, '<div class="toc">'
-    print >> output_file, "<ul>"
+    output_file.write(b'<div class="toc">\n')
+    output_file.write(b"<ul>\n")
 
     # Flatten the list of lists into a single list of filenames
     sorted_filenames = sorted(itertools.chain.from_iterable(file_hash))
@@ -104,31 +108,28 @@
     # Print out a nice table of contents
     for filename in sorted_filenames:
         stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<li><a href="#id%d">%s</a></li>' % (id_table.get(filename), stripped_filename)
+        output_file.write(('<li><a href="#id%d">%s</a></li>\n' % (id_table.get(filename), stripped_filename)).encode())
 
-    print >> output_file, "</ul>"
-    print >> output_file, "</div><!-- table of contents -->"
+    output_file.write(b"</ul>\n")
+    output_file.write(b"</div><!-- table of contents -->\n")
     # Output the individual notice file lists
-    print >>output_file, '<table cellpadding="0" cellspacing="0" border="0">'
+    output_file.write(b'<table cellpadding="0" cellspacing="0" border="0">\n')
     for value in file_hash:
-        print >> output_file, '<tr id="id%d"><td class="same-license">' % id_table.get(value[0])
-        print >> output_file, '<div class="label">Notices for file(s):</div>'
-        print >> output_file, '<div class="file-list">'
+        output_file.write(('<tr id="id%d"><td class="same-license">\n' % id_table.get(value[0])).encode())
+        output_file.write(b'<div class="label">Notices for file(s):</div>\n')
+        output_file.write(b'<div class="file-list">\n')
         for filename in value:
-            print >> output_file, "%s <br/>" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))
-        print >> output_file, "</div><!-- file-list -->"
-        print >> output_file
-        print >> output_file, '<pre class="license-text">'
-        print >> output_file, html_escape(open(value[0]).read())
-        print >> output_file, "</pre><!-- license-text -->"
-        print >> output_file, "</td></tr><!-- same-license -->"
-        print >> output_file
-        print >> output_file
-        print >> output_file
+            output_file.write(("%s <br/>\n" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))).encode())
+        output_file.write(b"</div><!-- file-list -->\n\n")
+        output_file.write(b'<pre class="license-text">\n')
+        with open(value[0], "rb") as notice_file:
+            output_file.write(html_escape(notice_file.read()))
+        output_file.write(b"\n</pre><!-- license-text -->\n")
+        output_file.write(b"</td></tr><!-- same-license -->\n\n\n\n")
 
     # Finish off the file output
-    print >> output_file, "</table>"
-    print >> output_file, "</body></html>"
+    output_file.write(b"</table>\n")
+    output_file.write(b"</body></html>\n")
     output_file.close()
 
 def combine_notice_files_text(file_hash, input_dir, output_filename, file_title):
@@ -136,14 +137,18 @@
 
     SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
     output_file = open(output_filename, "wb")
-    print >> output_file, file_title
+    output_file.write(file_title.encode())
+    output_file.write(b"\n")
     for value in file_hash:
-      print >> output_file, "============================================================"
-      print >> output_file, "Notices for file(s):"
-      for filename in value:
-        print >> output_file, SRC_DIR_STRIP_RE.sub(r"\1", filename)
-      print >> output_file, "------------------------------------------------------------"
-      print >> output_file, open(value[0]).read()
+        output_file.write(b"============================================================\n")
+        output_file.write(b"Notices for file(s):\n")
+        for filename in value:
+            output_file.write(SRC_DIR_STRIP_RE.sub(r"\1", filename).encode())
+            output_file.write(b"\n")
+        output_file.write(b"------------------------------------------------------------\n")
+        with open(value[0], "rb") as notice_file:
+            output_file.write(notice_file.read())
+            output_file.write(b"\n")
     output_file.close()
 
 def combine_notice_files_xml(files_with_same_hash, input_dir, output_filename):
@@ -154,26 +159,24 @@
     # Set up a filename to row id table (anchors inside tables don't work in
     # most browsers, but href's to table row ids do)
     id_table = {}
-    for file_key in files_with_same_hash.keys():
-        for filename in files_with_same_hash[file_key]:
+    for file_key, files in files_with_same_hash.items():
+        for filename in files:
              id_table[filename] = file_key
 
     # Open the output file, and output the header pieces
     output_file = open(output_filename, "wb")
 
-    print >> output_file, '<?xml version="1.0" encoding="utf-8"?>'
-    print >> output_file, "<licenses>"
+    output_file.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
+    output_file.write(b"<licenses>\n")
 
     # Flatten the list of lists into a single list of filenames
-    sorted_filenames = sorted(id_table.keys())
+    sorted_filenames = sorted(list(id_table))
 
     # Print out a nice table of contents
     for filename in sorted_filenames:
         stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<file-name contentId="%s">%s</file-name>' % (id_table.get(filename), stripped_filename)
-
-    print >> output_file
-    print >> output_file
+        output_file.write(('<file-name contentId="%s">%s</file-name>\n' % (id_table.get(filename), stripped_filename)).encode())
+    output_file.write(b"\n\n")
 
     processed_file_keys = []
     # Output the individual notice file lists
@@ -183,11 +186,13 @@
             continue
         processed_file_keys.append(file_key)
 
-        print >> output_file, '<file-content contentId="%s"><![CDATA[%s]]></file-content>' % (file_key, html_escape(open(filename).read()))
-        print >> output_file
+        output_file.write(('<file-content contentId="%s"><![CDATA[' % file_key).encode())
+        with open(filename, "rb") as notice_file:
+            output_file.write(html_escape(notice_file.read()))
+        output_file.write(b"]]></file-content>\n\n")
 
     # Finish off the file output
-    print >> output_file, "</licenses>"
+    output_file.write(b"</licenses>\n")
     output_file.close()
 
 def get_args():
@@ -253,7 +258,7 @@
                 file_md5sum = md5sum(filename)
                 files_with_same_hash[file_md5sum].append(filename)
 
-    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(files_with_same_hash.keys())]
+    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(list(files_with_same_hash))]
 
     combine_notice_files_text(filesets, input_dir, txt_output_file, file_title)
 
diff --git a/scripts/get_clang_version.py b/scripts/get_clang_version.py
index 17bc88b..691c45d 100755
--- a/scripts/get_clang_version.py
+++ b/scripts/get_clang_version.py
@@ -21,8 +21,12 @@
 
 
 ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".")
+LLVM_PREBUILTS_VERSION = os.environ.get("LLVM_PREBUILTS_VERSION")
 
 def get_clang_prebuilts_version(global_go):
+  if LLVM_PREBUILTS_VERSION:
+    return LLVM_PREBUILTS_VERSION
+
   # TODO(b/187231324): Get clang version from the json file once it is no longer
   # hard-coded in global.go
   if global_go is None:
@@ -30,7 +34,7 @@
   with open(global_go) as infile:
     contents = infile.read()
 
-  regex_rev = r'\tClangDefaultVersion\s+= "(?P<rev>clang-r\d+[a-z]?\d?)"'
+  regex_rev = r'\tClangDefaultVersion\s+= "(?P<rev>clang-.*)"'
   match_rev = re.search(regex_rev, contents)
   if match_rev is None:
     raise RuntimeError('Parsing clang info failed')
diff --git a/scripts/hiddenapi/signature_patterns.py b/scripts/hiddenapi/signature_patterns.py
index 0acb2a0..e75ee95 100755
--- a/scripts/hiddenapi/signature_patterns.py
+++ b/scripts/hiddenapi/signature_patterns.py
@@ -13,8 +13,10 @@
 # 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.
-"""Generate a set of signature patterns from the modular flags generated by a
-bootclasspath_fragment that can be used to select a subset of monolithic flags
+"""Generate a set of signature patterns for a bootclasspath_fragment.
+
+The patterns are generated from the modular flags produced by the
+bootclasspath_fragment and are used to select a subset of the monolithic flags
 against which the modular flags can be compared.
 """
 
@@ -22,33 +24,122 @@
 import csv
 import sys
 
+
 def dict_reader(csvfile):
     return csv.DictReader(
-        csvfile, delimiter=',', quotechar='|', fieldnames=['signature']
-    )
+        csvfile, delimiter=',', quotechar='|', fieldnames=['signature'])
 
 
-def produce_patterns_from_file(file):
+def dotPackageToSlashPackage(pkg):
+    return pkg.replace('.', '/')
+
+
+def slashPackageToDotPackage(pkg):
+    return pkg.replace('/', '.')
+
+
+def isSplitPackage(splitPackages, pkg):
+    return splitPackages and (pkg in splitPackages or '*' in splitPackages)
+
+
+def matchedByPackagePrefixPattern(packagePrefixes, prefix):
+    for packagePrefix in packagePrefixes:
+        if prefix == packagePrefix:
+            return packagePrefix
+        elif prefix.startswith(packagePrefix) and prefix[len(
+                packagePrefix)] == '/':
+            return packagePrefix
+    return False
+
+
+def validate_package_prefixes(splitPackages, packagePrefixes):
+    # If there are no package prefixes then there is no possible conflict
+    # between them and the split packages.
+    if len(packagePrefixes) == 0:
+        return
+
+    # Check to make sure that the split packages and package prefixes do not
+    # overlap.
+    errors = []
+    for splitPackage in splitPackages:
+        if splitPackage == '*':
+            # A package prefix matches a split package.
+            packagePrefixesForOutput = ', '.join(
+                map(slashPackageToDotPackage, packagePrefixes))
+            errors.append(
+                'split package "*" conflicts with all package prefixes %s\n'
+                '    add split_packages:[] to fix' % packagePrefixesForOutput)
+        else:
+            packagePrefix = matchedByPackagePrefixPattern(
+                packagePrefixes, splitPackage)
+            if packagePrefix:
+                # A package prefix matches a split package.
+                splitPackageForOutput = slashPackageToDotPackage(splitPackage)
+                packagePrefixForOutput = slashPackageToDotPackage(packagePrefix)
+                errors.append(
+                    'split package %s is matched by package prefix %s' %
+                    (splitPackageForOutput, packagePrefixForOutput))
+    return errors
+
+
+def validate_split_packages(splitPackages):
+    errors = []
+    if '*' in splitPackages and len(splitPackages) > 1:
+        errors.append('split packages are invalid as they contain both the'
+                      ' wildcard (*) and specific packages, use the wildcard or'
+                      ' specific packages, not a mixture')
+    return errors
+
+
+def produce_patterns_from_file(file, splitPackages=None, packagePrefixes=None):
     with open(file, 'r') as f:
-        return produce_patterns_from_stream(f)
+        return produce_patterns_from_stream(f, splitPackages, packagePrefixes)
 
 
-def produce_patterns_from_stream(stream):
-    # Read in all the signatures into a list and remove member names.
+def produce_patterns_from_stream(stream,
+                                 splitPackages=None,
+                                 packagePrefixes=None):
+    splitPackages = set(splitPackages or [])
+    packagePrefixes = list(packagePrefixes or [])
+    # Read in all the signatures into a list and remove any unnecessary class
+    # and member names.
     patterns = set()
     for row in dict_reader(stream):
         signature = row['signature']
-        text = signature.removeprefix("L")
+        text = signature.removeprefix('L')
         # Remove the class specific member signature
-        pieces = text.split(";->")
+        pieces = text.split(';->')
         qualifiedClassName = pieces[0]
-        # Remove inner class names as they cannot be separated
-        # from the containing outer class.
-        pieces = qualifiedClassName.split("$", maxsplit=1)
-        pattern = pieces[0]
+        pieces = qualifiedClassName.rsplit('/', maxsplit=1)
+        pkg = pieces[0]
+        # If the package is split across multiple modules then it cannot be used
+        # to select the subset of the monolithic flags that this module
+        # produces. In that case we need to keep the name of the class but can
+        # discard any nested class names as an outer class cannot be split
+        # across modules.
+        #
+        # If the package is not split then every class in the package must be
+        # provided by this module so there is no need to list the classes
+        # explicitly so just use the package name instead.
+        if isSplitPackage(splitPackages, pkg):
+            # Remove inner class names.
+            pieces = qualifiedClassName.split('$', maxsplit=1)
+            pattern = pieces[0]
+        else:
+            # Add a * to ensure that the pattern matches the classes in that
+            # package.
+            pattern = pkg + '/*'
         patterns.add(pattern)
 
-    patterns = list(patterns) #pylint: disable=redefined-variable-type
+    # Remove any patterns that would be matched by a package prefix pattern.
+    patterns = list(
+        filter(lambda p: not matchedByPackagePrefixPattern(packagePrefixes, p),
+               patterns))
+    # Add the package prefix patterns to the list. Add a ** to ensure that each
+    # package prefix pattern will match the classes in that package and all
+    # sub-packages.
+    patterns = patterns + list(map(lambda x: x + '/**', packagePrefixes))
+    # Sort the patterns.
     patterns.sort()
     return patterns
 
@@ -56,24 +147,47 @@
 def main(args):
     args_parser = argparse.ArgumentParser(
         description='Generate a set of signature patterns '
-        'that select a subset of monolithic hidden API files.'
-    )
+        'that select a subset of monolithic hidden API files.')
     args_parser.add_argument(
         '--flags',
         help='The stub flags file which contains an entry for every dex member',
     )
+    args_parser.add_argument(
+        '--split-package',
+        action='append',
+        help='A package that is split across multiple bootclasspath_fragment modules'
+    )
+    args_parser.add_argument(
+        '--package-prefix',
+        action='append',
+        help='A package prefix unique to this set of flags')
     args_parser.add_argument('--output', help='Generated signature prefixes')
     args = args_parser.parse_args(args)
 
+    splitPackages = set(map(dotPackageToSlashPackage, args.split_package or []))
+    errors = validate_split_packages(splitPackages)
+
+    packagePrefixes = list(
+        map(dotPackageToSlashPackage, args.package_prefix or []))
+
+    if not errors:
+        errors = validate_package_prefixes(splitPackages, packagePrefixes)
+
+    if errors:
+        for error in errors:
+            print(error)
+        sys.exit(1)
+
     # Read in all the patterns into a list.
-    patterns = produce_patterns_from_file(args.flags)
+    patterns = produce_patterns_from_file(args.flags, splitPackages,
+                                          packagePrefixes)
 
     # Write out all the patterns.
     with open(args.output, 'w') as outputFile:
         for pattern in patterns:
             outputFile.write(pattern)
-            outputFile.write("\n")
+            outputFile.write('\n')
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     main(sys.argv[1:])
diff --git a/scripts/hiddenapi/signature_patterns_test.py b/scripts/hiddenapi/signature_patterns_test.py
index 3babe54..b59dfd7 100755
--- a/scripts/hiddenapi/signature_patterns_test.py
+++ b/scripts/hiddenapi/signature_patterns_test.py
@@ -13,37 +13,99 @@
 # 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.
-
 """Unit tests for signature_patterns.py."""
 import io
 import unittest
 
-from signature_patterns import * #pylint: disable=unused-wildcard-import,wildcard-import
+from signature_patterns import *  #pylint: disable=unused-wildcard-import,wildcard-import
 
 
 class TestGeneratedPatterns(unittest.TestCase):
-    def produce_patterns_from_string(self, csvdata):
-        with io.StringIO(csvdata) as f:
-            return produce_patterns_from_stream(f)
 
-    def test_generate(self):
-        #pylint: disable=line-too-long
-        patterns = self.produce_patterns_from_string(
-            '''
+    csvFlags = """
 Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked
 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,public-api
 Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
 Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
-'''
-        )
-        #pylint: enable=line-too-long
+"""
+
+    def produce_patterns_from_string(self,
+                                     csv,
+                                     splitPackages=None,
+                                     packagePrefixes=None):
+        with io.StringIO(csv) as f:
+            return produce_patterns_from_stream(f, splitPackages,
+                                                packagePrefixes)
+
+    def test_generate_default(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags)
         expected = [
-            "java/lang/Character",
-            "java/lang/Object",
-            "java/lang/ProcessBuilder",
+            'java/lang/*',
         ]
         self.assertEqual(expected, patterns)
 
+    def test_generate_split_package(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, splitPackages={'java/lang'})
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_split_package_wildcard(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, splitPackages={'*'})
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, packagePrefixes={'java/lang'})
+        expected = [
+            'java/lang/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix_top_package(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, packagePrefixes={'java'})
+        expected = [
+            'java/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_split_package_wildcard_conflicts_with_other_split_packages(self):
+        errors = validate_split_packages({'*', 'java'})
+        expected = [
+            'split packages are invalid as they contain both the wildcard (*)'
+            ' and specific packages, use the wildcard or specific packages,'
+            ' not a mixture'
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_wildcard_conflicts_with_package_prefixes(self):
+        errors = validate_package_prefixes({'*'}, packagePrefixes={'java'})
+        expected = [
+            'split package "*" conflicts with all package prefixes java\n'
+            '    add split_packages:[] to fix',
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_conflict(self):
+        errors = validate_package_prefixes({'java/split'},
+                                           packagePrefixes={'java'})
+        expected = [
+            'split package java.split is matched by package prefix java',
+        ]
+        self.assertEqual(expected, errors)
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/scripts/manifest.py b/scripts/manifest.py
index 04f7405..81f9c61 100755
--- a/scripts/manifest.py
+++ b/scripts/manifest.py
@@ -123,4 +123,4 @@
 def write_xml(f, doc):
   f.write('<?xml version="1.0" encoding="utf-8"?>\n')
   for node in doc.childNodes:
-    f.write(node.toxml(encoding='utf-8') + '\n')
+    f.write(node.toxml() + '\n')
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 4ef4399..c8d4f76 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -25,7 +25,6 @@
 import sys
 from xml.dom import minidom
 
-
 from manifest import android_ns
 from manifest import get_children_with_tag
 from manifest import parse_manifest
@@ -33,49 +32,68 @@
 
 
 class ManifestMismatchError(Exception):
-  pass
+    pass
 
 
 def parse_args():
-  """Parse commandline arguments."""
+    """Parse commandline arguments."""
 
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--uses-library', dest='uses_libraries',
-                      action='append',
-                      help='specify uses-library entries known to the build system')
-  parser.add_argument('--optional-uses-library',
-                      dest='optional_uses_libraries',
-                      action='append',
-                      help='specify uses-library entries known to the build system with required:false')
-  parser.add_argument('--enforce-uses-libraries',
-                      dest='enforce_uses_libraries',
-                      action='store_true',
-                      help='check the uses-library entries known to the build system against the manifest')
-  parser.add_argument('--enforce-uses-libraries-relax',
-                      dest='enforce_uses_libraries_relax',
-                      action='store_true',
-                      help='do not fail immediately, just save the error message to file')
-  parser.add_argument('--enforce-uses-libraries-status',
-                      dest='enforce_uses_libraries_status',
-                      help='output file to store check status (error message)')
-  parser.add_argument('--extract-target-sdk-version',
-                      dest='extract_target_sdk_version',
-                      action='store_true',
-                      help='print the targetSdkVersion from the manifest')
-  parser.add_argument('--dexpreopt-config',
-                      dest='dexpreopt_configs',
-                      action='append',
-                      help='a paths to a dexpreopt.config of some library')
-  parser.add_argument('--aapt',
-                      dest='aapt',
-                      help='path to aapt executable')
-  parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file')
-  parser.add_argument('input', help='input AndroidManifest.xml file')
-  return parser.parse_args()
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--uses-library',
+        dest='uses_libraries',
+        action='append',
+        help='specify uses-library entries known to the build system')
+    parser.add_argument(
+        '--optional-uses-library',
+        dest='optional_uses_libraries',
+        action='append',
+        help='specify uses-library entries known to the build system with '
+        'required:false'
+    )
+    parser.add_argument(
+        '--enforce-uses-libraries',
+        dest='enforce_uses_libraries',
+        action='store_true',
+        help='check the uses-library entries known to the build system against '
+        'the manifest'
+    )
+    parser.add_argument(
+        '--enforce-uses-libraries-relax',
+        dest='enforce_uses_libraries_relax',
+        action='store_true',
+        help='do not fail immediately, just save the error message to file')
+    parser.add_argument(
+        '--enforce-uses-libraries-status',
+        dest='enforce_uses_libraries_status',
+        help='output file to store check status (error message)')
+    parser.add_argument(
+        '--extract-target-sdk-version',
+        dest='extract_target_sdk_version',
+        action='store_true',
+        help='print the targetSdkVersion from the manifest')
+    parser.add_argument(
+        '--dexpreopt-config',
+        dest='dexpreopt_configs',
+        action='append',
+        help='a paths to a dexpreopt.config of some library')
+    parser.add_argument('--aapt', dest='aapt', help='path to aapt executable')
+    parser.add_argument(
+        '--output', '-o', dest='output', help='output AndroidManifest.xml file')
+    parser.add_argument('input', help='input AndroidManifest.xml file')
+    return parser.parse_args()
+
+
+C_RED = "\033[1;31m"
+C_GREEN = "\033[1;32m"
+C_BLUE = "\033[1;34m"
+C_OFF = "\033[0m"
+C_BOLD = "\033[1m"
 
 
 def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path):
-  """Verify that the <uses-library> tags in the manifest match those provided
+    """Verify that the <uses-library> tags in the manifest match those provided
+
   by the build system.
 
   Args:
@@ -84,274 +102,293 @@
     optional: optional libs known to the build system
     relax:    if true, suppress error on mismatch and just write it to file
     is_apk:   if the manifest comes from an APK or an XML file
-  """
-  if is_apk:
-    manifest_required, manifest_optional, tags = extract_uses_libs_apk(manifest)
-  else:
-    manifest_required, manifest_optional, tags = extract_uses_libs_xml(manifest)
+    """
+    if is_apk:
+        manifest_required, manifest_optional, tags = extract_uses_libs_apk(
+            manifest)
+    else:
+        manifest_required, manifest_optional, tags = extract_uses_libs_xml(
+            manifest)
 
-  # Trim namespace component. Normally Soong does that automatically when it
-  # handles module names specified in Android.bp properties. However not all
-  # <uses-library> entries in the manifest correspond to real modules: some of
-  # the optional libraries may be missing at build time. Therefor this script
-  # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the
-  # optional namespace part manually.
-  required = trim_namespace_parts(required)
-  optional = trim_namespace_parts(optional)
+    # Trim namespace component. Normally Soong does that automatically when it
+    # handles module names specified in Android.bp properties. However not all
+    # <uses-library> entries in the manifest correspond to real modules: some of
+    # the optional libraries may be missing at build time. Therefor this script
+    # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the
+    # optional namespace part manually.
+    required = trim_namespace_parts(required)
+    optional = trim_namespace_parts(optional)
 
-  if manifest_required == required and manifest_optional == optional:
-    return None
+    if manifest_required == required and manifest_optional == optional:
+        return None
 
-  errmsg = ''.join([
-    'mismatch in the <uses-library> tags between the build system and the '
-      'manifest:\n',
-    '\t- required libraries in build system: [%s]\n' % ', '.join(required),
-    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_required),
-    '\t- optional libraries in build system: [%s]\n' % ', '.join(optional),
-    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_optional),
-    '\t- tags in the manifest (%s):\n' % path,
-    '\t\t%s\n' % '\t\t'.join(tags),
-      'note: the following options are available:\n',
-    '\t- to temporarily disable the check on command line, rebuild with ',
-      'RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" ',
-      'and disable AOT-compilation in dexpreopt)\n',
-    '\t- to temporarily disable the check for the whole product, set ',
-      'PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles\n',
-    '\t- to fix the check, make build system properties coherent with the '
-      'manifest\n',
-    '\t- see build/make/Changes.md for details\n'])
+    #pylint: disable=line-too-long
+    errmsg = ''.join([
+        'mismatch in the <uses-library> tags between the build system and the '
+        'manifest:\n',
+        '\t- required libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(required), C_OFF),
+        '\t                 vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_required), C_OFF),
+        '\t- optional libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(optional), C_OFF),
+        '\t                 vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_optional), C_OFF),
+        '\t- tags in the manifest (%s):\n' % path,
+        '\t\t%s\n' % '\t\t'.join(tags),
+        '%snote:%s the following options are available:\n' % (C_BLUE, C_OFF),
+        '\t- to temporarily disable the check on command line, rebuild with ',
+        '%sRELAX_USES_LIBRARY_CHECK=true%s' % (C_BOLD, C_OFF),
+        ' (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)\n',
+        '\t- to temporarily disable the check for the whole product, set ',
+        '%sPRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true%s in the product makefiles\n' % (C_BOLD, C_OFF),
+        '\t- to fix the check, make build system properties coherent with the manifest\n',
+        '\t- for details, see %sbuild/make/Changes.md%s' % (C_GREEN, C_OFF),
+        ' and %shttps://source.android.com/devices/tech/dalvik/art-class-loader-context%s\n' % (C_GREEN, C_OFF)
+    ])
+    #pylint: enable=line-too-long
 
-  if not relax:
-    raise ManifestMismatchError(errmsg)
+    if not relax:
+        raise ManifestMismatchError(errmsg)
 
-  return errmsg
+    return errmsg
 
 
-MODULE_NAMESPACE = re.compile("^//[^:]+:")
+MODULE_NAMESPACE = re.compile('^//[^:]+:')
+
 
 def trim_namespace_parts(modules):
-  """Trim the namespace part of each module, if present. Leave only the name."""
+    """Trim the namespace part of each module, if present.
 
-  trimmed = []
-  for module in modules:
-    trimmed.append(MODULE_NAMESPACE.sub('', module))
-  return trimmed
+    Leave only the name.
+    """
+
+    trimmed = []
+    for module in modules:
+        trimmed.append(MODULE_NAMESPACE.sub('', module))
+    return trimmed
 
 
 def extract_uses_libs_apk(badging):
-  """Extract <uses-library> tags from the manifest of an APK."""
+    """Extract <uses-library> tags from the manifest of an APK."""
 
-  pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
+    pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
 
-  required = []
-  optional = []
-  lines = []
-  for match in re.finditer(pattern, badging):
-    lines.append(match.group(0))
-    libname = match.group(2)
-    if match.group(1) == None:
-      required.append(libname)
-    else:
-      optional.append(libname)
+    required = []
+    optional = []
+    lines = []
+    for match in re.finditer(pattern, badging):
+        lines.append(match.group(0))
+        libname = match.group(2)
+        if match.group(1) is None:
+            required.append(libname)
+        else:
+            optional.append(libname)
 
-  required = first_unique_elements(required)
-  optional = first_unique_elements(optional)
-  tags = first_unique_elements(lines)
-  return required, optional, tags
+    required = first_unique_elements(required)
+    optional = first_unique_elements(optional)
+    tags = first_unique_elements(lines)
+    return required, optional, tags
 
 
-def extract_uses_libs_xml(xml):
-  """Extract <uses-library> tags from the manifest."""
+def extract_uses_libs_xml(xml): #pylint: disable=inconsistent-return-statements
+    """Extract <uses-library> tags from the manifest."""
 
-  manifest = parse_manifest(xml)
-  elems = get_children_with_tag(manifest, 'application')
-  application = elems[0] if len(elems) == 1 else None
-  if len(elems) > 1:
-    raise RuntimeError('found multiple <application> tags')
-  elif not elems:
-    if uses_libraries or optional_uses_libraries:
-      raise ManifestMismatchError('no <application> tag found')
-    return
+    manifest = parse_manifest(xml)
+    elems = get_children_with_tag(manifest, 'application')
+    application = elems[0] if len(elems) == 1 else None
+    if len(elems) > 1: #pylint: disable=no-else-raise
+        raise RuntimeError('found multiple <application> tags')
+    elif not elems:
+        if uses_libraries or optional_uses_libraries: #pylint: disable=undefined-variable
+            raise ManifestMismatchError('no <application> tag found')
+        return
 
-  libs = get_children_with_tag(application, 'uses-library')
+    libs = get_children_with_tag(application, 'uses-library')
 
-  required = [uses_library_name(x) for x in libs if uses_library_required(x)]
-  optional = [uses_library_name(x) for x in libs if not uses_library_required(x)]
+    required = [uses_library_name(x) for x in libs if uses_library_required(x)]
+    optional = [
+        uses_library_name(x) for x in libs if not uses_library_required(x)
+    ]
 
-  # render <uses-library> tags as XML for a pretty error message
-  tags = []
-  for lib in libs:
-    tags.append(lib.toprettyxml())
+    # render <uses-library> tags as XML for a pretty error message
+    tags = []
+    for lib in libs:
+        tags.append(lib.toprettyxml())
 
-  required = first_unique_elements(required)
-  optional = first_unique_elements(optional)
-  tags = first_unique_elements(tags)
-  return required, optional, tags
+    required = first_unique_elements(required)
+    optional = first_unique_elements(optional)
+    tags = first_unique_elements(tags)
+    return required, optional, tags
 
 
 def first_unique_elements(l):
-  result = []
-  [result.append(x) for x in l if x not in result]
-  return result
+    result = []
+    for x in l:
+        if x not in result:
+            result.append(x)
+    return result
 
 
 def uses_library_name(lib):
-  """Extract the name attribute of a uses-library tag.
+    """Extract the name attribute of a uses-library tag.
 
   Args:
     lib: a <uses-library> tag.
-  """
-  name = lib.getAttributeNodeNS(android_ns, 'name')
-  return name.value if name is not None else ""
+    """
+    name = lib.getAttributeNodeNS(android_ns, 'name')
+    return name.value if name is not None else ''
 
 
 def uses_library_required(lib):
-  """Extract the required attribute of a uses-library tag.
+    """Extract the required attribute of a uses-library tag.
 
   Args:
     lib: a <uses-library> tag.
-  """
-  required = lib.getAttributeNodeNS(android_ns, 'required')
-  return (required.value == 'true') if required is not None else True
+    """
+    required = lib.getAttributeNodeNS(android_ns, 'required')
+    return (required.value == 'true') if required is not None else True
 
 
-def extract_target_sdk_version(manifest, is_apk = False):
-  """Returns the targetSdkVersion from the manifest.
+def extract_target_sdk_version(manifest, is_apk=False):
+    """Returns the targetSdkVersion from the manifest.
 
   Args:
     manifest: manifest (either parsed XML or aapt dump of APK)
     is_apk:   if the manifest comes from an APK or an XML file
-  """
-  if is_apk:
-    return extract_target_sdk_version_apk(manifest)
-  else:
-    return extract_target_sdk_version_xml(manifest)
+    """
+    if is_apk: #pylint: disable=no-else-return
+        return extract_target_sdk_version_apk(manifest)
+    else:
+        return extract_target_sdk_version_xml(manifest)
 
 
 def extract_target_sdk_version_apk(badging):
-  """Extract targetSdkVersion tags from the manifest of an APK."""
+    """Extract targetSdkVersion tags from the manifest of an APK."""
 
-  pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
+    pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
 
-  for match in re.finditer(pattern, badging):
-    return match.group(1)
+    for match in re.finditer(pattern, badging):
+        return match.group(1)
 
-  raise RuntimeError('cannot find targetSdkVersion in the manifest')
+    raise RuntimeError('cannot find targetSdkVersion in the manifest')
 
 
 def extract_target_sdk_version_xml(xml):
-  """Extract targetSdkVersion tags from the manifest."""
+    """Extract targetSdkVersion tags from the manifest."""
 
-  manifest = parse_manifest(xml)
+    manifest = parse_manifest(xml)
 
-  # Get or insert the uses-sdk element
-  uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
-  if len(uses_sdk) > 1:
-    raise RuntimeError('found multiple uses-sdk elements')
-  elif len(uses_sdk) == 0:
-    raise RuntimeError('missing uses-sdk element')
+    # Get or insert the uses-sdk element
+    uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
+    if len(uses_sdk) > 1: #pylint: disable=no-else-raise
+        raise RuntimeError('found multiple uses-sdk elements')
+    elif len(uses_sdk) == 0:
+        raise RuntimeError('missing uses-sdk element')
 
-  uses_sdk = uses_sdk[0]
+    uses_sdk = uses_sdk[0]
 
-  min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
-  if min_attr is None:
-    raise RuntimeError('minSdkVersion is not specified')
+    min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
+    if min_attr is None:
+        raise RuntimeError('minSdkVersion is not specified')
 
-  target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
-  if target_attr is None:
-    target_attr = min_attr
+    target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
+    if target_attr is None:
+        target_attr = min_attr
 
-  return target_attr.value
+    return target_attr.value
 
 
 def load_dexpreopt_configs(configs):
-  """Load dexpreopt.config files and map module names to library names."""
-  module_to_libname = {}
+    """Load dexpreopt.config files and map module names to library names."""
+    module_to_libname = {}
 
-  if configs is None:
-    configs = []
+    if configs is None:
+        configs = []
 
-  for config in configs:
-    with open(config, 'r') as f:
-      contents = json.load(f)
-    module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
+    for config in configs:
+        with open(config, 'r') as f:
+            contents = json.load(f)
+        module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
 
-  return module_to_libname
+    return module_to_libname
 
 
 def translate_libnames(modules, module_to_libname):
-  """Translate module names into library names using the mapping."""
-  if modules is None:
-    modules = []
+    """Translate module names into library names using the mapping."""
+    if modules is None:
+        modules = []
 
-  libnames = []
-  for name in modules:
-    if name in module_to_libname:
-      name = module_to_libname[name]
-    libnames.append(name)
+    libnames = []
+    for name in modules:
+        if name in module_to_libname:
+            name = module_to_libname[name]
+        libnames.append(name)
 
-  return libnames
+    return libnames
 
 
 def main():
-  """Program entry point."""
-  try:
-    args = parse_args()
+    """Program entry point."""
+    try:
+        args = parse_args()
 
-    # The input can be either an XML manifest or an APK, they are parsed and
-    # processed in different ways.
-    is_apk = args.input.endswith('.apk')
-    if is_apk:
-      aapt = args.aapt if args.aapt != None else "aapt"
-      manifest = subprocess.check_output([aapt, "dump", "badging", args.input])
-    else:
-      manifest = minidom.parse(args.input)
+        # The input can be either an XML manifest or an APK, they are parsed and
+        # processed in different ways.
+        is_apk = args.input.endswith('.apk')
+        if is_apk:
+            aapt = args.aapt if args.aapt is not None else 'aapt'
+            manifest = subprocess.check_output(
+                [aapt, 'dump', 'badging', args.input]).decode('utf-8')
+        else:
+            manifest = minidom.parse(args.input)
 
-    if args.enforce_uses_libraries:
-      # Load dexpreopt.config files and build a mapping from module names to
-      # library names. This is necessary because build system addresses
-      # libraries by their module name (`uses_libs`, `optional_uses_libs`,
-      # `LOCAL_USES_LIBRARIES`, `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain
-      # module names), while the manifest addresses libraries by their name.
-      mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
-      required = translate_libnames(args.uses_libraries, mod_to_lib)
-      optional = translate_libnames(args.optional_uses_libraries, mod_to_lib)
+        if args.enforce_uses_libraries:
+            # Load dexpreopt.config files and build a mapping from module
+            # names to library names. This is necessary because build system
+            # addresses libraries by their module name (`uses_libs`,
+            # `optional_uses_libs`, `LOCAL_USES_LIBRARIES`,
+            # `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain module names), while
+            # the manifest addresses libraries by their name.
+            mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
+            required = translate_libnames(args.uses_libraries, mod_to_lib)
+            optional = translate_libnames(args.optional_uses_libraries,
+                                          mod_to_lib)
 
-      # Check if the <uses-library> lists in the build system agree with those
-      # in the manifest. Raise an exception on mismatch, unless the script was
-      # passed a special parameter to suppress exceptions.
-      errmsg = enforce_uses_libraries(manifest, required, optional,
-        args.enforce_uses_libraries_relax, is_apk, args.input)
+            # Check if the <uses-library> lists in the build system agree with
+            # those in the manifest. Raise an exception on mismatch, unless the
+            # script was passed a special parameter to suppress exceptions.
+            errmsg = enforce_uses_libraries(manifest, required, optional,
+                                            args.enforce_uses_libraries_relax,
+                                            is_apk, args.input)
 
-      # Create a status file that is empty on success, or contains an error
-      # message on failure. When exceptions are suppressed, dexpreopt command
-      # command will check file size to determine if the check has failed.
-      if args.enforce_uses_libraries_status:
-        with open(args.enforce_uses_libraries_status, 'w') as f:
-          if not errmsg == None:
-            f.write("%s\n" % errmsg)
+            # Create a status file that is empty on success, or contains an
+            # error message on failure. When exceptions are suppressed,
+            # dexpreopt command command will check file size to determine if
+            # the check has failed.
+            if args.enforce_uses_libraries_status:
+                with open(args.enforce_uses_libraries_status, 'w') as f:
+                    if errmsg is not None:
+                        f.write('%s\n' % errmsg)
 
-    if args.extract_target_sdk_version:
-      try:
-        print(extract_target_sdk_version(manifest, is_apk))
-      except:
-        # Failed; don't crash, return "any" SDK version. This will result in
-        # dexpreopt not adding any compatibility libraries.
-        print(10000)
+        if args.extract_target_sdk_version:
+            try:
+                print(extract_target_sdk_version(manifest, is_apk))
+            except: #pylint: disable=bare-except
+                # Failed; don't crash, return "any" SDK version. This will
+                # result in dexpreopt not adding any compatibility libraries.
+                print(10000)
 
-    if args.output:
-      # XML output is supposed to be written only when this script is invoked
-      # with XML input manifest, not with an APK.
-      if is_apk:
-        raise RuntimeError('cannot save APK manifest as XML')
+        if args.output:
+            # XML output is supposed to be written only when this script is
+            # invoked with XML input manifest, not with an APK.
+            if is_apk:
+                raise RuntimeError('cannot save APK manifest as XML')
 
-      with open(args.output, 'wb') as f:
-        write_xml(f, manifest)
+            with open(args.output, 'w') as f:
+                write_xml(f, manifest)
 
-  # pylint: disable=broad-except
-  except Exception as err:
-    print('error: ' + str(err), file=sys.stderr)
-    sys.exit(-1)
+    # pylint: disable=broad-except
+    except Exception as err:
+        print('%serror:%s ' % (C_RED, C_OFF) + str(err), file=sys.stderr)
+        sys.exit(-1)
+
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/scripts/manifest_check_test.py b/scripts/manifest_check_test.py
index e3e8ac4..3be7a30 100755
--- a/scripts/manifest_check_test.py
+++ b/scripts/manifest_check_test.py
@@ -26,202 +26,235 @@
 
 
 def uses_library_xml(name, attr=''):
-  return '<uses-library android:name="%s"%s />' % (name, attr)
+    return '<uses-library android:name="%s"%s />' % (name, attr)
 
 
 def required_xml(value):
-  return ' android:required="%s"' % ('true' if value else 'false')
+    return ' android:required="%s"' % ('true' if value else 'false')
 
 
 def uses_library_apk(name, sfx=''):
-  return "uses-library%s:'%s'" % (sfx, name)
+    return "uses-library%s:'%s'" % (sfx, name)
 
 
 def required_apk(value):
-  return '' if value else '-not-required'
+    return '' if value else '-not-required'
 
 
 class EnforceUsesLibrariesTest(unittest.TestCase):
-  """Unit tests for add_extract_native_libs function."""
+    """Unit tests for add_extract_native_libs function."""
 
-  def run_test(self, xml, apk, uses_libraries=[], optional_uses_libraries=[]):
-    doc = minidom.parseString(xml)
-    try:
-      relax = False
-      manifest_check.enforce_uses_libraries(doc, uses_libraries,
-        optional_uses_libraries, relax, False, 'path/to/X/AndroidManifest.xml')
-      manifest_check.enforce_uses_libraries(apk, uses_libraries,
-        optional_uses_libraries, relax, True, 'path/to/X/X.apk')
-      return True
-    except manifest_check.ManifestMismatchError:
-      return False
+    def run_test(self, xml, apk, uses_libraries=[], optional_uses_libraries=[]): #pylint: disable=dangerous-default-value
+        doc = minidom.parseString(xml)
+        try:
+            relax = False
+            manifest_check.enforce_uses_libraries(
+                doc, uses_libraries, optional_uses_libraries, relax, False,
+                'path/to/X/AndroidManifest.xml')
+            manifest_check.enforce_uses_libraries(apk, uses_libraries,
+                                                  optional_uses_libraries,
+                                                  relax, True,
+                                                  'path/to/X/X.apk')
+            return True
+        except manifest_check.ManifestMismatchError:
+            return False
 
-  xml_tmpl = (
-      '<?xml version="1.0" encoding="utf-8"?>\n'
-      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <application>\n'
-      '    %s\n'
-      '    </application>\n'
-      '</manifest>\n')
+    xml_tmpl = (
+        '<?xml version="1.0" encoding="utf-8"?>\n<manifest '
+        'xmlns:android="http://schemas.android.com/apk/res/android">\n    '
+        '<application>\n    %s\n    </application>\n</manifest>\n')
 
-  apk_tmpl = (
-      "package: name='com.google.android.something' versionCode='100'\n"
-      "sdkVersion:'29'\n"
-      "targetSdkVersion:'29'\n"
-      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n"
-      "%s\n"
-      "densities: '160' '240' '320' '480' '640' '65534")
+    apk_tmpl = (
+        "package: name='com.google.android.something' versionCode='100'\n"
+        "sdkVersion:'29'\n"
+        "targetSdkVersion:'29'\n"
+        "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n"
+        '%s\n'
+        "densities: '160' '240' '320' '480' '640' '65534")
 
-  def test_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_apk('foo'))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_apk('foo'))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_uses_library_required(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(True)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(True)))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_uses_library_required(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(True)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(True)))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_expected_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_expected_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_expected_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_apk('foo'))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_expected_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_apk('foo'))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_missing_uses_library(self):
-    xml = self.xml_tmpl % ('')
-    apk = self.apk_tmpl % ('')
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_missing_uses_library(self):
+        xml = self.xml_tmpl % ('')
+        apk = self.apk_tmpl % ('')
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_missing_optional_uses_library(self):
-    xml = self.xml_tmpl % ('')
-    apk = self.apk_tmpl % ('')
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_missing_optional_uses_library(self):
+        xml = self.xml_tmpl % ('')
+        apk = self.apk_tmpl % ('')
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_extra_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_xml('foo'))
-    matches = self.run_test(xml, apk)
-    self.assertFalse(matches)
+    def test_extra_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_xml('foo'))
+        matches = self.run_test(xml, apk)
+        self.assertFalse(matches)
 
-  def test_extra_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk)
-    self.assertFalse(matches)
+    def test_extra_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk)
+        self.assertFalse(matches)
 
-  def test_multiple_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar')]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo', 'bar'])
-    self.assertTrue(matches)
+    def test_multiple_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('bar')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('bar')]))
+        matches = self.run_test(xml, apk, uses_libraries=['foo', 'bar'])
+        self.assertTrue(matches)
 
-  def test_multiple_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo', 'bar'])
-    self.assertTrue(matches)
+    def test_multiple_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, optional_uses_libraries=['foo', 'bar'])
+        self.assertTrue(matches)
 
-  def test_order_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar')]))
-    matches = self.run_test(xml, apk, uses_libraries=['bar', 'foo'])
-    self.assertFalse(matches)
+    def test_order_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('bar')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('bar')]))
+        matches = self.run_test(xml, apk, uses_libraries=['bar', 'foo'])
+        self.assertFalse(matches)
 
-  def test_order_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['bar', 'foo'])
-    self.assertFalse(matches)
+    def test_order_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, optional_uses_libraries=['bar', 'foo'])
+        self.assertFalse(matches)
 
-  def test_duplicate_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('foo')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('foo')]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_duplicate_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('foo')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('foo')]))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_duplicate_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('foo', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('foo', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_duplicate_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('foo', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('foo', required_apk(False))
+        ]))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_mixed(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'],
-                            optional_uses_libraries=['bar'])
-    self.assertTrue(matches)
+    def test_mixed(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo'),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo'),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, uses_libraries=['foo'], optional_uses_libraries=['bar'])
+        self.assertTrue(matches)
 
-  def test_mixed_with_namespace(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, uses_libraries=['//x/y/z:foo'],
-                            optional_uses_libraries=['//x/y/z:bar'])
-    self.assertTrue(matches)
+    def test_mixed_with_namespace(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo'),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo'),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml,
+            apk,
+            uses_libraries=['//x/y/z:foo'],
+            optional_uses_libraries=['//x/y/z:bar'])
+        self.assertTrue(matches)
 
 
 class ExtractTargetSdkVersionTest(unittest.TestCase):
-  def run_test(self, xml, apk, version):
-    doc = minidom.parseString(xml)
-    v = manifest_check.extract_target_sdk_version(doc, is_apk=False)
-    self.assertEqual(v, version)
-    v = manifest_check.extract_target_sdk_version(apk, is_apk=True)
-    self.assertEqual(v, version)
 
-  xml_tmpl = (
-      '<?xml version="1.0" encoding="utf-8"?>\n'
-      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="%s" />\n'
-      '</manifest>\n')
+    def run_test(self, xml, apk, version):
+        doc = minidom.parseString(xml)
+        v = manifest_check.extract_target_sdk_version(doc, is_apk=False)
+        self.assertEqual(v, version)
+        v = manifest_check.extract_target_sdk_version(apk, is_apk=True)
+        self.assertEqual(v, version)
 
-  apk_tmpl = (
-      "package: name='com.google.android.something' versionCode='100'\n"
-      "sdkVersion:'28'\n"
-      "targetSdkVersion:'%s'\n"
-      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n")
+    xml_tmpl = (
+        '<?xml version="1.0" encoding="utf-8"?>\n<manifest '
+        'xmlns:android="http://schemas.android.com/apk/res/android">\n    '
+        '<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="%s" '
+        '/>\n</manifest>\n')
 
-  def test_targert_sdk_version_28(self):
-    xml = self.xml_tmpl % "28"
-    apk = self.apk_tmpl % "28"
-    self.run_test(xml, apk, "28")
+    apk_tmpl = (
+        "package: name='com.google.android.something' versionCode='100'\n"
+        "sdkVersion:'28'\n"
+        "targetSdkVersion:'%s'\n"
+        "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n")
 
-  def test_targert_sdk_version_29(self):
-    xml = self.xml_tmpl % "29"
-    apk = self.apk_tmpl % "29"
-    self.run_test(xml, apk, "29")
+    def test_targert_sdk_version_28(self):
+        xml = self.xml_tmpl % '28'
+        apk = self.apk_tmpl % '28'
+        self.run_test(xml, apk, '28')
+
+    def test_targert_sdk_version_29(self):
+        xml = self.xml_tmpl % '29'
+        apk = self.apk_tmpl % '29'
+        self.run_test(xml, apk, '29')
+
 
 if __name__ == '__main__':
-  unittest.main(verbosity=2)
+    unittest.main(verbosity=2)
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index 55d0fd1..d80a617 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -352,7 +352,7 @@
     if args.extract_native_libs is not None:
       add_extract_native_libs(doc, args.extract_native_libs)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
index 3a0a25d..f6fcaaf 100755
--- a/scripts/manifest_fixer_test.py
+++ b/scripts/manifest_fixer_test.py
@@ -16,16 +16,16 @@
 #
 """Unit tests for manifest_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
+import xml.etree.ElementTree as ET
 
 import manifest_fixer
 
 sys.dont_write_bytecode = True
 
-
 class CompareVersionGtTest(unittest.TestCase):
   """Unit tests for compare_version_gt function."""
 
@@ -59,7 +59,7 @@
     doc = minidom.parseString(input_manifest)
     manifest_fixer.raise_min_sdk_version(doc, min_sdk_version,
                                          target_sdk_version, library)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -80,13 +80,16 @@
       attrs += ' ' + extra
     return '    <uses-sdk%s/>\n' % (attrs)
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def test_no_uses_sdk(self):
     """Tests inserting a uses-sdk element into a manifest."""
 
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -95,7 +98,7 @@
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28',
                                                   extra='extra="foo"')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -103,7 +106,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise(self):
     """Tests raising a minSdkVersion attribute."""
@@ -111,7 +114,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_min(self):
     """Tests a minSdkVersion that doesn't need raising."""
@@ -119,7 +122,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '27', '27', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_codename(self):
     """Tests raising a minSdkVersion attribute to a codename."""
@@ -127,7 +130,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='P')
     output = self.raise_min_sdk_version_test(manifest_input, 'P', 'P', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_codename(self):
     """Tests a minSdkVersion codename that doesn't need raising."""
@@ -135,7 +138,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='P')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target(self):
     """Tests an existing targetSdkVersion is preserved."""
@@ -143,7 +146,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='26', target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -151,7 +154,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target_no_min(self):
     """"Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -159,7 +162,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -167,7 +170,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -175,7 +178,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -183,7 +186,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -191,7 +194,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_extra(self):
     """Tests that extra attributes and elements are maintained."""
@@ -204,12 +207,12 @@
     # pylint: disable=line-too-long
     expected = self.manifest_tmpl % (
         '    <!-- comment -->\n'
-        '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" extra="foo"/>\n'
+        '    <uses-sdk android:minSdkVersion="28" extra="foo" android:targetSdkVersion="29"/>\n'
         '    <application/>\n')
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_indent(self):
     """Tests that an inserted element copies the existing indentation."""
@@ -223,17 +226,20 @@
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddLoggingParentTest(unittest.TestCase):
   """Unit tests for add_logging_parent function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def add_logging_parent_test(self, input_manifest, logging_parent=None):
     doc = minidom.parseString(input_manifest)
     if logging_parent:
       manifest_fixer.add_logging_parent(doc, logging_parent)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -257,23 +263,26 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent()
     output = self.add_logging_parent_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_logging_parent(self):
     """Tests manifest_fixer with no logging_parent."""
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent('FOO')
     output = self.add_logging_parent_test(manifest_input, 'FOO')
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesLibrariesTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, new_uses_libraries):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_libraries(doc, new_uses_libraries, True)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -301,7 +310,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, [])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_not_overwrite(self):
     """new_uses_libraries must not overwrite existing tags."""
@@ -310,7 +319,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_add(self):
     """New names are added with 'required:true'."""
@@ -323,7 +332,7 @@
         ('baz', 'true'),
         ('qux', 'true')])
     output = self.run_test(manifest_input, ['bar', 'baz', 'qux'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_application(self):
     """When there is no <application> tag, the tag is added."""
@@ -336,7 +345,7 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_empty_application(self):
     """Even when here is an empty <application/> tag, the libs are added."""
@@ -350,16 +359,19 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesNonSdkApiTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_non_sdk_api(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -377,23 +389,26 @@
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(False)
     expected = self.manifest_tmpl % self.uses_non_sdk_api(True)
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_already_set(self):
     """new_uses_libraries must not overwrite existing tags."""
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(True)
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class UseEmbeddedDexTest(unittest.TestCase):
   """Unit tests for add_use_embedded_dex function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_use_embedded_dex(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -410,13 +425,13 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.use_embedded_dex('true')
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('true')
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_not_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('false')
@@ -426,10 +441,13 @@
 class AddExtractNativeLibsTest(unittest.TestCase):
   """Unit tests for add_extract_native_libs function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, value):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_extract_native_libs(doc, value)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -446,19 +464,19 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('true')
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_set_false(self):
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('false')
     output = self.run_test(manifest_input, False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_match(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
     expected = manifest_input
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_conflict(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
@@ -468,10 +486,13 @@
 class AddNoCodeApplicationTest(unittest.TestCase):
   """Unit tests for set_has_code_to_false function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.set_has_code_to_false(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -485,26 +506,26 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_no_has_code(self):
     manifest_input = self.manifest_tmpl % '    <application/>\n'
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_has_code_false(self):
     """ Do nothing if there's already an application elemeent. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
   def test_has_application_has_code_true(self):
     """ Do nothing if there's already an application elemeent even if its
      hasCode attribute is true. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="true"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
 
 if __name__ == '__main__':
diff --git a/scripts/package-check.sh b/scripts/package-check.sh
index d7e602f..9f4a9da 100755
--- a/scripts/package-check.sh
+++ b/scripts/package-check.sh
@@ -42,7 +42,7 @@
   fi
   # Transform to a slash-separated path and add a trailing slash to enforce
   # package name boundary.
-  prefixes+=("${package//\./\/}/")
+  prefixes+=("${package//\.//}/")
   shift
 done
 
diff --git a/scripts/rbc-run b/scripts/rbc-run
index e2fa6d1..7243421 100755
--- a/scripts/rbc-run
+++ b/scripts/rbc-run
@@ -1,16 +1,17 @@
 #! /bin/bash
 # Convert and run one configuration
-# Args: <product>-<variant>
-[[ $# -eq 1 && "$1" =~ ^(.*)-(.*)$ ]] || { echo Usage: ${0##*/} PRODUCT-VARIANT >&2; exit 1; }
-declare -r product="${BASH_REMATCH[1]:-aosp_arm}"
-declare -r variant="${BASH_REMATCH[2]:-eng}"
+# Args: a product/board makefile optionally followed by additional arguments
+#       that will be passed to rbcrun.
+[[ $# -gt 1 && -f "$1" && -f "$2" ]] || { echo "Usage: ${0##*/} product.mk input_variables.mk [Additional rbcrun arguments]" >&2; exit 1; }
 set -eu
-declare -r output_root=${OUT_DIR:-out}
-declare -r runner="$output_root/soong/.bootstrap/bin/rbcrun"
-declare -r converter="$output_root/soong/.bootstrap/bin/mk2rbc"
-declare -r launcher=$output_root/launchers/run.rbc
-$converter -mode=write -r --outdir $output_root --launcher=$launcher $product
-printf "#TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant\n"
-env TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant \
-  $runner RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $launcher
+
+declare -r output_root="${OUT_DIR:-out}"
+declare -r runner="${output_root}/soong/rbcrun"
+declare -r converter="${output_root}/soong/mk2rbc"
+declare -r launcher="${output_root}/rbc/launcher.rbc"
+declare -r makefile="$1"
+declare -r input_variables="$2"
+shift 2
+"${converter}" -mode=write -r --outdir "${output_root}/rbc" --input_variables "${input_variables}" --launcher="${launcher}" "${makefile}"
+"${runner}" RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $@ "${launcher}"
 
diff --git a/scripts/reverse-deps.sh b/scripts/reverse-deps.sh
index 02b7dcb..410f5c0 100755
--- a/scripts/reverse-deps.sh
+++ b/scripts/reverse-deps.sh
@@ -67,6 +67,9 @@
       $0 ~ /^\S\S*:$/ {
         inoutput = 0
       }
+      $1 == "validations:" {
+        inoutput = 0
+      }
       inoutput != 0 {
         print gensub(/^\s*/, "", "g")" "depth
       }
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 32d5b17..c150e8c 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -86,7 +86,7 @@
     if args.test_file_name:
       overwrite_test_file_name(doc, args.test_file_name)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/test_config_fixer_test.py b/scripts/test_config_fixer_test.py
index 1272c6b..d00a593 100644
--- a/scripts/test_config_fixer_test.py
+++ b/scripts/test_config_fixer_test.py
@@ -16,7 +16,7 @@
 #
 """Unit tests for test_config_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
@@ -59,7 +59,7 @@
     manifest = minidom.parseString(self.manifest)
 
     test_config_fixer.overwrite_package_name(doc, manifest, "com.soong.foo")
-    output = StringIO.StringIO()
+    output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.
@@ -86,7 +86,7 @@
     doc = minidom.parseString(self.test_config % ("foo.apk"))
 
     test_config_fixer.overwrite_test_file_name(doc, "bar.apk")
-    output = StringIO.StringIO()
+    output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.
diff --git a/scripts/transitive-deps.sh b/scripts/transitive-deps.sh
index ba36ba4..cf9fb94 100755
--- a/scripts/transitive-deps.sh
+++ b/scripts/transitive-deps.sh
@@ -100,7 +100,7 @@
         currFileName = ""
         currExt = ""
       }
-      $1 == "outputs:" {
+      $1 == "outputs:" || $1 == "validations:" {
         ininput = 0
       }
       ininput == 0 && $0 ~ /^\S\S*:$/ {
diff --git a/scripts/unpack-prebuilt-apex.sh b/scripts/unpack-prebuilt-apex.sh
index 1acdeb5..f34a480 100755
--- a/scripts/unpack-prebuilt-apex.sh
+++ b/scripts/unpack-prebuilt-apex.sh
@@ -29,8 +29,6 @@
 shift 4
 REQUIRED_PATHS=$@
 
-set -x 1
-
 rm -fr $OUTPUT_DIR
 mkdir -p $OUTPUT_DIR
 
diff --git a/scripts/update_out b/scripts/update_out
new file mode 100755
index 0000000..d3950cf
--- /dev/null
+++ b/scripts/update_out
@@ -0,0 +1,21 @@
+#! /bin/bash
+# Run given command application and update the contents of a given file.
+# Will not change the file if its contents has not changed.
+[[ $# -gt 1 ]] || { echo "Usage: ${0##*/} FILE COMMAND" >&2; exit 1; }
+set -u
+declare -r outfile="$1"
+shift
+if [[ ! -f $outfile ]]; then
+	$@ >$outfile
+	exit
+fi
+
+declare -r newout=${outfile}.new
+$@ >$newout
+rc=$?
+if cmp -s $newout $outfile; then
+	rm $newout
+else
+	mv -f $newout $outfile
+fi
+exit $rc
diff --git a/sdk/Android.bp b/sdk/Android.bp
index 0c9bf27..f42b478 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -11,11 +11,14 @@
         "soong-android",
         "soong-apex",
         "soong-cc",
+        "soong-dexpreopt",
         "soong-java",
     ],
     srcs: [
         "bp.go",
+        "build_release.go",
         "exports.go",
+        "member_trait.go",
         "member_type.go",
         "sdk.go",
         "update.go",
@@ -23,12 +26,15 @@
     testSrcs: [
         "bootclasspath_fragment_sdk_test.go",
         "bp_test.go",
+        "build_release_test.go",
         "cc_sdk_test.go",
         "compat_config_sdk_test.go",
         "exports_test.go",
         "java_sdk_test.go",
         "license_sdk_test.go",
+        "member_trait_test.go",
         "sdk_test.go",
+        "systemserverclasspath_fragment_sdk_test.go",
         "testing.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index bed2ecf..2dacdb5 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -40,6 +40,7 @@
 			}
 		`, apex, fragment)),
 		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+		android.FixtureAddFile("frameworks/base/config/boot-image-profile.txt", nil),
 		android.FixtureAddFile("build/soong/scripts/check_boot_jars/package_allowed_list.txt", nil),
 	)
 }
@@ -138,8 +139,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -166,8 +167,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -339,8 +340,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -424,8 +425,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -539,12 +540,6 @@
         snapshot/hiddenapi/index.csv
 			`, rule)
 
-			// Make sure that the permitted packages from the prebuilts end up in the
-			// updatable-bcp-packages.txt file.
-			rule = module.Output("updatable-bcp-packages.txt")
-			expectedContents := `'mybootlib\nmyothersdklibrary\n'`
-			android.AssertStringEquals(t, "updatable-bcp-packages.txt", expectedContents, rule.Args["content"])
-
 			rule = module.Output("out/soong/hiddenapi/hiddenapi-flags.csv.valid")
 			android.AssertStringDoesContain(t, "verify-overlaps", rule.RuleParams.Command, " snapshot/hiddenapi/filtered-flags.csv:snapshot/hiddenapi/signature-patterns.csv ")
 		}),
@@ -655,8 +650,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -858,8 +853,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
diff --git a/sdk/build_release.go b/sdk/build_release.go
new file mode 100644
index 0000000..a3f0899
--- /dev/null
+++ b/sdk/build_release.go
@@ -0,0 +1,324 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+// Supports customizing sdk snapshot output based on target build release.
+
+// buildRelease represents the version of a build system used to create a specific release.
+//
+// The name of the release, is the same as the code for the dessert release, e.g. S, T, etc.
+type buildRelease struct {
+	// The name of the release, e.g. S, T, etc.
+	name string
+
+	// The index of this structure within the buildReleases list.
+	ordinal int
+}
+
+// String returns the name of the build release.
+func (s *buildRelease) String() string {
+	return s.name
+}
+
+// buildReleaseSet represents a set of buildRelease objects.
+type buildReleaseSet struct {
+	// Set of *buildRelease represented as a map from *buildRelease to struct{}.
+	contents map[*buildRelease]struct{}
+}
+
+// addItem adds a build release to the set.
+func (s *buildReleaseSet) addItem(release *buildRelease) {
+	s.contents[release] = struct{}{}
+}
+
+// addRange adds all the build releases from start (inclusive) to end (inclusive).
+func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) {
+	for i := start.ordinal; i <= end.ordinal; i += 1 {
+		s.addItem(buildReleases[i])
+	}
+}
+
+// contains returns true if the set contains the specified build release.
+func (s *buildReleaseSet) contains(release *buildRelease) bool {
+	_, ok := s.contents[release]
+	return ok
+}
+
+// String returns a string representation of the set, sorted from earliest to latest release.
+func (s *buildReleaseSet) String() string {
+	list := []string{}
+	for _, release := range buildReleases {
+		if _, ok := s.contents[release]; ok {
+			list = append(list, release.name)
+		}
+	}
+	return fmt.Sprintf("[%s]", strings.Join(list, ","))
+}
+
+var (
+	// nameToBuildRelease contains a map from name to build release.
+	nameToBuildRelease = map[string]*buildRelease{}
+
+	// buildReleases lists all the available build releases.
+	buildReleases = []*buildRelease{}
+
+	// allBuildReleaseSet is the set of all build releases.
+	allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	// Add the build releases from oldest to newest.
+	buildReleaseS = initBuildRelease("S")
+	buildReleaseT = initBuildRelease("T")
+)
+
+// initBuildRelease creates a new build release with the specified name.
+func initBuildRelease(name string) *buildRelease {
+	ordinal := len(nameToBuildRelease)
+	release := &buildRelease{name: name, ordinal: ordinal}
+	nameToBuildRelease[name] = release
+	buildReleases = append(buildReleases, release)
+	allBuildReleaseSet.addItem(release)
+	return release
+}
+
+// latestBuildRelease returns the latest build release, i.e. the last one added.
+func latestBuildRelease() *buildRelease {
+	return buildReleases[len(buildReleases)-1]
+}
+
+// nameToRelease maps from build release name to the corresponding build release (if it exists) or
+// the error if it does not.
+func nameToRelease(name string) (*buildRelease, error) {
+	if r, ok := nameToBuildRelease[name]; ok {
+		return r, nil
+	}
+
+	return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet)
+}
+
+// parseBuildReleaseSet parses a build release set string specification into a build release set.
+//
+// The specification consists of one of the following:
+// * a single build release name, e.g. S, T, etc.
+// * a closed range (inclusive to inclusive), e.g. S-T
+// * an open range, e.g. T+.
+//
+// This returns the set if the specification was valid or an error.
+func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) {
+	set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	if strings.HasSuffix(specification, "+") {
+		rangeStart := strings.TrimSuffix(specification, "+")
+		start, err := nameToRelease(rangeStart)
+		if err != nil {
+			return nil, err
+		}
+		end := latestBuildRelease()
+		set.addRange(start, end)
+	} else if strings.Contains(specification, "-") {
+		limits := strings.SplitN(specification, "-", 2)
+		start, err := nameToRelease(limits[0])
+		if err != nil {
+			return nil, err
+		}
+
+		end, err := nameToRelease(limits[1])
+		if err != nil {
+			return nil, err
+		}
+
+		if start.ordinal > end.ordinal {
+			return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name)
+		}
+
+		set.addRange(start, end)
+	} else {
+		release, err := nameToRelease(specification)
+		if err != nil {
+			return nil, err
+		}
+		set.addItem(release)
+	}
+
+	return set, nil
+}
+
+// Given a set of properties (struct value), set the value of a field within that struct (or one of
+// its embedded structs) to its zero value.
+type fieldPrunerFunc func(structValue reflect.Value)
+
+// A property that can be cleared by a propertyPruner.
+type prunerProperty struct {
+	// The name of the field for this property. It is a "."-separated path for fields in non-anonymous
+	// sub-structs.
+	name string
+
+	// Sets the associated field to its zero value.
+	prunerFunc fieldPrunerFunc
+}
+
+// propertyPruner provides support for pruning (i.e. setting to their zero value) properties from
+// a properties structure.
+type propertyPruner struct {
+	// The properties that the pruner will clear.
+	properties []prunerProperty
+}
+
+// gatherFields recursively processes the supplied structure and a nested structures, selecting the
+// fields that require pruning and populates the propertyPruner.properties with the information
+// needed to prune those fields.
+//
+// containingStructAccessor is a func that if given an object will return a field whose value is
+// of the supplied structType. It is nil on initial entry to this method but when this method is
+// called recursively on a field that is a nested structure containingStructAccessor is set to a
+// func that provides access to the field's value.
+//
+// namePrefix is the prefix to the fields that are being visited. It is "" on initial entry to this
+// method but when this method is called recursively on a field that is a nested structure
+// namePrefix is the result of appending the field name (plus a ".") to the previous name prefix.
+// Unless the field is anonymous in which case it is passed through unchanged.
+//
+// selector is a func that will select whether the supplied field requires pruning or not. If it
+// returns true then the field will be added to those to be pruned, otherwise it will not.
+func (p *propertyPruner) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string, selector fieldSelectorFunc) {
+	for f := 0; f < structType.NumField(); f++ {
+		field := structType.Field(f)
+		if field.PkgPath != "" {
+			// Ignore unexported fields.
+			continue
+		}
+
+		// Save a copy of the field index for use in the function.
+		fieldIndex := f
+
+		name := namePrefix + field.Name
+
+		fieldGetter := func(container reflect.Value) reflect.Value {
+			if containingStructAccessor != nil {
+				// This is an embedded structure so first access the field for the embedded
+				// structure.
+				container = containingStructAccessor(container)
+			}
+
+			// Skip through interface and pointer values to find the structure.
+			container = getStructValue(container)
+
+			defer func() {
+				if r := recover(); r != nil {
+					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
+				}
+			}()
+
+			// Return the field.
+			return container.Field(fieldIndex)
+		}
+
+		zeroValue := reflect.Zero(field.Type)
+		fieldPruner := func(container reflect.Value) {
+			if containingStructAccessor != nil {
+				// This is an embedded structure so first access the field for the embedded
+				// structure.
+				container = containingStructAccessor(container)
+			}
+
+			// Skip through interface and pointer values to find the structure.
+			container = getStructValue(container)
+
+			defer func() {
+				if r := recover(); r != nil {
+					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
+				}
+			}()
+
+			// Set the field.
+			container.Field(fieldIndex).Set(zeroValue)
+		}
+
+		if selector(name, field) {
+			property := prunerProperty{
+				name,
+				fieldPruner,
+			}
+			p.properties = append(p.properties, property)
+		} else if field.Type.Kind() == reflect.Struct {
+			// Gather fields from the nested or embedded structure.
+			var subNamePrefix string
+			if field.Anonymous {
+				subNamePrefix = namePrefix
+			} else {
+				subNamePrefix = name + "."
+			}
+			p.gatherFields(field.Type, fieldGetter, subNamePrefix, selector)
+		}
+	}
+}
+
+// pruneProperties will prune (set to zero value) any properties in the supplied struct.
+//
+// The struct must be of the same type as was originally passed to newPropertyPruner to create this
+// propertyPruner.
+func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) {
+	structValue := reflect.ValueOf(propertiesStruct)
+	for _, property := range p.properties {
+		property.prunerFunc(structValue)
+	}
+}
+
+// fieldSelectorFunc is called to select whether a specific field should be pruned or not.
+// name is the name of the field, including any prefixes from containing str
+type fieldSelectorFunc func(name string, field reflect.StructField) bool
+
+// newPropertyPruner creates a new property pruner for the structure type for the supplied
+// properties struct.
+//
+// The returned pruner can be used on any properties structure of the same type as the supplied set
+// of properties.
+func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner {
+	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
+	pruner := &propertyPruner{}
+	pruner.gatherFields(structType, nil, "", selector)
+	return pruner
+}
+
+// newPropertyPrunerByBuildRelease creates a property pruner that will clear any properties in the
+// structure which are not supported by the specified target build release.
+//
+// A property is pruned if its field has a tag of the form:
+//     `supported_build_releases:"<build-release-set>"`
+// and the resulting build release set does not contain the target build release. Properties that
+// have no such tag are assumed to be supported by all releases.
+func newPropertyPrunerByBuildRelease(propertiesStruct interface{}, targetBuildRelease *buildRelease) *propertyPruner {
+	return newPropertyPruner(propertiesStruct, func(name string, field reflect.StructField) bool {
+		if supportedBuildReleases, ok := field.Tag.Lookup("supported_build_releases"); ok {
+			set, err := parseBuildReleaseSet(supportedBuildReleases)
+			if err != nil {
+				panic(fmt.Errorf("invalid `supported_build_releases` tag on %s of %T: %s", name, propertiesStruct, err))
+			}
+
+			// If the field does not support tha target release then prune it.
+			return !set.contains(targetBuildRelease)
+
+		} else {
+			// Any untagged fields are assumed to be supported by all build releases so should never be
+			// pruned.
+			return false
+		}
+	})
+}
diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go
new file mode 100644
index 0000000..dff276d
--- /dev/null
+++ b/sdk/build_release_test.go
@@ -0,0 +1,185 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+// Tests for build_release.go
+
+var (
+	// Some additional test specific releases that are added after the currently supported ones and
+	// so are treated as being for future releases.
+	buildReleaseFuture1 = initBuildRelease("F1")
+	buildReleaseFuture2 = initBuildRelease("F2")
+)
+
+func TestNameToRelease(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		release, err := nameToRelease("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertDeepEquals(t, "release", buildReleaseS, release)
+	})
+	t.Run("invalid release", func(t *testing.T) {
+		release, err := nameToRelease("A")
+		android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release)
+		// Uses a wildcard in the error message to allow for additional build releases to be added to
+		// the supported set without breaking this test.
+		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,T.*,F1,F2\]`, []error{err})
+	})
+}
+
+func TestParseBuildReleaseSet(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S]", set.String())
+	})
+	t.Run("open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1+")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[F1,F2]", set.String())
+	})
+	t.Run("closed range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S-F1")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S,T,F1]", set.String())
+	})
+	invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String()
+	t.Run("invalid release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A+")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range start", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range end", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("T-A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid closed range reversed", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), `invalid closed range, start release "F1" is later than end release "S"`)
+	})
+}
+
+func TestBuildReleaseSetContains(t *testing.T) {
+	t.Run("contains", func(t *testing.T) {
+		set, _ := parseBuildReleaseSet("F1-F2")
+		android.AssertBoolEquals(t, "set contains F1", true, set.contains(buildReleaseFuture1))
+		android.AssertBoolEquals(t, "set does not contain S", false, set.contains(buildReleaseS))
+		android.AssertBoolEquals(t, "set contains F2", true, set.contains(buildReleaseFuture2))
+		android.AssertBoolEquals(t, "set does not contain T", false, set.contains(buildReleaseT))
+	})
+}
+
+func TestPropertyPrunerInvalidTag(t *testing.T) {
+	type brokenStruct struct {
+		Broken string `supported_build_releases:"A"`
+	}
+	type containingStruct struct {
+		Nested brokenStruct
+	}
+
+	t.Run("broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Broken of *sdk.brokenStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&brokenStruct{}, buildReleaseS)
+		})
+	})
+
+	t.Run("nested broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Nested.Broken of *sdk.containingStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&containingStruct{}, buildReleaseS)
+		})
+	})
+}
+
+func TestPropertyPrunerByBuildRelease(t *testing.T) {
+	type nested struct {
+		F1_only string `supported_build_releases:"F1"`
+	}
+
+	type testBuildReleasePruner struct {
+		Default      string
+		S_and_T_only string `supported_build_releases:"S-T"`
+		T_later      string `supported_build_releases:"T+"`
+		Nested       nested
+	}
+
+	input := testBuildReleasePruner{
+		Default:      "Default",
+		S_and_T_only: "S_and_T_only",
+		T_later:      "T_later",
+		Nested: nested{
+			F1_only: "F1_only",
+		},
+	}
+
+	t.Run("target S", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseS)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.T_later = ""
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target T", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseT)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target F1", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture1)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.S_and_T_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target F2", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture2)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.S_and_T_only = ""
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+}
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 25e35fc..cd63dac 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -15,6 +15,7 @@
 package sdk
 
 import (
+	"fmt"
 	"testing"
 
 	"android/soong/android"
@@ -32,6 +33,23 @@
 	"some/where/stubslib.map.txt":     nil,
 }
 
+// Adds a native bridge target to the configured list of targets.
+var prepareForTestWithNativeBridgeTarget = android.FixtureModifyConfig(func(config android.Config) {
+	config.Targets[android.Android] = append(config.Targets[android.Android], android.Target{
+		Os: android.Android,
+		Arch: android.Arch{
+			ArchType:     android.Arm64,
+			ArchVariant:  "armv8-a",
+			CpuVariant:   "cpu",
+			Abi:          nil,
+			ArchFeatures: nil,
+		},
+		NativeBridge:             android.NativeBridgeEnabled,
+		NativeBridgeHostArchName: "x86_64",
+		NativeBridgeRelativePath: "native_bridge",
+	})
+})
+
 func testSdkWithCc(t *testing.T, bp string) *android.TestResult {
 	t.Helper()
 	return testSdkWithFs(t, bp, ccTestFs)
@@ -1754,7 +1772,6 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    recovery_available: true,
     vendor_available: true,
     stl: "none",
     compile_multilib: "both",
@@ -1789,7 +1806,6 @@
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
     installable: false,
-    recovery_available: true,
     vendor_available: true,
     stl: "none",
     compile_multilib: "both",
@@ -1979,6 +1995,146 @@
 	)
 }
 
+func TestSnapshotWithCcHeadersLibraryAndNativeBridgeSupport(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		PrepareForTestWithSdkBuildComponents,
+		ccTestFs.AddToFixture(),
+		prepareForTestWithNativeBridgeTarget,
+	).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				native_bridge_support: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			native_bridge_supported: true,
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_library_headers {
+    name: "mynativeheaders",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    native_bridge_supported: true,
+    stl: "none",
+    compile_multilib: "both",
+    system_shared_libs: [],
+    export_include_dirs: ["include/myinclude"],
+}
+`),
+		checkAllCopyRules(`
+myinclude/Test.h -> include/myinclude/Test.h
+`),
+	)
+}
+
+// TestSnapshotWithCcHeadersLibrary_DetectsNativeBridgeSpecificProperties verifies that when a
+// module that has different output files for a native bridge target requests the native bridge
+// variants are copied into the sdk snapshot that it reports an error.
+func TestSnapshotWithCcHeadersLibrary_DetectsNativeBridgeSpecificProperties(t *testing.T) {
+	android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		PrepareForTestWithSdkBuildComponents,
+		ccTestFs.AddToFixture(),
+		prepareForTestWithNativeBridgeTarget,
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`\QArchitecture variant "arm64_native_bridge" of sdk member "mynativeheaders" has properties distinct from other variants; this is not yet supported. The properties are:
+        export_include_dirs: [
+            "arm64_native_bridge/include/myinclude_nativebridge",
+            "arm64_native_bridge/include/myinclude",
+        ],\E`)).
+		RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				native_bridge_support: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			native_bridge_supported: true,
+			target: {
+				native_bridge: {
+					export_include_dirs: ["myinclude_nativebridge"],
+				},
+			},
+		}
+	`)
+}
+
+func TestSnapshotWithCcHeadersLibraryAndImageVariants(t *testing.T) {
+	testImageVariant := func(t *testing.T, property, trait string) {
+		result := android.GroupFixturePreparers(
+			cc.PrepareForTestWithCcDefaultModules,
+			PrepareForTestWithSdkBuildComponents,
+			ccTestFs.AddToFixture(),
+		).RunTestWithBp(t, fmt.Sprintf(`
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				%s: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			%s: true,
+		}
+	`, trait, property))
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkUnversionedAndroidBpContents(fmt.Sprintf(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_library_headers {
+    name: "mynativeheaders",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    %s: true,
+    stl: "none",
+    compile_multilib: "both",
+    system_shared_libs: [],
+    export_include_dirs: ["include/myinclude"],
+}
+`, property)),
+			checkAllCopyRules(`
+myinclude/Test.h -> include/myinclude/Test.h
+`),
+		)
+	}
+
+	t.Run("ramdisk", func(t *testing.T) {
+		testImageVariant(t, "ramdisk_available", "ramdisk_image_required")
+	})
+
+	t.Run("recovery", func(t *testing.T) {
+		testImageVariant(t, "recovery_available", "recovery_image_required")
+	})
+}
+
 func TestHostSnapshotWithCcHeadersLibrary(t *testing.T) {
 	result := testSdkWithCc(t, `
 		sdk {
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 9efb3a4..0d9b4a0 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -482,6 +482,71 @@
 	)
 }
 
+func TestSnapshotWithJavaSystemserverLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
+	).RunTestWithBp(t, `
+		module_exports {
+			name: "myexports",
+			java_systemserver_libs: ["myjavalib"],
+		}
+
+		java_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			java_resources: ["resource.txt"],
+			// The aidl files should not be copied to the snapshot because a java_systemserver_libs member
+			// is not intended to be used for compiling Java, only for accessing the dex implementation
+			// jar.
+			aidl: {
+				export_include_dirs: ["aidl"],
+			},
+			system_modules: "none",
+			sdk_version: "none",
+			compile_dex: true,
+			permitted_packages: ["pkg.myjavalib"],
+		}
+	`)
+
+	CheckSnapshot(t, result, "myexports", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    permitted_packages: ["pkg.myjavalib"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myexports_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    permitted_packages: ["pkg.myjavalib"],
+}
+
+module_exports_snapshot {
+    name: "myexports@current",
+    visibility: ["//visibility:public"],
+    java_systemserver_libs: ["myexports_myjavalib@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/myexports/common_os/empty -> java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar
+`),
+	)
+}
+
 func TestHostSnapshotWithJavaImplLibrary(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
@@ -1205,6 +1270,55 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_AnnotationsZip(t *testing.T) {
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        annotations: "sdk_library/public/myjavalib_annotations.zip",
+        sdk_version: "current",
+    },
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_annotations.zip -> sdk_library/public/myjavalib_annotations.zip
+		`),
+		checkMergeZips(".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip"),
+	)
+}
+
 func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
@@ -1258,7 +1372,7 @@
 			ctx := android.ModuleInstallPathContextForTesting(result.Config)
 			dexJarBuildPath := func(name string, kind android.SdkKind) string {
 				dep := result.Module(name, "android_common").(java.SdkLibraryDependency)
-				path := dep.SdkApiStubDexJar(ctx, kind)
+				path := dep.SdkApiStubDexJar(ctx, kind).Path()
 				return path.RelativeToTop().String()
 			}
 
diff --git a/sdk/member_trait.go b/sdk/member_trait.go
new file mode 100644
index 0000000..4229ca8
--- /dev/null
+++ b/sdk/member_trait.go
@@ -0,0 +1,126 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import (
+	"reflect"
+
+	"android/soong/android"
+	"github.com/google/blueprint/proptools"
+)
+
+// Contains information about the sdk properties that list sdk members by trait, e.g.
+// native_bridge.
+type sdkMemberTraitListProperty struct {
+	// getter for the list of member names
+	getter func(properties interface{}) []string
+
+	// the trait of member referenced in the list
+	memberTrait android.SdkMemberTrait
+}
+
+// Cache of dynamically generated dynamicSdkMemberTraits objects. The key is the pointer
+// to a slice of SdkMemberTrait instances returned by android.RegisteredSdkMemberTraits().
+var dynamicSdkMemberTraitsMap android.OncePer
+
+// A dynamically generated set of member list properties and associated structure type.
+//
+// Instances of this are created by createDynamicSdkMemberTraits.
+type dynamicSdkMemberTraits struct {
+	// The dynamically generated structure type.
+	//
+	// Contains one []string exported field for each SdkMemberTrait returned by android.RegisteredSdkMemberTraits(). The name of
+	// the field is the exported form of the value returned by SdkMemberTrait.SdkPropertyName().
+	propertiesStructType reflect.Type
+
+	// Information about each of the member trait specific list properties.
+	memberTraitListProperties []*sdkMemberTraitListProperty
+}
+
+func (d *dynamicSdkMemberTraits) createMemberTraitListProperties() interface{} {
+	return reflect.New(d.propertiesStructType).Interface()
+}
+
+func getDynamicSdkMemberTraits(key android.OnceKey, registeredTraits []android.SdkMemberTrait) *dynamicSdkMemberTraits {
+	// Get the cached value, creating new instance if necessary.
+	return dynamicSdkMemberTraitsMap.Once(key, func() interface{} {
+		return createDynamicSdkMemberTraits(registeredTraits)
+	}).(*dynamicSdkMemberTraits)
+}
+
+// Create the dynamicSdkMemberTraits from the list of registered member traits.
+//
+// A struct is created which contains one exported field per member trait corresponding to
+// the SdkMemberTrait.SdkPropertyName() value.
+//
+// A list of sdkMemberTraitListProperty instances is created, one per member trait that provides:
+// * a reference to the member trait.
+// * a getter for the corresponding field in the properties struct.
+//
+func createDynamicSdkMemberTraits(sdkMemberTraits []android.SdkMemberTrait) *dynamicSdkMemberTraits {
+
+	var listProperties []*sdkMemberTraitListProperty
+	memberTraitToProperty := map[android.SdkMemberTrait]*sdkMemberTraitListProperty{}
+	var fields []reflect.StructField
+
+	// Iterate over the member traits creating StructField and sdkMemberTraitListProperty objects.
+	nextFieldIndex := 0
+	for _, memberTrait := range sdkMemberTraits {
+
+		p := memberTrait.SdkPropertyName()
+
+		var getter func(properties interface{}) []string
+
+		// Create a dynamic exported field for the member trait's property.
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(p),
+			Type: reflect.TypeOf([]string{}),
+		})
+
+		// Copy the field index for use in the getter func as using the loop variable directly will
+		// cause all funcs to use the last value.
+		fieldIndex := nextFieldIndex
+		nextFieldIndex += 1
+
+		getter = func(properties interface{}) []string {
+			// The properties is expected to be of the following form (where
+			// <Module_traits> is the name of an SdkMemberTrait.SdkPropertyName().
+			//     properties *struct {<Module_traits> []string, ....}
+			//
+			// Although it accesses the field by index the following reflection code is equivalent to:
+			//    *properties.<Module_traits>
+			//
+			list := reflect.ValueOf(properties).Elem().Field(fieldIndex).Interface().([]string)
+			return list
+		}
+
+		// Create an sdkMemberTraitListProperty for the member trait.
+		memberListProperty := &sdkMemberTraitListProperty{
+			getter:      getter,
+			memberTrait: memberTrait,
+		}
+
+		memberTraitToProperty[memberTrait] = memberListProperty
+		listProperties = append(listProperties, memberListProperty)
+	}
+
+	// Create a dynamic struct from the collated fields.
+	propertiesStructType := reflect.StructOf(fields)
+
+	return &dynamicSdkMemberTraits{
+		memberTraitListProperties: listProperties,
+		propertiesStructType:      propertiesStructType,
+	}
+}
diff --git a/sdk/member_trait_test.go b/sdk/member_trait_test.go
new file mode 100644
index 0000000..a3db189
--- /dev/null
+++ b/sdk/member_trait_test.go
@@ -0,0 +1,287 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import (
+	"fmt"
+	"path/filepath"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+	"github.com/google/blueprint"
+)
+
+type fakeMemberTrait struct {
+	android.SdkMemberTraitBase
+}
+
+type fakeMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (t *fakeMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	for _, name := range names {
+		ctx.AddVariationDependencies(nil, dependencyTag, name)
+
+		if ctx.RequiresTrait(name, extraTrait) {
+			ctx.AddVariationDependencies(nil, dependencyTag, name+"_extra")
+		}
+		if ctx.RequiresTrait(name, specialTrait) {
+			ctx.AddVariationDependencies(nil, dependencyTag, name+"_special")
+		}
+	}
+}
+
+func (t *fakeMemberType) IsInstance(module android.Module) bool {
+	return true
+}
+
+func (t *fakeMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	moduleType := "java_import"
+	if ctx.RequiresTrait(extraTrait) {
+		moduleType = "java_test_import"
+	}
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, moduleType)
+}
+
+func (t *fakeMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	return &fakeMemberTypeProperties{}
+}
+
+type fakeMemberTypeProperties struct {
+	android.SdkMemberPropertiesBase
+
+	path android.Path
+}
+
+func (t *fakeMemberTypeProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
+	headerJars := variant.(java.ApexDependency).HeaderJars()
+	if len(headerJars) != 1 {
+		panic(fmt.Errorf("there must be only one header jar from %q", variant.Name()))
+	}
+
+	t.path = headerJars[0]
+}
+
+func (t *fakeMemberTypeProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
+	if t.path != nil {
+		relative := filepath.Join("javalibs", t.path.Base())
+		ctx.SnapshotBuilder().CopyToSnapshot(t.path, relative)
+		propertySet.AddProperty("jars", []string{relative})
+	}
+}
+
+var (
+	extraTrait = &fakeMemberTrait{
+		SdkMemberTraitBase: android.SdkMemberTraitBase{
+			PropertyName: "extra",
+		},
+	}
+
+	specialTrait = &fakeMemberTrait{
+		SdkMemberTraitBase: android.SdkMemberTraitBase{
+			PropertyName: "special",
+		},
+	}
+
+	fakeType = &fakeMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "fake_members",
+			SupportsSdk:  true,
+			Traits: []android.SdkMemberTrait{
+				extraTrait,
+				specialTrait,
+			},
+		},
+	}
+)
+
+func init() {
+	android.RegisterSdkMemberTrait(extraTrait)
+	android.RegisterSdkMemberTrait(specialTrait)
+	android.RegisterSdkMemberType(fakeType)
+}
+
+func TestBasicTrait_WithoutTrait(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				fake_members: ["myjavalib"],
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    fake_members: ["mysdk_myjavalib@current"],
+}
+`),
+	)
+}
+
+func TestBasicTrait_MultipleTraits(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				fake_members: ["myjavalib", "anotherjavalib"],
+				traits: {
+					extra: ["myjavalib"],
+					special: ["myjavalib", "anotherjavalib"],
+				},
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "myjavalib_extra",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "myjavalib_special",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "anotherjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "anotherjavalib_special",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_test_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib_extra",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib_extra.jar"],
+}
+
+java_import {
+    name: "myjavalib_special",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib_special.jar"],
+}
+
+java_import {
+    name: "anotherjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/anotherjavalib.jar"],
+}
+
+java_import {
+    name: "anotherjavalib_special",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/anotherjavalib_special.jar"],
+}
+`),
+	)
+}
+
+func TestTraitUnsupportedByMemberType(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				java_header_libs: ["myjavalib"],
+				traits: {
+					extra: ["myjavalib"],
+				},
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`\Qsdk member "myjavalib" has traits [extra] that are unsupported by its member type "java_header_libs"\E`)).
+		RunTest(t)
+}
diff --git a/sdk/member_type.go b/sdk/member_type.go
index ee27c86..10669fe 100644
--- a/sdk/member_type.go
+++ b/sdk/member_type.go
@@ -35,7 +35,7 @@
 
 	// the dependency tag used for items in this list that can be used to determine the memberType
 	// for a resolved dependency.
-	dependencyTag android.SdkMemberTypeDependencyTag
+	dependencyTag android.SdkMemberDependencyTag
 }
 
 func (p *sdkMemberTypeListProperty) propertyName() string {
@@ -64,14 +64,7 @@
 	return reflect.New(d.propertiesStructType).Interface()
 }
 
-func getDynamicSdkMemberTypes(registry *android.SdkMemberTypesRegistry) *dynamicSdkMemberTypes {
-
-	// Get a key that uniquely identifies the registry contents.
-	key := registry.UniqueOnceKey()
-
-	// Get the registered types.
-	registeredTypes := registry.RegisteredTypes()
-
+func getDynamicSdkMemberTypes(key android.OnceKey, registeredTypes []android.SdkMemberType) *dynamicSdkMemberTypes {
 	// Get the cached value, creating new instance if necessary.
 	return dynamicSdkMemberTypesMap.Once(key, func() interface{} {
 		return createDynamicSdkMemberTypes(registeredTypes)
diff --git a/sdk/sdk.go b/sdk/sdk.go
index 6dea752..84c9a96 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -53,6 +53,13 @@
 	// list properties, e.g. java_libs.
 	dynamicMemberTypeListProperties interface{}
 
+	// The dynamically generated information about the registered SdkMemberTrait
+	dynamicSdkMemberTraits *dynamicSdkMemberTraits
+
+	// The dynamically created instance of the properties struct containing the sdk member trait
+	// list properties.
+	dynamicMemberTraitListProperties interface{}
+
 	// Information about the OsType specific member variants depended upon by this variant.
 	//
 	// Set by OsType specific variants in the collectMembers() method and used by the
@@ -104,17 +111,25 @@
 	s := &sdk{}
 	s.properties.Module_exports = moduleExports
 	// Get the dynamic sdk member type data for the currently registered sdk member types.
-	var typeRegistry *android.SdkMemberTypesRegistry
-	if moduleExports {
-		typeRegistry = android.ModuleExportsMemberTypes
-	} else {
-		typeRegistry = android.SdkMemberTypes
-	}
-	s.dynamicSdkMemberTypes = getDynamicSdkMemberTypes(typeRegistry)
+	sdkMemberTypeKey, sdkMemberTypes := android.RegisteredSdkMemberTypes(moduleExports)
+	s.dynamicSdkMemberTypes = getDynamicSdkMemberTypes(sdkMemberTypeKey, sdkMemberTypes)
 	// Create an instance of the dynamically created struct that contains all the
 	// properties for the member type specific list properties.
 	s.dynamicMemberTypeListProperties = s.dynamicSdkMemberTypes.createMemberTypeListProperties()
-	s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties)
+
+	sdkMemberTraitsKey, sdkMemberTraits := android.RegisteredSdkMemberTraits()
+	s.dynamicSdkMemberTraits = getDynamicSdkMemberTraits(sdkMemberTraitsKey, sdkMemberTraits)
+	// Create an instance of the dynamically created struct that contains all the properties for the
+	// member trait specific list properties.
+	s.dynamicMemberTraitListProperties = s.dynamicSdkMemberTraits.createMemberTraitListProperties()
+
+	// Create a wrapper around the dynamic trait specific properties so that they have to be
+	// specified within a traits:{} section in the .bp file.
+	traitsWrapper := struct {
+		Traits interface{}
+	}{s.dynamicMemberTraitListProperties}
+
+	s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties, &traitsWrapper)
 
 	// Make sure that the prebuilt visibility property is verified for errors.
 	android.AddVisibilityProperty(s, "prebuilt_visibility", &s.properties.Prebuilt_visibility)
@@ -145,6 +160,11 @@
 	return s.dynamicSdkMemberTypes.memberTypeToProperty[memberType]
 }
 
+// memberTraitListProperties returns the list of *sdkMemberTraitListProperty instances for this sdk.
+func (s *sdk) memberTraitListProperties() []*sdkMemberTraitListProperty {
+	return s.dynamicSdkMemberTraits.memberTraitListProperties
+}
+
 func (s *sdk) snapshot() bool {
 	return s.properties.Snapshot
 }
@@ -198,15 +218,53 @@
 	}}
 }
 
+// gatherTraits gathers the traits from the dynamically generated trait specific properties.
+//
+// Returns a map from member name to the set of required traits.
+func (s *sdk) gatherTraits() map[string]android.SdkMemberTraitSet {
+	traitListByMember := map[string][]android.SdkMemberTrait{}
+	for _, memberListProperty := range s.memberTraitListProperties() {
+		names := memberListProperty.getter(s.dynamicMemberTraitListProperties)
+		for _, name := range names {
+			traitListByMember[name] = append(traitListByMember[name], memberListProperty.memberTrait)
+		}
+	}
+
+	traitSetByMember := map[string]android.SdkMemberTraitSet{}
+	for name, list := range traitListByMember {
+		traitSetByMember[name] = android.NewSdkMemberTraitSet(list)
+	}
+
+	return traitSetByMember
+}
+
 // newDependencyContext creates a new SdkDependencyContext for this sdk.
 func (s *sdk) newDependencyContext(mctx android.BottomUpMutatorContext) android.SdkDependencyContext {
+	traits := s.gatherTraits()
+
 	return &dependencyContext{
 		BottomUpMutatorContext: mctx,
+		requiredTraits:         traits,
 	}
 }
 
 type dependencyContext struct {
 	android.BottomUpMutatorContext
+
+	// Map from member name to the set of traits that the sdk requires the member provides.
+	requiredTraits map[string]android.SdkMemberTraitSet
+}
+
+func (d *dependencyContext) RequiredTraits(name string) android.SdkMemberTraitSet {
+	if s, ok := d.requiredTraits[name]; ok {
+		return s
+	} else {
+		return android.EmptySdkMemberTraitSet()
+	}
+}
+
+func (d *dependencyContext) RequiresTrait(name string, trait android.SdkMemberTrait) bool {
+	return d.RequiredTraits(name).Contains(trait)
 }
 
 var _ android.SdkDependencyContext = (*dependencyContext)(nil)
@@ -287,8 +345,21 @@
 				}
 				names := memberListProperty.getter(s.dynamicMemberTypeListProperties)
 				if len(names) > 0 {
+					memberType := memberListProperty.memberType
+
+					// Verify that the member type supports the specified traits.
+					supportedTraits := memberType.SupportedTraits()
+					for _, name := range names {
+						requiredTraits := ctx.RequiredTraits(name)
+						unsupportedTraits := requiredTraits.Subtract(supportedTraits)
+						if !unsupportedTraits.Empty() {
+							ctx.ModuleErrorf("sdk member %q has traits %s that are unsupported by its member type %q", name, unsupportedTraits, memberType.SdkPropertyName())
+						}
+					}
+
+					// Add dependencies using the appropriate tag.
 					tag := memberListProperty.dependencyTag
-					memberListProperty.memberType.AddDependencies(ctx, tag, names)
+					memberType.AddDependencies(ctx, tag, names)
 				}
 			}
 		}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 85e3d87..83294f6 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -21,6 +21,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/java"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -706,4 +707,86 @@
 			snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)),
 		)
 	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			prepareForSdkTestWithJava,
+			java.PrepareForTestWithJavaDefaultModules,
+			java.PrepareForTestWithJavaSdkLibraryFiles,
+			java.FixtureWithLastReleaseApis("mysdklibrary"),
+			android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
+				contents: ["mysdklibrary"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				public: {enabled: true},
+				permitted_packages: ["mysdklibrary"],
+			}
+		`),
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+			}),
+		).RunTest(t)
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: ["mysdklibrary"],
+    hidden_api: {
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        stub_flags: "hiddenapi/stub-flags.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    permitted_packages: ["mysdklibrary"],
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+`),
+
+			checkAllCopyRules(`
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
+`),
+		)
+	})
+
 }
diff --git a/sdk/systemserverclasspath_fragment_sdk_test.go b/sdk/systemserverclasspath_fragment_sdk_test.go
new file mode 100644
index 0000000..16e3e7f
--- /dev/null
+++ b/sdk/systemserverclasspath_fragment_sdk_test.go
@@ -0,0 +1,171 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// 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 sdk
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+	"android/soong/java"
+)
+
+func TestSnapshotWithSystemServerClasspathFragment(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mysdklibrary"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:mylib", "myapex:mysdklibrary"),
+		prepareForSdkTestWithApex,
+
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+				java_sdk_libs: [
+					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+					// a java_sdk_libs module because it is used in the mysystemserverclasspathfragment's
+					// contents property. However, it is specified here to ensure that duplicates are
+					// correctly deduped.
+					"mysdklibrary",
+				],
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+			}
+
+			systemserverclasspath_fragment {
+				name: "mysystemserverclasspathfragment",
+				apex_available: ["myapex"],
+				contents: [
+					"mylib",
+					"mysdklibrary",
+				],
+			}
+
+			java_library {
+				name: "mylib",
+				apex_available: ["myapex"],
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				min_sdk_version: "2",
+				compile_dex: true,
+				permitted_packages: ["mylib"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				apex_available: ["myapex"],
+				srcs: ["Test.java"],
+				shared_library: false,
+				public: {enabled: true},
+				min_sdk_version: "2",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_import {
+    name: "mylib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    permitted_packages: ["mylib"],
+}
+
+prebuilt_systemserverclasspath_fragment {
+    name: "mysystemserverclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: [
+        "mylib",
+        "mysdklibrary",
+    ],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdk_mysdklibrary@current",
+    sdk_member_name: "mysdklibrary",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_import {
+    name: "mysdk_mylib@current",
+    sdk_member_name: "mylib",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    permitted_packages: ["mylib"],
+}
+
+prebuilt_systemserverclasspath_fragment {
+    name: "mysdk_mysystemserverclasspathfragment@current",
+    sdk_member_name: "mysystemserverclasspathfragment",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: [
+        "mysdk_mylib@current",
+        "mysdk_mysdklibrary@current",
+    ],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_sdk_libs: ["mysdk_mysdklibrary@current"],
+    java_systemserver_libs: ["mysdk_mylib@current"],
+    systemserverclasspath_fragments: ["mysdk_mysystemserverclasspathfragment@current"],
+}
+`),
+	)
+}
diff --git a/sdk/testing.go b/sdk/testing.go
index 3254cf9..294f1a5 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -136,6 +136,7 @@
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
+		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
 
 	buildParams := sdk.BuildParamsForTests()
@@ -253,6 +254,13 @@
 	}
 	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
 
+	// If the generated snapshot builders not for the current release then it cannot be loaded by
+	// the current release.
+	currentBuildRelease := latestBuildRelease()
+	if snapshotBuildInfo.targetBuildRelease != currentBuildRelease {
+		return
+	}
+
 	// The preparers from the original source fixture.
 	sourcePreparers := result.Preparer()
 
@@ -476,6 +484,9 @@
 	// The final output zip.
 	outputZip string
 
+	// The target build release.
+	targetBuildRelease *buildRelease
+
 	// The test specific customizations for each snapshot test.
 	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
 }
diff --git a/sdk/update.go b/sdk/update.go
index 96a6e69..389e845 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -81,6 +81,19 @@
 //     snapshot module only. The zip file containing the generated snapshot will be
 //     <sdk-name>-<number>.zip.
 //
+// SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE
+//     This allows the target build release (i.e. the release version of the build within which
+//     the snapshot will be used) of the snapshot to be specified. If unspecified then it defaults
+//     to the current build release version. Otherwise, it must be the name of one of the build
+//     releases defined in nameToBuildRelease, e.g. S, T, etc..
+//
+//     The generated snapshot must only be used in the specified target release. If the target
+//     build release is not the current build release then the generated Android.bp file not be
+//     checked for compatibility.
+//
+//     e.g. if setting SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S will cause the generated snapshot
+//     to be compatible with S.
+//
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
@@ -185,7 +198,7 @@
 	s.multilibUsages = multilibNone
 	ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
 		tag := ctx.OtherModuleDependencyTag(child)
-		if memberTag, ok := tag.(android.SdkMemberTypeDependencyTag); ok {
+		if memberTag, ok := tag.(android.SdkMemberDependencyTag); ok {
 			memberType := memberTag.SdkMemberType(child)
 
 			// If a nil SdkMemberType was returned then this module should not be added to the sdk.
@@ -358,6 +371,14 @@
 		snapshotZipFileSuffix = "-" + version
 	}
 
+	currentBuildRelease := latestBuildRelease()
+	targetBuildReleaseEnv := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE", currentBuildRelease.name)
+	targetBuildRelease, err := nameToRelease(targetBuildReleaseEnv)
+	if err != nil {
+		ctx.ModuleErrorf("invalid SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE: %s", err)
+		targetBuildRelease = currentBuildRelease
+	}
+
 	builder := &snapshotBuilder{
 		ctx:                   ctx,
 		sdk:                   s,
@@ -369,6 +390,7 @@
 		prebuiltModules:       make(map[string]*bpModule),
 		allMembersByName:      allMembersByName,
 		exportedMembersByName: exportedMembersByName,
+		targetBuildRelease:    targetBuildRelease,
 	}
 	s.builderForTests = builder
 
@@ -393,10 +415,18 @@
 	members := s.groupMemberVariantsByMemberThenType(ctx, memberVariantDeps)
 
 	// Create the prebuilt modules for each of the member modules.
+	traits := s.gatherTraits()
 	for _, member := range members {
 		memberType := member.memberType
 
-		memberCtx := &memberContext{ctx, builder, memberType, member.name}
+		name := member.name
+		requiredTraits := traits[name]
+		if requiredTraits == nil {
+			requiredTraits = android.EmptySdkMemberTraitSet()
+		}
+
+		// Create the snapshot for the member.
+		memberCtx := &memberContext{ctx, builder, memberType, name, requiredTraits}
 
 		prebuiltModule := memberType.AddPrebuiltModule(memberCtx, member)
 		s.createMemberSnapshot(memberCtx, member, prebuiltModule.(*bpModule))
@@ -441,7 +471,11 @@
 	generateBpContents(&bp.generatedContents, bpFile)
 
 	contents := bp.content.String()
-	syntaxCheckSnapshotBpFile(ctx, contents)
+	// If the snapshot is being generated for the current build release then check the syntax to make
+	// sure that it is compatible.
+	if targetBuildRelease == currentBuildRelease {
+		syntaxCheckSnapshotBpFile(ctx, contents)
+	}
 
 	bp.build(pctx, ctx, nil)
 
@@ -765,6 +799,8 @@
 	name string
 }
 
+var _ android.BpPropertyTag = propertyTag{}
+
 // A BpPropertyTag to add to a property that contains references to other sdk members.
 //
 // This will cause the references to be rewritten to a versioned reference in the version
@@ -1041,6 +1077,9 @@
 
 	// The set of exported members by name.
 	exportedMembersByName map[string]struct{}
+
+	// The target build release for which the snapshot is to be generated.
+	targetBuildRelease *buildRelease
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -1383,19 +1422,19 @@
 	osInfo.Properties = osSpecificVariantPropertiesFactory()
 
 	// Group the variants by arch type.
-	var variantsByArchName = make(map[string][]android.Module)
-	var archTypes []android.ArchType
+	var variantsByArchId = make(map[archId][]android.Module)
+	var archIds []archId
 	for _, variant := range osTypeVariants {
-		archType := variant.Target().Arch.ArchType
-		archTypeName := archType.Name
-		if _, ok := variantsByArchName[archTypeName]; !ok {
-			archTypes = append(archTypes, archType)
+		target := variant.Target()
+		id := archIdFromTarget(target)
+		if _, ok := variantsByArchId[id]; !ok {
+			archIds = append(archIds, id)
 		}
 
-		variantsByArchName[archTypeName] = append(variantsByArchName[archTypeName], variant)
+		variantsByArchId[id] = append(variantsByArchId[id], variant)
 	}
 
-	if commonVariants, ok := variantsByArchName["common"]; ok {
+	if commonVariants, ok := variantsByArchId[commonArchId]; ok {
 		if len(osTypeVariants) != 1 {
 			panic(fmt.Errorf("Expected to only have 1 variant when arch type is common but found %d", len(osTypeVariants)))
 		}
@@ -1405,11 +1444,9 @@
 		osInfo.Properties.PopulateFromVariant(ctx, commonVariants[0])
 	} else {
 		// Create an arch specific info for each supported architecture type.
-		for _, archType := range archTypes {
-			archTypeName := archType.Name
-
-			archVariants := variantsByArchName[archTypeName]
-			archInfo := newArchSpecificInfo(ctx, archType, osType, osSpecificVariantPropertiesFactory, archVariants)
+		for _, id := range archIds {
+			archVariants := variantsByArchId[id]
+			archInfo := newArchSpecificInfo(ctx, id, osType, osSpecificVariantPropertiesFactory, archVariants)
 
 			osInfo.archInfos = append(osInfo.archInfos, archInfo)
 		}
@@ -1418,6 +1455,16 @@
 	return osInfo
 }
 
+func (osInfo *osTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(osInfo.archInfos) == 0 {
+		pruner.pruneProperties(osInfo.Properties)
+	} else {
+		for _, archInfo := range osInfo.archInfos {
+			archInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from arch type specific
 // properties into os type specific properties.
 func (osInfo *osTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
@@ -1428,7 +1475,7 @@
 
 	multilib := multilibNone
 	for _, archInfo := range osInfo.archInfos {
-		multilib = multilib.addArchType(archInfo.archType)
+		multilib = multilib.addArchType(archInfo.archId.archType)
 
 		// Optimize the arch properties first.
 		archInfo.optimizeProperties(ctx, commonValueExtractor)
@@ -1521,23 +1568,67 @@
 	return fmt.Sprintf("OsType{%s}", osInfo.osType)
 }
 
+// archId encapsulates the information needed to identify a combination of arch type and native
+// bridge support.
+//
+// Conceptually, native bridge support is a facet of an android.Target, not an android.Arch as it is
+// essentially using one android.Arch to implement another. However, in terms of the handling of
+// the variants native bridge is treated as part of the arch variation. See the ArchVariation method
+// on android.Target.
+//
+// So, it makes sense when optimizing the variants to combine native bridge with the arch type.
+type archId struct {
+	// The arch type of the variant's target.
+	archType android.ArchType
+
+	// True if the variants is for the native bridge, false otherwise.
+	nativeBridge bool
+}
+
+// propertyName returns the name of the property corresponding to use for this arch id.
+func (i *archId) propertyName() string {
+	name := i.archType.Name
+	if i.nativeBridge {
+		// Note: This does not result in a valid property because there is no architecture specific
+		// native bridge property, only a generic "native_bridge" property. However, this will be used
+		// in error messages if there is an attempt to use this in a generated bp file.
+		name += "_native_bridge"
+	}
+	return name
+}
+
+func (i *archId) String() string {
+	return fmt.Sprintf("ArchType{%s}, NativeBridge{%t}", i.archType, i.nativeBridge)
+}
+
+// archIdFromTarget returns an archId initialized from information in the supplied target.
+func archIdFromTarget(target android.Target) archId {
+	return archId{
+		archType:     target.Arch.ArchType,
+		nativeBridge: target.NativeBridge == android.NativeBridgeEnabled,
+	}
+}
+
+// commonArchId is the archId for the common architecture.
+var commonArchId = archId{archType: android.Common}
+
 type archTypeSpecificInfo struct {
 	baseInfo
 
-	archType android.ArchType
-	osType   android.OsType
+	archId archId
+	osType android.OsType
 
-	linkInfos []*linkTypeSpecificInfo
+	imageVariantInfos []*imageVariantSpecificInfo
 }
 
 var _ propertiesContainer = (*archTypeSpecificInfo)(nil)
 
 // Create a new archTypeSpecificInfo for the specified arch type and its properties
 // structures populated with information from the variants.
-func newArchSpecificInfo(ctx android.SdkMemberContext, archType android.ArchType, osType android.OsType, variantPropertiesFactory variantPropertiesFactoryFunc, archVariants []android.Module) *archTypeSpecificInfo {
+func newArchSpecificInfo(ctx android.SdkMemberContext, archId archId, osType android.OsType, variantPropertiesFactory variantPropertiesFactoryFunc, archVariants []android.Module) *archTypeSpecificInfo {
 
 	// Create an arch specific info into which the variant properties can be copied.
-	archInfo := &archTypeSpecificInfo{archType: archType, osType: osType}
+	archInfo := &archTypeSpecificInfo{archId: archId, osType: osType}
 
 	// Create the properties into which the arch type specific properties will be
 	// added.
@@ -1546,27 +1637,23 @@
 	if len(archVariants) == 1 {
 		archInfo.Properties.PopulateFromVariant(ctx, archVariants[0])
 	} else {
-		// There is more than one variant for this arch type which must be differentiated
-		// by link type.
-		for _, linkVariant := range archVariants {
-			linkType := getLinkType(linkVariant)
-			if linkType == "" {
-				panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(archVariants)))
-			} else {
-				linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant)
+		// Group the variants by image type.
+		variantsByImage := make(map[string][]android.Module)
+		for _, variant := range archVariants {
+			image := variant.ImageVariation().Variation
+			variantsByImage[image] = append(variantsByImage[image], variant)
+		}
 
-				archInfo.linkInfos = append(archInfo.linkInfos, linkInfo)
-			}
+		// Create the image variant info in a fixed order.
+		for _, imageVariantName := range android.SortedStringKeys(variantsByImage) {
+			variants := variantsByImage[imageVariantName]
+			archInfo.imageVariantInfos = append(archInfo.imageVariantInfos, newImageVariantSpecificInfo(ctx, imageVariantName, variantPropertiesFactory, variants))
 		}
 	}
 
 	return archInfo
 }
 
-func (archInfo *archTypeSpecificInfo) optimizableProperties() interface{} {
-	return archInfo.Properties
-}
-
 // Get the link type of the variant
 //
 // If the variant is not differentiated by link type then it returns "",
@@ -1587,34 +1674,159 @@
 	return linkType
 }
 
+func (archInfo *archTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(archInfo.imageVariantInfos) == 0 {
+		pruner.pruneProperties(archInfo.Properties)
+	} else {
+		for _, imageVariantInfo := range archInfo.imageVariantInfos {
+			imageVariantInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from link type specific
 // properties into arch type specific properties.
 func (archInfo *archTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
-	if len(archInfo.linkInfos) == 0 {
+	if len(archInfo.imageVariantInfos) == 0 {
 		return
 	}
 
-	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.linkInfos)
+	// Optimize the image variant properties first.
+	for _, imageVariantInfo := range archInfo.imageVariantInfos {
+		imageVariantInfo.optimizeProperties(ctx, commonValueExtractor)
+	}
+
+	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.imageVariantInfos)
 }
 
 // Add the properties for an arch type to a property set.
 func (archInfo *archTypeSpecificInfo) addToPropertySet(ctx *memberContext, archPropertySet android.BpPropertySet, archOsPrefix string) {
-	archTypeName := archInfo.archType.Name
-	archTypePropertySet := archPropertySet.AddPropertySet(archOsPrefix + archTypeName)
+	archPropertySuffix := archInfo.archId.propertyName()
+	propertySetName := archOsPrefix + archPropertySuffix
+	archTypePropertySet := archPropertySet.AddPropertySet(propertySetName)
 	// Enable the <os>_<arch> variant explicitly when we've disabled it by default on host.
 	if ctx.memberType.IsHostOsDependent() && archInfo.osType.Class == android.Host {
 		archTypePropertySet.AddProperty("enabled", true)
 	}
 	addSdkMemberPropertiesToSet(ctx, archInfo.Properties, archTypePropertySet)
 
-	for _, linkInfo := range archInfo.linkInfos {
-		linkPropertySet := archTypePropertySet.AddPropertySet(linkInfo.linkType)
-		addSdkMemberPropertiesToSet(ctx, linkInfo.Properties, linkPropertySet)
+	for _, imageVariantInfo := range archInfo.imageVariantInfos {
+		imageVariantInfo.addToPropertySet(ctx, archTypePropertySet)
+	}
+
+	// If this is for a native bridge architecture then make sure that the property set does not
+	// contain any properties as providing native bridge specific properties is not currently
+	// supported.
+	if archInfo.archId.nativeBridge {
+		propertySetContents := getPropertySetContents(archTypePropertySet)
+		if propertySetContents != "" {
+			ctx.SdkModuleContext().ModuleErrorf("Architecture variant %q of sdk member %q has properties distinct from other variants; this is not yet supported. The properties are:\n%s",
+				propertySetName, ctx.name, propertySetContents)
+		}
 	}
 }
 
+// getPropertySetContents returns the string representation of the contents of a property set, after
+// recursively pruning any empty nested property sets.
+func getPropertySetContents(propertySet android.BpPropertySet) string {
+	set := propertySet.(*bpPropertySet)
+	set.transformContents(pruneEmptySetTransformer{})
+	if len(set.properties) != 0 {
+		contents := &generatedContents{}
+		contents.Indent()
+		outputPropertySet(contents, set)
+		setAsString := contents.content.String()
+		return setAsString
+	}
+	return ""
+}
+
 func (archInfo *archTypeSpecificInfo) String() string {
-	return fmt.Sprintf("ArchType{%s}", archInfo.archType)
+	return archInfo.archId.String()
+}
+
+type imageVariantSpecificInfo struct {
+	baseInfo
+
+	imageVariant string
+
+	linkInfos []*linkTypeSpecificInfo
+}
+
+func newImageVariantSpecificInfo(ctx android.SdkMemberContext, imageVariant string, variantPropertiesFactory variantPropertiesFactoryFunc, imageVariants []android.Module) *imageVariantSpecificInfo {
+
+	// Create an image variant specific info into which the variant properties can be copied.
+	imageInfo := &imageVariantSpecificInfo{imageVariant: imageVariant}
+
+	// Create the properties into which the image variant specific properties will be added.
+	imageInfo.Properties = variantPropertiesFactory()
+
+	if len(imageVariants) == 1 {
+		imageInfo.Properties.PopulateFromVariant(ctx, imageVariants[0])
+	} else {
+		// There is more than one variant for this image variant which must be differentiated by link
+		// type.
+		for _, linkVariant := range imageVariants {
+			linkType := getLinkType(linkVariant)
+			if linkType == "" {
+				panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(imageVariants)))
+			} else {
+				linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant)
+
+				imageInfo.linkInfos = append(imageInfo.linkInfos, linkInfo)
+			}
+		}
+	}
+
+	return imageInfo
+}
+
+func (imageInfo *imageVariantSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(imageInfo.linkInfos) == 0 {
+		pruner.pruneProperties(imageInfo.Properties)
+	} else {
+		for _, linkInfo := range imageInfo.linkInfos {
+			linkInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
+// Optimize the properties by extracting common properties from link type specific
+// properties into arch type specific properties.
+func (imageInfo *imageVariantSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
+	if len(imageInfo.linkInfos) == 0 {
+		return
+	}
+
+	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, imageInfo.Properties, imageInfo.linkInfos)
+}
+
+// Add the properties for an arch type to a property set.
+func (imageInfo *imageVariantSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) {
+	if imageInfo.imageVariant != android.CoreVariation {
+		propertySet = propertySet.AddPropertySet(imageInfo.imageVariant)
+	}
+
+	addSdkMemberPropertiesToSet(ctx, imageInfo.Properties, propertySet)
+
+	for _, linkInfo := range imageInfo.linkInfos {
+		linkInfo.addToPropertySet(ctx, propertySet)
+	}
+
+	// If this is for a non-core image variant then make sure that the property set does not contain
+	// any properties as providing non-core image variant specific properties for prebuilts is not
+	// currently supported.
+	if imageInfo.imageVariant != android.CoreVariation {
+		propertySetContents := getPropertySetContents(propertySet)
+		if propertySetContents != "" {
+			ctx.SdkModuleContext().ModuleErrorf("Image variant %q of sdk member %q has properties distinct from other variants; this is not yet supported. The properties are:\n%s",
+				imageInfo.imageVariant, ctx.name, propertySetContents)
+		}
+	}
+}
+
+func (imageInfo *imageVariantSpecificInfo) String() string {
+	return imageInfo.imageVariant
 }
 
 type linkTypeSpecificInfo struct {
@@ -1640,6 +1852,15 @@
 	return linkInfo
 }
 
+func (l *linkTypeSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) {
+	linkPropertySet := propertySet.AddPropertySet(l.linkType)
+	addSdkMemberPropertiesToSet(ctx, l.Properties, linkPropertySet)
+}
+
+func (l *linkTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	pruner.pruneProperties(l.Properties)
+}
+
 func (l *linkTypeSpecificInfo) String() string {
 	return fmt.Sprintf("LinkType{%s}", l.linkType)
 }
@@ -1649,6 +1870,9 @@
 	builder          *snapshotBuilder
 	memberType       android.SdkMemberType
 	name             string
+
+	// The set of traits required of this member.
+	requiredTraits android.SdkMemberTraitSet
 }
 
 func (m *memberContext) SdkModuleContext() android.ModuleContext {
@@ -1667,17 +1891,21 @@
 	return m.name
 }
 
+func (m *memberContext) RequiresTrait(trait android.SdkMemberTrait) bool {
+	return m.requiredTraits.Contains(trait)
+}
+
 func (s *sdk) createMemberSnapshot(ctx *memberContext, member *sdkMember, bpModule *bpModule) {
 
 	memberType := member.memberType
 
 	// Do not add the prefer property if the member snapshot module is a source module type.
+	config := ctx.sdkMemberContext.Config()
 	if !memberType.UsesSourceModuleTypeInSnapshot() {
 		// Set the prefer based on the environment variable. This is a temporary work around to allow a
 		// snapshot to be created that sets prefer: true.
 		// TODO(b/174997203): Remove once the ability to select the modules to prefer can be done
 		//  dynamically at build time not at snapshot generation time.
-		config := ctx.sdkMemberContext.Config()
 		prefer := config.IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER")
 
 		// Set prefer. Setting this to false is not strictly required as that is the default but it does
@@ -1719,6 +1947,11 @@
 	commonProperties := variantPropertiesFactory()
 	commonProperties.Base().Os = android.CommonOS
 
+	// Create a property pruner that will prune any properties unsupported by the target build
+	// release.
+	targetBuildRelease := ctx.builder.targetBuildRelease
+	unsupportedPropertyPruner := newPropertyPrunerByBuildRelease(commonProperties, targetBuildRelease)
+
 	// Create common value extractor that can be used to optimize the properties.
 	commonValueExtractor := newCommonValueExtractor(commonProperties)
 
@@ -1733,6 +1966,8 @@
 		// independent properties structs.
 		osSpecificPropertiesContainers = append(osSpecificPropertiesContainers, osInfo)
 
+		osInfo.pruneUnsupportedProperties(unsupportedPropertyPruner)
+
 		// Optimize the properties across all the variants for a specific os type.
 		osInfo.optimizeProperties(ctx, commonValueExtractor)
 	}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index c6e98c7..e1df8ac 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -26,6 +26,7 @@
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/cc"
+	"android/soong/snapshot"
 	"android/soong/tradefed"
 )
 
@@ -41,8 +42,6 @@
 	pctx.Import("android/soong/android")
 
 	registerShBuildComponents(android.InitRegistrationContext)
-
-	android.RegisterBp2BuildMutator("sh_binary", ShBinaryBp2Build)
 }
 
 func registerShBuildComponents(ctx android.RegistrationContext) {
@@ -195,6 +194,9 @@
 	return proptools.String(s.properties.Sub_dir)
 }
 
+func (s *ShBinary) RelativeInstallPath() string {
+	return s.SubDir()
+}
 func (s *ShBinary) Installable() bool {
 	return s.properties.Installable == nil || proptools.Bool(s.properties.Installable)
 }
@@ -267,13 +269,16 @@
 	s.generateAndroidBuildActions(ctx)
 	installDir := android.PathForModuleInstall(ctx, "bin", proptools.String(s.properties.Sub_dir))
 	s.installedFile = ctx.InstallExecutable(installDir, s.outputFilePath.Base(), s.outputFilePath)
+	for _, symlink := range s.Symlinks() {
+		ctx.InstallSymlink(installDir, symlink, s.installedFile)
+	}
 }
 
 func (s *ShBinary) AndroidMkEntries() []android.AndroidMkEntries {
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "EXECUTABLES",
 		OutputFile: android.OptionalPathForPath(s.outputFilePath),
-		Include:    "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
@@ -422,11 +427,11 @@
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "NATIVE_TESTS",
 		OutputFile: android.OptionalPathForPath(s.outputFilePath),
-		Include:    "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
-				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", s.installDir)
 				entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
@@ -505,7 +510,9 @@
 }
 
 type bazelShBinaryAttributes struct {
-	Srcs bazel.LabelListAttribute
+	Srcs     bazel.LabelListAttribute
+	Filename string
+	Sub_dir  string
 	// Bazel also supports the attributes below, but (so far) these are not required for Bionic
 	// deps
 	// data
@@ -527,24 +534,34 @@
 	// visibility
 }
 
-func ShBinaryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*ShBinary)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
+func (m *ShBinary) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	srcs := bazel.MakeLabelListAttribute(
 		android.BazelLabelForModuleSrc(ctx, []string{*m.properties.Src}))
 
+	var filename string
+	if m.properties.Filename != nil {
+		filename = *m.properties.Filename
+	}
+
+	var subDir string
+	if m.properties.Sub_dir != nil {
+		subDir = *m.properties.Sub_dir
+	}
+
 	attrs := &bazelShBinaryAttributes{
-		Srcs: srcs,
+		Srcs:     srcs,
+		Filename: filename,
+		Sub_dir:  subDir,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
-		Rule_class: "sh_binary",
+		Rule_class:        "sh_binary",
+		Bzl_load_location: "//build/bazel/rules:sh_binary.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 var Bool = proptools.Bool
+
+var _ snapshot.RelativeInstallPath = (*ShBinary)(nil)
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 865d5f3..28b6fb9 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -42,7 +42,10 @@
 }
 
 func TestShTestSubDir(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -50,17 +53,20 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo_test"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 }
 
 func TestShTest(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -72,13 +78,13 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 
 	expectedData := []string{":testdata/data1", ":testdata/sub/data2"}
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
diff --git a/shared/Android.bp b/shared/Android.bp
index deb17f8..3c84f55 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -9,11 +9,13 @@
         "env.go",
         "paths.go",
         "debug.go",
+        "proto.go",
     ],
     testSrcs: [
         "paths_test.go",
     ],
     deps: [
         "soong-bazel",
+        "golang-protobuf-proto",
     ],
 }
diff --git a/shared/proto.go b/shared/proto.go
new file mode 100644
index 0000000..232656b
--- /dev/null
+++ b/shared/proto.go
@@ -0,0 +1,41 @@
+// Copyright 2021 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 shared
+
+import (
+	"io/ioutil"
+	"os"
+
+	"google.golang.org/protobuf/proto"
+)
+
+// Save takes a protobuf message, marshals to an array of bytes
+// and is then saved to a file.
+func Save(pb proto.Message, filepath string) (err error) {
+	data, err := proto.Marshal(pb)
+	if err != nil {
+		return err
+	}
+	tempFilepath := filepath + ".tmp"
+	if err := ioutil.WriteFile(tempFilepath, []byte(data), 0644 /* rw-r--r-- */); err != nil {
+		return err
+	}
+
+	if err := os.Rename(tempFilepath, filepath); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/snapshot/Android.bp b/snapshot/Android.bp
index f17ac53..3354993 100644
--- a/snapshot/Android.bp
+++ b/snapshot/Android.bp
@@ -11,12 +11,20 @@
         "soong",
         "soong-android",
     ],
+    // Source file name convention is to include _snapshot as a
+    // file suffix for files that are generating snapshots.
     srcs: [
+        "host_fake_snapshot.go",
+        "host_snapshot.go",
         "recovery_snapshot.go",
         "snapshot.go",
         "snapshot_base.go",
         "util.go",
         "vendor_snapshot.go",
     ],
+    testSrcs: [
+        "host_test.go",
+        "test.go",
+    ],
     pluginFor: ["soong_build"],
 }
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
new file mode 100644
index 0000000..6b4e12b
--- /dev/null
+++ b/snapshot/host_fake_snapshot.go
@@ -0,0 +1,149 @@
+// Copyright 2021 The Android Open Source Project
+//
+// 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 snapshot
+
+import (
+	"encoding/json"
+	"path/filepath"
+
+	"android/soong/android"
+)
+
+// The host_snapshot module creates a snapshot of host tools to be used
+// in a minimal source tree.   In order to create the host_snapshot the
+// user must explicitly list the modules to be included.  The
+// host-fake-snapshot, defined in this file, is a utility to help determine
+// which host modules are being used in the minimal source tree.
+//
+// The host-fake-snapshot is designed to run in a full source tree and
+// will result in a snapshot that contains an empty file for each host
+// tool found in the tree.  The fake snapshot is only used to determine
+// the host modules that the minimal source tree depends on, hence the
+// snapshot uses an empty file for each module and saves on having to
+// actually build any tool to generate the snapshot.  The fake snapshot
+// is compatible with an actual host_snapshot and is installed into a
+// minimal source tree via the development/vendor_snapshot/update.py
+// script.
+//
+// After generating the fake snapshot and installing into the minimal
+// source tree, the dependent modules are determined via the
+// development/vendor_snapshot/update.py script (see script for more
+// information).  These modules are then used to define the actual
+// host_snapshot to be used.  This is a similar process to the other
+// snapshots (vendor, recovery,...)
+//
+// Example
+//
+// Full source tree:
+//   1/ Generate fake host snapshot
+//
+// Minimal source tree:
+//   2/ Install the fake host snapshot
+//   3/ List the host modules used from the snapshot
+//   4/ Remove fake host snapshot
+//
+// Full source tree:
+//   4/ Create host_snapshot with modules identified in step 3
+//
+// Minimal source tree:
+//   5/ Install host snapshot
+//   6/ Build
+//
+// The host-fake-snapshot is a singleton module, that will be built
+// if HOST_FAKE_SNAPSHOT_ENABLE=true.
+
+func init() {
+	registerHostSnapshotComponents(android.InitRegistrationContext)
+}
+
+func registerHostSnapshotComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton)
+}
+
+type hostFakeSingleton struct {
+	snapshotDir string
+	zipFile     android.OptionalPath
+}
+
+func (c *hostFakeSingleton) init() {
+	c.snapshotDir = "host-fake-snapshot"
+
+}
+func HostToolsFakeAndroidSingleton() android.Singleton {
+	singleton := &hostFakeSingleton{}
+	singleton.init()
+	return singleton
+}
+
+func (c *hostFakeSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	if !ctx.DeviceConfig().HostFakeSnapshotEnabled() {
+		return
+	}
+	// Find all host binary modules add 'fake' versions to snapshot
+	var outputs android.Paths
+	seen := make(map[string]bool)
+	var jsonData []SnapshotJsonFlags
+	ctx.VisitAllModules(func(module android.Module) {
+		if module.Target().Os != ctx.Config().BuildOSTarget.Os {
+			return
+		}
+		if module.Target().Arch.ArchType != ctx.Config().BuildOSTarget.Arch.ArchType {
+			return
+		}
+
+		if android.IsModulePrebuilt(module) {
+			return
+		}
+
+		if !module.Enabled() || module.IsHideFromMake() {
+			return
+		}
+		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
+		if !apexInfo.IsForPlatform() {
+			return
+		}
+		path := hostBinToolPath(module)
+		if path.Valid() && path.String() != "" {
+			outFile := filepath.Join(c.snapshotDir, path.String())
+			if !seen[outFile] {
+				seen[outFile] = true
+				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
+				jsonData = append(jsonData, *hostBinJsonDesc(module))
+			}
+		}
+	})
+
+	marsh, err := json.Marshal(jsonData)
+	if err != nil {
+		ctx.Errorf("host fake snapshot json marshal failure: %#v", err)
+		return
+	}
+	outputs = append(outputs, WriteStringToFileRule(ctx, string(marsh), filepath.Join(c.snapshotDir, "host_snapshot.json")))
+	c.zipFile = zipSnapshot(ctx, c.snapshotDir, c.snapshotDir, outputs)
+
+}
+func (c *hostFakeSingleton) MakeVars(ctx android.MakeVarsContext) {
+	if !c.zipFile.Valid() {
+		return
+	}
+	ctx.Phony(
+		"host-fake-snapshot",
+		c.zipFile.Path())
+
+	ctx.DistForGoal(
+		"host-fake-snapshot",
+		c.zipFile.Path())
+
+}
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
new file mode 100644
index 0000000..252cef8
--- /dev/null
+++ b/snapshot/host_snapshot.go
@@ -0,0 +1,221 @@
+// Copyright 2021 The Android Open Source Project
+//
+// 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 snapshot
+
+import (
+	"encoding/json"
+	"fmt"
+	"path/filepath"
+	"sort"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+//
+// The host_snapshot module creates a snapshot of the modules defined in
+// the deps property.  The modules within the deps property (host tools)
+// are ones that return a valid path via HostToolPath() of the
+// HostToolProvider.  The created snapshot contains the binaries and any
+// transitive PackagingSpecs of the included host tools, along with a JSON
+// meta file.
+//
+// The snapshot is installed into a source tree via
+// development/vendor_snapshot/update.py, the included modules are
+// provided as preferred prebuilts.
+//
+// To determine which tools to include in the host snapshot see
+// host_fake_snapshot.go.
+
+func init() {
+	registerHostBuildComponents(android.InitRegistrationContext)
+}
+
+func registerHostBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("host_snapshot", hostSnapshotFactory)
+}
+
+// Relative installation path
+type RelativeInstallPath interface {
+	RelativeInstallPath() string
+}
+
+type hostSnapshot struct {
+	android.ModuleBase
+	android.PackagingBase
+
+	zipFile    android.OptionalPath
+	installDir android.InstallPath
+}
+
+func hostSnapshotFactory() android.Module {
+	module := &hostSnapshot{}
+	initHostToolsModule(module)
+	return module
+}
+func initHostToolsModule(module *hostSnapshot) {
+	android.InitPackageModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.HostSupported, android.MultilibCommon)
+}
+
+var dependencyTag = struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	android.PackagingItemAlwaysDepTag
+}{}
+
+func (f *hostSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
+	f.AddDeps(ctx, dependencyTag)
+}
+func (f *hostSnapshot) installFileName() string {
+	return f.Name() + ".zip"
+}
+
+// Create zipfile with JSON description, notice files... for dependent modules
+func (f *hostSnapshot) CreateMetaData(ctx android.ModuleContext, fileName string) android.OutputPath {
+	var jsonData []SnapshotJsonFlags
+	var metaPaths android.Paths
+
+	metaZipFile := android.PathForModuleOut(ctx, fileName).OutputPath
+
+	// Create JSON file based on the direct dependencies
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		desc := hostBinJsonDesc(dep)
+		if desc != nil {
+			jsonData = append(jsonData, *desc)
+		}
+		if len(dep.EffectiveLicenseFiles()) > 0 {
+			noticeFile := android.PathForModuleOut(ctx, "NOTICE_FILES", dep.Name()+".txt").OutputPath
+			android.CatFileRule(ctx, dep.EffectiveLicenseFiles(), noticeFile)
+			metaPaths = append(metaPaths, noticeFile)
+		}
+
+	})
+	// Sort notice paths and json data for repeatble build
+	sort.Slice(jsonData, func(i, j int) bool {
+		return (jsonData[i].ModuleName < jsonData[j].ModuleName)
+	})
+	sort.Slice(metaPaths, func(i, j int) bool {
+		return (metaPaths[i].String() < metaPaths[j].String())
+	})
+
+	marsh, err := json.Marshal(jsonData)
+	if err != nil {
+		ctx.ModuleErrorf("host snapshot json marshal failure: %#v", err)
+		return android.OutputPath{}
+	}
+
+	jsonZipFile := android.PathForModuleOut(ctx, "host_snapshot.json").OutputPath
+	metaPaths = append(metaPaths, jsonZipFile)
+	rspFile := android.PathForModuleOut(ctx, "host_snapshot.rsp").OutputPath
+	android.WriteFileRule(ctx, jsonZipFile, string(marsh))
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	builder.Command().
+		BuiltTool("soong_zip").
+		FlagWithArg("-C ", android.PathForModuleOut(ctx).OutputPath.String()).
+		FlagWithOutput("-o ", metaZipFile).
+		FlagWithRspFileInputList("-r ", rspFile, metaPaths)
+	builder.Build("zip_meta", fmt.Sprintf("zipping meta data for %s", ctx.ModuleName()))
+
+	return metaZipFile
+}
+
+// Create the host tool zip file
+func (f *hostSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Create a zip file for the binaries, and a zip of the meta data, then merge zips
+	depsZipFile := android.PathForModuleOut(ctx, f.Name()+"_deps.zip").OutputPath
+	modsZipFile := android.PathForModuleOut(ctx, f.Name()+"_mods.zip").OutputPath
+	outputFile := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+
+	f.installDir = android.PathForModuleInstall(ctx)
+
+	f.CopyDepsToZip(ctx, depsZipFile)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", depsZipFile).
+		FlagWithOutput("-o ", modsZipFile).
+		Text("**/*:" + proptools.ShellEscape(f.installDir.String()))
+
+	metaZipFile := f.CreateMetaData(ctx, f.Name()+"_meta.zip")
+
+	builder.Command().
+		BuiltTool("merge_zips").
+		Output(outputFile).
+		Input(metaZipFile).
+		Input(modsZipFile)
+
+	builder.Build("manifest", fmt.Sprintf("Adding manifest %s", f.installFileName()))
+	zip := ctx.InstallFile(f.installDir, f.installFileName(), outputFile)
+	f.zipFile = android.OptionalPathForPath(zip)
+
+}
+
+// Implements android.AndroidMkEntriesProvider
+func (f *hostSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
+	if !f.zipFile.Valid() {
+		return []android.AndroidMkEntries{}
+	}
+
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: f.zipFile,
+		DistFiles:  android.MakeDefaultDistFiles(f.zipFile.Path()),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+			},
+		},
+	}}
+}
+
+// Get host tools path and relative install string helpers
+func hostBinToolPath(m android.Module) android.OptionalPath {
+	if provider, ok := m.(android.HostToolProvider); ok {
+		return provider.HostToolPath()
+	}
+	return android.OptionalPath{}
+
+}
+func hostRelativePathString(m android.Module) string {
+	var outString string
+	if rel, ok := m.(RelativeInstallPath); ok {
+		outString = rel.RelativeInstallPath()
+	}
+	return outString
+}
+
+// Create JSON description for given module, only create descriptions for binary modueles which
+// provide a valid HostToolPath
+func hostBinJsonDesc(m android.Module) *SnapshotJsonFlags {
+	path := hostBinToolPath(m)
+	relPath := hostRelativePathString(m)
+	if path.Valid() && path.String() != "" {
+		return &SnapshotJsonFlags{
+			ModuleName:          m.Name(),
+			ModuleStemName:      filepath.Base(path.String()),
+			Filename:            path.String(),
+			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
+			RelativeInstallPath: relPath,
+		}
+	}
+	return nil
+}
diff --git a/snapshot/host_test.go b/snapshot/host_test.go
new file mode 100644
index 0000000..ab9fedd
--- /dev/null
+++ b/snapshot/host_test.go
@@ -0,0 +1,170 @@
+// Copyright 2021 The Android Open Source Project
+//
+// 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 snapshot
+
+import (
+	"path/filepath"
+	"testing"
+
+	"android/soong/android"
+)
+
+// host_snapshot and host-fake-snapshot test functions
+
+type hostTestModule struct {
+	android.ModuleBase
+	props struct {
+		Deps []string
+	}
+}
+
+func hostTestBinOut(bin string) string {
+	return filepath.Join("out", "bin", bin)
+}
+
+func (c *hostTestModule) HostToolPath() android.OptionalPath {
+	return (android.OptionalPathForPath(android.PathForTesting(hostTestBinOut(c.Name()))))
+}
+
+func hostTestModuleFactory() android.Module {
+	m := &hostTestModule{}
+	m.AddProperties(&m.props)
+	android.InitAndroidArchModule(m, android.HostSupported, android.MultilibFirst)
+	return m
+}
+func (m *hostTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	builtFile := android.PathForModuleOut(ctx, m.Name())
+	dir := ctx.Target().Arch.ArchType.Multilib
+	installDir := android.PathForModuleInstall(ctx, dir)
+	ctx.InstallFile(installDir, m.Name(), builtFile)
+}
+
+// Common blueprint used for testing
+var hostTestBp = `
+		license_kind {
+			name: "test_notice",
+			conditions: ["notice"],
+		}
+		license {
+			name: "host_test_license",
+			visibility: ["//visibility:public"],
+			license_kinds: [
+				"test_notice"
+			],
+			license_text: [
+				"NOTICE",
+			],
+		}
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+		component {
+			name: "bar",
+			licenses: ["host_test_license"],
+		}
+		`
+
+var hostTestModBp = `
+		host_snapshot {
+			name: "test-host-snapshot",
+			deps: [
+				"foo",
+			],
+		}
+		`
+
+var prepareForHostTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.PrepareForTestWithLicenses,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("component", hostTestModuleFactory)
+	}),
+)
+
+// Prepare for host_snapshot test
+var prepareForHostModTest = android.GroupFixturePreparers(
+	prepareForHostTest,
+	android.FixtureWithRootAndroidBp(hostTestBp+hostTestModBp),
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		registerHostBuildComponents(ctx)
+	}),
+)
+
+// Prepare for fake host snapshot test disabled
+var prepareForFakeHostTest = android.GroupFixturePreparers(
+	prepareForHostTest,
+	android.FixtureWithRootAndroidBp(hostTestBp),
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		registerHostSnapshotComponents(ctx)
+	}),
+)
+
+// Prepare for fake host snapshot test enabled
+var prepareForFakeHostTestEnabled = android.GroupFixturePreparers(
+	prepareForFakeHostTest,
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.HostFakeSnapshotEnabled = true
+	}),
+)
+
+// Validate that a hostSnapshot object is created containing zip files and JSON file
+// content of zip file is not validated as this is done by PackagingSpecs
+func TestHostSnapshot(t *testing.T) {
+	result := prepareForHostModTest.RunTest(t)
+	t.Helper()
+	ctx := result.TestContext.ModuleForTests("test-host-snapshot", result.Config.BuildOS.String()+"_common")
+	mod := ctx.Module().(*hostSnapshot)
+	if ctx.MaybeOutput("host_snapshot.json").Rule == nil {
+		t.Error("Manifest file not found")
+	}
+	zips := []string{"_deps.zip", "_mods.zip", ".zip"}
+
+	for _, zip := range zips {
+		zFile := mod.Name() + zip
+		if ctx.MaybeOutput(zFile).Rule == nil {
+			t.Error("Zip file ", zFile, "not found")
+		}
+
+	}
+}
+
+// Validate fake host snapshot contains binary modules as well as the JSON meta file
+func TestFakeHostSnapshotEnable(t *testing.T) {
+	result := prepareForFakeHostTestEnabled.RunTest(t)
+	t.Helper()
+	bins := []string{"foo", "bar"}
+	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
+	if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", "host_snapshot.json")).Rule == nil {
+		t.Error("Manifest file not found")
+	}
+	for _, bin := range bins {
+		if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", hostTestBinOut(bin))).Rule == nil {
+			t.Error("Binary file ", bin, "not found")
+		}
+
+	}
+}
+
+// Validate not fake host snapshot if HostFakeSnapshotEnabled has not been set to true
+func TestFakeHostSnapshotDisable(t *testing.T) {
+	result := prepareForFakeHostTest.RunTest(t)
+	t.Helper()
+	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
+	if len(ctx.AllOutputs()) != 0 {
+		t.Error("Fake host snapshot not empty when disabled")
+	}
+
+}
diff --git a/snapshot/recovery_snapshot.go b/snapshot/recovery_snapshot.go
index 9b3919c..f1e31ca 100644
--- a/snapshot/recovery_snapshot.go
+++ b/snapshot/recovery_snapshot.go
@@ -71,6 +71,10 @@
 	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
 }
 
+func (RecoverySnapshotImage) RegisterAdditionalModule(ctx android.RegistrationContext, name string, factory android.ModuleFactory) {
+	ctx.RegisterModuleType(name, factory)
+}
+
 func (RecoverySnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
 	// RECOVERY_SNAPSHOT_VERSION must be set to 'current' in order to generate a
 	// snapshot.
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index de93f3e..79d3cf6 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -102,3 +102,19 @@
 		return isDirectoryExcluded(filepath.Dir(dir), excludedMap, includedMap)
 	}
 }
+
+// This is to be saved as .json files, which is for development/vendor_snapshot/update.py.
+// These flags become Android.bp snapshot module properties.
+//
+// Attributes are optional and will be populated based on each module's need.
+// Common attributes are defined here, languages may extend this struct to add
+// additional attributes.
+type SnapshotJsonFlags struct {
+	ModuleName          string `json:",omitempty"`
+	RelativeInstallPath string `json:",omitempty"`
+	Filename            string `json:",omitempty"`
+	ModuleStemName      string `json:",omitempty"`
+
+	// dependencies
+	Required []string `json:",omitempty"`
+}
diff --git a/snapshot/test.go b/snapshot/test.go
new file mode 100644
index 0000000..346af2b
--- /dev/null
+++ b/snapshot/test.go
@@ -0,0 +1,24 @@
+// Copyright 2021 The Android Open Source Project
+//
+// 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 snapshot
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMain(m *testing.M) {
+	os.Exit(m.Run())
+}
diff --git a/snapshot/util.go b/snapshot/util.go
index 2297dfc..f447052 100644
--- a/snapshot/util.go
+++ b/snapshot/util.go
@@ -34,3 +34,22 @@
 	})
 	return outPath
 }
+
+// zip snapshot
+func zipSnapshot(ctx android.SingletonContext, dir string, baseName string, snapshotOutputs android.Paths) android.OptionalPath {
+	zipPath := android.PathForOutput(
+		ctx, dir, baseName+".zip")
+
+	zipRule := android.NewRuleBuilder(pctx, ctx)
+	rspFile := android.PathForOutput(
+		ctx, dir, baseName+"_list.rsp")
+
+	zipRule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", zipPath).
+		FlagWithArg("-C ", android.PathForOutput(ctx, dir).String()).
+		FlagWithRspFileInputList("-r ", rspFile, snapshotOutputs)
+
+	zipRule.Build(zipPath.String(), baseName+" snapshot "+zipPath.String())
+	return android.OptionalPathForPath(zipPath)
+}
diff --git a/soong.bash b/soong.bash
index 8cf2ec6..db7af7c 100755
--- a/soong.bash
+++ b/soong.bash
@@ -15,8 +15,8 @@
 # limitations under the License.
 
 echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
-echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Use `m --soong-only` with a standalone OUT_DIR instead.' >&2
 echo 'Without envsetup.sh, use:' >&2
-echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '  build/soong/soong_ui.bash --make-mode --soong-only' >&2
 echo '======================================================' >&2
 exit 1
diff --git a/tests/androidmk_test.sh b/tests/androidmk_test.sh
new file mode 100755
index 0000000..331dc77
--- /dev/null
+++ b/tests/androidmk_test.sh
@@ -0,0 +1,135 @@
+#!/bin/bash -eu
+
+set -o pipefail
+
+# How to run: bash path-to-script/androidmk_test.sh
+# Tests of converting license functionality of the androidmk tool
+REAL_TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+$REAL_TOP/build/soong/soong_ui.bash --make-mode androidmk
+
+source "$(dirname "$0")/lib.sh"
+
+# Expect to create a new license module
+function test_rewrite_license_property_inside_current_directory {
+  setup
+
+  # Create an Android.mk file
+  mkdir -p a/b
+  cat > a/b/Android.mk <<'EOF'
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind1 license_kind2
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/license_notice1 $(LOCAL_PATH)/license_notice2
+include $(BUILD_PACKAGE)
+EOF
+
+  # Create an expected Android.bp file for the module "foo"
+  cat > a/b/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+license {
+    name: "a_b_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "license_kind1",
+        "license_kind2",
+    ],
+    license_text: [
+        "license_notice1",
+        "license_notice2",
+    ],
+}
+
+android_app {
+    name: "foo",
+}
+EOF
+
+  run_androidmk_test "a/b/Android.mk" "a/b/Android.bp"
+}
+
+# Expect to reference to an existing license module
+function test_rewrite_license_property_outside_current_directory {
+  setup
+
+  # Create an Android.mk file
+  mkdir -p a/b/c/d
+  cat > a/b/c/d/Android.mk <<'EOF'
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind1 license_kind2
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../license_notice1 $(LOCAL_PATH)/../../license_notice2
+include $(BUILD_PACKAGE)
+EOF
+
+  # Create an expected (input) Android.bp file at a/b/
+  cat > a/b/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+license {
+    name: "a_b_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "license_kind1",
+        "license_kind2",
+    ],
+    license_text: [
+        "license_notice1",
+        "license_notice2",
+    ],
+}
+
+android_app {
+    name: "bar",
+}
+EOF
+
+  # Create an expected (output) Android.bp file for the module "foo"
+  cat > a/b/c/d/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+android_app {
+    name: "foo",
+}
+EOF
+
+  run_androidmk_test "a/b/c/d/Android.mk" "a/b/c/d/Android.bp"
+}
+
+run_androidmk_test () {
+  export ANDROID_BUILD_TOP="$MOCK_TOP"
+
+  local out=$($REAL_TOP/*/host/*/bin/androidmk "$1")
+  local expected=$(<"$2")
+
+  if [[ "$out" != "$expected" ]]; then
+    ANDROID_BUILD_TOP="$REAL_TOP"
+    cleanup_mock_top
+    fail "The output is not the same as the expected"
+  fi
+
+  ANDROID_BUILD_TOP="$REAL_TOP"
+  cleanup_mock_top
+  echo "Succeeded"
+}
+
+test_rewrite_license_property_inside_current_directory
+
+test_rewrite_license_property_outside_current_directory
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 95a193a..e92a561 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -17,10 +17,10 @@
 function test_null_build() {
   setup
   run_soong
-  local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
   local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
   run_soong
-  local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
   local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
@@ -36,12 +36,12 @@
 function test_soong_build_rebuilt_if_blueprint_changes() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Bootstrap Ninja file did not change"
@@ -472,17 +472,35 @@
   fi
 }
 
-function test_null_build_after_docs {
+function test_soong_docs_smoke() {
   setup
-  run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  prebuilts/build-tools/linux-x86/bin/ninja -f out/combined.ninja soong_docs
+  run_soong soong_docs
+
+  [[ -e "out/soong/docs/soong_build.html" ]] || fail "Documentation for main page not created"
+  [[ -e "out/soong/docs/cc.html" ]] || fail "Documentation for C++ modules not created"
+}
+
+function test_null_build_after_soong_docs() {
+  setup
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  if [[ "$mtime1" != "$mtime2" ]]; then
+  run_soong soong_docs
+  local docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html)
+
+  run_soong soong_docs
+  local docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html)
+
+  if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then
+    fail "Output Ninja file changed on null build"
+  fi
+
+  run_soong
+  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+
+  if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
     fail "Output Ninja file changed on null build"
   fi
 }
@@ -523,7 +541,7 @@
 function test_bp2build_smoke {
   setup
   run_soong bp2build
-  [[ -e out/soong/.bootstrap/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
+  [[ -e out/soong/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
 }
 
@@ -533,7 +551,7 @@
 
   run_soong bp2build
 
-  if [[ ! -f "./out/soong/.bootstrap/bp2build_workspace_marker" ]]; then
+  if [[ ! -f "./out/soong/bp2build_workspace_marker" ]]; then
     fail "Marker file was not generated"
   fi
 }
@@ -574,10 +592,10 @@
   setup
 
   run_soong bp2build
-  local mtime1=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong bp2build
-  local mtime2=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "Output Ninja file changed on null build"
@@ -608,7 +626,7 @@
 function test_multiple_soong_build_modes() {
   setup
   run_soong json-module-graph bp2build nothing
-  if [[ ! -f "out/soong/.bootstrap/bp2build_workspace_marker" ]]; then
+  if [[ ! -f "out/soong/bp2build_workspace_marker" ]]; then
     fail "bp2build marker file was not generated"
   fi
 
@@ -762,11 +780,11 @@
     fail "Output Ninja file changed when switching to bp2build"
   fi
 
-  local marker_mtime1=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local marker_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong
   local output_mtime3=$(stat -c "%y" out/soong/build.ninja)
-  local marker_mtime2=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local marker_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
   if [[ "$output_mtime1" != "$output_mtime3" ]]; then
     fail "Output Ninja file changed when switching to regular build from bp2build"
   fi
@@ -776,7 +794,7 @@
 
   run_soong bp2build
   local output_mtime4=$(stat -c "%y" out/soong/build.ninja)
-  local marker_mtime3=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local marker_mtime3=$(stat -c "%y" out/soong/bp2build_workspace_marker)
   if [[ "$output_mtime1" != "$output_mtime4" ]]; then
     fail "Output Ninja file changed when switching back to bp2build"
   fi
@@ -809,7 +827,8 @@
 
 test_smoke
 test_null_build
-test_null_build_after_docs
+test_soong_docs_smoke
+test_null_build_after_soong_docs
 test_soong_build_rebuilt_if_blueprint_changes
 test_glob_noop_incremental
 test_add_file_to_glob
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 379eb65..4f37c2b 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -11,10 +11,10 @@
 function test_bp2build_null_build() {
   setup
   run_soong bp2build
-  local output_mtime1=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local output_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong bp2build
-  local output_mtime2=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local output_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   if [[ "$output_mtime1" != "$output_mtime2" ]]; then
     fail "Output bp2build marker file changed on null build"
@@ -36,10 +36,10 @@
   touch foo/bar/a.txt foo/bar/b.txt
 
   run_soong bp2build
-  local output_mtime1=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local output_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong bp2build
-  local output_mtime2=$(stat -c "%y" out/soong/.bootstrap/bp2build_workspace_marker)
+  local output_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   if [[ "$output_mtime1" != "$output_mtime2" ]]; then
     fail "Output bp2build marker file changed on null build"
@@ -105,7 +105,7 @@
   # NOTE: We don't actually use the extra BUILD file for anything here
   run_bazel build --package_path=out/soong/workspace //foo/...
 
-  local the_answer_file="bazel-out/k8-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
+  local the_answer_file="bazel-out/android_target-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
   if [[ ! -f "${the_answer_file}" ]]; then
     fail "Expected '${the_answer_file}' to be generated, but was missing"
   fi
diff --git a/tests/lib.sh b/tests/lib.sh
index e777820..e6074f8 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -105,7 +105,7 @@
 }
 
 function run_soong() {
-  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests "$@"
+  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-config --soong-only --skip-soong-tests "$@"
 }
 
 function create_mock_bazel() {
@@ -125,7 +125,7 @@
 }
 
 run_ninja() {
-  build/soong/soong_ui.bash --make-mode --skip-make --skip-soong-tests "$@"
+  build/soong/soong_ui.bash --make-mode --skip-config --soong-only --skip-soong-tests "$@"
 }
 
 info "Starting Soong integration test suite $(basename $0)"
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 6304a11..76a918b 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -3,7 +3,9 @@
 set -o pipefail
 
 TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+"$TOP/build/soong/tests/androidmk_test.sh"
 "$TOP/build/soong/tests/bootstrap_test.sh"
 "$TOP/build/soong/tests/mixed_mode_test.sh"
 "$TOP/build/soong/tests/bp2build_bazel_test.sh"
 "$TOP/build/soong/tests/soong_test.sh"
+"$TOP/build/bazel/ci/rbc_regression_test.sh" aosp_arm64-userdebug
diff --git a/third_party/zip/reader_test.go b/third_party/zip/reader_test.go
index dfaae78..11c6d6e 100644
--- a/third_party/zip/reader_test.go
+++ b/third_party/zip/reader_test.go
@@ -786,7 +786,7 @@
 	}
 }
 
-// Verify the number of files is sane.
+// Verify the number of files is within expected bounds
 func TestIssue10956(t *testing.T) {
 	data := []byte("PK\x06\x06PK\x06\a0000\x00\x00\x00\x00\x00\x00\x00\x00" +
 		"0000PK\x05\x06000000000000" +
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index 3d96c84..da55829 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -227,17 +227,17 @@
 }
 
 func AutoGenRustTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool) android.Path {
+	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool, testInstallBase string) android.Path {
 	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, "")
+			autogenTemplate(ctx, autogenPath, templatePath.String(), config, testInstallBase)
 		} else {
 			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${RustDeviceTestConfigTemplate}", config, "")
+				autogenTemplate(ctx, autogenPath, "${RustDeviceTestConfigTemplate}", config, testInstallBase)
 			} else {
-				autogenTemplate(ctx, autogenPath, "${RustHostTestConfigTemplate}", config, "")
+				autogenTemplate(ctx, autogenPath, "${RustHostTestConfigTemplate}", config, testInstallBase)
 			}
 		}
 		return autogenPath
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index 65b91bc..a3a1aaf 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -250,7 +250,10 @@
 	newFile = filepath.Join(basePath, newFile)
 	oldFile := newFile + ".previous"
 
-	if _, err := os.Stat(newFile); err != nil {
+	if _, err := os.Stat(newFile); os.IsNotExist(err) {
+		// If the file doesn't exist, assume no installed files exist either
+		return
+	} else if err != nil {
 		ctx.Fatalf("Expected %q to be readable", newFile)
 	}
 
diff --git a/ui/build/config.go b/ui/build/config.go
index 2cd7d55..b6d0d27 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -50,6 +50,7 @@
 	jsonModuleGraph bool
 	bp2build        bool
 	queryview       bool
+	soongDocs       bool
 	skipConfig      bool
 	skipKati        bool
 	skipKatiNinja   bool
@@ -82,6 +83,8 @@
 
 	// Set by multiproduct_kati
 	emptyNinjaFile bool
+
+	metricsUploader string
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -110,9 +113,6 @@
 	// Don't use bazel at all.
 	noBazel bazelBuildMode = iota
 
-	// Only generate build files (in a subdirectory of the out directory) and exit.
-	generateBuildFiles
-
 	// Generate synthetic build files and incorporate these files into a build which
 	// partially uses Bazel. Build metadata may come from Android.bp or BUILD files.
 	mixedBuild
@@ -239,7 +239,8 @@
 	// Precondition: the current directory is the top of the source tree
 	checkTopDir(ctx)
 
-	if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') {
+	srcDir := absPath(ctx, ".")
+	if strings.ContainsRune(srcDir, ' ') {
 		ctx.Println("You are building in a directory whose absolute path contains a space character:")
 		ctx.Println()
 		ctx.Printf("%q\n", srcDir)
@@ -247,6 +248,8 @@
 		ctx.Fatalln("Directory names containing spaces are not supported")
 	}
 
+	ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)
+
 	if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
 		ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
 		ctx.Println()
@@ -357,13 +360,16 @@
 }
 
 func buildConfig(config Config) *smpb.BuildConfig {
-	return &smpb.BuildConfig{
+	c := &smpb.BuildConfig{
 		ForceUseGoma:    proto.Bool(config.ForceUseGoma()),
 		UseGoma:         proto.Bool(config.UseGoma()),
 		UseRbe:          proto.Bool(config.UseRBE()),
 		BazelAsNinja:    proto.Bool(config.UseBazel()),
 		BazelMixedBuild: proto.Bool(config.bazelBuildMode() == mixedBuild),
 	}
+	c.Targets = append(c.Targets, config.arguments...)
+
+	return c
 }
 
 // getConfigArgs processes the command arguments based on the build action and creates a set of new
@@ -646,6 +652,8 @@
 			c.bp2build = true
 		} else if arg == "queryview" {
 			c.queryview = true
+		} else if arg == "soong_docs" {
+			c.soongDocs = true
 		} else {
 			if arg == "checkbuild" {
 				c.checkbuild = true
@@ -713,21 +721,22 @@
 }
 
 func (c *configImpl) SoongBuildInvocationNeeded() bool {
-	if c.Dist() {
-		return true
-	}
-
 	if len(c.Arguments()) > 0 {
 		// Explicit targets requested that are not special targets like b2pbuild
 		// or the JSON module graph
 		return true
 	}
 
-	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() {
+	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() {
 		// Command line was empty, the default Ninja target is built
 		return true
 	}
 
+	// bp2build + dist may be used to dist bp2build logs but does not require SoongBuildInvocation
+	if c.Dist() && !c.Bp2Build() {
+		return true
+	}
+
 	// build.ninja doesn't need to be generated
 	return false
 }
@@ -776,16 +785,29 @@
 		panic("Unknown GOOS")
 	}
 }
+
 func (c *configImpl) HostToolDir() string {
-	return filepath.Join(c.SoongOutDir(), "host", c.PrebuiltOS(), "bin")
+	if c.SkipKatiNinja() {
+		return filepath.Join(c.SoongOutDir(), "host", c.PrebuiltOS(), "bin")
+	} else {
+		return filepath.Join(c.OutDir(), "host", c.PrebuiltOS(), "bin")
+	}
 }
 
-func (c *configImpl) MainNinjaFile() string {
-	return shared.JoinPath(c.SoongOutDir(), "build.ninja")
+func (c *configImpl) NamedGlobFile(name string) string {
+	return shared.JoinPath(c.SoongOutDir(), "globs-"+name+".ninja")
+}
+
+func (c *configImpl) UsedEnvFile(tag string) string {
+	return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+tag)
 }
 
 func (c *configImpl) Bp2BuildMarkerFile() string {
-	return shared.JoinPath(c.SoongOutDir(), ".bootstrap/bp2build_workspace_marker")
+	return shared.JoinPath(c.SoongOutDir(), "bp2build_workspace_marker")
+}
+
+func (c *configImpl) SoongDocsHtml() string {
+	return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
 }
 
 func (c *configImpl) QueryviewMarkerFile() string {
@@ -833,6 +855,10 @@
 	return c.queryview
 }
 
+func (c *configImpl) SoongDocs() bool {
+	return c.soongDocs
+}
+
 func (c *configImpl) IsVerbose() bool {
 	return c.verbose
 }
@@ -1227,22 +1253,25 @@
 }
 
 func (c *configImpl) MetricsUploaderApp() string {
-	if p, ok := c.environ.Get("ANDROID_ENABLE_METRICS_UPLOAD"); ok {
-		return p
-	}
-	return ""
+	return c.metricsUploader
 }
 
-// LogsDir returns the logs directory where build log and metrics
-// files are located. By default, the logs directory is the out
+// LogsDir returns the absolute path to the logs directory where build log and
+// metrics files are located. By default, the logs directory is the out
 // directory. If the argument dist is specified, the logs directory
 // is <dist_dir>/logs.
 func (c *configImpl) LogsDir() string {
+	dir := c.OutDir()
 	if c.Dist() {
 		// Always write logs to the real dist dir, even if Bazel is using a rigged dist dir for other files
-		return filepath.Join(c.RealDistDir(), "logs")
+		dir = filepath.Join(c.RealDistDir(), "logs")
 	}
-	return c.OutDir()
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "\nError making log dir '%s' absolute: %s\n", dir, err.Error())
+		os.Exit(1)
+	}
+	return absDir
 }
 
 // BazelMetricsDir returns the <logs dir>/bazel_metrics directory
@@ -1258,3 +1287,14 @@
 func (c *configImpl) EmptyNinjaFile() bool {
 	return c.emptyNinjaFile
 }
+
+func GetMetricsUploader(topDir string, env *Environment) string {
+	if p, ok := env.Get("METRICS_UPLOADER"); ok {
+		metricsUploader := filepath.Join(topDir, p)
+		if _, err := os.Stat(metricsUploader); err == nil {
+			return metricsUploader
+		}
+	}
+
+	return ""
+}
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 1f2158b..e293275 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -1003,6 +1003,7 @@
 	tests := []struct {
 		name                string
 		environ             Environment
+		arguments           []string
 		useBazel            bool
 		expectedBuildConfig *smpb.BuildConfig
 	}{
@@ -1074,6 +1075,20 @@
 			},
 		},
 		{
+			name:      "specified targets",
+			environ:   Environment{},
+			useBazel:  true,
+			arguments: []string{"droid", "dist"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(true),
+				BazelMixedBuild: proto.Bool(false),
+				Targets:         []string{"droid", "dist"},
+			},
+		},
+		{
 			name: "all set",
 			environ: Environment{
 				"FORCE_USE_GOMA=1",
@@ -1095,8 +1110,9 @@
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			c := &configImpl{
-				environ:  &tc.environ,
-				useBazel: tc.useBazel,
+				environ:   &tc.environ,
+				useBazel:  tc.useBazel,
+				arguments: tc.arguments,
 			}
 			config := Config{c}
 			actualBuildConfig := buildConfig(config)
@@ -1106,3 +1122,65 @@
 		})
 	}
 }
+
+func TestGetMetricsUploaderApp(t *testing.T) {
+
+	metricsUploaderDir := "metrics_uploader_dir"
+	metricsUploaderBinary := "metrics_uploader_binary"
+	metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary)
+	tests := []struct {
+		description string
+		environ     Environment
+		createFiles bool
+		expected    string
+	}{{
+		description: "Uploader binary exist",
+		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
+		createFiles: true,
+		expected:    metricsUploaderPath,
+	}, {
+		description: "Uploader binary not exist",
+		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
+		createFiles: false,
+		expected:    "",
+	}, {
+		description: "Uploader binary variable not set",
+		createFiles: true,
+		expected:    "",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				t.Fatalf("got unexpected error: %v", err)
+			})
+
+			// Create the root source tree.
+			topDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create temp dir: %v", err)
+			}
+			defer os.RemoveAll(topDir)
+
+			expected := tt.expected
+			if len(expected) > 0 {
+				expected = filepath.Join(topDir, expected)
+			}
+
+			if tt.createFiles {
+				if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil {
+					t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err)
+				}
+				if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil {
+					t.Errorf("failed to create file %s: %v", expected, err)
+				}
+			}
+
+			actual := GetMetricsUploader(topDir, &tt.environ)
+
+			if actual != expected {
+				t.Errorf("expecting: %s, actual: %s", expected, actual)
+			}
+		})
+	}
+}
diff --git a/ui/build/context.go b/ui/build/context.go
index f5e987e..4a4352c 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -71,9 +71,9 @@
 		realTime := end - begin
 		c.Metrics.SetTimeMetrics(
 			soong_metrics_proto.PerfInfo{
-				Desc:      &desc,
-				Name:      &name,
-				StartTime: &begin,
-				RealTime:  &realTime})
+				Description: &desc,
+				Name:        &name,
+				StartTime:   &begin,
+				RealTime:    &realTime})
 	}
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 3d16073..3f10f75 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -163,6 +163,7 @@
 	"AUX_OS_VARIANT_LIST",
 	"PRODUCT_SOONG_NAMESPACES",
 	"SOONG_SDK_SNAPSHOT_PREFER",
+	"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE",
 	"SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR",
 	"SOONG_SDK_SNAPSHOT_VERSION",
 }
@@ -262,9 +263,9 @@
 	}, exportEnvVars...), BannerVars...)
 
 	// We need Roboleaf converter and runner in the mixed mode
-	runMicrofactory(ctx, config, ".bootstrap/bin/mk2rbc", "android/soong/mk2rbc/cmd",
+	runMicrofactory(ctx, config, "mk2rbc", "android/soong/mk2rbc/cmd",
 		map[string]string{"android/soong": "build/soong"})
-	runMicrofactory(ctx, config, ".bootstrap/bin/rbcrun", "rbcrun/cmd",
+	runMicrofactory(ctx, config, "rbcrun", "rbcrun/cmd",
 		map[string]string{"go.starlark.net": "external/starlark-go", "rbcrun": "build/make/tools/rbcrun"})
 
 	makeVars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true, "")
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 04d106b..5360342 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -37,6 +38,18 @@
 const (
 	availableEnvFile = "soong.environment.available"
 	usedEnvFile      = "soong.environment.used"
+
+	soongBuildTag      = "build"
+	bp2buildTag        = "bp2build"
+	jsonModuleGraphTag = "modulegraph"
+	queryviewTag       = "queryview"
+	soongDocsTag       = "soong_docs"
+
+	// bootstrapEpoch is used to determine if an incremental build is incompatible with the current
+	// version of bootstrap and needs cleaning before continuing the build.  Increment this for
+	// incompatible changes, for example when moving the location of the bpglob binary that is
+	// executed during bootstrap before the primary builder has had a chance to update the path.
+	bootstrapEpoch = 1
 )
 
 func writeEnvironmentFile(ctx Context, envFile string, envDeps map[string]string) error {
@@ -75,7 +88,6 @@
 	soongOutDir               string
 	outDir                    string
 	runGoTests                bool
-	useValidations            bool
 	debugCompilation          bool
 	subninjas                 []string
 	primaryBuilderInvocations []bootstrap.PrimaryBuilderInvocation
@@ -97,10 +109,6 @@
 	return c.runGoTests
 }
 
-func (c BlueprintConfig) UseValidationsForGoTests() bool {
-	return c.useValidations
-}
-
 func (c BlueprintConfig) DebugCompilation() bool {
 	return c.debugCompilation
 }
@@ -113,158 +121,218 @@
 	return c.primaryBuilderInvocations
 }
 
-func environmentArgs(config Config, suffix string) []string {
+func environmentArgs(config Config, tag string) []string {
 	return []string{
 		"--available_env", shared.JoinPath(config.SoongOutDir(), availableEnvFile),
-		"--used_env", shared.JoinPath(config.SoongOutDir(), usedEnvFile+suffix),
+		"--used_env", config.UsedEnvFile(tag),
 	}
 }
 
-func writeEmptyGlobFile(ctx Context, path string) {
+func writeEmptyFile(ctx Context, path string) {
 	err := os.MkdirAll(filepath.Dir(path), 0777)
 	if err != nil {
-		ctx.Fatalf("Failed to create parent directories of empty ninja glob file '%s': %s", path, err)
+		ctx.Fatalf("Failed to create parent directories of empty file '%s': %s", path, err)
 	}
 
-	if _, err := os.Stat(path); os.IsNotExist(err) {
+	if exists, err := fileExists(path); err != nil {
+		ctx.Fatalf("Failed to check if file '%s' exists: %s", path, err)
+	} else if !exists {
 		err = ioutil.WriteFile(path, nil, 0666)
 		if err != nil {
-			ctx.Fatalf("Failed to create empty ninja glob file '%s': %s", path, err)
+			ctx.Fatalf("Failed to create empty file '%s': %s", path, err)
 		}
 	}
 }
 
+func fileExists(path string) (bool, error) {
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
+func primaryBuilderInvocation(
+	config Config,
+	name string,
+	output string,
+	specificArgs []string,
+	description string) bootstrap.PrimaryBuilderInvocation {
+	commonArgs := make([]string, 0, 0)
+
+	if !config.skipSoongTests {
+		commonArgs = append(commonArgs, "-t")
+	}
+
+	commonArgs = append(commonArgs, "-l", filepath.Join(config.FileListDir(), "Android.bp.list"))
+
+	if os.Getenv("SOONG_DELVE") != "" {
+		commonArgs = append(commonArgs, "--delve_listen", os.Getenv("SOONG_DELVE"))
+		commonArgs = append(commonArgs, "--delve_path", shared.ResolveDelveBinary())
+	}
+
+	allArgs := make([]string, 0, 0)
+	allArgs = append(allArgs, specificArgs...)
+	allArgs = append(allArgs,
+		"--globListDir", name,
+		"--globFile", config.NamedGlobFile(name))
+
+	allArgs = append(allArgs, commonArgs...)
+	allArgs = append(allArgs, environmentArgs(config, name)...)
+	allArgs = append(allArgs, "Android.bp")
+
+	return bootstrap.PrimaryBuilderInvocation{
+		Inputs:      []string{"Android.bp"},
+		Outputs:     []string{output},
+		Args:        allArgs,
+		Description: description,
+	}
+}
+
+// bootstrapEpochCleanup deletes files used by bootstrap during incremental builds across
+// incompatible changes.  Incompatible changes are marked by incrementing the bootstrapEpoch
+// constant.  A tree is considered out of date for the current epoch of the
+// .soong.bootstrap.epoch.<epoch> file doesn't exist.
+func bootstrapEpochCleanup(ctx Context, config Config) {
+	epochFile := fmt.Sprintf(".soong.bootstrap.epoch.%d", bootstrapEpoch)
+	epochPath := filepath.Join(config.SoongOutDir(), epochFile)
+	if exists, err := fileExists(epochPath); err != nil {
+		ctx.Fatalf("failed to check if bootstrap epoch file %q exists: %q", epochPath, err)
+	} else if !exists {
+		// The tree is out of date for the current epoch, delete files used by bootstrap
+		// and force the primary builder to rerun.
+		os.Remove(filepath.Join(config.SoongOutDir(), "build.ninja"))
+		for _, globFile := range bootstrapGlobFileList(config) {
+			os.Remove(globFile)
+		}
+
+		// Mark the tree as up to date with the current epoch by writing the epoch marker file.
+		writeEmptyFile(ctx, epochPath)
+	}
+}
+
+func bootstrapGlobFileList(config Config) []string {
+	return []string{
+		config.NamedGlobFile(soongBuildTag),
+		config.NamedGlobFile(bp2buildTag),
+		config.NamedGlobFile(jsonModuleGraphTag),
+		config.NamedGlobFile(queryviewTag),
+		config.NamedGlobFile(soongDocsTag),
+	}
+}
+
 func bootstrapBlueprint(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 	defer ctx.EndTrace()
 
-	var args bootstrap.Args
+	// Clean up some files for incremental builds across incompatible changes.
+	bootstrapEpochCleanup(ctx, config)
 
-	bootstrapGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.ninja")
-	bp2buildGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.bp2build.ninja")
-	queryviewGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.queryview.ninja")
-	moduleGraphGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.modulegraph.ninja")
-
-	// The glob .ninja files are subninja'd. However, they are generated during
-	// the build itself so we write an empty file so that the subninja doesn't
-	// fail on clean builds
-	writeEmptyGlobFile(ctx, bootstrapGlobFile)
-	writeEmptyGlobFile(ctx, bp2buildGlobFile)
-	writeEmptyGlobFile(ctx, queryviewGlobFile)
-	writeEmptyGlobFile(ctx, moduleGraphGlobFile)
-
-	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
-
-	args.RunGoTests = !config.skipSoongTests
-	args.UseValidations = true // Use validations to depend on tests
-	args.SoongOutDir = config.SoongOutDir()
-	args.OutDir = config.OutDir()
-	args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
-	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
-	// The primary builder (aka soong_build) will use bootstrapGlobFile as the globFile to generate build.ninja(.d)
-	// Building soong_build does not require a glob file
-	// Using "" instead of "<soong_build_glob>.ninja" will ensure that an unused glob file is not written to out/soong/.bootstrap during StagePrimary
-	args.Subninjas = []string{bootstrapGlobFile, bp2buildGlobFile, moduleGraphGlobFile, queryviewGlobFile}
-	args.EmptyNinjaFile = config.EmptyNinjaFile()
-
-	args.DelveListen = os.Getenv("SOONG_DELVE")
-	if args.DelveListen != "" {
-		args.DelvePath = shared.ResolveDelveBinary()
+	mainSoongBuildExtraArgs := []string{"-o", config.SoongNinjaFile()}
+	if config.EmptyNinjaFile() {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 	}
 
-	commonArgs := bootstrap.PrimaryBuilderExtraFlags(args, config.MainNinjaFile())
-	mainSoongBuildInputs := []string{"Android.bp"}
+	mainSoongBuildInvocation := primaryBuilderInvocation(
+		config,
+		soongBuildTag,
+		config.SoongNinjaFile(),
+		mainSoongBuildExtraArgs,
+		fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
+	)
 
 	if config.bazelBuildMode() == mixedBuild {
-		mainSoongBuildInputs = append(mainSoongBuildInputs, config.Bp2BuildMarkerFile())
+		// Mixed builds call Bazel from soong_build and they therefore need the
+		// Bazel workspace to be available. Make that so by adding a dependency on
+		// the bp2build marker file to the action that invokes soong_build .
+		mainSoongBuildInvocation.Inputs = append(mainSoongBuildInvocation.Inputs,
+			config.Bp2BuildMarkerFile())
 	}
 
-	soongBuildArgs := []string{
-		"--globListDir", "build",
-		"--globFile", bootstrapGlobFile,
+	bp2buildInvocation := primaryBuilderInvocation(
+		config,
+		bp2buildTag,
+		config.Bp2BuildMarkerFile(),
+		[]string{
+			"--bp2build_marker", config.Bp2BuildMarkerFile(),
+		},
+		fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
+	)
+
+	jsonModuleGraphInvocation := primaryBuilderInvocation(
+		config,
+		jsonModuleGraphTag,
+		config.ModuleGraphFile(),
+		[]string{
+			"--module_graph_file", config.ModuleGraphFile(),
+		},
+		fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
+	)
+
+	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
+	queryviewInvocation := primaryBuilderInvocation(
+		config,
+		queryviewTag,
+		config.QueryviewMarkerFile(),
+		[]string{
+			"--bazel_queryview_dir", queryviewDir,
+		},
+		fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
+	)
+
+	soongDocsInvocation := primaryBuilderInvocation(
+		config,
+		soongDocsTag,
+		config.SoongDocsHtml(),
+		[]string{
+			"--soong_docs", config.SoongDocsHtml(),
+		},
+		fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
+	)
+
+	globFiles := []string{
+		config.NamedGlobFile(soongBuildTag),
+		config.NamedGlobFile(bp2buildTag),
+		config.NamedGlobFile(jsonModuleGraphTag),
+		config.NamedGlobFile(queryviewTag),
+		config.NamedGlobFile(soongDocsTag),
 	}
 
-	soongBuildArgs = append(soongBuildArgs, commonArgs...)
-	soongBuildArgs = append(soongBuildArgs, environmentArgs(config, "")...)
-	soongBuildArgs = append(soongBuildArgs, "Android.bp")
-
-	mainSoongBuildInvocation := bootstrap.PrimaryBuilderInvocation{
-		Inputs:  mainSoongBuildInputs,
-		Outputs: []string{config.MainNinjaFile()},
-		Args:    soongBuildArgs,
+	// The glob .ninja files are subninja'd. However, they are generated during
+	// the build itself so we write an empty file if the file does not exist yet
+	// so that the subninja doesn't fail on clean builds
+	for _, globFile := range bootstrapGlobFileList(config) {
+		writeEmptyFile(ctx, globFile)
 	}
 
-	bp2buildArgs := []string{
-		"--bp2build_marker", config.Bp2BuildMarkerFile(),
-		"--globListDir", "bp2build",
-		"--globFile", bp2buildGlobFile,
-	}
+	var blueprintArgs bootstrap.Args
 
-	bp2buildArgs = append(bp2buildArgs, commonArgs...)
-	bp2buildArgs = append(bp2buildArgs, environmentArgs(config, ".bp2build")...)
-	bp2buildArgs = append(bp2buildArgs, "Android.bp")
-
-	bp2buildInvocation := bootstrap.PrimaryBuilderInvocation{
-		Inputs:  []string{"Android.bp"},
-		Outputs: []string{config.Bp2BuildMarkerFile()},
-		Args:    bp2buildArgs,
-	}
-
-	queryviewArgs := []string{
-		"--bazel_queryview_dir", filepath.Join(config.SoongOutDir(), "queryview"),
-		"--globListDir", "queryview",
-		"--globFile", queryviewGlobFile,
-	}
-
-	queryviewArgs = append(queryviewArgs, commonArgs...)
-	queryviewArgs = append(queryviewArgs, environmentArgs(config, ".queryview")...)
-	queryviewArgs = append(queryviewArgs, "Android.bp")
-
-	queryviewInvocation := bootstrap.PrimaryBuilderInvocation{
-		Inputs:  []string{"Android.bp"},
-		Outputs: []string{config.QueryviewMarkerFile()},
-		Args:    queryviewArgs,
-	}
-
-	moduleGraphArgs := []string{
-		"--module_graph_file", config.ModuleGraphFile(),
-		"--globListDir", "modulegraph",
-		"--globFile", moduleGraphGlobFile,
-	}
-
-	moduleGraphArgs = append(moduleGraphArgs, commonArgs...)
-	moduleGraphArgs = append(moduleGraphArgs, environmentArgs(config, ".modulegraph")...)
-	moduleGraphArgs = append(moduleGraphArgs, "Android.bp")
-
-	moduleGraphInvocation := bootstrap.PrimaryBuilderInvocation{
-		Inputs:  []string{"Android.bp"},
-		Outputs: []string{config.ModuleGraphFile()},
-		Args:    moduleGraphArgs,
-	}
-
-	args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{
-		bp2buildInvocation,
-		mainSoongBuildInvocation,
-		moduleGraphInvocation,
-		queryviewInvocation,
-	}
+	blueprintArgs.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
+	blueprintArgs.OutFile = shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja")
+	blueprintArgs.EmptyNinjaFile = false
 
 	blueprintCtx := blueprint.NewContext()
 	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 	blueprintConfig := BlueprintConfig{
-		soongOutDir:               config.SoongOutDir(),
-		toolDir:                   config.HostToolDir(),
-		outDir:                    config.OutDir(),
-		runGoTests:                !config.skipSoongTests,
-		useValidations:            true,
-		debugCompilation:          os.Getenv("SOONG_DELVE") != "",
-		subninjas:                 args.Subninjas,
-		primaryBuilderInvocations: args.PrimaryBuilderInvocations,
+		soongOutDir: config.SoongOutDir(),
+		toolDir:     config.HostToolDir(),
+		outDir:      config.OutDir(),
+		runGoTests:  !config.skipSoongTests,
+		// If we want to debug soong_build, we need to compile it for debugging
+		debugCompilation: os.Getenv("SOONG_DELVE") != "",
+		subninjas:        globFiles,
+		primaryBuilderInvocations: []bootstrap.PrimaryBuilderInvocation{
+			mainSoongBuildInvocation,
+			bp2buildInvocation,
+			jsonModuleGraphInvocation,
+			queryviewInvocation,
+			soongDocsInvocation},
 	}
 
-	args.EmptyNinjaFile = false
-	bootstrapDeps := bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
-	err := deptools.WriteDepFile(bootstrapDepFile, args.OutFile, bootstrapDeps)
+	bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig)
+	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja.d")
+	err := deptools.WriteDepFile(bootstrapDepFile, blueprintArgs.OutFile, bootstrapDeps)
 	if err != nil {
 		ctx.Fatalf("Error writing depfile '%s': %s", bootstrapDepFile, err)
 	}
@@ -275,6 +343,7 @@
 		v, _ := currentEnv.Get(k)
 		return v
 	}
+
 	if stale, _ := shared.StaleEnvFile(envFile, getenv); stale {
 		os.Remove(envFile)
 	}
@@ -290,11 +359,6 @@
 	// unused variables were changed?
 	envFile := filepath.Join(config.SoongOutDir(), availableEnvFile)
 
-	dir := filepath.Join(config.SoongOutDir(), ".bootstrap")
-	if err := os.MkdirAll(dir, 0755); err != nil {
-		ctx.Fatalf("Cannot mkdir " + dir)
-	}
-
 	buildMode := config.bazelBuildMode()
 	integratedBp2Build := buildMode == mixedBuild
 
@@ -309,6 +373,7 @@
 	soongBuildEnv.Set("BAZEL_OUTPUT_BASE", filepath.Join(config.BazelOutDir(), "output"))
 	soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
 	soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir())
+	soongBuildEnv.Set("LOG_DIR", config.LogsDir())
 
 	// For Soong bootstrapping tests
 	if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
@@ -324,16 +389,26 @@
 		ctx.BeginTrace(metrics.RunSoong, "environment check")
 		defer ctx.EndTrace()
 
-		soongBuildEnvFile := filepath.Join(config.SoongOutDir(), usedEnvFile)
-		checkEnvironmentFile(soongBuildEnv, soongBuildEnvFile)
+		checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongBuildTag))
 
-		if integratedBp2Build {
-			bp2buildEnvFile := filepath.Join(config.SoongOutDir(), usedEnvFile+".bp2build")
-			checkEnvironmentFile(soongBuildEnv, bp2buildEnvFile)
+		if integratedBp2Build || config.Bp2Build() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(bp2buildTag))
+		}
+
+		if config.JsonModuleGraph() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
+		}
+
+		if config.Queryview() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(queryviewTag))
+		}
+
+		if config.SoongDocs() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongDocsTag))
 		}
 	}()
 
-	runMicrofactory(ctx, config, filepath.Join(config.HostToolDir(), "bpglob"), "github.com/google/blueprint/bootstrap/bpglob",
+	runMicrofactory(ctx, config, "bpglob", "github.com/google/blueprint/bootstrap/bpglob",
 		map[string]string{"github.com/google/blueprint": "build/blueprint"})
 
 	ninja := func(name, ninjaFile string, targets ...string) {
@@ -386,12 +461,16 @@
 		targets = append(targets, config.QueryviewMarkerFile())
 	}
 
-	if config.SoongBuildInvocationNeeded() {
-		// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
-		targets = append(targets, config.MainNinjaFile())
+	if config.SoongDocs() {
+		targets = append(targets, config.SoongDocsHtml())
 	}
 
-	ninja("bootstrap", ".bootstrap/build.ninja", targets...)
+	if config.SoongBuildInvocationNeeded() {
+		// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
+		targets = append(targets, config.SoongNinjaFile())
+	}
+
+	ninja("bootstrap", "bootstrap.ninja", targets...)
 
 	var soongBuildMetrics *soong_metrics_proto.SoongBuildMetrics
 	if shouldCollectBuildSoongMetrics(config) {
@@ -411,8 +490,7 @@
 	}
 }
 
-func runMicrofactory(ctx Context, config Config, relExePath string, pkg string, mapping map[string]string) {
-	name := filepath.Base(relExePath)
+func runMicrofactory(ctx Context, config Config, name string, pkg string, mapping map[string]string) {
 	ctx.BeginTrace(metrics.RunSoong, name)
 	defer ctx.EndTrace()
 	cfg := microfactory.Config{TrimPath: absPath(ctx, ".")}
@@ -420,7 +498,7 @@
 		cfg.Map(pkgPrefix, pathPrefix)
 	}
 
-	exePath := filepath.Join(config.SoongOutDir(), relExePath)
+	exePath := filepath.Join(config.SoongOutDir(), name)
 	dir := filepath.Dir(exePath)
 	if err := os.MkdirAll(dir, 0777); err != nil {
 		ctx.Fatalf("cannot create %s: %s", dir, err)
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index f9a60b6..86c8568 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -63,7 +63,6 @@
 	cmd.StartOrFatal()
 
 	outDir := config.OutDir()
-	bootstrapDir := filepath.Join(outDir, "soong", ".bootstrap")
 	modulePathsDir := filepath.Join(outDir, ".module_paths")
 	variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
 
@@ -77,6 +76,9 @@
 	// out/build_date.txt is considered a "source file"
 	buildDatetimeFilePath := filepath.Join(outDir, "build_date.txt")
 
+	// bpglob is built explicitly using Microfactory
+	bpglob := filepath.Join(config.SoongOutDir(), "bpglob")
+
 	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
@@ -86,11 +88,11 @@
 			// Leaf node is not in the out directory.
 			continue
 		}
-		if strings.HasPrefix(line, bootstrapDir) ||
-			strings.HasPrefix(line, modulePathsDir) ||
+		if strings.HasPrefix(line, modulePathsDir) ||
 			line == variablesFilePath ||
 			line == dexpreoptConfigFilePath ||
-			line == buildDatetimeFilePath {
+			line == buildDatetimeFilePath ||
+			line == bpglob {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
 			// full build rules in the primary build.ninja file.
 			continue
diff --git a/ui/build/upload.go b/ui/build/upload.go
index 55ada33..687f519 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -70,12 +70,11 @@
 	return metricsFiles
 }
 
-// UploadMetrics uploads a set of metrics files to a server for analysis. An
-// uploader full path is specified in ANDROID_ENABLE_METRICS_UPLOAD environment
-// variable in order to upload the set of metrics files. 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.
+// 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) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index b740c11..764a1e1 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -80,13 +80,10 @@
 		createFiles bool
 		files       []string
 	}{{
-		description: "ANDROID_ENABLE_METRICS_UPLOAD not set",
-	}, {
-		description: "no metrics files to upload",
-		uploader:    "fake",
+		description: "no metrics uploader",
 	}, {
 		description: "non-existent metrics files no upload",
-		uploader:    "fake",
+		uploader:    "echo",
 		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3"},
 	}, {
 		description: "trigger upload",
@@ -137,9 +134,9 @@
 			config := Config{&configImpl{
 				environ: &Environment{
 					"OUT_DIR=" + outDir,
-					"ANDROID_ENABLE_METRICS_UPLOAD=" + tt.uploader,
 				},
-				buildDateTime: strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
+				buildDateTime:   strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
+				metricsUploader: tt.uploader,
 			}}
 
 			UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
@@ -192,9 +189,10 @@
 
 			config := Config{&configImpl{
 				environ: &Environment{
-					"ANDROID_ENABLE_METRICS_UPLOAD=fake",
 					"OUT_DIR=/bad",
-				}}}
+				},
+				metricsUploader: "echo",
+			}}
 
 			UploadMetrics(ctx, config, true, time.Now(), metricsFile)
 			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 1590ab0..3ba3907 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -23,7 +23,9 @@
         "golang-protobuf-proto",
         "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
+        "soong-ui-bp2build_metrics_proto",
         "soong-ui-tracer",
+        "soong-shared",
     ],
     srcs: [
         "metrics.go",
@@ -57,3 +59,15 @@
         "upload_proto/upload.pb.go",
     ],
 }
+
+bootstrap_go_package {
+    name: "soong-ui-bp2build_metrics_proto",
+    pkgPath: "android/soong/ui/metrics/bp2build_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "bp2build_metrics_proto/bp2build_metrics.pb.go",
+    ],
+}
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
new file mode 100644
index 0000000..11177e4
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
@@ -0,0 +1,222 @@
+// Copyright 2021 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.9.1
+// source: bp2build_metrics.proto
+
+package bp2build_metrics_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Bp2BuildMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Total number of Soong modules converted to generated targets
+	GeneratedModuleCount uint64 `protobuf:"varint,1,opt,name=generatedModuleCount,proto3" json:"generatedModuleCount,omitempty"`
+	// Total number of Soong modules converted to handcrafted targets
+	HandCraftedModuleCount uint64 `protobuf:"varint,2,opt,name=handCraftedModuleCount,proto3" json:"handCraftedModuleCount,omitempty"`
+	// Total number of unconverted Soong modules
+	UnconvertedModuleCount uint64 `protobuf:"varint,3,opt,name=unconvertedModuleCount,proto3" json:"unconvertedModuleCount,omitempty"`
+	// Counts of generated Bazel targets per Bazel rule class
+	RuleClassCount map[string]uint64 `protobuf:"bytes,4,rep,name=ruleClassCount,proto3" json:"ruleClassCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	// List of converted modules
+	ConvertedModules []string `protobuf:"bytes,5,rep,name=convertedModules,proto3" json:"convertedModules,omitempty"`
+}
+
+func (x *Bp2BuildMetrics) Reset() {
+	*x = Bp2BuildMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_bp2build_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Bp2BuildMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Bp2BuildMetrics) ProtoMessage() {}
+
+func (x *Bp2BuildMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_bp2build_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Bp2BuildMetrics.ProtoReflect.Descriptor instead.
+func (*Bp2BuildMetrics) Descriptor() ([]byte, []int) {
+	return file_bp2build_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Bp2BuildMetrics) GetGeneratedModuleCount() uint64 {
+	if x != nil {
+		return x.GeneratedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetHandCraftedModuleCount() uint64 {
+	if x != nil {
+		return x.HandCraftedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetUnconvertedModuleCount() uint64 {
+	if x != nil {
+		return x.UnconvertedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetRuleClassCount() map[string]uint64 {
+	if x != nil {
+		return x.RuleClassCount
+	}
+	return nil
+}
+
+func (x *Bp2BuildMetrics) GetConvertedModules() []string {
+	if x != nil {
+		return x.ConvertedModules
+	}
+	return nil
+}
+
+var File_bp2build_metrics_proto protoreflect.FileDescriptor
+
+var file_bp2build_metrics_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x8f, 0x03, 0x0a, 0x0f, 0x42, 0x70, 0x32, 0x42, 0x75,
+	0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x67, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75,
+	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
+	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36,
+	0x0a, 0x16, 0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16,
+	0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76,
+	0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72,
+	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x69,
+	0x0a, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43,
+	0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e,
+	0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x41, 0x0a, 0x13, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61,
+	0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x31, 0x5a, 0x2f, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x2f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
+}
+
+var (
+	file_bp2build_metrics_proto_rawDescOnce sync.Once
+	file_bp2build_metrics_proto_rawDescData = file_bp2build_metrics_proto_rawDesc
+)
+
+func file_bp2build_metrics_proto_rawDescGZIP() []byte {
+	file_bp2build_metrics_proto_rawDescOnce.Do(func() {
+		file_bp2build_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_bp2build_metrics_proto_rawDescData)
+	})
+	return file_bp2build_metrics_proto_rawDescData
+}
+
+var file_bp2build_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_bp2build_metrics_proto_goTypes = []interface{}{
+	(*Bp2BuildMetrics)(nil), // 0: soong_build_bp2build_metrics.Bp2BuildMetrics
+	nil,                     // 1: soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
+}
+var file_bp2build_metrics_proto_depIdxs = []int32{
+	1, // 0: soong_build_bp2build_metrics.Bp2BuildMetrics.ruleClassCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_bp2build_metrics_proto_init() }
+func file_bp2build_metrics_proto_init() {
+	if File_bp2build_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_bp2build_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Bp2BuildMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_bp2build_metrics_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_bp2build_metrics_proto_goTypes,
+		DependencyIndexes: file_bp2build_metrics_proto_depIdxs,
+		MessageInfos:      file_bp2build_metrics_proto_msgTypes,
+	}.Build()
+	File_bp2build_metrics_proto = out.File
+	file_bp2build_metrics_proto_rawDesc = nil
+	file_bp2build_metrics_proto_goTypes = nil
+	file_bp2build_metrics_proto_depIdxs = nil
+}
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
new file mode 100644
index 0000000..5e88966
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
@@ -0,0 +1,35 @@
+// Copyright 2021 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.
+
+syntax = "proto3";
+
+package soong_build_bp2build_metrics;
+option go_package = "android/soong/ui/metrics/bp2build_metrics_proto";
+
+message Bp2BuildMetrics {
+  // Total number of Soong modules converted to generated targets
+  uint64 generatedModuleCount = 1;
+
+  // Total number of Soong modules converted to handcrafted targets
+  uint64 handCraftedModuleCount = 2;
+
+  // Total number of unconverted Soong modules
+  uint64 unconvertedModuleCount = 3;
+
+  // Counts of generated Bazel targets per Bazel rule class
+  map<string, uint64> ruleClassCount = 4;
+
+  // List of converted modules
+  repeated string convertedModules = 5;
+}
diff --git a/ui/metrics/bp2build_metrics_proto/regen.sh b/ui/metrics/bp2build_metrics_proto/regen.sh
new file mode 100755
index 0000000..bfe4294
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/regen.sh
@@ -0,0 +1,29 @@
+#!/bin/bash -e
+
+# Copyright 2021 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.
+
+# Generates the golang source file of bp2build_metrics.proto protobuf file.
+
+function die() { echo "ERROR: $1" >&2; exit 1; }
+
+readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
+
+if ! hash aprotoc &>/dev/null; then
+  die "could not find aprotoc. ${error_msg}"
+fi
+
+if ! aprotoc --go_out=paths=source_relative:. bp2build_metrics.proto; then
+  die "build failed. ${error_msg}"
+fi
diff --git a/ui/metrics/event.go b/ui/metrics/event.go
index c3367e3..ebe664f 100644
--- a/ui/metrics/event.go
+++ b/ui/metrics/event.go
@@ -69,7 +69,7 @@
 func (e event) perfInfo() soong_metrics_proto.PerfInfo {
 	realTime := uint64(_now().Sub(e.start).Nanoseconds())
 	return soong_metrics_proto.PerfInfo{
-		Desc:                  proto.String(e.desc),
+		Description:           proto.String(e.desc),
 		Name:                  proto.String(e.name),
 		StartTime:             proto.Uint64(uint64(e.start.UnixNano())),
 		RealTime:              proto.Uint64(realTime),
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index ccf9bd8..80f8c1a 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -25,24 +25,19 @@
 // that captures the metrics and is them added as a perfInfo into the set
 // of the collected metrics. Finally, when soong_ui has finished the build,
 // the defer Dump function is invoked to store the collected metrics to the
-// raw protobuf file in the $OUT directory.
-//
-// There is one additional step that occurs after the raw protobuf file is written.
-// If the configuration environment variable ANDROID_ENABLE_METRICS_UPLOAD is
-// set with the path, the raw protobuf file is uploaded to the destination. See
-// ui/build/upload.go for more details. The filename of the raw protobuf file
-// and the list of files to be uploaded is defined in cmd/soong_ui/main.go.
-//
-// See ui/metrics/event.go for the explanation of what an event is and how
-// the metrics system is a stack based system.
+// raw protobuf file in the $OUT directory and this raw protobuf file will be
+// uploaded to the destination. See ui/build/upload.go for more details. The
+// filename of the raw protobuf file and the list of files to be uploaded is
+// defined in cmd/soong_ui/main.go. See ui/metrics/event.go for the explanation
+// of what an event is and how the metrics system is a stack based system.
 
 import (
-	"io/ioutil"
 	"os"
 	"runtime"
 	"strings"
 	"time"
 
+	"android/soong/shared"
 	"google.golang.org/protobuf/proto"
 
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
@@ -201,7 +196,7 @@
 	}
 	m.metrics.HostOs = proto.String(runtime.GOOS)
 
-	return save(&m.metrics, out)
+	return shared.Save(&m.metrics, out)
 }
 
 // SetSoongBuildMetrics sets the metrics collected from the soong_build
@@ -233,25 +228,5 @@
 
 // Dump saves the collected CUJs metrics to the raw protobuf file.
 func (c *CriticalUserJourneysMetrics) Dump(filename string) (err error) {
-	return save(&c.cujs, filename)
-}
-
-// save takes a protobuf message, marshals to an array of bytes
-// and is then saved to a file.
-func save(pb proto.Message, filename string) (err error) {
-	data, err := proto.Marshal(pb)
-	if err != nil {
-		return err
-	}
-
-	tempFilename := filename + ".tmp"
-	if err := ioutil.WriteFile(tempFilename, []byte(data), 0644 /* rw-r--r-- */); err != nil {
-		return err
-	}
-
-	if err := os.Rename(tempFilename, filename); err != nil {
-		return err
-	}
-
-	return nil
+	return shared.Save(&c.cujs, filename)
 }
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 697e954..2e530b0 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -14,7 +14,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.26.0
+// 	protoc-gen-go v1.27.1
 // 	protoc        v3.9.1
 // source: metrics.proto
 
@@ -518,6 +518,9 @@
 	// Whether build is occurring in a mixed build mode, where Bazel maintains the
 	// definition and build of some modules in cooperation with Soong.
 	BazelMixedBuild *bool `protobuf:"varint,5,opt,name=bazel_mixed_build,json=bazelMixedBuild" json:"bazel_mixed_build,omitempty"`
+	// These are the targets soong passes to ninja, these targets include special
+	// targets such as droid as well as the regular build targets.
+	Targets []string `protobuf:"bytes,6,rep,name=targets" json:"targets,omitempty"`
 }
 
 func (x *BuildConfig) Reset() {
@@ -587,6 +590,13 @@
 	return false
 }
 
+func (x *BuildConfig) GetTargets() []string {
+	if x != nil {
+		return x.Targets
+	}
+	return nil
+}
+
 type SystemResourceInfo struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -650,7 +660,7 @@
 	unknownFields protoimpl.UnknownFields
 
 	// The description for the phase/action/part while the tool running.
-	Desc *string `protobuf:"bytes,1,opt,name=desc" json:"desc,omitempty"`
+	Description *string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
 	// The name for the running phase/action/part.
 	Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
 	// The absolute start time.
@@ -699,9 +709,9 @@
 	return file_metrics_proto_rawDescGZIP(), []int{3}
 }
 
-func (x *PerfInfo) GetDesc() string {
-	if x != nil && x.Desc != nil {
-		return *x.Desc
+func (x *PerfInfo) GetDescription() string {
+	if x != nil && x.Description != nil {
+		return *x.Description
 	}
 	return ""
 }
@@ -1238,7 +1248,7 @@
 	0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x52, 0x4d, 0x10, 0x01, 0x12,
 	0x09, 0x0a, 0x05, 0x41, 0x52, 0x4d, 0x36, 0x34, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x58, 0x38,
 	0x36, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x58, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x10, 0x04, 0x22,
-	0xb9, 0x01, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+	0xd3, 0x01, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
 	0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
 	0x08, 0x52, 0x07, 0x75, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
 	0x65, 0x5f, 0x72, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x73, 0x65,
@@ -1249,100 +1259,102 @@
 	0x08, 0x52, 0x0c, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x41, 0x73, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x12,
 	0x2a, 0x0a, 0x11, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62,
 	0x75, 0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x61, 0x7a, 0x65,
-	0x6c, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x22, 0x6f, 0x0a, 0x12, 0x53,
-	0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66,
-	0x6f, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x68, 0x79, 0x73, 0x69,
-	0x63, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x4d,
-	0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62,
-	0x6c, 0x65, 0x5f, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61,
-	0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x70, 0x75, 0x73, 0x22, 0xf3, 0x01, 0x0a,
-	0x08, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73,
-	0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x12, 0x0a,
-	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65,
-	0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20,
-	0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a,
-	0x0a, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x65,
-	0x12, 0x60, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x65,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x28, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52,
-	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15, 0x70, 0x72, 0x6f,
-	0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e,
-	0x66, 0x6f, 0x22, 0xb9, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28,
-	0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72,
-	0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69,
-	0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x79, 0x73, 0x74,
-	0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65,
-	0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73,
-	0x73, 0x5f, 0x6b, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52,
-	0x73, 0x73, 0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61,
-	0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x0f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73,
-	0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66,
-	0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a,
-	0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b,
-	0x69, 0x6f, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c,
-	0x69, 0x6f, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x08, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c,
-	0x0a, 0x1a, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74,
-	0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e,
-	0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c,
-	0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74,
-	0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43,
-	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x22, 0xe5,
-	0x01, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66,
-	0x6f, 0x12, 0x5b, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65,
-	0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x6f,
-	0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x42, 0x75, 0x69,
-	0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57,
-	0x4e, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f,
-	0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
-	0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
-	0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x4d, 0x6f,
-	0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x2f, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79,
-	0x73, 0x74, 0x65, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
-	0x00, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x4f, 0x4f, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04,
-	0x4d, 0x41, 0x4b, 0x45, 0x10, 0x02, 0x22, 0x6c, 0x0a, 0x1a, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63,
-	0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72,
-	0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
+	0x6c, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x12, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52,
+	0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x74,
+	0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6d, 0x65,
+	0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61,
+	0x6c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12,
+	0x25, 0x0a, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x70, 0x75,
+	0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62,
+	0x6c, 0x65, 0x43, 0x70, 0x75, 0x73, 0x22, 0x81, 0x02, 0x0a, 0x08, 0x50, 0x65, 0x72, 0x66, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61,
+	0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73,
+	0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c,
+	0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x61,
+	0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f,
+	0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x6d,
+	0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63,
+	0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69,
+	0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
 	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
-	0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x22, 0x62, 0x0a, 0x1b, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
-	0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x73, 0x4d, 0x65, 0x74, 0x72,
-	0x69, 0x63, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
-	0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x52, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x22, 0xc3, 0x01, 0x0a, 0x11, 0x53, 0x6f, 0x6f,
-	0x6e, 0x67, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18,
-	0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
-	0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69,
-	0x61, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69,
-	0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c,
-	0x6c, 0x6f, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-	0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f,
-	0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61,
-	0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61,
-	0x78, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x65, 0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x28,
-	0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
-	0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0xb9, 0x03, 0x0a, 0x13, 0x50,
+	0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e,
+	0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74,
+	0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+	0x12, 0x2c, 0x0a, 0x12, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f,
+	0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x73, 0x79,
+	0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x1c,
+	0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11,
+	0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74,
+	0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61,
+	0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f,
+	0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61,
+	0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74,
+	0x5f, 0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70,
+	0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x5f, 0x6b, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74,
+	0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74,
+	0x63, 0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75,
+	0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74,
+	0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74,
+	0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74,
+	0x63, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f,
+	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x77,
+	0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x5b, 0x0a, 0x0c, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65,
+	0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
+	0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6f,
+	0x66, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x2f, 0x0a,
+	0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x0b, 0x0a, 0x07,
+	0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x4f, 0x4f,
+	0x4e, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41, 0x4b, 0x45, 0x10, 0x02, 0x22, 0x6c,
+	0x0a, 0x1a, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f,
+	0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x12, 0x3a, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42,
+	0x61, 0x73, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x62, 0x0a, 0x1b,
+	0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72,
+	0x6e, 0x65, 0x79, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x63,
+	0x75, 0x6a, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
+	0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72,
+	0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x04, 0x63, 0x75, 0x6a, 0x73,
+	0x22, 0xc3, 0x01, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d,
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
+	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
+	0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0d, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11,
+	0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c,
+	0x6c, 0x6f, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61,
+	0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69,
+	0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73,
+	0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x65,
+	0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index ef42f54..db0a14a 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -123,6 +123,10 @@
   // Whether build is occurring in a mixed build mode, where Bazel maintains the
   // definition and build of some modules in cooperation with Soong.
   optional bool bazel_mixed_build = 5;
+
+  // These are the targets soong passes to ninja, these targets include special
+  // targets such as droid as well as the regular build targets.
+  repeated string targets = 6;
 }
 
 message SystemResourceInfo {
@@ -135,7 +139,7 @@
 
 message PerfInfo {
   // The description for the phase/action/part while the tool running.
-  optional string desc = 1;
+  optional string description = 1;
 
   // The name for the running phase/action/part.
   optional string name = 2;
diff --git a/ui/terminal/simple_status.go b/ui/terminal/simple_status.go
index 4e8c568..3157813 100644
--- a/ui/terminal/simple_status.go
+++ b/ui/terminal/simple_status.go
@@ -22,30 +22,41 @@
 )
 
 type simpleStatusOutput struct {
-	writer    io.Writer
-	formatter formatter
+	writer      io.Writer
+	formatter   formatter
+	keepANSI    bool
+	outputLevel status.MsgLevel
 }
 
 // NewSimpleStatusOutput returns a StatusOutput that represents the
 // current build status similarly to Ninja's built-in terminal
 // output.
-func NewSimpleStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
+func NewSimpleStatusOutput(w io.Writer, formatter formatter, keepANSI bool, quietBuild bool) status.StatusOutput {
+	level := status.StatusLvl
+	if quietBuild {
+		level = status.PrintLvl
+	}
 	return &simpleStatusOutput{
-		writer:    w,
-		formatter: formatter,
+		writer:      w,
+		formatter:   formatter,
+		keepANSI:    keepANSI,
+		outputLevel: level,
 	}
 }
 
 func (s *simpleStatusOutput) Message(level status.MsgLevel, message string) {
-	if level >= status.StatusLvl {
+	if level >= s.outputLevel {
 		fmt.Fprintln(s.writer, s.formatter.message(level, message))
 	}
 }
 
-func (s *simpleStatusOutput) StartAction(action *status.Action, counts status.Counts) {
+func (s *simpleStatusOutput) StartAction(_ *status.Action, _ status.Counts) {
 }
 
 func (s *simpleStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+	if s.outputLevel > status.StatusLvl {
+		return
+	}
 	str := result.Description
 	if str == "" {
 		str = result.Command
@@ -54,7 +65,9 @@
 	progress := s.formatter.progress(counts) + str
 
 	output := s.formatter.result(result)
-	output = string(stripAnsiEscapes([]byte(output)))
+	if !s.keepANSI {
+		output = string(stripAnsiEscapes([]byte(output)))
+	}
 
 	if output != "" {
 		fmt.Fprint(s.writer, progress, "\n", output)
diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go
index 6bdf140..06a4064 100644
--- a/ui/terminal/smart_status.go
+++ b/ui/terminal/smart_status.go
@@ -77,7 +77,12 @@
 		s.requestedTableHeight = h
 	}
 
-	s.updateTermSize()
+	if w, h, ok := termSize(s.writer); ok {
+		s.termWidth, s.termHeight = w, h
+		s.computeTableHeight()
+	} else {
+		s.tableMode = false
+	}
 
 	if s.tableMode {
 		// Add empty lines at the bottom of the screen to scroll back the existing history
@@ -296,40 +301,44 @@
 	close(s.sigwinch)
 }
 
+// computeTableHeight recomputes s.tableHeight based on s.termHeight and s.requestedTableHeight.
+func (s *smartStatusOutput) computeTableHeight() {
+	tableHeight := s.requestedTableHeight
+	if tableHeight == 0 {
+		tableHeight = s.termHeight / 4
+		if tableHeight < 1 {
+			tableHeight = 1
+		} else if tableHeight > 10 {
+			tableHeight = 10
+		}
+	}
+	if tableHeight > s.termHeight-1 {
+		tableHeight = s.termHeight - 1
+	}
+	s.tableHeight = tableHeight
+}
+
+// updateTermSize recomputes the table height after a SIGWINCH and pans any existing text if
+// necessary.
 func (s *smartStatusOutput) updateTermSize() {
 	if w, h, ok := termSize(s.writer); ok {
-		firstUpdate := s.termHeight == 0 && s.termWidth == 0
 		oldScrollingHeight := s.termHeight - s.tableHeight
 
 		s.termWidth, s.termHeight = w, h
 
 		if s.tableMode {
-			tableHeight := s.requestedTableHeight
-			if tableHeight == 0 {
-				tableHeight = s.termHeight / 4
-				if tableHeight < 1 {
-					tableHeight = 1
-				} else if tableHeight > 10 {
-					tableHeight = 10
-				}
-			}
-			if tableHeight > s.termHeight-1 {
-				tableHeight = s.termHeight - 1
-			}
-			s.tableHeight = tableHeight
+			s.computeTableHeight()
 
 			scrollingHeight := s.termHeight - s.tableHeight
 
-			if !firstUpdate {
-				// If the scrolling region has changed, attempt to pan the existing text so that it is
-				// not overwritten by the table.
-				if scrollingHeight < oldScrollingHeight {
-					pan := oldScrollingHeight - scrollingHeight
-					if pan > s.tableHeight {
-						pan = s.tableHeight
-					}
-					fmt.Fprint(s.writer, ansi.panDown(pan))
+			// If the scrolling region has changed, attempt to pan the existing text so that it is
+			// not overwritten by the table.
+			if scrollingHeight < oldScrollingHeight {
+				pan := oldScrollingHeight - scrollingHeight
+				if pan > s.tableHeight {
+					pan = s.tableHeight
 				}
+				fmt.Fprint(s.writer, ansi.panDown(pan))
 			}
 		}
 	}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index d8e7392..ff0af47 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -26,12 +26,12 @@
 //
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild bool) status.StatusOutput {
+func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
 	formatter := newFormatter(statusFormat, quietBuild)
 
-	if !forceSimpleOutput && isSmartTerminal(w) {
-		return NewSmartStatusOutput(w, formatter)
+	if forceSimpleOutput || quietBuild || !isSmartTerminal(w) {
+		return NewSimpleStatusOutput(w, formatter, forceKeepANSI, quietBuild)
 	} else {
-		return NewSimpleStatusOutput(w, formatter)
+		return NewSmartStatusOutput(w, formatter)
 	}
 }
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index aa69dff..810e31d 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -94,7 +94,7 @@
 
 			t.Run("smart", func(t *testing.T) {
 				smart := &fakeSmartTerminal{termWidth: 40}
-				stat := NewStatusOutput(smart, "", false, false)
+				stat := NewStatusOutput(smart, "", false, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -105,7 +105,7 @@
 
 			t.Run("simple", func(t *testing.T) {
 				simple := &bytes.Buffer{}
-				stat := NewStatusOutput(simple, "", false, false)
+				stat := NewStatusOutput(simple, "", false, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -116,7 +116,7 @@
 
 			t.Run("force simple", func(t *testing.T) {
 				smart := &fakeSmartTerminal{termWidth: 40}
-				stat := NewStatusOutput(smart, "", true, false)
+				stat := NewStatusOutput(smart, "", true, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -269,7 +269,7 @@
 	os.Setenv(tableHeightEnVar, "")
 
 	smart := &fakeSmartTerminal{termWidth: 40}
-	stat := NewStatusOutput(smart, "", false, false)
+	stat := NewStatusOutput(smart, "", false, false, false)
 	smartStat := stat.(*smartStatusOutput)
 	smartStat.sigwinchHandled = make(chan bool)