Merge "Enable hwasan use after scope detection."
diff --git a/android/apex.go b/android/apex.go
index b9efe4e..6b4054d 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,18 +878,6 @@
 	"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,
diff --git a/android/bazel.go b/android/bazel.go
index 4f613df..9278008 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
@@ -25,18 +24,35 @@
 	"github.com/google/blueprint/proptools"
 )
 
+type bazelModuleProperties struct {
+	// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
+	// will import the handcrafted build target into the autogenerated file. Note: this may result in
+	// a conflict due to duplicate targets if bp2build_available is also set.
+	Label *string
+
+	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
+	// cause a conflict due to the duplicate targets if label is also set.
+	//
+	// This is a bool pointer to support tristates: true, false, not set.
+	//
+	// To opt-in a module, set bazel_module: { bp2build_available: true }
+	// 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
+}
+
 // Properties contains common module properties for Bazel migration purposes.
 type properties struct {
 	// In USE_BAZEL_ANALYSIS=1 mode, this represents the Bazel target replacing
 	// this Soong module.
-	Bazel_module bazel.BazelModuleProperties
+	Bazel_module bazelModuleProperties
 }
 
 // namespacedVariableProperties is a map from a string representing a Soong
-// config variable namespace, like "android" or "vendor_name" to a struct
-// pointer representing the soong_config_variables property of a module created
-// by a soong_config_module_type or soong_config_module_type_import.
-type namespacedVariableProperties map[string]interface{}
+// 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.
@@ -68,11 +84,19 @@
 	convertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool
 	GetBazelBuildFileContents(c Config, path, name string) (string, error)
 
-	// For namespaced config variable support
+	// 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(string)
+	SetBaseModuleType(baseModuleType string)
 }
 
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@@ -202,33 +226,42 @@
 
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
 	bp2buildDefaultConfig = Bp2BuildConfig{
-		"bionic":                                             Bp2BuildDefaultTrueRecursively,
+		"art/libdexfile": Bp2BuildDefaultTrueRecursively,
+		"bionic":         Bp2BuildDefaultTrueRecursively,
+		"build/bazel/examples/soong_config_variables":        Bp2BuildDefaultTrueRecursively,
 		"build/bazel/examples/apex/minimal":                  Bp2BuildDefaultTrueRecursively,
+		"build/soong":                                        Bp2BuildDefaultTrue,
 		"build/soong/cc/libbuildversion":                     Bp2BuildDefaultTrue, // Skip tests subdir
+		"cts/common/device-side/nativetesthelper/jni":        Bp2BuildDefaultTrueRecursively,
 		"development/sdk":                                    Bp2BuildDefaultTrueRecursively,
 		"external/arm-optimized-routines":                    Bp2BuildDefaultTrueRecursively,
 		"external/boringssl":                                 Bp2BuildDefaultTrueRecursively,
 		"external/brotli":                                    Bp2BuildDefaultTrue,
 		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
 		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
-		"external/googletest/googletest":                     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/lz4/lib":                                   Bp2BuildDefaultTrue,
+		"external/lzma/C":                                    Bp2BuildDefaultTrueRecursively,
 		"external/mdnsresponder":                             Bp2BuildDefaultTrueRecursively,
 		"external/minijail":                                  Bp2BuildDefaultTrueRecursively,
 		"external/pcre":                                      Bp2BuildDefaultTrueRecursively,
 		"external/protobuf":                                  Bp2BuildDefaultTrueRecursively,
 		"external/python/six":                                Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
 		"external/scudo":                                     Bp2BuildDefaultTrueRecursively,
 		"external/selinux/libselinux":                        Bp2BuildDefaultTrueRecursively,
 		"external/zlib":                                      Bp2BuildDefaultTrueRecursively,
 		"external/zstd":                                      Bp2BuildDefaultTrueRecursively,
 		"frameworks/native/libs/adbd_auth":                   Bp2BuildDefaultTrueRecursively,
+		"frameworks/proto_logging/stats/stats_log_api_gen":   Bp2BuildDefaultTrueRecursively,
+		"libnativehelper":                                    Bp2BuildDefaultTrueRecursively,
 		"packages/modules/adb":                               Bp2BuildDefaultTrue,
 		"packages/modules/adb/crypto":                        Bp2BuildDefaultTrueRecursively,
 		"packages/modules/adb/libs":                          Bp2BuildDefaultTrueRecursively,
@@ -237,6 +270,8 @@
 		"packages/modules/adb/proto":                         Bp2BuildDefaultTrueRecursively,
 		"packages/modules/adb/tls":                           Bp2BuildDefaultTrueRecursively,
 		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
+		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
+		"system/core/debuggerd":                              Bp2BuildDefaultTrue,
 		"system/core/diagnose_usb":                           Bp2BuildDefaultTrueRecursively,
 		"system/core/libasyncio":                             Bp2BuildDefaultTrue,
 		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
@@ -245,29 +280,80 @@
 		"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/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,
 	}
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
+		"libnativehelper_compat_libc++",              // Broken compile: implicit declaration of function 'strerror_r' is invalid in C99
+		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+
+		"libandroid_runtime_lazy", // depends on unconverted modules: libbinder_headers
+		"libcmd",                  // depends on unconverted modules: libbinder
+
+		"chkcon", "sefcontext_compile", // depends on unconverted modules: libsepol
+
+		"libsepol", // TODO(b/207408632): Unsupported case of .l sources in cc library rules
+
+		"get_clang_version_test", // depends on unconverted module: get_clang_version
+
+		"libbinder",               // TODO(b/188503688): Disabled for some archs,
+		"libactivitymanager_aidl", // TODO(b/207426160): Depends on activity_manager_procstate_aidl, which is an aidl filegroup.
+
+		"libnativehelper_lazy_mts_jni", // depends on unconverted modules: libgmock_ndk
+		"libnativehelper_mts_jni",      // depends on unconverted modules: libgmock_ndk
+		"libnativetesthelper_jni",      // depends on unconverted modules: libgtest_ndk_c++
+
+		"statslog-framework-java-gen", "statslog.cpp", "statslog.h", "statslog.rs", "statslog_header.rs", // depends on unconverted modules: stats-log-api-gen
+
+		"stats-log-api-gen", // depends on unconverted modules: libstats_proto_host, libprotobuf-cpp-full
+
+		"libstatslog", // depends on unconverted modules: statslog.cpp, statslog.h, ...
+
+		"libgmock_main_ndk", "libgmock_ndk", // depends on unconverted module: libgtest_ndk_c++
+
+		"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.
+
+		"pbtombstone",                                  // depends on libprotobuf-cpp-lite, libtombstone_proto
+		"crash_dump",                                   // depends on unconverted module libprotobuf-cpp-lite
 		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
 
-		"libc_malloc_debug", // depends on libunwindstack, which depends on unsupported module art_cc_library_statics
+		"libunwindstack_local", "libunwindstack_utils", // depends on unconverted module libunwindstack
+		"libunwindstack",    // depends on libdexfile_support, of unsupported module type art_cc_library_static
+		"libc_malloc_debug", // depends on unconverted module libunwindstack
 
 		"libbase_ndk", // http://b/186826477, fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
 
+		"lib_linker_config_proto_lite", // contains .proto sources
+
 		"libprotobuf-python",               // contains .proto sources
 		"libprotobuf-internal-protos",      // we don't handle path property for fileegroups
 		"libprotobuf-internal-python-srcs", // we don't handle path property for fileegroups
 
 		"libseccomp_policy", // b/201094425: depends on func_to_syscall_nrs, which depends on py_binary, which is unsupported in mixed builds.
-		"libfdtrack",        // depends on libunwindstack, which depends on unsupported module art_cc_library_statics
+		"libfdtrack",        // depends on unconverted module libunwindstack
 
 		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
 
@@ -278,10 +364,6 @@
 		"platform_tools_properties",
 		"build_tools_source_properties",
 
-		"libminijail", // b/202491296: Uses unsupported c_std property.
-		"minijail0",   // depends on unconverted modules: libminijail
-		"drop_privs",  // depends on unconverted modules: libminijail
-
 		// Tests. Handle later.
 		"libbionic_tests_headers_posix", // http://b/186024507, cc_library_static, sched.h, time.h not found
 		"libjemalloc5_integrationtest",
@@ -308,14 +390,12 @@
 		"libgtest_ndk_c++",      // b/201816222: Requires sdk_version support.
 		"libgtest_main_ndk_c++", // b/201816222: Requires sdk_version support.
 
-		"abb",                     // depends on unconverted modules: libadbd_core, libadbd_services, libcmd, libbinder, libutils, libselinux
-		"adb",                     // depends on unconverted modules: bin2c_fastdeployagent, libadb_crypto, libadb_host, libadb_pairing_connection, libadb_protos, libandroidfw, libapp_processes_protos_full, libfastdeploy_host, libmdnssd, libopenscreen-discovery, libopenscreen-platform-impl, libusb, libutils, libziparchive, libzstd, AdbWinApi
-		"adbd",                    // depends on unconverted modules: libadb_crypto, libadb_pairing_connection, libadb_protos, libadbd, libadbd_core, libapp_processes_protos_lite, libmdnssd, libzstd, libadbd_services, libcap, libminijail, libselinux
-		"bionic_tests_zipalign",   // depends on unconverted modules: libziparchive, libutils
-		"linker",                  // depends on unconverted modules: liblinker_debuggerd_stub, libdebuggerd_handler_fallback, libziparchive, liblinker_main, liblinker_malloc
+		"abb",                     // depends on unconverted modules: libadbd_core, libadbd_services,
+		"adb",                     // depends on unconverted modules: bin2c_fastdeployagent, libadb_crypto, libadb_host, libadb_pairing_connection, libadb_protos, libandroidfw, libapp_processes_protos_full, libfastdeploy_host, libopenscreen-discovery, libopenscreen-platform-impl, libusb, libzstd, AdbWinApi
+		"adbd",                    // depends on unconverted modules: libadb_crypto, libadb_pairing_connection, libadb_protos, libadbd, libadbd_core, libapp_processes_protos_lite, libzstd, libadbd_services, libcap, libminijail
+		"linker",                  // depends on unconverted modules: libdebuggerd_handler_fallback
 		"linker_reloc_bench_main", // depends on unconverted modules: liblinker_reloc_bench_*
-		"sefcontext_compile",      // depends on unconverted modules: libsepol
-		"versioner",               // depends on unconverted modules: libclang_cxx_host, libLLVM_host
+		"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
@@ -334,6 +414,9 @@
 	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
 		"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
 	}
 
 	// Used for quicker lookups
diff --git a/android/config.go b/android/config.go
index 6f05565..ed63ddf 100644
--- a/android/config.go
+++ b/android/config.go
@@ -155,9 +155,10 @@
 	fs         pathtools.FileSystem
 	mockBpList string
 
-	runningAsBp2Build        bool
-	bp2buildPackageConfig    Bp2BuildConfig
-	bp2buildModuleTypeConfig map[string]bool
+	runningAsBp2Build              bool
+	bp2buildPackageConfig          Bp2BuildConfig
+	bp2buildModuleTypeConfig       map[string]bool
+	Bp2buildSoongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions
 
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
@@ -323,7 +324,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"},
@@ -855,7 +856,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.
diff --git a/android/defaults.go b/android/defaults.go
index be80cf1..9046002 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -213,10 +213,60 @@
 
 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 c8e2e9b..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",
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/makevars.go b/android/makevars.go
index 665d576..ece7091 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -456,6 +456,9 @@
 		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, " |")
 		}
@@ -463,12 +466,14 @@
 			fmt.Fprintf(buf, " %s", dep.String())
 		}
 		fmt.Fprintln(buf)
-
-		fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@", preserveSymlinksFlag)
+		fmt.Fprintln(buf, "\t@echo \"Install $@\"")
+		fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag)
 		if install.executable {
-			fmt.Fprintf(buf, " && chmod +x $@")
+			fmt.Fprintf(buf, "\tchmod +x $@\n")
 		}
-		fmt.Fprintln(buf)
+		if extraFiles := install.extraFiles; extraFiles != nil {
+			fmt.Fprintf(buf, "\tunzip -qDD -d '%s' '%s'\n", extraFiles.dir.String(), extraFiles.zip.String())
+		}
 		fmt.Fprintln(buf)
 	}
 
@@ -504,6 +509,7 @@
 			fromStr = symlink.absFrom
 		}
 
+		fmt.Fprintln(buf, "\t@echo \"Symlink $@\"")
 		fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr)
 		fmt.Fprintln(buf)
 		fmt.Fprintln(buf)
diff --git a/android/module.go b/android/module.go
index c778078..4a388b5 100644
--- a/android/module.go
+++ b/android/module.go
@@ -381,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.
 	//
@@ -1138,7 +1148,7 @@
 //         }
 //     }
 //
-//     func NewMyModule() android.Module) {
+//     func NewMyModule() android.Module {
 //         m := &myModule{}
 //         m.AddProperties(&m.properties)
 //         android.InitAndroidModule(m)
@@ -2235,10 +2245,16 @@
 	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
@@ -2852,12 +2868,20 @@
 
 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 {
@@ -2878,7 +2902,8 @@
 	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)
@@ -2906,6 +2931,7 @@
 				implicitDeps:  implicitDeps,
 				orderOnlyDeps: orderOnlyDeps,
 				executable:    executable,
+				extraFiles:    extraZip,
 			})
 		} else {
 			rule := Cp
@@ -2913,6 +2939,13 @@
 				rule = CpExecutable
 			}
 
+			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(),
@@ -2921,6 +2954,9 @@
 				Implicits:   implicitDeps,
 				OrderOnly:   orderOnlyDeps,
 				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"extraCmds": extraCmds,
+				},
 			})
 		}
 
diff --git a/android/mutator.go b/android/mutator.go
index 4b37377..461cb17 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -529,10 +529,10 @@
 	mod.base().addBp2buildInfo(info)
 }
 
-func (t *topDownMutatorContext) AppendProperties(props ...interface{}) {
+func (t *topDownMutatorContext) appendPrependHelper(props []interface{},
+	extendFn func([]interface{}, interface{}, proptools.ExtendPropertyFilterFunc) error) {
 	for _, p := range props {
-		err := proptools.AppendMatchingProperties(t.Module().base().customizableProperties,
-			p, nil)
+		err := extendFn(t.Module().base().customizableProperties, p, nil)
 		if err != nil {
 			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
 				t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
@@ -543,18 +543,12 @@
 	}
 }
 
+func (t *topDownMutatorContext) AppendProperties(props ...interface{}) {
+	t.appendPrependHelper(props, proptools.AppendMatchingProperties)
+}
+
 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)
-			}
-		}
-	}
+	t.appendPrependHelper(props, proptools.PrependMatchingProperties)
 }
 
 // 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 04366d3..b36bf04 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -214,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."),
 	}
 }
 
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 35aadd8..edda244 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -293,7 +293,7 @@
 			`),
 		},
 		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.",
 		},
 	},
 }
diff --git a/android/prebuilt.go b/android/prebuilt.go
index e189892..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
 }
@@ -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/soong_config_modules.go b/android/soong_config_modules.go
index 065440d..91bbce6 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -380,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...)
@@ -415,14 +418,13 @@
 	if !conditionalFactoryProps.IsValid() {
 		return factory
 	}
-	useBp2buildHook := bp2build && proptools.BoolDefault(moduleType.Bp2buildAvailable, false)
 
 	return func() (blueprint.Module, []interface{}) {
 		module, props := factory()
 		conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
 		props = append(props, conditionalProps.Interface())
 
-		if useBp2buildHook {
+		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
@@ -434,7 +436,7 @@
 					// 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: conditionalProps.Interface(),
+						moduleType.ConfigNamespace: []interface{}{conditionalProps.Interface()},
 					})
 				}
 			})
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 1af89ba..09a5057 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -15,12 +15,12 @@
 package soongconfig
 
 import (
-	"android/soong/bazel"
 	"fmt"
 	"io"
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/parser"
@@ -121,8 +121,6 @@
 
 	// the list of properties that this module type will extend.
 	Properties []string
-
-	Bazel_module bazel.BazelModuleProperties
 }
 
 func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
@@ -233,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.
@@ -436,7 +532,6 @@
 
 	affectableProperties []string
 	variableNames        []string
-	Bp2buildAvailable    *bool
 }
 
 func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
@@ -445,7 +540,6 @@
 		ConfigNamespace:      props.Config_namespace,
 		BaseModuleType:       props.Module_type,
 		variableNames:        props.Variables,
-		Bp2buildAvailable:    props.Bazel_module.Bp2build_available,
 	}
 
 	for _, name := range props.Bool_variables {
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/variable.go b/android/variable.go
index 89cd59e..a29c6f8 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -15,13 +15,14 @@
 package android
 
 import (
-	"android/soong/android/soongconfig"
-	"android/soong/bazel"
 	"fmt"
 	"reflect"
 	"runtime"
 	"strings"
 
+	"android/soong/android/soongconfig"
+	"android/soong/bazel"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -245,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"`
 
@@ -564,6 +565,10 @@
 	FullConfig string
 }
 
+func (p *ProductConfigProperty) AlwaysEmit() bool {
+	return p.Namespace != ""
+}
+
 func (p *ProductConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
 	if p.Namespace == "" {
 		return bazel.ProductVariableConfigurationAxis(p.FullConfig)
@@ -640,19 +645,135 @@
 	}
 
 	if m, ok := module.(Bazelable); ok && m.namespacedVariableProps() != nil {
-		for namespace, namespacedVariableProp := range m.namespacedVariableProps() {
-			productVariableValues(
-				soongconfig.SoongConfigProperty,
-				namespacedVariableProp,
-				namespace,
-				"",
-				&productConfigProperties)
+		for namespace, namespacedVariableProps := range m.namespacedVariableProps() {
+			for _, namespacedVariableProp := range namespacedVariableProps {
+				productVariableValues(
+					soongconfig.SoongConfigProperty,
+					namespacedVariableProp,
+					namespace,
+					"",
+					&productConfigProperties)
+			}
 		}
 	}
 
+	productConfigProperties.zeroValuesForNamespacedVariables()
+
 	return productConfigProperties
 }
 
+// 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
+		}
+	}
+
+	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 {
@@ -665,7 +786,19 @@
 		FullConfig: config,              // e.g. size, feature1-x86, size__conditions_default
 	}
 
-	(*p)[propertyName][productConfigProp] = property
+	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 (
@@ -701,19 +834,10 @@
 	return v, true
 }
 
-// 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)
-
+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: {
@@ -818,9 +942,10 @@
 						field.Field(k).Interface(), // e.g. ["-DDEFAULT"], ["foo", "bar"]
 					)
 				}
-			} else {
-				// Not a conditions_default or a struct prop, i.e. regular
-				// product variables, or not a string-typed config var.
+			} 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,
@@ -834,6 +959,20 @@
 	}
 }
 
+// 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/apex/androidmk.go b/apex/androidmk.go
index 2091274..7764b6b 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)
@@ -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:
@@ -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)")
 		}
@@ -389,6 +376,8 @@
 				}
 				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
@@ -413,16 +402,6 @@
 					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, " && "))
-				}
 
 				if a.mergedNotices.Merged.Valid() {
 					fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNotices.Merged.Path().String())
@@ -450,7 +429,7 @@
 				}
 
 				distCoverageFiles(w, "ndk_apis_usedby_apex", a.nativeApisUsedByModuleFile.String())
-				distCoverageFiles(w, "ndk_apis_usedby_apex", a.nativeApisBackedByModuleFile.String())
+				distCoverageFiles(w, "ndk_apis_backedby_apex", a.nativeApisBackedByModuleFile.String())
 				distCoverageFiles(w, "java_apis_used_by_apex", a.javaApisUsedByModuleFile.String())
 			}
 		}}
diff --git a/apex/apex.go b/apex/apex.go
index 20660d3..c1a4b40 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -145,6 +145,16 @@
 	// 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
@@ -399,14 +409,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.
@@ -432,6 +442,10 @@
 	modulePaths []string
 }
 
+func (*apexBundle) InstallBypassMake() bool {
+	return true
+}
+
 // apexFileClass represents a type of file that can be included in APEX.
 type apexFileClass int
 
@@ -2087,7 +2101,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.
@@ -2652,7 +2668,6 @@
 		"libbuildversion",
 		"libmath",
 		"libprocpartition",
-		"libsync",
 	}
 	//
 	// Module separator
@@ -2760,7 +2775,6 @@
 		"libstagefright_metadatautils",
 		"libstagefright_mpeg2extractor",
 		"libstagefright_mpeg2support",
-		"libsync",
 		"libui",
 		"libui_headers",
 		"libunwindstack",
@@ -2901,7 +2915,6 @@
 		"libstagefright_m4vh263dec",
 		"libstagefright_m4vh263enc",
 		"libstagefright_mp3dec",
-		"libsync",
 		"libui",
 		"libui_headers",
 		"libunwindstack",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 59ea206..130581c 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")
 	}),
 )
@@ -6204,7 +6206,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) {
diff --git a/apex/builder.go b/apex/builder.go
index 2e21ddf..0880e2b 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -414,18 +414,35 @@
 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
+	}
+
 	// 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()
-
+		var installedPath android.InstallPath
 		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
@@ -442,11 +459,22 @@
 		} else {
 			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
@@ -454,6 +482,10 @@
 		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)
+			}
 		}
 
 		// Copy the test files (if any)
@@ -472,6 +504,11 @@
 		}
 	}
 	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)
+	}
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
@@ -840,47 +877,55 @@
 		a.outputFile = signedCompressedOutputFile
 	}
 
+	installSuffix := suffix
+	if a.isCompressed {
+		installSuffix = imageCapexSuffix
+	}
+
 	// Install to $OUT/soong/{target,host}/.../apex.
-	ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
+	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
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 61e7a0b..84bdcdd 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -17,11 +17,13 @@
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
 	"android/soong/android"
 	"android/soong/java"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -53,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 {
@@ -223,13 +224,10 @@
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().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)
 				},
 			},
@@ -259,6 +257,9 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().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
@@ -471,6 +472,10 @@
 	inputApex android.Path
 }
 
+func (p *Prebuilt) InstallBypassMake() bool {
+	return true
+}
+
 type ApexFileProperties struct {
 	// the path to the prebuilt .apex file to import.
 	//
@@ -756,15 +761,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()...)
 	}
 }
 
@@ -964,10 +969,10 @@
 	}
 
 	// 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)...)
+		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx, true)...)
 	}
 }
 
diff --git a/apex/vndk.go b/apex/vndk.go
index 75c0fb0..cf525a8 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,25 @@
 	// 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")
+	if name == "com.android.art" {
+		dir := android.PathForModuleInPartitionInstall(ctx, "system", "bin")
+		addSymlink("/apex/com.android.art/bin/dalvikvm", dir, "dalvikvm")
 		dex2oat := "dex2oat32"
 		if ctx.Config().Android64() {
 			dex2oat = "dex2oat64"
 		}
-		addSymlink("/apex/com.android.art/bin/"+dex2oat, "$(TARGET_OUT)/bin", "dex2oat")
-		return
+		addSymlink("/apex/com.android.art/bin/"+dex2oat, dir, "dex2oat")
+	} else if name == "com.android.art" || strings.HasPrefix(name, "com.android.art.") {
+		dir := android.PathForModuleInPartitionInstall(ctx, "system", "bin")
+		symlinks = append(symlinks,
+			dir.Join(ctx, "dalvikvm"),
+			dir.Join(ctx, "dex2oat"))
 	}
-	return
+
+	return symlinks
 }
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 4475b5e..fd8cf67 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -271,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,
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index ca58606..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",
@@ -1378,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
 	}
@@ -1389,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/properties.go b/bazel/properties.go
index a438481..b370bbf 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -24,23 +24,6 @@
 	"github.com/google/blueprint"
 )
 
-type BazelModuleProperties struct {
-	// The label of the Bazel target replacing this Soong module. When run in conversion mode, this
-	// will import the handcrafted build target into the autogenerated file. Note: this may result in
-	// a conflict due to duplicate targets if bp2build_available is also set.
-	Label *string
-
-	// If true, bp2build will generate the converted Bazel target for this module. Note: this may
-	// cause a conflict due to the duplicate targets if label is also set.
-	//
-	// This is a bool pointer to support tristates: true, false, not set.
-	//
-	// To opt-in a module, set bazel_module: { bp2build_available: true }
-	// 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
-}
-
 // BazelTargetModuleProperties contain properties and metadata used for
 // Blueprint to BUILD file conversion.
 type BazelTargetModuleProperties struct {
@@ -417,6 +400,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
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 1250e92..9b66354 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -18,6 +18,7 @@
     ],
     deps: [
         "soong-android",
+        "soong-android-soongconfig",
         "soong-apex",
         "soong-bazel",
         "soong-cc",
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index aa1cf70..10ee582 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -532,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
@@ -556,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
@@ -565,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
 			}
@@ -575,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
 				}
@@ -660,7 +660,7 @@
 		}
 
 		propertyName := proptools.PropertyNameForField(field.Name)
-		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
+		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1, false)
 		if err != nil {
 			panic(
 				fmt.Errorf(
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index a3d902a..d23ea01 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -1691,8 +1691,10 @@
 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{
@@ -1702,45 +1704,58 @@
 		// 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"},
+		{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"},
+		{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"},
+		{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"},
+		{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"},
+		{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"},
+		{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"},
+		{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"},
+		{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 _, tc := range testCases {
+	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)
@@ -1749,61 +1764,67 @@
 		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),
+				"cc_library with c_std: %s, cpp_std: %s and gnu_extensions: %s", tc.c_std, tc.cpp_std, tc.gnu_extensions),
 			moduleTypeUnderTest:                "cc_library",
 			moduleTypeUnderTestFactory:         cc.LibraryFactory,
 			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
 			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
 cc_library {
-	name: "a",
+	name: "%s_full",
 %s // cpp_std: *string
+%s // c_std: *string
 %s // gnu_extensions: *bool
 	include_build_directory: false,
 }
-`, cppStdProp, gnuExtensionsProp),
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
 			expectedBazelTargets: []string{
-				makeBazelTarget("cc_library", "a", attrs),
+				makeBazelTarget("cc_library", 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),
+				"cc_library_static with c_std: %s, cpp_std: %s and gnu_extensions: %s", tc.c_std, tc.cpp_std, tc.gnu_extensions),
 			moduleTypeUnderTest:                "cc_library_static",
 			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
 			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
 			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
 cc_library_static {
-	name: "a",
+	name: "%s_static",
 %s // cpp_std: *string
+%s // c_std: *string
 %s // gnu_extensions: *bool
 	include_build_directory: false,
 }
-`, cppStdProp, gnuExtensionsProp),
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
 			expectedBazelTargets: []string{
-				makeBazelTarget("cc_library_static", "a", attrs),
+				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),
+				"cc_library_shared with c_std: %s, cpp_std: %s and gnu_extensions: %s", tc.c_std, tc.cpp_std, tc.gnu_extensions),
 			moduleTypeUnderTest:                "cc_library_shared",
 			moduleTypeUnderTestFactory:         cc.LibrarySharedFactory,
 			moduleTypeUnderTestBp2BuildMutator: cc.CcLibrarySharedBp2Build,
 			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
 cc_library_shared {
-	name: "a",
+	name: "%s_shared",
 %s // cpp_std: *string
+%s // c_std: *string
 %s // gnu_extensions: *bool
 	include_build_directory: false,
 }
-`, cppStdProp, gnuExtensionsProp),
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
 			expectedBazelTargets: []string{
-				makeBazelTarget("cc_library_shared", "a", attrs),
+				makeBazelTarget("cc_library_shared", name_prefix+"_shared", attrs),
 			},
 		})
 	}
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 0bcf91d..c953259 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -95,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
 			}
 		}
@@ -107,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
@@ -129,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) {
@@ -137,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
@@ -154,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
 		}
@@ -163,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
 		}
@@ -190,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
 	}
@@ -202,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
 		}
@@ -227,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
 	}
@@ -249,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 944cb83..81a4b26 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -25,6 +25,8 @@
 
 	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
 }
 
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index d09238a..3e6d9e6 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -82,7 +82,8 @@
 }
 
 func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
-	files := CreateSoongInjectionFiles(android.Config{}, CodegenMetrics{})
+	testConfig := android.TestConfig("", make(map[string]string), "", make(map[string][]byte))
+	files := CreateSoongInjectionFiles(testConfig, CodegenMetrics{})
 
 	expectedFilePaths := []bazelFilepath{
 		{
@@ -97,6 +98,10 @@
 			dir:      "metrics",
 			basename: "converted_modules.txt",
 		},
+		{
+			dir:      "product_config",
+			basename: "soong_config_variables.bzl",
+		},
 	}
 
 	if len(files) != len(expectedFilePaths) {
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
index 2e6f54c..d21db04 100644
--- a/bp2build/soong_config_module_type_conversion_test.go
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -32,6 +32,8 @@
 	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) {
@@ -42,7 +44,6 @@
 	config_namespace: "acme",
 	bool_variables: ["feature1"],
 	properties: ["cflags"],
-	bazel_module: { bp2build_available: true },
 }
 
 custom_cc_library_static {
@@ -83,7 +84,6 @@
 	config_namespace: "acme",
 	bool_variables: ["feature1"],
 	properties: ["cflags"],
-	bazel_module: { bp2build_available: true },
 }
 `
 	bp := `
@@ -138,7 +138,6 @@
 	config_namespace: "acme",
 	variables: ["board"],
 	properties: ["cflags"],
-	bazel_module: { bp2build_available: true },
 }
 
 custom_cc_library_static {
@@ -199,7 +198,6 @@
 	config_namespace: "acme",
 	variables: ["feature1", "feature2", "board"],
 	properties: ["cflags"],
-	bazel_module: { bp2build_available: true },
 }
 
 custom_cc_library_static {
@@ -269,7 +267,6 @@
 	config_namespace: "acme",
 	variables: ["board"],
 	properties: ["cflags", "static_libs"],
-	bazel_module: { bp2build_available: true },
 }
 
 custom_cc_library_static {
@@ -324,3 +321,527 @@
     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,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		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,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		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,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		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,
+		moduleTypeUnderTestBp2BuildMutator: cc.BinaryBp2build,
+		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,
+		moduleTypeUnderTestBp2BuildMutator: cc.BinaryBp2build,
+		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,
+		moduleTypeUnderTestBp2BuildMutator: cc.BinaryBp2build,
+		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/cc/androidmk.go b/cc/androidmk.go
index 45bb1ca..636bab8 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -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())
diff --git a/cc/bp2build.go b/cc/bp2build.go
index dde8dd4..eabd814 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -252,6 +252,7 @@
 
 	// Not affected by arch variants
 	stl    *string
+	cStd   *string
 	cppStd *string
 
 	localIncludes    bazel.StringListAttribute
@@ -278,8 +279,7 @@
 
 	localIncludeDirs := props.Local_include_dirs
 	if axis == bazel.NoConfigAxis {
-		ca.cppStd = bp2buildResolveCppStdValue(props.Cpp_std, props.Gnu_extensions)
-
+		ca.cStd, ca.cppStd = bp2buildResolveCppStdValue(props.C_std, props.Cpp_std, props.Gnu_extensions)
 		if includeBuildDirectory(props.Include_build_directory) {
 			localIncludeDirs = append(localIncludeDirs, ".")
 		}
@@ -371,24 +371,24 @@
 	return bazel.AppendBazelLabelLists(allSrcsLabelList, generatedSrcsLabelList), anySrcs
 }
 
-func bp2buildResolveCppStdValue(cpp_std *string, gnu_extensions *bool) *string {
-	var cppStd *string
-	// If cpp_std is not specified, don't generate it in the
-	// BUILD file. For readability purposes, cpp_std and gnu_extensions are
-	// combined into a single -std=<version> copt, except in the
-	// default case where cpp_std is nil and gnu_extensions is true or unspecified,
-	// then the toolchain's default "gnu++17" will be used.
+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 {
-		// TODO(b/202491296): Handle C_std.
-		// These transformations are shared with compiler.go.
-		cppStdVal := parseCppStd(cpp_std)
-		_, cppStdVal = maybeReplaceGnuToC(gnu_extensions, "", cppStdVal)
-		cppStd = &cppStdVal
+		cppStdVal = parseCppStd(cpp_std)
 	} else if gnu_extensions != nil && !*gnu_extensions {
-		cppStdVal := "c++17"
-		cppStd = &cppStdVal
+		cppStdVal = "c++17"
 	}
-	return cppStd
+	if c_std != nil {
+		cStdVal = parseCStd(c_std)
+	} else if gnu_extensions != nil && !*gnu_extensions {
+		cStdVal = "c99"
+	}
+
+	cStdVal, cppStdVal = maybeReplaceGnuToC(gnu_extensions, cStdVal, cppStdVal)
+	return &cStdVal, &cppStdVal
 }
 
 // bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
@@ -611,6 +611,7 @@
 				ctx.ModuleErrorf("Could not convert product variable %s property", dep.excludesField)
 			}
 
+			dep.attribute.EmitEmptyList = productConfigProp.AlwaysEmit()
 			dep.attribute.SetSelectValue(
 				productConfigProp.ConfigurationAxis(),
 				productConfigProp.SelectKey(),
diff --git a/cc/compiler.go b/cc/compiler.go
index ffe8b2e..2e62b00 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -304,11 +304,24 @@
 	cppStd := String(cppStdPtr)
 	switch cppStd {
 	case "":
-		cppStd = config.CppStdVersion
+		return config.CppStdVersion
 	case "experimental":
-		cppStd = config.ExperimentalCppStdVersion
+		return config.ExperimentalCppStdVersion
+	default:
+		return cppStd
 	}
-	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,
@@ -479,13 +492,7 @@
 
 	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)
 
 	cStd, cppStd = maybeReplaceGnuToC(compiler.Properties.Gnu_extensions, cStd, cppStd)
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 9577a8c..492cd98 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -101,6 +101,9 @@
 	"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",
diff --git a/cc/library.go b/cc/library.go
index dbf927d..3dceda0 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -253,6 +253,7 @@
 
 	Stl     *string
 	Cpp_std *string
+	C_std   *string
 
 	// This is shared only.
 	Link_crt                 bazel.BoolAttribute
@@ -335,6 +336,7 @@
 		Rtti:                        compilerAttrs.rtti,
 		Stl:                         compilerAttrs.stl,
 		Cpp_std:                     compilerAttrs.cppStd,
+		C_std:                       compilerAttrs.cStd,
 
 		Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
 
@@ -2420,6 +2422,7 @@
 			Rtti:                   compilerAttrs.rtti,
 			Stl:                    compilerAttrs.stl,
 			Cpp_std:                compilerAttrs.cppStd,
+			C_std:                  compilerAttrs.cStd,
 			Export_includes:        exportedIncludes.Includes,
 			Export_system_includes: exportedIncludes.SystemIncludes,
 			Local_includes:         compilerAttrs.localIncludes,
@@ -2445,6 +2448,7 @@
 			Rtti:       compilerAttrs.rtti,
 			Stl:        compilerAttrs.stl,
 			Cpp_std:    compilerAttrs.cppStd,
+			C_std:      compilerAttrs.cStd,
 
 			Export_includes:          exportedIncludes.Includes,
 			Export_system_includes:   exportedIncludes.SystemIncludes,
@@ -2480,6 +2484,7 @@
 	Rtti       bazel.BoolAttribute
 	Stl        *string
 	Cpp_std    *string
+	C_std      *string
 
 	Export_includes        bazel.StringListAttribute
 	Export_system_includes bazel.StringListAttribute
@@ -2516,6 +2521,7 @@
 	Rtti       bazel.BoolAttribute
 	Stl        *string
 	Cpp_std    *string
+	C_std      *string
 
 	Export_includes        bazel.StringListAttribute
 	Export_system_includes bazel.StringListAttribute
diff --git a/cc/library_headers.go b/cc/library_headers.go
index ede6ab3..141a2a5 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -102,7 +102,7 @@
 
 // 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()
 }
diff --git a/cc/library_headers_test.go b/cc/library_headers_test.go
index 564ef61..9789ab0 100644
--- a/cc/library_headers_test.go
+++ b/cc/library_headers_test.go
@@ -15,8 +15,13 @@
 package cc
 
 import (
+	"fmt"
 	"strings"
 	"testing"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
 )
 
 func TestLibraryHeaders(t *testing.T) {
@@ -60,3 +65,43 @@
 		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
 	}
 }
+
+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"],
+		}
+	`
+
+	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/prebuilt.go b/cc/prebuilt.go
index 16945ac..a279054 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -235,7 +235,7 @@
 	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 +247,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 +265,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,14 +284,14 @@
 // 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()
 
 	// Prebuilt shared libraries can be included in APEXes
@@ -304,7 +308,7 @@
 }
 
 func NewPrebuiltStaticLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
-	module, library := NewPrebuiltLibrary(hod)
+	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyStatic()
 	module.bazelHandler = &prebuiltStaticLibraryBazelHandler{module: module, library: library}
 	return module, library
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/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/java/androidmk.go b/java/androidmk.go
index 4c115d5..272a4fd 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -696,12 +696,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_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..0aae928 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"],
diff --git a/java/base.go b/java/base.go
index c1a5299..d9b1260 100644
--- a/java/base.go
+++ b/java/base.go
@@ -510,9 +510,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")
 		}
 
 	}
@@ -1066,7 +1066,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
@@ -1076,7 +1076,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
@@ -1105,7 +1105,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 {
@@ -1508,7 +1510,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 {
@@ -1519,6 +1521,7 @@
 			return nil, nil
 		}
 		jars = append(jars, turbineJar)
+		headerJar = turbineJar
 	}
 
 	jars = append(jars, extraJars...)
@@ -1532,20 +1535,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,
diff --git a/java/builder.go b/java/builder.go
index ae124a3..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{
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 284a19a..a722946 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -625,7 +625,6 @@
 		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx)
 
 	if profile != nil {
-		cmd.FlagWithArg("--compiler-filter=", "speed-profile")
 		cmd.FlagWithInput("--profile-file=", profile)
 	}
 
diff --git a/java/java_test.go b/java/java_test.go
index c039f72..6e4e673 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -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)
 		}
 	}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index d7f14d6..3065d57 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.
@@ -637,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
@@ -1580,14 +1590,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)
@@ -2383,6 +2408,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.
@@ -2459,11 +2516,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)
@@ -2496,6 +2623,81 @@
 	}}
 }
 
+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
 }
@@ -2547,6 +2749,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 {
@@ -2593,6 +2822,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) {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index be23536..2271573 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,
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index d75635c..e263cc4 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -125,6 +125,12 @@
 	// TODO(b/203233647): Add better mechanism to make it optional.
 	_, unknown = android.RemoveFromList("car-frameworks-service-module", 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.
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index 7bb0246..bb5a680 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -46,8 +46,6 @@
 	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`)
-	noWarn   = flag.Bool("no_warnings", false, "don't 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
@@ -75,7 +73,6 @@
 	flagAlias("root", "d")
 	flagAlias("dry_run", "n")
 	flagAlias("convert_dependents", "r")
-	flagAlias("no_warnings", "w")
 	flagAlias("error_stat", "e")
 }
 
@@ -207,9 +204,9 @@
 		}
 	}
 
-	printStats()
 	if *errstat {
 		errorLogger.printStatistics()
+		printStats()
 	}
 	if !ok {
 		os.Exit(1)
@@ -329,17 +326,16 @@
 	}()
 
 	mk2starRequest := mk2rbc.Request{
-		MkFile:             mkFile,
-		Reader:             nil,
-		RootDir:            *rootDir,
-		OutputDir:          *outputTop,
-		OutputSuffix:       *suffix,
-		TracedVariables:    tracedVariables,
-		TraceCalls:         *traceCalls,
-		WarnPartialSuccess: !*noWarn,
-		SourceFS:           os.DirFS(*rootDir),
-		MakefileFinder:     makefileFinder,
-		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 {
@@ -417,9 +413,6 @@
 
 func printStats() {
 	var sortedFiles []string
-	if *noWarn && !*verbose {
-		return
-	}
 	for p := range converted {
 		sortedFiles = append(sortedFiles, p)
 	}
@@ -435,27 +428,22 @@
 			nOk++
 		}
 	}
-	if !*noWarn {
-		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 && (nPartial > 0 || nFailed > 0) {
-		fmt.Fprintln(os.Stderr, "Succeeded: ", nOk, " Partial: ", nPartial, " 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)
+			}
+		}
 	}
 }
 
@@ -468,8 +456,8 @@
 	data map[string]datum
 }
 
-func (ebt errorSink) NewError(sourceFile string, sourceLine int, node parser.Node, message string, args ...interface{}) {
-	fmt.Fprintf(os.Stderr, "%s:%d ", sourceFile, sourceLine)
+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 {
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 8279d2e..ec0b279 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -18,8 +18,6 @@
 	"fmt"
 	"strconv"
 	"strings"
-
-	mkparser "android/soong/androidmk/parser"
 )
 
 // Represents an expression in the Starlark code. An expression has
@@ -106,7 +104,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,12 +114,12 @@
 		}
 	}
 	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(")")
@@ -224,9 +222,9 @@
 		s.expr.emit(ctx)
 		ctx.write("))")
 	case starlarkTypeBool:
-		ctx.write("((")
+		ctx.write(`("true" if (`)
 		s.expr.emit(ctx)
-		ctx.write(`) ? "true" : "")`)
+		ctx.write(`) else "")`)
 	case starlarkTypeVoid:
 		ctx.write(`""`)
 	default:
@@ -285,20 +283,33 @@
 }
 
 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() {
@@ -414,7 +425,7 @@
 	return &v
 }
 
-// concatExpr generates epxr1 + expr2 + ... + exprN in Starlark.
+// concatExpr generates expr1 + expr2 + ... + exprN in Starlark.
 type concatExpr struct {
 	items []starlarkExpr
 }
@@ -607,8 +618,8 @@
 }
 
 type badExpr struct {
-	node    mkparser.Node
-	message string
+	errorLocation ErrorLocation
+	message       string
 }
 
 func (b *badExpr) eval(_ map[string]starlarkExpr) (res starlarkExpr, same bool) {
@@ -617,15 +628,15 @@
 	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")
 }
 
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index b87ab41..4b9779c 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -102,6 +102,7 @@
 	"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},
@@ -156,23 +157,31 @@
 
 // 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        ErrorLogger
-	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
 }
 
 // ErrorLogger prints errors and gathers error statistics.
 // Its NewError function is called on every error encountered during the conversion.
 type ErrorLogger interface {
-	NewError(sourceFile string, sourceLine int, node mkparser.Node, text string, args ...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
@@ -248,10 +257,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)
@@ -306,6 +311,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
@@ -370,18 +379,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
-	nodeLocator        func(pos mkparser.Pos) int
+	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) {
@@ -560,7 +568,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)
@@ -1023,10 +1031,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(ctx.script.mkFile, ctx.script.nodeLocator(node.Pos()), node, text, 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 {
@@ -1544,18 +1552,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(ctx.script.mkFile, ctx.script.nodeLocator(failedNode.Pos()), failedNode, message, 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 {
@@ -1621,6 +1625,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()
 }
@@ -1659,14 +1667,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,
-		nodeLocator:        func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
+		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
@@ -1704,7 +1711,7 @@
 	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, globals_base)\n", cfnPrintGlobals)
+	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 c2c4654..dfdf274 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -105,15 +105,12 @@
 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")
 `,
 	},
 	{
@@ -207,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")
 `,
 	},
 	{
@@ -282,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\"")
 `,
 	},
 	{
@@ -368,6 +359,21 @@
 `,
 	},
 	{
+		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: `
@@ -689,6 +695,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")
 
@@ -698,6 +705,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")
 `,
 	},
 	{
@@ -857,9 +865,7 @@
   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")
-  # MK2RBC TRANSLATION ERROR: SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config
-  # x := $(SOONG_CONFIG_cvd_grub_config)
-  rblf.warning("product.mk", "partially successful conversion")
+  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",
@@ -1046,15 +1052,11 @@
 		in: `
 foo: foo.c
 	gcc -o $@ $*`,
-		expected: `# MK2RBC TRANSLATION ERROR: unsupported line rule:       foo: foo.c
-#gcc -o $@ $*
-# rule:       foo: foo.c
-# gcc -o $@ $*
-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", "unsupported line rule:       foo: foo.c\n#gcc -o $@ $*")
 `,
 	},
 	{
@@ -1062,14 +1064,27 @@
 		mkname: "product.mk",
 		in: `
 override FOO:=`,
-		expected: `# MK2RBC TRANSLATION ERROR: cannot handle override directive
-# override FOO :=
-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.mk2rbc_error("product.mk:2", "cannot handle override directive")
   g["override FOO"] = ""
-  rblf.warning("product.mk", "partially successful conversion")
+`,
+	},
+	{
+		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
 `,
 	},
 }
@@ -1079,6 +1094,7 @@
 	class varClass
 	starlarkType
 }{
+	{"NATIVE_COVERAGE", VarClassSoong, starlarkTypeBool},
 	{"PRODUCT_NAME", VarClassConfig, starlarkTypeString},
 	{"PRODUCT_MODEL", VarClassConfig, starlarkTypeString},
 	{"PRODUCT_PACKAGES", VarClassConfig, starlarkTypeList},
@@ -1140,13 +1156,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 3fe1a17..d38299d 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -183,6 +183,7 @@
 	value    starlarkExpr
 	mkValue  *mkparser.MakeString
 	flavor   assignmentFlavor
+	location ErrorLocation
 	isTraced bool
 	previous *assignmentNode
 }
@@ -223,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/variable.go b/mk2rbc/variable.go
index ded07fe..6b67a7c 100644
--- a/mk2rbc/variable.go
+++ b/mk2rbc/variable.go
@@ -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/rust/Android.bp b/rust/Android.bp
index cda2dbc..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",
diff --git a/rust/config/global.go b/rust/config/global.go
index ebddb75..d3826ac 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.56.1"
+	RustDefaultVersion = "1.56.1p1"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/rust.go b/rust/rust.go
index 0fd781e..b575c7a 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -581,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
 		}
 	}
@@ -1554,7 +1554,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/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/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index f183c05..1e3f6ce 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -17,7 +17,6 @@
 MODULES_SDK_AND_EXPORTS=(
   art-module-sdk
   art-module-test-exports
-  compos-module-sdk
   conscrypt-module-host-exports
   conscrypt-module-sdk
   conscrypt-module-test-exports
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/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/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index a376e06..b19949a 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -7,4 +7,4 @@
 "$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_product_config.sh" aosp_arm64-userdebug
+"$TOP/build/bazel/ci/rbc_regression_test.sh" aosp_arm64-userdebug
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 1590ab0..96f6389 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -23,6 +23,7 @@
         "golang-protobuf-proto",
         "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
+        "soong-ui-bp2build_metrics_proto",
         "soong-ui-tracer",
     ],
     srcs: [
@@ -57,3 +58,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