Merge "disable usage of plugins as static libs"
diff --git a/Android.bp b/Android.bp
index 42a8e5c..63de015 100644
--- a/Android.bp
+++ b/Android.bp
@@ -119,3 +119,14 @@
 dexpreopt_systemserver_check {
     name: "dexpreopt_systemserver_check",
 }
+
+// buildinfo.prop contains common properties for system/build.prop, like ro.build.version.*
+buildinfo_prop {
+    name: "buildinfo.prop",
+
+    // not installable because this will be included to system/build.prop
+    installable: false,
+
+    // Currently, only microdroid can refer to buildinfo.prop
+    visibility: ["//packages/modules/Virtualization/microdroid"],
+}
diff --git a/OWNERS b/OWNERS
index 0cfb241..0662016 100644
--- a/OWNERS
+++ b/OWNERS
@@ -2,12 +2,13 @@
 # approving build related projects.
 
 # AMER
-ahumesky@google.com
+agespino@google.com
 alexmarquez@google.com
 asmundak@google.com
 ccross@android.com
 colefaust@google.com
 cparsons@google.com
+dacek@google.com
 delmerico@google.com
 dwillemsen@google.com
 eakammer@google.com
@@ -17,13 +18,12 @@
 spandandas@google.com
 tradical@google.com
 usta@google.com
+vinhdaitran@google.com
 weiwli@google.com
 yudiliu@google.com
-yuntaoxu@google.com
 
 # APAC
 jingwen@google.com
-ruperts@google.com
 
 # EMEA
 hansson@google.com
diff --git a/android/Android.bp b/android/Android.bp
index 49d5b91..d583703 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -19,6 +19,7 @@
         "soong-shared",
         "soong-starlark-format",
         "soong-ui-metrics_proto",
+        "soong-android-allowlists",
 
         "golang-protobuf-proto",
         "golang-protobuf-encoding-prototext",
@@ -35,6 +36,7 @@
         "bazel.go",
         "bazel_handler.go",
         "bazel_paths.go",
+        "buildinfo_prop.go",
         "config.go",
         "config_bp2build.go",
         "csuite_config.go",
diff --git a/android/allowlists/Android.bp b/android/allowlists/Android.bp
new file mode 100644
index 0000000..05cffc1
--- /dev/null
+++ b/android/allowlists/Android.bp
@@ -0,0 +1,25 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-android-allowlists",
+    pkgPath: "android/soong/android/allowlists",
+    srcs: [
+        "allowlists.go",
+    ],
+}
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
new file mode 100644
index 0000000..9aae6fd
--- /dev/null
+++ b/android/allowlists/allowlists.go
@@ -0,0 +1,425 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package allowlists
+
+// Configuration to decide if modules in a directory should default to true/false for bp2build_available
+type Bp2BuildConfig map[string]BazelConversionConfigEntry
+type BazelConversionConfigEntry int
+
+const (
+	// iota + 1 ensures that the int value is not 0 when used in the Bp2buildAllowlist map,
+	// which can also mean that the key doesn't exist in a lookup.
+
+	// all modules in this package and subpackages default to bp2build_available: true.
+	// allows modules to opt-out.
+	Bp2BuildDefaultTrueRecursively BazelConversionConfigEntry = iota + 1
+
+	// all modules in this package (not recursively) default to bp2build_available: true.
+	// allows modules to opt-out.
+	Bp2BuildDefaultTrue
+
+	// all modules in this package (not recursively) default to bp2build_available: false.
+	// allows modules to opt-in.
+	Bp2BuildDefaultFalse
+)
+
+var (
+	Bp2buildDefaultConfig = Bp2BuildConfig{
+		"art/libartpalette":                     Bp2BuildDefaultTrueRecursively,
+		"art/libdexfile":                        Bp2BuildDefaultTrueRecursively,
+		"art/runtime":                           Bp2BuildDefaultTrueRecursively,
+		"art/tools":                             Bp2BuildDefaultTrue,
+		"bionic":                                Bp2BuildDefaultTrueRecursively,
+		"bootable/recovery/tools/recovery_l10n": Bp2BuildDefaultTrue,
+		"build/bazel/examples/soong_config_variables":        Bp2BuildDefaultTrueRecursively,
+		"build/bazel/examples/apex/minimal":                  Bp2BuildDefaultTrueRecursively,
+		"build/make/tools/signapk":                           Bp2BuildDefaultTrue,
+		"build/make/target/product/security":                 Bp2BuildDefaultTrue,
+		"build/soong":                                        Bp2BuildDefaultTrue,
+		"build/soong/cc/libbuildversion":                     Bp2BuildDefaultTrue, // Skip tests subdir
+		"build/soong/cc/ndkstubgen":                          Bp2BuildDefaultTrue,
+		"build/soong/cc/symbolfile":                          Bp2BuildDefaultTrue,
+		"build/soong/linkerconfig":                           Bp2BuildDefaultTrueRecursively,
+		"build/soong/scripts":                                Bp2BuildDefaultTrueRecursively,
+		"cts/common/device-side/nativetesthelper/jni":        Bp2BuildDefaultTrueRecursively,
+		"development/apps/DevelopmentSettings":               Bp2BuildDefaultTrue,
+		"development/apps/Fallback":                          Bp2BuildDefaultTrue,
+		"development/apps/WidgetPreview":                     Bp2BuildDefaultTrue,
+		"development/samples/BasicGLSurfaceView":             Bp2BuildDefaultTrue,
+		"development/samples/BluetoothChat":                  Bp2BuildDefaultTrue,
+		"development/samples/BrokenKeyDerivation":            Bp2BuildDefaultTrue,
+		"development/samples/Compass":                        Bp2BuildDefaultTrue,
+		"development/samples/ContactManager":                 Bp2BuildDefaultTrue,
+		"development/samples/FixedGridLayout":                Bp2BuildDefaultTrue,
+		"development/samples/HelloEffects":                   Bp2BuildDefaultTrue,
+		"development/samples/Home":                           Bp2BuildDefaultTrue,
+		"development/samples/HoneycombGallery":               Bp2BuildDefaultTrue,
+		"development/samples/JetBoy":                         Bp2BuildDefaultTrue,
+		"development/samples/KeyChainDemo":                   Bp2BuildDefaultTrue,
+		"development/samples/LceDemo":                        Bp2BuildDefaultTrue,
+		"development/samples/LunarLander":                    Bp2BuildDefaultTrue,
+		"development/samples/MultiResolution":                Bp2BuildDefaultTrue,
+		"development/samples/MultiWindow":                    Bp2BuildDefaultTrue,
+		"development/samples/NotePad":                        Bp2BuildDefaultTrue,
+		"development/samples/Obb":                            Bp2BuildDefaultTrue,
+		"development/samples/RSSReader":                      Bp2BuildDefaultTrue,
+		"development/samples/ReceiveShareDemo":               Bp2BuildDefaultTrue,
+		"development/samples/SearchableDictionary":           Bp2BuildDefaultTrue,
+		"development/samples/SipDemo":                        Bp2BuildDefaultTrue,
+		"development/samples/SkeletonApp":                    Bp2BuildDefaultTrue,
+		"development/samples/Snake":                          Bp2BuildDefaultTrue,
+		"development/samples/SpellChecker/":                  Bp2BuildDefaultTrueRecursively,
+		"development/samples/ThemedNavBarKeyboard":           Bp2BuildDefaultTrue,
+		"development/samples/ToyVpn":                         Bp2BuildDefaultTrue,
+		"development/samples/TtsEngine":                      Bp2BuildDefaultTrue,
+		"development/samples/USB/AdbTest":                    Bp2BuildDefaultTrue,
+		"development/samples/USB/MissileLauncher":            Bp2BuildDefaultTrue,
+		"development/samples/VoiceRecognitionService":        Bp2BuildDefaultTrue,
+		"development/samples/VoicemailProviderDemo":          Bp2BuildDefaultTrue,
+		"development/samples/WiFiDirectDemo":                 Bp2BuildDefaultTrue,
+		"development/sdk":                                    Bp2BuildDefaultTrueRecursively,
+		"external/arm-optimized-routines":                    Bp2BuildDefaultTrueRecursively,
+		"external/auto/android-annotation-stubs":             Bp2BuildDefaultTrueRecursively,
+		"external/auto/common":                               Bp2BuildDefaultTrueRecursively,
+		"external/auto/service":                              Bp2BuildDefaultTrueRecursively,
+		"external/boringssl":                                 Bp2BuildDefaultTrueRecursively,
+		"external/bouncycastle":                              Bp2BuildDefaultTrue,
+		"external/brotli":                                    Bp2BuildDefaultTrue,
+		"external/conscrypt":                                 Bp2BuildDefaultTrue,
+		"external/e2fsprogs":                                 Bp2BuildDefaultTrueRecursively,
+		"external/error_prone":                               Bp2BuildDefaultTrueRecursively,
+		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
+		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
+		"external/googletest":                                Bp2BuildDefaultTrueRecursively,
+		"external/gwp_asan":                                  Bp2BuildDefaultTrueRecursively,
+		"external/hamcrest":                                  Bp2BuildDefaultTrueRecursively,
+		"external/icu":                                       Bp2BuildDefaultTrueRecursively,
+		"external/icu/android_icu4j":                         Bp2BuildDefaultFalse, // java rules incomplete
+		"external/icu/icu4j":                                 Bp2BuildDefaultFalse, // java rules incomplete
+		"external/jarjar":                                    Bp2BuildDefaultTrueRecursively,
+		"external/javapoet":                                  Bp2BuildDefaultTrueRecursively,
+		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
+		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
+		"external/junit":                                     Bp2BuildDefaultTrueRecursively,
+		"external/libcap":                                    Bp2BuildDefaultTrueRecursively,
+		"external/libcxx":                                    Bp2BuildDefaultTrueRecursively,
+		"external/libcxxabi":                                 Bp2BuildDefaultTrueRecursively,
+		"external/libevent":                                  Bp2BuildDefaultTrueRecursively,
+		"external/libpng":                                    Bp2BuildDefaultTrueRecursively,
+		"external/lz4/lib":                                   Bp2BuildDefaultTrue,
+		"external/lzma/C":                                    Bp2BuildDefaultTrueRecursively,
+		"external/mdnsresponder":                             Bp2BuildDefaultTrueRecursively,
+		"external/minijail":                                  Bp2BuildDefaultTrueRecursively,
+		"external/pcre":                                      Bp2BuildDefaultTrueRecursively,
+		"external/protobuf":                                  Bp2BuildDefaultTrueRecursively,
+		"external/python/six":                                Bp2BuildDefaultTrueRecursively,
+		"external/rappor":                                    Bp2BuildDefaultTrueRecursively,
+		"external/scudo":                                     Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libselinux":                        Bp2BuildDefaultTrueRecursively,
+		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
+		"external/zlib":                                      Bp2BuildDefaultTrueRecursively,
+		"external/zstd":                                      Bp2BuildDefaultTrueRecursively,
+		"frameworks/base/media/tests/MediaDump":              Bp2BuildDefaultTrue,
+		"frameworks/base/startop/apps/test":                  Bp2BuildDefaultTrue,
+		"frameworks/base/tests/appwidgets/AppWidgetHostTest": Bp2BuildDefaultTrueRecursively,
+		"frameworks/native/libs/adbd_auth":                   Bp2BuildDefaultTrueRecursively,
+		"frameworks/native/opengl/tests/gl2_cameraeye":       Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/gl2_java":            Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testLatency":         Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testPauseResume":     Bp2BuildDefaultTrue,
+		"frameworks/native/opengl/tests/testViewport":        Bp2BuildDefaultTrue,
+		"frameworks/proto_logging/stats/stats_log_api_gen":   Bp2BuildDefaultTrueRecursively,
+		"libnativehelper":                                    Bp2BuildDefaultTrueRecursively,
+		"packages/apps/DevCamera":                            Bp2BuildDefaultTrue,
+		"packages/apps/HTMLViewer":                           Bp2BuildDefaultTrue,
+		"packages/apps/Protips":                              Bp2BuildDefaultTrue,
+		"packages/modules/StatsD/lib/libstatssocket":         Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb":                               Bp2BuildDefaultTrue,
+		"packages/modules/adb/apex":                          Bp2BuildDefaultTrue,
+		"packages/modules/adb/crypto":                        Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/libs":                          Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/pairing_auth":                  Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/pairing_connection":            Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/proto":                         Bp2BuildDefaultTrueRecursively,
+		"packages/modules/adb/tls":                           Bp2BuildDefaultTrueRecursively,
+		"packages/providers/MediaProvider/tools/dialogs":     Bp2BuildDefaultTrue,
+		"packages/screensavers/Basic":                        Bp2BuildDefaultTrue,
+		"packages/services/Car/tests/SampleRearViewCamera":   Bp2BuildDefaultTrue,
+		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
+		"prebuilts/tools/common/m2":                          Bp2BuildDefaultTrue,
+		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
+		"system/apex/proto":                                  Bp2BuildDefaultTrueRecursively,
+		"system/apex/libs":                                   Bp2BuildDefaultTrueRecursively,
+		"system/core/debuggerd":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/diagnose_usb":                           Bp2BuildDefaultTrueRecursively,
+		"system/core/libasyncio":                             Bp2BuildDefaultTrue,
+		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
+		"system/core/libcutils":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/libpackagelistparser":                   Bp2BuildDefaultTrueRecursively,
+		"system/core/libprocessgroup":                        Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc":               Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc_format":        Bp2BuildDefaultTrue,
+		"system/core/libsystem":                              Bp2BuildDefaultTrueRecursively,
+		"system/core/libutils":                               Bp2BuildDefaultTrueRecursively,
+		"system/core/libvndksupport":                         Bp2BuildDefaultTrueRecursively,
+		"system/core/property_service/libpropertyinfoparser": Bp2BuildDefaultTrueRecursively,
+		"system/libbase":                                     Bp2BuildDefaultTrueRecursively,
+		"system/libprocinfo":                                 Bp2BuildDefaultTrue,
+		"system/libziparchive":                               Bp2BuildDefaultTrueRecursively,
+		"system/logging/liblog":                              Bp2BuildDefaultTrueRecursively,
+		"system/memory/libmemunreachable":                    Bp2BuildDefaultTrueRecursively,
+		"system/sepolicy/apex":                               Bp2BuildDefaultTrueRecursively,
+		"system/timezone/apex":                               Bp2BuildDefaultTrueRecursively,
+		"system/timezone/output_data":                        Bp2BuildDefaultTrueRecursively,
+		"system/unwinding/libbacktrace":                      Bp2BuildDefaultTrueRecursively,
+		"system/unwinding/libunwindstack":                    Bp2BuildDefaultTrueRecursively,
+		"tools/apksig":                                       Bp2BuildDefaultTrue,
+		"tools/platform-compat/java/android/compat":          Bp2BuildDefaultTrueRecursively,
+	}
+
+	Bp2buildKeepExistingBuildFile = map[string]bool{
+		// This is actually build/bazel/build.BAZEL symlinked to ./BUILD
+		".":/*recursive = */ false,
+
+		// build/bazel/examples/apex/... BUILD files should be generated, so
+		// build/bazel is not recursive. Instead list each subdirectory under
+		// build/bazel explicitly.
+		"build/bazel":/* recursive = */ false,
+		"build/bazel/ci/dist":/* recursive = */ false,
+		"build/bazel/examples/android_app":/* recursive = */ true,
+		"build/bazel/examples/java":/* recursive = */ true,
+		"build/bazel/bazel_skylib":/* recursive = */ true,
+		"build/bazel/rules":/* recursive = */ true,
+		"build/bazel/rules_cc":/* recursive = */ true,
+		"build/bazel/scripts":/* recursive = */ true,
+		"build/bazel/tests":/* recursive = */ true,
+		"build/bazel/platforms":/* recursive = */ true,
+		"build/bazel/product_variables":/* recursive = */ true,
+		"build/bazel/vendor/google":/* recursive = */ true,
+		"build/bazel_common_rules":/* recursive = */ true,
+		// build/make/tools/signapk BUILD file is generated, so build/make/tools is not recursive.
+		"build/make/tools":/* recursive = */ false,
+		"build/pesto":/* recursive = */ true,
+
+		// external/bazelbuild-rules_android/... is needed by mixed builds, otherwise mixed builds analysis fails
+		// e.g. ERROR: Analysis of target '@soong_injection//mixed_builds:buildroot' failed
+		"external/bazelbuild-rules_android":/* recursive = */ true,
+		"external/bazel-skylib":/* recursive = */ true,
+		"external/guava":/* recursive = */ true,
+		"external/jsr305":/* recursive = */ true,
+		"frameworks/ex/common":/* recursive = */ true,
+
+		"packages/apps/Music":/* recursive = */ true,
+		"packages/apps/QuickSearchBox":/* recursive = */ true,
+		"packages/apps/WallpaperPicker":/* recursive = */ false,
+
+		"prebuilts/bundletool":/* recursive = */ true,
+		"prebuilts/gcc":/* recursive = */ true,
+		"prebuilts/build-tools":/* recursive = */ false,
+		"prebuilts/jdk/jdk11":/* recursive = */ false,
+		"prebuilts/sdk":/* recursive = */ false,
+		"prebuilts/sdk/current/extras/app-toolkit":/* recursive = */ false,
+		"prebuilts/sdk/current/support":/* recursive = */ false,
+		"prebuilts/sdk/tools":/* recursive = */ false,
+		"prebuilts/r8":/* recursive = */ false,
+	}
+
+	Bp2buildModuleAlwaysConvertList = []string{
+		//external/avb
+		"avbtool",
+		"libavb",
+		"avb_headers",
+
+		//external/fec
+		"libfec_rs",
+
+		//system/core/libsparse
+		"libsparse",
+
+		//system/extras/ext4_utils
+		"libext4_utils",
+
+		//system/extras/libfec
+		"libfec",
+
+		//system/extras/squashfs_utils
+		"libsquashfs_utils",
+
+		//system/extras/verity/fec
+		"fec",
+
+		//packages/apps/Car/libs/car-ui-lib/car-ui-androidx
+		// genrule dependencies for java_imports
+		"car-ui-androidx-annotation-nodeps",
+		"car-ui-androidx-collection-nodeps",
+		"car-ui-androidx-core-common-nodeps",
+		"car-ui-androidx-lifecycle-common-nodeps",
+		"car-ui-androidx-constraintlayout-solver-nodeps",
+	}
+
+	Bp2buildModuleTypeAlwaysConvertList = []string{
+		"java_import",
+		"java_import_host",
+	}
+
+	Bp2buildModuleDoNotConvertList = []string{
+		// cc bugs
+		"libsepol",                                  // TODO(b/207408632): Unsupported case of .l sources in cc library rules
+		"libactivitymanager_aidl",                   // TODO(b/207426160): Unsupported use of aidl sources (via Dactivity_manager_procstate_aidl) in a cc_library
+		"gen-kotlin-build-file.py",                  // TODO(b/198619163) module has same name as source
+		"libgtest_ndk_c++", "libgtest_main_ndk_c++", // TODO(b/201816222): Requires sdk_version support.
+		"linkerconfig", "mdnsd", // TODO(b/202876379): has arch-variant static_executable
+		"linker",       // TODO(b/228316882): cc_binary uses link_crt
+		"libdebuggerd", // TODO(b/228314770): support product variable-specific header_libs
+		"versioner",    // TODO(b/228313961):  depends on prebuilt shared library libclang-cpp_host as a shared library, which does not supply expected providers for a shared library
+
+		// java bugs
+		"libbase_ndk", // TODO(b/186826477): fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
+
+		// python protos
+		"libprotobuf-python",                           // TODO(b/196084681): contains .proto sources
+		"apex_build_info_proto", "apex_manifest_proto", // TODO(b/196084681): a python lib with proto sources
+		"linker_config_proto", // TODO(b/196084681): contains .proto sources
+
+		// genrule incompatibilities
+		"brotli-fuzzer-corpus",                                       // TODO(b/202015218): outputs are in location incompatible with bazel genrule handling.
+		"platform_tools_properties", "build_tools_source_properties", // TODO(b/203369847): multiple genrules in the same package creating the same file
+
+		// aar support
+		"prebuilt_car-ui-androidx-core-common",         // TODO(b/224773339), genrule dependency creates an .aar, not a .jar
+		"prebuilt_platform-robolectric-4.4-prebuilt",   // aosp/1999250, needs .aar support in Jars
+		"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
+
+		// path property for filegroups
+		"conscrypt",                        // TODO(b/210751803), we don't handle path property for filegroups
+		"conscrypt-for-host",               // TODO(b/210751803), we don't handle path property for filegroups
+		"host-libprotobuf-java-full",       // TODO(b/210751803), we don't handle path property for filegroups
+		"libprotobuf-internal-protos",      // TODO(b/210751803), we don't handle path property for filegroups
+		"libprotobuf-internal-python-srcs", // TODO(b/210751803), we don't handle path property for filegroups
+		"libprotobuf-java-full",            // TODO(b/210751803), we don't handle path property for filegroups
+		"libprotobuf-java-util-full",       // TODO(b/210751803), we don't handle path property for filegroups
+		"auto_value_plugin_resources",      // TODO(b/210751803), we don't handle path property for filegroups
+
+		// go deps:
+		"analyze_bcpf",                                                                               // depends on bpmodify a blueprint_go_binary.
+		"apex-protos",                                                                                // depends on soong_zip, a go binary
+		"generated_android_icu4j_src_files", "generated_android_icu4j_test_files", "icu4c_test_data", // depends on unconverted modules: soong_zip
+		"host_bionic_linker_asm",                                                  // depends on extract_linker, a go binary.
+		"host_bionic_linker_script",                                               // depends on extract_linker, a go binary.
+		"libc_musl_sysroot_bionic_arch_headers",                                   // depends on soong_zip
+		"libc_musl_sysroot_zlib_headers",                                          // depends on soong_zip and zip2zip
+		"libc_musl_sysroot_bionic_headers",                                        // 218405924, depends on soong_zip and generates duplicate srcs
+		"libc_musl_sysroot_libc++_headers", "libc_musl_sysroot_libc++abi_headers", // depends on soong_zip, zip2zip
+		"robolectric-sqlite4java-native", // depends on soong_zip, a go binary
+		"robolectric_tzdata",             // depends on soong_zip, a go binary
+
+		// rust support
+		"libtombstoned_client_rust_bridge_code", "libtombstoned_client_wrapper", // rust conversions are not supported
+
+		// unconverted deps
+		"CarHTMLViewer",                // depends on unconverted modules android.car-stubs, car-ui-lib
+		"abb",                          // depends on unconverted modules: libcmd, libbinder
+		"adb",                          // depends on unconverted modules: AdbWinApi, libandroidfw, libopenscreen-discovery, libopenscreen-platform-impl, libusb, bin2c_fastdeployagent, AdbWinUsbApi
+		"android_icu4j_srcgen",         // depends on unconverted modules: currysrc
+		"android_icu4j_srcgen_binary",  // depends on unconverted modules: android_icu4j_srcgen, currysrc
+		"apex_manifest_proto_java",     // b/210751803, depends on libprotobuf-java-full
+		"art-script",                   // depends on unconverted modules: dalvikvm, dex2oat
+		"bin2c_fastdeployagent",        // depends on unconverted modules: deployagent
+		"chkcon", "sefcontext_compile", // depends on unconverted modules: libsepol
+		"com.android.runtime",                                        // depends on unconverted modules: bionic-linker-config, linkerconfig
+		"conv_linker_config",                                         // depends on unconverted modules: linker_config_proto
+		"currysrc",                                                   // depends on unconverted modules: currysrc_org.eclipse, guavalib, jopt-simple-4.9
+		"dex2oat-script",                                             // depends on unconverted modules: dex2oat
+		"generated_android_icu4j_resources",                          // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
+		"generated_android_icu4j_test_resources",                     // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
+		"host-libprotobuf-java-nano",                                 // b/220869005, depends on libprotobuf-java-nano
+		"libadb_host",                                                // depends on unconverted modules: AdbWinApi, libopenscreen-discovery, libopenscreen-platform-impl, libusb
+		"libart",                                                     // depends on unconverted modules: apex-info-list-tinyxml, libtinyxml2, libnativeloader-headers, heapprofd_client_api, art_operator_srcs, libcpu_features, libodrstatslog, libelffile, art_cmdlineparser_headers, cpp-define-generator-definitions, libdexfile, libnativebridge, libnativeloader, libsigchain, libartbase, libprofile, cpp-define-generator-asm-support
+		"libart-runtime-gtest",                                       // depends on unconverted modules: libgtest_isolated, libart-compiler, libdexfile, libprofile, libartbase, libartbase-art-gtest
+		"libart_headers",                                             // depends on unconverted modules: art_libartbase_headers
+		"libartd",                                                    // depends on unconverted modules: art_operator_srcs, libcpu_features, libodrstatslog, libelffiled, art_cmdlineparser_headers, cpp-define-generator-definitions, libdexfiled, libnativebridge, libnativeloader, libsigchain, libartbased, libprofiled, cpp-define-generator-asm-support, apex-info-list-tinyxml, libtinyxml2, libnativeloader-headers, heapprofd_client_api
+		"libartd-runtime-gtest",                                      // depends on unconverted modules: libgtest_isolated, libartd-compiler, libdexfiled, libprofiled, libartbased, libartbased-art-gtest
+		"libdebuggerd_handler",                                       // depends on unconverted module libdebuggerd_handler_core
+		"libdebuggerd_handler_core", "libdebuggerd_handler_fallback", // depends on unconverted module libdebuggerd
+		"libdexfile",                                              // depends on unconverted modules: dexfile_operator_srcs, libartbase, libartpalette,
+		"libdexfile_static",                                       // depends on unconverted modules: libartbase, libdexfile
+		"libdexfiled",                                             // depends on unconverted modules: dexfile_operator_srcs, libartbased, libartpalette
+		"libfastdeploy_host",                                      // depends on unconverted modules: libandroidfw, libusb, AdbWinApi
+		"libgmock_main_ndk",                                       // depends on unconverted modules: libgtest_ndk_c++
+		"libgmock_ndk",                                            // depends on unconverted modules: libgtest_ndk_c++
+		"libnativehelper_lazy_mts_jni", "libnativehelper_mts_jni", // depends on unconverted modules: libnativetesthelper_jni, libgmock_ndk
+		"libnativetesthelper_jni",   // depends on unconverted modules: libgtest_ndk_c++
+		"libprotobuf-java-nano",     // b/220869005, depends on non-public_current SDK
+		"libstatslog",               // depends on unconverted modules: libstatspull, statsd-aidl-ndk, libbinder_ndk
+		"libstatslog_art",           // depends on unconverted modules: statslog_art.cpp, statslog_art.h
+		"linker_reloc_bench_main",   // depends on unconverted modules: liblinker_reloc_bench_*
+		"pbtombstone", "crash_dump", // depends on libdebuggerd, libunwindstack
+		"robolectric-sqlite4java-0.282",             // depends on unconverted modules: robolectric-sqlite4java-import, robolectric-sqlite4java-native
+		"static_crasher",                            // depends on unconverted modules: libdebuggerd_handler
+		"stats-log-api-gen",                         // depends on unconverted modules: libstats_proto_host
+		"statslog.cpp", "statslog.h", "statslog.rs", // depends on unconverted modules: stats-log-api-gen
+		"statslog_art.cpp", "statslog_art.h", "statslog_header.rs", // depends on unconverted modules: stats-log-api-gen
+		"timezone-host",       // depends on unconverted modules: art.module.api.annotations
+		"truth-host-prebuilt", // depends on unconverted modules: truth-prebuilt
+		"truth-prebuilt",      // depends on unconverted modules: asm-7.0, guava
+
+		// b/215723302; awaiting tz{data,_version} to then rename targets conflicting with srcs
+		"tzdata",
+		"tz_version",
+	}
+
+	Bp2buildCcLibraryStaticOnlyList = []string{}
+
+	MixedBuildsDisabledList = []string{
+		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+
+		"libbrotli",               // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
+		"minijail_constants_json", // http://b/200899432, bazel-built cc_genrule does not work in mixed build when it is a dependency of another soong module.
+
+		"cap_names.h",                                  // TODO(b/204913827) runfiles need to be handled in mixed builds
+		"libcap",                                       // TODO(b/204913827) runfiles need to be handled in mixed builds
+		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
+
+		// Depends on libprotobuf-cpp-*
+		"libadb_pairing_connection",
+		"libadb_pairing_connection_static",
+		"libadb_pairing_server", "libadb_pairing_server_static",
+
+		// TODO(b/204811222) support suffix in cc_binary
+		"acvp_modulewrapper",
+		"android.hardware.media.c2@1.0-service-v4l2",
+		"app_process",
+		"bar_test",
+		"bench_cxa_atexit",
+		"bench_noop",
+		"bench_noop_nostl",
+		"bench_noop_static",
+		"boringssl_self_test",
+		"boringssl_self_test_vendor",
+		"bssl",
+		"cavp",
+		"crash_dump",
+		"crasher",
+		"libcxx_test_template",
+		"linker",
+		"memory_replay",
+		"native_bridge_guest_linker",
+		"native_bridge_stub_library_defaults",
+		"noop",
+		"simpleperf_ndk",
+		"toybox-static",
+		"zlib_bench",
+	}
+)
diff --git a/android/androidmk.go b/android/androidmk.go
index e9c63fb..5c715b4 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -578,7 +578,7 @@
 			}
 		}
 
-		if !base.InRamdisk() && !base.InVendorRamdisk() {
+		if !base.InVendorRamdisk() {
 			a.AddPaths("LOCAL_FULL_INIT_RC", base.initRcPaths)
 		}
 		if len(base.vintfFragmentsPaths) > 0 {
diff --git a/android/apex.go b/android/apex.go
index b127f74..019efdd 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -58,9 +58,6 @@
 	// to true.
 	UsePlatformApis bool
 
-	// The list of SDK modules that the containing apexBundle depends on.
-	RequiredSdks SdkRefs
-
 	// List of Apex variant names that this module is associated with. This initially is the
 	// same as the `ApexVariationName` field.  Then when multiple apex variants are merged in
 	// mergeApexVariations, ApexInfo struct of the merged variant holds the list of apexBundles
@@ -110,9 +107,6 @@
 // thus wouldn't be merged.
 func (i ApexInfo) mergedName(ctx PathContext) string {
 	name := "apex" + strconv.Itoa(i.MinSdkVersion.FinalOrFutureInt())
-	for _, sdk := range i.RequiredSdks {
-		name += "_" + sdk.Name + "_" + sdk.Version
-	}
 	return name
 }
 
@@ -850,52 +844,28 @@
 	}
 	return list
 }(map[string]int{
-	"android.net.ipsec.ike":                                    30,
-	"androidx.annotation_annotation-nodeps":                    29,
-	"androidx.arch.core_core-common-nodeps":                    29,
-	"androidx.collection_collection-nodeps":                    29,
-	"androidx.collection_collection-ktx-nodeps":                30,
-	"androidx.concurrent_concurrent-futures-nodeps":            30,
-	"androidx.lifecycle_lifecycle-common-java8-nodeps":         30,
-	"androidx.lifecycle_lifecycle-common-nodeps":               29,
-	"androidx.room_room-common-nodeps":                         30,
 	"androidx-constraintlayout_constraintlayout-solver-nodeps": 29,
 	"apache-commons-compress":                                  29,
 	"bouncycastle_ike_digests":                                 30,
 	"brotli-java":                                              29,
-	"captiveportal-lib":                                        28,
-	"error_prone_annotations":                                  30,
 	"flatbuffer_headers":                                       30,
-	"framework-permission":                                     30,
 	"gemmlowp_headers":                                         30,
-	"guava-listenablefuture-prebuilt-jar":                      30,
 	"ike-internals":                                            30,
-	"kotlinx-coroutines-android":                               28,
-	"kotlinx-coroutines-android-nodeps":                        30,
-	"kotlinx-coroutines-core":                                  28,
-	"kotlinx-coroutines-core-nodeps":                           30,
 	"libbrotli":                                                30,
 	"libcrypto_static":                                         30,
 	"libeigen":                                                 30,
 	"liblz4":                                                   30,
 	"libmdnssd":                                                30,
-	"libneuralnetworks_common":                                 30,
-	"libneuralnetworks_headers":                                30,
-	"libneuralnetworks":                                        30,
 	"libprocpartition":                                         30,
 	"libprotobuf-java-lite":                                    30,
 	"libprotoutil":                                             30,
 	"libtextclassifier_hash_headers":                           30,
 	"libtextclassifier_hash_static":                            30,
 	"libtflite_kernel_utils":                                   30,
-	"libwatchdog":                                              29,
 	"libzstd":                                                  30,
-	"metrics-constants-protos":                                 28,
 	"net-utils-framework-common":                               29,
-	"permissioncontroller-statsd":                              28,
 	"philox_random_headers":                                    30,
 	"philox_random":                                            30,
-	"service-permission":                                       30,
 	"tensorflow_headers":                                       30,
 	"xz-java":                                                  29,
 })
diff --git a/android/apex_test.go b/android/apex_test.go
index 1e2f3bd..0bf4c9c 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -33,10 +33,10 @@
 		{
 			name: "single",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"foo", "apex10000"},
@@ -45,25 +45,25 @@
 		{
 			name: "merge",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, false, SdkRefs{{"baz", "1"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, false, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_1", FutureApiLevel, false, false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, false}},
+				{"apex10000", FutureApiLevel, false, false, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, false}},
 			wantAliases: [][2]string{
-				{"bar", "apex10000_baz_1"},
-				{"foo", "apex10000_baz_1"},
+				{"bar", "apex10000"},
+				{"foo", "apex10000"},
 			},
 		},
 		{
 			name: "don't merge version",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", uncheckedFinalApiLevel(30), false, false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", uncheckedFinalApiLevel(30), false, false, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex30", uncheckedFinalApiLevel(30), false, false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex30", uncheckedFinalApiLevel(30), false, false, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex30"},
@@ -73,11 +73,11 @@
 		{
 			name: "merge updatable",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, false, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, true, false, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, false, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
@@ -85,32 +85,17 @@
 			},
 		},
 		{
-			name: "don't merge sdks",
-			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
-			},
-			wantMerged: []ApexInfo{
-				{"apex10000_baz_2", FutureApiLevel, false, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000_baz_1", FutureApiLevel, false, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-			},
-			wantAliases: [][2]string{
-				{"bar", "apex10000_baz_2"},
-				{"foo", "apex10000_baz_1"},
-			},
-		},
-		{
 			name: "don't merge when for prebuilt_apex",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, false, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 				// This one should not be merged in with the others because it is for
 				// a prebuilt_apex.
-				{"baz", FutureApiLevel, true, false, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
+				{"baz", FutureApiLevel, true, false, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, true, false, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
-				{"baz", FutureApiLevel, true, false, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, false, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"baz", FutureApiLevel, true, false, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
@@ -120,11 +105,11 @@
 		{
 			name: "merge different UsePlatformApis but don't allow using platform api",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, true, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, true, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, false, false, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, false, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
@@ -134,11 +119,11 @@
 		{
 			name: "merge same UsePlatformApis and allow using platform api",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, true, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, true, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, true, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, true, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, false, true, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, true, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
diff --git a/android/arch.go b/android/arch.go
index 6b81022..f732a7d 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1199,14 +1199,6 @@
 				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
 					mergePropertyStruct(ctx, genProps, bionicProperties)
 				}
-
-				// Special case:  to ease the transition from glibc to musl, apply linux_glibc
-				// properties (which has historically mean host linux) to musl variants.
-				field = "Linux_glibc"
-				prefix = "target.linux_glibc"
-				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
-					mergePropertyStruct(ctx, genProps, bionicProperties)
-				}
 			}
 
 			// Handle target OS properties in the form:
@@ -1426,14 +1418,6 @@
 			if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
 				result = append(result, osArchProperties)
 			}
-
-			// Special case:  to ease the transition from glibc to musl, apply linux_glibc
-			// properties (which has historically mean host linux) to musl variants.
-			field = "Linux_glibc_" + archType.Name
-			userFriendlyField = "target.linux_glibc_" + archType.Name
-			if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
-				result = append(result, osArchProperties)
-			}
 		}
 	}
 
diff --git a/android/arch_test.go b/android/arch_test.go
index 68dc7f5..dd0b115 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -606,12 +606,12 @@
 				{
 					module:   "foo",
 					variant:  "linux_musl_x86_64",
-					property: []string{"root", "host", "linux", "host_linux", "musl", "linux_glibc", "linux_musl", "not_windows", "x86_64", "lib64", "linux_x86_64", "linux_musl_x86_64", "linux_glibc_x86_64"},
+					property: []string{"root", "host", "linux", "host_linux", "musl", "linux_musl", "not_windows", "x86_64", "lib64", "linux_x86_64", "linux_musl_x86_64"},
 				},
 				{
 					module:   "foo",
 					variant:  "linux_musl_x86",
-					property: []string{"root", "host", "linux", "host_linux", "musl", "linux_glibc", "linux_musl", "not_windows", "x86", "lib32", "linux_x86", "linux_musl_x86", "linux_glibc_x86"},
+					property: []string{"root", "host", "linux", "host_linux", "musl", "linux_musl", "not_windows", "x86", "lib32", "linux_x86", "linux_musl_x86"},
 				},
 			},
 		},
diff --git a/android/bazel.go b/android/bazel.go
index edf67d4..67002ec 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -24,6 +24,15 @@
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
+
+	"android/soong/android/allowlists"
+)
+
+const (
+	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
+	// no package path. This is also the module dir for top level Android.bp
+	// modules.
+	Bp2BuildTopLevel = "."
 )
 
 type bazelModuleProperties struct {
@@ -87,7 +96,7 @@
 	HandcraftedLabel() string
 	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
 	ShouldConvertWithBp2build(ctx BazelConversionContext) bool
-	shouldConvertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool
+	shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool
 	GetBazelBuildFileContents(c Config, path, name string) (string, error)
 	ConvertWithBp2build(ctx TopDownMutatorContext)
 
@@ -161,231 +170,13 @@
 	return "" // no label for unconverted module
 }
 
-// Configuration to decide if modules in a directory should default to true/false for bp2build_available
-type Bp2BuildConfig map[string]BazelConversionConfigEntry
-type BazelConversionConfigEntry int
+type bp2BuildConversionAllowlist struct {
+	// Configure modules in these directories to enable bp2build_available: true or false by default.
+	defaultConfig allowlists.Bp2BuildConfig
 
-const (
-	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
-	// no package path. This is also the module dir for top level Android.bp
-	// modules.
-	BP2BUILD_TOPLEVEL = "."
-
-	// iota + 1 ensures that the int value is not 0 when used in the Bp2buildAllowlist map,
-	// which can also mean that the key doesn't exist in a lookup.
-
-	// all modules in this package and subpackages default to bp2build_available: true.
-	// allows modules to opt-out.
-	Bp2BuildDefaultTrueRecursively BazelConversionConfigEntry = iota + 1
-
-	// all modules in this package (not recursively) default to bp2build_available: true.
-	// allows modules to opt-out.
-	Bp2BuildDefaultTrue
-
-	// all modules in this package (not recursively) default to bp2build_available: false.
-	// allows modules to opt-in.
-	Bp2BuildDefaultFalse
-)
-
-var (
 	// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
 	// in the synthetic Bazel workspace.
-	bp2buildKeepExistingBuildFile = map[string]bool{
-		// This is actually build/bazel/build.BAZEL symlinked to ./BUILD
-		".":/*recursive = */ false,
-
-		// build/bazel/examples/apex/... BUILD files should be generated, so
-		// build/bazel is not recursive. Instead list each subdirectory under
-		// build/bazel explicitly.
-		"build/bazel":/* recursive = */ false,
-		"build/bazel/ci/dist":/* recursive = */ false,
-		"build/bazel/examples/android_app":/* recursive = */ true,
-		"build/bazel/examples/java":/* recursive = */ true,
-		"build/bazel/bazel_skylib":/* recursive = */ true,
-		"build/bazel/rules":/* recursive = */ true,
-		"build/bazel/rules_cc":/* recursive = */ true,
-		"build/bazel/scripts":/* recursive = */ true,
-		"build/bazel/tests":/* recursive = */ true,
-		"build/bazel/platforms":/* recursive = */ true,
-		"build/bazel/product_variables":/* recursive = */ true,
-		"build/bazel/vendor/google":/* recursive = */ true,
-		"build/bazel_common_rules":/* recursive = */ true,
-		// build/make/tools/signapk BUILD file is generated, so build/make/tools is not recursive.
-		"build/make/tools":/* recursive = */ false,
-		"build/pesto":/* recursive = */ true,
-
-		// external/bazelbuild-rules_android/... is needed by mixed builds, otherwise mixed builds analysis fails
-		// e.g. ERROR: Analysis of target '@soong_injection//mixed_builds:buildroot' failed
-		"external/bazelbuild-rules_android":/* recursive = */ true,
-		"external/bazel-skylib":/* recursive = */ true,
-		"external/guava":/* recursive = */ true,
-		"external/jsr305":/* recursive = */ true,
-		"frameworks/ex/common":/* recursive = */ true,
-
-		"packages/apps/Music":/* recursive = */ true,
-		"packages/apps/QuickSearchBox":/* recursive = */ true,
-		"packages/apps/WallpaperPicker":/* recursive = */ false,
-
-		"prebuilts/bundletool":/* recursive = */ true,
-		"prebuilts/gcc":/* recursive = */ true,
-		"prebuilts/build-tools":/* recursive = */ false,
-		"prebuilts/jdk/jdk11":/* recursive = */ false,
-		"prebuilts/sdk":/* recursive = */ false,
-		"prebuilts/sdk/current/extras/app-toolkit":/* recursive = */ false,
-		"prebuilts/sdk/current/support":/* recursive = */ false,
-		"prebuilts/sdk/tools":/* recursive = */ false,
-		"prebuilts/r8":/* recursive = */ false,
-	}
-
-	// Configure modules in these directories to enable bp2build_available: true or false by default.
-	bp2buildDefaultConfig = Bp2BuildConfig{
-		"art/libartpalette":                     Bp2BuildDefaultTrueRecursively,
-		"art/libdexfile":                        Bp2BuildDefaultTrueRecursively,
-		"art/runtime":                           Bp2BuildDefaultTrueRecursively,
-		"art/tools":                             Bp2BuildDefaultTrue,
-		"bionic":                                Bp2BuildDefaultTrueRecursively,
-		"bootable/recovery/tools/recovery_l10n": Bp2BuildDefaultTrue,
-		"build/bazel/examples/soong_config_variables":        Bp2BuildDefaultTrueRecursively,
-		"build/bazel/examples/apex/minimal":                  Bp2BuildDefaultTrueRecursively,
-		"build/make/tools/signapk":                           Bp2BuildDefaultTrue,
-		"build/make/target/product/security":                 Bp2BuildDefaultTrue,
-		"build/soong":                                        Bp2BuildDefaultTrue,
-		"build/soong/cc/libbuildversion":                     Bp2BuildDefaultTrue, // Skip tests subdir
-		"build/soong/cc/ndkstubgen":                          Bp2BuildDefaultTrue,
-		"build/soong/cc/symbolfile":                          Bp2BuildDefaultTrue,
-		"build/soong/linkerconfig":                           Bp2BuildDefaultTrueRecursively,
-		"build/soong/scripts":                                Bp2BuildDefaultTrueRecursively,
-		"cts/common/device-side/nativetesthelper/jni":        Bp2BuildDefaultTrueRecursively,
-		"development/apps/DevelopmentSettings":               Bp2BuildDefaultTrue,
-		"development/apps/Fallback":                          Bp2BuildDefaultTrue,
-		"development/apps/WidgetPreview":                     Bp2BuildDefaultTrue,
-		"development/samples/BasicGLSurfaceView":             Bp2BuildDefaultTrue,
-		"development/samples/BluetoothChat":                  Bp2BuildDefaultTrue,
-		"development/samples/BrokenKeyDerivation":            Bp2BuildDefaultTrue,
-		"development/samples/Compass":                        Bp2BuildDefaultTrue,
-		"development/samples/ContactManager":                 Bp2BuildDefaultTrue,
-		"development/samples/FixedGridLayout":                Bp2BuildDefaultTrue,
-		"development/samples/HelloEffects":                   Bp2BuildDefaultTrue,
-		"development/samples/Home":                           Bp2BuildDefaultTrue,
-		"development/samples/HoneycombGallery":               Bp2BuildDefaultTrue,
-		"development/samples/JetBoy":                         Bp2BuildDefaultTrue,
-		"development/samples/KeyChainDemo":                   Bp2BuildDefaultTrue,
-		"development/samples/LceDemo":                        Bp2BuildDefaultTrue,
-		"development/samples/LunarLander":                    Bp2BuildDefaultTrue,
-		"development/samples/MultiResolution":                Bp2BuildDefaultTrue,
-		"development/samples/MultiWindow":                    Bp2BuildDefaultTrue,
-		"development/samples/NotePad":                        Bp2BuildDefaultTrue,
-		"development/samples/Obb":                            Bp2BuildDefaultTrue,
-		"development/samples/RSSReader":                      Bp2BuildDefaultTrue,
-		"development/samples/ReceiveShareDemo":               Bp2BuildDefaultTrue,
-		"development/samples/SearchableDictionary":           Bp2BuildDefaultTrue,
-		"development/samples/SipDemo":                        Bp2BuildDefaultTrue,
-		"development/samples/SkeletonApp":                    Bp2BuildDefaultTrue,
-		"development/samples/Snake":                          Bp2BuildDefaultTrue,
-		"development/samples/SpellChecker/":                  Bp2BuildDefaultTrueRecursively,
-		"development/samples/ThemedNavBarKeyboard":           Bp2BuildDefaultTrue,
-		"development/samples/ToyVpn":                         Bp2BuildDefaultTrue,
-		"development/samples/TtsEngine":                      Bp2BuildDefaultTrue,
-		"development/samples/USB/AdbTest":                    Bp2BuildDefaultTrue,
-		"development/samples/USB/MissileLauncher":            Bp2BuildDefaultTrue,
-		"development/samples/VoiceRecognitionService":        Bp2BuildDefaultTrue,
-		"development/samples/VoicemailProviderDemo":          Bp2BuildDefaultTrue,
-		"development/samples/WiFiDirectDemo":                 Bp2BuildDefaultTrue,
-		"development/sdk":                                    Bp2BuildDefaultTrueRecursively,
-		"external/arm-optimized-routines":                    Bp2BuildDefaultTrueRecursively,
-		"external/auto/android-annotation-stubs":             Bp2BuildDefaultTrueRecursively,
-		"external/auto/common":                               Bp2BuildDefaultTrueRecursively,
-		"external/auto/service":                              Bp2BuildDefaultTrueRecursively,
-		"external/boringssl":                                 Bp2BuildDefaultTrueRecursively,
-		"external/bouncycastle":                              Bp2BuildDefaultTrue,
-		"external/brotli":                                    Bp2BuildDefaultTrue,
-		"external/conscrypt":                                 Bp2BuildDefaultTrue,
-		"external/e2fsprogs":                                 Bp2BuildDefaultTrueRecursively,
-		"external/error_prone":                               Bp2BuildDefaultTrueRecursively,
-		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
-		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
-		"external/googletest":                                Bp2BuildDefaultTrueRecursively,
-		"external/gwp_asan":                                  Bp2BuildDefaultTrueRecursively,
-		"external/icu":                                       Bp2BuildDefaultTrueRecursively,
-		"external/icu/android_icu4j":                         Bp2BuildDefaultFalse, // java rules incomplete
-		"external/icu/icu4j":                                 Bp2BuildDefaultFalse, // java rules incomplete
-		"external/javapoet":                                  Bp2BuildDefaultTrueRecursively,
-		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
-		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
-		"external/libcap":                                    Bp2BuildDefaultTrueRecursively,
-		"external/libcxx":                                    Bp2BuildDefaultTrueRecursively,
-		"external/libcxxabi":                                 Bp2BuildDefaultTrueRecursively,
-		"external/libevent":                                  Bp2BuildDefaultTrueRecursively,
-		"external/libpng":                                    Bp2BuildDefaultTrueRecursively,
-		"external/lz4/lib":                                   Bp2BuildDefaultTrue,
-		"external/lzma/C":                                    Bp2BuildDefaultTrueRecursively,
-		"external/mdnsresponder":                             Bp2BuildDefaultTrueRecursively,
-		"external/minijail":                                  Bp2BuildDefaultTrueRecursively,
-		"external/pcre":                                      Bp2BuildDefaultTrueRecursively,
-		"external/protobuf":                                  Bp2BuildDefaultTrueRecursively,
-		"external/python/six":                                Bp2BuildDefaultTrueRecursively,
-		"external/scudo":                                     Bp2BuildDefaultTrueRecursively,
-		"external/selinux/libselinux":                        Bp2BuildDefaultTrueRecursively,
-		"external/selinux/libsepol":                          Bp2BuildDefaultTrueRecursively,
-		"external/zlib":                                      Bp2BuildDefaultTrueRecursively,
-		"external/zstd":                                      Bp2BuildDefaultTrueRecursively,
-		"frameworks/base/media/tests/MediaDump":              Bp2BuildDefaultTrue,
-		"frameworks/base/startop/apps/test":                  Bp2BuildDefaultTrue,
-		"frameworks/base/tests/appwidgets/AppWidgetHostTest": Bp2BuildDefaultTrueRecursively,
-		"frameworks/native/libs/adbd_auth":                   Bp2BuildDefaultTrueRecursively,
-		"frameworks/native/opengl/tests/gl2_cameraeye":       Bp2BuildDefaultTrue,
-		"frameworks/native/opengl/tests/gl2_java":            Bp2BuildDefaultTrue,
-		"frameworks/native/opengl/tests/testLatency":         Bp2BuildDefaultTrue,
-		"frameworks/native/opengl/tests/testPauseResume":     Bp2BuildDefaultTrue,
-		"frameworks/native/opengl/tests/testViewport":        Bp2BuildDefaultTrue,
-		"frameworks/proto_logging/stats/stats_log_api_gen":   Bp2BuildDefaultTrueRecursively,
-		"libnativehelper":                                    Bp2BuildDefaultTrueRecursively,
-		"packages/apps/DevCamera":                            Bp2BuildDefaultTrue,
-		"packages/apps/HTMLViewer":                           Bp2BuildDefaultTrue,
-		"packages/apps/Protips":                              Bp2BuildDefaultTrue,
-		"packages/modules/StatsD/lib/libstatssocket":         Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb":                               Bp2BuildDefaultTrue,
-		"packages/modules/adb/apex":                          Bp2BuildDefaultTrue,
-		"packages/modules/adb/crypto":                        Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb/libs":                          Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb/pairing_auth":                  Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb/pairing_connection":            Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb/proto":                         Bp2BuildDefaultTrueRecursively,
-		"packages/modules/adb/tls":                           Bp2BuildDefaultTrueRecursively,
-		"packages/providers/MediaProvider/tools/dialogs":     Bp2BuildDefaultTrue,
-		"packages/screensavers/Basic":                        Bp2BuildDefaultTrue,
-		"packages/services/Car/tests/SampleRearViewCamera":   Bp2BuildDefaultTrue,
-		"prebuilts/clang/host/linux-x86":                     Bp2BuildDefaultTrueRecursively,
-		"prebuilts/tools/common/m2":                          Bp2BuildDefaultTrue,
-		"prebuilts/sdk/tools/jetifier/jetifier-standalone":   Bp2BuildDefaultTrue,
-		"system/apex":                                        Bp2BuildDefaultFalse, // TODO(b/207466993): flaky failures
-		"system/apex/proto":                                  Bp2BuildDefaultTrueRecursively,
-		"system/apex/libs":                                   Bp2BuildDefaultTrueRecursively,
-		"system/core/debuggerd":                              Bp2BuildDefaultTrueRecursively,
-		"system/core/diagnose_usb":                           Bp2BuildDefaultTrueRecursively,
-		"system/core/libasyncio":                             Bp2BuildDefaultTrue,
-		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
-		"system/core/libcutils":                              Bp2BuildDefaultTrueRecursively,
-		"system/core/libpackagelistparser":                   Bp2BuildDefaultTrueRecursively,
-		"system/core/libprocessgroup":                        Bp2BuildDefaultTrue,
-		"system/core/libprocessgroup/cgrouprc":               Bp2BuildDefaultTrue,
-		"system/core/libprocessgroup/cgrouprc_format":        Bp2BuildDefaultTrue,
-		"system/core/libsystem":                              Bp2BuildDefaultTrueRecursively,
-		"system/core/libutils":                               Bp2BuildDefaultTrueRecursively,
-		"system/core/libvndksupport":                         Bp2BuildDefaultTrueRecursively,
-		"system/core/property_service/libpropertyinfoparser": Bp2BuildDefaultTrueRecursively,
-		"system/libbase":                                     Bp2BuildDefaultTrueRecursively,
-		"system/libprocinfo":                                 Bp2BuildDefaultTrue,
-		"system/libziparchive":                               Bp2BuildDefaultTrueRecursively,
-		"system/logging/liblog":                              Bp2BuildDefaultTrueRecursively,
-		"system/sepolicy/apex":                               Bp2BuildDefaultTrueRecursively,
-		"system/timezone/apex":                               Bp2BuildDefaultTrueRecursively,
-		"system/timezone/output_data":                        Bp2BuildDefaultTrueRecursively,
-		"system/unwinding/libbacktrace":                      Bp2BuildDefaultTrueRecursively,
-		"system/unwinding/libunwindstack":                    Bp2BuildDefaultTrueRecursively,
-		"tools/apksig":                                       Bp2BuildDefaultTrue,
-		"tools/platform-compat/java/android/compat":          Bp2BuildDefaultTrueRecursively,
-	}
+	keepExistingBuildFile map[string]bool
 
 	// Per-module allowlist to always opt modules in of both bp2build and mixed builds.
 	// These modules are usually in directories with many other modules that are not ready for
@@ -393,235 +184,150 @@
 	//
 	// A module can either be in this list or its directory allowlisted entirely
 	// in bp2buildDefaultConfig, but not both at the same time.
-	bp2buildModuleAlwaysConvertList = []string{
-		//external/avb
-		"avbtool",
-		"libavb",
-		"avb_headers",
-
-		//external/fec
-		"libfec_rs",
-
-		//system/core/libsparse
-		"libsparse",
-
-		//system/extras/ext4_utils
-		"libext4_utils",
-
-		//system/extras/libfec
-		"libfec",
-
-		//system/extras/squashfs_utils
-		"libsquashfs_utils",
-
-		//system/extras/verity/fec
-		"fec",
-
-		//packages/apps/Car/libs/car-ui-lib/car-ui-androidx
-		// genrule dependencies for java_imports
-		"car-ui-androidx-annotation-nodeps",
-		"car-ui-androidx-collection-nodeps",
-		"car-ui-androidx-core-common-nodeps",
-		"car-ui-androidx-lifecycle-common-nodeps",
-		"car-ui-androidx-constraintlayout-solver-nodeps",
-	}
+	moduleAlwaysConvert map[string]bool
 
 	// Per-module-type allowlist to always opt modules in to both bp2build and mixed builds
 	// when they have the same type as one listed.
-	bp2buildModuleTypeAlwaysConvertList = []string{
-		"java_import",
-		"java_import_host",
-	}
+	moduleTypeAlwaysConvert map[string]bool
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
-	bp2buildModuleDoNotConvertList = []string{
-		// cc bugs
-		"libsepol",                                  // TODO(b/207408632): Unsupported case of .l sources in cc library rules
-		"libactivitymanager_aidl",                   // TODO(b/207426160): Unsupported use of aidl sources (via Dactivity_manager_procstate_aidl) in a cc_library
-		"gen-kotlin-build-file.py",                  // TODO(b/198619163) module has same name as source
-		"libgtest_ndk_c++", "libgtest_main_ndk_c++", // TODO(b/201816222): Requires sdk_version support.
-		"linkerconfig", "mdnsd", // TODO(b/202876379): has arch-variant static_executable
-		"linker",       // TODO(b/228316882): cc_binary uses link_crt
-		"libdebuggerd", // TODO(b/228314770): support product variable-specific header_libs
-		"versioner",    // TODO(b/228313961):  depends on prebuilt shared library libclang-cpp_host as a shared library, which does not supply expected providers for a shared library
-
-		// java bugs
-		"libbase_ndk", // TODO(b/186826477): fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
-
-		// python protos
-		"libprotobuf-python",                           // TODO(b/196084681): contains .proto sources
-		"apex_build_info_proto", "apex_manifest_proto", // TODO(b/196084681): a python lib with proto sources
-		"linker_config_proto", // TODO(b/196084681): contains .proto sources
-
-		// genrule incompatibilities
-		"brotli-fuzzer-corpus",                                       // TODO(b/202015218): outputs are in location incompatible with bazel genrule handling.
-		"platform_tools_properties", "build_tools_source_properties", // TODO(b/203369847): multiple genrules in the same package creating the same file
-
-		// aar support
-		"prebuilt_car-ui-androidx-core-common",         // TODO(b/224773339), genrule dependency creates an .aar, not a .jar
-		"prebuilt_platform-robolectric-4.4-prebuilt",   // aosp/1999250, needs .aar support in Jars
-		"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
-
-		// path property for filegroups
-		"conscrypt",                        // TODO(b/210751803), we don't handle path property for filegroups
-		"conscrypt-for-host",               // TODO(b/210751803), we don't handle path property for filegroups
-		"host-libprotobuf-java-full",       // TODO(b/210751803), we don't handle path property for filegroups
-		"libprotobuf-internal-protos",      // TODO(b/210751803), we don't handle path property for filegroups
-		"libprotobuf-internal-python-srcs", // TODO(b/210751803), we don't handle path property for filegroups
-		"libprotobuf-java-full",            // TODO(b/210751803), we don't handle path property for filegroups
-		"libprotobuf-java-util-full",       // TODO(b/210751803), we don't handle path property for filegroups
-
-		// go deps:
-		"analyze_bcpf",                                                                               // depends on bpmodify a blueprint_go_binary.
-		"apex-protos",                                                                                // depends on soong_zip, a go binary
-		"generated_android_icu4j_src_files", "generated_android_icu4j_test_files", "icu4c_test_data", // depends on unconverted modules: soong_zip
-		"host_bionic_linker_asm",                                                  // depends on extract_linker, a go binary.
-		"host_bionic_linker_script",                                               // depends on extract_linker, a go binary.
-		"libc_musl_sysroot_bionic_arch_headers",                                   // depends on soong_zip
-		"libc_musl_sysroot_bionic_headers",                                        // 218405924, depends on soong_zip and generates duplicate srcs
-		"libc_musl_sysroot_libc++_headers", "libc_musl_sysroot_libc++abi_headers", // depends on soong_zip, zip2zip
-		"robolectric-sqlite4java-native", // depends on soong_zip, a go binary
-		"robolectric_tzdata",             // depends on soong_zip, a go binary
-
-		// rust support
-		"libtombstoned_client_rust_bridge_code", "libtombstoned_client_wrapper", // rust conversions are not supported
-
-		// unconverted deps
-		"CarHTMLViewer",                // depends on unconverted modules android.car-stubs, car-ui-lib
-		"abb",                          // depends on unconverted modules: libcmd, libbinder
-		"adb",                          // depends on unconverted modules: AdbWinApi, libandroidfw, libopenscreen-discovery, libopenscreen-platform-impl, libusb, bin2c_fastdeployagent, AdbWinUsbApi
-		"android_icu4j_srcgen",         // depends on unconverted modules: currysrc
-		"android_icu4j_srcgen_binary",  // depends on unconverted modules: android_icu4j_srcgen, currysrc
-		"apex_manifest_proto_java",     // b/210751803, depends on libprotobuf-java-full
-		"art-script",                   // depends on unconverted modules: dalvikvm, dex2oat
-		"bin2c_fastdeployagent",        // depends on unconverted modules: deployagent
-		"chkcon", "sefcontext_compile", // depends on unconverted modules: libsepol
-		"com.android.runtime",                                        // depends on unconverted modules: bionic-linker-config, linkerconfig
-		"conv_linker_config",                                         // depends on unconverted modules: linker_config_proto
-		"currysrc",                                                   // depends on unconverted modules: currysrc_org.eclipse, guavalib, jopt-simple-4.9
-		"dex2oat-script",                                             // depends on unconverted modules: dex2oat
-		"generated_android_icu4j_resources",                          // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
-		"generated_android_icu4j_test_resources",                     // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
-		"host-libprotobuf-java-nano",                                 // b/220869005, depends on libprotobuf-java-nano
-		"libadb_host",                                                // depends on unconverted modules: AdbWinApi, libopenscreen-discovery, libopenscreen-platform-impl, libusb
-		"libart",                                                     // depends on unconverted modules: apex-info-list-tinyxml, libtinyxml2, libnativeloader-headers, heapprofd_client_api, art_operator_srcs, libcpu_features, libodrstatslog, libelffile, art_cmdlineparser_headers, cpp-define-generator-definitions, libdexfile, libnativebridge, libnativeloader, libsigchain, libartbase, libprofile, cpp-define-generator-asm-support
-		"libart-runtime-gtest",                                       // depends on unconverted modules: libgtest_isolated, libart-compiler, libdexfile, libprofile, libartbase, libartbase-art-gtest
-		"libart_headers",                                             // depends on unconverted modules: art_libartbase_headers
-		"libartd",                                                    // depends on unconverted modules: art_operator_srcs, libcpu_features, libodrstatslog, libelffiled, art_cmdlineparser_headers, cpp-define-generator-definitions, libdexfiled, libnativebridge, libnativeloader, libsigchain, libartbased, libprofiled, cpp-define-generator-asm-support, apex-info-list-tinyxml, libtinyxml2, libnativeloader-headers, heapprofd_client_api
-		"libartd-runtime-gtest",                                      // depends on unconverted modules: libgtest_isolated, libartd-compiler, libdexfiled, libprofiled, libartbased, libartbased-art-gtest
-		"libdebuggerd_handler",                                       // depends on unconverted module libdebuggerd_handler_core
-		"libdebuggerd_handler_core", "libdebuggerd_handler_fallback", // depends on unconverted module libdebuggerd
-		"libdexfile",                                              // depends on unconverted modules: dexfile_operator_srcs, libartbase, libartpalette,
-		"libdexfile_static",                                       // depends on unconverted modules: libartbase, libdexfile
-		"libdexfiled",                                             // depends on unconverted modules: dexfile_operator_srcs, libartbased, libartpalette
-		"libfastdeploy_host",                                      // depends on unconverted modules: libandroidfw, libusb, AdbWinApi
-		"libgmock_main_ndk",                                       // depends on unconverted modules: libgtest_ndk_c++
-		"libgmock_ndk",                                            // depends on unconverted modules: libgtest_ndk_c++
-		"libnativehelper_lazy_mts_jni", "libnativehelper_mts_jni", // depends on unconverted modules: libnativetesthelper_jni, libgmock_ndk
-		"libnativetesthelper_jni",   // depends on unconverted modules: libgtest_ndk_c++
-		"libprotobuf-java-nano",     // b/220869005, depends on non-public_current SDK
-		"libstatslog",               // depends on unconverted modules: libstatspull, statsd-aidl-ndk, libbinder_ndk
-		"libstatslog_art",           // depends on unconverted modules: statslog_art.cpp, statslog_art.h
-		"linker_reloc_bench_main",   // depends on unconverted modules: liblinker_reloc_bench_*
-		"pbtombstone", "crash_dump", // depends on libdebuggerd, libunwindstack
-		"robolectric-sqlite4java-0.282",             // depends on unconverted modules: robolectric-sqlite4java-import, robolectric-sqlite4java-native
-		"static_crasher",                            // depends on unconverted modules: libdebuggerd_handler
-		"stats-log-api-gen",                         // depends on unconverted modules: libstats_proto_host
-		"statslog.cpp", "statslog.h", "statslog.rs", // depends on unconverted modules: stats-log-api-gen
-		"statslog_art.cpp", "statslog_art.h", "statslog_header.rs", // depends on unconverted modules: stats-log-api-gen
-		"timezone-host",       // depends on unconverted modules: art.module.api.annotations
-		"truth-host-prebuilt", // depends on unconverted modules: truth-prebuilt
-		"truth-prebuilt",      // depends on unconverted modules: asm-7.0, guava
-	}
+	moduleDoNotConvert map[string]bool
 
 	// Per-module denylist of cc_library modules to only generate the static
 	// variant if their shared variant isn't ready or buildable by Bazel.
-	bp2buildCcLibraryStaticOnlyList = []string{}
+	ccLibraryStaticOnly map[string]bool
 
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
-	mixedBuildsDisabledList = []string{
-		"art_libdexfile_dex_instruction_list_header", // breaks libart_mterp.armng, header not found
+	mixedBuildsDisabled map[string]bool
+}
 
-		"libbrotli",               // http://b/198585397, ld.lld: error: bionic/libc/arch-arm64/generic/bionic/memmove.S:95:(.text+0x10): relocation R_AARCH64_CONDBR19 out of range: -1404176 is not in [-1048576, 1048575]; references __memcpy
-		"minijail_constants_json", // http://b/200899432, bazel-built cc_genrule does not work in mixed build when it is a dependency of another soong module.
-
-		"cap_names.h",                                  // TODO(b/204913827) runfiles need to be handled in mixed builds
-		"libcap",                                       // TODO(b/204913827) runfiles need to be handled in mixed builds
-		"libprotobuf-cpp-full", "libprotobuf-cpp-lite", // Unsupported product&vendor suffix. b/204811222 and b/204810610.
-
-		// Depends on libprotobuf-cpp-*
-		"libadb_pairing_connection",
-		"libadb_pairing_connection_static",
-		"libadb_pairing_server", "libadb_pairing_server_static",
-
-		// TODO(b/204811222) support suffix in cc_binary
-		"acvp_modulewrapper",
-		"android.hardware.media.c2@1.0-service-v4l2",
-		"app_process",
-		"bar_test",
-		"bench_cxa_atexit",
-		"bench_noop",
-		"bench_noop_nostl",
-		"bench_noop_static",
-		"boringssl_self_test",
-		"boringssl_self_test_vendor",
-		"bssl",
-		"cavp",
-		"crash_dump",
-		"crasher",
-		"libcxx_test_template",
-		"linker",
-		"memory_replay",
-		"native_bridge_guest_linker",
-		"native_bridge_stub_library_defaults",
-		"noop",
-		"simpleperf_ndk",
-		"toybox-static",
-		"zlib_bench",
-	}
-
-	// Used for quicker lookups
-	bp2buildModuleDoNotConvert      = map[string]bool{}
-	bp2buildModuleAlwaysConvert     = map[string]bool{}
-	bp2buildModuleTypeAlwaysConvert = map[string]bool{}
-	bp2buildCcLibraryStaticOnly     = map[string]bool{}
-	mixedBuildsDisabled             = map[string]bool{}
-)
-
-func init() {
-	for _, moduleName := range bp2buildModuleAlwaysConvertList {
-		bp2buildModuleAlwaysConvert[moduleName] = true
-	}
-
-	for _, moduleType := range bp2buildModuleTypeAlwaysConvertList {
-		bp2buildModuleTypeAlwaysConvert[moduleType] = true
-	}
-
-	for _, moduleName := range bp2buildModuleDoNotConvertList {
-		bp2buildModuleDoNotConvert[moduleName] = true
-	}
-
-	for _, moduleName := range bp2buildCcLibraryStaticOnlyList {
-		bp2buildCcLibraryStaticOnly[moduleName] = true
-	}
-
-	for _, moduleName := range mixedBuildsDisabledList {
-		mixedBuildsDisabled[moduleName] = true
+// NewBp2BuildAllowlist creates a new, empty bp2BuildConversionAllowlist
+// which can be populated using builder pattern Set* methods
+func NewBp2BuildAllowlist() bp2BuildConversionAllowlist {
+	return bp2BuildConversionAllowlist{
+		allowlists.Bp2BuildConfig{},
+		map[string]bool{},
+		map[string]bool{},
+		map[string]bool{},
+		map[string]bool{},
+		map[string]bool{},
+		map[string]bool{},
 	}
 }
 
+// SetDefaultConfig copies the entries from defaultConfig into the allowlist
+func (a bp2BuildConversionAllowlist) SetDefaultConfig(defaultConfig allowlists.Bp2BuildConfig) bp2BuildConversionAllowlist {
+	if a.defaultConfig == nil {
+		a.defaultConfig = allowlists.Bp2BuildConfig{}
+	}
+	for k, v := range defaultConfig {
+		a.defaultConfig[k] = v
+	}
+
+	return a
+}
+
+// SetKeepExistingBuildFile copies the entries from keepExistingBuildFile into the allowlist
+func (a bp2BuildConversionAllowlist) SetKeepExistingBuildFile(keepExistingBuildFile map[string]bool) bp2BuildConversionAllowlist {
+	if a.keepExistingBuildFile == nil {
+		a.keepExistingBuildFile = map[string]bool{}
+	}
+	for k, v := range keepExistingBuildFile {
+		a.keepExistingBuildFile[k] = v
+	}
+
+	return a
+}
+
+// SetModuleAlwaysConvertList copies the entries from moduleAlwaysConvert into the allowlist
+func (a bp2BuildConversionAllowlist) SetModuleAlwaysConvertList(moduleAlwaysConvert []string) bp2BuildConversionAllowlist {
+	if a.moduleAlwaysConvert == nil {
+		a.moduleAlwaysConvert = map[string]bool{}
+	}
+	for _, m := range moduleAlwaysConvert {
+		a.moduleAlwaysConvert[m] = true
+	}
+
+	return a
+}
+
+// SetModuleTypeAlwaysConvertList copies the entries from moduleTypeAlwaysConvert into the allowlist
+func (a bp2BuildConversionAllowlist) SetModuleTypeAlwaysConvertList(moduleTypeAlwaysConvert []string) bp2BuildConversionAllowlist {
+	if a.moduleTypeAlwaysConvert == nil {
+		a.moduleTypeAlwaysConvert = map[string]bool{}
+	}
+	for _, m := range moduleTypeAlwaysConvert {
+		a.moduleTypeAlwaysConvert[m] = true
+	}
+
+	return a
+}
+
+// SetModuleDoNotConvertList copies the entries from moduleDoNotConvert into the allowlist
+func (a bp2BuildConversionAllowlist) SetModuleDoNotConvertList(moduleDoNotConvert []string) bp2BuildConversionAllowlist {
+	if a.moduleDoNotConvert == nil {
+		a.moduleDoNotConvert = map[string]bool{}
+	}
+	for _, m := range moduleDoNotConvert {
+		a.moduleDoNotConvert[m] = true
+	}
+
+	return a
+}
+
+// SetCcLibraryStaticOnlyList copies the entries from ccLibraryStaticOnly into the allowlist
+func (a bp2BuildConversionAllowlist) SetCcLibraryStaticOnlyList(ccLibraryStaticOnly []string) bp2BuildConversionAllowlist {
+	if a.ccLibraryStaticOnly == nil {
+		a.ccLibraryStaticOnly = map[string]bool{}
+	}
+	for _, m := range ccLibraryStaticOnly {
+		a.ccLibraryStaticOnly[m] = true
+	}
+
+	return a
+}
+
+// SetMixedBuildsDisabledList copies the entries from mixedBuildsDisabled into the allowlist
+func (a bp2BuildConversionAllowlist) SetMixedBuildsDisabledList(mixedBuildsDisabled []string) bp2BuildConversionAllowlist {
+	if a.mixedBuildsDisabled == nil {
+		a.mixedBuildsDisabled = map[string]bool{}
+	}
+	for _, m := range mixedBuildsDisabled {
+		a.mixedBuildsDisabled[m] = true
+	}
+
+	return a
+}
+
+var bp2buildAllowlist = NewBp2BuildAllowlist().
+	SetDefaultConfig(allowlists.Bp2buildDefaultConfig).
+	SetKeepExistingBuildFile(allowlists.Bp2buildKeepExistingBuildFile).
+	SetModuleAlwaysConvertList(allowlists.Bp2buildModuleAlwaysConvertList).
+	SetModuleTypeAlwaysConvertList(allowlists.Bp2buildModuleTypeAlwaysConvertList).
+	SetModuleDoNotConvertList(allowlists.Bp2buildModuleDoNotConvertList).
+	SetCcLibraryStaticOnlyList(allowlists.Bp2buildCcLibraryStaticOnlyList).
+	SetMixedBuildsDisabledList(allowlists.MixedBuildsDisabledList)
+
+// GenerateCcLibraryStaticOnly returns whether a cc_library module should only
+// generate a static version of itself based on the current global configuration.
 func GenerateCcLibraryStaticOnly(moduleName string) bool {
-	return bp2buildCcLibraryStaticOnly[moduleName]
+	return bp2buildAllowlist.ccLibraryStaticOnly[moduleName]
 }
 
+// ShouldKeepExistingBuildFileForDir returns whether an existing BUILD file should be
+// added to the build symlink forest based on the current global configuration.
 func ShouldKeepExistingBuildFileForDir(dir string) bool {
-	if _, ok := bp2buildKeepExistingBuildFile[dir]; ok {
+	return shouldKeepExistingBuildFileForDir(bp2buildAllowlist, dir)
+}
+
+func shouldKeepExistingBuildFileForDir(allowlist bp2BuildConversionAllowlist, dir string) bool {
+	if _, ok := allowlist.keepExistingBuildFile[dir]; ok {
 		// Exact dir match
 		return true
 	}
 	// Check if subtree match
-	for prefix, recursive := range bp2buildKeepExistingBuildFile {
+	for prefix, recursive := range allowlist.keepExistingBuildFile {
 		if recursive {
 			if strings.HasPrefix(dir, prefix+"/") {
 				return true
@@ -632,9 +338,19 @@
 	return false
 }
 
-// MixedBuildsEnabled checks that a module is ready to be replaced by a
+// MixedBuildsEnabled returns true if a module is ready to be replaced by a
+// converted or handcrafted Bazel target. As a side effect, calling this
+// method will also log whether this module is mixed build enabled for
+// metrics reporting.
+func MixedBuildsEnabled(ctx ModuleContext) bool {
+	mixedBuildEnabled := mixedBuildPossible(ctx)
+	ctx.Config().LogMixedBuild(ctx, mixedBuildEnabled)
+	return mixedBuildEnabled
+}
+
+// mixedBuildPossible returns true if a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
-func (b *BazelModuleBase) MixedBuildsEnabled(ctx ModuleContext) bool {
+func mixedBuildPossible(ctx ModuleContext) bool {
 	if ctx.Os() == Windows {
 		// Windows toolchains are not currently supported.
 		return false
@@ -655,7 +371,7 @@
 		// variants of a cc_library.
 		return false
 	}
-	return !mixedBuildsDisabled[ctx.Module().Name()]
+	return !bp2buildAllowlist.mixedBuildsDisabled[ctx.Module().Name()]
 }
 
 // ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
@@ -667,53 +383,66 @@
 	return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
 }
 
-// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
+// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
 func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
 	return b.shouldConvertWithBp2build(ctx, ctx.Module())
 }
 
-func (b *BazelModuleBase) shouldConvertWithBp2build(ctx BazelConversionContext, module blueprint.Module) bool {
-	moduleName := module.Name()
-	moduleNameAllowed := bp2buildModuleAlwaysConvert[moduleName]
-	moduleTypeAllowed := bp2buildModuleTypeAlwaysConvert[ctx.OtherModuleType(module)]
-	allowlistConvert := moduleNameAllowed || moduleTypeAllowed
-	if moduleNameAllowed && moduleTypeAllowed {
-		ctx.(BaseModuleContext).ModuleErrorf("A module cannot be in bp2buildModuleAlwaysConvert and also be" +
-			" in bp2buildModuleTypeAlwaysConvert")
-	}
+type bazelOtherModuleContext interface {
+	ModuleErrorf(format string, args ...interface{})
+	Config() Config
+	OtherModuleType(m blueprint.Module) string
+	OtherModuleName(m blueprint.Module) string
+	OtherModuleDir(m blueprint.Module) string
+}
 
-	if bp2buildModuleDoNotConvert[moduleName] {
-		if moduleNameAllowed {
-			ctx.(BaseModuleContext).ModuleErrorf("a module cannot be in bp2buildModuleDoNotConvert" +
-				" and also be in bp2buildModuleAlwaysConvert")
-		}
-		return false
-	}
-
+func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool {
 	if !b.bazelProps().Bazel_module.CanConvertToBazel {
 		return false
 	}
 
 	propValue := b.bazelProperties.Bazel_module.Bp2build_available
 	packagePath := ctx.OtherModuleDir(module)
+
 	// Modules in unit tests which are enabled in the allowlist by type or name
 	// trigger this conditional because unit tests run under the "." package path
-	isTestModule := packagePath == "." && proptools.BoolDefault(propValue, false)
-	if allowlistConvert && !isTestModule && ShouldKeepExistingBuildFileForDir(packagePath) {
+	isTestModule := packagePath == Bp2BuildTopLevel && proptools.BoolDefault(propValue, false)
+	if isTestModule {
+		return true
+	}
+
+	moduleName := module.Name()
+	allowlist := ctx.Config().bp2buildPackageConfig
+	moduleNameAllowed := allowlist.moduleAlwaysConvert[moduleName]
+	moduleTypeAllowed := allowlist.moduleTypeAlwaysConvert[ctx.OtherModuleType(module)]
+	allowlistConvert := moduleNameAllowed || moduleTypeAllowed
+	if moduleNameAllowed && moduleTypeAllowed {
+		ctx.ModuleErrorf("A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert")
+		return false
+	}
+
+	if allowlist.moduleDoNotConvert[moduleName] {
 		if moduleNameAllowed {
-			ctx.(BaseModuleContext).ModuleErrorf("A module cannot be in a directory listed in bp2buildKeepExistingBuildFile"+
-				" and also be in bp2buildModuleAlwaysConvert. Directory: '%s'", packagePath)
+			ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert")
 		}
 		return false
 	}
 
-	config := ctx.Config().bp2buildPackageConfig
-	// This is a tristate value: true, false, or unset.
-	if bp2buildDefaultTrueRecursively(packagePath, config) {
+	if allowlistConvert && shouldKeepExistingBuildFileForDir(allowlist, packagePath) {
 		if moduleNameAllowed {
-			ctx.(BaseModuleContext).ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
-				" or Bp2BuildDefaultTrueRecursively and also be in bp2buildModuleAlwaysConvert. Directory: '%s'",
-				packagePath)
+			ctx.ModuleErrorf("A module cannot be in a directory listed in keepExistingBuildFile"+
+				" and also be in moduleAlwaysConvert. Directory: '%s'", packagePath)
+			return false
+		}
+	}
+
+	// This is a tristate value: true, false, or unset.
+	if ok, directoryPath := bp2buildDefaultTrueRecursively(packagePath, allowlist.defaultConfig); ok {
+		if moduleNameAllowed {
+			ctx.ModuleErrorf("A module cannot be in a directory marked Bp2BuildDefaultTrue"+
+				" or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: '%s'",
+				directoryPath)
+			return false
 		}
 
 		// Allow modules to explicitly opt-out.
@@ -734,14 +463,16 @@
 //
 // This function will also return false if the package doesn't match anything in
 // the config.
-func bp2buildDefaultTrueRecursively(packagePath string, config Bp2BuildConfig) bool {
-	ret := false
-
+//
+// This function will also return the allowlist entry which caused a particular
+// package to be enabled. Since packages can be enabled via a recursive declaration,
+// the path returned will not always be the same as the one provided.
+func bp2buildDefaultTrueRecursively(packagePath string, config allowlists.Bp2BuildConfig) (bool, string) {
 	// Check if the package path has an exact match in the config.
-	if config[packagePath] == Bp2BuildDefaultTrue || config[packagePath] == Bp2BuildDefaultTrueRecursively {
-		return true
-	} else if config[packagePath] == Bp2BuildDefaultFalse {
-		return false
+	if config[packagePath] == allowlists.Bp2BuildDefaultTrue || config[packagePath] == allowlists.Bp2BuildDefaultTrueRecursively {
+		return true, packagePath
+	} else if config[packagePath] == allowlists.Bp2BuildDefaultFalse {
+		return false, packagePath
 	}
 
 	// If not, check for the config recursively.
@@ -749,15 +480,15 @@
 	// e.g. for x/y/z, iterate over x, x/y, then x/y/z, taking the final value from the allowlist.
 	for _, part := range strings.Split(packagePath, "/") {
 		packagePrefix += part
-		if config[packagePrefix] == Bp2BuildDefaultTrueRecursively {
+		if config[packagePrefix] == allowlists.Bp2BuildDefaultTrueRecursively {
 			// package contains this prefix and this prefix should convert all modules
-			return true
+			return true, packagePrefix
 		}
 		// Continue to the next part of the package dir.
 		packagePrefix += "/"
 	}
 
-	return ret
+	return false, packagePath
 }
 
 // GetBazelBuildFileContents returns the file contents of a hand-crafted BUILD file if available or
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index d851a98..fa26fc8 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -28,6 +28,7 @@
 
 	"android/soong/bazel/cquery"
 	"android/soong/shared"
+	"github.com/google/blueprint"
 
 	"android/soong/bazel"
 )
@@ -101,6 +102,9 @@
 
 	// Returns build statements which should get registered to reflect Bazel's outputs.
 	BuildStatementsToRegister() []bazel.BuildStatement
+
+	// Returns the depsets defined in Bazel's aquery response.
+	AqueryDepsets() []bazel.AqueryDepset
 }
 
 type bazelRunner interface {
@@ -128,6 +132,9 @@
 
 	// Build statements which should get registered to reflect Bazel's outputs.
 	buildStatements []bazel.BuildStatement
+
+	// Depsets which should be used for Bazel's build statements.
+	depsets []bazel.AqueryDepset
 }
 
 var _ BazelContext = &bazelContext{}
@@ -175,6 +182,10 @@
 	return []bazel.BuildStatement{}
 }
 
+func (m MockBazelContext) AqueryDepsets() []bazel.AqueryDepset {
+	return []bazel.AqueryDepset{}
+}
+
 var _ BazelContext = MockBazelContext{}
 
 func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
@@ -236,6 +247,10 @@
 	return []bazel.BuildStatement{}
 }
 
+func (m noopBazelContext) AqueryDepsets() []bazel.AqueryDepset {
+	return []bazel.AqueryDepset{}
+}
+
 func NewBazelContext(c *config) (BazelContext, error) {
 	// TODO(cparsons): Assess USE_BAZEL=1 instead once "mixed Soong/Bazel builds"
 	// are production ready.
@@ -746,7 +761,7 @@
 		return err
 	}
 
-	context.buildStatements, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
+	context.buildStatements, context.depsets, err = bazel.AqueryBuildStatements([]byte(aqueryOutput))
 	if err != nil {
 		return err
 	}
@@ -772,6 +787,10 @@
 	return context.buildStatements
 }
 
+func (context *bazelContext) AqueryDepsets() []bazel.AqueryDepset {
+	return context.depsets
+}
+
 func (context *bazelContext) OutputBase() string {
 	return context.paths.outputBase
 }
@@ -804,6 +823,23 @@
 		ctx.AddNinjaFileDeps(file)
 	}
 
+	for _, depset := range ctx.Config().BazelContext.AqueryDepsets() {
+		var outputs []Path
+		for _, depsetDepId := range depset.TransitiveDepSetIds {
+			otherDepsetName := bazelDepsetName(depsetDepId)
+			outputs = append(outputs, PathForPhony(ctx, otherDepsetName))
+		}
+		for _, artifactPath := range depset.DirectArtifacts {
+			outputs = append(outputs, PathForBazelOut(ctx, artifactPath))
+		}
+		thisDepsetName := bazelDepsetName(depset.Id)
+		ctx.Build(pctx, BuildParams{
+			Rule:      blueprint.Phony,
+			Outputs:   []WritablePath{PathForPhony(ctx, thisDepsetName)},
+			Implicits: outputs,
+		})
+	}
+
 	// Register bazel-owned build statements (obtained from the aquery invocation).
 	for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
 		if len(buildStatement.Command) < 1 {
@@ -838,6 +874,10 @@
 		for _, inputPath := range buildStatement.InputPaths {
 			cmd.Implicit(PathForBazelOut(ctx, inputPath))
 		}
+		for _, inputDepsetId := range buildStatement.InputDepsetIds {
+			otherDepsetName := bazelDepsetName(inputDepsetId)
+			cmd.Implicit(PathForPhony(ctx, otherDepsetName))
+		}
 
 		if depfile := buildStatement.Depfile; depfile != nil {
 			cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
@@ -853,7 +893,8 @@
 		// build statement have later timestamps than the outputs.
 		rule.Restat()
 
-		rule.Build(fmt.Sprintf("bazel %d", index), buildStatement.Mnemonic)
+		desc := fmt.Sprintf("%s: %s", buildStatement.Mnemonic, buildStatement.OutputPaths)
+		rule.Build(fmt.Sprintf("bazel %d", index), desc)
 	}
 }
 
@@ -882,3 +923,7 @@
 		osType: ctx.Os(),
 	}
 }
+
+func bazelDepsetName(depsetId int) string {
+	return fmt.Sprintf("bazel_depset_%d", depsetId)
+}
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index f353a9d..fa10f62 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -79,6 +79,7 @@
 	OtherModuleType(m blueprint.Module) string
 	OtherModuleName(m blueprint.Module) string
 	OtherModuleDir(m blueprint.Module) string
+	ModuleErrorf(format string, args ...interface{})
 }
 
 // A subset of the ModuleContext methods which are sufficient to resolve references to paths/deps in
diff --git a/android/bazel_test.go b/android/bazel_test.go
index e5d8fbb..482df2a 100644
--- a/android/bazel_test.go
+++ b/android/bazel_test.go
@@ -13,59 +13,67 @@
 // limitations under the License.
 package android
 
-import "testing"
+import (
+	"android/soong/android/allowlists"
+	"android/soong/bazel"
+	"fmt"
+	"testing"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
 
 func TestConvertAllModulesInPackage(t *testing.T) {
 	testCases := []struct {
-		prefixes   Bp2BuildConfig
+		prefixes   allowlists.Bp2BuildConfig
 		packageDir string
 	}{
 		{
-			prefixes: Bp2BuildConfig{
-				"a": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a/b": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a/b":   Bp2BuildDefaultTrueRecursively,
-				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a/b":   allowlists.Bp2BuildDefaultTrueRecursively,
+				"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultTrueRecursively,
-				"d/e/f": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultTrueRecursively,
+				"d/e/f": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultFalse,
-				"a/b":   Bp2BuildDefaultTrueRecursively,
-				"a/b/c": Bp2BuildDefaultFalse,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultFalse,
+				"a/b":   allowlists.Bp2BuildDefaultTrueRecursively,
+				"a/b/c": allowlists.Bp2BuildDefaultFalse,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultTrueRecursively,
-				"a/b":   Bp2BuildDefaultFalse,
-				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultTrueRecursively,
+				"a/b":   allowlists.Bp2BuildDefaultFalse,
+				"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a",
 		},
 	}
 
 	for _, test := range testCases {
-		if !bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes) {
+		if ok, _ := bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes); !ok {
 			t.Errorf("Expected to convert all modules in %s based on %v, but failed.", test.packageDir, test.prefixes)
 		}
 	}
@@ -73,62 +81,308 @@
 
 func TestModuleOptIn(t *testing.T) {
 	testCases := []struct {
-		prefixes   Bp2BuildConfig
+		prefixes   allowlists.Bp2BuildConfig
 		packageDir string
 	}{
 		{
-			prefixes: Bp2BuildConfig{
-				"a/b": Bp2BuildDefaultFalse,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a/b": allowlists.Bp2BuildDefaultFalse,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":   Bp2BuildDefaultFalse,
-				"a/b": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":   allowlists.Bp2BuildDefaultFalse,
+				"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a/b": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a/b": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a", // opt-in by default
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultTrueRecursively,
-				"d/e/f": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultTrueRecursively,
+				"d/e/f": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "foo/bar",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultTrueRecursively,
-				"a/b":   Bp2BuildDefaultFalse,
-				"a/b/c": Bp2BuildDefaultTrueRecursively,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultTrueRecursively,
+				"a/b":   allowlists.Bp2BuildDefaultFalse,
+				"a/b/c": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			packageDir: "a/b",
 		},
 		{
-			prefixes: Bp2BuildConfig{
-				"a":     Bp2BuildDefaultFalse,
-				"a/b":   Bp2BuildDefaultTrueRecursively,
-				"a/b/c": Bp2BuildDefaultFalse,
+			prefixes: allowlists.Bp2BuildConfig{
+				"a":     allowlists.Bp2BuildDefaultFalse,
+				"a/b":   allowlists.Bp2BuildDefaultTrueRecursively,
+				"a/b/c": allowlists.Bp2BuildDefaultFalse,
 			},
 			packageDir: "a",
 		},
 	}
 
 	for _, test := range testCases {
-		if bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes) {
+		if ok, _ := bp2buildDefaultTrueRecursively(test.packageDir, test.prefixes); ok {
 			t.Errorf("Expected to allow module opt-in in %s based on %v, but failed.", test.packageDir, test.prefixes)
 		}
 	}
 }
+
+type TestBazelModule struct {
+	bazel.TestModuleInfo
+	BazelModuleBase
+}
+
+var _ blueprint.Module = TestBazelModule{}
+
+func (m TestBazelModule) Name() string {
+	return m.TestModuleInfo.ModuleName
+}
+
+func (m TestBazelModule) GenerateBuildActions(blueprint.ModuleContext) {
+}
+
+type TestBazelConversionContext struct {
+	omc       bazel.OtherModuleTestContext
+	allowlist bp2BuildConversionAllowlist
+	errors    []string
+}
+
+var _ bazelOtherModuleContext = &TestBazelConversionContext{}
+
+func (bcc *TestBazelConversionContext) OtherModuleType(m blueprint.Module) string {
+	return bcc.omc.OtherModuleType(m)
+}
+
+func (bcc *TestBazelConversionContext) OtherModuleName(m blueprint.Module) string {
+	return bcc.omc.OtherModuleName(m)
+}
+
+func (bcc *TestBazelConversionContext) OtherModuleDir(m blueprint.Module) string {
+	return bcc.omc.OtherModuleDir(m)
+}
+
+func (bcc *TestBazelConversionContext) ModuleErrorf(format string, args ...interface{}) {
+	bcc.errors = append(bcc.errors, fmt.Sprintf(format, args...))
+}
+
+func (bcc *TestBazelConversionContext) Config() Config {
+	return Config{
+		&config{
+			bp2buildPackageConfig: bcc.allowlist,
+		},
+	}
+}
+
+var bazelableBazelModuleBase = BazelModuleBase{
+	bazelProperties: properties{
+		Bazel_module: bazelModuleProperties{
+			CanConvertToBazel: true,
+		},
+	},
+}
+
+func TestBp2BuildAllowlist(t *testing.T) {
+	testCases := []struct {
+		description    string
+		shouldConvert  bool
+		expectedErrors []string
+		module         TestBazelModule
+		allowlist      bp2BuildConversionAllowlist
+	}{
+		{
+			description:   "allowlist enables module",
+			shouldConvert: true,
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "dir1",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+			},
+		},
+		{
+			description:    "module in name allowlist and type allowlist fails",
+			shouldConvert:  false,
+			expectedErrors: []string{"A module cannot be in moduleAlwaysConvert and also be in moduleTypeAlwaysConvert"},
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "dir1",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				moduleTypeAlwaysConvert: map[string]bool{
+					"rule1": true,
+				},
+			},
+		},
+		{
+			description:    "module in allowlist and denylist fails",
+			shouldConvert:  false,
+			expectedErrors: []string{"a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert"},
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "dir1",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				moduleDoNotConvert: map[string]bool{
+					"foo": true,
+				},
+			},
+		},
+		{
+			description:    "module in allowlist and existing BUILD file",
+			shouldConvert:  false,
+			expectedErrors: []string{"A module cannot be in a directory listed in keepExistingBuildFile and also be in moduleAlwaysConvert. Directory: 'existing/build/dir'"},
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "existing/build/dir",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				keepExistingBuildFile: map[string]bool{
+					"existing/build/dir": true,
+				},
+			},
+		},
+		{
+			description:    "module allowlist and enabled directory",
+			shouldConvert:  false,
+			expectedErrors: []string{"A module cannot be in a directory marked Bp2BuildDefaultTrue or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: 'existing/build/dir'"},
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "existing/build/dir",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				defaultConfig: allowlists.Bp2BuildConfig{
+					"existing/build/dir": allowlists.Bp2BuildDefaultTrue,
+				},
+			},
+		},
+		{
+			description:    "module allowlist and enabled subdirectory",
+			shouldConvert:  false,
+			expectedErrors: []string{"A module cannot be in a directory marked Bp2BuildDefaultTrue or Bp2BuildDefaultTrueRecursively and also be in moduleAlwaysConvert. Directory: 'existing/build/dir'"},
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        "existing/build/dir/subdir",
+				},
+				BazelModuleBase: bazelableBazelModuleBase,
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				defaultConfig: allowlists.Bp2BuildConfig{
+					"existing/build/dir": allowlists.Bp2BuildDefaultTrueRecursively,
+				},
+			},
+		},
+		{
+			description:   "module enabled in unit test short-circuits other allowlists",
+			shouldConvert: true,
+			module: TestBazelModule{
+				TestModuleInfo: bazel.TestModuleInfo{
+					ModuleName: "foo",
+					Typ:        "rule1",
+					Dir:        ".",
+				},
+				BazelModuleBase: BazelModuleBase{
+					bazelProperties: properties{
+						Bazel_module: bazelModuleProperties{
+							CanConvertToBazel:  true,
+							Bp2build_available: proptools.BoolPtr(true),
+						},
+					},
+				},
+			},
+			allowlist: bp2BuildConversionAllowlist{
+				moduleAlwaysConvert: map[string]bool{
+					"foo": true,
+				},
+				moduleDoNotConvert: map[string]bool{
+					"foo": true,
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.description, func(t *testing.T) {
+			bcc := &TestBazelConversionContext{
+				omc: bazel.OtherModuleTestContext{
+					Modules: []bazel.TestModuleInfo{
+						test.module.TestModuleInfo,
+					},
+				},
+				allowlist: test.allowlist,
+			}
+
+			shouldConvert := test.module.shouldConvertWithBp2build(bcc, test.module.TestModuleInfo)
+			if test.shouldConvert != shouldConvert {
+				t.Errorf("Module shouldConvert expected to be: %v, but was: %v", test.shouldConvert, shouldConvert)
+			}
+
+			errorsMatch := true
+			if len(test.expectedErrors) != len(bcc.errors) {
+				errorsMatch = false
+			} else {
+				for i, err := range test.expectedErrors {
+					if err != bcc.errors[i] {
+						errorsMatch = false
+					}
+				}
+			}
+			if !errorsMatch {
+				t.Errorf("Expected errors to be: %v, but were: %v", test.expectedErrors, bcc.errors)
+			}
+		})
+	}
+}
diff --git a/android/buildinfo_prop.go b/android/buildinfo_prop.go
new file mode 100644
index 0000000..6339a71
--- /dev/null
+++ b/android/buildinfo_prop.go
@@ -0,0 +1,182 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	ctx := InitRegistrationContext
+	ctx.RegisterSingletonModuleType("buildinfo_prop", buildinfoPropFactory)
+}
+
+type buildinfoPropProperties struct {
+	// Whether this module is directly installable to one of the partitions. Default: true.
+	Installable *bool
+}
+
+type buildinfoPropModule struct {
+	SingletonModuleBase
+
+	properties buildinfoPropProperties
+
+	outputFilePath OutputPath
+	installPath    InstallPath
+}
+
+var _ OutputFileProducer = (*buildinfoPropModule)(nil)
+
+func (p *buildinfoPropModule) installable() bool {
+	return proptools.BoolDefault(p.properties.Installable, true)
+}
+
+// OutputFileProducer
+func (p *buildinfoPropModule) OutputFiles(tag string) (Paths, error) {
+	if tag != "" {
+		return nil, fmt.Errorf("unsupported tag %q", tag)
+	}
+	return Paths{p.outputFilePath}, nil
+}
+
+func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	p.outputFilePath = PathForModuleOut(ctx, p.Name()).OutputPath
+	if !ctx.Config().KatiEnabled() {
+		WriteFileRule(ctx, p.outputFilePath, "# no buildinfo.prop if kati is disabled")
+		return
+	}
+
+	rule := NewRuleBuilder(pctx, ctx)
+	cmd := rule.Command().Text("(")
+
+	writeString := func(str string) {
+		cmd.Text(`echo "` + str + `" && `)
+	}
+
+	writeString("# begin build properties")
+	writeString("# autogenerated by build/soong/android/buildinfo_prop.go")
+
+	writeProp := func(key, value string) {
+		if strings.Contains(key, "=") {
+			panic(fmt.Errorf("wrong property key %q: key must not contain '='", key))
+		}
+		writeString(key + "=" + value)
+	}
+
+	config := ctx.Config()
+
+	writeProp("ro.build.version.sdk", config.PlatformSdkVersion().String())
+	writeProp("ro.build.version.preview_sdk", config.PlatformPreviewSdkVersion())
+	writeProp("ro.build.version.codename", config.PlatformSdkCodename())
+	writeProp("ro.build.version.all_codenames", strings.Join(config.PlatformVersionActiveCodenames(), ","))
+	writeProp("ro.build.version.release", config.PlatformVersionLastStable())
+	writeProp("ro.build.version.release_or_codename", config.PlatformVersionName())
+	writeProp("ro.build.version.security_patch", config.PlatformSecurityPatch())
+	writeProp("ro.build.version.base_os", config.PlatformBaseOS())
+	writeProp("ro.build.version.min_supported_target_sdk", config.PlatformMinSupportedTargetSdkVersion())
+
+	if config.Eng() {
+		writeProp("ro.build.type", "eng")
+	} else if config.Debuggable() {
+		writeProp("ro.build.type", "userdebug")
+	} else {
+		writeProp("ro.build.type", "user")
+	}
+
+	// Currently, only a few properties are implemented to unblock microdroid use case.
+	// TODO(b/189164487): support below properties as well and replace build/make/tools/buildinfo.sh
+	/*
+		if $BOARD_USE_VBMETA_DIGTEST_IN_FINGERPRINT {
+			writeProp("ro.build.legacy.id", config.BuildID())
+		} else {
+			writeProp("ro.build.id", config.BuildId())
+		}
+		writeProp("ro.build.display.id", $BUILD_DISPLAY_ID)
+		writeProp("ro.build.version.incremental", $BUILD_NUMBER)
+		writeProp("ro.build.version.preview_sdk_fingerprint", $PLATFORM_PREVIEW_SDK_FINGERPRINT)
+		writeProp("ro.build.version.known_codenames", $PLATFORM_VERSION_KNOWN_CODENAMES)
+		writeProp("ro.build.version.release_or_preview_display", $PLATFORM_DISPLAY_VERSION)
+		writeProp("ro.build.date", `$DATE`)
+		writeProp("ro.build.date.utc", `$DATE +%s`)
+		writeProp("ro.build.user", $BUILD_USERNAME)
+		writeProp("ro.build.host", $BUILD_HOSTNAME)
+		writeProp("ro.build.tags", $BUILD_VERSION_TAGS)
+		writeProp("ro.build.flavor", $TARGET_BUILD_FLAVOR)
+		// These values are deprecated, use "ro.product.cpu.abilist"
+		// instead (see below).
+		writeString("# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,")
+		writeString("# use ro.product.cpu.abilist instead.")
+		writeProp("ro.product.cpu.abi", $TARGET_CPU_ABI)
+		if [ -n "$TARGET_CPU_ABI2" ] {
+			writeProp("ro.product.cpu.abi2", $TARGET_CPU_ABI2)
+		}
+
+		if [ -n "$PRODUCT_DEFAULT_LOCALE" ] {
+			writeProp("ro.product.locale", $PRODUCT_DEFAULT_LOCALE)
+		}
+		writeProp("ro.wifi.channels", $PRODUCT_DEFAULT_WIFI_CHANNELS)
+		writeString("# ro.build.product is obsolete; use ro.product.device")
+		writeProp("ro.build.product", $TARGET_DEVICE)
+
+		writeString("# Do not try to parse description or thumbprint")
+		writeProp("ro.build.description", $PRIVATE_BUILD_DESC)
+		if [ -n "$BUILD_THUMBPRINT" ] {
+			writeProp("ro.build.thumbprint", $BUILD_THUMBPRINT)
+		}
+	*/
+
+	writeString("# end build properties")
+
+	cmd.Text("true) > ").Output(p.outputFilePath)
+	rule.Build("build.prop", "generating build.prop")
+
+	if !p.installable() {
+		p.SkipInstall()
+	}
+
+	p.installPath = PathForModuleInstall(ctx)
+	ctx.InstallFile(p.installPath, p.Name(), p.outputFilePath)
+}
+
+func (f *buildinfoPropModule) GenerateSingletonBuildActions(ctx SingletonContext) {
+	// does nothing; buildinfo_prop is a singeton because two buildinfo modules don't make sense.
+}
+
+func (p *buildinfoPropModule) AndroidMkEntries() []AndroidMkEntries {
+	return []AndroidMkEntries{AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: OptionalPathForPath(p.outputFilePath),
+		ExtraEntries: []AndroidMkExtraEntriesFunc{
+			func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", p.installPath.String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
+				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
+			},
+		},
+	}}
+}
+
+// buildinfo_prop module generates a build.prop file, which contains a set of common
+// system/build.prop properties, such as ro.build.version.*.  Not all properties are implemented;
+// currently this module is only for microdroid.
+func buildinfoPropFactory() SingletonModule {
+	module := &buildinfoPropModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidModule(module)
+	return module
+}
diff --git a/android/config.go b/android/config.go
index 5dcb959..d695217 100644
--- a/android/config.go
+++ b/android/config.go
@@ -158,7 +158,7 @@
 	mockBpList string
 
 	runningAsBp2Build              bool
-	bp2buildPackageConfig          Bp2BuildConfig
+	bp2buildPackageConfig          bp2BuildConversionAllowlist
 	Bp2buildSoongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions
 
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
@@ -170,6 +170,10 @@
 	ninjaFileDepsSet sync.Map
 
 	OncePer
+
+	mixedBuildsLock           sync.Mutex
+	mixedBuildEnabledModules  map[string]struct{}
+	mixedBuildDisabledModules map[string]struct{}
 }
 
 type deviceConfig struct {
@@ -375,7 +379,9 @@
 		// passed to PathForSource or PathForModuleSrc.
 		TestAllowNonExistentPaths: true,
 
-		BazelContext: noopBazelContext{},
+		BazelContext:              noopBazelContext{},
+		mixedBuildDisabledModules: make(map[string]struct{}),
+		mixedBuildEnabledModules:  make(map[string]struct{}),
 	}
 	config.deviceConfig = &deviceConfig{
 		config: config,
@@ -466,8 +472,10 @@
 		runGoTests:        runGoTests,
 		multilibConflicts: make(map[ArchType]bool),
 
-		moduleListFile: moduleListFile,
-		fs:             pathtools.NewOsFs(absSrcDir),
+		moduleListFile:            moduleListFile,
+		fs:                        pathtools.NewOsFs(absSrcDir),
+		mixedBuildDisabledModules: make(map[string]struct{}),
+		mixedBuildEnabledModules:  make(map[string]struct{}),
 	}
 
 	config.deviceConfig = &deviceConfig{
@@ -550,7 +558,7 @@
 	}
 
 	config.BazelContext, err = NewBazelContext(config)
-	config.bp2buildPackageConfig = bp2buildDefaultConfig
+	config.bp2buildPackageConfig = bp2buildAllowlist
 
 	return Config{config}, err
 }
@@ -777,8 +785,12 @@
 	return String(c.productVariables.Platform_base_os)
 }
 
+func (c *config) PlatformVersionLastStable() string {
+	return String(c.productVariables.Platform_version_last_stable)
+}
+
 func (c *config) MinSupportedSdkVersion() ApiLevel {
-	return uncheckedFinalApiLevel(16)
+	return uncheckedFinalApiLevel(19)
 }
 
 func (c *config) FinalApiLevels() []ApiLevel {
@@ -1482,6 +1494,10 @@
 	return c.productVariables.MissingUsesLibraries
 }
 
+func (c *config) TargetMultitreeUpdateMeta() bool {
+	return c.productVariables.MultitreeUpdateMeta
+}
+
 func (c *deviceConfig) DeviceArch() string {
 	return String(c.config.productVariables.DeviceArch)
 }
@@ -2030,3 +2046,14 @@
 func (c *config) UseHostMusl() bool {
 	return Bool(c.productVariables.HostMusl)
 }
+
+func (c *config) LogMixedBuild(ctx ModuleContext, useBazel bool) {
+	moduleName := ctx.Module().Name()
+	c.mixedBuildsLock.Lock()
+	defer c.mixedBuildsLock.Unlock()
+	if useBazel {
+		c.mixedBuildEnabledModules[moduleName] = struct{}{}
+	} else {
+		c.mixedBuildDisabledModules[moduleName] = struct{}{}
+	}
+}
diff --git a/android/filegroup.go b/android/filegroup.go
index 50356d1..1bf5e07 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -115,7 +115,7 @@
 }
 
 func (fg *fileGroup) maybeGenerateBazelBuildActions(ctx ModuleContext) {
-	if !fg.MixedBuildsEnabled(ctx) {
+	if !MixedBuildsEnabled(ctx) {
 		return
 	}
 
diff --git a/android/fixture.go b/android/fixture.go
index 728f031..0690a5a 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -586,6 +586,18 @@
 	})
 }
 
+// FixtureExpectsOneErrorPattern returns an error handler that will cause the test to fail
+// if there is more than one error or the error does not match the pattern.
+//
+// If the test fails this handler will call `result.FailNow()` which will exit the goroutine within
+// which the test is being run which means that the RunTest() method will not return.
+func FixtureExpectsOneErrorPattern(pattern string) FixtureErrorHandler {
+	return FixtureCustomErrorHandler(func(t *testing.T, result *TestResult) {
+		t.Helper()
+		CheckErrorsAgainstExpectations(t, result.Errs, []string{pattern})
+	})
+}
+
 // FixtureCustomErrorHandler creates a custom error handler
 func FixtureCustomErrorHandler(function func(t *testing.T, result *TestResult)) FixtureErrorHandler {
 	return simpleErrorHandler{
diff --git a/android/hooks.go b/android/hooks.go
index 5e3a4a7..2ad3b5f 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -89,8 +89,17 @@
 	l.appendPrependHelper(props, proptools.PrependMatchingProperties)
 }
 
-func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
-	inherited := []interface{}{&l.Module().base().commonProperties}
+func (l *loadHookContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) blueprint.Module {
+	return l.bp.CreateModule(factory, name, props...)
+}
+
+type createModuleContext interface {
+	Module() Module
+	createModule(blueprint.ModuleFactory, string, ...interface{}) blueprint.Module
+}
+
+func createModule(ctx createModuleContext, factory ModuleFactory, ext string, props ...interface{}) Module {
+	inherited := []interface{}{&ctx.Module().base().commonProperties}
 
 	var typeName string
 	if typeNameLookup, ok := ModuleTypeByFactory()[reflect.ValueOf(factory)]; ok {
@@ -101,12 +110,12 @@
 		filePath, _ := factoryFunc.FileLine(factoryPtr)
 		typeName = fmt.Sprintf("%s_%s", path.Base(filePath), factoryFunc.Name())
 	}
-	typeName = typeName + "_loadHookModule"
+	typeName = typeName + "_" + ext
 
-	module := l.bp.CreateModule(ModuleFactoryAdaptor(factory), typeName, append(inherited, props...)...).(Module)
+	module := ctx.createModule(ModuleFactoryAdaptor(factory), typeName, append(inherited, props...)...).(Module)
 
-	if l.Module().base().variableProperties != nil && module.base().variableProperties != nil {
-		src := l.Module().base().variableProperties
+	if ctx.Module().base().variableProperties != nil && module.base().variableProperties != nil {
+		src := ctx.Module().base().variableProperties
 		dst := []interface{}{
 			module.base().variableProperties,
 			// Put an empty copy of the src properties into dst so that properties in src that are not in dst
@@ -122,6 +131,10 @@
 	return module
 }
 
+func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
+	return createModule(l, factory, "_loadHookModule", props...)
+}
+
 func (l *loadHookContext) registerScopedModuleType(name string, factory blueprint.ModuleFactory) {
 	l.bp.RegisterScopedModuleType(name, factory)
 }
diff --git a/android/licenses.go b/android/licenses.go
index e60c7a2..bd14b26 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -335,4 +335,6 @@
 	ctx.Strict("TEXTNOTICE", ctx.Config().HostToolPath(ctx, "textnotice").String())
 	ctx.Strict("COMPLIANCENOTICE_BOM", ctx.Config().HostToolPath(ctx, "compliancenotice_bom").String())
 	ctx.Strict("COMPLIANCENOTICE_SHIPPEDLIBS", ctx.Config().HostToolPath(ctx, "compliancenotice_shippedlibs").String())
+	ctx.Strict("COMPLIANCE_LISTSHARE", ctx.Config().HostToolPath(ctx, "compliance_listshare").String())
+	ctx.Strict("COMPLIANCE_CHECKSHARE", ctx.Config().HostToolPath(ctx, "compliance_checkshare").String())
 }
diff --git a/android/metrics.go b/android/metrics.go
index 9038bde..1580f82 100644
--- a/android/metrics.go
+++ b/android/metrics.go
@@ -17,6 +17,7 @@
 import (
 	"io/ioutil"
 	"runtime"
+	"sort"
 
 	"github.com/google/blueprint/metrics"
 	"google.golang.org/protobuf/proto"
@@ -78,6 +79,23 @@
 		}
 		metrics.Events = append(metrics.Events, &perfInfo)
 	}
+	mixedBuildsInfo := soong_metrics_proto.MixedBuildsInfo{}
+	mixedBuildEnabledModules := make([]string, 0, len(config.mixedBuildEnabledModules))
+	for module, _ := range config.mixedBuildEnabledModules {
+		mixedBuildEnabledModules = append(mixedBuildEnabledModules, module)
+	}
+
+	mixedBuildDisabledModules := make([]string, 0, len(config.mixedBuildDisabledModules))
+	for module, _ := range config.mixedBuildDisabledModules {
+		mixedBuildDisabledModules = append(mixedBuildDisabledModules, module)
+	}
+	// Sorted for deterministic output.
+	sort.Strings(mixedBuildEnabledModules)
+	sort.Strings(mixedBuildDisabledModules)
+
+	mixedBuildsInfo.MixedBuildEnabledModules = mixedBuildEnabledModules
+	mixedBuildsInfo.MixedBuildDisabledModules = mixedBuildDisabledModules
+	metrics.MixedBuildsInfo = &mixedBuildsInfo
 
 	return metrics
 }
diff --git a/android/module.go b/android/module.go
index 66a5f60..ab68e24 100644
--- a/android/module.go
+++ b/android/module.go
@@ -3734,6 +3734,8 @@
 	Installed_paths   []string `json:"installed,omitempty"`
 	SrcJars           []string `json:"srcjars,omitempty"`
 	Paths             []string `json:"path,omitempty"`
+	Static_libs       []string `json:"static_libs,omitempty"`
+	Libs              []string `json:"libs,omitempty"`
 }
 
 func CheckBlueprintSyntax(ctx BaseModuleContext, filename string, contents string) []error {
diff --git a/android/mutator.go b/android/mutator.go
index 739e4ee..02a6143 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -15,12 +15,9 @@
 package android
 
 import (
-	"reflect"
-
 	"android/soong/bazel"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/proptools"
 )
 
 // Phases:
@@ -553,29 +550,16 @@
 	t.Module().base().commonProperties.DebugName = name
 }
 
+func (t *topDownMutatorContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) blueprint.Module {
+	return t.bp.CreateModule(factory, name, props...)
+}
+
 func (t *topDownMutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
-	inherited := []interface{}{&t.Module().base().commonProperties}
-	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), append(inherited, props...)...).(Module)
-
-	if t.Module().base().variableProperties != nil && module.base().variableProperties != nil {
-		src := t.Module().base().variableProperties
-		dst := []interface{}{
-			module.base().variableProperties,
-			// Put an empty copy of the src properties into dst so that properties in src that are not in dst
-			// don't cause a "failed to find property to extend" error.
-			proptools.CloneEmptyProperties(reflect.ValueOf(src)).Interface(),
-		}
-		err := proptools.AppendMatchingProperties(dst, src, nil)
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	return module
+	return createModule(t, factory, "_topDownMutatorModule", props...)
 }
 
 func (t *topDownMutatorContext) createModuleWithoutInheritance(factory ModuleFactory, props ...interface{}) Module {
-	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), props...).(Module)
+	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), "", props...).(Module)
 	return module
 }
 
diff --git a/android/namespace_test.go b/android/namespace_test.go
index ea399da..87d1320 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"errors"
 	"path/filepath"
 	"reflect"
 	"testing"
@@ -24,577 +23,555 @@
 )
 
 func TestDependingOnModuleInSameNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a in the same namespace")
 	}
 }
 
 func TestDependingOnModuleInRootNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
-			test_module {
-				name: "a",
-			}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
+				test_module {
+					name: "a",
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b in root namespace does not depend on module a in the root namespace")
 	}
 }
 
 func TestImplicitlyImportRootNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			test_module {
-				name: "a",
-			}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestDependingOnBlueprintModuleInRootNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			".": `
-			blueprint_test_module {
-				name: "a",
-			}
+				blueprint_test_module {
+					name: "a",
+				}
 			`,
 			"dir1": `
-			soong_namespace {
-			}
-			blueprint_test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				blueprint_test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestDependingOnModuleInImportedNamespace(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a in the same namespace")
 	}
 }
 
 func TestDependingOnModuleInNonImportedNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(
-			`dir3/Android.bp:4:4: "b" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:4:5: "b" depends on undefined module "a"
 Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."]
-Module "a" can be found in these namespaces: ["dir1" "dir2"]`),
-	}
-
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1" "dir2"]\E`)).
+		RunTest(t)
 }
 
 func TestDependingOnModuleByFullyQualifiedReference(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["//dir1:a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["//dir1:a"],
+				}
 			`,
-		},
-	)
-	a := getModule(ctx, "a")
-	b := getModule(ctx, "b")
-	if !dependsOn(ctx, b, a) {
+		}),
+	).RunTest(t)
+
+	a := getModule(result, "a")
+	b := getModule(result, "b")
+	if !dependsOn(result, b, a) {
 		t.Errorf("module b does not depend on module a")
 	}
 }
 
 func TestSameNameInTwoNamespaces(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id: "1",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-				id: "2",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id: "1",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+					id: "2",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"3",
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-				id:"4",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"3",
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+					id:"4",
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	one := findModuleById(ctx, "1")
-	two := findModuleById(ctx, "2")
-	three := findModuleById(ctx, "3")
-	four := findModuleById(ctx, "4")
-	if !dependsOn(ctx, two, one) {
+	one := findModuleById(result, "1")
+	two := findModuleById(result, "2")
+	three := findModuleById(result, "3")
+	four := findModuleById(result, "4")
+	if !dependsOn(result, two, one) {
 		t.Fatalf("Module 2 does not depend on module 1 in its namespace")
 	}
-	if dependsOn(ctx, two, three) {
+	if dependsOn(result, two, three) {
 		t.Fatalf("Module 2 depends on module 3 in another namespace")
 	}
-	if !dependsOn(ctx, four, three) {
+	if !dependsOn(result, four, three) {
 		t.Fatalf("Module 4 does not depend on module 3 in its namespace")
 	}
-	if dependsOn(ctx, four, one) {
+	if dependsOn(result, four, one) {
 		t.Fatalf("Module 4 depends on module 1 in another namespace")
 	}
 }
 
 func TestSearchOrder(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id: "1",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id: "1",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"2",
-			}
-			test_module {
-				name: "b",
-				id:"3",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"2",
+				}
+				test_module {
+					name: "b",
+					id:"3",
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				id:"4",
-			}
-			test_module {
-				name: "b",
-				id:"5",
-			}
-			test_module {
-				name: "c",
-				id:"6",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					id:"4",
+				}
+				test_module {
+					name: "b",
+					id:"5",
+				}
+				test_module {
+					name: "c",
+					id:"6",
+				}
 			`,
 			".": `
-			test_module {
-				name: "a",
-				id: "7",
-			}
-			test_module {
-				name: "b",
-				id: "8",
-			}
-			test_module {
-				name: "c",
-				id: "9",
-			}
-			test_module {
-				name: "d",
-				id: "10",
-			}
+				test_module {
+					name: "a",
+					id: "7",
+				}
+				test_module {
+					name: "b",
+					id: "8",
+				}
+				test_module {
+					name: "c",
+					id: "9",
+				}
+				test_module {
+					name: "d",
+					id: "10",
+				}
 			`,
 			"dir4": `
-			soong_namespace {
-				imports: ["dir1", "dir2", "dir3"]
-			}
-			test_module {
-				name: "test_me",
-				id:"0",
-				deps: ["a", "b", "c", "d"],
-			}
+				soong_namespace {
+					imports: ["dir1", "dir2", "dir3"]
+				}
+				test_module {
+					name: "test_me",
+					id:"0",
+					deps: ["a", "b", "c", "d"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	testMe := findModuleById(ctx, "0")
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "1")) {
+	testMe := findModuleById(result, "0")
+	if !dependsOn(result, testMe, findModuleById(result, "1")) {
 		t.Errorf("test_me doesn't depend on id 1")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "3")) {
+	if !dependsOn(result, testMe, findModuleById(result, "3")) {
 		t.Errorf("test_me doesn't depend on id 3")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "6")) {
+	if !dependsOn(result, testMe, findModuleById(result, "6")) {
 		t.Errorf("test_me doesn't depend on id 6")
 	}
-	if !dependsOn(ctx, testMe, findModuleById(ctx, "10")) {
+	if !dependsOn(result, testMe, findModuleById(result, "10")) {
 		t.Errorf("test_me doesn't depend on id 10")
 	}
-	if numDeps(ctx, testMe) != 4 {
-		t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(ctx, testMe))
+	if numDeps(result, testMe) != 4 {
+		t.Errorf("num dependencies of test_me = %v, not 4\n", numDeps(result, testMe))
 	}
 }
 
 func TestTwoNamespacesCanImportEachOther(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-				imports: ["dir2"]
-			}
-			test_module {
-				name: "a",
-			}
-			test_module {
-				name: "c",
-				deps: ["b"],
-			}
+				soong_namespace {
+					imports: ["dir2"]
+				}
+				test_module {
+					name: "a",
+				}
+				test_module {
+					name: "c",
+					deps: ["b"],
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestImportingNonexistentNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-				imports: ["a_nonexistent_namespace"]
-			}
-			test_module {
-				name: "a",
-				deps: ["a_nonexistent_module"]
-			}
+				soong_namespace {
+					imports: ["a_nonexistent_namespace"]
+				}
+				test_module {
+					name: "a",
+					deps: ["a_nonexistent_module"]
+				}
 			`,
-		},
-	)
-
-	// should complain about the missing namespace and not complain about the unresolvable dependency
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:2:4: module "soong_namespace": namespace a_nonexistent_namespace does not exist`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		// should complain about the missing namespace and not complain about the unresolvable dependency
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:2:5: module "soong_namespace": namespace a_nonexistent_namespace does not exist\E`)).
+		RunTest(t)
 }
 
 func TestNamespacesDontInheritParentNamespaces(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1/subdir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/subdir1/Android.bp:4:4: "b" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/subdir1/Android.bp:4:5: "b" depends on undefined module "a"
 Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."]
-Module "a" can be found in these namespaces: ["dir1"]`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1"]\E`)).
+		RunTest(t)
 }
 
 func TestModulesDoReceiveParentNamespace(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir1/subdir": `
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
+		}),
+	).RunTest(t)
 
-	// setupTest will report any errors
+	// RunTest will report any errors
 }
 
 func TestNamespaceImportsNotTransitive(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+				}
 			`,
 			"dir2": `
-			soong_namespace {
-				imports: ["dir1"],
-			}
-			test_module {
-				name: "b",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir1"],
+				}
+				test_module {
+					name: "b",
+					deps: ["a"],
+				}
 			`,
 			"dir3": `
-			soong_namespace {
-				imports: ["dir2"],
-			}
-			test_module {
-				name: "c",
-				deps: ["a"],
-			}
+				soong_namespace {
+					imports: ["dir2"],
+				}
+				test_module {
+					name: "c",
+					deps: ["a"],
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir3/Android.bp:5:4: "c" depends on undefined module "a"
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:5:5: "c" depends on undefined module "a"
 Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."]
-Module "a" can be found in these namespaces: ["dir1"]`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+Module "a" can be found in these namespaces: ["dir1"]\E`)).
+		RunTest(t)
 }
 
 func TestTwoNamepacesInSameDir(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			soong_namespace {
-			}
+				soong_namespace {
+				}
+				soong_namespace {
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:4:4: namespace dir1 already exists`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:4:5: namespace dir1 already exists\E`)).
+		RunTest(t)
 }
 
 func TestNamespaceNotAtTopOfFile(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			test_module {
-				name: "a"
-			}
-			soong_namespace {
-			}
+				test_module {
+					name: "a"
+				}
+				soong_namespace {
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:5:4: a namespace must be the first module in the file`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:5:5: a namespace must be the first module in the file\E`)).
+		RunTest(t)
 }
 
 func TestTwoModulesWithSameNameInSameNamespace(t *testing.T) {
-	_, errs := setupTestExpectErrs(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a"
-			}
-			test_module {
-				name: "a"
-			}
+				soong_namespace {
+				}
+				test_module {
+					name: "a"
+				}
+				test_module {
+					name: "a"
+				}
 			`,
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`dir1/Android.bp:7:4: module "a" already defined
-       dir1/Android.bp:4:4 <-- previous definition here`),
-	}
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		}),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/Android.bp:7:5: module "a" already defined
+       dir1/Android.bp:4:5 <-- previous definition here\E`)).
+		RunTest(t)
 }
 
 func TestDeclaringNamespaceInNonAndroidBpFile(t *testing.T) {
-	_, errs := setupTestFromFiles(t,
-		map[string][]byte{
-			"Android.bp": []byte(`
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		FixtureWithRootAndroidBp(`
 				build = ["include.bp"]
-			`),
-			"include.bp": []byte(`
+		`),
+		FixtureAddTextFile("include.bp", `
 				soong_namespace {
 				}
-			`),
-		},
-	)
-
-	expectedErrors := []error{
-		errors.New(`include.bp:2:5: A namespace may only be declared in a file named Android.bp`),
-	}
-
-	if len(errs) != 1 || errs[0].Error() != expectedErrors[0].Error() {
-		t.Errorf("Incorrect errors. Expected:\n%v\n, got:\n%v\n", expectedErrors, errs)
-	}
+		`),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(
+			`\Qinclude.bp:2:5: A namespace may only be declared in a file named Android.bp\E`,
+		)).
+		RunTest(t)
 }
 
 // so that the generated .ninja file will have consistent names
 func TestConsistentNamespaceNames(t *testing.T) {
-	ctx := setupTest(t,
-		map[string]string{
+	result := GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": "soong_namespace{}",
 			"dir2": "soong_namespace{}",
 			"dir3": "soong_namespace{}",
-		})
+		}),
+	).RunTest(t)
 
-	ns1, _ := ctx.NameResolver.namespaceAt("dir1")
-	ns2, _ := ctx.NameResolver.namespaceAt("dir2")
-	ns3, _ := ctx.NameResolver.namespaceAt("dir3")
+	ns1, _ := result.NameResolver.namespaceAt("dir1")
+	ns2, _ := result.NameResolver.namespaceAt("dir2")
+	ns3, _ := result.NameResolver.namespaceAt("dir3")
 	actualIds := []string{ns1.id, ns2.id, ns3.id}
 	expectedIds := []string{"1", "2", "3"}
 	if !reflect.DeepEqual(actualIds, expectedIds) {
@@ -604,103 +581,88 @@
 
 // so that the generated .ninja file will have consistent names
 func TestRename(t *testing.T) {
-	_ = setupTest(t,
-		map[string]string{
+	GroupFixturePreparers(
+		prepareForTestWithNamespace,
+		dirBpToPreparer(map[string]string{
 			"dir1": `
-			soong_namespace {
-			}
-			test_module {
-				name: "a",
-				deps: ["c"],
-			}
-			test_module {
-				name: "b",
-				rename: "c",
-			}
-		`})
-	// setupTest will report any errors
+				soong_namespace {
+				}
+				test_module {
+					name: "a",
+					deps: ["c"],
+				}
+				test_module {
+					name: "b",
+					rename: "c",
+				}
+			`,
+		}),
+	).RunTest(t)
+
+	// RunTest will report any errors
 }
 
 // some utils to support the tests
 
-func mockFiles(bps map[string]string) (files map[string][]byte) {
-	files = make(map[string][]byte, len(bps))
+var prepareForTestWithNamespace = GroupFixturePreparers(
+	FixtureRegisterWithContext(registerNamespaceBuildComponents),
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.PreArchMutators(RegisterNamespaceMutator)
+	}),
+	FixtureModifyContext(func(ctx *TestContext) {
+		ctx.RegisterModuleType("test_module", newTestModule)
+		ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
+		ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+			ctx.BottomUp("rename", renameMutator)
+		})
+	}),
+)
+
+// dirBpToPreparer takes a map from directory to the contents of the Android.bp file and produces a
+// FixturePreparer.
+func dirBpToPreparer(bps map[string]string) FixturePreparer {
+	files := make(MockFS, len(bps))
 	files["Android.bp"] = []byte("")
 	for dir, text := range bps {
 		files[filepath.Join(dir, "Android.bp")] = []byte(text)
 	}
-	return files
+	return files.AddToFixture()
 }
 
-func setupTestFromFiles(t *testing.T, bps MockFS) (ctx *TestContext, errs []error) {
-	result := GroupFixturePreparers(
-		FixtureModifyContext(func(ctx *TestContext) {
-			ctx.RegisterModuleType("test_module", newTestModule)
-			ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
-			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.BottomUp("rename", renameMutator)
-			})
-		}),
-		PrepareForTestWithNamespace,
-		bps.AddToFixture(),
-	).
-		// Ignore errors for now so tests can check them later.
-		ExtendWithErrorHandler(FixtureIgnoreErrors).
-		RunTest(t)
-
-	return result.TestContext, result.Errs
-}
-
-func setupTestExpectErrs(t *testing.T, bps map[string]string) (ctx *TestContext, errs []error) {
-	files := make(map[string][]byte, len(bps))
-	files["Android.bp"] = []byte("")
-	for dir, text := range bps {
-		files[filepath.Join(dir, "Android.bp")] = []byte(text)
-	}
-	return setupTestFromFiles(t, files)
-}
-
-func setupTest(t *testing.T, bps map[string]string) (ctx *TestContext) {
-	t.Helper()
-	ctx, errs := setupTestExpectErrs(t, bps)
-	FailIfErrored(t, errs)
-	return ctx
-}
-
-func dependsOn(ctx *TestContext, module TestingModule, possibleDependency TestingModule) bool {
+func dependsOn(result *TestResult, module TestingModule, possibleDependency TestingModule) bool {
 	depends := false
 	visit := func(dependency blueprint.Module) {
 		if dependency == possibleDependency.module {
 			depends = true
 		}
 	}
-	ctx.VisitDirectDeps(module.module, visit)
+	result.VisitDirectDeps(module.module, visit)
 	return depends
 }
 
-func numDeps(ctx *TestContext, module TestingModule) int {
+func numDeps(result *TestResult, module TestingModule) int {
 	count := 0
 	visit := func(dependency blueprint.Module) {
 		count++
 	}
-	ctx.VisitDirectDeps(module.module, visit)
+	result.VisitDirectDeps(module.module, visit)
 	return count
 }
 
-func getModule(ctx *TestContext, moduleName string) TestingModule {
-	return ctx.ModuleForTests(moduleName, "")
+func getModule(result *TestResult, moduleName string) TestingModule {
+	return result.ModuleForTests(moduleName, "")
 }
 
-func findModuleById(ctx *TestContext, id string) (module TestingModule) {
+func findModuleById(result *TestResult, id string) (module TestingModule) {
 	visit := func(candidate blueprint.Module) {
 		testModule, ok := candidate.(*testModule)
 		if ok {
 			if testModule.properties.Id == id {
-				module = newTestingModule(ctx.config, testModule)
+				module = newTestingModule(result.config, testModule)
 			}
 		}
 	}
-	ctx.VisitAllModules(visit)
+	result.VisitAllModules(visit)
 	return module
 }
 
@@ -747,7 +709,7 @@
 	}
 }
 
-func (b *blueprintTestModule) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+func (b *blueprintTestModule) DynamicDependencies(_ blueprint.DynamicDependerModuleContext) []string {
 	return b.properties.Deps
 }
 
diff --git a/android/paths.go b/android/paths.go
index e7829b9..e0e5ae5 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1057,7 +1057,8 @@
 	}
 
 	// absolute path already checked by validateSafePath
-	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
+	// special-case api surface gen files for now
+	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") {
 		return ret, fmt.Errorf("source path %q is in output", ret.String())
 	}
 
@@ -1073,7 +1074,8 @@
 	}
 
 	// absolute path already checked by validatePath
-	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
+	// special-case for now
+	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) && !strings.Contains(ret.String(), ctx.Config().soongOutDir+"/.export") {
 		return ret, fmt.Errorf("source path %q is in output", ret.String())
 	}
 
diff --git a/android/sdk.go b/android/sdk.go
index 1d63d7a..3a56240 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -23,24 +23,8 @@
 	"github.com/google/blueprint/proptools"
 )
 
-// RequiredSdks provides access to the set of SDKs required by an APEX and its contents.
-//
-// Extracted from SdkAware to make it easier to define custom subsets of the
-// SdkAware interface and improve code navigation within the IDE.
-//
-// In addition to its use in SdkAware this interface must also be implemented by
-// APEX to specify the SDKs required by that module and its contents. e.g. APEX
-// is expected to implement RequiredSdks() by reading its own properties like
-// `uses_sdks`.
-type RequiredSdks interface {
-	// RequiredSdks returns the set of SDKs required by an APEX and its contents.
-	RequiredSdks() SdkRefs
-}
-
 // sdkAwareWithoutModule is provided simply to improve code navigation with the IDE.
 type sdkAwareWithoutModule interface {
-	RequiredSdks
-
 	// SdkMemberComponentName will return the name to use for a component of this module based on the
 	// base name of this module.
 	//
@@ -81,7 +65,6 @@
 
 	ContainingSdk() SdkRef
 	MemberName() string
-	BuildWithSdks(sdks SdkRefs)
 }
 
 // SdkAware is the interface that must be supported by any module to become a member of SDK or to be
@@ -150,9 +133,6 @@
 	// The SDK that this module is a member of. nil if it is not a member of any SDK
 	ContainingSdk *SdkRef `blueprint:"mutated"`
 
-	// The list of SDK names and versions that are used to build this module
-	RequiredSdks SdkRefs `blueprint:"mutated"`
-
 	// Name of the module that this sdk member is representing
 	Sdk_member_name *string
 }
@@ -208,16 +188,6 @@
 	return proptools.String(s.properties.Sdk_member_name)
 }
 
-// BuildWithSdks is used to mark that this module has to be built with the given SDK(s).
-func (s *SdkBase) BuildWithSdks(sdks SdkRefs) {
-	s.properties.RequiredSdks = sdks
-}
-
-// RequiredSdks returns the SDK(s) that this module has to be built with
-func (s *SdkBase) RequiredSdks() SdkRefs {
-	return s.properties.RequiredSdks
-}
-
 // InitSdkAwareModule initializes the SdkBase struct. This must be called by all modules including
 // SdkBase.
 func InitSdkAwareModule(m SdkAware) {
diff --git a/android/testing.go b/android/testing.go
index a9632e9..ac02db9 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -211,7 +211,7 @@
 	ctx.finalDeps = append(ctx.finalDeps, f)
 }
 
-func (ctx *TestContext) RegisterBp2BuildConfig(config Bp2BuildConfig) {
+func (ctx *TestContext) RegisterBp2BuildConfig(config bp2BuildConversionAllowlist) {
 	ctx.config.bp2buildPackageConfig = config
 }
 
diff --git a/android/variable.go b/android/variable.go
index 077b810..9478c0c 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -199,6 +199,7 @@
 	Platform_preview_sdk_version              *string  `json:",omitempty"`
 	Platform_min_supported_target_sdk_version *string  `json:",omitempty"`
 	Platform_base_os                          *string  `json:",omitempty"`
+	Platform_version_last_stable              *string  `json:",omitempty"`
 
 	DeviceName                            *string  `json:",omitempty"`
 	DeviceProduct                         *string  `json:",omitempty"`
@@ -351,6 +352,8 @@
 	RecoverySnapshotDirsIncluded []string `json:",omitempty"`
 	HostFakeSnapshotEnabled      bool     `json:",omitempty"`
 
+	MultitreeUpdateMeta bool `json:",omitempty"`
+
 	BoardVendorSepolicyDirs           []string `json:",omitempty"`
 	BoardOdmSepolicyDirs              []string `json:",omitempty"`
 	BoardReqdMaskPolicy               []string `json:",omitempty"`
@@ -460,12 +463,13 @@
 	*v = productVariables{
 		BuildNumberFile: stringPtr("build_number.txt"),
 
-		Platform_version_name:             stringPtr("S"),
-		Platform_sdk_version:              intPtr(30),
-		Platform_sdk_codename:             stringPtr("S"),
-		Platform_sdk_final:                boolPtr(false),
-		Platform_version_active_codenames: []string{"S"},
-		Platform_vndk_version:             stringPtr("S"),
+		Platform_version_name:               stringPtr("S"),
+		Platform_base_sdk_extension_version: intPtr(30),
+		Platform_sdk_version:                intPtr(30),
+		Platform_sdk_codename:               stringPtr("S"),
+		Platform_sdk_final:                  boolPtr(false),
+		Platform_version_active_codenames:   []string{"S"},
+		Platform_vndk_version:               stringPtr("S"),
 
 		HostArch:                   stringPtr("x86_64"),
 		HostSecondaryArch:          stringPtr("x86"),
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 8030326..8afbe7e 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -234,10 +234,10 @@
 		if n != 0 {
 			split := splitFunc(s, n)
 			if n != -1 {
-				if len(split) > n {
+				if len(split) > n || len(split) == 0 {
 					panic("oops!")
 				} else {
-					n -= len(split)
+					n -= len(split) - 1
 				}
 			}
 			curMs.appendString(split[0])
@@ -279,7 +279,7 @@
 
 func (ms *MakeString) EndsWith(ch rune) bool {
 	s := ms.Strings[len(ms.Strings)-1]
-	return s[len(s)-1] == uint8(ch)
+	return len(s) > 0 && s[len(s)-1] == uint8(ch)
 }
 
 func (ms *MakeString) ReplaceLiteral(input string, output string) {
diff --git a/androidmk/parser/make_strings_test.go b/androidmk/parser/make_strings_test.go
index fbb289b..7e842a5 100644
--- a/androidmk/parser/make_strings_test.go
+++ b/androidmk/parser/make_strings_test.go
@@ -75,6 +75,16 @@
 			genMakeString(""),
 		},
 	},
+	{
+		// "x$(var1)y bar"
+		in:  genMakeString("x", "var1", "y bar"),
+		sep: " ",
+		n:   2,
+		expected: []*MakeString{
+			genMakeString("x", "var1", "y"),
+			genMakeString("bar"),
+		},
+	},
 }
 
 func TestMakeStringSplitN(t *testing.T) {
@@ -217,6 +227,36 @@
 	}
 }
 
+var endsWithTestCases = []struct {
+	in       *MakeString
+	endsWith rune
+	expected bool
+}{
+	{
+		in:       genMakeString("foo", "X", "bar ="),
+		endsWith: '=',
+		expected: true,
+	},
+	{
+		in:       genMakeString("foo", "X", "bar ="),
+		endsWith: ':',
+		expected: false,
+	},
+	{
+		in:       genMakeString("foo", "X", ""),
+		endsWith: '=',
+		expected: false,
+	},
+}
+
+func TestMakeStringEndsWith(t *testing.T) {
+	for _, test := range endsWithTestCases {
+		if test.in.EndsWith(test.endsWith) != test.expected {
+			t.Errorf("with:\n%q\nexpected:\n%t\ngot:\n%t", test.in.Dump(), test.expected, !test.expected)
+		}
+	}
+}
+
 func dumpArray(a []*MakeString) string {
 	ret := make([]string, len(a))
 
diff --git a/apex/Android.bp b/apex/Android.bp
index 41224ec..d3417c2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -14,6 +14,7 @@
         "soong-cc",
         "soong-filesystem",
         "soong-java",
+        "soong-multitree",
         "soong-provenance",
         "soong-python",
         "soong-rust",
diff --git a/apex/apex.go b/apex/apex.go
index a7b0a4f..80db7c2 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -19,6 +19,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -33,6 +34,7 @@
 	prebuilt_etc "android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/java"
+	"android/soong/multitree"
 	"android/soong/python"
 	"android/soong/rust"
 	"android/soong/sh"
@@ -48,7 +50,7 @@
 	ctx.RegisterModuleType("apex_vndk", vndkApexBundleFactory)
 	ctx.RegisterModuleType("apex_defaults", defaultsFactory)
 	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	ctx.RegisterModuleType("override_apex", overrideApexFactory)
+	ctx.RegisterModuleType("override_apex", OverrideApexFactory)
 	ctx.RegisterModuleType("apex_set", apexSetFactory)
 
 	ctx.PreArchMutators(registerPreArchMutators)
@@ -158,12 +160,6 @@
 	// 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
-	// other words, they are also built with the SDKs specified here.
-	Uses_sdks []string
-
 	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
 	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
 	// container. When set to zip, contents are stored in a zip container directly. This type is
@@ -358,6 +354,7 @@
 	android.OverridableModuleBase
 	android.SdkBase
 	android.BazelModuleBase
+	multitree.ExportableModuleBase
 
 	// Properties
 	properties            apexBundleProperties
@@ -789,19 +786,6 @@
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
-
-	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
-	// This field currently isn't used.
-	// TODO(jiyong): consider dropping this feature
-	// TODO(jiyong): ensure that all apexes are with non-empty uses_sdks
-	if len(a.properties.Uses_sdks) > 0 {
-		sdkRefs := []android.SdkRef{}
-		for _, str := range a.properties.Uses_sdks {
-			parsed := android.ParseSdkRef(ctx, str, "uses_sdks")
-			sdkRefs = append(sdkRefs, parsed)
-		}
-		a.BuildWithSdks(sdkRefs)
-	}
 }
 
 // DepsMutator for the overridden properties.
@@ -966,7 +950,6 @@
 	apexInfo := android.ApexInfo{
 		ApexVariationName: apexVariationName,
 		MinSdkVersion:     minSdkVersion,
-		RequiredSdks:      a.RequiredSdks(),
 		Updatable:         a.Updatable(),
 		UsePlatformApis:   a.UsePlatformApis(),
 		InApexVariants:    []string{apexVariationName},
@@ -994,6 +977,7 @@
 
 // apexInfoMutator delegates the work of identifying which modules need an ApexInfo and apex
 // specific variant to modules that support the ApexInfoMutator.
+// It also propagates updatable=true to apps of updatable apexes
 func apexInfoMutator(mctx android.TopDownMutatorContext) {
 	if !mctx.Module().Enabled() {
 		return
@@ -1001,8 +985,8 @@
 
 	if a, ok := mctx.Module().(ApexInfoMutator); ok {
 		a.ApexInfoMutator(mctx)
-		return
 	}
+	enforceAppUpdatability(mctx)
 }
 
 // apexStrictUpdatibilityLintMutator propagates strict_updatability_linting to transitive deps of a mainline module
@@ -1033,6 +1017,22 @@
 	}
 }
 
+// enforceAppUpdatability propagates updatable=true to apps of updatable apexes
+func enforceAppUpdatability(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
+		return
+	}
+	if apex, ok := mctx.Module().(*apexBundle); ok && apex.Updatable() {
+		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
+		mctx.VisitDirectDeps(func(module android.Module) {
+			// ignore android_test_app
+			if app, ok := module.(*java.AndroidApp); ok {
+				app.SetUpdatable(true)
+			}
+		})
+	}
+}
+
 // TODO: b/215736885 Whittle the denylist
 // Transitive deps of certain mainline modules baseline NewApi errors
 // Skip these mainline modules for now
@@ -1359,6 +1359,21 @@
 	}
 }
 
+var _ multitree.Exportable = (*apexBundle)(nil)
+
+func (a *apexBundle) Exportable() bool {
+	if a.properties.ApexType == flattenedApex {
+		return false
+	}
+	return true
+}
+
+func (a *apexBundle) TaggedOutputs() map[string]android.Paths {
+	ret := make(map[string]android.Paths)
+	ret["apex"] = android.Paths{a.outputFile}
+	return ret
+}
+
 var _ cc.Coverage = (*apexBundle)(nil)
 
 // Implements cc.Coverage
@@ -1656,13 +1671,33 @@
 var _ androidApp = (*java.AndroidApp)(nil)
 var _ androidApp = (*java.AndroidAppImport)(nil)
 
+func sanitizedBuildIdForPath(ctx android.BaseModuleContext) string {
+	buildId := ctx.Config().BuildId()
+
+	// The build ID is used as a suffix for a filename, so ensure that
+	// the set of characters being used are sanitized.
+	// - any word character: [a-zA-Z0-9_]
+	// - dots: .
+	// - dashes: -
+	validRegex := regexp.MustCompile(`^[\w\.\-\_]+$`)
+	if !validRegex.MatchString(buildId) {
+		ctx.ModuleErrorf("Unable to use build id %s as filename suffix, valid characters are [a-z A-Z 0-9 _ . -].", buildId)
+	}
+	return buildId
+}
+
 func apexFileForAndroidApp(ctx android.BaseModuleContext, aapp androidApp) apexFile {
 	appDir := "app"
 	if aapp.Privileged() {
 		appDir = "priv-app"
 	}
-	dirInApex := filepath.Join(appDir, aapp.InstallApkName())
+
+	// TODO(b/224589412, b/226559955): Ensure that the subdirname is suffixed
+	// so that PackageManager correctly invalidates the existing installed apk
+	// in favour of the new APK-in-APEX.  See bugs for more information.
+	dirInApex := filepath.Join(appDir, aapp.InstallApkName()+"@"+sanitizedBuildIdForPath(ctx))
 	fileToCopy := aapp.OutputFile()
+
 	af := newApexFile(ctx, fileToCopy, aapp.BaseModuleName(), dirInApex, app, aapp)
 	af.jacocoReportClassesFile = aapp.JacocoReportClassesFile()
 	af.lintDepSets = aapp.LintDepSets()
@@ -1895,8 +1930,12 @@
 					if ap.Privileged() {
 						appDir = "priv-app"
 					}
-					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(),
-						filepath.Join(appDir, ap.BaseModuleName()), appSet, ap)
+					// TODO(b/224589412, b/226559955): Ensure that the dirname is
+					// suffixed so that PackageManager correctly invalidates the
+					// existing installed apk in favour of the new APK-in-APEX.
+					// See bugs for more information.
+					appDirName := filepath.Join(appDir, ap.BaseModuleName()+"@"+sanitizedBuildIdForPath(ctx))
+					af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(), appDirName, appSet, ap)
 					af.certificate = java.PresignedCertificate
 					filesInfo = append(filesInfo, af)
 				} else {
@@ -2361,6 +2400,7 @@
 	android.InitSdkAwareModule(module)
 	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
 	android.InitBazelModule(module)
+	multitree.InitExportableModule(module)
 	return module
 }
 
@@ -2411,6 +2451,7 @@
 type OverrideApex struct {
 	android.ModuleBase
 	android.OverrideModuleBase
+	android.BazelModuleBase
 }
 
 func (o *OverrideApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -2419,16 +2460,64 @@
 
 // override_apex is used to create an apex module based on another apex module by overriding some of
 // its properties.
-func overrideApexFactory() android.Module {
+func OverrideApexFactory() android.Module {
 	m := &OverrideApex{}
 
 	m.AddProperties(&overridableProperties{})
 
 	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	android.InitOverrideModule(m)
+	android.InitBazelModule(m)
 	return m
 }
 
+func (o *OverrideApex) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	if ctx.ModuleType() != "override_apex" {
+		return
+	}
+
+	baseApexModuleName := o.OverrideModuleBase.GetOverriddenModuleName()
+	baseModule, baseApexExists := ctx.ModuleFromName(baseApexModuleName)
+	if !baseApexExists {
+		panic(fmt.Errorf("Base apex module doesn't exist: %s", baseApexModuleName))
+	}
+
+	a, baseModuleIsApex := baseModule.(*apexBundle)
+	if !baseModuleIsApex {
+		panic(fmt.Errorf("Base module is not apex module: %s", baseApexModuleName))
+	}
+	attrs, props := convertWithBp2build(a, ctx)
+
+	for _, p := range o.GetProperties() {
+		overridableProperties, ok := p.(*overridableProperties)
+		if !ok {
+			continue
+		}
+		// Key
+		if overridableProperties.Key != nil {
+			attrs.Key = bazel.LabelAttribute{}
+			attrs.Key.SetValue(android.BazelLabelForModuleDepSingle(ctx, *overridableProperties.Key))
+		}
+
+		// Certificate
+		if overridableProperties.Certificate != nil {
+			attrs.Certificate = bazel.LabelAttribute{}
+			attrs.Certificate.SetValue(android.BazelLabelForModuleDepSingle(ctx, *overridableProperties.Certificate))
+		}
+
+		// Prebuilts
+		prebuiltsLabelList := android.BazelLabelForModuleDeps(ctx, overridableProperties.Prebuilts)
+		attrs.Prebuilts = bazel.MakeLabelListAttribute(prebuiltsLabelList)
+
+		// Compressible
+		if overridableProperties.Compressible != nil {
+			attrs.Compressible = bazel.BoolAttribute{Value: overridableProperties.Compressible}
+		}
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: o.Name()}, &attrs)
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Vality check routines
 //
@@ -3132,33 +3221,6 @@
 	//
 	// Module separator
 	//
-	m["com.android.permission"] = []string{
-		"car-ui-lib",
-		"iconloader",
-		"kotlin-annotations",
-		"kotlin-stdlib",
-		"kotlin-stdlib-jdk7",
-		"kotlin-stdlib-jdk8",
-		"kotlinx-coroutines-android",
-		"kotlinx-coroutines-android-nodeps",
-		"kotlinx-coroutines-core",
-		"kotlinx-coroutines-core-nodeps",
-		"permissioncontroller-statsd",
-		"GooglePermissionController",
-		"PermissionController",
-		"SettingsLibActionBarShadow",
-		"SettingsLibAppPreference",
-		"SettingsLibBarChartPreference",
-		"SettingsLibLayoutPreference",
-		"SettingsLibProgressBar",
-		"SettingsLibSearchWidget",
-		"SettingsLibSettingsTheme",
-		"SettingsLibRestrictedLockUtils",
-		"SettingsLibHelpUtils",
-	}
-	//
-	// Module separator
-	//
 	m["com.android.runtime"] = []string{
 		"bionic_libc_platform_headers",
 		"libarm-optimized-routines-math",
@@ -3406,6 +3468,11 @@
 		return
 	}
 
+	attrs, props := convertWithBp2build(a, ctx)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, &attrs)
+}
+
+func convertWithBp2build(a *apexBundle, ctx android.TopDownMutatorContext) (bazelApexBundleAttributes, bazel.BazelTargetModuleProperties) {
 	var manifestLabelAttribute bazel.LabelAttribute
 	if a.properties.Manifest != nil {
 		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.Manifest))
@@ -3417,8 +3484,15 @@
 	}
 
 	var fileContextsLabelAttribute bazel.LabelAttribute
-	if a.properties.File_contexts != nil {
+	if a.properties.File_contexts == nil {
+		// See buildFileContexts(), if file_contexts is not specified the default one is used, which is //system/sepolicy/apex:<module name>-file_contexts
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, a.Name()+"-file_contexts"))
+	} else if strings.HasPrefix(*a.properties.File_contexts, ":") {
+		// File_contexts is a module
 		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.properties.File_contexts))
+	} else {
+		// File_contexts is a file
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.File_contexts))
 	}
 
 	// TODO(b/219503907) this would need to be set to a.MinSdkVersionValue(ctx) but
@@ -3476,7 +3550,7 @@
 		compressibleAttribute.Value = a.overridableProperties.Compressible
 	}
 
-	attrs := &bazelApexBundleAttributes{
+	attrs := bazelApexBundleAttributes{
 		Manifest:              manifestLabelAttribute,
 		Android_manifest:      androidManifestLabelAttribute,
 		File_contexts:         fileContextsLabelAttribute,
@@ -3494,10 +3568,10 @@
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "apex",
-		Bzl_load_location: "//build/bazel/rules:apex.bzl",
+		Bzl_load_location: "//build/bazel/rules/apex:apex.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+	return attrs, props
 }
 
 // The following conversions are based on this table where the rows are the compile_multilib
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 77cbb58..bcb55af 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -223,6 +223,7 @@
 		// 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")
+		variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
 	}),
 )
 
@@ -682,7 +683,7 @@
 		"etc/myetc",
 		"javalib/myjar.jar",
 		"lib64/mylib.so",
-		"app/AppFoo/AppFoo.apk",
+		"app/AppFoo@TEST.BUILD_ID/AppFoo.apk",
 		"overlay/blue/rro.apk",
 		"etc/bpf/bpf.o",
 		"etc/bpf/bpf2.o",
@@ -2393,6 +2394,7 @@
 			key: "myapex.key",
 			apps: ["AppFoo"],
 			min_sdk_version: "29",
+			updatable: false,
 		}
 
 		apex_key {
@@ -5682,8 +5684,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/AppFoo/AppFoo.apk")
-	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPriv/AppFooPriv.apk")
+	ensureContains(t, copyCmds, "image.apex/app/AppFoo@TEST.BUILD_ID/AppFoo.apk")
+	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPriv@TEST.BUILD_ID/AppFooPriv.apk")
 
 	appZipRule := ctx.ModuleForTests("AppFoo", "android_common_apex10000").Description("zip jni libs")
 	// JNI libraries are uncompressed
@@ -5700,6 +5702,36 @@
 	}
 }
 
+func TestApexWithAppImportBuildId(t *testing.T) {
+	invalidBuildIds := []string{"../", "a b", "a/b", "a/b/../c", "/a"}
+	for _, id := range invalidBuildIds {
+		message := fmt.Sprintf("Unable to use build id %s as filename suffix", id)
+		fixture := android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.BuildId = proptools.StringPtr(id)
+		})
+		testApexError(t, message, `apex {
+			name: "myapex",
+			key: "myapex.key",
+			apps: ["AppFooPrebuilt"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		android_app_import {
+			name: "AppFooPrebuilt",
+			apk: "PrebuiltAppFoo.apk",
+			presigned: true,
+			apex_available: ["myapex"],
+		}
+	`, fixture)
+	}
+}
+
 func TestApexWithAppImports(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -5745,8 +5777,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/AppFooPrebuilt/AppFooPrebuilt.apk")
-	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt/AwesomePrebuiltAppFooPriv.apk")
+	ensureContains(t, copyCmds, "image.apex/app/AppFooPrebuilt@TEST.BUILD_ID/AppFooPrebuilt.apk")
+	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPrivPrebuilt@TEST.BUILD_ID/AwesomePrebuiltAppFooPriv.apk")
 }
 
 func TestApexWithAppImportsPrefer(t *testing.T) {
@@ -5787,7 +5819,7 @@
 	}))
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
-		"app/AppFoo/AppFooPrebuilt.apk",
+		"app/AppFoo@TEST.BUILD_ID/AppFooPrebuilt.apk",
 	})
 }
 
@@ -5820,7 +5852,7 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureContains(t, copyCmds, "image.apex/app/TesterHelpAppFoo/TesterHelpAppFoo.apk")
+	ensureContains(t, copyCmds, "image.apex/app/TesterHelpAppFoo@TEST.BUILD_ID/TesterHelpAppFoo.apk")
 }
 
 func TestApexPropertiesShouldBeDefaultable(t *testing.T) {
@@ -6263,8 +6295,8 @@
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	ensureNotContains(t, copyCmds, "image.apex/app/app/app.apk")
-	ensureContains(t, copyCmds, "image.apex/app/override_app/override_app.apk")
+	ensureNotContains(t, copyCmds, "image.apex/app/app@TEST.BUILD_ID/app.apk")
+	ensureContains(t, copyCmds, "image.apex/app/override_app@TEST.BUILD_ID/override_app.apk")
 
 	ensureNotContains(t, copyCmds, "image.apex/etc/bpf/bpf.o")
 	ensureContains(t, copyCmds, "image.apex/etc/bpf/override_bpf.o")
@@ -7168,7 +7200,7 @@
 	content := bundleConfigRule.Args["content"]
 
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
-	ensureContains(t, content, `"apex_config":{"apex_embedded_apk_config":[{"package_name":"com.android.foo","path":"app/AppFoo/AppFoo.apk"}]}`)
+	ensureContains(t, content, `"apex_config":{"apex_embedded_apk_config":[{"package_name":"com.android.foo","path":"app/AppFoo@TEST.BUILD_ID/AppFoo.apk"}]}`)
 }
 
 func TestAppSetBundle(t *testing.T) {
@@ -7199,9 +7231,9 @@
 	if len(copyCmds) != 3 {
 		t.Fatalf("Expected 3 commands, got %d in:\n%s", len(copyCmds), s)
 	}
-	ensureMatches(t, copyCmds[0], "^rm -rf .*/app/AppSet$")
-	ensureMatches(t, copyCmds[1], "^mkdir -p .*/app/AppSet$")
-	ensureMatches(t, copyCmds[2], "^unzip .*-d .*/app/AppSet .*/AppSet.zip$")
+	ensureMatches(t, copyCmds[0], "^rm -rf .*/app/AppSet@TEST.BUILD_ID$")
+	ensureMatches(t, copyCmds[1], "^mkdir -p .*/app/AppSet@TEST.BUILD_ID$")
+	ensureMatches(t, copyCmds[2], "^unzip .*-d .*/app/AppSet@TEST.BUILD_ID .*/AppSet.zip$")
 }
 
 func TestAppSetBundlePrebuilt(t *testing.T) {
@@ -9324,6 +9356,69 @@
 	}
 }
 
+// updatable apexes should propagate updatable=true to its apps
+func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: %v,
+			apps: [
+				"myapp",
+			],
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		android_app {
+			name: "myapp",
+			updatable: %v,
+			apex_available: [
+				"myapex",
+			],
+			sdk_version: "current",
+			min_sdk_version: "30",
+		}
+		`
+	testCases := []struct {
+		name                      string
+		apex_is_updatable_bp      bool
+		app_is_updatable_bp       bool
+		app_is_updatable_expected bool
+	}{
+		{
+			name:                      "Non-updatable apex respects updatable property of non-updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: false,
+		},
+		{
+			name:                      "Non-updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      false,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex respects updatable property of updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       true,
+			app_is_updatable_expected: true,
+		},
+		{
+			name:                      "Updatable apex sets updatable=true on non-updatable app",
+			apex_is_updatable_bp:      true,
+			app_is_updatable_bp:       false,
+			app_is_updatable_expected: true,
+		},
+	}
+	for _, testCase := range testCases {
+		result := testApex(t, fmt.Sprintf(bp, testCase.apex_is_updatable_bp, testCase.app_is_updatable_bp))
+		myapp := result.ModuleForTests("myapp", "android_common").Module().(*java.AndroidApp)
+		android.AssertBoolEquals(t, testCase.name, testCase.app_is_updatable_expected, myapp.Updatable())
+	}
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/apex/builder.go b/apex/builder.go
index 293f388..abbf8ad 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -388,7 +388,7 @@
 
 	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 
-	installSymbolFiles := !ctx.Config().KatiEnabled() || a.ExportedToMake()
+	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
 
 	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
 	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
diff --git a/apex/key.go b/apex/key.go
index 829410e..9c5bb05 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -230,7 +230,7 @@
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "apex_key",
-		Bzl_load_location: "//build/bazel/rules:apex_key.bzl",
+		Bzl_load_location: "//build/bazel/rules/apex:apex_key.bzl",
 	}
 
 	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
diff --git a/bazel/Android.bp b/bazel/Android.bp
index 80af2bd..9e7edc7 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -10,11 +10,11 @@
         "configurability.go",
         "constants.go",
         "properties.go",
+        "testing.go",
     ],
     testSrcs: [
         "aquery_test.go",
         "properties_test.go",
-        "testing.go",
     ],
     pluginFor: [
         "soong_build",
diff --git a/bazel/aquery.go b/bazel/aquery.go
index fd8cf67..e05cbd6 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -43,6 +43,18 @@
 	Value string
 }
 
+// AqueryDepset is a depset definition from Bazel's aquery response. This is
+// akin to the `depSetOfFiles` in the response proto, except that direct
+// artifacts are enumerated by full path instead of by ID.
+// A depset is a data structure for efficient transitive handling of artifact
+// paths. A single depset consists of one or more artifact paths and one or
+// more "child" depsets.
+type AqueryDepset struct {
+	Id                  int
+	DirectArtifacts     []string
+	TransitiveDepSetIds []int
+}
+
 // depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
 // Represents a data structure containing one or more files. Depsets in Bazel are an efficient
 // data structure for storing large numbers of file paths.
@@ -79,21 +91,21 @@
 	Command      string
 	Depfile      *string
 	OutputPaths  []string
-	InputPaths   []string
 	SymlinkPaths []string
 	Env          []KeyValuePair
 	Mnemonic     string
+
+	// Inputs of this build statement, either as unexpanded depsets or expanded
+	// input paths. There should be no overlap between these fields; an input
+	// path should either be included as part of an unexpanded depset or a raw
+	// input path string, but not both.
+	InputDepsetIds []int
+	InputPaths     []string
 }
 
 // A helper type for aquery processing which facilitates retrieval of path IDs from their
 // less readable Bazel structures (depset and path fragment).
 type aqueryArtifactHandler struct {
-	// Maps middleman artifact Id to input artifact depset ID.
-	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
-	// if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
-	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
-	// that action instead.
-	middlemanIdToDepsetIds map[int][]int
 	// Maps depset Id to depset struct.
 	depsetIdToDepset map[int]depSetOfFiles
 	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
@@ -132,12 +144,11 @@
 		artifactIdToPath[artifact.Id] = artifactPath
 	}
 
-	depsetIdToDepset := map[int]depSetOfFiles{}
-	for _, depset := range aqueryResult.DepSetOfFiles {
-		depsetIdToDepset[depset.Id] = depset
-	}
-
-	// Do a pass through all actions to identify which artifacts are middleman artifacts.
+	// Map middleman artifact Id to input artifact depset ID.
+	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
+	// if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
+	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
+	// that action instead.
 	middlemanIdToDepsetIds := map[int][]int{}
 	for _, actionEntry := range aqueryResult.Actions {
 		if actionEntry.Mnemonic == "Middleman" {
@@ -146,14 +157,64 @@
 			}
 		}
 	}
+
+	// Store all depset IDs to validate all depset links are resolvable.
+	depsetIds := map[int]bool{}
+	for _, depset := range aqueryResult.DepSetOfFiles {
+		depsetIds[depset.Id] = true
+	}
+
+	depsetIdToDepset := map[int]depSetOfFiles{}
+	// Validate and adjust aqueryResult.DepSetOfFiles values.
+	for _, depset := range aqueryResult.DepSetOfFiles {
+		filteredArtifactIds := []int{}
+		for _, artifactId := range depset.DirectArtifactIds {
+			path, pathExists := artifactIdToPath[artifactId]
+			if !pathExists {
+				return nil, fmt.Errorf("undefined input artifactId %d", artifactId)
+			}
+			// Filter out any inputs which are universally dropped, and swap middleman
+			// artifacts with their corresponding depsets.
+			if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
+				// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
+				depset.TransitiveDepSetIds = append(depset.TransitiveDepSetIds, depsetsToUse...)
+			} else if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
+				// Drop these artifacts.
+				// See go/python-binary-host-mixed-build for more details.
+				// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
+				// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
+				// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+				// but it doesn't contain sufficient information so no Ninja build statements are generated
+				// for creating it.
+				// So in mixed build mode, when these two are used as input of some Ninja build statement,
+				// since there is no build statement to create them, they should be removed from input paths.
+				// TODO(b/197135294): Clean up this custom runfiles handling logic when
+				// SourceSymlinkManifest and SymlinkTree actions are supported.
+			} else {
+				// TODO(b/216194240): Filter out bazel tools.
+				filteredArtifactIds = append(filteredArtifactIds, artifactId)
+			}
+		}
+		depset.DirectArtifactIds = filteredArtifactIds
+		for _, childDepsetId := range depset.TransitiveDepSetIds {
+			if _, exists := depsetIds[childDepsetId]; !exists {
+				return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
+			}
+		}
+		depsetIdToDepset[depset.Id] = depset
+	}
+
 	return &aqueryArtifactHandler{
-		middlemanIdToDepsetIds:     middlemanIdToDepsetIds,
 		depsetIdToDepset:           depsetIdToDepset,
 		depsetIdToArtifactIdsCache: map[int][]int{},
 		artifactIdToPath:           artifactIdToPath,
 	}, nil
 }
 
+// getInputPaths flattens the depsets of the given IDs and returns all transitive
+// input paths contained in these depsets.
+// This is a potentially expensive operation, and should not be invoked except
+// for actions which need specialized input handling.
 func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
 	inputPaths := []string{}
 
@@ -163,48 +224,15 @@
 			return nil, err
 		}
 		for _, inputId := range inputArtifacts {
-			if middlemanInputDepsetIds, isMiddlemanArtifact := a.middlemanIdToDepsetIds[inputId]; isMiddlemanArtifact {
-				// Add all inputs from middleman actions which created middleman artifacts which are
-				// in the inputs for this action.
-				swappedInputPaths, err := a.getInputPaths(middlemanInputDepsetIds)
-				if err != nil {
-					return nil, err
-				}
-				inputPaths = append(inputPaths, swappedInputPaths...)
-			} else {
-				inputPath, exists := a.artifactIdToPath[inputId]
-				if !exists {
-					return nil, fmt.Errorf("undefined input artifactId %d", inputId)
-				}
-				inputPaths = append(inputPaths, inputPath)
+			inputPath, exists := a.artifactIdToPath[inputId]
+			if !exists {
+				return nil, fmt.Errorf("undefined input artifactId %d", inputId)
 			}
+			inputPaths = append(inputPaths, inputPath)
 		}
 	}
 
-	// TODO(b/197135294): Clean up this custom runfiles handling logic when
-	// SourceSymlinkManifest and SymlinkTree actions are supported.
-	filteredInputPaths := filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths)
-
-	return filteredInputPaths, nil
-}
-
-// See go/python-binary-host-mixed-build for more details.
-// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
-// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
-// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
-// but it doesn't contain sufficient information so no Ninja build statements are generated
-// for creating it.
-// So in mixed build mode, when these two are used as input of some Ninja build statement,
-// since there is no build statement to create them, they should be removed from input paths.
-func filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths []string) []string {
-	filteredInputPaths := []string{}
-	for _, path := range inputPaths {
-		if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
-			continue
-		}
-		filteredInputPaths = append(filteredInputPaths, path)
-	}
-	return filteredInputPaths
+	return inputPaths, nil
 }
 
 func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
@@ -227,115 +255,233 @@
 	}
 }
 
-// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
-// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
-// aquery invocation).
-func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
+// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
+// which should be registered (and output  to a ninja file) to correspond with Bazel's
+// action graph, as described by the given action graph json proto.
+// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
+// are one-to-one with Bazel's depSetOfFiles objects.
+func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, []AqueryDepset, error) {
 	buildStatements := []BuildStatement{}
+	depsets := []AqueryDepset{}
 
 	var aqueryResult actionGraphContainer
 	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	aqueryHandler, err := newAqueryHandler(aqueryResult)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	for _, actionEntry := range aqueryResult.Actions {
 		if shouldSkipAction(actionEntry) {
 			continue
 		}
-		outputPaths := []string{}
-		var depfile *string
-		for _, outputId := range actionEntry.OutputIds {
-			outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
-			if !exists {
-				return nil, fmt.Errorf("undefined outputId %d", outputId)
-			}
-			ext := filepath.Ext(outputPath)
-			if ext == ".d" {
-				if depfile != nil {
-					return nil, fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
-				} else {
-					depfile = &outputPath
-				}
-			} else {
-				outputPaths = append(outputPaths, outputPath)
-			}
-		}
-		inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
-		if err != nil {
-			return nil, err
-		}
 
-		buildStatement := BuildStatement{
-			Command:     strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " "),
-			Depfile:     depfile,
-			OutputPaths: outputPaths,
-			InputPaths:  inputPaths,
-			Env:         actionEntry.EnvironmentVariables,
-			Mnemonic:    actionEntry.Mnemonic,
-		}
-
+		var buildStatement BuildStatement
 		if isSymlinkAction(actionEntry) {
-			if len(inputPaths) != 1 || len(outputPaths) != 1 {
-				return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
-			}
-			out := outputPaths[0]
-			outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
-			out = proptools.ShellEscapeIncludingSpaces(out)
-			in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
-			// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
-			buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
-			buildStatement.SymlinkPaths = outputPaths[:]
+			buildStatement, err = aqueryHandler.symlinkActionBuildStatement(actionEntry)
 		} else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
-			if len(outputPaths) != 1 {
-				return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
-			}
-			expandedTemplateContent := expandTemplateContent(actionEntry)
-			// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
-			// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
-			// change \n to space and mess up the format of Python programs.
-			// sed is used to convert \\n back to \n before saving to output file.
-			// See go/python-binary-host-mixed-build for more details.
-			command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
-				escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
-			buildStatement.Command = command
+			buildStatement, err = aqueryHandler.templateExpandActionBuildStatement(actionEntry)
 		} else if isPythonZipperAction(actionEntry) {
-			if len(inputPaths) < 1 || len(outputPaths) != 1 {
-				return nil, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
-			}
-			buildStatement.InputPaths, buildStatement.Command = removePy3wrapperScript(buildStatement)
-			buildStatement.Command = addCommandForPyBinaryRunfilesDir(buildStatement, inputPaths[0], outputPaths[0])
-			// Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
-			// In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
-			// which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
-			//
-			// The following logic relies on that Bazel aquery output returns actions in the order that
-			// PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
-			// in that order, the following logic might not find the build statement generated for Python binary
-			// stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
-			// See go/python-binary-host-mixed-build for more details.
-			pythonZipFilePath := outputPaths[0]
-			pyBinaryFound := false
-			for i, _ := range buildStatements {
-				if len(buildStatements[i].OutputPaths) == 1 && buildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
-					buildStatements[i].InputPaths = append(buildStatements[i].InputPaths, pythonZipFilePath)
-					pyBinaryFound = true
-				}
-			}
-			if !pyBinaryFound {
-				return nil, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
-			}
+			buildStatement, err = aqueryHandler.pythonZipperActionBuildStatement(actionEntry, buildStatements)
 		} else if len(actionEntry.Arguments) < 1 {
-			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
+			return nil, nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
+		} else {
+			buildStatement, err = aqueryHandler.normalActionBuildStatement(actionEntry)
+		}
+
+		if err != nil {
+			return nil, nil, err
 		}
 		buildStatements = append(buildStatements, buildStatement)
 	}
 
-	return buildStatements, nil
+	// Iterate over depset IDs in the initial aquery order to preserve determinism.
+	for _, depset := range aqueryResult.DepSetOfFiles {
+		// Use the depset in the aqueryHandler, as this contains the augmented depsets.
+		depset = aqueryHandler.depsetIdToDepset[depset.Id]
+		directPaths := []string{}
+		for _, artifactId := range depset.DirectArtifactIds {
+			pathString := aqueryHandler.artifactIdToPath[artifactId]
+			directPaths = append(directPaths, pathString)
+		}
+		aqueryDepset := AqueryDepset{
+			Id:                  depset.Id,
+			DirectArtifacts:     directPaths,
+			TransitiveDepSetIds: depset.TransitiveDepSetIds,
+		}
+		depsets = append(depsets, aqueryDepset)
+	}
+	return buildStatements, depsets, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) validateInputDepsets(inputDepsetIds []int) ([]int, error) {
+	// Validate input depsets correspond to real depsets.
+	for _, depsetId := range inputDepsetIds {
+		if _, exists := aqueryHandler.depsetIdToDepset[depsetId]; !exists {
+			return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+		}
+	}
+	return inputDepsetIds, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) normalActionBuildStatement(actionEntry action) (BuildStatement, error) {
+	command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
+	inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+
+	buildStatement := BuildStatement{
+		Command:        command,
+		Depfile:        depfile,
+		OutputPaths:    outputPaths,
+		InputDepsetIds: inputDepsetIds,
+		Env:            actionEntry.EnvironmentVariables,
+		Mnemonic:       actionEntry.Mnemonic,
+	}
+	return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) pythonZipperActionBuildStatement(actionEntry action, prevBuildStatements []BuildStatement) (BuildStatement, error) {
+	inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+
+	if len(inputPaths) < 1 || len(outputPaths) != 1 {
+		return BuildStatement{}, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
+	}
+	command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
+	inputPaths, command = removePy3wrapperScript(inputPaths, command)
+	command = addCommandForPyBinaryRunfilesDir(command, inputPaths[0], outputPaths[0])
+	// Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
+	// In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
+	// which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
+	//
+	// The following logic relies on that Bazel aquery output returns actions in the order that
+	// PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
+	// in that order, the following logic might not find the build statement generated for Python binary
+	// stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
+	// See go/python-binary-host-mixed-build for more details.
+	pythonZipFilePath := outputPaths[0]
+	pyBinaryFound := false
+	for i, _ := range prevBuildStatements {
+		if len(prevBuildStatements[i].OutputPaths) == 1 && prevBuildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
+			prevBuildStatements[i].InputPaths = append(prevBuildStatements[i].InputPaths, pythonZipFilePath)
+			pyBinaryFound = true
+		}
+	}
+	if !pyBinaryFound {
+		return BuildStatement{}, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
+	}
+
+	buildStatement := BuildStatement{
+		Command:     command,
+		Depfile:     depfile,
+		OutputPaths: outputPaths,
+		InputPaths:  inputPaths,
+		Env:         actionEntry.EnvironmentVariables,
+		Mnemonic:    actionEntry.Mnemonic,
+	}
+	return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry action) (BuildStatement, error) {
+	outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	if len(outputPaths) != 1 {
+		return BuildStatement{}, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
+	}
+	expandedTemplateContent := expandTemplateContent(actionEntry)
+	// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
+	// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
+	// change \n to space and mess up the format of Python programs.
+	// sed is used to convert \\n back to \n before saving to output file.
+	// See go/python-binary-host-mixed-build for more details.
+	command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
+		escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
+	inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+
+	buildStatement := BuildStatement{
+		Command:        command,
+		Depfile:        depfile,
+		OutputPaths:    outputPaths,
+		InputDepsetIds: inputDepsetIds,
+		Env:            actionEntry.EnvironmentVariables,
+		Mnemonic:       actionEntry.Mnemonic,
+	}
+	return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry action) (BuildStatement, error) {
+	outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+
+	inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+	if err != nil {
+		return BuildStatement{}, err
+	}
+	if len(inputPaths) != 1 || len(outputPaths) != 1 {
+		return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
+	}
+	out := outputPaths[0]
+	outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
+	out = proptools.ShellEscapeIncludingSpaces(out)
+	in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
+	// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
+	command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
+	symlinkPaths := outputPaths[:]
+
+	buildStatement := BuildStatement{
+		Command:      command,
+		Depfile:      depfile,
+		OutputPaths:  outputPaths,
+		InputPaths:   inputPaths,
+		Env:          actionEntry.EnvironmentVariables,
+		Mnemonic:     actionEntry.Mnemonic,
+		SymlinkPaths: symlinkPaths,
+	}
+	return buildStatement, nil
+}
+
+func (aqueryHandler *aqueryArtifactHandler) getOutputPaths(actionEntry action) (outputPaths []string, depfile *string, err error) {
+	for _, outputId := range actionEntry.OutputIds {
+		outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
+		if !exists {
+			err = fmt.Errorf("undefined outputId %d", outputId)
+			return
+		}
+		ext := filepath.Ext(outputPath)
+		if ext == ".d" {
+			if depfile != nil {
+				err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
+				return
+			} else {
+				depfile = &outputPath
+			}
+		} else {
+			outputPaths = append(outputPaths, outputPath)
+		}
+	}
+	return
 }
 
 // expandTemplateContent substitutes the tokens in a template.
@@ -372,10 +518,10 @@
 // removed from input paths and command of creating python zip file.
 // See go/python-binary-host-mixed-build for more details.
 // TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
-func removePy3wrapperScript(bs BuildStatement) (newInputPaths []string, newCommand string) {
+func removePy3wrapperScript(inputPaths []string, command string) (newInputPaths []string, newCommand string) {
 	// Remove from inputs
 	filteredInputPaths := []string{}
-	for _, path := range bs.InputPaths {
+	for _, path := range inputPaths {
 		if !strings.HasSuffix(path, py3wrapperFileName) {
 			filteredInputPaths = append(filteredInputPaths, path)
 		}
@@ -384,7 +530,7 @@
 
 	// Remove from command line
 	var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
-	newCommand = re.ReplaceAllString(bs.Command, "")
+	newCommand = re.ReplaceAllString(command, "")
 	return
 }
 
@@ -395,14 +541,14 @@
 // so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
 // See go/python-binary-host-mixed-build for more details.
 // TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
-func addCommandForPyBinaryRunfilesDir(bs BuildStatement, zipperCommandPath, zipFilePath string) string {
+func addCommandForPyBinaryRunfilesDir(oldCommand string, zipperCommandPath, zipFilePath string) string {
 	// Unzip the zip file, zipFilePath looks like <python_binary>.zip
 	runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
 	command := fmt.Sprintf("%s x %s -d %s", zipperCommandPath, zipFilePath, runfilesDirName)
 	// Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
 	// when running the python binary stub script.
 	command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
-	return bs.Command + " && " + command
+	return oldCommand + " && " + command
 }
 
 func isSymlinkAction(a action) bool {
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 68e50c2..2328411 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -223,7 +223,7 @@
     "parentId": 13
   }]
 }`
-	actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString))
+	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements([]byte(inputString))
 	expectedBuildStatements := []BuildStatement{}
 	for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} {
 		expectedBuildStatements = append(expectedBuildStatements,
@@ -234,11 +234,7 @@
 				OutputPaths: []string{
 					fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
 				},
-				InputPaths: []string{
-					"../sourceroot/bionic/libc/SYSCALLS.TXT",
-					"../sourceroot/bionic/libc/tools/gensyscalls.py",
-					"../bazel_tools/tools/genrule/genrule-setup.sh",
-				},
+				InputDepsetIds: []int{1},
 				Env: []KeyValuePair{
 					KeyValuePair{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
 				},
@@ -246,6 +242,16 @@
 			})
 	}
 	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
+
+	expectedFlattenedInputs := []string{
+		"../sourceroot/bionic/libc/SYSCALLS.TXT",
+		"../sourceroot/bionic/libc/tools/gensyscalls.py",
+		"../bazel_tools/tools/genrule/genrule-setup.sh",
+	}
+	actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
+	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+	}
 }
 
 func TestInvalidOutputId(t *testing.T) {
@@ -280,11 +286,11 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, "undefined outputId 3")
 }
 
-func TestInvalidInputDepsetId(t *testing.T) {
+func TestInvalidInputDepsetIdFromAction(t *testing.T) {
 	const inputString = `
 {
   "artifacts": [{
@@ -316,10 +322,47 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, "undefined input depsetId 2")
 }
 
+func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "x",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [1],
+    "outputIds": [1],
+    "primaryOutputId": 1
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1, 2],
+    "transitiveDepSetIds": [42]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "two"
+  }]
+}`
+
+	_, _, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)")
+}
+
 func TestInvalidInputArtifactId(t *testing.T) {
 	const inputString = `
 {
@@ -352,7 +395,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, "undefined input artifactId 3")
 }
 
@@ -389,7 +432,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, "undefined path fragment id 3")
 }
 
@@ -431,7 +474,7 @@
   }]
 }`
 
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
 	}
@@ -492,7 +535,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`)
 }
 
@@ -699,23 +742,31 @@
   }]
 }`
 
-	actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString))
-	// Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
-	// are given via a deep depset, but the depset is flattened when returned as a
-	// BuildStatement slice.
-	inputPaths := []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root"}
-	for i := 1; i < 20; i++ {
-		inputPaths = append(inputPaths, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
-	}
+	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements([]byte(inputString))
+
 	expectedBuildStatements := []BuildStatement{
 		BuildStatement{
-			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",
+			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"},
+			InputDepsetIds: []int{1},
+			Mnemonic:       "Action",
 		},
 	}
 	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
+
+	// Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
+	// are given via a deep depset, but the depset is flattened when returned as a
+	// BuildStatement slice.
+	expectedFlattenedInputs := []string{}
+	for i := 1; i < 20; i++ {
+		expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
+	}
+	expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
+
+	actualFlattenedInputs := flattenDepsets([]int{1}, actualDepsets)
+	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+	}
 }
 
 func TestMiddlemenAction(t *testing.T) {
@@ -785,24 +836,74 @@
   }]
 }`
 
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actualBuildStatements, actualDepsets, err := AqueryBuildStatements([]byte(inputString))
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
 	}
-	if expected := 1; len(actual) != expected {
-		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
+	if expected := 1; len(actualBuildStatements) != expected {
+		t.Fatalf("Expected %d build statements, got %d", expected, len(actualBuildStatements))
 	}
 
-	bs := actual[0]
-	expectedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
-	if !reflect.DeepEqual(bs.InputPaths, expectedInputs) {
-		t.Errorf("Expected main action inputs %q, but got %q", expectedInputs, bs.InputPaths)
+	bs := actualBuildStatements[0]
+	if len(bs.InputPaths) > 0 {
+		t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
+	}
+
+	expectedInputDepsets := []int{2}
+	if !reflect.DeepEqual(bs.InputDepsetIds, expectedInputDepsets) {
+		t.Errorf("Expected main action depset IDs %v, but got %v", expectedInputDepsets, bs.InputDepsetIds)
 	}
 
 	expectedOutputs := []string{"output"}
 	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
 		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
 	}
+
+	expectedAllDepsets := []AqueryDepset{
+		{
+			Id:              1,
+			DirectArtifacts: []string{"middleinput_one", "middleinput_two"},
+		},
+		{
+			Id:                  2,
+			DirectArtifacts:     []string{"maininput_one", "maininput_two"},
+			TransitiveDepSetIds: []int{1},
+		},
+	}
+	if !reflect.DeepEqual(actualDepsets, expectedAllDepsets) {
+		t.Errorf("Expected depsets %v, but got %v", expectedAllDepsets, actualDepsets)
+	}
+
+	expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
+	actualFlattenedInputs := flattenDepsets(bs.InputDepsetIds, actualDepsets)
+
+	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
+		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
+	}
+}
+
+// Returns the contents of given depsets in concatenated post order.
+func flattenDepsets(depsetIdsToFlatten []int, allDepsets []AqueryDepset) []string {
+	depsetsById := map[int]AqueryDepset{}
+	for _, depset := range allDepsets {
+		depsetsById[depset.Id] = depset
+	}
+	result := []string{}
+	for _, depsetId := range depsetIdsToFlatten {
+		result = append(result, flattenDepset(depsetId, depsetsById)...)
+	}
+	return result
+}
+
+// Returns the contents of a given depset in post order.
+func flattenDepset(depsetIdToFlatten int, allDepsets map[int]AqueryDepset) []string {
+	depset := allDepsets[depsetIdToFlatten]
+	result := []string{}
+	for _, depsetId := range depset.TransitiveDepSetIds {
+		result = append(result, flattenDepset(depsetId, allDepsets)...)
+	}
+	result = append(result, depset.DirectArtifacts...)
+	return result
 }
 
 func TestSimpleSymlink(t *testing.T) {
@@ -849,7 +950,7 @@
   }]
 }`
 
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
 
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
@@ -913,7 +1014,7 @@
   }]
 }`
 
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
 
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
@@ -970,7 +1071,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`)
 }
 
@@ -1011,7 +1112,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
 }
 
@@ -1045,7 +1146,7 @@
   }]
 }`
 
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
 
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
@@ -1091,7 +1192,7 @@
   }]
 }`
 
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `Expect 1 output to template expand action, got: output []`)
 }
 
@@ -1211,7 +1312,7 @@
     "label": "python_binary"
   }]
 }`
-	actual, err := AqueryBuildStatements([]byte(inputString))
+	actual, _, err := AqueryBuildStatements([]byte(inputString))
 
 	if err != nil {
 		t.Errorf("Unexpected error %q", err)
@@ -1264,7 +1365,7 @@
     "label": "python_binary.zip"
   }]
 }`
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input [], output ["python_binary.zip"]`)
 }
 
@@ -1360,7 +1461,7 @@
     "parentId": 11
   }]
 }`
-	_, err := AqueryBuildStatements([]byte(inputString))
+	_, _, err := AqueryBuildStatements([]byte(inputString))
 	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input ["../bazel_tools/tools/zip/zipper/zipper" "python_binary.py"], output []`)
 }
 
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index c7f9776..7b76b74 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -329,7 +329,7 @@
 func TestPartitionLabelListAttribute(t *testing.T) {
 	testCases := []struct {
 		name           string
-		ctx            *otherModuleTestContext
+		ctx            *OtherModuleTestContext
 		labelList      LabelListAttribute
 		filters        LabelPartitions
 		expected       PartitionToLabelListAttribute
@@ -337,7 +337,7 @@
 	}{
 		{
 			name: "no configurable values",
-			ctx:  &otherModuleTestContext{},
+			ctx:  &OtherModuleTestContext{},
 			labelList: LabelListAttribute{
 				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
 			},
@@ -354,7 +354,7 @@
 		},
 		{
 			name: "no configurable values, remainder partition",
-			ctx:  &otherModuleTestContext{},
+			ctx:  &OtherModuleTestContext{},
 			labelList: LabelListAttribute{
 				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
 			},
@@ -371,7 +371,7 @@
 		},
 		{
 			name: "no configurable values, empty partition",
-			ctx:  &otherModuleTestContext{},
+			ctx:  &OtherModuleTestContext{},
 			labelList: LabelListAttribute{
 				Value: makeLabelList([]string{"a.a", "c.c"}, []string{}),
 			},
@@ -387,8 +387,8 @@
 		},
 		{
 			name: "no configurable values, has map",
-			ctx: &otherModuleTestContext{
-				modules: []testModuleInfo{testModuleInfo{name: "srcs", typ: "fg", dir: "dir"}},
+			ctx: &OtherModuleTestContext{
+				Modules: []TestModuleInfo{{ModuleName: "srcs", Typ: "fg", Dir: "dir"}},
 			},
 			labelList: LabelListAttribute{
 				Value: makeLabelList([]string{"a.a", "srcs", "b.b", "c.c"}, []string{}),
@@ -406,7 +406,7 @@
 		},
 		{
 			name: "configurable values, keeps empty if excludes",
-			ctx:  &otherModuleTestContext{},
+			ctx:  &OtherModuleTestContext{},
 			labelList: LabelListAttribute{
 				ConfigurableValues: configurableLabelLists{
 					ArchConfigurationAxis: labelListSelectValues{
@@ -450,7 +450,7 @@
 		},
 		{
 			name: "error for multiple partitions same value",
-			ctx:  &otherModuleTestContext{},
+			ctx:  &OtherModuleTestContext{},
 			labelList: LabelListAttribute{
 				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
 			},
diff --git a/bazel/testing.go b/bazel/testing.go
index 23c8350..9a43b61 100644
--- a/bazel/testing.go
+++ b/bazel/testing.go
@@ -20,86 +20,86 @@
 	"github.com/google/blueprint"
 )
 
-// testModuleInfo implements blueprint.Module interface with sufficient information to mock a subset of
+// TestModuleInfo implements blueprint.Module interface with sufficient information to mock a subset of
 // a blueprint ModuleContext
-type testModuleInfo struct {
-	name string
-	typ  string
-	dir  string
+type TestModuleInfo struct {
+	ModuleName string
+	Typ        string
+	Dir        string
 }
 
 // Name returns name for testModuleInfo -- required to implement blueprint.Module
-func (mi testModuleInfo) Name() string {
-	return mi.name
+func (mi TestModuleInfo) Name() string {
+	return mi.ModuleName
 }
 
 // GenerateBuildActions unused, but required to implmeent blueprint.Module
-func (mi testModuleInfo) GenerateBuildActions(blueprint.ModuleContext) {}
+func (mi TestModuleInfo) GenerateBuildActions(blueprint.ModuleContext) {}
 
-func (mi testModuleInfo) equals(other testModuleInfo) bool {
-	return mi.name == other.name && mi.typ == other.typ && mi.dir == other.dir
+func (mi TestModuleInfo) equals(other TestModuleInfo) bool {
+	return mi.ModuleName == other.ModuleName && mi.Typ == other.Typ && mi.Dir == other.Dir
 }
 
 // ensure testModuleInfo implements blueprint.Module
-var _ blueprint.Module = testModuleInfo{}
+var _ blueprint.Module = TestModuleInfo{}
 
-// otherModuleTestContext is a mock context that implements OtherModuleContext
-type otherModuleTestContext struct {
-	modules []testModuleInfo
+// OtherModuleTestContext is a mock context that implements OtherModuleContext
+type OtherModuleTestContext struct {
+	Modules []TestModuleInfo
 	errors  []string
 }
 
 // ModuleFromName retrieves the testModuleInfo corresponding to name, if it exists
-func (omc *otherModuleTestContext) ModuleFromName(name string) (blueprint.Module, bool) {
-	for _, m := range omc.modules {
-		if m.name == name {
+func (omc *OtherModuleTestContext) ModuleFromName(name string) (blueprint.Module, bool) {
+	for _, m := range omc.Modules {
+		if m.ModuleName == name {
 			return m, true
 		}
 	}
-	return testModuleInfo{}, false
+	return TestModuleInfo{}, false
 }
 
 // testModuleInfo returns the testModuleInfo corresponding to a blueprint.Module if it exists in omc
-func (omc *otherModuleTestContext) testModuleInfo(m blueprint.Module) (testModuleInfo, bool) {
-	mi, ok := m.(testModuleInfo)
+func (omc *OtherModuleTestContext) testModuleInfo(m blueprint.Module) (TestModuleInfo, bool) {
+	mi, ok := m.(TestModuleInfo)
 	if !ok {
-		return testModuleInfo{}, false
+		return TestModuleInfo{}, false
 	}
-	for _, other := range omc.modules {
+	for _, other := range omc.Modules {
 		if other.equals(mi) {
 			return mi, true
 		}
 	}
-	return testModuleInfo{}, false
+	return TestModuleInfo{}, false
 }
 
 // OtherModuleType returns type of m if it exists in omc
-func (omc *otherModuleTestContext) OtherModuleType(m blueprint.Module) string {
+func (omc *OtherModuleTestContext) OtherModuleType(m blueprint.Module) string {
 	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.typ
+		return mi.Typ
 	}
 	return ""
 }
 
 // OtherModuleName returns name of m if it exists in omc
-func (omc *otherModuleTestContext) OtherModuleName(m blueprint.Module) string {
+func (omc *OtherModuleTestContext) OtherModuleName(m blueprint.Module) string {
 	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.name
+		return mi.ModuleName
 	}
 	return ""
 }
 
 // OtherModuleDir returns dir of m if it exists in omc
-func (omc *otherModuleTestContext) OtherModuleDir(m blueprint.Module) string {
+func (omc *OtherModuleTestContext) OtherModuleDir(m blueprint.Module) string {
 	if mi, ok := omc.testModuleInfo(m); ok {
-		return mi.dir
+		return mi.Dir
 	}
 	return ""
 }
 
-func (omc *otherModuleTestContext) ModuleErrorf(format string, args ...interface{}) {
+func (omc *OtherModuleTestContext) ModuleErrorf(format string, args ...interface{}) {
 	omc.errors = append(omc.errors, fmt.Sprintf(format, args...))
 }
 
 // Ensure otherModuleTestContext implements OtherModuleContext
-var _ OtherModuleContext = &otherModuleTestContext{}
+var _ OtherModuleContext = &OtherModuleTestContext{}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 8a171d4..e0ce194 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -18,6 +18,7 @@
     ],
     deps: [
         "soong-android",
+        "soong-android-allowlists",
         "soong-android-soongconfig",
         "soong-shared",
         "soong-apex",
@@ -44,7 +45,9 @@
         "cc_library_shared_conversion_test.go",
         "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
+        "cc_prebuilt_library_conversion_test.go",
         "cc_prebuilt_library_shared_test.go",
+        "cc_prebuilt_library_static_test.go",
         "conversion_test.go",
         "filegroup_conversion_test.go",
         "genrule_conversion_test.go",
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index 3824586..a216c9d 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -74,7 +74,8 @@
         package_name: "com.google",
         resource_dirs: ["resa", "resb"],
         manifest: "manifest/AndroidManifest.xml",
-        static_libs: ["static_lib_dep"]
+        static_libs: ["static_lib_dep"],
+        java_version: "7",
 }
 `,
 		expectedBazelTargets: []string{
@@ -87,6 +88,7 @@
     ]`,
 				"custom_package": `"com.google"`,
 				"deps":           `[":static_lib_dep"]`,
+				"javacopts":      `["-source 1.7 -target 1.7"]`,
 			}),
 		}})
 }
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
index 9057189..3ed21db 100644
--- a/bp2build/apex_conversion_test.go
+++ b/bp2build/apex_conversion_test.go
@@ -41,9 +41,27 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 }
 
+func runOverrideApexTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerOverrideApexModuleTypes, tc)
+}
+
+func registerOverrideApexModuleTypes(ctx android.RegistrationContext) {
+	// CC module types needed as they can be APEX dependencies
+	cc.RegisterCCBuildComponents(ctx)
+
+	ctx.RegisterModuleType("sh_binary", sh.ShBinaryFactory)
+	ctx.RegisterModuleType("cc_binary", cc.BinaryFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+	ctx.RegisterModuleType("apex_key", apex.ApexKeyFactory)
+	ctx.RegisterModuleType("android_app_certificate", java.AndroidAppCertificateFactory)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("apex", apex.BundleFactory)
+}
+
 func TestApexBundleSimple(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
-		description:                "apex - example with all props",
+		description:                "apex - example with all props, file_context is a module in same Android.bp",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
 		filesystem:                 map[string]string{},
@@ -71,13 +89,11 @@
 	bazel_module: { bp2build_available: false },
 }
 
-// TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
 	name: "pretend_prebuilt_1",
 	bazel_module: { bp2build_available: false },
 }
 
-// TODO(b/194878861): Add bp2build support for prebuilt_etc
 cc_library {
 	name: "pretend_prebuilt_2",
 	bazel_module: { bp2build_available: false },
@@ -86,7 +102,7 @@
 filegroup {
 	name: "com.android.apogee-file_contexts",
 	srcs: [
-			"com.android.apogee-file_contexts",
+		"com.android.apogee-file_contexts",
 	],
 	bazel_module: { bp2build_available: false },
 }
@@ -98,7 +114,7 @@
 	name: "com.android.apogee",
 	manifest: "apogee_manifest.json",
 	androidManifest: "ApogeeAndroidManifest.xml",
-	file_contexts: "com.android.apogee-file_contexts",
+	file_contexts: ":com.android.apogee-file_contexts",
 	min_sdk_version: "29",
 	key: "com.android.apogee.key",
 	certificate: "com.android.apogee.certificate",
@@ -157,13 +173,97 @@
 		}})
 }
 
+func TestApexBundleSimple_fileContextsInAnotherAndroidBp(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is a module in another Android.bp",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem: map[string]string{
+			"a/b/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+	file_contexts: ":com.android.apogee-file_contexts",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"//a/b:com.android.apogee-file_contexts"`,
+			}),
+		}})
+}
+
+func TestApexBundleSimple_fileContextsIsFile(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is a file",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+	file_contexts: "file_contexts_file",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"file_contexts_file"`,
+			}),
+		}})
+}
+
+func TestApexBundleSimple_fileContextsIsNotSpecified(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - file contexts is not specified",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
+			}),
+		}})
+}
+
 func TestApexBundleCompileMultilibBoth(t *testing.T) {
 	runApexTestCase(t, bp2buildTestCase{
 		description:                "apex - example with compile_multilib=both",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("both"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("both"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `[
@@ -187,6 +287,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -196,8 +297,16 @@
 		description:                "apex - example with compile_multilib=first",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("first"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("first"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `select({
@@ -226,6 +335,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -235,8 +345,16 @@
 		description:                "apex - example with compile_multilib=32",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("32"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("32"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_32": `[
@@ -247,6 +365,7 @@
         "//build/bazel/platforms/arch:x86": [":native_shared_lib_2"],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -256,8 +375,16 @@
 		description:                "apex - example with compile_multilib=64",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
-		blueprint:                  createMultilibBlueprint("64"),
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
+		blueprint: createMultilibBlueprint("64"),
 		expectedBazelTargets: []string{
 			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
 				"native_shared_libs_64": `select({
@@ -273,6 +400,7 @@
         ],
         "//conditions:default": [],
     })`,
+				"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 			}),
 		}})
 }
@@ -282,7 +410,15 @@
 		description:                "apex - default property values",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
 		blueprint: `
 apex {
 	name: "com.android.apogee",
@@ -290,7 +426,8 @@
 }
 `,
 		expectedBazelTargets: []string{makeBazelTarget("apex", "com.android.apogee", attrNameToString{
-			"manifest": `"apogee_manifest.json"`,
+			"manifest":      `"apogee_manifest.json"`,
+			"file_contexts": `"//system/sepolicy/apex:com.android.apogee-file_contexts"`,
 		}),
 		}})
 }
@@ -300,7 +437,15 @@
 		description:                "apex - has bazel module props",
 		moduleTypeUnderTest:        "apex",
 		moduleTypeUnderTestFactory: apex.BundleFactory,
-		filesystem:                 map[string]string{},
+		filesystem: map[string]string{
+			"system/sepolicy/apex/Android.bp": `
+filegroup {
+	name: "apogee-file_contexts",
+	srcs: [ "apogee-file_contexts", ],
+	bazel_module: { bp2build_available: false },
+}
+`,
+		},
 		blueprint: `
 apex {
 	name: "apogee",
@@ -309,7 +454,8 @@
 }
 `,
 		expectedBazelTargets: []string{makeBazelTarget("apex", "apogee", attrNameToString{
-			"manifest": `"manifest.json"`,
+			"manifest":      `"manifest.json"`,
+			"file_contexts": `"//system/sepolicy/apex:apogee-file_contexts"`,
 		}),
 		}})
 }
@@ -363,3 +509,137 @@
 	},
 }`
 }
+
+func TestBp2BuildOverrideApex(t *testing.T) {
+	runOverrideApexTestCase(t, bp2buildTestCase{
+		description:                "override_apex",
+		moduleTypeUnderTest:        "override_apex",
+		moduleTypeUnderTestFactory: apex.OverrideApexFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex_key {
+	name: "com.android.apogee.key",
+	public_key: "com.android.apogee.avbpubkey",
+	private_key: "com.android.apogee.pem",
+	bazel_module: { bp2build_available: false },
+}
+
+android_app_certificate {
+	name: "com.android.apogee.certificate",
+	certificate: "com.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_1",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_2",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "pretend_prebuilt_1",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "pretend_prebuilt_2",
+	bazel_module: { bp2build_available: false },
+}
+
+filegroup {
+	name: "com.android.apogee-file_contexts",
+	srcs: [
+		"com.android.apogee-file_contexts",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+cc_binary { name: "cc_binary_1", bazel_module: { bp2build_available: false } }
+sh_binary { name: "sh_binary_2", bazel_module: { bp2build_available: false } }
+
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+	androidManifest: "ApogeeAndroidManifest.xml",
+	file_contexts: ":com.android.apogee-file_contexts",
+	min_sdk_version: "29",
+	key: "com.android.apogee.key",
+	certificate: "com.android.apogee.certificate",
+	updatable: false,
+	installable: false,
+	compressible: false,
+	native_shared_libs: [
+	    "native_shared_lib_1",
+	    "native_shared_lib_2",
+	],
+	binaries: [
+		"cc_binary_1",
+		"sh_binary_2",
+	],
+	prebuilts: [
+	    "pretend_prebuilt_1",
+	    "pretend_prebuilt_2",
+	],
+	bazel_module: { bp2build_available: false },
+}
+
+apex_key {
+	name: "com.google.android.apogee.key",
+	public_key: "com.google.android.apogee.avbpubkey",
+	private_key: "com.google.android.apogee.pem",
+	bazel_module: { bp2build_available: false },
+}
+
+android_app_certificate {
+	name: "com.google.android.apogee.certificate",
+	certificate: "com.google.android.apogee",
+	bazel_module: { bp2build_available: false },
+}
+
+override_apex {
+	name: "com.google.android.apogee",
+	base: ":com.android.apogee",
+	key: "com.google.android.apogee.key",
+	certificate: "com.google.android.apogee.certificate",
+	prebuilts: [],
+	compressible: true,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.google.android.apogee", attrNameToString{
+				"android_manifest": `"ApogeeAndroidManifest.xml"`,
+				"binaries": `[
+        ":cc_binary_1",
+        ":sh_binary_2",
+    ]`,
+				"certificate":     `":com.google.android.apogee.certificate"`,
+				"file_contexts":   `":com.android.apogee-file_contexts"`,
+				"installable":     "False",
+				"key":             `":com.google.android.apogee.key"`,
+				"manifest":        `"apogee_manifest.json"`,
+				"min_sdk_version": `"29"`,
+				"native_shared_libs_32": `[
+        ":native_shared_lib_1",
+        ":native_shared_lib_2",
+    ]`,
+				"native_shared_libs_64": `select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+				"prebuilts":    `[]`,
+				"updatable":    "False",
+				"compressible": "True",
+			}),
+		}})
+}
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index b21a477..0f3ca79 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -20,6 +20,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/android/allowlists"
 	"android/soong/python"
 )
 
@@ -922,7 +923,7 @@
 		moduleTypeUnderTestFactory android.ModuleFactory
 		expectedCount              map[string]int
 		description                string
-		bp2buildConfig             android.Bp2BuildConfig
+		bp2buildConfig             allowlists.Bp2BuildConfig
 		checkDir                   string
 		fs                         map[string]string
 	}{
@@ -937,10 +938,10 @@
 				"not_migrated":                       0,
 				"also_not_migrated":                  0,
 			},
-			bp2buildConfig: android.Bp2BuildConfig{
-				"migrated":                android.Bp2BuildDefaultTrueRecursively,
-				"migrated/but_not_really": android.Bp2BuildDefaultFalse,
-				"not_migrated":            android.Bp2BuildDefaultFalse,
+			bp2buildConfig: allowlists.Bp2BuildConfig{
+				"migrated":                allowlists.Bp2BuildDefaultTrueRecursively,
+				"migrated/but_not_really": allowlists.Bp2BuildDefaultFalse,
+				"not_migrated":            allowlists.Bp2BuildDefaultFalse,
 			},
 			fs: map[string]string{
 				"migrated/Android.bp":                           `filegroup { name: "a" }`,
@@ -960,9 +961,9 @@
 				"package-opt-out":            1,
 				"package-opt-out/subpackage": 0,
 			},
-			bp2buildConfig: android.Bp2BuildConfig{
-				"package-opt-in":  android.Bp2BuildDefaultFalse,
-				"package-opt-out": android.Bp2BuildDefaultTrueRecursively,
+			bp2buildConfig: allowlists.Bp2BuildConfig{
+				"package-opt-in":  allowlists.Bp2BuildDefaultFalse,
+				"package-opt-out": allowlists.Bp2BuildDefaultTrueRecursively,
 			},
 			fs: map[string]string{
 				"package-opt-in/Android.bp": `
@@ -1004,7 +1005,8 @@
 		config := android.TestConfig(buildDir, nil, "", fs)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildConfig(testCase.bp2buildConfig)
+		allowlist := android.NewBp2BuildAllowlist().SetDefaultConfig(testCase.bp2buildConfig)
+		ctx.RegisterBp2BuildConfig(allowlist)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
index 17337f0..037564b 100644
--- a/bp2build/cc_binary_conversion_test.go
+++ b/bp2build/cc_binary_conversion_test.go
@@ -130,6 +130,7 @@
     },
     sdk_version: "current",
     min_sdk_version: "29",
+    use_version_lib: true,
 }
 `,
 		targets: []testBazelTarget{
@@ -153,8 +154,9 @@
         "keep_symbols_list": ["symbol"],
         "none": True,
     }`,
-        "sdk_version": `"current"`,
-        "min_sdk_version": `"29"`,
+				"sdk_version":     `"current"`,
+				"min_sdk_version": `"29"`,
+				"use_version_lib": `True`,
 			},
 			},
 		},
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 5767861..ab92981 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -117,6 +117,7 @@
     include_build_directory: false,
     sdk_version: "current",
     min_sdk_version: "29",
+    use_version_lib: true,
 }
 `,
 		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
@@ -142,8 +143,9 @@
         "//build/bazel/platforms/os:linux_bionic": ["bionic.cpp"],
         "//conditions:default": [],
     })`,
-      "sdk_version": `"current"`,
-      "min_sdk_version": `"29"`,
+			"sdk_version":     `"current"`,
+			"min_sdk_version": `"29"`,
+			"use_version_lib": `True`,
 		}),
 	})
 }
@@ -1912,9 +1914,9 @@
 		{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: "experimental", gnu_extensions: "", bazel_c_std: "gnu17"},
+		{c_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c17"},
+		{c_std: "experimental", gnu_extensions: "true", bazel_c_std: "gnu17"},
 		{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"},
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
index 22c9dfe..7c2c100 100644
--- a/bp2build/cc_library_shared_conversion_test.go
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -176,8 +176,8 @@
         ":whole_static_lib_1",
         ":whole_static_lib_2",
     ]`,
-        "sdk_version": `"current"`,
-        "min_sdk_version": `"29"`,
+				"sdk_version":     `"current"`,
+				"min_sdk_version": `"29"`,
 			}),
 		},
 	})
@@ -496,3 +496,27 @@
 	},
 	)
 }
+
+func TestCcLibrarySharedSystemSharedLibsSharedEmpty(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                "cc_library_shared system_shared_libs empty shared default",
+		moduleTypeUnderTest:        "cc_library_shared",
+		moduleTypeUnderTestFactory: cc.LibrarySharedFactory,
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_defaults {
+    name: "empty_defaults",
+    shared: {
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
+}
+cc_library_shared {
+    name: "empty",
+    defaults: ["empty_defaults"],
+}
+`,
+		expectedBazelTargets: []string{makeBazelTarget("cc_library_shared", "empty", attrNameToString{
+			"system_dynamic_deps": "[]",
+		})},
+	})
+}
diff --git a/bp2build/cc_prebuilt_library_conversion_test.go b/bp2build/cc_prebuilt_library_conversion_test.go
new file mode 100644
index 0000000..3cf8969
--- /dev/null
+++ b/bp2build/cc_prebuilt_library_conversion_test.go
@@ -0,0 +1,250 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/cc"
+)
+
+func TestPrebuiltLibraryStaticAndSharedSimple(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library static and shared simple",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	srcs: ["libf.so"],
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest_bp2build_cc_library_static", attrNameToString{
+					"static_library": `"libf.so"`,
+				}),
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `"libf.so"`,
+				}),
+			},
+		})
+}
+
+func TestPrebuiltLibraryWithArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library with arch variance",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	arch: {
+		arm64: { srcs: ["libf.so"], },
+		arm: { srcs: ["libg.so"], },
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest_bp2build_cc_library_static", attrNameToString{
+					"static_library": `select({
+        "//build/bazel/platforms/arch:arm": "libg.so",
+        "//build/bazel/platforms/arch:arm64": "libf.so",
+        "//conditions:default": None,
+    })`,
+				}),
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `select({
+        "//build/bazel/platforms/arch:arm": "libg.so",
+        "//build/bazel/platforms/arch:arm64": "libf.so",
+        "//conditions:default": None,
+    })`,
+				}),
+			},
+		})
+}
+
+func TestPrebuiltLibraryAdditionalAttrs(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library additional attributes",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so":    "",
+				"testdir/1/": "",
+				"testdir/2/": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	srcs: ["libf.so"],
+	export_include_dirs: ["testdir/1/"],
+	export_system_include_dirs: ["testdir/2/"],
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest_bp2build_cc_library_static", attrNameToString{
+					"static_library":         `"libf.so"`,
+					"export_includes":        `["testdir/1/"]`,
+					"export_system_includes": `["testdir/2/"]`,
+				}),
+				// TODO(b/229374533): When fixed, update this test
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `"libf.so"`,
+				}),
+			},
+		})
+}
+
+func TestPrebuiltLibrarySharedStanzaFails(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library with shared stanza fails because multiple sources",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	srcs: ["libf.so"],
+	shared: {
+		srcs: ["libg.so"],
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedErr: fmt.Errorf("Expected at most one source file"),
+		})
+}
+
+func TestPrebuiltLibraryStaticStanzaFails(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library with static stanza fails because multiple sources",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	srcs: ["libf.so"],
+	static: {
+		srcs: ["libg.so"],
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedErr: fmt.Errorf("Expected at most one source file"),
+		})
+}
+
+func TestPrebuiltLibrarySharedAndStaticStanzas(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library with both shared and static stanzas",
+			moduleTypeUnderTest:        "cc_prebuilt_library",
+			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library {
+	name: "libtest",
+	static: {
+		srcs: ["libf.so"],
+	},
+	shared: {
+		srcs: ["libg.so"],
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest_bp2build_cc_library_static", attrNameToString{
+					"static_library": `"libf.so"`,
+				}),
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `"libg.so"`,
+				}),
+			},
+		})
+}
+
+// TODO(b/228623543): When this bug is fixed, enable this test
+//func TestPrebuiltLibraryOnlyShared(t *testing.T) {
+//	runBp2BuildTestCaseSimple(t,
+//		bp2buildTestCase{
+//			description:                "prebuilt library shared only",
+//			moduleTypeUnderTest:        "cc_prebuilt_library",
+//			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+//			filesystem: map[string]string{
+//				"libf.so": "",
+//			},
+//			blueprint: `
+//cc_prebuilt_library {
+//	name: "libtest",
+//	srcs: ["libf.so"],
+//	static: {
+//		enabled: false,
+//	},
+//	bazel_module: { bp2build_available: true },
+//}`,
+//			expectedBazelTargets: []string{
+//				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+//					"shared_library": `"libf.so"`,
+//				}),
+//			},
+//		})
+//}
+
+// TODO(b/228623543): When this bug is fixed, enable this test
+//func TestPrebuiltLibraryOnlyStatic(t *testing.T) {
+//	runBp2BuildTestCaseSimple(t,
+//		bp2buildTestCase{
+//			description:                "prebuilt library static only",
+//			moduleTypeUnderTest:        "cc_prebuilt_library",
+//			moduleTypeUnderTestFactory: cc.PrebuiltLibraryFactory,
+//			filesystem: map[string]string{
+//				"libf.so": "",
+//			},
+//			blueprint: `
+//cc_prebuilt_library {
+//	name: "libtest",
+//	srcs: ["libf.so"],
+//	shared: {
+//		enabled: false,
+//	},
+//	bazel_module: { bp2build_available: true },
+//}`,
+//			expectedBazelTargets: []string{
+//				makeBazelTarget("prebuilt_library_static", "libtest_bp2build_cc_library_static", attrNameToString{
+//					"static_library": `"libf.so"`,
+//				}),
+//			},
+//		})
+//}
diff --git a/bp2build/cc_prebuilt_library_shared_test.go b/bp2build/cc_prebuilt_library_shared_test.go
index ef2fddc..57905e5 100644
--- a/bp2build/cc_prebuilt_library_shared_test.go
+++ b/bp2build/cc_prebuilt_library_shared_test.go
@@ -1,6 +1,7 @@
 package bp2build
 
 import (
+	"fmt"
 	"testing"
 
 	"android/soong/cc"
@@ -59,3 +60,26 @@
 			},
 		})
 }
+
+func TestSharedPrebuiltLibrarySharedStanzaFails(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library shared with shared stanza fails because multiple sources",
+			moduleTypeUnderTest:        "cc_prebuilt_library_shared",
+			moduleTypeUnderTestFactory: cc.PrebuiltSharedLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_shared {
+	name: "libtest",
+	srcs: ["libf.so"],
+	shared: {
+		srcs: ["libg.so"],
+	},
+	bazel_module: { bp2build_available: true},
+}`,
+			expectedErr: fmt.Errorf("Expected at most one source file"),
+		})
+}
diff --git a/bp2build/cc_prebuilt_library_static_test.go b/bp2build/cc_prebuilt_library_static_test.go
new file mode 100644
index 0000000..3feb1f1
--- /dev/null
+++ b/bp2build/cc_prebuilt_library_static_test.go
@@ -0,0 +1,98 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/cc"
+)
+
+func TestStaticPrebuiltLibrary(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library static simple",
+			moduleTypeUnderTest:        "cc_prebuilt_library_static",
+			moduleTypeUnderTestFactory: cc.PrebuiltStaticLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_static {
+	name: "libtest",
+	srcs: ["libf.so"],
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest", attrNameToString{
+					"static_library": `"libf.so"`,
+				}),
+			},
+		})
+}
+
+func TestStaticPrebuiltLibraryWithArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library static with arch variance",
+			moduleTypeUnderTest:        "cc_prebuilt_library_static",
+			moduleTypeUnderTestFactory: cc.PrebuiltStaticLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_static {
+	name: "libtest",
+	arch: {
+		arm64: { srcs: ["libf.so"], },
+		arm: { srcs: ["libg.so"], },
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_static", "libtest", attrNameToString{
+					"static_library": `select({
+        "//build/bazel/platforms/arch:arm": "libg.so",
+        "//build/bazel/platforms/arch:arm64": "libf.so",
+        "//conditions:default": None,
+    })`,
+				}),
+			},
+		})
+}
+
+func TestStaticPrebuiltLibraryStaticStanzaFails(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library with static stanza fails because multiple sources",
+			moduleTypeUnderTest:        "cc_prebuilt_library_static",
+			moduleTypeUnderTestFactory: cc.PrebuiltStaticLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_static {
+	name: "libtest",
+	srcs: ["libf.so"],
+	static: {
+		srcs: ["libg.so"],
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedErr: fmt.Errorf("Expected at most one source file"),
+		})
+}
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index 4fc07e0..d7a76a8 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -52,6 +52,7 @@
     jni_libs: ["jni-lib-1"],
     javacflags: ["-Xdoclint:all/protected"],
     bazel_module: { bp2build_available: true },
+    java_version: "8",
 }`,
 		expectedBazelTargets: []string{
 			makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
@@ -59,7 +60,10 @@
 				"main_class": `"com.android.test.MainClass"`,
 				"deps":       `["//other:jni-lib-1"]`,
 				"jvm_flags":  `["-Djava.library.path=$${RUNPATH}other"]`,
-				"javacopts":  `["-Xdoclint:all/protected"]`,
+				"javacopts": `[
+        "-Xdoclint:all/protected",
+        "-source 1.8 -target 1.8",
+    ]`,
 				"target_compatible_with": `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index ccc52ef..e4d9cbc 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -158,6 +158,22 @@
 	})
 }
 
+func TestJavaLibraryJavaVersion(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    java_version: "11",
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"srcs":      `["a.java"]`,
+				"javacopts": `["-source 11 -target 11"]`,
+			}),
+		},
+	})
+}
+
 func TestJavaLibraryErrorproneJavacflagsEnabledManually(t *testing.T) {
 	runJavaLibraryTestCase(t, bp2buildTestCase{
 		blueprint: `java_library {
@@ -251,3 +267,108 @@
 			}),
 		}})
 }
+
+func TestJavaLibraryResources(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":      "",
+			"res/b.res":      "",
+			"res/dir1/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resources: ["res/a.res", "res/b.res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resources": `[
+        "res/a.res",
+        "res/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourceDirs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":      "",
+			"res/b.res":      "",
+			"res/dir1/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources": `[
+        "res/a.res",
+        "res/b.res",
+        "res/dir1/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesExcludeDir(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":         "",
+			"res/exclude/b.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+	exclude_java_resource_dirs: ["res/exclude"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources":             `["res/a.res"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesExcludeFile(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":            "",
+			"res/dir1/b.res":       "",
+			"res/dir1/exclude.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res"],
+	exclude_java_resources: ["res/dir1/exclude.res"],
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"resource_strip_prefix": `"res"`,
+				"resources": `[
+        "res/a.res",
+        "res/dir1/b.res",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryResourcesFailsWithMultipleDirs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		filesystem: map[string]string{
+			"res/a.res":  "",
+			"res1/a.res": "",
+		},
+		blueprint: `java_library {
+    name: "java-lib-1",
+	java_resource_dirs: ["res", "res1"],
+}`,
+		expectedErr:          fmt.Errorf("bp2build does not support more than one directory in java_resource_dirs (b/226423379)"),
+		expectedBazelTargets: []string{},
+	})
+}
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
index 73abdd2..83cc551 100644
--- a/bp2build/java_library_host_conversion_test.go
+++ b/bp2build/java_library_host_conversion_test.go
@@ -43,6 +43,7 @@
     name: "java-lib-host-2",
     srcs: ["c.java"],
     bazel_module: { bp2build_available: true },
+    java_version: "9",
 }`,
 		expectedBazelTargets: []string{
 			makeBazelTarget("java_library", "java-lib-host-1", attrNameToString{
@@ -54,7 +55,8 @@
     })`,
 			}),
 			makeBazelTarget("java_library", "java-lib-host-2", attrNameToString{
-				"srcs": `["c.java"]`,
+				"javacopts": `["-source 1.9 -target 1.9"]`,
+				"srcs":      `["c.java"]`,
 				"target_compatible_with": `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
index c2a2182..dc763e7 100644
--- a/bp2build/java_plugin_conversion_test.go
+++ b/bp2build/java_plugin_conversion_test.go
@@ -39,6 +39,7 @@
     libs: ["java-lib-1"],
     static_libs: ["java-lib-2"],
     bazel_module: { bp2build_available: true },
+    java_version: "7",
 }
 
 java_library {
@@ -66,6 +67,7 @@
         "a.java",
         "b.java",
     ]`,
+				"javacopts": `["-source 1.7 -target 1.7"]`,
 			}),
 		},
 	})
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
index 67f8044..c6feeb8 100644
--- a/bp2build/java_proto_conversion_test.go
+++ b/bp2build/java_proto_conversion_test.go
@@ -102,6 +102,7 @@
 		blueprint: `java_library_static {
     name: "java-protos",
     srcs: ["a.proto"],
+    java_version: "7",
 }
 `,
 		expectedBazelTargets: []string{
@@ -115,7 +116,8 @@
 					"deps": `[":java-protos_proto"]`,
 				}),
 			makeBazelTarget("java_library", "java-protos", attrNameToString{
-				"exports": `[":java-protos_java_proto_lite"]`,
+				"exports":   `[":java-protos_java_proto_lite"]`,
+				"javacopts": `["-source 1.7 -target 1.7"]`,
 			}),
 		},
 	})
diff --git a/bp2build/prebuilt_etc_conversion_test.go b/bp2build/prebuilt_etc_conversion_test.go
index 3a5d5bb..2e4b221 100644
--- a/bp2build/prebuilt_etc_conversion_test.go
+++ b/bp2build/prebuilt_etc_conversion_test.go
@@ -45,11 +45,11 @@
 }
 `,
 		expectedBazelTargets: []string{
-			makeBazelTarget("prebuilt_etc", "apex_tz_version", attrNameToString{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
 				"filename":    `"tz_version"`,
 				"installable": `False`,
 				"src":         `"version/tz_version"`,
-				"sub_dir":     `"tz"`,
+				"dir":         `"etc/tz"`,
 			})}})
 }
 
@@ -75,7 +75,7 @@
 }
 `,
 		expectedBazelTargets: []string{
-			makeBazelTarget("prebuilt_etc", "apex_tz_version", attrNameToString{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
 				"filename":    `"tz_version"`,
 				"installable": `False`,
 				"src": `select({
@@ -83,7 +83,7 @@
         "//build/bazel/platforms/arch:arm64": "arm64",
         "//conditions:default": "version/tz_version",
     })`,
-				"sub_dir": `"tz"`,
+				"dir": `"etc/tz"`,
 			})}})
 }
 
@@ -114,7 +114,7 @@
 }
 `,
 		expectedBazelTargets: []string{
-			makeBazelTarget("prebuilt_etc", "apex_tz_version", attrNameToString{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
 				"filename":    `"tz_version"`,
 				"installable": `False`,
 				"src": `select({
@@ -125,6 +125,59 @@
         "//build/bazel/platforms/os_arch:linux_bionic_arm64": "darwin_or_arm64",
         "//conditions:default": "version/tz_version",
     })`,
-				"sub_dir": `"tz"`,
+				"dir": `"etc/tz"`,
+			})}})
+}
+
+func runPrebuiltUsrShareTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "prebuilt_usr_share"
+	(&tc).moduleTypeUnderTestFactory = etc.PrebuiltUserShareFactory
+	runBp2BuildTestCase(t, registerPrebuiltEtcModuleTypes, tc)
+}
+
+func registerPrebuiltUsrShareModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestPrebuiltUsrShareSimple(t *testing.T) {
+	runPrebuiltUsrShareTestCase(t, bp2buildTestCase{
+		description: "prebuilt_usr_share - simple example",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_usr_share {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    sub_dir: "tz",
+    installable: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src":         `"version/tz_version"`,
+				"dir":         `"usr/share/tz"`,
+			})}})
+}
+
+func TestPrebuiltEtcNoSubdir(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description: "prebuilt_etc - no subdir",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    installable: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src":         `"version/tz_version"`,
+				"dir":         `"etc"`,
 			})}})
 }
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 15a6335..7d48191 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -90,6 +90,20 @@
 	}
 }
 
+func isDir(path string, fi os.FileInfo) bool {
+	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+		return fi.IsDir()
+	}
+
+	fi2, err := os.Stat(path)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Cannot stat '%s': %s\n", path, err)
+		os.Exit(1)
+	}
+
+	return fi2.IsDir()
+}
+
 // Recursively plants a symlink forest at forestDir. The symlink tree will
 // contain every file in buildFilesDir and srcDir excluding the files in
 // exclude. Collects every directory encountered during the traversal of srcDir
@@ -145,8 +159,18 @@
 			continue
 		}
 
+		sDir := false
+		bDir := false
+		if sExists {
+			sDir = isDir(shared.JoinPath(topdir, srcChild), srcChildEntry)
+		}
+
+		if bExists {
+			bDir = isDir(shared.JoinPath(topdir, buildFilesChild), buildFilesChildEntry)
+		}
+
 		if !sExists {
-			if buildFilesChildEntry.IsDir() && excludeChild != nil {
+			if bDir && excludeChild != nil {
 				// Not in the source tree, but we have to exclude something from under
 				// this subtree, so descend
 				plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
@@ -155,7 +179,7 @@
 				symlinkIntoForest(topdir, forestChild, buildFilesChild)
 			}
 		} else if !bExists {
-			if srcChildEntry.IsDir() && excludeChild != nil {
+			if sDir && excludeChild != nil {
 				// Not in the build file tree, but we have to exclude something from
 				// under this subtree, so descend
 				plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
@@ -163,10 +187,10 @@
 				// Not in the build file tree, symlink source tree, carry on
 				symlinkIntoForest(topdir, forestChild, srcChild)
 			}
-		} else if srcChildEntry.IsDir() && buildFilesChildEntry.IsDir() {
+		} else if sDir && bDir {
 			// Both are directories. Descend.
 			plantSymlinkForestRecursive(topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
-		} else if !srcChildEntry.IsDir() && !buildFilesChildEntry.IsDir() {
+		} else if !sDir && !bDir {
 			// Neither is a directory. Prioritize BUILD files generated by bp2build
 			// over any BUILD file imported into external/.
 			fmt.Fprintf(os.Stderr, "Both '%s' and '%s' exist, symlinking the former to '%s'\n",
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 53b60fa..029ba49 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -25,14 +25,17 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/android/allowlists"
 	"android/soong/bazel"
 )
 
 var (
 	// A default configuration for tests to not have to specify bp2build_available on top level targets.
-	bp2buildConfig = android.Bp2BuildConfig{
-		android.BP2BUILD_TOPLEVEL: android.Bp2BuildDefaultTrueRecursively,
-	}
+	bp2buildConfig = android.NewBp2BuildAllowlist().SetDefaultConfig(
+		allowlists.Bp2BuildConfig{
+			android.Bp2BuildTopLevel: allowlists.Bp2BuildDefaultTrueRecursively,
+		},
+	)
 
 	buildDir string
 )
@@ -81,8 +84,9 @@
 	expectedBazelTargets       []string
 	filesystem                 map[string]string
 	dir                        string
-	expectedErr                error
-	unconvertedDepsMode        unconvertedDepsMode
+	// An error with a string contained within the string of the expected error
+	expectedErr         error
+	unconvertedDepsMode unconvertedDepsMode
 }
 
 func runBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc bp2buildTestCase) {
diff --git a/build_kzip.bash b/build_kzip.bash
index aff2d6d..6219021 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -36,7 +36,7 @@
 declare -r out="${OUT_DIR:-out}"
 
 # Build extraction files for C++ and Java. Build `merge_zips` which we use later.
-build/soong/soong_ui.bash --build-mode --all-modules --dir=$PWD -k merge_zips xref_cxx xref_java
+build/soong/soong_ui.bash --build-mode --all-modules --dir=$PWD -k merge_zips xref_cxx xref_java xref_rust
 
 # Build extraction file for Go the files in build/{blueprint,soong} directories.
 declare -r abspath_out=$(realpath "${out}")
@@ -44,7 +44,7 @@
 declare -r go_root=$(realpath prebuilts/go/linux-x86)
 declare -r source_root=$PWD
 
-# TODO(asmundak): Until b/182183061 is fixed, default corpus has to be specified 
+# TODO(asmundak): Until b/182183061 is fixed, default corpus has to be specified
 # in the rules file. Generate this file on the fly with corpus value set from the
 # environment variable.
 for dir in blueprint soong; do
diff --git a/build_test.bash b/build_test.bash
index 1dc6660..8b91e2c 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -25,7 +25,8 @@
 
 # Products that are broken or otherwise don't work with multiproduct_kati
 SKIPPED_PRODUCTS=(
-    # Both of these products are for soong-only builds, and will fail the kati stage.
+    # These products are for soong-only builds, and will fail the kati stage.
+    linux_bionic
     mainline_sdk
     ndk
 )
diff --git a/cc/Android.bp b/cc/Android.bp
index 9103a48..ce94467 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -15,6 +15,7 @@
         "soong-etc",
         "soong-fuzz",
         "soong-genrule",
+        "soong-multitree",
         "soong-snapshot",
         "soong-tradefed",
     ],
@@ -65,6 +66,7 @@
         "library.go",
         "library_headers.go",
         "library_sdk_member.go",
+        "library_stub.go",
         "native_bridge_sdk_trait.go",
         "object.go",
         "test.go",
@@ -89,17 +91,20 @@
     ],
     testSrcs: [
         "afdo_test.go",
+        "binary_test.go",
         "cc_test.go",
         "compiler_test.go",
         "gen_test.go",
         "genrule_test.go",
         "library_headers_test.go",
+        "library_stub_test.go",
         "library_test.go",
         "object_test.go",
         "prebuilt_test.go",
         "proto_test.go",
         "sanitize_test.go",
         "test_data_test.go",
+        "tidy_test.go",
         "vendor_public_library_test.go",
         "vendor_snapshot_test.go",
     ],
diff --git a/cc/OWNERS b/cc/OWNERS
index a438b15..ffbf14a 100644
--- a/cc/OWNERS
+++ b/cc/OWNERS
@@ -1,4 +1,4 @@
 per-file ndk_*.go = danalbert@google.com
-per-file tidy.go = srhines@google.com, chh@google.com
+per-file tidy*.go = srhines@google.com, chh@google.com
 per-file afdo.go,afdo_test.go,lto.go,pgo.go = srhines@google.com, pirama@google.com, yikong@google.com
 per-file coverage.go = pirama@google.com, srhines@google.com, allenhair@google.com
diff --git a/cc/binary.go b/cc/binary.go
index 89e7262..c5017c1 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -615,6 +615,7 @@
 		Linkopts:          baseAttrs.linkopts,
 		Link_crt:          baseAttrs.linkCrt,
 		Use_libcrt:        baseAttrs.useLibcrt,
+		Use_version_lib:   baseAttrs.useVersionLib,
 		Rtti:              baseAttrs.rtti,
 		Stl:               baseAttrs.stl,
 		Cpp_std:           baseAttrs.cppStd,
@@ -665,8 +666,9 @@
 	Linkopts                 bazel.StringListAttribute
 	Additional_linker_inputs bazel.LabelListAttribute
 
-	Link_crt   bazel.BoolAttribute
-	Use_libcrt bazel.BoolAttribute
+	Link_crt        bazel.BoolAttribute
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
 
 	Rtti    bazel.BoolAttribute
 	Stl     *string
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 8ec3871..cba5974 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -49,3 +49,23 @@
 	expectedUnStrippedFile := "outputbase/execroot/__main__/foo"
 	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
 }
+
+func TestBinaryLinkerScripts(t *testing.T) {
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_binary {
+			name: "foo",
+			srcs: ["foo.cc"],
+			linker_scripts: ["foo.ld", "bar.ld"],
+		}`)
+
+	binFoo := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("ld")
+
+	android.AssertStringListContains(t, "missing dependency on linker_scripts",
+		binFoo.Implicits.Strings(), "foo.ld")
+	android.AssertStringListContains(t, "missing dependency on linker_scripts",
+		binFoo.Implicits.Strings(), "bar.ld")
+	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
+		binFoo.Args["ldFlags"], "-Wl,--script,foo.ld")
+	android.AssertStringDoesContain(t, "missing flag for linker_scripts",
+		binFoo.Args["ldFlags"], "-Wl,--script,bar.ld")
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 811e228..19855fa 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -141,6 +141,7 @@
 	}
 }
 
+// Parses properties common to static and shared libraries. Also used for prebuilt libraries.
 func bp2buildParseStaticOrSharedProps(ctx android.BazelConversionPathContext, module *Module, lib *libraryDecorator, isStatic bool) staticOrSharedAttributes {
 	attrs := staticOrSharedAttributes{}
 
@@ -166,21 +167,17 @@
 	attrs.System_dynamic_deps.ForceSpecifyEmptyList = true
 
 	if isStatic {
-		for axis, configToProps := range module.GetArchVariantProperties(ctx, &StaticProperties{}) {
-			for config, props := range configToProps {
-				if staticOrSharedProps, ok := props.(*StaticProperties); ok {
-					setAttrs(axis, config, staticOrSharedProps.Static)
-				}
+		bp2BuildPropParseHelper(ctx, module, &StaticProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+			if staticOrSharedProps, ok := props.(*StaticProperties); ok {
+				setAttrs(axis, config, staticOrSharedProps.Static)
 			}
-		}
+		})
 	} else {
-		for axis, configToProps := range module.GetArchVariantProperties(ctx, &SharedProperties{}) {
-			for config, props := range configToProps {
-				if staticOrSharedProps, ok := props.(*SharedProperties); ok {
-					setAttrs(axis, config, staticOrSharedProps.Shared)
-				}
+		bp2BuildPropParseHelper(ctx, module, &SharedProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+			if staticOrSharedProps, ok := props.(*SharedProperties); ok {
+				setAttrs(axis, config, staticOrSharedProps.Shared)
 			}
-		}
+		})
 	}
 
 	partitionedSrcs := groupSrcsByExtension(ctx, attrs.Srcs)
@@ -198,30 +195,72 @@
 
 // Convenience struct to hold all attributes parsed from prebuilt properties.
 type prebuiltAttributes struct {
-	Src bazel.LabelAttribute
+	Src     bazel.LabelAttribute
+	Enabled bazel.BoolAttribute
 }
 
 // NOTE: Used outside of Soong repo project, in the clangprebuilts.go bootstrap_go_package
-func Bp2BuildParsePrebuiltLibraryProps(ctx android.BazelConversionPathContext, module *Module) prebuiltAttributes {
+func Bp2BuildParsePrebuiltLibraryProps(ctx android.BazelConversionPathContext, module *Module, isStatic bool) prebuiltAttributes {
+	manySourceFileError := func(axis bazel.ConfigurationAxis, config string) {
+		ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most one source file for %s %s\n", axis, config)
+	}
 	var srcLabelAttribute bazel.LabelAttribute
 
-	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltLinkerProperties{}) {
-		for config, props := range configToProps {
-			if prebuiltLinkerProperties, ok := props.(*prebuiltLinkerProperties); ok {
-				if len(prebuiltLinkerProperties.Srcs) > 1 {
-					ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file for %s %s\n", axis, config)
-					continue
-				} else if len(prebuiltLinkerProperties.Srcs) == 0 {
-					continue
-				}
-				src := android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinkerProperties.Srcs[0])
-				srcLabelAttribute.SetSelectValue(axis, config, src)
-			}
+	parseSrcs := func(ctx android.BazelConversionPathContext, axis bazel.ConfigurationAxis, config string, srcs []string) {
+		if len(srcs) > 1 {
+			manySourceFileError(axis, config)
+			return
+		} else if len(srcs) == 0 {
+			return
 		}
+		if srcLabelAttribute.SelectValue(axis, config) != nil {
+			manySourceFileError(axis, config)
+			return
+		}
+
+		src := android.BazelLabelForModuleSrcSingle(ctx, srcs[0])
+		srcLabelAttribute.SetSelectValue(axis, config, src)
+	}
+
+	bp2BuildPropParseHelper(ctx, module, &prebuiltLinkerProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		if prebuiltLinkerProperties, ok := props.(*prebuiltLinkerProperties); ok {
+			parseSrcs(ctx, axis, config, prebuiltLinkerProperties.Srcs)
+		}
+	})
+
+	var enabledLabelAttribute bazel.BoolAttribute
+	parseAttrs := func(axis bazel.ConfigurationAxis, config string, props StaticOrSharedProperties) {
+		if props.Enabled != nil {
+			enabledLabelAttribute.SetSelectValue(axis, config, props.Enabled)
+		}
+		parseSrcs(ctx, axis, config, props.Srcs)
+	}
+
+	if isStatic {
+		bp2BuildPropParseHelper(ctx, module, &StaticProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+			if staticProperties, ok := props.(*StaticProperties); ok {
+				parseAttrs(axis, config, staticProperties.Static)
+			}
+		})
+	} else {
+		bp2BuildPropParseHelper(ctx, module, &SharedProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+			if sharedProperties, ok := props.(*SharedProperties); ok {
+				parseAttrs(axis, config, sharedProperties.Shared)
+			}
+		})
 	}
 
 	return prebuiltAttributes{
-		Src: srcLabelAttribute,
+		Src:     srcLabelAttribute,
+		Enabled: enabledLabelAttribute,
+	}
+}
+
+func bp2BuildPropParseHelper(ctx android.ArchVariantContext, module *Module, propsType interface{}, parseFunc func(axis bazel.ConfigurationAxis, config string, props interface{})) {
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, propsType) {
+		for config, props := range configToProps {
+			parseFunc(axis, config, props)
+		}
 	}
 }
 
@@ -316,21 +355,18 @@
 }
 
 func (ca *compilerAttributes) convertStlProps(ctx android.ArchVariantContext, module *Module) {
-	stlPropsByArch := module.GetArchVariantProperties(ctx, &StlProperties{})
-	for _, configToProps := range stlPropsByArch {
-		for _, props := range configToProps {
-			if stlProps, ok := props.(*StlProperties); ok {
-				if stlProps.Stl == nil {
-					continue
-				}
-				if ca.stl == nil {
-					ca.stl = stlProps.Stl
-				} else if ca.stl != stlProps.Stl {
-					ctx.ModuleErrorf("Unsupported conversion: module with different stl for different variants: %s and %s", *ca.stl, stlProps.Stl)
-				}
+	bp2BuildPropParseHelper(ctx, module, &StlProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		if stlProps, ok := props.(*StlProperties); ok {
+			if stlProps.Stl == nil {
+				return
+			}
+			if ca.stl == nil {
+				ca.stl = stlProps.Stl
+			} else if ca.stl != stlProps.Stl {
+				ctx.ModuleErrorf("Unsupported conversion: module with different stl for different variants: %s and %s", *ca.stl, stlProps.Stl)
 			}
 		}
-	}
+	})
 }
 
 func (ca *compilerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
@@ -542,8 +578,8 @@
 }
 
 func bp2BuildParseSdkAttributes(module *Module) sdkAttributes {
-	return sdkAttributes {
-		Sdk_version: module.Properties.Sdk_version,
+	return sdkAttributes{
+		Sdk_version:     module.Properties.Sdk_version,
 		Min_sdk_version: module.Properties.Min_sdk_version,
 	}
 }
@@ -670,17 +706,15 @@
 }
 
 func (la *linkerAttributes) convertStripProps(ctx android.BazelConversionPathContext, module *Module) {
-	for axis, configToProps := range module.GetArchVariantProperties(ctx, &StripProperties{}) {
-		for config, props := range configToProps {
-			if stripProperties, ok := props.(*StripProperties); ok {
-				la.stripKeepSymbols.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols)
-				la.stripKeepSymbolsList.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_list)
-				la.stripKeepSymbolsAndDebugFrame.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_and_debug_frame)
-				la.stripAll.SetSelectValue(axis, config, stripProperties.Strip.All)
-				la.stripNone.SetSelectValue(axis, config, stripProperties.Strip.None)
-			}
+	bp2BuildPropParseHelper(ctx, module, &StripProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		if stripProperties, ok := props.(*StripProperties); ok {
+			la.stripKeepSymbols.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols)
+			la.stripKeepSymbolsList.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_list)
+			la.stripKeepSymbolsAndDebugFrame.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_and_debug_frame)
+			la.stripAll.SetSelectValue(axis, config, stripProperties.Strip.All)
+			la.stripNone.SetSelectValue(axis, config, stripProperties.Strip.None)
 		}
-	}
+	})
 }
 
 func (la *linkerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
@@ -816,18 +850,16 @@
 	} else {
 		exported = BazelIncludes{}
 	}
-	for axis, configToProps := range module.GetArchVariantProperties(ctx, &FlagExporterProperties{}) {
-		for config, props := range configToProps {
-			if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-				if len(flagExporterProperties.Export_include_dirs) > 0 {
-					exported.Includes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.Includes.SelectValue(axis, config), flagExporterProperties.Export_include_dirs...)))
-				}
-				if len(flagExporterProperties.Export_system_include_dirs) > 0 {
-					exported.SystemIncludes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.SystemIncludes.SelectValue(axis, config), flagExporterProperties.Export_system_include_dirs...)))
-				}
+	bp2BuildPropParseHelper(ctx, module, &FlagExporterProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
+			if len(flagExporterProperties.Export_include_dirs) > 0 {
+				exported.Includes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.Includes.SelectValue(axis, config), flagExporterProperties.Export_include_dirs...)))
+			}
+			if len(flagExporterProperties.Export_system_include_dirs) > 0 {
+				exported.SystemIncludes.SetSelectValue(axis, config, android.FirstUniqueStrings(append(exported.SystemIncludes.SelectValue(axis, config), flagExporterProperties.Export_system_include_dirs...)))
 			}
 		}
-	}
+	})
 	exported.AbsoluteIncludes.DeduplicateAxesFromBase()
 	exported.Includes.DeduplicateAxesFromBase()
 	exported.SystemIncludes.DeduplicateAxesFromBase()
@@ -895,22 +927,19 @@
 
 func bp2buildBinaryLinkerProps(ctx android.BazelConversionPathContext, m *Module) binaryLinkerAttrs {
 	attrs := binaryLinkerAttrs{}
-	archVariantProps := m.GetArchVariantProperties(ctx, &BinaryLinkerProperties{})
-	for axis, configToProps := range archVariantProps {
-		for _, p := range configToProps {
-			props := p.(*BinaryLinkerProperties)
-			staticExecutable := props.Static_executable
-			if axis == bazel.NoConfigAxis {
-				if linkBinaryShared := !proptools.Bool(staticExecutable); !linkBinaryShared {
-					attrs.Linkshared = &linkBinaryShared
-				}
-			} else if staticExecutable != nil {
-				// TODO(b/202876379): Static_executable is arch-variant; however, linkshared is a
-				// nonconfigurable attribute. Only 4 AOSP modules use this feature, defer handling
-				ctx.ModuleErrorf("bp2build cannot migrate a module with arch/target-specific static_executable values")
+	bp2BuildPropParseHelper(ctx, m, &BinaryLinkerProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		linkerProps := props.(*BinaryLinkerProperties)
+		staticExecutable := linkerProps.Static_executable
+		if axis == bazel.NoConfigAxis {
+			if linkBinaryShared := !proptools.Bool(staticExecutable); !linkBinaryShared {
+				attrs.Linkshared = &linkBinaryShared
 			}
+		} else if staticExecutable != nil {
+			// TODO(b/202876379): Static_executable is arch-variant; however, linkshared is a
+			// nonconfigurable attribute. Only 4 AOSP modules use this feature, defer handling
+			ctx.ModuleErrorf("bp2build cannot migrate a module with arch/target-specific static_executable values")
 		}
-	}
+	})
 
 	return attrs
 }
diff --git a/cc/builder.go b/cc/builder.go
index 525b1a1..107cd58 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -855,13 +855,24 @@
 	deps = append(deps, crtBegin...)
 	deps = append(deps, crtEnd...)
 
+	var depFile android.WritablePath
+	var depFileLdFlags string
+	depsType := blueprint.DepsNone
+	if !ctx.Windows() && !ctx.Darwin() {
+		// lld only supports --dependency-file for elf files
+		depFile = outputFile.ReplaceExtension(ctx, "d")
+		depFileLdFlags = " -Wl,--dependency-file=" + depFile.String()
+		depsType = blueprint.DepsGCC
+		implicitOutputs = append(implicitOutputs, depFile)
+	}
+
 	rule := ld
 	args := map[string]string{
 		"ldCmd":         ldCmd,
 		"crtBegin":      strings.Join(crtBegin.Strings(), " "),
 		"libFlags":      strings.Join(libFlagsList, " "),
 		"extraLibFlags": flags.extraLibFlags,
-		"ldFlags":       flags.globalLdFlags + " " + flags.localLdFlags,
+		"ldFlags":       flags.globalLdFlags + " " + flags.localLdFlags + depFileLdFlags,
 		"crtEnd":        strings.Join(crtEnd.Strings(), " "),
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
@@ -872,6 +883,8 @@
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:            rule,
+		Deps:            depsType,
+		Depfile:         depFile,
 		Description:     "link " + outputFile.Base(),
 		Output:          outputFile,
 		ImplicitOutputs: implicitOutputs,
@@ -1025,18 +1038,33 @@
 
 	ldCmd := "${config.ClangBin}/clang++"
 
+	var implicitOutputs android.WritablePaths
+	var depFile android.WritablePath
+	var depFileLdFlags string
+	depsType := blueprint.DepsNone
+	if !ctx.Windows() && !ctx.Darwin() {
+		// lld only supports --dependency-file for elf files
+		depFile = outputFile.ReplaceExtension(ctx, "d")
+		depFileLdFlags = " -Wl,--dependency-file=" + depFile.String()
+		depsType = blueprint.DepsGCC
+		implicitOutputs = append(implicitOutputs, depFile)
+	}
+
 	rule := partialLd
 	args := map[string]string{
 		"ldCmd":   ldCmd,
-		"ldFlags": flags.globalLdFlags + " " + flags.localLdFlags,
+		"ldFlags": flags.globalLdFlags + " " + flags.localLdFlags + depFileLdFlags,
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
 		rule = partialLdRE
 		args["inCommaList"] = strings.Join(objFiles.Strings(), ",")
 		args["implicitInputs"] = strings.Join(deps.Strings(), ",")
+		args["implicitOutputs"] = strings.Join(implicitOutputs.Strings(), ",")
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
+		Deps:        depsType,
+		Depfile:     depFile,
 		Description: "link " + outputFile.Base(),
 		Output:      outputFile,
 		Inputs:      objFiles,
diff --git a/cc/cc.go b/cc/cc.go
index 00b82d5..8606920 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -48,7 +48,6 @@
 		ctx.BottomUp("vndk", VndkMutator).Parallel()
 		ctx.BottomUp("link", LinkageMutator).Parallel()
 		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
-		ctx.BottomUp("version_selector", versionSelectorMutator).Parallel()
 		ctx.BottomUp("version", versionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 		ctx.BottomUp("sysprop_cc", SyspropMutator).Parallel()
@@ -746,6 +745,7 @@
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
+	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
@@ -1788,7 +1788,7 @@
 	bazelActionsUsed := false
 	// Mixed builds mode is disabled for modules outside of device OS.
 	// TODO(b/200841190): Support non-device OS in mixed builds.
-	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
+	if android.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
 		bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
 	}
 	return bazelActionsUsed
@@ -2116,7 +2116,7 @@
 
 	variations = append([]blueprint.Variation(nil), variations...)
 
-	if version != "" && CanBeOrLinkAgainstVersionVariants(mod) {
+	if version != "" && canBeOrLinkAgainstVersionVariants(mod) {
 		// Version is explicitly specified. i.e. libFoo#30
 		variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
 		if tag, ok := depTag.(libraryDependencyTag); ok {
@@ -3556,13 +3556,14 @@
 	case fullLibrary:
 		if !prebuilt {
 			libraryBp2Build(ctx, c)
+		} else {
+			prebuiltLibraryBp2Build(ctx, c)
 		}
 	case headerLibrary:
 		libraryHeadersBp2Build(ctx, c)
 	case staticLibrary:
-
 		if prebuilt {
-			prebuiltLibraryStaticBp2Build(ctx, c)
+			prebuiltLibraryStaticBp2Build(ctx, c, false)
 		} else {
 			sharedOrStaticLibraryBp2Build(ctx, c, true)
 		}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 09cc352..2951b5a 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4042,7 +4042,7 @@
 	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
 
 	cflags := []string{"-Wall", "-Werror", "-std=candcpp"}
-	cstd := []string{"-std=gnu99", "-std=conly"}
+	cstd := []string{"-std=gnu11", "-std=conly"}
 	cppstd := []string{"-std=gnu++17", "-std=cpp", "-fno-rtti"}
 
 	lastIncludes := []string{
diff --git a/cc/check.go b/cc/check.go
index a357a97..3d290a9 100644
--- a/cc/check.go
+++ b/cc/check.go
@@ -87,6 +87,8 @@
 			ctx.PropertyErrorf(prop, "Bad flag: `%s` is not allowed", flag)
 		} else if strings.HasPrefix(flag, "-Wl,--version-script") {
 			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use version_script instead", flag)
+		} else if flag == "-T" || strings.HasPrefix(flag, "--script") {
+			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use linker_scripts instead", flag)
 		} else if flag == "--coverage" {
 			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use native_coverage instead", flag)
 		} else if strings.Contains(flag, " ") {
diff --git a/cc/config/global.go b/cc/config/global.go
index 9c71683..1c4ad7f 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -279,15 +279,15 @@
 		"-w",
 	}
 
-	CStdVersion               = "gnu99"
+	CStdVersion               = "gnu11"
 	CppStdVersion             = "gnu++17"
-	ExperimentalCStdVersion   = "gnu11"
+	ExperimentalCStdVersion   = "gnu17"
 	ExperimentalCppStdVersion = "gnu++2a"
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r450784b"
-	ClangDefaultShortVersion = "14.0.4"
+	ClangDefaultVersion      = "clang-r450784e"
+	ClangDefaultShortVersion = "14.0.7"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index ba1043b..1f90843 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -46,6 +46,7 @@
 			"-misc-no-recursion",
 			"-misc-non-private-member-variables-in-classes",
 			"-misc-unused-parameters",
+			"-performance-no-int-to-ptr",
 			// the following groups are excluded by -*
 			// -altera-*
 			// -cppcoreguidelines-*
@@ -122,6 +123,7 @@
 	{"hardware/qcom", tidyExternalVendor},
 	{"vendor/", tidyExternalVendor},
 	{"vendor/google", tidyDefault},
+	{"vendor/google_arc/libs/org.chromium.arc.mojom", tidyExternalVendor},
 	{"vendor/google_devices", tidyExternalVendor},
 }
 
diff --git a/cc/installer.go b/cc/installer.go
index 2522610..e2c0e7b 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -31,7 +31,7 @@
 	Install_in_root *bool `android:"arch_variant"`
 
 	// Install output directly in {partition}/xbin
-	Install_in_xbin *bool `android:"arch_vvariant"`
+	Install_in_xbin *bool `android:"arch_variant"`
 }
 
 type installLocation int
diff --git a/cc/libbuildversion/tests/Android.bp b/cc/libbuildversion/tests/Android.bp
index 0e97fed..c616a33 100644
--- a/cc/libbuildversion/tests/Android.bp
+++ b/cc/libbuildversion/tests/Android.bp
@@ -35,6 +35,16 @@
                 dir: "host/",
             },
         },
+        linux_musl_x86: {
+            dist: {
+                dir: "host32/",
+            },
+        },
+        linux_musl_x86_64: {
+            dist: {
+                dir: "host/",
+            },
+        },
         linux_glibc_x86: {
             dist: {
                 dir: "host32/",
diff --git a/cc/library.go b/cc/library.go
index 035a90e..fdbbccb 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -352,6 +352,7 @@
 		Stl:                      compilerAttrs.stl,
 		Cpp_std:                  compilerAttrs.cppStd,
 		C_std:                    compilerAttrs.cStd,
+		Use_version_lib:          linkerAttrs.useVersionLib,
 
 		Features: linkerAttrs.features,
 	}
@@ -374,6 +375,7 @@
 		Stl:                      compilerAttrs.stl,
 		Cpp_std:                  compilerAttrs.cppStd,
 		C_std:                    compilerAttrs.cStd,
+		Use_version_lib:          linkerAttrs.useVersionLib,
 
 		Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
 
@@ -2342,7 +2344,7 @@
 	}
 }
 
-func CanBeOrLinkAgainstVersionVariants(module interface {
+func canBeOrLinkAgainstVersionVariants(module interface {
 	Host() bool
 	InRamdisk() bool
 	InVendorRamdisk() bool
@@ -2350,15 +2352,14 @@
 	return !module.Host() && !module.InRamdisk() && !module.InVendorRamdisk()
 }
 
-func CanBeVersionVariant(module interface {
+func canBeVersionVariant(module interface {
 	Host() bool
 	InRamdisk() bool
 	InVendorRamdisk() bool
-	InRecovery() bool
 	CcLibraryInterface() bool
 	Shared() bool
 }) bool {
-	return CanBeOrLinkAgainstVersionVariants(module) &&
+	return canBeOrLinkAgainstVersionVariants(module) &&
 		module.CcLibraryInterface() && module.Shared()
 }
 
@@ -2369,37 +2370,41 @@
 	return nil
 }
 
-// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
-func versionSelectorMutator(mctx android.BottomUpMutatorContext) {
-	if library := moduleLibraryInterface(mctx.Module()); library != nil && CanBeVersionVariant(mctx.Module().(*Module)) {
-		if library.buildShared() {
-			versions := library.stubsVersions(mctx)
-			if len(versions) > 0 {
-				normalizeVersions(mctx, versions)
-				if mctx.Failed() {
-					return
-				}
-				// Set the versions on the pre-mutated module so they can be read by any llndk modules that
-				// depend on the implementation library and haven't been mutated yet.
-				library.setAllStubsVersions(versions)
-			}
-		}
+// setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
+func setStubsVersions(mctx android.BottomUpMutatorContext, library libraryInterface, module *Module) {
+	if !library.buildShared() || !canBeVersionVariant(module) {
+		return
 	}
+	versions := library.stubsVersions(mctx)
+	if len(versions) <= 0 {
+		return
+	}
+	normalizeVersions(mctx, versions)
+	if mctx.Failed() {
+		return
+	}
+	// Set the versions on the pre-mutated module so they can be read by any llndk modules that
+	// depend on the implementation library and haven't been mutated yet.
+	library.setAllStubsVersions(versions)
 }
 
 // versionMutator splits a module into the mandatory non-stubs variant
 // (which is unnamed) and zero or more stubs variants.
 func versionMutator(mctx android.BottomUpMutatorContext) {
-	if library := moduleLibraryInterface(mctx.Module()); library != nil && CanBeVersionVariant(mctx.Module().(*Module)) {
+	if mctx.Os() != android.Android {
+		return
+	}
+
+	m, ok := mctx.Module().(*Module)
+	if library := moduleLibraryInterface(mctx.Module()); library != nil && canBeVersionVariant(m) {
+		setStubsVersions(mctx, library, m)
+
 		createVersionVariations(mctx, library.allStubsVersions())
 		return
 	}
 
-	if m, ok := mctx.Module().(*Module); ok {
+	if ok {
 		if m.SplitPerApiLevel() && m.IsSdkVariant() {
-			if mctx.Os() != android.Android {
-				return
-			}
 			createPerApiVersionVariations(mctx, m.MinSdkVersion())
 		}
 	}
diff --git a/cc/library_stub.go b/cc/library_stub.go
new file mode 100644
index 0000000..4d0148d
--- /dev/null
+++ b/cc/library_stub.go
@@ -0,0 +1,163 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"android/soong/android"
+	"android/soong/multitree"
+)
+
+func init() {
+	RegisterLibraryStubBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterLibraryStubBuildComponents(ctx android.RegistrationContext) {
+	// cc_api_stub_library shares a lot of ndk_library, and this will be refactored later
+	ctx.RegisterModuleType("cc_api_stub_library", CcApiStubLibraryFactory)
+	ctx.RegisterModuleType("cc_api_contribution", CcApiContributionFactory)
+}
+
+func CcApiStubLibraryFactory() android.Module {
+	module, decorator := NewLibrary(android.DeviceSupported)
+	apiStubDecorator := &apiStubDecorator{
+		libraryDecorator: decorator,
+	}
+	apiStubDecorator.BuildOnlyShared()
+
+	module.compiler = apiStubDecorator
+	module.linker = apiStubDecorator
+	module.installer = nil
+	module.library = apiStubDecorator
+	module.Properties.HideFromMake = true // TODO: remove
+
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth)
+	module.AddProperties(&module.Properties,
+		&apiStubDecorator.properties,
+		&apiStubDecorator.MutatedProperties,
+		&apiStubDecorator.apiStubLibraryProperties)
+	return module
+}
+
+type apiStubLiraryProperties struct {
+	Imported_includes []string `android:"path"`
+}
+
+type apiStubDecorator struct {
+	*libraryDecorator
+	properties               libraryProperties
+	apiStubLibraryProperties apiStubLiraryProperties
+}
+
+func (compiler *apiStubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+	firstVersion := String(compiler.properties.First_version)
+	return ndkLibraryVersions(ctx, android.ApiLevelOrPanic(ctx, firstVersion))
+}
+
+func (decorator *apiStubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+	if decorator.stubsVersion() == "" {
+		decorator.setStubsVersion("current")
+	} // TODO: fix
+	symbolFile := String(decorator.properties.Symbol_file)
+	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
+		android.ApiLevelOrPanic(ctx, decorator.stubsVersion()),
+		"")
+	return compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+}
+
+func (decorator *apiStubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objects Objects) android.Path {
+	decorator.reexportDirs(android.PathsForModuleSrc(ctx, decorator.apiStubLibraryProperties.Imported_includes)...)
+	return decorator.libraryDecorator.link(ctx, flags, deps, objects)
+}
+
+func init() {
+	pctx.HostBinToolVariable("gen_api_surface_build_files", "gen_api_surface_build_files")
+}
+
+type CcApiContribution struct {
+	android.ModuleBase
+	properties ccApiContributionProperties
+}
+
+type ccApiContributionProperties struct {
+	Symbol_file        *string `android:"path"`
+	First_version      *string
+	Export_include_dir *string
+}
+
+func CcApiContributionFactory() android.Module {
+	module := &CcApiContribution{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidModule(module)
+	return module
+}
+
+// Do some simple validations
+// Majority of the build rules will be created in the ctx of the api surface this module contributes to
+func (contrib *CcApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if contrib.properties.Symbol_file == nil {
+		ctx.PropertyErrorf("symbol_file", "%v does not have symbol file", ctx.ModuleName())
+	}
+	if contrib.properties.First_version == nil {
+		ctx.PropertyErrorf("first_version", "%v does not have first_version for stub variants", ctx.ModuleName())
+	}
+}
+
+// Path is out/soong/.export/ but will be different in final multi-tree layout
+func outPathApiSurface(ctx android.ModuleContext, myModuleName string, pathComponent string) android.OutputPath {
+	return android.PathForOutput(ctx, ".export", ctx.ModuleName(), myModuleName, pathComponent)
+}
+
+func (contrib *CcApiContribution) CopyFilesWithTag(apiSurfaceContext android.ModuleContext) map[string]android.Paths {
+	// copy map.txt for now
+	// hardlinks cannot be created since nsjail creates a different mountpoint for out/
+	myDir := apiSurfaceContext.OtherModuleDir(contrib)
+	genMapTxt := outPathApiSurface(apiSurfaceContext, contrib.Name(), String(contrib.properties.Symbol_file))
+	apiSurfaceContext.Build(pctx, android.BuildParams{
+		Rule:        android.Cp,
+		Description: "import map.txt file",
+		Input:       android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Symbol_file)),
+		Output:      genMapTxt,
+	})
+
+	outputs := make(map[string]android.Paths)
+	outputs["map"] = []android.Path{genMapTxt}
+
+	if contrib.properties.Export_include_dir != nil {
+		includeDir := android.PathForSource(apiSurfaceContext, myDir, String(contrib.properties.Export_include_dir))
+		outputs["export_include_dir"] = []android.Path{includeDir}
+	}
+	return outputs
+}
+
+var _ multitree.ApiContribution = (*CcApiContribution)(nil)
+
+/*
+func (contrib *CcApiContribution) GenerateBuildFiles(apiSurfaceContext android.ModuleContext) android.Paths {
+	genAndroidBp := outPathApiSurface(apiSurfaceContext, contrib.Name(), "Android.bp")
+
+	// generate Android.bp
+	apiSurfaceContext.Build(pctx, android.BuildParams{
+		Rule:        genApiSurfaceBuildFiles,
+		Description: "generate API surface build files",
+		Outputs:     []android.WritablePath{genAndroidBp},
+		Args: map[string]string{
+			"name":          contrib.Name() + "." + apiSurfaceContext.ModuleName(), //e.g. liblog.ndk
+			"symbol_file":   String(contrib.properties.Symbol_file),
+			"first_version": String(contrib.properties.First_version),
+		},
+	})
+	return []android.Path{genAndroidBp}
+}
+*/
diff --git a/cc/library_stub_test.go b/cc/library_stub_test.go
new file mode 100644
index 0000000..15b56d2
--- /dev/null
+++ b/cc/library_stub_test.go
@@ -0,0 +1,108 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	_ "fmt"
+	_ "sort"
+
+	"testing"
+
+	"android/soong/android"
+	"android/soong/multitree"
+)
+
+func TestCcApiStubLibraryOutputFiles(t *testing.T) {
+	bp := `
+		cc_api_stub_library {
+			name: "foo",
+			symbol_file: "foo.map.txt",
+			first_version: "29",
+		}
+	`
+	result := prepareForCcTest.RunTestWithBp(t, bp)
+	outputs := result.ModuleForTests("foo", "android_arm64_armv8-a_shared").AllOutputs()
+	expected_file_suffixes := []string{".c", "stub.map", ".o", ".so"}
+	for _, expected_file_suffix := range expected_file_suffixes {
+		android.AssertBoolEquals(t, expected_file_suffix+" file not found in output", true, android.SuffixInList(outputs, expected_file_suffix))
+	}
+}
+
+func TestCcApiStubLibraryVariants(t *testing.T) {
+	bp := `
+		cc_api_stub_library {
+			name: "foo",
+			symbol_file: "foo.map.txt",
+			first_version: "29",
+		}
+	`
+	result := prepareForCcTest.RunTestWithBp(t, bp)
+	variants := result.ModuleVariantsForTests("foo")
+	expected_variants := []string{"29", "30", "S", "Tiramisu"} //TODO: make this test deterministic by using fixtures
+	for _, expected_variant := range expected_variants {
+		android.AssertBoolEquals(t, expected_variant+" variant not found in foo", true, android.SubstringInList(variants, expected_variant))
+	}
+}
+
+func TestCcLibraryUsesCcApiStubLibrary(t *testing.T) {
+	bp := `
+		cc_api_stub_library {
+			name: "foo",
+			symbol_file: "foo.map.txt",
+			first_version: "29",
+		}
+		cc_library {
+			name: "foo_user",
+			shared_libs: [
+				"foo#29",
+			],
+		}
+
+	`
+	prepareForCcTest.RunTestWithBp(t, bp)
+}
+
+func TestApiSurfaceOutputs(t *testing.T) {
+	bp := `
+		api_surface {
+			name: "mysdk",
+			contributions: [
+				"foo",
+			],
+		}
+
+		cc_api_contribution {
+			name: "foo",
+			symbol_file: "foo.map.txt",
+			first_version: "29",
+		}
+	`
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		multitree.PrepareForTestWithApiSurface,
+	).RunTestWithBp(t, bp)
+	mysdk := result.ModuleForTests("mysdk", "")
+
+	actual_surface_inputs := mysdk.Rule("phony").BuildParams.Inputs.Strings()
+	expected_file_suffixes := []string{"mysdk/foo/foo.map.txt"}
+	for _, expected_file_suffix := range expected_file_suffixes {
+		android.AssertBoolEquals(t, expected_file_suffix+" file not found in input", true, android.SuffixInList(actual_surface_inputs, expected_file_suffix))
+	}
+
+	// check args/inputs to rule
+	/*api_surface_gen_rule_args := result.ModuleForTests("mysdk", "").Rule("genApiSurfaceBuildFiles").Args
+	android.AssertStringEquals(t, "name", "foo.mysdk", api_surface_gen_rule_args["name"])
+	android.AssertStringEquals(t, "symbol_file", "foo.map.txt", api_surface_gen_rule_args["symbol_file"])*/
+}
diff --git a/cc/linker.go b/cc/linker.go
index bea65d4..f346584 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -227,6 +227,9 @@
 	// local file name to pass to the linker as --dynamic-list
 	Dynamic_list *string `android:"path,arch_variant"`
 
+	// local files to pass to the linker as --script
+	Linker_scripts []string `android:"path,arch_variant"`
+
 	// list of static libs that should not be used to build this module
 	Exclude_static_libs []string `android:"arch_variant"`
 
@@ -602,6 +605,17 @@
 				flags.LdFlagsDeps = append(flags.LdFlagsDeps, dynamicList.Path())
 			}
 		}
+
+		linkerScriptPaths := android.PathsForModuleSrc(ctx, linker.Properties.Linker_scripts)
+		if len(linkerScriptPaths) > 0 && (ctx.Darwin() || ctx.Windows()) {
+			ctx.PropertyErrorf("linker_scripts", "Only supported for ELF files")
+		} else {
+			for _, linkerScriptPath := range linkerScriptPaths {
+				flags.Local.LdFlags = append(flags.Local.LdFlags,
+					"-Wl,--script,"+linkerScriptPath.String())
+				flags.LdFlagsDeps = append(flags.LdFlagsDeps, linkerScriptPath)
+			}
+		}
 	}
 
 	return flags
diff --git a/cc/ndk_abi.go b/cc/ndk_abi.go
index 927fa2e..3456c32 100644
--- a/cc/ndk_abi.go
+++ b/cc/ndk_abi.go
@@ -46,7 +46,7 @@
 
 		if m, ok := module.(*Module); ok {
 			if installer, ok := m.installer.(*stubDecorator); ok {
-				if canDumpAbi() {
+				if canDumpAbi(ctx.Config()) {
 					depPaths = append(depPaths, installer.abiDumpPath)
 				}
 			}
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index fc682ff..0879257 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -53,9 +53,9 @@
 
 	abitidy = pctx.AndroidStaticRule("abitidy",
 		blueprint.RuleParams{
-			Command:     "$abitidy --all -i $in -o $out",
+			Command:     "$abitidy --all $flags -i $in -o $out",
 			CommandDeps: []string{"$abitidy"},
-		})
+		}, "flags")
 
 	abidiff = pctx.AndroidStaticRule("abidiff",
 		blueprint.RuleParams{
@@ -93,7 +93,7 @@
 type libraryProperties struct {
 	// Relative path to the symbol map.
 	// An example file can be seen here: TODO(danalbert): Make an example.
-	Symbol_file *string
+	Symbol_file *string `android:"path"`
 
 	// The first API level a library was available. A library will be generated
 	// for every API level beginning with this one.
@@ -104,6 +104,12 @@
 	// used. This is only needed to work around platform bugs like
 	// https://github.com/android-ndk/ndk/issues/265.
 	Unversioned_until *string
+
+	// If true, does not emit errors when APIs lacking type information are
+	// found. This is false by default and should not be enabled outside bionic,
+	// where it is enabled pending a fix for http://b/190554910 (no debug info
+	// for asm implemented symbols).
+	Allow_untyped_symbols *bool
 }
 
 type stubDecorator struct {
@@ -278,6 +284,10 @@
 }
 
 func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
+	// libc/libm stubs libraries end up mismatching with clang's internal definition of these
+	// functions (which have noreturn attributes and other things). Because we just want to create a
+	// stub with symbol definitions, and types aren't important in C, ignore the mismatch.
+	flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin")
 	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
 		android.Paths{src}, nil, nil, nil, nil)
 }
@@ -315,8 +325,18 @@
 }
 
 // Feature flag.
-func canDumpAbi() bool {
-	return runtime.GOOS != "darwin"
+func canDumpAbi(config android.Config) bool {
+	if runtime.GOOS == "darwin" {
+		return false
+	}
+	// abidw doesn't currently handle top-byte-ignore correctly. Disable ABI
+	// dumping for those configs while we wait for a fix. We'll still have ABI
+	// checking coverage from non-hwasan builds.
+	// http://b/190554910
+	if android.InList("hwaddress", config.SanitizeDevice()) {
+		return false
+	}
+	return true
 }
 
 // Feature flag to disable diffing against prebuilts.
@@ -339,14 +359,22 @@
 			"symbolList": symbolList.String(),
 		},
 	})
+
 	this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx,
 		this.apiLevel.String(), ctx.Arch().ArchType.String(),
 		this.libraryName(ctx), "abi.xml")
+	untypedFlag := "--abort-on-untyped-symbols"
+	if proptools.BoolDefault(this.properties.Allow_untyped_symbols, false) {
+		untypedFlag = ""
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        abitidy,
 		Description: fmt.Sprintf("abitidy %s", implementationLibrary),
 		Input:       abiRawPath,
 		Output:      this.abiDumpPath,
+		Args: map[string]string{
+			"flags": untypedFlag,
+		},
 	})
 }
 
@@ -444,7 +472,7 @@
 	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
 	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 	c.versionScriptPath = nativeAbiResult.versionScript
-	if canDumpAbi() {
+	if canDumpAbi(ctx.Config()) {
 		c.dumpAbi(ctx, nativeAbiResult.symbolList)
 		if canDiffAbi() {
 			c.diffAbi(ctx)
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 5980319..f54c6f8 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -253,6 +253,7 @@
 func NewPrebuiltLibrary(hod android.HostOrDeviceSupported, srcsProperty string) (*Module, *libraryDecorator) {
 	module, library := NewLibrary(hod)
 	module.compiler = nil
+	module.bazelable = true
 
 	prebuilt := &prebuiltLibraryLinker{
 		libraryDecorator: library,
@@ -338,8 +339,21 @@
 	Export_system_includes bazel.StringListAttribute
 }
 
-func prebuiltLibraryStaticBp2Build(ctx android.TopDownMutatorContext, module *Module) {
-	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module)
+// TODO(b/228623543): The below is not entirely true until the bug is fixed. For now, both targets are always generated
+// Implements bp2build for cc_prebuilt_library modules. This will generate:
+// * Only a prebuilt_library_static if the shared.enabled property is set to false across all variants.
+// * Only a prebuilt_library_shared if the static.enabled property is set to false across all variants
+// * Both a prebuilt_library_static and prebuilt_library_shared if the aforementioned properties are not false across
+//   all variants
+//
+// In all cases, prebuilt_library_static target names will be appended with "_bp2build_cc_library_static".
+func prebuiltLibraryBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	prebuiltLibraryStaticBp2Build(ctx, module, true)
+	prebuiltLibrarySharedBp2Build(ctx, module)
+}
+
+func prebuiltLibraryStaticBp2Build(ctx android.TopDownMutatorContext, module *Module, fullBuild bool) {
+	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module, true)
 	exportedIncludes := Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx, module)
 
 	attrs := &bazelPrebuiltLibraryStaticAttributes{
@@ -354,7 +368,10 @@
 	}
 
 	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+	if fullBuild {
+		name += "_bp2build_cc_library_static"
+	}
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
 }
 
 type bazelPrebuiltLibrarySharedAttributes struct {
@@ -362,7 +379,7 @@
 }
 
 func prebuiltLibrarySharedBp2Build(ctx android.TopDownMutatorContext, module *Module) {
-	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module)
+	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module, false)
 
 	attrs := &bazelPrebuiltLibrarySharedAttributes{
 		Shared_library: prebuiltAttrs.Src,
@@ -374,7 +391,7 @@
 	}
 
 	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, attrs)
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
 }
 
 type prebuiltObjectProperties struct {
@@ -510,7 +527,16 @@
 func (p *prebuiltObjectLinker) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 	if len(p.properties.Srcs) > 0 {
-		return p.Prebuilt.SingleSourcePath(ctx)
+		// Copy objects to a name matching the final installed name
+		in := p.Prebuilt.SingleSourcePath(ctx)
+		outputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".o")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        android.CpExecutable,
+			Description: "prebuilt",
+			Output:      outputFile,
+			Input:       in,
+		})
+		return outputFile
 	}
 	return nil
 }
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 814fef6..53169de 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -969,6 +969,22 @@
 				})
 			}
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok {
+			// If it's a Java module with native dependencies through jni,
+			// set the sanitizer for them
+			if jniSanitizeable, ok := mctx.Module().(JniSanitizeable); ok {
+				if jniSanitizeable.IsSanitizerEnabledForJni(mctx, t.name()) {
+					mctx.VisitDirectDeps(func(child android.Module) {
+						if c, ok := child.(PlatformSanitizeable); ok &&
+							mctx.OtherModuleDependencyTag(child) == JniFuzzLibTag &&
+							c.SanitizePropDefined() &&
+							!c.SanitizeNever() &&
+							!c.IsSanitizerExplicitlyDisabled(t) {
+							c.SetSanitizeDep(true)
+						}
+					})
+				}
+			}
+
 			// If an APEX module includes a lib which is enabled for a sanitizer T, then
 			// the APEX module is also enabled for the same sanitizer type.
 			mctx.VisitDirectDeps(func(child android.Module) {
@@ -1280,6 +1296,11 @@
 	AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string)
 }
 
+type JniSanitizeable interface {
+	android.Module
+	IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool
+}
+
 func (c *Module) MinimalRuntimeDep() bool {
 	return c.sanitize.Properties.MinimalRuntimeDep
 }
@@ -1407,7 +1428,7 @@
 			}
 			c.SetSanitizeDep(false)
 		} else if sanitizeable, ok := mctx.Module().(Sanitizeable); ok && sanitizeable.IsSanitizerEnabled(mctx, t.name()) {
-			// APEX modules fall here
+			// APEX and Java fuzz modules fall here
 			sanitizeable.AddSanitizerDependencies(mctx, t.name())
 			mctx.CreateVariations(t.variationName())
 		} else if c, ok := mctx.Module().(*Module); ok {
diff --git a/cc/test.go b/cc/test.go
index ead7877..5703571 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -30,7 +30,8 @@
 	// if set, build against the gtest library. Defaults to true.
 	Gtest *bool
 
-	// if set, use the isolated gtest runner. Defaults to false.
+	// if set, use the isolated gtest runner. Defaults to true if gtest is also true and the arch is Windows, false
+	// otherwise.
 	Isolated *bool
 }
 
@@ -256,6 +257,13 @@
 	return BoolDefault(test.LinkerProperties.Gtest, true)
 }
 
+func (test *testDecorator) isolated(ctx BaseModuleContext) bool {
+	if !ctx.Windows() {
+		return BoolDefault(test.LinkerProperties.Isolated, false)
+	}
+	return BoolDefault(test.LinkerProperties.Isolated, false)
+}
+
 func (test *testDecorator) testBinary() bool {
 	return true
 }
@@ -288,7 +296,7 @@
 	if test.gtest() {
 		if ctx.useSdk() && ctx.Device() {
 			deps.StaticLibs = append(deps.StaticLibs, "libgtest_main_ndk_c++", "libgtest_ndk_c++")
-		} else if BoolDefault(test.LinkerProperties.Isolated, false) {
+		} else if test.isolated(ctx) {
 			deps.StaticLibs = append(deps.StaticLibs, "libgtest_isolated_main")
 			// The isolated library requires liblog, but adding it
 			// as a static library means unit tests cannot override
@@ -424,7 +432,7 @@
 		var options []tradefed.Option
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
 	}
-	if Bool(test.testDecorator.LinkerProperties.Isolated) {
+	if test.isolated(ctx) {
 		configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
 	}
 	if test.Properties.Test_options.Run_test_as != nil {
diff --git a/cc/testing.go b/cc/testing.go
index 32f7c60..ecdae8b 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -29,6 +29,7 @@
 	RegisterBinaryBuildComponents(ctx)
 	RegisterLibraryBuildComponents(ctx)
 	RegisterLibraryHeadersBuildComponents(ctx)
+	RegisterLibraryStubBuildComponents(ctx)
 
 	ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
diff --git a/cc/tidy.go b/cc/tidy.go
index 750e9de..ac1521b 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -76,9 +76,10 @@
 	// the global WITH_TIDY or module 'tidy' property is true.
 	flags.Tidy = true
 
-	// If explicitly enabled, by global default or local tidy property,
+	// If explicitly enabled, by global WITH_TIDY or local tidy:true property,
 	// set flags.NeedTidyFiles to make this module depend on .tidy files.
-	if ctx.Config().ClangTidy() || Bool(tidy.Properties.Tidy) {
+	// Note that locally set tidy:true is ignored if ALLOW_LOCAL_TIDY_TRUE is not set to true.
+	if ctx.Config().IsEnvTrue("WITH_TIDY") || (ctx.Config().IsEnvTrue("ALLOW_LOCAL_TIDY_TRUE") && Bool(tidy.Properties.Tidy)) {
 		flags.NeedTidyFiles = true
 	}
 
@@ -95,10 +96,15 @@
 	if !android.SubstringInList(flags.TidyFlags, "-header-filter=") {
 		defaultDirs := ctx.Config().Getenv("DEFAULT_TIDY_HEADER_DIRS")
 		headerFilter := "-header-filter="
+		// Default header filter should include only the module directory,
+		// not the out/soong/.../ModuleDir/...
+		// Otherwise, there will be too many warnings from generated files in out/...
+		// If a module wants to see warnings in the generated source files,
+		// it should specify its own -header-filter flag.
 		if defaultDirs == "" {
-			headerFilter += ctx.ModuleDir() + "/"
+			headerFilter += "^" + ctx.ModuleDir() + "/"
 		} else {
-			headerFilter += "\"(" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
+			headerFilter += "\"(^" + ctx.ModuleDir() + "/|" + defaultDirs + ")\""
 		}
 		flags.TidyFlags = append(flags.TidyFlags, headerFilter)
 	}
diff --git a/cc/tidy_test.go b/cc/tidy_test.go
new file mode 100644
index 0000000..339b302
--- /dev/null
+++ b/cc/tidy_test.go
@@ -0,0 +1,98 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestWithTidy(t *testing.T) {
+	// When WITH_TIDY=1 or (ALLOW_LOCAL_TIDY_TRUE=1 and local tidy:true)
+	// a C++ library should depend on .tidy files.
+	testCases := []struct {
+		withTidy, allowLocalTidyTrue string // "_" means undefined
+		needTidyFile                 []bool // for {libfoo_0, libfoo_1} and {libbar_0, libbar_1}
+	}{
+		{"_", "_", []bool{false, false, false}},
+		{"_", "0", []bool{false, false, false}},
+		{"_", "1", []bool{false, true, false}},
+		{"_", "true", []bool{false, true, false}},
+		{"0", "_", []bool{false, false, false}},
+		{"0", "1", []bool{false, true, false}},
+		{"1", "_", []bool{true, true, false}},
+		{"1", "false", []bool{true, true, false}},
+		{"1", "1", []bool{true, true, false}},
+		{"true", "_", []bool{true, true, false}},
+	}
+	bp := `
+		cc_library_shared {
+			name: "libfoo_0", // depends on .tidy if WITH_TIDY=1
+			srcs: ["foo.c"],
+		}
+		cc_library_shared { // depends on .tidy if WITH_TIDY=1 or ALLOW_LOCAL_TIDY_TRUE=1
+			name: "libfoo_1",
+			srcs: ["foo.c"],
+			tidy: true,
+		}
+		cc_library_shared { // no .tidy
+			name: "libfoo_2",
+			srcs: ["foo.c"],
+			tidy: false,
+		}
+		cc_library_static {
+			name: "libbar_0", // depends on .tidy if WITH_TIDY=1
+			srcs: ["bar.c"],
+		}
+		cc_library_static { // depends on .tidy if WITH_TIDY=1 or ALLOW_LOCAL_TIDY_TRUE=1
+			name: "libbar_1",
+			srcs: ["bar.c"],
+			tidy: true,
+		}
+		cc_library_static { // no .tidy
+			name: "libbar_2",
+			srcs: ["bar.c"],
+			tidy: false,
+		}`
+	for index, test := range testCases {
+		testName := fmt.Sprintf("case%d,%v,%v", index, test.withTidy, test.allowLocalTidyTrue)
+		t.Run(testName, func(t *testing.T) {
+			testEnv := map[string]string{}
+			if test.withTidy != "_" {
+				testEnv["WITH_TIDY"] = test.withTidy
+			}
+			if test.allowLocalTidyTrue != "_" {
+				testEnv["ALLOW_LOCAL_TIDY_TRUE"] = test.allowLocalTidyTrue
+			}
+			ctx := android.GroupFixturePreparers(prepareForCcTest, android.FixtureMergeEnv(testEnv)).RunTestWithBp(t, bp)
+			for n := 0; n < 3; n++ {
+				checkLibraryRule := func(foo, variant, ruleName string) {
+					libName := fmt.Sprintf("lib%s_%d", foo, n)
+					tidyFile := "out/soong/.intermediates/" + libName + "/" + variant + "/obj/" + foo + ".tidy"
+					depFiles := ctx.ModuleForTests(libName, variant).Rule(ruleName).Validations.Strings()
+					if test.needTidyFile[n] {
+						android.AssertStringListContains(t, libName+" needs .tidy file", depFiles, tidyFile)
+					} else {
+						android.AssertStringListDoesNotContain(t, libName+" does not need .tidy file", depFiles, tidyFile)
+					}
+				}
+				checkLibraryRule("foo", "android_arm64_armv8-a_shared", "ld")
+				checkLibraryRule("bar", "android_arm64_armv8-a_static", "ar")
+			}
+		})
+	}
+}
diff --git a/cc/util.go b/cc/util.go
index b256b9a..4e10037 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -15,9 +15,7 @@
 package cc
 
 import (
-	"fmt"
 	"path/filepath"
-	"regexp"
 	"strings"
 
 	"android/soong/android"
@@ -30,30 +28,12 @@
 	return android.JoinWithPrefix(dirs.Strings(), "-I")
 }
 
-func ldDirsToFlags(dirs []string) string {
-	return android.JoinWithPrefix(dirs, "-L")
-}
-
-func libNamesToFlags(names []string) string {
-	return android.JoinWithPrefix(names, "-l")
-}
-
 var indexList = android.IndexList
 var inList = android.InList
 var filterList = android.FilterList
 var removeListFromList = android.RemoveListFromList
 var removeFromList = android.RemoveFromList
 
-var libNameRegexp = regexp.MustCompile(`^lib(.*)$`)
-
-func moduleToLibName(module string) (string, error) {
-	matches := libNameRegexp.FindStringSubmatch(module)
-	if matches == nil {
-		return "", fmt.Errorf("Library module name %s does not start with lib", module)
-	}
-	return matches[1], nil
-}
-
 func flagsToBuilderFlags(in Flags) builderFlags {
 	return builderFlags{
 		globalCommonFlags:     strings.Join(in.Global.CommonFlags, " "),
@@ -113,13 +93,6 @@
 	return list
 }
 
-func addSuffix(list []string, suffix string) []string {
-	for i := range list {
-		list[i] = list[i] + suffix
-	}
-	return list
-}
-
 // linkDirOnDevice/linkName -> target
 func makeSymlinkCmd(linkDirOnDevice string, linkName string, target string) string {
 	dir := filepath.Join("$(PRODUCT_OUT)", linkDirOnDevice)
diff --git a/cmd/go2bp/go2bp.go b/cmd/go2bp/go2bp.go
index 07cb5df..fb5a746 100644
--- a/cmd/go2bp/go2bp.go
+++ b/cmd/go2bp/go2bp.go
@@ -335,12 +335,15 @@
 	}
 
 	cmd := exec.Command("go", "list", "-json", "./...")
-	output, err := cmd.Output()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to dump the go packages: %v\n", err)
+	var stdoutb, stderrb bytes.Buffer
+	cmd.Stdout = &stdoutb
+	cmd.Stderr = &stderrb
+	if err := cmd.Run(); err != nil {
+		fmt.Fprintf(os.Stderr, "Running %q to dump the Go packages failed: %v, stderr:\n%s\n",
+			cmd.String(), err, stderrb.Bytes())
 		os.Exit(1)
 	}
-	decoder := json.NewDecoder(bytes.NewReader(output))
+	decoder := json.NewDecoder(bytes.NewReader(stdoutb.Bytes()))
 
 	pkgs := []*GoPackage{}
 	pkgMap := map[string]*GoPackage{}
diff --git a/cmd/path_interposer/main.go b/cmd/path_interposer/main.go
index a4fe3e4..8b9de52 100644
--- a/cmd/path_interposer/main.go
+++ b/cmd/path_interposer/main.go
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// This tool tries to prohibit access to tools on the system on which the build
+// is run.
+//
+// The rationale is that if the build uses a binary that is not shipped in the
+// source tree, it is unknowable which version of that binary will be installed
+// and therefore the output of the build will be unpredictable. Therefore, we
+// should make every effort to use only tools under our control.
+//
+// This is currently implemented by a "sandbox" that sets $PATH to a specific,
+// single directory and creates a symlink for every binary in $PATH in it. That
+// symlink will point to path_interposer, which then uses an embedded
+// configuration to determine whether to allow access to the binary (in which
+// case it calls the original executable) or not (in which case it fails). It
+// can also optionally log invocations.
+//
+// This, of course, does not help if one invokes the tool in question with its
+// full path.
 package main
 
 import (
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 36513b6..7bc9ab2 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -196,10 +196,6 @@
 	// If the library is optional or required.
 	Optional bool
 
-	// If the library is implicitly infered by Soong (as opposed to explicitly added via `uses_libs`
-	// or `optional_uses_libs`.
-	Implicit bool
-
 	// On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
 	Host android.Path
 
@@ -290,9 +286,8 @@
 const AnySdkVersion int = android.FutureApiLevelInt
 
 // Add class loader context for the given library to the map entry for the given SDK version.
-func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int,
-	lib string, optional, implicit bool, hostPath, installPath android.Path,
-	nestedClcMap ClassLoaderContextMap) error {
+func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
+	optional bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) error {
 
 	// For prebuilts, library should have the same name as the source module.
 	lib = android.RemoveOptionalPrebuiltPrefix(lib)
@@ -341,7 +336,6 @@
 	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
 		Name:        lib,
 		Optional:    optional,
-		Implicit:    implicit,
 		Host:        hostPath,
 		Device:      devicePath,
 		Subcontexts: subcontexts,
@@ -354,10 +348,9 @@
 // about paths). For the subset of libraries that are used in dexpreopt, their build/install paths
 // are validated later before CLC is used (in validateClassLoaderContext).
 func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, sdkVer int,
-	lib string, optional, implicit bool, hostPath, installPath android.Path,
-	nestedClcMap ClassLoaderContextMap) {
+	lib string, optional bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
 
-	err := clcMap.addContext(ctx, sdkVer, lib, optional, implicit, hostPath, installPath, nestedClcMap)
+	err := clcMap.addContext(ctx, sdkVer, lib, optional, hostPath, installPath, nestedClcMap)
 	if err != nil {
 		ctx.ModuleErrorf(err.Error())
 	}
@@ -401,15 +394,13 @@
 // included). This is the list of libraries that should be in the <uses-library> tags in the
 // manifest. Some of them may be present in the source manifest, others are added by manifest_fixer.
 // Required and optional libraries are in separate lists.
-func (clcMap ClassLoaderContextMap) usesLibs(implicit bool) (required []string, optional []string) {
+func (clcMap ClassLoaderContextMap) UsesLibs() (required []string, optional []string) {
 	if clcMap != nil {
 		clcs := clcMap[AnySdkVersion]
 		required = make([]string, 0, len(clcs))
 		optional = make([]string, 0, len(clcs))
 		for _, clc := range clcs {
-			if implicit && !clc.Implicit {
-				// Skip, this is an explicit library and we need only the implicit ones.
-			} else if clc.Optional {
+			if clc.Optional {
 				optional = append(optional, clc.Name)
 			} else {
 				required = append(required, clc.Name)
@@ -419,14 +410,6 @@
 	return required, optional
 }
 
-func (clcMap ClassLoaderContextMap) UsesLibs() ([]string, []string) {
-	return clcMap.usesLibs(false)
-}
-
-func (clcMap ClassLoaderContextMap) ImplicitUsesLibs() ([]string, []string) {
-	return clcMap.usesLibs(true)
-}
-
 func (clcMap ClassLoaderContextMap) Dump() string {
 	jsonCLC := toJsonClassLoaderContext(clcMap)
 	bytes, err := json.MarshalIndent(jsonCLC, "", "  ")
@@ -631,7 +614,6 @@
 type jsonClassLoaderContext struct {
 	Name        string
 	Optional    bool
-	Implicit    bool
 	Host        string
 	Device      string
 	Subcontexts []*jsonClassLoaderContext
@@ -664,7 +646,6 @@
 		clcs = append(clcs, &ClassLoaderContext{
 			Name:        clc.Name,
 			Optional:    clc.Optional,
-			Implicit:    clc.Implicit,
 			Host:        constructPath(ctx, clc.Host),
 			Device:      clc.Device,
 			Subcontexts: fromJsonClassLoaderContextRec(ctx, clc.Subcontexts),
@@ -678,6 +659,9 @@
 	jClcMap := make(jsonClassLoaderContextMap)
 	for sdkVer, clcs := range clcMap {
 		sdkVerStr := fmt.Sprintf("%d", sdkVer)
+		if sdkVer == AnySdkVersion {
+			sdkVerStr = "any"
+		}
 		jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs)
 	}
 	return jClcMap
@@ -697,7 +681,6 @@
 		jClcs[i] = &jsonClassLoaderContext{
 			Name:        clc.Name,
 			Optional:    clc.Optional,
-			Implicit:    clc.Implicit,
 			Host:        host,
 			Device:      clc.Device,
 			Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts),
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 5d3a9d9..8b3c013 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -50,34 +50,33 @@
 	ctx := testContext()
 
 	optional := false
-	implicit := true
 
 	m := make(ClassLoaderContextMap)
 
-	m.AddContext(ctx, AnySdkVersion, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, AnySdkVersion, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	m.AddContext(ctx, AnySdkVersion, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, AnySdkVersion, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
 
 	// Add some libraries with nested subcontexts.
 
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, AnySdkVersion, "a1", optional, implicit, buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
-	m1.AddContext(ctx, AnySdkVersion, "b1", optional, implicit, buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
+	m1.AddContext(ctx, AnySdkVersion, "a1", optional, buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
+	m1.AddContext(ctx, AnySdkVersion, "b1", optional, buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
 
 	m2 := make(ClassLoaderContextMap)
-	m2.AddContext(ctx, AnySdkVersion, "a2", optional, implicit, buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
-	m2.AddContext(ctx, AnySdkVersion, "b2", optional, implicit, buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
-	m2.AddContext(ctx, AnySdkVersion, "c2", optional, implicit, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
+	m2.AddContext(ctx, AnySdkVersion, "a2", optional, buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
+	m2.AddContext(ctx, AnySdkVersion, "b2", optional, buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
+	m2.AddContext(ctx, AnySdkVersion, "c2", optional, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
 
 	m3 := make(ClassLoaderContextMap)
-	m3.AddContext(ctx, AnySdkVersion, "a3", optional, implicit, buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
-	m3.AddContext(ctx, AnySdkVersion, "b3", optional, implicit, buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
+	m3.AddContext(ctx, AnySdkVersion, "a3", optional, buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
+	m3.AddContext(ctx, AnySdkVersion, "b3", optional, buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
 
-	m.AddContext(ctx, AnySdkVersion, "d", optional, implicit, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
 	// When the same library is both in conditional and unconditional context, it should be removed
 	// from conditional context.
-	m.AddContext(ctx, 42, "f", optional, implicit, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
-	m.AddContext(ctx, AnySdkVersion, "f", optional, implicit, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContext(ctx, 42, "f", optional, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	m.AddContext(ctx, AnySdkVersion, "f", optional, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
 
 	// Merge map with implicit root library that is among toplevel contexts => does nothing.
 	m.AddContextMap(m1, "c")
@@ -86,12 +85,12 @@
 	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	m.AddContext(ctx, 29, AndroidHidlManager, optional, implicit, buildPath(ctx, AndroidHidlManager), nil, nil)
-	m.AddContext(ctx, 29, AndroidHidlBase, optional, implicit, buildPath(ctx, AndroidHidlBase), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlManager, optional, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlBase, optional, buildPath(ctx, AndroidHidlBase), nil, nil)
 
 	// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
 	// needed as a compatibility library if "android.test.runner" is in CLC as well.
-	m.AddContext(ctx, 30, AndroidTestMock, optional, implicit, buildPath(ctx, AndroidTestMock), nil, nil)
+	m.AddContext(ctx, 30, AndroidTestMock, optional, buildPath(ctx, AndroidTestMock), nil, nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -165,12 +164,11 @@
 func TestCLCJson(t *testing.T) {
 	ctx := testContext()
 	optional := false
-	implicit := true
 	m := make(ClassLoaderContextMap)
-	m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, 29, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, 30, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContext(ctx, AnySdkVersion, "d", optional, implicit, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, 29, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, 30, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
 	jsonCLC := toJsonClassLoaderContext(m)
 	restored := fromJsonClassLoaderContext(ctx, jsonCLC)
 	android.AssertIntEquals(t, "The size of the maps should be the same.", len(m), len(restored))
@@ -191,13 +189,12 @@
 func testCLCUnknownPath(t *testing.T, whichPath string) {
 	ctx := testContext()
 	optional := false
-	implicit := true
 
 	m := make(ClassLoaderContextMap)
 	if whichPath == "build" {
-		m.AddContext(ctx, AnySdkVersion, "a", optional, implicit, nil, nil, nil)
+		m.AddContext(ctx, AnySdkVersion, "a", optional, nil, nil, nil)
 	} else {
-		m.AddContext(ctx, AnySdkVersion, "a", optional, implicit, buildPath(ctx, "a"), nil, nil)
+		m.AddContext(ctx, AnySdkVersion, "a", optional, buildPath(ctx, "a"), nil, nil)
 	}
 
 	// The library should be added to <uses-library> tags by the manifest_fixer.
@@ -232,11 +229,10 @@
 func TestCLCNestedConditional(t *testing.T) {
 	ctx := testContext()
 	optional := false
-	implicit := true
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, 42, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m1.AddContext(ctx, 42, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), m1)
+	err := m.addContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), m1)
 	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
@@ -245,12 +241,11 @@
 func TestCLCSdkVersionOrder(t *testing.T) {
 	ctx := testContext()
 	optional := false
-	implicit := true
 	m := make(ClassLoaderContextMap)
-	m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, 29, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, 30, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContext(ctx, AnySdkVersion, "d", optional, implicit, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, 29, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	m.AddContext(ctx, 30, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -287,7 +282,6 @@
 func TestCLCMExcludeLibs(t *testing.T) {
 	ctx := testContext()
 	const optional = false
-	const implicit = true
 
 	excludeLibs := func(t *testing.T, m ClassLoaderContextMap, excluded_libs ...string) ClassLoaderContextMap {
 		// Dump the CLCM before creating a new copy that excludes a specific set of libraries.
@@ -305,7 +299,7 @@
 
 	t.Run("exclude nothing", func(t *testing.T) {
 		m := make(ClassLoaderContextMap)
-		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 
 		a := excludeLibs(t, m)
 
@@ -314,7 +308,6 @@
     {
       "Name": "a",
       "Optional": false,
-      "Implicit": true,
       "Host": "out/soong/a.jar",
       "Device": "/system/a.jar",
       "Subcontexts": []
@@ -325,8 +318,8 @@
 
 	t.Run("one item from list", func(t *testing.T) {
 		m := make(ClassLoaderContextMap)
-		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-		m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+		m.AddContext(ctx, 28, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
 
 		a := excludeLibs(t, m, "a")
 
@@ -335,7 +328,6 @@
     {
       "Name": "b",
       "Optional": false,
-      "Implicit": true,
       "Host": "out/soong/b.jar",
       "Device": "/system/b.jar",
       "Subcontexts": []
@@ -347,8 +339,8 @@
 
 	t.Run("all items from a list", func(t *testing.T) {
 		m := make(ClassLoaderContextMap)
-		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-		m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+		m.AddContext(ctx, 28, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
 
 		a := excludeLibs(t, m, "a", "b")
 
@@ -357,11 +349,11 @@
 
 	t.Run("items from a subcontext", func(t *testing.T) {
 		s := make(ClassLoaderContextMap)
-		s.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-		s.AddContext(ctx, AnySdkVersion, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+		s.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+		s.AddContext(ctx, AnySdkVersion, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
 
 		m := make(ClassLoaderContextMap)
-		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), s)
+		m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), s)
 
 		a := excludeLibs(t, m, "b")
 
@@ -370,14 +362,12 @@
     {
       "Name": "a",
       "Optional": false,
-      "Implicit": true,
       "Host": "out/soong/a.jar",
       "Device": "/system/a.jar",
       "Subcontexts": [
         {
           "Name": "c",
           "Optional": false,
-          "Implicit": true,
           "Host": "out/soong/c.jar",
           "Device": "/system/c.jar",
           "Subcontexts": []
@@ -389,6 +379,35 @@
 	})
 }
 
+// Test that CLC is correctly serialized to JSON.
+func TestCLCtoJSON(t *testing.T) {
+	ctx := testContext()
+	optional := false
+	m := make(ClassLoaderContextMap)
+	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
+	android.AssertStringEquals(t, "output CLCM ", `{
+  "28": [
+    {
+      "Name": "a",
+      "Optional": false,
+      "Host": "out/soong/a.jar",
+      "Device": "/system/a.jar",
+      "Subcontexts": []
+    }
+  ],
+  "any": [
+    {
+      "Name": "b",
+      "Optional": false,
+      "Host": "out/soong/b.jar",
+      "Device": "/system/b.jar",
+      "Subcontexts": []
+    }
+  ]
+}`, m.Dump())
+}
+
 func checkError(t *testing.T, have error, want string) {
 	if have == nil {
 		t.Errorf("\nwant error: '%s'\nhave: none", want)
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 153b025..ab9e418 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -36,8 +36,6 @@
 
 	PreoptWithUpdatableBcp bool // If updatable boot jars are included in dexpreopt or not.
 
-	UseArtImage bool // use the art image (use other boot class path dex files without image)
-
 	HasSystemOther        bool     // store odex files that match PatternsOnSystemOther on the system_other partition
 	PatternsOnSystemOther []string // patterns (using '%' to denote a prefix match) to put odex on the system_other partition
 
diff --git a/docs/tidy.md b/docs/tidy.md
index 890c3a0..2eb8234 100644
--- a/docs/tidy.md
+++ b/docs/tidy.md
@@ -31,7 +31,7 @@
 
 The global default can be overwritten by module properties in Android.bp.
 
-### `tidy` and `tidy_checks`
+### `tidy`, `tidy_checks`, and `ALLOW_LOCAL_TIDY_TRUE`
 
 For example, in
 [system/bpf/Android.bp](https://android.googlesource.com/platform/system/bpf/+/refs/heads/master/Android.bp),
@@ -52,8 +52,16 @@
 }
 ```
 That means in normal builds, even without `WITH_TIDY=1`,
-the modules that use `bpf_defaults` will run clang-tidy
+the modules that use `bpf_defaults` _should_ run clang-tidy
 over C/C++ source files with the given `tidy_checks`.
+
+However since clang-tidy warnings and its runtime cost might
+not be wanted by all people, the default is to ignore the
+`tidy:true` property unless the environment variable
+`ALLOW_LOCAL_TIDY_TRUE` is set to true or 1.
+To run clang-tidy on all modules that should be tested with clang-tidy,
+`ALLOW_LOCAL_TIDY_TRUE` or `WITH_TIDY` should be set to true or 1.
+
 Note that `clang-analyzer-security*` is included in `tidy_checks`
 but not all `clang-analyzer-*` checks. Check `cert-err34-c` is
 disabled, although `cert-*` is selected.
@@ -80,6 +88,9 @@
 }
 ```
 
+Note that `tidy:false` always disables clang-tidy, no matter
+`ALLOW_LOCAL_TIDY_TRUE` is set or not.
+
 ### `tidy_checks_as_errors`
 
 The global tidy checks are enabled as warnings.
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index a142833..719771f 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -474,6 +474,7 @@
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -668,25 +669,17 @@
 
 // For Bazel / bp2build
 
-type bazelPrebuiltEtcAttributes struct {
+type bazelPrebuiltFileAttributes struct {
 	Src         bazel.LabelAttribute
 	Filename    string
-	Sub_dir     string
+	Dir         string
 	Installable bazel.BoolAttribute
 }
 
 // ConvertWithBp2build performs bp2build conversion of PrebuiltEtc
-func (p *PrebuiltEtc) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	// All prebuilt_* modules are PrebuiltEtc, but at this time, we only convert prebuilt_etc modules.
-	if p.installDirBase != "etc" {
-		return
-	}
-
-	prebuiltEtcBp2BuildInternal(ctx, p)
-}
-
-func prebuiltEtcBp2BuildInternal(ctx android.TopDownMutatorContext, module *PrebuiltEtc) {
-	var srcLabelAttribute bazel.LabelAttribute
+// All prebuilt_* modules are PrebuiltEtc, which we treat uniformily as *PrebuiltFile*
+func (module *PrebuiltEtc) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	var src bazel.LabelAttribute
 	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltEtcProperties{}) {
 		for config, p := range configToProps {
 			props, ok := p.(*prebuiltEtcProperties)
@@ -695,7 +688,7 @@
 			}
 			if props.Src != nil {
 				label := android.BazelLabelForModuleSrcSingle(ctx, *props.Src)
-				srcLabelAttribute.SetSelectValue(axis, config, label)
+				src.SetSelectValue(axis, config, label)
 			}
 		}
 	}
@@ -705,26 +698,30 @@
 		filename = *module.properties.Filename
 	}
 
-	var subDir string
-	if module.subdirProperties.Sub_dir != nil {
-		subDir = *module.subdirProperties.Sub_dir
+	var dir = module.installDirBase
+	// prebuilt_file supports only `etc` or `usr/share`
+	if !(dir == "etc" || dir == "usr/share") {
+		return
+	}
+	if subDir := module.subdirProperties.Sub_dir; subDir != nil {
+		dir = dir + "/" + *subDir
 	}
 
-	var installableBoolAttribute bazel.BoolAttribute
-	if module.properties.Installable != nil {
-		installableBoolAttribute.Value = module.properties.Installable
+	var installable bazel.BoolAttribute
+	if install := module.properties.Installable; install != nil {
+		installable.Value = install
 	}
 
-	attrs := &bazelPrebuiltEtcAttributes{
-		Src:         srcLabelAttribute,
+	attrs := &bazelPrebuiltFileAttributes{
+		Src:         src,
 		Filename:    filename,
-		Sub_dir:     subDir,
-		Installable: installableBoolAttribute,
+		Dir:         dir,
+		Installable: installable,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "prebuilt_etc",
-		Bzl_load_location: "//build/bazel/rules:prebuilt_etc.bzl",
+		Rule_class:        "prebuilt_file",
+		Bzl_load_location: "//build/bazel/rules:prebuilt_file.bzl",
 	}
 
 	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 38684d3..857dfa7 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -15,6 +15,7 @@
         "bootimg.go",
         "filesystem.go",
         "logical_partition.go",
+        "raw_binary.go",
         "system_image.go",
         "vbmeta.go",
         "testing.go",
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 33beb37..352b451 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -228,6 +228,15 @@
 	return output
 }
 
+// Calculates avb_salt from some input for deterministic output.
+func (b *bootimg) salt() string {
+	var input []string
+	input = append(input, b.properties.Cmdline...)
+	input = append(input, proptools.StringDefault(b.properties.Partition_name, b.Name()))
+	input = append(input, proptools.String(b.properties.Header_version))
+	return sha1sum(input)
+}
+
 func (b *bootimg) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
 	var sb strings.Builder
 	var deps android.Paths
@@ -248,6 +257,7 @@
 	addStr("avb_add_hash_footer_args", "") // TODO(jiyong): add --rollback_index
 	partitionName := proptools.StringDefault(b.properties.Partition_name, b.Name())
 	addStr("partition_name", partitionName)
+	addStr("avb_salt", b.salt())
 
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
 	android.WriteFileRule(ctx, propFile, sb.String())
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index ccf9e9d..6e1e78a 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -15,7 +15,9 @@
 package filesystem
 
 import (
+	"crypto/sha256"
 	"fmt"
+	"io"
 	"path/filepath"
 	"strings"
 
@@ -88,6 +90,13 @@
 
 	// Symbolic links to be created under root with "ln -sf <target> <name>".
 	Symlinks []symlinkDefinition
+
+	// Seconds since unix epoch to override timestamps of file entries
+	Fake_timestamp *string
+
+	// When set, passed to mkuserimg_mke2fs --mke2fs_uuid & --mke2fs_hash_seed.
+	// Otherwise, they'll be set as random which might cause indeterministic build output.
+	Uuid *string
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -276,6 +285,11 @@
 	return fcBin.OutputPath
 }
 
+// Calculates avb_salt from entry list (sorted) for deterministic output.
+func (f *filesystem) salt() string {
+	return sha1sum(f.entries)
+}
+
 func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
 	type prop struct {
 		name  string
@@ -321,12 +335,19 @@
 		addStr("avb_add_hashtree_footer_args", "--do_not_generate_fec")
 		partitionName := proptools.StringDefault(f.properties.Partition_name, f.Name())
 		addStr("partition_name", partitionName)
+		addStr("avb_salt", f.salt())
 	}
 
 	if proptools.String(f.properties.File_contexts) != "" {
 		addPath("selinux_fc", f.buildFileContexts(ctx))
 	}
-
+	if timestamp := proptools.String(f.properties.Fake_timestamp); timestamp != "" {
+		addStr("timestamp", timestamp)
+	}
+	if uuid := proptools.String(f.properties.Uuid); uuid != "" {
+		addStr("uuid", uuid)
+		addStr("hash_seed", uuid)
+	}
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Text("rm").Flag("-rf").Output(propFile)
@@ -451,3 +472,11 @@
 	}
 	return specs
 }
+
+func sha1sum(values []string) string {
+	h := sha256.New()
+	for _, value := range values {
+		io.WriteString(h, value)
+	}
+	return fmt.Sprintf("%x", h.Sum(nil))
+}
diff --git a/filesystem/raw_binary.go b/filesystem/raw_binary.go
new file mode 100644
index 0000000..f726124
--- /dev/null
+++ b/filesystem/raw_binary.go
@@ -0,0 +1,120 @@
+// Copyright (C) 2022 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 filesystem
+
+import (
+	"fmt"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+var (
+	toRawBinary = pctx.AndroidStaticRule("toRawBinary",
+		blueprint.RuleParams{
+			Command:     "${objcopy} --output-target=binary ${in} ${out}",
+			CommandDeps: []string{"$objcopy"},
+		},
+		"objcopy")
+)
+
+func init() {
+	pctx.Import("android/soong/cc/config")
+
+	android.RegisterModuleType("raw_binary", rawBinaryFactory)
+}
+
+type rawBinary struct {
+	android.ModuleBase
+
+	properties rawBinaryProperties
+
+	output     android.OutputPath
+	installDir android.InstallPath
+}
+
+type rawBinaryProperties struct {
+	// Set the name of the output. Defaults to <module_name>.bin.
+	Stem *string
+
+	// Name of input executable. Can be a name of a target.
+	Src *string `android:"path,arch_variant"`
+}
+
+func rawBinaryFactory() android.Module {
+	module := &rawBinary{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+func (r *rawBinary) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// do nothing
+}
+
+func (r *rawBinary) installFileName() string {
+	return proptools.StringDefault(r.properties.Stem, r.BaseModuleName()+".bin")
+}
+
+func (r *rawBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	inputFile := android.PathForModuleSrc(ctx, proptools.String(r.properties.Src))
+	outputFile := android.PathForModuleOut(ctx, r.installFileName()).OutputPath
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        toRawBinary,
+		Description: "prefix symbols " + outputFile.Base(),
+		Output:      outputFile,
+		Input:       inputFile,
+		Args: map[string]string{
+			"objcopy": "${config.ClangBin}/llvm-objcopy",
+		},
+	})
+
+	r.output = outputFile
+	r.installDir = android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(r.installDir, r.installFileName(), r.output)
+}
+
+var _ android.AndroidMkEntriesProvider = (*rawBinary)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (r *rawBinary) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(r.output),
+	}}
+}
+
+var _ Filesystem = (*rawBinary)(nil)
+
+func (r *rawBinary) OutputPath() android.Path {
+	return r.output
+}
+
+func (r *rawBinary) SignedOutputPath() android.Path {
+	return nil
+}
+
+var _ android.OutputFileProducer = (*rawBinary)(nil)
+
+// Implements android.OutputFileProducer
+func (r *rawBinary) OutputFiles(tag string) (android.Paths, error) {
+	if tag == "" {
+		return []android.Path{r.output}, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}
diff --git a/finder/finder.go b/finder/finder.go
index b4834b1..c5196c8 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -94,6 +94,10 @@
 	// RootDirs are the root directories used to initiate the search
 	RootDirs []string
 
+	// Whether symlinks are followed. If set, symlinks back to their own parent
+	// directory don't work.
+	FollowSymlinks bool
+
 	// ExcludeDirs are directory names that if encountered are removed from the search
 	ExcludeDirs []string
 
@@ -1415,9 +1419,14 @@
 				// If stat fails this is probably a broken or dangling symlink, treat it as a file.
 				subfiles = append(subfiles, child.Name())
 			} else if childStat.IsDir() {
-				// Skip symlink dirs.
-				// We don't have to support symlink dirs because
-				// that would cause duplicates.
+				// Skip symlink dirs if not requested otherwise. Android has a number
+				// of symlinks creating infinite source trees which would otherwise get
+				// us in an infinite loop.
+				// TODO(b/197349722): Revisit this once symlink loops are banned in the
+				// source tree.
+				if f.cacheMetadata.Config.FollowSymlinks {
+					subdirs = append(subdirs, child.Name())
+				}
 			} else {
 				// We do have to support symlink files because the link name might be
 				// different than the target name
diff --git a/finder/finder_test.go b/finder/finder_test.go
index 788dbdd..8f73719 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -90,6 +90,7 @@
 		CacheParams{
 			"/cwd",
 			[]string{root},
+			false,
 			nil,
 			nil,
 			[]string{"findme.txt", "skipme.txt"},
@@ -121,6 +122,7 @@
 		CacheParams{
 			"/cwd",
 			[]string{root},
+			false,
 			nil,
 			nil,
 			[]string{"findme.txt", "skipme.txt"},
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index 89f8187..700cdf0 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -82,6 +82,9 @@
 	Hwasan_options []string `json:"hwasan_options,omitempty"`
 	// Additional options to be passed to HWASAN when running on host in Haiku.
 	Asan_options []string `json:"asan_options,omitempty"`
+	// If there's a Java fuzzer with JNI, a different version of Jazzer would
+	// need to be added to the fuzzer package than one without JNI
+	IsJni *bool `json:"is_jni,omitempty"`
 }
 
 type FuzzProperties struct {
diff --git a/genrule/genrule.go b/genrule/genrule.go
index c52ddee..3531ee6 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -578,7 +578,7 @@
 
 	bazelModuleLabel := g.GetBazelLabel(ctx, g)
 	bazelActionsUsed := false
-	if g.MixedBuildsEnabled(ctx) {
+	if android.MixedBuildsEnabled(ctx) {
 		bazelActionsUsed = g.GenerateBazelBuildActions(ctx, bazelModuleLabel)
 	}
 	if !bazelActionsUsed {
diff --git a/java/aar.go b/java/aar.go
index 8e10253..00ff7e7 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -598,16 +598,26 @@
 // AAR (android library) prebuilts
 //
 
+// Properties for android_library_import
 type AARImportProperties struct {
+	// ARR (android library prebuilt) filepath. Exactly one ARR is required.
 	Aars []string `android:"path"`
-
-	Sdk_version     *string
+	// If not blank, set to the version of the sdk to compile against.
+	// Defaults to private.
+	// Values are of one of the following forms:
+	// 1) numerical API level, "current", "none", or "core_platform"
+	// 2) An SDK kind with an API level: "<sdk kind>_<API level>"
+	// See build/soong/android/sdk_version.go for the complete and up to date list of SDK kinds.
+	// If the SDK kind is empty, it will be set to public
+	Sdk_version *string
+	// If not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
 	Min_sdk_version *string
-
+	// List of java static libraries that the included ARR (android library prebuilts) has dependencies to.
 	Static_libs []string
-	Libs        []string
-
-	// if set to true, run Jetifier against .aar file. Defaults to false.
+	// List of java libraries that the included ARR (android library prebuilts) has dependencies to.
+	Libs []string
+	// If set to true, run Jetifier against .aar file. Defaults to false.
 	Jetifier *bool
 }
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 7772b70..a297b2c 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -82,8 +82,8 @@
 		if minSdkVersion.FinalOrFutureInt() >= 23 {
 			args = append(args, fmt.Sprintf("--extract-native-libs=%v", !params.UseEmbeddedNativeLibs))
 		} else if params.UseEmbeddedNativeLibs {
-			ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it",
-				minSdkVersion)
+			ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%s doesn't support it",
+				minSdkVersion.String())
 		}
 	}
 
@@ -96,9 +96,9 @@
 	}
 
 	if params.ClassLoaderContexts != nil {
-		// manifest_fixer should add only the implicit SDK libraries inferred by Soong, not those added
-		// explicitly via `uses_libs`/`optional_uses_libs`.
-		requiredUsesLibs, optionalUsesLibs := params.ClassLoaderContexts.ImplicitUsesLibs()
+		// Libraries propagated via `uses_libs`/`optional_uses_libs` are also added (they may be
+		// propagated from dependencies).
+		requiredUsesLibs, optionalUsesLibs := params.ClassLoaderContexts.UsesLibs()
 
 		for _, usesLib := range requiredUsesLibs {
 			args = append(args, "--uses-library", usesLib)
diff --git a/java/androidmk.go b/java/androidmk.go
index 80b828d..439b1d1 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -132,6 +132,16 @@
 	return entriesList
 }
 
+func (j *JavaFuzzLibrary) AndroidMkEntries() []android.AndroidMkEntries {
+	entriesList := j.Library.AndroidMkEntries()
+	entries := &entriesList[0]
+	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+		entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", "null-suite")
+		androidMkWriteTestData(j.jniFilePaths, entries)
+	})
+	return entriesList
+}
+
 // Called for modules that are a component of a test suite.
 func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string, perTestcaseDirectory bool) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
@@ -532,6 +542,9 @@
 	if !outputFile.Valid() {
 		outputFile = android.OptionalPathForPath(dstubs.apiFile)
 	}
+	if !outputFile.Valid() {
+		outputFile = android.OptionalPathForPath(dstubs.apiVersionsXml)
+	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: outputFile,
diff --git a/java/app.go b/java/app.go
index 2b52eab..5f14cef 100755
--- a/java/app.go
+++ b/java/app.go
@@ -299,10 +299,6 @@
 
 // If an updatable APK sets min_sdk_version, min_sdk_vesion of JNI libs should match with it.
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
-// b/155209650: until min_sdk_version is properly supported, use sdk_version instead.
-// because, sdk_version is overridden by min_sdk_version (if set as smaller)
-// and sdkLinkType is checked with dependencies so we can be sure that the whole dependency tree
-// will meet the requirements.
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
 	ctx.VisitDirectDeps(func(m android.Module) {
@@ -313,10 +309,10 @@
 		// The domain of cc.sdk_version is "current" and <number>
 		// We can rely on android.SdkSpec to convert it to <number> so that "current" is
 		// handled properly regardless of sdk finalization.
-		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.SdkVersion()).EffectiveVersion(ctx)
+		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.MinSdkVersion()).EffectiveVersion(ctx)
 		if err != nil || minSdkVersion.LessThan(jniSdkVersion) {
-			ctx.OtherModuleErrorf(dep, "sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
-				dep.SdkVersion(), minSdkVersion, ctx.ModuleName())
+			ctx.OtherModuleErrorf(dep, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
+				dep.MinSdkVersion(), minSdkVersion, ctx.ModuleName())
 			return
 		}
 
@@ -842,6 +838,10 @@
 	return Bool(a.appProperties.Updatable)
 }
 
+func (a *AndroidApp) SetUpdatable(val bool) {
+	a.appProperties.Updatable = &val
+}
+
 func (a *AndroidApp) getCertString(ctx android.BaseModuleContext) string {
 	certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(ctx.ModuleName())
 	if overridden {
@@ -900,6 +900,7 @@
 	module.Module.dexProperties.Optimize.Shrink = proptools.BoolPtr(true)
 
 	module.Module.properties.Instrument = true
+	module.Module.properties.Supports_static_instrumentation = true
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 
 	module.addHostAndDeviceProperties()
@@ -1019,6 +1020,7 @@
 	module.Module.dexProperties.Optimize.EnabledByDefault = true
 
 	module.Module.properties.Instrument = true
+	module.Module.properties.Supports_static_instrumentation = true
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true)
 	module.appProperties.AlwaysPackageNativeLibs = true
@@ -1225,28 +1227,17 @@
 
 func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) {
 	if !ctx.Config().UnbundledBuild() || ctx.Config().UnbundledBuildImage() {
-		reqTag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false, false)
-		ctx.AddVariationDependencies(nil, reqTag, u.usesLibraryProperties.Uses_libs...)
-
-		optTag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true, false)
-		ctx.AddVariationDependencies(nil, optTag, u.presentOptionalUsesLibs(ctx)...)
-
+		ctx.AddVariationDependencies(nil, usesLibReqTag, u.usesLibraryProperties.Uses_libs...)
+		ctx.AddVariationDependencies(nil, usesLibOptTag, u.presentOptionalUsesLibs(ctx)...)
 		// Only add these extra dependencies if the module depends on framework libs. This avoids
 		// creating a cyclic dependency:
 		//     e.g. framework-res -> org.apache.http.legacy -> ... -> framework-res.
 		if hasFrameworkLibs {
-			// Add implicit <uses-library> dependencies on compatibility libraries. Some of them are
-			// optional, and some required --- this depends on the most common usage of the library
-			// and may be wrong for some apps (they need explicit `uses_libs`/`optional_uses_libs`).
-
-			compat28OptTag := makeUsesLibraryDependencyTag(28, true, true)
-			ctx.AddVariationDependencies(nil, compat28OptTag, dexpreopt.OptionalCompatUsesLibs28...)
-
-			compat29ReqTag := makeUsesLibraryDependencyTag(29, false, true)
-			ctx.AddVariationDependencies(nil, compat29ReqTag, dexpreopt.CompatUsesLibs29...)
-
-			compat30OptTag := makeUsesLibraryDependencyTag(30, true, true)
-			ctx.AddVariationDependencies(nil, compat30OptTag, dexpreopt.OptionalCompatUsesLibs30...)
+			// Dexpreopt needs paths to the dex jars of these libraries in order to construct
+			// class loader context for dex2oat. Add them as a dependency with a special tag.
+			ctx.AddVariationDependencies(nil, usesLibCompat29ReqTag, dexpreopt.CompatUsesLibs29...)
+			ctx.AddVariationDependencies(nil, usesLibCompat28OptTag, dexpreopt.OptionalCompatUsesLibs28...)
+			ctx.AddVariationDependencies(nil, usesLibCompat30OptTag, dexpreopt.OptionalCompatUsesLibs30...)
 		}
 	}
 }
@@ -1305,7 +1296,7 @@
 				replaceInList(u.usesLibraryProperties.Uses_libs, dep, libName)
 				replaceInList(u.usesLibraryProperties.Optional_uses_libs, dep, libName)
 			}
-			clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional, tag.implicit,
+			clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional,
 				lib.DexJarBuildPath().PathOrNil(), lib.DexJarInstallPath(),
 				lib.ClassLoaderContexts())
 		} else if ctx.Config().AllowMissingDependencies() {
diff --git a/java/app_import.go b/java/app_import.go
index a1c4d58..b017eca 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -466,7 +466,7 @@
 //                 apk: "prebuilts/example_xhdpi.apk",
 //             },
 //         },
-//         certificate: "PRESIGNED",
+//         presigned: true,
 //     }
 func AndroidAppImportFactory() android.Module {
 	module := &AndroidAppImport{}
diff --git a/java/app_test.go b/java/app_test.go
index 6a4508c..b83a333 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -427,7 +427,8 @@
 			name: "libjni",
 			stl: "none",
 			system_shared_libs: [],
-			sdk_version: "29",
+			sdk_version: "current",
+			min_sdk_version: "29",
 		}
 	`
 	fs := map[string][]byte{
@@ -481,12 +482,13 @@
 			name: "libjni",
 			stl: "none",
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
-	testJavaError(t, `"libjni" .*: sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
+	testJavaError(t, `"libjni" .*: min_sdk_version\(current\) is higher than min_sdk_version\(29\)`, bp)
 }
 
-func TestUpdatableApps_ErrorIfDepSdkVersionIsHigher(t *testing.T) {
+func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) {
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -503,6 +505,7 @@
 			shared_libs: ["libbar"],
 			system_shared_libs: [],
 			sdk_version: "27",
+			min_sdk_version: "27",
 		}
 
 		cc_library {
@@ -510,6 +513,7 @@
 			stl: "none",
 			system_shared_libs: [],
 			sdk_version: "current",
+			min_sdk_version: "current",
 		}
 	`
 	testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp)
@@ -2505,12 +2509,20 @@
 	prebuilt := result.ModuleForTests("prebuilt", "android_common")
 
 	// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
-	// This should not include explicit `uses_libs`/`optional_uses_libs` entries.
+	// These also include explicit `uses_libs`/`optional_uses_libs` entries, as they may be
+	// propagated from dependencies.
 	actualManifestFixerArgs := app.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
 	expectManifestFixerArgs := `--extract-native-libs=true ` +
 		`--uses-library qux ` +
 		`--uses-library quuz ` +
-		`--uses-library runtime-library`
+		`--uses-library foo ` +
+		`--uses-library com.non.sdk.lib ` +
+		`--uses-library runtime-library ` +
+		`--uses-library runtime-required-x ` +
+		`--uses-library runtime-required-y ` +
+		`--optional-uses-library bar ` +
+		`--optional-uses-library runtime-optional-x ` +
+		`--optional-uses-library runtime-optional-y`
 	android.AssertStringDoesContain(t, "manifest_fixer args", actualManifestFixerArgs, expectManifestFixerArgs)
 
 	// Test that all libraries are verified (library order matters).
diff --git a/java/base.go b/java/base.go
index fe5c3e9..0900daa 100644
--- a/java/base.go
+++ b/java/base.go
@@ -170,6 +170,9 @@
 	}
 
 	Instrument bool `blueprint:"mutated"`
+	// If true, then the module supports statically including the jacocoagent
+	// into the library.
+	Supports_static_instrumentation bool `blueprint:"mutated"`
 
 	// List of files to include in the META-INF/services folder of the resulting jar.
 	Services []string `android:"path,arch_variant"`
@@ -185,12 +188,12 @@
 // constructing a new module.
 type DeviceProperties struct {
 	// If not blank, set to the version of the sdk to compile against.
-	// Defaults to compiling against the current platform.
+	// Defaults to private.
 	// Values are of one of the following forms:
-	// 1) numerical API level or "current"
-	// 2) An SDK kind with an API level: "<sdk kind>_<API level>". See
-	// build/soong/android/sdk_version.go for the complete and up to date list of
-	// SDK kinds. If the SDK kind value is empty, it will be set to public.
+	// 1) numerical API level, "current", "none", or "core_platform"
+	// 2) An SDK kind with an API level: "<sdk kind>_<API level>"
+	// See build/soong/android/sdk_version.go for the complete and up to date list of SDK kinds.
+	// If the SDK kind is empty, it will be set to public.
 	Sdk_version *string
 
 	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
@@ -207,7 +210,7 @@
 
 	// Whether to compile against the platform APIs instead of an SDK.
 	// If true, then sdk_version must be empty. The value of this field
-	// is ignored when module's type isn't android_app.
+	// is ignored when module's type isn't android_app, android_test, or android_test_helper_app.
 	Platform_apis *bool
 
 	Aidl struct {
@@ -442,9 +445,6 @@
 	// manifest file to use instead of properties.Manifest
 	overrideManifest android.OptionalPath
 
-	// map of SDK version to class loader context
-	classLoaderContexts dexpreopt.ClassLoaderContextMap
-
 	// list of plugins that this java module is exporting
 	exportedPluginJars android.Paths
 
@@ -605,7 +605,8 @@
 }
 
 func (j *Module) shouldInstrumentStatic(ctx android.BaseModuleContext) bool {
-	return j.shouldInstrument(ctx) &&
+	return j.properties.Supports_static_instrumentation &&
+		j.shouldInstrument(ctx) &&
 		(ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_STATIC") ||
 			ctx.Config().UnbundledBuild())
 }
@@ -723,8 +724,10 @@
 			if component, ok := dep.(SdkLibraryComponentDependency); ok {
 				if lib := component.OptionalSdkLibraryImplementation(); lib != nil {
 					// Add library as optional if it's one of the optional compatibility libs.
-					optional := android.InList(*lib, dexpreopt.OptionalCompatUsesLibs)
-					tag := makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, optional, true)
+					tag := usesLibReqTag
+					if android.InList(*lib, dexpreopt.OptionalCompatUsesLibs) {
+						tag = usesLibOptTag
+					}
 					ctx.AddVariationDependencies(nil, tag, *lib)
 				}
 			}
@@ -745,9 +748,7 @@
 		// Kotlin files
 		ctx.AddVariationDependencies(nil, kotlinStdlibTag,
 			"kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8")
-		if len(j.properties.Plugins) > 0 {
-			ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations")
-		}
+		ctx.AddVariationDependencies(nil, kotlinAnnotationsTag, "kotlin-annotations")
 	}
 
 	// Framework libraries need special handling in static coverage builds: they should not have
@@ -1019,6 +1020,7 @@
 		ctx.PropertyErrorf("common_srcs", "common_srcs must be .kt files")
 	}
 
+	nonGeneratedSrcJars := srcFiles.FilterByExt(".srcjar")
 	srcFiles = j.genSources(ctx, srcFiles, flags)
 
 	// Collect javac flags only after computing the full set of srcFiles to
@@ -1103,8 +1105,6 @@
 		flags.classpath = append(flags.classpath, deps.kotlinStdlib...)
 		flags.classpath = append(flags.classpath, deps.kotlinAnnotations...)
 
-		flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...)
-
 		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.bootClasspath...)
 		flags.kotlincClasspath = append(flags.kotlincClasspath, flags.classpath...)
 
@@ -1136,9 +1136,12 @@
 		// Jar kotlin classes into the final jar after javac
 		if BoolDefault(j.properties.Static_kotlin_stdlib, true) {
 			kotlinJars = append(kotlinJars, deps.kotlinStdlib...)
+			kotlinJars = append(kotlinJars, deps.kotlinAnnotations...)
 			kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinStdlib...)
+			kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinAnnotations...)
 		} else {
 			flags.dexClasspath = append(flags.dexClasspath, deps.kotlinStdlib...)
+			flags.dexClasspath = append(flags.dexClasspath, deps.kotlinAnnotations...)
 		}
 	}
 
@@ -1481,22 +1484,22 @@
 	}
 
 	if ctx.Device() {
-		lintSDKVersionString := func(sdkSpec android.SdkSpec) string {
+		lintSDKVersion := func(sdkSpec android.SdkSpec) android.ApiLevel {
 			if v := sdkSpec.ApiLevel; !v.IsPreview() {
-				return v.String()
+				return v
 			} else {
-				return ctx.Config().DefaultAppTargetSdk(ctx).String()
+				return ctx.Config().DefaultAppTargetSdk(ctx)
 			}
 		}
 
 		j.linter.name = ctx.ModuleName()
-		j.linter.srcs = srcFiles
-		j.linter.srcJars = srcJars
+		j.linter.srcs = append(srcFiles, nonGeneratedSrcJars...)
+		j.linter.srcJars, _ = android.FilterPathList(srcJars, nonGeneratedSrcJars)
 		j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...)
 		j.linter.classes = j.implementationJarFile
-		j.linter.minSdkVersion = lintSDKVersionString(j.MinSdkVersion(ctx))
-		j.linter.targetSdkVersion = lintSDKVersionString(j.TargetSdkVersion(ctx))
-		j.linter.compileSdkVersion = lintSDKVersionString(j.SdkVersion(ctx))
+		j.linter.minSdkVersion = lintSDKVersion(j.MinSdkVersion(ctx))
+		j.linter.targetSdkVersion = lintSDKVersion(j.TargetSdkVersion(ctx))
+		j.linter.compileSdkVersion = lintSDKVersion(j.SdkVersion(ctx))
 		j.linter.compileSdkKind = j.SdkVersion(ctx).Kind
 		j.linter.javaLanguageLevel = flags.javaVersion.String()
 		j.linter.kotlinLanguageLevel = "1.3"
@@ -1694,6 +1697,8 @@
 		dpInfo.Jarjar_rules = append(dpInfo.Jarjar_rules, j.expandJarjarRules.String())
 	}
 	dpInfo.Paths = append(dpInfo.Paths, j.modulePaths...)
+	dpInfo.Static_libs = append(dpInfo.Static_libs, j.properties.Static_libs...)
+	dpInfo.Libs = append(dpInfo.Libs, j.properties.Libs...)
 }
 
 func (j *Module) CompilerDeps() []string {
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 52ce77d..f4cef7f 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -84,6 +84,9 @@
 		}
 	}
 
+	target := ctx.Module().Target()
+	variations = append(variations, target.Variations()...)
+
 	addedDep := false
 	if ctx.OtherModuleDependencyVariantExists(variations, name) {
 		ctx.AddFarVariationDependencies(variations, tag, name)
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index c3a5d5f..0345aad 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -273,7 +273,7 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, BOOTCLASSPATH)
-	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 
 	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
 		// If code coverage has been enabled for the framework then append the properties with
diff --git a/java/config/config.go b/java/config/config.go
index 95b841f..d744002 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -68,6 +68,11 @@
 		"-J-XX:+TieredCompilation",
 		"-J-XX:TieredStopAtLevel=1",
 	}
+	dexerJavaVmFlagsList = []string{
+		`-JXX:OnError="cat hs_err_pid%p.log"`,
+		"-JXX:CICompilerCount=6",
+		"-JXX:+UseDynamicNumberOfGCThreads",
+	}
 )
 
 func init() {
@@ -81,11 +86,16 @@
 	exportedVars.ExportStringStaticVariable("ErrorProneHeapSize", "4096M")
 	exportedVars.ExportStringStaticVariable("ErrorProneHeapFlags", "-J-Xmx${ErrorProneHeapSize}")
 
-	exportedVars.ExportStringListStaticVariable("DexFlags", []string{
-		`-JXX:OnError="cat hs_err_pid%p.log"`,
-		"-JXX:CICompilerCount=6",
-		"-JXX:+UseDynamicNumberOfGCThreads",
-	})
+	// D8 invocations are shorter lived, so we restrict their JIT tiering relative to R8.
+	// Note that the `-JXX` prefix syntax is specific to the R8/D8 invocation wrappers.
+	exportedVars.ExportStringListStaticVariable("D8Flags", append([]string{
+		"-JXmx2048M",
+		"-JXX:+TieredCompilation",
+		"-JXX:TieredStopAtLevel=1",
+	}, dexerJavaVmFlagsList...))
+	exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{
+		"-JXmx2048M",
+	}, dexerJavaVmFlagsList...))
 
 	exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
 		`-Xmaxerrs 9999999`,
diff --git a/java/config/makevars.go b/java/config/makevars.go
index df447a1..273aca0 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -43,9 +43,10 @@
 	ctx.Strict("JAVADOC", "${JavadocCmd}")
 	ctx.Strict("COMMON_JDK_FLAGS", "${CommonJdkFlags}")
 
-	ctx.Strict("DX", "${D8Cmd}")
-	ctx.Strict("DX_COMMAND", "${D8Cmd} -JXms16M -JXmx2048M")
-	ctx.Strict("R8_COMPAT_PROGUARD", "${R8Cmd}")
+	ctx.Strict("D8", "${D8Cmd}")
+	ctx.Strict("R8", "${R8Cmd}")
+	ctx.Strict("D8_COMMAND", "${D8Cmd} ${D8Flags}")
+	ctx.Strict("R8_COMMAND", "${R8Cmd} ${R8Flags}")
 
 	ctx.Strict("TURBINE", "${TurbineJar}")
 
@@ -78,8 +79,6 @@
 	ctx.Strict("CLASS2NONSDKLIST", "${Class2NonSdkList}")
 	ctx.Strict("HIDDENAPI", "${HiddenAPI}")
 
-	ctx.Strict("DEX_FLAGS", "${DexFlags}")
-
 	ctx.Strict("AIDL", "${AidlCmd}")
 	ctx.Strict("AAPT2", "${Aapt2Cmd}")
 	ctx.Strict("ZIPALIGN", "${ZipAlign}")
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index cf39746..513c606 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -138,11 +138,29 @@
     },
 }
 
+// Same as core-module-lib-stubs-for-system-modules, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "core-module-lib-stubs-for-system-modules-no-annotations",
+    visibility: ["//visibility:private"],
+    static_libs: [
+        "core-module-lib-stubs-for-system-modules",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    dist: {
+        dest: "system-modules/module-lib/core-for-system-modules-no-annotations.jar",
+        targets: dist_targets,
+    },
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 // Used when compiling higher-level code with sdk_version "module_current"
 java_system_modules {
     name: "core-module-lib-stubs-system-modules",
     libs: [
-        "core-module-lib-stubs-for-system-modules",
+        "core-module-lib-stubs-for-system-modules-no-annotations",
     ],
     visibility: ["//visibility:public"],
 }
@@ -174,6 +192,24 @@
     patch_module: "java.base",
 }
 
+// Same as legacy.core.platform.api.stubs, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "legacy.core.platform.api.no.annotations.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "legacy.core.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 java_library {
     name: "stable.core.platform.api.stubs",
     visibility: core_platform_visibility,
@@ -191,12 +227,30 @@
     patch_module: "java.base",
 }
 
+// Same as stable.core.platform.api.stubs, but android annotations are
+// stripped. This is used by the Java toolchain, while the annotated stub is to
+// be used by Kotlin one.
+java_library {
+    name: "stable.core.platform.api.no.annotations.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "stable.core.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 // Used when compiling higher-level code against *.core.platform.api.stubs.
 java_system_modules {
     name: "legacy-core-platform-api-stubs-system-modules",
     visibility: core_platform_visibility,
     libs: [
-        "legacy.core.platform.api.stubs",
+        "legacy.core.platform.api.no.annotations.stubs",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
         "core-lambda-stubs-for-system-modules",
@@ -212,7 +266,7 @@
     name: "stable-core-platform-api-stubs-system-modules",
     visibility: core_platform_visibility,
     libs: [
-        "stable.core.platform.api.stubs",
+        "stable.core.platform.api.no.annotations.stubs",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
         "core-lambda-stubs-for-system-modules",
diff --git a/java/core-libraries/jarjar-strip-annotations-rules.txt b/java/core-libraries/jarjar-strip-annotations-rules.txt
new file mode 100644
index 0000000..a1c261b
--- /dev/null
+++ b/java/core-libraries/jarjar-strip-annotations-rules.txt
@@ -0,0 +1,4 @@
+strip-annotation android.annotation.NotNull
+strip-annotation android.annotation.Nullable
+strip-annotation androidx.annotation.RecentlyNonNull
+strip-annotation androidx.annotation.RecentlyNullable
diff --git a/java/dex.go b/java/dex.go
index 84665e7..13d6e4a 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -95,7 +95,7 @@
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
 			`mkdir -p $$(dirname $tmpJar) && ` +
 			`${config.Zip2ZipCmd} -i $in -o $tmpJar -x '**/*.dex' && ` +
-			`$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $tmpJar && ` +
+			`$d8Template${config.D8Cmd} ${config.D8Flags} --output $outDir $d8Flags $tmpJar && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
 			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in`,
 		CommandDeps: []string{
@@ -128,7 +128,7 @@
 			`mkdir -p $$(dirname ${outUsage}) && ` +
 			`mkdir -p $$(dirname $tmpJar) && ` +
 			`${config.Zip2ZipCmd} -i $in -o $tmpJar -x '**/*.dex' && ` +
-			`$r8Template${config.R8Cmd} ${config.DexFlags} -injars $tmpJar --output $outDir ` +
+			`$r8Template${config.R8Cmd} ${config.R8Flags} -injars $tmpJar --output $outDir ` +
 			`--no-data-resources ` +
 			`-printmapping ${outDict} ` +
 			`-printusage ${outUsage} ` +
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 7c5f055..0adaf99 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -259,10 +259,6 @@
 	isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
 
 	bootImage := defaultBootImageConfig(ctx)
-	if global.UseArtImage {
-		bootImage = artBootImageConfig(ctx)
-	}
-
 	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
 
 	targets := ctx.MultiTargets()
diff --git a/java/dexpreopt.go_v1 b/java/dexpreopt.go_v1
new file mode 100644
index 0000000..0adaf99
--- /dev/null
+++ b/java/dexpreopt.go_v1
@@ -0,0 +1,404 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+)
+
+type DexpreopterInterface interface {
+	IsInstallable() bool // Structs that embed dexpreopter must implement this.
+	dexpreoptDisabled(ctx android.BaseModuleContext) bool
+	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
+	AndroidMkEntriesForApex() []android.AndroidMkEntries
+}
+
+type dexpreopterInstall struct {
+	// A unique name to distinguish an output from others for the same java library module. Usually in
+	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
+	name string
+
+	// The name of the input java module.
+	moduleName string
+
+	// The path to the dexpreopt output on host.
+	outputPathOnHost android.Path
+
+	// The directory on the device for the output to install to.
+	installDirOnDevice android.InstallPath
+
+	// The basename (the last segment of the path) for the output to install as.
+	installFileOnDevice string
+}
+
+// The full module name of the output in the makefile.
+func (install *dexpreopterInstall) FullModuleName() string {
+	return install.moduleName + install.SubModuleName()
+}
+
+// The sub-module name of the output in the makefile (the name excluding the java module name).
+func (install *dexpreopterInstall) SubModuleName() string {
+	return "-dexpreopt-" + install.name
+}
+
+// Returns Make entries for installing the file.
+//
+// This function uses a value receiver rather than a pointer receiver to ensure that the object is
+// safe to use in `android.AndroidMkExtraEntriesFunc`.
+func (install dexpreopterInstall) ToMakeEntries() android.AndroidMkEntries {
+	return android.AndroidMkEntries{
+		Class:      "ETC",
+		SubName:    install.SubModuleName(),
+		OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
+				entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
+			},
+		},
+	}
+}
+
+type dexpreopter struct {
+	dexpreoptProperties DexpreoptProperties
+
+	installPath         android.InstallPath
+	uncompressedDex     bool
+	isSDKLibrary        bool
+	isApp               bool
+	isTest              bool
+	isPresignedPrebuilt bool
+	preventInstall      bool
+
+	manifestFile        android.Path
+	statusFile          android.WritablePath
+	enforceUsesLibs     bool
+	classLoaderContexts dexpreopt.ClassLoaderContextMap
+
+	// See the `dexpreopt` function for details.
+	builtInstalled        string
+	builtInstalledForApex []dexpreopterInstall
+
+	// The config is used for two purposes:
+	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
+	//   a <uses-library> is defined in Android.bp, but used in Android.mk (see dex_preopt_config_merger.py).
+	//   Note that dexpreopt.config might be needed even if dexpreopt is disabled for the library itself.
+	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
+	//   dexpreopt another partition).
+	configPath android.WritablePath
+}
+
+type DexpreoptProperties struct {
+	Dex_preopt struct {
+		// If false, prevent dexpreopting.  Defaults to true.
+		Enabled *bool
+
+		// If true, generate an app image (.art file) for this module.
+		App_image *bool
+
+		// If true, use a checked-in profile to guide optimization.  Defaults to false unless
+		// a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR
+		// that matches the name of this module, in which case it is defaulted to true.
+		Profile_guided *bool
+
+		// If set, provides the path to profile relative to the Android.bp file.  If not set,
+		// defaults to searching for a file that matches the name of this module in the default
+		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
+		Profile *string `android:"path"`
+	}
+}
+
+func init() {
+	dexpreopt.DexpreoptRunningInSoong = true
+}
+
+func isApexVariant(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return !apexInfo.IsForPlatform()
+}
+
+func forPrebuiltApex(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return apexInfo.ForPrebuiltApex
+}
+
+func moduleName(ctx android.BaseModuleContext) string {
+	// Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not
+	// expected by dexpreopter.
+	return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
+}
+
+func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool {
+	if !ctx.Device() {
+		return true
+	}
+
+	if d.isTest {
+		return true
+	}
+
+	if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) {
+		return true
+	}
+
+	// If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be
+	// dexpreopted.
+	if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) {
+		return true
+	}
+
+	if !android.IsModulePreferred(ctx.Module()) {
+		return true
+	}
+
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	if global.DisablePreopt {
+		return true
+	}
+
+	if inList(moduleName(ctx), global.DisablePreoptModules) {
+		return true
+	}
+
+	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+	if isApexVariant(ctx) {
+		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
+		// building the entire system image.
+		if !isApexSystemServerJar || ctx.Config().UnbundledBuild() {
+			return true
+		}
+	} else {
+		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
+		if isApexSystemServerJar {
+			return true
+		}
+	}
+
+	// TODO: contains no java code
+
+	return false
+}
+
+func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) {
+	if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
+		return
+	}
+	dexpreopt.RegisterToolDeps(ctx)
+}
+
+func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
+	return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+}
+
+// Returns the install path of the dex jar of a module.
+//
+// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
+// than the `name` in the path `/apex/<name>` as suggested in its comment.
+//
+// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a
+// system server jar, which is fine because we currently only preopt system server jars for APEXes.
+func (d *dexpreopter) getInstallPath(
+	ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, moduleName(ctx))
+		return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/"))
+	}
+	if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) &&
+		filepath.Base(defaultInstallPath.PartitionDir()) != "apex" {
+		ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt")
+	}
+	return defaultInstallPath
+}
+
+func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
+	// the dexpreopter struct hasn't been fully initialized before we're called,
+	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
+	// disabled, even if installable is true.
+	if d.installPath.Base() == "." {
+		return
+	}
+
+	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
+
+	providesUsesLib := moduleName(ctx)
+	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
+		name := ulib.ProvidesUsesLib()
+		if name != nil {
+			providesUsesLib = *name
+		}
+	}
+
+	// If it is test, make config files regardless of its dexpreopt setting.
+	// The config files are required for apps defined in make which depend on the lib.
+	if d.isTest && d.dexpreoptDisabled(ctx) {
+		return
+	}
+
+	isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+
+	bootImage := defaultBootImageConfig(ctx)
+	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
+
+	targets := ctx.MultiTargets()
+	if len(targets) == 0 {
+		// assume this is a java library, dexpreopt for all arches for now
+		for _, target := range ctx.Config().Targets[android.Android] {
+			if target.NativeBridge == android.NativeBridgeDisabled {
+				targets = append(targets, target)
+			}
+		}
+		if isSystemServerJar && !d.isSDKLibrary {
+			// If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
+			targets = targets[:1]
+		}
+	}
+
+	var archs []android.ArchType
+	var images android.Paths
+	var imagesDeps []android.OutputPaths
+	for _, target := range targets {
+		archs = append(archs, target.Arch.ArchType)
+		variant := bootImage.getVariant(target)
+		images = append(images, variant.imagePathOnHost)
+		imagesDeps = append(imagesDeps, variant.imagesDeps)
+	}
+	// The image locations for all Android variants are identical.
+	hostImageLocations, deviceImageLocations := bootImage.getAnyAndroidVariant().imageLocations()
+
+	var profileClassListing android.OptionalPath
+	var profileBootListing android.OptionalPath
+	profileIsTextListing := false
+	if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) {
+		// If dex_preopt.profile_guided is not set, default it based on the existence of the
+		// dexprepot.profile option or the profile class listing.
+		if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
+			profileClassListing = android.OptionalPathForPath(
+				android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
+			profileBootListing = android.ExistentPathForSource(ctx,
+				ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot")
+			profileIsTextListing = true
+		} else if global.ProfileDir != "" {
+			profileClassListing = android.ExistentPathForSource(ctx,
+				global.ProfileDir, moduleName(ctx)+".prof")
+		}
+	}
+
+	// Full dexpreopt config, used to create dexpreopt build rules.
+	dexpreoptConfig := &dexpreopt.ModuleConfig{
+		Name:            moduleName(ctx),
+		DexLocation:     dexLocation,
+		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath,
+		DexPath:         dexJarFile,
+		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
+		UncompressedDex: d.uncompressedDex,
+		HasApkLibraries: false,
+		PreoptFlags:     nil,
+
+		ProfileClassListing:  profileClassListing,
+		ProfileIsTextListing: profileIsTextListing,
+		ProfileBootListing:   profileBootListing,
+
+		EnforceUsesLibrariesStatusFile: dexpreopt.UsesLibrariesStatusFile(ctx),
+		EnforceUsesLibraries:           d.enforceUsesLibs,
+		ProvidesUsesLibrary:            providesUsesLib,
+		ClassLoaderContexts:            d.classLoaderContexts,
+
+		Archs:                           archs,
+		DexPreoptImagesDeps:             imagesDeps,
+		DexPreoptImageLocationsOnHost:   hostImageLocations,
+		DexPreoptImageLocationsOnDevice: deviceImageLocations,
+
+		PreoptBootClassPathDexFiles:     dexFiles.Paths(),
+		PreoptBootClassPathDexLocations: dexLocations,
+
+		PreoptExtractedApk: false,
+
+		NoCreateAppImage:    !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true),
+		ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false),
+
+		PresignedPrebuilt: d.isPresignedPrebuilt,
+	}
+
+	d.configPath = android.PathForModuleOut(ctx, "dexpreopt", "dexpreopt.config")
+	dexpreopt.WriteModuleConfig(ctx, dexpreoptConfig, d.configPath)
+
+	if d.dexpreoptDisabled(ctx) {
+		return
+	}
+
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
+
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig)
+	if err != nil {
+		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
+		return
+	}
+
+	dexpreoptRule.Build("dexpreopt", "dexpreopt")
+
+	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+
+	for _, install := range dexpreoptRule.Installs() {
+		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
+		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+		installBase := filepath.Base(install.To)
+		arch := filepath.Base(installDir)
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+
+		if isApexSystemServerJar {
+			// APEX variants of java libraries are hidden from Make, so their dexpreopt
+			// outputs need special handling. Currently, for APEX variants of java
+			// libraries, only those in the system server classpath are handled here.
+			// Preopting of boot classpath jars in the ART APEX are handled in
+			// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
+			// The installs will be handled by Make as sub-modules of the java library.
+			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
+				name:                arch + "-" + installBase,
+				moduleName:          moduleName(ctx),
+				outputPathOnHost:    install.From,
+				installDirOnDevice:  installPath,
+				installFileOnDevice: installBase,
+			})
+		} else if !d.preventInstall {
+			ctx.InstallFile(installPath, installBase, install.From)
+		}
+	}
+
+	if !isApexSystemServerJar {
+		d.builtInstalled = dexpreoptRule.Installs().String()
+	}
+}
+
+func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
+	return d.builtInstalledForApex
+}
+
+func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
+	var entries []android.AndroidMkEntries
+	for _, install := range d.builtInstalledForApex {
+		entries = append(entries, install.ToMakeEntries())
+	}
+	return entries
+}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 3d91aec..7c4da3e 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -213,12 +213,6 @@
 // writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
 // paths and so on.
 //
-// 2.5. JIT-Zygote configuration
-// -----------------------------
-//
-// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling
-// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
-//
 
 var artApexNames = []string{
 	"com.android.art",
@@ -938,11 +932,8 @@
 		ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " "))
 
 		var imageNames []string
-		// TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
-		// different way as a part of the ART APEX. However, there is a special JIT-Zygote build
-		// configuration which uses the primary ART image instead of the Framework boot image
-		// extension, and it relies on the ART image being exposed to Make. To fix this, it is
-		// necessary to rework the logic in makefiles.
+		// The primary ART boot image is exposed to Make for testing (gtests) and benchmarking
+		// (golem) purposes.
 		for _, current := range append(d.otherImages, image) {
 			imageNames = append(imageNames, current.name)
 			for _, variant := range current.variants {
diff --git a/java/dexpreopt_bootjars.go_v1 b/java/dexpreopt_bootjars.go_v1
new file mode 100644
index 0000000..07a357b
--- /dev/null
+++ b/java/dexpreopt_bootjars.go_v1
@@ -0,0 +1,952 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/proptools"
+)
+
+// =================================================================================================
+// WIP - see http://b/177892522 for details
+//
+// The build support for boot images is currently being migrated away from singleton to modules so
+// the documentation may not be strictly accurate. Rather than update the documentation at every
+// step which will create a lot of churn the changes that have been made will be listed here and the
+// documentation will be updated once it is closer to the final result.
+//
+// Changes:
+// 1) dex_bootjars is now a singleton module and not a plain singleton.
+// 2) Boot images are now represented by the boot_image module type.
+// 3) The art boot image is called "art-boot-image", the framework boot image is called
+//    "framework-boot-image".
+// 4) They are defined in art/build/boot/Android.bp and frameworks/base/boot/Android.bp
+//    respectively.
+// 5) Each boot_image retrieves the appropriate boot image configuration from the map returned by
+//    genBootImageConfigs() using the image_name specified in the boot_image module.
+// =================================================================================================
+
+// This comment describes:
+//   1. ART boot images in general (their types, structure, file layout, etc.)
+//   2. build system support for boot images
+//
+// 1. ART boot images
+// ------------------
+//
+// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot
+// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a
+// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is
+// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled
+// against it (compilation may take place either on host, known as "dexpreopt", or on device, known
+// as "dexopt").
+//
+// A boot image is not a single file, but a collection of interrelated files. Each boot image has a
+// number of components that correspond to the Java libraries that constitute it. For each component
+// there are multiple files:
+//   - *.oat or *.odex file with native code (architecture-specific, one per instruction set)
+//   - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set)
+//   - *.vdex file with verification metadata for the DEX bytecode (architecture independent)
+//
+// *.vdex files for the boot images do not contain the DEX bytecode itself, because the
+// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot
+// image is not self-contained and cannot be used without its DEX files. To simplify the management
+// of boot image files, ART uses a certain naming scheme and associates the following metadata with
+// each boot image:
+//   - A stem, which is a symbolic name that is prepended to boot image file names.
+//   - A location (on-device path to the boot image files).
+//   - A list of boot image locations (on-device paths to dependency boot images).
+//   - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used
+//     to compile the boot image).
+//
+// There are two kinds of boot images:
+//   - primary boot images
+//   - boot image extensions
+//
+// 1.1. Primary boot images
+// ------------------------
+//
+// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not
+// depend on any other images, and other boot images may depend on it.
+//
+// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/,
+// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets
+// (32 and 64 bits), it will have three components with the following files:
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex}
+//   - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex}
+//
+// The files of the first component are special: they do not have the component name appended after
+// the stem. This naming convention dates back to the times when the boot image was not split into
+// components, and there were just boot.oat and boot.art. The decision to split was motivated by
+// licensing reasons for one of the bootclasspath libraries.
+//
+// As of November 2020 the only primary boot image in Android is the image in the ART APEX
+// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART
+// module. When the ART module gets updated, the primary boot image will be updated with it, and all
+// dependent images will get invalidated (the checksum of the primary image stored in dependent
+// images will not match), unless they are updated in sync with the ART module.
+//
+// 1.2. Boot image extensions
+// --------------------------
+//
+// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular,
+// this subset does not include the Core bootclasspath libraries that go into the primary boot
+// image). A boot image extension depends on the primary boot image and optionally some other boot
+// image extensions. Other images may depend on it. In other words, boot image extensions can form
+// acyclic dependency graphs.
+//
+// The motivation for boot image extensions comes from the Mainline project. Consider a situation
+// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android
+// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java
+// code for C might have changed compared to the code that was used to compile the boot image.
+// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B
+// that does not depend on C is up to date). To avoid this, the original monolithic boot image is
+// split in two parts: the primary boot image that contains A B, and the boot image extension that
+// contains C and depends on the primary boot image (extends it).
+//
+// For example, assuming that the stem is "boot", the location is /system/framework, the set of
+// bootclasspath libraries is D E (where D is part of the platform and is located in
+// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in
+// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits),
+// it will have two components with the following files:
+//   - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex}
+//   - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex}
+//
+// As of November 2020 the only boot image extension in Android is the Framework boot image
+// extension. It extends the primary ART boot image and contains Framework libraries and other
+// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the
+// ART image. The Framework boot image extension is updated together with the platform. In the
+// future other boot image extensions may be added for some updatable modules.
+//
+//
+// 2. Build system support for boot images
+// ---------------------------------------
+//
+// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX
+// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat
+// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the
+// core libraries as they are already part of the primary ART boot image.
+//
+// 2.1. Libraries that go in the boot images
+// -----------------------------------------
+//
+// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX
+// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The
+// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and
+// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries,
+// but more product-specific libraries can be added in the product makefiles.
+//
+// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is a
+// colon-separated pair <apex>:<library>, where <apex> is the variant name of a non-updatable APEX,
+// "platform" if the library is a part of the platform in the system partition, or "system_ext" if
+// it's in the system_ext partition.
+//
+// In these variables APEXes are identified by their "variant names", i.e. the names they get
+// mounted as in /apex on device. In Soong modules that is the name set in the "apex_name"
+// properties, which default to the "name" values. For example, many APEXes have both
+// com.android.xxx and com.google.android.xxx modules in Soong, but take the same place
+// /apex/com.android.xxx at runtime. In these cases the variant name is always com.android.xxx,
+// regardless which APEX goes into the product. See also android.ApexInfo.ApexVariationName and
+// apex.apexBundleProperties.Apex_name.
+//
+// A related variable PRODUCT_APEX_BOOT_JARS contains bootclasspath libraries that are in APEXes.
+// They are not included in the boot image. The only exception here are ART jars and core-icu4j.jar
+// that have been historically part of the boot image and are now in apexes; they are in boot images
+// and core-icu4j.jar is generally treated as being part of PRODUCT_BOOT_JARS.
+//
+// One exception to the above rules are "coverage" builds (a special build flavor which requires
+// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in
+// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent)
+// needs to be added to the list of bootclasspath DEX jars.
+//
+// In general, there is a requirement that the source code for a boot image library must be
+// available at build time (e.g. it cannot be a stub that has a separate implementation library).
+//
+// 2.2. Static configs
+// -------------------
+//
+// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must
+// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image
+// configs are constructed very early during the build, before build rule generation. The configs
+// provide predefined paths to boot image files (these paths depend only on static build
+// configuration, such as PRODUCT variables, and use hard-coded directory names).
+//
+// 2.3. Singleton
+// --------------
+//
+// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no
+// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules.
+// Soong loops through all modules and compares each module against a list of bootclasspath library
+// names. Then it generates build rules that copy DEX jars from their intermediate module-specific
+// locations to the hard-coded locations predefined in the boot image configs.
+//
+// It would be possible to use a module with proper dependencies instead, but that would require
+// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method
+// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile,
+// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables
+// for each module, and is included later.
+//
+// 2.4. Install rules
+// ------------------
+//
+// The primary boot image and the Framework extension are installed in different ways. The primary
+// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged
+// together with other APEX contents, extracted and mounted on device. The Framework boot image
+// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong
+// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
+// paths and so on.
+//
+
+var artApexNames = []string{
+	"com.android.art",
+	"com.android.art.debug",
+	"com.android.art.testing",
+	"com.google.android.art",
+	"com.google.android.art.debug",
+	"com.google.android.art.testing",
+}
+
+func init() {
+	RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
+}
+
+// Target-independent description of a boot image.
+type bootImageConfig struct {
+	// If this image is an extension, the image that it extends.
+	extends *bootImageConfig
+
+	// Image name (used in directory names and ninja rule names).
+	name string
+
+	// Basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}.
+	stem string
+
+	// Output directory for the image files.
+	dir android.OutputPath
+
+	// Output directory for the image files with debug symbols.
+	symbolsDir android.OutputPath
+
+	// Subdirectory where the image files are installed.
+	installDirOnHost string
+
+	// Subdirectory where the image files on device are installed.
+	installDirOnDevice string
+
+	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
+	// needed.
+	profileInstallPathInApex string
+
+	// A list of (location, jar) pairs for the Java modules in this image.
+	modules android.ConfiguredJarList
+
+	// File paths to jars.
+	dexPaths     android.WritablePaths // for this image
+	dexPathsDeps android.WritablePaths // for the dependency images and in this image
+
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
+	// File path to a zip archive with all image files (or nil, if not needed).
+	zip android.WritablePath
+
+	// Rules which should be used in make to install the outputs.
+	profileInstalls android.RuleBuilderInstalls
+
+	// Path to the license metadata file for the module that built the profile.
+	profileLicenseMetadataFile android.OptionalPath
+
+	// Path to the image profile file on host (or empty, if profile is not generated).
+	profilePathOnHost android.Path
+
+	// Target-dependent fields.
+	variants []*bootImageVariant
+
+	// Path of the preloaded classes file.
+	preloadedClassesFile string
+}
+
+// Target-dependent description of a boot image.
+type bootImageVariant struct {
+	*bootImageConfig
+
+	// Target for which the image is generated.
+	target android.Target
+
+	// The "locations" of jars.
+	dexLocations     []string // for this image
+	dexLocationsDeps []string // for the dependency images and in this image
+
+	// Paths to image files.
+	imagePathOnHost   android.OutputPath // first image file path on host
+	imagePathOnDevice string             // first image file path on device
+
+	// All the files that constitute this image variant, i.e. .art, .oat and .vdex files.
+	imagesDeps android.OutputPaths
+
+	// The path to the primary image variant's imagePathOnHost field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
+	primaryImages android.OutputPath
+
+	// The paths to the primary image variant's imagesDeps field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
+	primaryImagesDeps android.Paths
+
+	// Rules which should be used in make to install the outputs on host.
+	installs           android.RuleBuilderInstalls
+	vdexInstalls       android.RuleBuilderInstalls
+	unstrippedInstalls android.RuleBuilderInstalls
+
+	// Rules which should be used in make to install the outputs on device.
+	deviceInstalls android.RuleBuilderInstalls
+
+	// Path to the license metadata file for the module that built the image.
+	licenseMetadataFile android.OptionalPath
+}
+
+// Get target-specific boot image variant for the given boot image config and target.
+func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant {
+	for _, variant := range image.variants {
+		if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType {
+			return variant
+		}
+	}
+	return nil
+}
+
+// Return any (the first) variant which is for the device (as opposed to for the host).
+func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
+	for _, variant := range image.variants {
+		if variant.target.Os == android.Android {
+			return variant
+		}
+	}
+	return nil
+}
+
+// Return the name of a boot image module given a boot image config and a component (module) index.
+// A module name is a combination of the Java library name, and the boot image stem (that is stored
+// in the config).
+func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string {
+	// The first module of the primary boot image is special: its module name has only the stem, but
+	// not the library name. All other module names are of the form <stem>-<library name>
+	m := image.modules.Jar(idx)
+	name := image.stem
+	if idx != 0 || image.extends != nil {
+		name += "-" + android.ModuleStem(m)
+	}
+	return name
+}
+
+// Return the name of the first boot image module, or stem if the list of modules is empty.
+func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string {
+	if image.modules.Len() > 0 {
+		return image.moduleName(ctx, 0)
+	} else {
+		return image.stem
+	}
+}
+
+// Return filenames for the given boot image component, given the output directory and a list of
+// extensions.
+func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
+	ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts))
+	for i := 0; i < image.modules.Len(); i++ {
+		name := image.moduleName(ctx, i)
+		for _, ext := range exts {
+			ret = append(ret, dir.Join(ctx, name+ext))
+		}
+	}
+	return ret
+}
+
+// apexVariants returns a list of all *bootImageVariant that could be included in an apex.
+func (image *bootImageConfig) apexVariants() []*bootImageVariant {
+	variants := []*bootImageVariant{}
+	for _, variant := range image.variants {
+		// We also generate boot images for host (for testing), but we don't need those in the apex.
+		// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
+		if variant.target.Os == android.Android {
+			variants = append(variants, variant)
+		}
+	}
+	return variants
+}
+
+// Returns true if the boot image should be installed in the APEX.
+func (image *bootImageConfig) shouldInstallInApex() bool {
+	return strings.HasPrefix(image.installDirOnDevice, "apex/")
+}
+
+// Return boot image locations (as a list of symbolic paths).
+//
+// The image "location" is a symbolic path that, with multiarchitecture support, doesn't really
+// exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the
+// same for all supported architectures on the device. The concrete architecture specific files
+// actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64.
+//
+// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location"
+// /apex/com.android.art/javalib/boot.art (which is not an actual file).
+//
+// For a primary boot image the list of locations has a single element.
+//
+// For a boot image extension the list of locations contains a location for all dependency images
+// (including the primary image) and the location of the extension itself. For example, for the
+// Framework boot image extension that depends on the primary ART boot image the list contains two
+// elements.
+//
+// The location is passed as an argument to the ART tools like dex2oat instead of the real path.
+// ART tools will then reconstruct the architecture-specific real path.
+//
+func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) {
+	if image.extends != nil {
+		imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations()
+	}
+	return append(imageLocationsOnHost, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType)),
+		append(imageLocationsOnDevice, dexpreopt.PathStringToLocation(image.imagePathOnDevice, image.target.Arch.ArchType))
+}
+
+func dexpreoptBootJarsFactory() android.SingletonModule {
+	m := &dexpreoptBootJars{}
+	android.InitAndroidModule(m)
+	return m
+}
+
+func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonModuleType("dex_bootjars", dexpreoptBootJarsFactory)
+}
+
+func SkipDexpreoptBootJars(ctx android.PathContext) bool {
+	return dexpreopt.GetGlobalConfig(ctx).DisablePreoptBootImages
+}
+
+// Singleton module for generating boot image build rules.
+type dexpreoptBootJars struct {
+	android.SingletonModuleBase
+
+	// Default boot image config (currently always the Framework boot image extension). It should be
+	// noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension,
+	// but the switch is handled not here, but in the makefiles (triggered with
+	// DEXPREOPT_USE_ART_IMAGE=true).
+	defaultBootImage *bootImageConfig
+
+	// Build path to a config file that Soong writes for Make (to be used in makefiles that install
+	// the default boot image).
+	dexpreoptConfigForMake android.WritablePath
+}
+
+// Provide paths to boot images for use by modules that depend upon them.
+//
+// The build rules are created in GenerateSingletonBuildActions().
+func (d *dexpreoptBootJars) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Placeholder for now.
+}
+
+// Generate build rules for boot images.
+func (d *dexpreoptBootJars) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+	if SkipDexpreoptBootJars(ctx) {
+		return
+	}
+	if dexpreopt.GetCachedGlobalSoongConfig(ctx) == nil {
+		// No module has enabled dexpreopting, so we assume there will be no boot image to make.
+		return
+	}
+
+	d.dexpreoptConfigForMake = android.PathForOutput(ctx, ctx.Config().DeviceName(), "dexpreopt.config")
+	writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake)
+
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if !shouldBuildBootImages(ctx.Config(), global) {
+		return
+	}
+
+	defaultImageConfig := defaultBootImageConfig(ctx)
+	d.defaultBootImage = defaultImageConfig
+}
+
+// shouldBuildBootImages determines whether boot images should be built.
+func shouldBuildBootImages(config android.Config, global *dexpreopt.GlobalConfig) bool {
+	// Skip recompiling the boot image for the second sanitization phase. We'll get separate paths
+	// and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds.
+	// Note: this is technically incorrect. Compiled code contains stack checks which may depend
+	//       on ASAN settings.
+	if len(config.SanitizeDevice()) == 1 && config.SanitizeDevice()[0] == "address" && global.SanitizeLite {
+		return false
+	}
+	return true
+}
+
+// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined
+// paths in the global config.
+func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) {
+	// Create the super set of module names.
+	names := []string{}
+	names = append(names, android.SortedStringKeys(srcBootDexJarsByModule)...)
+	names = append(names, android.SortedStringKeys(dstBootJarsByModule)...)
+	names = android.SortedUniqueStrings(names)
+	for _, name := range names {
+		src := srcBootDexJarsByModule[name]
+		dst := dstBootJarsByModule[name]
+
+		if src == nil {
+			// A dex boot jar should be provided by the source java module. It needs to be installable or
+			// have compile_dex=true - cf. assignments to java.Module.dexJarFile.
+			//
+			// However, the source java module may be either replaced or overridden (using prefer:true) by
+			// a prebuilt java module with the same name. In that case the dex boot jar needs to be
+			// provided by the corresponding prebuilt APEX module. That APEX is the one that refers
+			// through a exported_(boot|systemserver)classpath_fragments property to a
+			// prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt
+			// java module in the contents property. If that chain is broken then this dependency will
+			// fail.
+			if !ctx.Config().AllowMissingDependencies() {
+				ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name)
+			} else {
+				ctx.AddMissingDependencies([]string{name})
+			}
+		} else if dst == nil {
+			ctx.ModuleErrorf("module %s is not part of the boot configuration", name)
+		} else {
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  src,
+				Output: dst,
+			})
+		}
+	}
+}
+
+// buildBootImageVariantsForAndroidOs generates rules to build the boot image variants for the
+// android.Android OsType and returns a map from the architectures to the paths of the generated
+// boot image files.
+//
+// The paths are returned because they are needed elsewhere in Soong, e.g. for populating an APEX.
+func buildBootImageVariantsForAndroidOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) bootImageFilesByArch {
+	return buildBootImageForOsType(ctx, image, profile, android.Android)
+}
+
+// buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the
+// config.BuildOS OsType, i.e. the type of OS on which the build is being running.
+//
+// The files need to be generated into their predefined location because they are used from there
+// both within Soong and outside, e.g. for ART based host side testing and also for use by some
+// cloud based tools. However, they are not needed by callers of this function and so the paths do
+// not need to be returned from this func, unlike the buildBootImageVariantsForAndroidOs func.
+func buildBootImageVariantsForBuildOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) {
+	buildBootImageForOsType(ctx, image, profile, ctx.Config().BuildOS)
+}
+
+// buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType
+// boot image files are required for and it creates rules to build the boot image
+// files for all the required architectures for them.
+//
+// It returns a map from android.ArchType to the predefined paths of the boot image files.
+func buildBootImageForOsType(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath, requiredOsType android.OsType) bootImageFilesByArch {
+	filesByArch := bootImageFilesByArch{}
+	for _, variant := range image.variants {
+		if variant.target.Os == requiredOsType {
+			buildBootImageVariant(ctx, variant, profile)
+			filesByArch[variant.target.Arch.ArchType] = variant.imagesDeps.Paths()
+		}
+	}
+
+	return filesByArch
+}
+
+// buildBootImageZipInPredefinedLocation generates a zip file containing all the boot image files.
+//
+// The supplied filesByArch is nil when the boot image files have not been generated. Otherwise, it
+// is a map from android.ArchType to the predefined locations.
+func buildBootImageZipInPredefinedLocation(ctx android.ModuleContext, image *bootImageConfig, filesByArch bootImageFilesByArch) {
+	if filesByArch == nil {
+		return
+	}
+
+	// Compute the list of files from all the architectures.
+	zipFiles := android.Paths{}
+	for _, archType := range android.ArchTypeList() {
+		zipFiles = append(zipFiles, filesByArch[archType]...)
+	}
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", image.zip).
+		FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()).
+		FlagWithInputList("-f ", zipFiles, " -f ")
+
+	rule.Build("zip_"+image.name, "zip "+image.name+" image")
+}
+
+// Generate boot image build rules for a specific target.
+func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) {
+
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	arch := image.target.Arch.ArchType
+	os := image.target.Os.String() // We need to distinguish host-x86 and device-x86.
+	symbolsDir := image.symbolsDir.Join(ctx, os, image.installDirOnHost, arch.String())
+	symbolsFile := symbolsDir.Join(ctx, image.stem+".oat")
+	outputDir := image.dir.Join(ctx, os, image.installDirOnHost, arch.String())
+	outputPath := outputDir.Join(ctx, image.stem+".oat")
+	oatLocation := dexpreopt.PathToLocation(outputPath, arch)
+	imagePath := outputPath.ReplaceExtension(ctx, "art")
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+
+	rule.Command().Text("mkdir").Flag("-p").Flag(symbolsDir.String())
+	rule.Command().Text("rm").Flag("-f").
+		Flag(symbolsDir.Join(ctx, "*.art").String()).
+		Flag(symbolsDir.Join(ctx, "*.oat").String()).
+		Flag(symbolsDir.Join(ctx, "*.invocation").String())
+	rule.Command().Text("rm").Flag("-f").
+		Flag(outputDir.Join(ctx, "*.art").String()).
+		Flag(outputDir.Join(ctx, "*.oat").String()).
+		Flag(outputDir.Join(ctx, "*.invocation").String())
+
+	cmd := rule.Command()
+
+	extraFlags := ctx.Config().Getenv("ART_BOOT_IMAGE_EXTRA_ARGS")
+	if extraFlags == "" {
+		// Use ANDROID_LOG_TAGS to suppress most logging by default...
+		cmd.Text(`ANDROID_LOG_TAGS="*:e"`)
+	} else {
+		// ...unless the boot image is generated specifically for testing, then allow all logging.
+		cmd.Text(`ANDROID_LOG_TAGS="*:v"`)
+	}
+
+	invocationPath := outputPath.ReplaceExtension(ctx, "invocation")
+
+	cmd.Tool(globalSoong.Dex2oat).
+		Flag("--avoid-storing-invocation").
+		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
+		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms).
+		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx)
+
+	if profile != nil {
+		cmd.FlagWithInput("--profile-file=", profile)
+	}
+
+	dirtyImageFile := "frameworks/base/config/dirty-image-objects"
+	dirtyImagePath := android.ExistentPathForSource(ctx, dirtyImageFile)
+	if dirtyImagePath.Valid() {
+		cmd.FlagWithInput("--dirty-image-objects=", dirtyImagePath.Path())
+	}
+
+	if image.extends != nil {
+		// It is a boot image extension, so it needs the boot image it depends on (in this case the
+		// primary ART APEX image).
+		artImage := image.primaryImages
+		cmd.
+			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
+			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
+			// Add the path to the first file in the boot image with the arch specific directory removed,
+			// dex2oat will reconstruct the path to the actual file when it needs it. As the actual path
+			// to the file cannot be passed to the command make sure to add the actual path as an Implicit
+			// dependency to ensure that it is built before the command runs.
+			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage).
+			// Similarly, the dex2oat tool will automatically find the paths to other files in the base
+			// boot image so make sure to add them as implicit dependencies to ensure that they are built
+			// before this command is run.
+			Implicits(image.primaryImagesDeps)
+	} else {
+		// It is a primary image, so it needs a base address.
+		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
+	}
+
+	// We always expect a preloaded classes file to be available. However, if we cannot find it, it's
+	// OK to not pass the flag to dex2oat.
+	preloadedClassesPath := android.ExistentPathForSource(ctx, image.preloadedClassesFile)
+	if preloadedClassesPath.Valid() {
+		cmd.FlagWithInput("--preloaded-classes=", preloadedClassesPath.Path())
+	}
+
+	cmd.
+		FlagForEachInput("--dex-file=", image.dexPaths.Paths()).
+		FlagForEachArg("--dex-location=", image.dexLocations).
+		Flag("--generate-debug-info").
+		Flag("--generate-build-id").
+		Flag("--image-format=lz4hc").
+		FlagWithArg("--oat-symbols=", symbolsFile.String()).
+		Flag("--strip").
+		FlagWithArg("--oat-file=", outputPath.String()).
+		FlagWithArg("--oat-location=", oatLocation).
+		FlagWithArg("--image=", imagePath.String()).
+		FlagWithArg("--instruction-set=", arch.String()).
+		FlagWithArg("--android-root=", global.EmptyDirectory).
+		FlagWithArg("--no-inline-from=", "core-oj.jar").
+		Flag("--force-determinism").
+		Flag("--abort-on-hard-verifier-error")
+
+	// Use the default variant/features for host builds.
+	// The map below contains only device CPU info (which might be x86 on some devices).
+	if image.target.Os == android.Android {
+		cmd.FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch])
+		cmd.FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch])
+	}
+
+	if global.BootFlags != "" {
+		cmd.Flag(global.BootFlags)
+	}
+
+	if extraFlags != "" {
+		cmd.Flag(extraFlags)
+	}
+
+	cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage))
+
+	installDir := filepath.Join("/", image.installDirOnHost, arch.String())
+
+	var vdexInstalls android.RuleBuilderInstalls
+	var unstrippedInstalls android.RuleBuilderInstalls
+	var deviceInstalls android.RuleBuilderInstalls
+
+	for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") {
+		cmd.ImplicitOutput(artOrOat)
+
+		// Install the .oat and .art files
+		rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base()))
+	}
+
+	for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") {
+		cmd.ImplicitOutput(vdex)
+
+		// Note that the vdex files are identical between architectures.
+		// Make rules will create symlinks to share them between architectures.
+		vdexInstalls = append(vdexInstalls,
+			android.RuleBuilderInstall{vdex, filepath.Join(installDir, vdex.Base())})
+	}
+
+	for _, unstrippedOat := range image.moduleFiles(ctx, symbolsDir, ".oat") {
+		cmd.ImplicitOutput(unstrippedOat)
+
+		// Install the unstripped oat files.  The Make rules will put these in $(TARGET_OUT_UNSTRIPPED)
+		unstrippedInstalls = append(unstrippedInstalls,
+			android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())})
+	}
+
+	if image.installDirOnHost != image.installDirOnDevice && !image.shouldInstallInApex() && !ctx.Config().UnbundledBuild() {
+		installDirOnDevice := filepath.Join("/", image.installDirOnDevice, arch.String())
+		for _, file := range image.moduleFiles(ctx, outputDir, ".art", ".oat", ".vdex") {
+			deviceInstalls = append(deviceInstalls,
+				android.RuleBuilderInstall{file, filepath.Join(installDirOnDevice, file.Base())})
+		}
+	}
+
+	rule.Build(image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String())
+
+	// save output and installed files for makevars
+	image.installs = rule.Installs()
+	image.vdexInstalls = vdexInstalls
+	image.unstrippedInstalls = unstrippedInstalls
+	image.deviceInstalls = deviceInstalls
+	image.licenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
+}
+
+const failureMessage = `ERROR: Dex2oat failed to compile a boot image.
+It is likely that the boot classpath is inconsistent.
+Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
+
+func bootImageProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	if global.DisableGenerateProfile {
+		return nil
+	}
+
+	defaultProfile := "frameworks/base/config/boot-image-profile.txt"
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+
+	var bootImageProfile android.Path
+	if len(global.BootImageProfiles) > 1 {
+		combinedBootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt")
+		rule.Command().Text("cat").Inputs(global.BootImageProfiles).Text(">").Output(combinedBootImageProfile)
+		bootImageProfile = combinedBootImageProfile
+	} else if len(global.BootImageProfiles) == 1 {
+		bootImageProfile = global.BootImageProfiles[0]
+	} else if path := android.ExistentPathForSource(ctx, defaultProfile); path.Valid() {
+		bootImageProfile = path.Path()
+	} else {
+		// No profile (not even a default one, which is the case on some branches
+		// like master-art-host that don't have frameworks/base).
+		// Return nil and continue without profile.
+		return nil
+	}
+
+	profile := image.dir.Join(ctx, "boot.prof")
+
+	rule.Command().
+		Text(`ANDROID_LOG_TAGS="*:e"`).
+		Tool(globalSoong.Profman).
+		Flag("--output-profile-type=boot").
+		FlagWithInput("--create-profile-from=", bootImageProfile).
+		FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
+		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
+		FlagWithOutput("--reference-profile-file=", profile)
+
+	if image == defaultBootImageConfig(ctx) {
+		rule.Install(profile, "/system/etc/boot-image.prof")
+		image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+		image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
+	}
+
+	rule.Build("bootJarsProfile", "profile boot jars")
+
+	image.profilePathOnHost = profile
+
+	return profile
+}
+
+// bootFrameworkProfileRule generates the rule to create the boot framework profile and
+// returns a path to the generated file.
+func bootFrameworkProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
+	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	if global.DisableGenerateProfile || ctx.Config().UnbundledBuild() {
+		return nil
+	}
+
+	defaultProfile := "frameworks/base/config/boot-profile.txt"
+	bootFrameworkProfile := android.PathForSource(ctx, defaultProfile)
+
+	profile := image.dir.Join(ctx, "boot.bprof")
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		Text(`ANDROID_LOG_TAGS="*:e"`).
+		Tool(globalSoong.Profman).
+		Flag("--output-profile-type=bprof").
+		FlagWithInput("--create-profile-from=", bootFrameworkProfile).
+		FlagForEachInput("--apk=", image.dexPathsDeps.Paths()).
+		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
+		FlagWithOutput("--reference-profile-file=", profile)
+
+	rule.Install(profile, "/system/etc/boot-image.bprof")
+	rule.Build("bootFrameworkProfile", "profile boot framework jars")
+	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+	image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
+
+	return profile
+}
+
+func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) {
+	var allPhonies android.Paths
+	for _, image := range image.variants {
+		arch := image.target.Arch.ArchType
+		suffix := arch.String()
+		// Host and target might both use x86 arch. We need to ensure the names are unique.
+		if image.target.Os.Class == android.Host {
+			suffix = "host-" + suffix
+		}
+		// Create a rule to call oatdump.
+		output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt")
+		rule := android.NewRuleBuilder(pctx, ctx)
+		imageLocationsOnHost, _ := image.imageLocations()
+		rule.Command().
+			BuiltTool("oatdump").
+			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
+			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":").
+			FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()).
+			FlagWithOutput("--output=", output).
+			FlagWithArg("--instruction-set=", arch.String())
+		rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
+
+		// Create a phony rule that depends on the output file and prints the path.
+		phony := android.PathForPhony(ctx, "dump-oat-boot-"+suffix)
+		rule = android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			Implicit(output).
+			ImplicitOutput(phony).
+			Text("echo").FlagWithArg("Output in ", output.String())
+		rule.Build("phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
+
+		allPhonies = append(allPhonies, phony)
+	}
+
+	phony := android.PathForPhony(ctx, "dump-oat-boot")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Phony,
+		Output:      phony,
+		Inputs:      allPhonies,
+		Description: "dump-oat-boot",
+	})
+}
+
+func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) {
+	data := dexpreopt.GetGlobalConfigRawData(ctx)
+
+	android.WriteFileRule(ctx, path, string(data))
+}
+
+// Define Make variables for boot image names, paths, etc. These variables are used in makefiles
+// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the
+// correct output directories.
+func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
+	if d.dexpreoptConfigForMake != nil {
+		ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String())
+		ctx.Strict("DEX_PREOPT_SOONG_CONFIG_FOR_MAKE", android.PathForOutput(ctx, "dexpreopt_soong.config").String())
+	}
+
+	image := d.defaultBootImage
+	if image == nil {
+		return
+	}
+
+	ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String())
+	if image.profileLicenseMetadataFile.Valid() {
+		ctx.Strict("DEXPREOPT_IMAGE_PROFILE_LICENSE_METADATA", image.profileLicenseMetadataFile.String())
+	}
+
+	global := dexpreopt.GetGlobalConfig(ctx)
+	dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
+	ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " "))
+	ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " "))
+
+	for _, variant := range image.variants {
+		suffix := ""
+		if variant.target.Os.Class == android.Host {
+			suffix = "_host"
+		}
+		sfx := suffix + "_" + variant.target.Arch.ArchType.String()
+		ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+sfx, variant.vdexInstalls.String())
+		ctx.Strict("DEXPREOPT_IMAGE_"+sfx, variant.imagePathOnHost.String())
+		ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+sfx, strings.Join(variant.imagesDeps.Strings(), " "))
+		ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, variant.installs.String())
+		ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, variant.unstrippedInstalls.String())
+		if variant.licenseMetadataFile.Valid() {
+			ctx.Strict("DEXPREOPT_IMAGE_LICENSE_METADATA_"+sfx, variant.licenseMetadataFile.String())
+		}
+	}
+	imageLocationsOnHost, imageLocationsOnDevice := image.getAnyAndroidVariant().imageLocations()
+	ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_HOST", strings.Join(imageLocationsOnHost, ":"))
+	ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE", strings.Join(imageLocationsOnDevice, ":"))
+	ctx.Strict("DEXPREOPT_IMAGE_ZIP", image.zip.String())
+
+	// There used to be multiple images for JIT-Zygote mode, not there's only one.
+	ctx.Strict("DEXPREOPT_IMAGE_NAMES", image.name)
+}
diff --git a/java/dexpreopt_config.go_v1 b/java/dexpreopt_config.go_v1
new file mode 100644
index 0000000..d71e2bb
--- /dev/null
+++ b/java/dexpreopt_config.go_v1
@@ -0,0 +1,215 @@
+// Copyright 2019 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+)
+
+// dexpreoptTargets returns the list of targets that are relevant to dexpreopting, which excludes architectures
+// supported through native bridge.
+func dexpreoptTargets(ctx android.PathContext) []android.Target {
+	var targets []android.Target
+	for _, target := range ctx.Config().Targets[android.Android] {
+		if target.NativeBridge == android.NativeBridgeDisabled {
+			targets = append(targets, target)
+		}
+	}
+	// We may also need the images on host in order to run host-based tests.
+	for _, target := range ctx.Config().Targets[ctx.Config().BuildOS] {
+		targets = append(targets, target)
+	}
+
+	return targets
+}
+
+var (
+	bootImageConfigKey     = android.NewOnceKey("bootImageConfig")
+	bootImageConfigRawKey  = android.NewOnceKey("bootImageConfigRaw")
+	artBootImageName       = "art"
+	frameworkBootImageName = "boot"
+)
+
+func genBootImageConfigRaw(ctx android.PathContext) map[string]*bootImageConfig {
+	return ctx.Config().Once(bootImageConfigRawKey, func() interface{} {
+		global := dexpreopt.GetGlobalConfig(ctx)
+
+		artModules := global.ArtApexJars
+		frameworkModules := global.BootJars.RemoveList(artModules)
+
+		// ART config for the primary boot image in the ART apex.
+		// It includes the Core Libraries.
+		artCfg := bootImageConfig{
+			name:                     artBootImageName,
+			stem:                     "boot",
+			installDirOnHost:         "apex/art_boot_images/javalib",
+			installDirOnDevice:       "system/framework",
+			profileInstallPathInApex: "etc/boot-image.prof",
+			modules:                  artModules,
+			preloadedClassesFile:     "art/build/boot/preloaded-classes",
+		}
+
+		// Framework config for the boot image extension.
+		// It includes framework libraries and depends on the ART config.
+		frameworkSubdir := "system/framework"
+		frameworkCfg := bootImageConfig{
+			extends:              &artCfg,
+			name:                 frameworkBootImageName,
+			stem:                 "boot",
+			installDirOnHost:     frameworkSubdir,
+			installDirOnDevice:   frameworkSubdir,
+			modules:              frameworkModules,
+			preloadedClassesFile: "frameworks/base/config/preloaded-classes",
+		}
+
+		return map[string]*bootImageConfig{
+			artBootImageName:       &artCfg,
+			frameworkBootImageName: &frameworkCfg,
+		}
+	}).(map[string]*bootImageConfig)
+}
+
+// Construct the global boot image configs.
+func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
+	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
+		targets := dexpreoptTargets(ctx)
+		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
+
+		configs := genBootImageConfigRaw(ctx)
+		artCfg := configs[artBootImageName]
+		frameworkCfg := configs[frameworkBootImageName]
+
+		// common to all configs
+		for _, c := range configs {
+			c.dir = deviceDir.Join(ctx, "dex_"+c.name+"jars")
+			c.symbolsDir = deviceDir.Join(ctx, "dex_"+c.name+"jars_unstripped")
+
+			// expands to <stem>.art for primary image and <stem>-<1st module>.art for extension
+			imageName := c.firstModuleNameOrStem(ctx) + ".art"
+
+			// The path to bootclasspath dex files needs to be known at module
+			// GenerateAndroidBuildAction time, before the bootclasspath modules have been compiled.
+			// Set up known paths for them, the singleton rules will copy them there.
+			// TODO(b/143682396): use module dependencies instead
+			inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input")
+			c.dexPaths = c.modules.BuildPaths(ctx, inputDir)
+			c.dexPathsByModule = c.modules.BuildPathsByModule(ctx, inputDir)
+			c.dexPathsDeps = c.dexPaths
+
+			// Create target-specific variants.
+			for _, target := range targets {
+				arch := target.Arch.ArchType
+				imageDir := c.dir.Join(ctx, target.Os.String(), c.installDirOnHost, arch.String())
+				variant := &bootImageVariant{
+					bootImageConfig:   c,
+					target:            target,
+					imagePathOnHost:   imageDir.Join(ctx, imageName),
+					imagePathOnDevice: filepath.Join("/", c.installDirOnDevice, arch.String(), imageName),
+					imagesDeps:        c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"),
+					dexLocations:      c.modules.DevicePaths(ctx.Config(), target.Os),
+				}
+				variant.dexLocationsDeps = variant.dexLocations
+				c.variants = append(c.variants, variant)
+			}
+
+			c.zip = c.dir.Join(ctx, c.name+".zip")
+		}
+
+		// specific to the framework config
+		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
+		for i := range targets {
+			frameworkCfg.variants[i].primaryImages = artCfg.variants[i].imagePathOnHost
+			frameworkCfg.variants[i].primaryImagesDeps = artCfg.variants[i].imagesDeps.Paths()
+			frameworkCfg.variants[i].dexLocationsDeps = append(artCfg.variants[i].dexLocations, frameworkCfg.variants[i].dexLocationsDeps...)
+		}
+
+		return configs
+	}).(map[string]*bootImageConfig)
+}
+
+func defaultBootImageConfig(ctx android.PathContext) *bootImageConfig {
+	return genBootImageConfigs(ctx)[frameworkBootImageName]
+}
+
+// Apex boot config allows to access build/install paths of apex boot jars without going
+// through the usual trouble of registering dependencies on those modules and extracting build paths
+// from those dependencies.
+type apexBootConfig struct {
+	// A list of apex boot jars.
+	modules android.ConfiguredJarList
+
+	// A list of predefined build paths to apex boot jars. They are configured very early,
+	// before the modules for these jars are processed and the actual paths are generated, and
+	// later on a singleton adds commands to copy actual jars to the predefined paths.
+	dexPaths android.WritablePaths
+
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
+	// A list of dex locations (a.k.a. on-device paths) to the boot jars.
+	dexLocations []string
+}
+
+var updatableBootConfigKey = android.NewOnceKey("apexBootConfig")
+
+// Returns apex boot config.
+func GetApexBootConfig(ctx android.PathContext) apexBootConfig {
+	return ctx.Config().Once(updatableBootConfigKey, func() interface{} {
+		apexBootJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars
+
+		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "apex_bootjars")
+		dexPaths := apexBootJars.BuildPaths(ctx, dir)
+		dexPathsByModuleName := apexBootJars.BuildPathsByModule(ctx, dir)
+
+		dexLocations := apexBootJars.DevicePaths(ctx.Config(), android.Android)
+
+		return apexBootConfig{apexBootJars, dexPaths, dexPathsByModuleName, dexLocations}
+	}).(apexBootConfig)
+}
+
+// Returns a list of paths and a list of locations for the boot jars used in dexpreopt (to be
+// passed in -Xbootclasspath and -Xbootclasspath-locations arguments for dex2oat).
+func bcpForDexpreopt(ctx android.PathContext, withUpdatable bool) (android.WritablePaths, []string) {
+	// Non-updatable boot jars (they are used both in the boot image and in dexpreopt).
+	bootImage := defaultBootImageConfig(ctx)
+	dexPaths := bootImage.dexPathsDeps
+	// The dex locations for all Android variants are identical.
+	dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps
+
+	if withUpdatable {
+		// Apex boot jars (they are used only in dexpreopt, but not in the boot image).
+		apexBootConfig := GetApexBootConfig(ctx)
+		dexPaths = append(dexPaths, apexBootConfig.dexPaths...)
+		dexLocations = append(dexLocations, apexBootConfig.dexLocations...)
+	}
+
+	return dexPaths, dexLocations
+}
+
+var defaultBootclasspathKey = android.NewOnceKey("defaultBootclasspath")
+
+var copyOf = android.CopyOf
+
+func init() {
+	android.RegisterMakeVarsProvider(pctx, dexpreoptConfigMakevars)
+}
+
+func dexpreoptConfigMakevars(ctx android.MakeVarsContext) {
+	ctx.Strict("DEXPREOPT_BOOT_JARS_MODULES", strings.Join(defaultBootImageConfig(ctx).modules.CopyOfApexJarPairs(), ":"))
+}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 3b1f7c0..115388b 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -135,6 +135,9 @@
 	// if set to true, Metalava will allow framework SDK to contain API levels annotations.
 	Api_levels_annotations_enabled *bool
 
+	// Apply the api levels database created by this module rather than generating one in this droidstubs.
+	Api_levels_module *string
+
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
@@ -234,6 +237,7 @@
 var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"}
 var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"}
 var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"}
+var metalavaAPILevelsModuleTag = dependencyTag{name: "metalava-api-levels-module-tag"}
 
 func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) {
 	d.Javadoc.addDeps(ctx)
@@ -255,6 +259,10 @@
 			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
 		}
 	}
+
+	if d.properties.Api_levels_module != nil {
+		ctx.AddDependency(ctx.Module(), metalavaAPILevelsModuleTag, proptools.String(d.properties.Api_levels_module))
+	}
 }
 
 func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
@@ -365,21 +373,35 @@
 }
 
 func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if !Bool(d.properties.Api_levels_annotations_enabled) {
-		return
+	var apiVersions android.Path
+	if proptools.Bool(d.properties.Api_levels_annotations_enabled) {
+		d.apiLevelsGenerationFlags(ctx, cmd)
+		apiVersions = d.apiVersionsXml
+	} else {
+		ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) {
+			if s, ok := m.(*Droidstubs); ok {
+				apiVersions = s.apiVersionsXml
+			} else {
+				ctx.PropertyErrorf("api_levels_module",
+					"module %q is not a droidstubs module", ctx.OtherModuleName(m))
+			}
+		})
 	}
+	if apiVersions != nil {
+		cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
+		cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
+		cmd.FlagWithInput("--apply-api-levels ", apiVersions)
+	}
+}
 
-	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
-
+func (d *Droidstubs) apiLevelsGenerationFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
 	if len(d.properties.Api_levels_annotations_dirs) == 0 {
 		ctx.PropertyErrorf("api_levels_annotations_dirs",
 			"has to be non-empty if api levels annotations was enabled!")
 	}
 
+	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
 	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
-	cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
 
 	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
 
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 10d99f3..9fdfdde 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -46,6 +46,12 @@
 			api_levels_annotations_enabled: true,
 			api_levels_jar_filename: "android.other.jar",
 		}
+
+		droidstubs {
+			name: "stubs-applying-api-versions",
+			srcs: ["bar-doc/a.java"],
+			api_levels_module: "bar-stubs-other",
+		}
 		`,
 		map[string][]byte{
 			"bar-doc/a.java": nil,
@@ -53,26 +59,37 @@
 	testcases := []struct {
 		moduleName          string
 		expectedJarFilename string
+		generate_xml        bool
 		high_mem            bool
 	}{
 		{
 			moduleName:          "bar-stubs",
+			generate_xml:        true,
 			expectedJarFilename: "android.jar",
 			high_mem:            false,
 		},
 		{
 			moduleName:          "bar-stubs-other",
+			generate_xml:        true,
 			expectedJarFilename: "android.other.jar",
 			high_mem:            true,
 		},
+		{
+			moduleName:   "stubs-applying-api-versions",
+			generate_xml: false,
+		},
 	}
 	for _, c := range testcases {
 		m := ctx.ModuleForTests(c.moduleName, "android_common")
 		manifest := m.Output("metalava.sbox.textproto")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest)
-		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
-		if actual := String(sboxProto.Commands[0].Command); !strings.Contains(actual, expected) {
-			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
+		cmdline := String(sboxProto.Commands[0].Command)
+		android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml)
+		if c.expectedJarFilename != "" {
+			expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
+			if !strings.Contains(cmdline, expected) {
+				t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline)
+			}
 		}
 
 		metalava := m.Rule("metalava")
diff --git a/java/fuzz.go b/java/fuzz.go
index 257f343..584c80b 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -15,14 +15,25 @@
 package java
 
 import (
-	"github.com/google/blueprint/proptools"
 	"sort"
 	"strings"
 
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/fuzz"
 )
 
+type jniProperties struct {
+	// list of jni libs
+	Jni_libs []string
+
+	// sanitization
+	Sanitizers []string
+}
+
 func init() {
 	RegisterJavaFuzzBuildComponents(android.InitRegistrationContext)
 }
@@ -35,11 +46,60 @@
 type JavaFuzzLibrary struct {
 	Library
 	fuzzPackagedModule fuzz.FuzzPackagedModule
+	jniProperties      jniProperties
+	jniFilePaths       android.Paths
+}
+
+// IsSanitizerEnabled implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) IsSanitizerEnabled(ctx android.BaseModuleContext, sanitizerName string) bool {
+	for _, s := range j.jniProperties.Sanitizers {
+		if sanitizerName == s {
+			return true
+		}
+	}
+	return false
+}
+
+// IsSanitizerEnabledForJni implemented to make JavaFuzzLibrary implement
+// cc.JniSanitizeable. It returns a bool for whether a cc dependency should be
+// sanitized for the given sanitizer or not.
+func (j *JavaFuzzLibrary) IsSanitizerEnabledForJni(ctx android.BaseModuleContext, sanitizerName string) bool {
+	return j.IsSanitizerEnabled(ctx, sanitizerName)
+}
+
+// EnableSanitizer implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) EnableSanitizer(sanitizerName string) {
+}
+
+// AddSanitizerDependencies implemented to make JavaFuzzLibrary implement
+// cc.Sanitizeable
+func (j *JavaFuzzLibrary) AddSanitizerDependencies(mctx android.BottomUpMutatorContext, sanitizerName string) {
+}
+
+// To verify that JavaFuzzLibrary implements cc.Sanitizeable
+var _ cc.Sanitizeable = (*JavaFuzzLibrary)(nil)
+
+func (j *JavaFuzzLibrary) DepsMutator(mctx android.BottomUpMutatorContext) {
+	if len(j.jniProperties.Jni_libs) > 0 {
+		if j.fuzzPackagedModule.FuzzProperties.Fuzz_config == nil {
+			config := &fuzz.FuzzConfig{}
+			j.fuzzPackagedModule.FuzzProperties.Fuzz_config = config
+		}
+		// this will be used by the ingestion pipeline to determine the version
+		// of jazzer to add to the fuzzer package
+		j.fuzzPackagedModule.FuzzProperties.Fuzz_config.IsJni = proptools.BoolPtr(true)
+
+		for _, target := range mctx.MultiTargets() {
+			sharedLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+			mctx.AddFarVariationDependencies(sharedLibVariations, cc.JniFuzzLibTag, j.jniProperties.Jni_libs...)
+		}
+	}
+	j.Library.DepsMutator(mctx)
 }
 
 func (j *JavaFuzzLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	j.Library.GenerateAndroidBuildActions(ctx)
-
 	if j.fuzzPackagedModule.FuzzProperties.Corpus != nil {
 		j.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, j.fuzzPackagedModule.FuzzProperties.Corpus)
 	}
@@ -55,6 +115,23 @@
 		android.WriteFileRule(ctx, configPath, j.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
 		j.fuzzPackagedModule.Config = configPath
 	}
+
+	ctx.VisitDirectDepsWithTag(cc.JniFuzzLibTag, func(dep android.Module) {
+		sharedLibInfo := ctx.OtherModuleProvider(dep, cc.SharedLibraryInfoProvider).(cc.SharedLibraryInfo)
+		if sharedLibInfo.SharedLibrary != nil {
+			libPath := android.PathForModuleOut(ctx, sharedLibInfo.SharedLibrary.Base())
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  sharedLibInfo.SharedLibrary,
+				Output: libPath,
+			})
+			j.jniFilePaths = append(j.jniFilePaths, libPath)
+		} else {
+			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
+		}
+	})
+
+	j.Library.GenerateAndroidBuildActions(ctx)
 }
 
 // java_fuzz builds and links sources into a `.jar` file for the host.
@@ -65,7 +142,8 @@
 	module := &JavaFuzzLibrary{}
 
 	module.addHostProperties()
-	module.Module.properties.Installable = proptools.BoolPtr(false)
+	module.AddProperties(&module.jniProperties)
+	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.AddProperties(&module.fuzzPackagedModule.FuzzProperties)
 
 	// java_fuzz packaging rules collide when both linux_glibc and linux_bionic are enabled, disable the linux_bionic variants.
@@ -83,7 +161,7 @@
 
 	module.initModuleAndImport(module)
 	android.InitSdkAwareModule(module)
-	InitJavaModule(module, android.HostSupported)
+	InitJavaModuleMultiTargets(module, android.HostSupported)
 	return module
 }
 
@@ -106,26 +184,26 @@
 
 	ctx.VisitAllModules(func(module android.Module) {
 		// Discard non-fuzz targets.
-		javaModule, ok := module.(*JavaFuzzLibrary)
+		javaFuzzModule, ok := module.(*JavaFuzzLibrary)
 		if !ok {
 			return
 		}
 
 		fuzzModuleValidator := fuzz.FuzzModule{
-			javaModule.ModuleBase,
-			javaModule.DefaultableModuleBase,
-			javaModule.ApexModuleBase,
+			javaFuzzModule.ModuleBase,
+			javaFuzzModule.DefaultableModuleBase,
+			javaFuzzModule.ApexModuleBase,
 		}
 
-		if ok := fuzz.IsValid(fuzzModuleValidator); !ok || *javaModule.Module.properties.Installable {
+		if ok := fuzz.IsValid(fuzzModuleValidator); !ok {
 			return
 		}
 
 		hostOrTargetString := "target"
-		if javaModule.Host() {
+		if javaFuzzModule.Host() {
 			hostOrTargetString = "host"
 		}
-		archString := javaModule.Arch().ArchType.String()
+		archString := javaFuzzModule.Arch().ArchType.String()
 
 		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
 		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
@@ -134,12 +212,17 @@
 		builder := android.NewRuleBuilder(pctx, ctx)
 
 		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
-		files = s.PackageArtifacts(ctx, module, javaModule.fuzzPackagedModule, archDir, builder)
+		files = s.PackageArtifacts(ctx, module, javaFuzzModule.fuzzPackagedModule, archDir, builder)
 
 		// Add .jar
-		files = append(files, fuzz.FileToZip{javaModule.outputFile, ""})
+		files = append(files, fuzz.FileToZip{javaFuzzModule.outputFile, ""})
 
-		archDirs[archOs], ok = s.BuildZipFile(ctx, module, javaModule.fuzzPackagedModule, files, builder, archDir, archString, "host", archOs, archDirs)
+		// Add jni .so files
+		for _, fPath := range javaFuzzModule.jniFilePaths {
+			files = append(files, fuzz.FileToZip{fPath, ""})
+		}
+
+		archDirs[archOs], ok = s.BuildZipFile(ctx, module, javaFuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
 		if !ok {
 			return
 		}
diff --git a/java/fuzz_test.go b/java/fuzz_test.go
index cf063eb..0a2c945 100644
--- a/java/fuzz_test.go
+++ b/java/fuzz_test.go
@@ -15,13 +15,17 @@
 package java
 
 import (
-	"android/soong/android"
 	"path/filepath"
+	"runtime"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
 )
 
 var prepForJavaFuzzTest = android.GroupFixturePreparers(
 	PrepareForTestWithJavaDefaultModules,
+	cc.PrepareForTestWithCcBuildComponents,
 	android.FixtureRegisterWithContext(RegisterJavaFuzzBuildComponents),
 )
 
@@ -32,6 +36,13 @@
 			srcs: ["a.java"],
 			libs: ["bar"],
 			static_libs: ["baz"],
+            jni_libs: [
+                "libjni",
+            ],
+            sanitizers: [
+                "address",
+                "fuzzer",
+            ],
 		}
 
 		java_library_host {
@@ -42,11 +53,21 @@
 		java_library_host {
 			name: "baz",
 			srcs: ["c.java"],
-		}`)
+		}
+
+		cc_library_shared {
+			name: "libjni",
+			host_supported: true,
+			device_supported: false,
+			stl: "none",
+		}
+		`)
 
 	osCommonTarget := result.Config.BuildOSCommonTarget.String()
-	javac := result.ModuleForTests("foo", osCommonTarget).Rule("javac")
-	combineJar := result.ModuleForTests("foo", osCommonTarget).Description("for javac")
+
+	osCommonTargetWithSan := osCommonTarget + "_asan" + "_fuzzer"
+	javac := result.ModuleForTests("foo", osCommonTargetWithSan).Rule("javac")
+	combineJar := result.ModuleForTests("foo", osCommonTargetWithSan).Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
@@ -62,4 +83,18 @@
 	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
 	}
+
+	ctx := result.TestContext
+	foo := ctx.ModuleForTests("foo", osCommonTargetWithSan).Module().(*JavaFuzzLibrary)
+
+	expected := "libjni.so"
+	if runtime.GOOS == "darwin" {
+		expected = "libjni.dylib"
+	}
+
+	fooJniFilePaths := foo.jniFilePaths
+	if len(fooJniFilePaths) != 1 || fooJniFilePaths[0].Rel() != expected {
+		t.Errorf(`expected foo test data relative path [%q], got %q`,
+			expected, fooJniFilePaths.Strings())
+	}
 }
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 3af5f1c..cf9c7ad 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -65,6 +65,8 @@
 type hiddenAPIModule interface {
 	android.Module
 	hiddenAPIIntf
+
+	MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec
 }
 
 type hiddenAPIIntf interface {
@@ -148,7 +150,7 @@
 	// Create a copy of the dex jar which has been encoded with hiddenapi flags.
 	flagsCSV := hiddenAPISingletonPaths(ctx).flags
 	outputDir := android.PathForModuleOut(ctx, "hiddenapi").OutputPath
-	encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, outputDir)
+	encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, android.SdkSpecNone, outputDir)
 
 	// Use the encoded dex jar from here onwards.
 	return encodedDex
@@ -246,7 +248,7 @@
 // The encode dex rule requires unzipping, encoding and rezipping the classes.dex files along with
 // all the resources from the input jar. It also ensures that if it was uncompressed in the input
 // it stays uncompressed in the output.
-func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, outputDir android.OutputPath) android.OutputPath {
+func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, minSdkVersion android.SdkSpec, outputDir android.OutputPath) android.OutputPath {
 
 	// The output file has the same name as the input file and is in the output directory.
 	output := outputDir.Join(ctx, dexInput.Base())
@@ -274,6 +276,15 @@
 		hiddenapiFlags = "--no-force-assign-all"
 	}
 
+	// If the library is targeted for Q and/or R then make sure that they do not
+	// have any S+ flags encoded as that will break the runtime.
+	minApiLevel := minSdkVersion.ApiLevel
+	if !minApiLevel.IsNone() {
+		if minApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(ctx, "R")) {
+			hiddenapiFlags = hiddenapiFlags + " --max-hiddenapi-level=max-target-r"
+		}
+	}
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIEncodeDexRule,
 		Description: "hiddenapi encode dex",
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 534a814..c90b2ff 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -1104,7 +1104,7 @@
 	for _, name := range android.SortedStringKeys(bootDexInfoByModule) {
 		bootDexInfo := bootDexInfoByModule[name]
 		unencodedDex := bootDexInfo.path
-		encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, outputDir)
+		encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, allFlagsCSV, bootDexInfo.uncompressDex, bootDexInfo.minSdkVersion, outputDir)
 		encodedBootDexJarsByModule[name] = encodedDex
 	}
 
@@ -1188,6 +1188,9 @@
 
 	// Indicates whether the dex jar needs uncompressing before encoding.
 	uncompressDex bool
+
+	// The minimum sdk version that the dex jar will be used on.
+	minSdkVersion android.SdkSpec
 }
 
 // bootDexInfoByModule is a map from module name (as returned by module.Name()) to the boot dex
@@ -1213,6 +1216,7 @@
 		bootDexJarsByModule[module.Name()] = bootDexInfo{
 			path:          bootDexJar,
 			uncompressDex: *hiddenAPIModule.uncompressDex(),
+			minSdkVersion: hiddenAPIModule.MinSdkVersion(ctx),
 		}
 	}
 
diff --git a/java/java.go b/java/java.go
index b34d6de..079d4b9 100644
--- a/java/java.go
+++ b/java/java.go
@@ -300,19 +300,11 @@
 
 type usesLibraryDependencyTag struct {
 	dependencyTag
-
-	// SDK version in which the library appared as a standalone library.
-	sdkVersion int
-
-	// If the dependency is optional or required.
-	optional bool
-
-	// Whether this is an implicit dependency inferred by Soong, or an explicit one added via
-	// `uses_libs`/`optional_uses_libs` properties.
-	implicit bool
+	sdkVersion int  // SDK version in which the library appared as a standalone library.
+	optional   bool // If the dependency is optional or required.
 }
 
-func makeUsesLibraryDependencyTag(sdkVersion int, optional bool, implicit bool) usesLibraryDependencyTag {
+func makeUsesLibraryDependencyTag(sdkVersion int, optional bool) usesLibraryDependencyTag {
 	return usesLibraryDependencyTag{
 		dependencyTag: dependencyTag{
 			name:          fmt.Sprintf("uses-library-%d", sdkVersion),
@@ -320,7 +312,6 @@
 		},
 		sdkVersion: sdkVersion,
 		optional:   optional,
-		implicit:   implicit,
 	}
 }
 
@@ -351,6 +342,11 @@
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	jniInstallTag           = installDependencyTag{name: "jni install"}
 	binaryInstallTag        = installDependencyTag{name: "binary install"}
+	usesLibReqTag           = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false)
+	usesLibOptTag           = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true)
+	usesLibCompat28OptTag   = makeUsesLibraryDependencyTag(28, true)
+	usesLibCompat29ReqTag   = makeUsesLibraryDependencyTag(29, false)
+	usesLibCompat30OptTag   = makeUsesLibraryDependencyTag(30, true)
 )
 
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
@@ -1996,10 +1992,8 @@
 	depTag := ctx.OtherModuleDependencyTag(depModule)
 	if depTag == libTag {
 		// Ok, propagate <uses-library> through non-static library dependencies.
-	} else if tag, ok := depTag.(usesLibraryDependencyTag); ok &&
-		tag.sdkVersion == dexpreopt.AnySdkVersion && tag.implicit {
-		// Ok, propagate <uses-library> through non-compatibility implicit <uses-library>
-		// dependencies.
+	} else if tag, ok := depTag.(usesLibraryDependencyTag); ok && tag.sdkVersion == dexpreopt.AnySdkVersion {
+		// Ok, propagate <uses-library> through non-compatibility <uses-library> dependencies.
 	} else if depTag == staticLibTag {
 		// Propagate <uses-library> through static library dependencies, unless it is a component
 		// library (such as stubs). Component libraries have a dependency on their SDK library,
@@ -2017,14 +2011,56 @@
 	// <uses_library> and should not be added to CLC, but the transitive <uses-library> dependencies
 	// from its CLC should be added to the current CLC.
 	if sdkLib != nil {
-		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, false, true,
+		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, false,
 			dep.DexJarBuildPath().PathOrNil(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
 	} else {
 		clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
 	}
 }
 
+type javaResourcesAttributes struct {
+	Resources             bazel.LabelListAttribute
+	Resource_strip_prefix *string
+}
+
+func (m *Library) convertJavaResourcesAttributes(ctx android.TopDownMutatorContext) *javaResourcesAttributes {
+	var resources bazel.LabelList
+	var resourceStripPrefix *string
+
+	if m.properties.Java_resources != nil {
+		resources.Append(android.BazelLabelForModuleSrc(ctx, m.properties.Java_resources))
+	}
+
+	//TODO(b/179889880) handle case where glob includes files outside package
+	resDeps := ResourceDirsToFiles(
+		ctx,
+		m.properties.Java_resource_dirs,
+		m.properties.Exclude_java_resource_dirs,
+		m.properties.Exclude_java_resources,
+	)
+
+	for i, resDep := range resDeps {
+		dir, files := resDep.dir, resDep.files
+
+		resources.Append(bazel.MakeLabelList(android.RootToModuleRelativePaths(ctx, files)))
+
+		// Bazel includes the relative path from the WORKSPACE root when placing the resource
+		// inside the JAR file, so we need to remove that prefix
+		resourceStripPrefix = proptools.StringPtr(dir.String())
+		if i > 0 {
+			// TODO(b/226423379) allow multiple resource prefixes
+			ctx.ModuleErrorf("bp2build does not support more than one directory in java_resource_dirs (b/226423379)")
+		}
+	}
+
+	return &javaResourcesAttributes{
+		Resources:             bazel.MakeLabelListAttribute(resources),
+		Resource_strip_prefix: resourceStripPrefix,
+	}
+}
+
 type javaCommonAttributes struct {
+	*javaResourcesAttributes
 	Srcs      bazel.LabelListAttribute
 	Plugins   bazel.LabelListAttribute
 	Javacopts bazel.StringListAttribute
@@ -2089,6 +2125,11 @@
 	if m.properties.Javacflags != nil {
 		javacopts = append(javacopts, m.properties.Javacflags...)
 	}
+	if m.properties.Java_version != nil {
+		javaVersion := normalizeJavaVersion(ctx, *m.properties.Java_version).String()
+		javacopts = append(javacopts, fmt.Sprintf("-source %s -target %s", javaVersion, javaVersion))
+	}
+
 	epEnabled := m.properties.Errorprone.Enabled
 	//TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
 	if Bool(epEnabled) {
@@ -2096,7 +2137,8 @@
 	}
 
 	commonAttrs := &javaCommonAttributes{
-		Srcs: javaSrcs,
+		Srcs:                    javaSrcs,
+		javaResourcesAttributes: m.convertJavaResourcesAttributes(ctx),
 		Plugins: bazel.MakeLabelListAttribute(
 			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
 		),
diff --git a/java/java_resources.go b/java/java_resources.go
index 787d74a..b0dc5a1 100644
--- a/java/java_resources.go
+++ b/java/java_resources.go
@@ -33,8 +33,13 @@
 	"**/*~",
 }
 
-func ResourceDirsToJarArgs(ctx android.ModuleContext,
-	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) {
+type resourceDeps struct {
+	dir   android.Path
+	files android.Paths
+}
+
+func ResourceDirsToFiles(ctx android.BaseModuleContext,
+	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (deps []resourceDeps) {
 	var excludeDirs []string
 	var excludeFiles []string
 
@@ -55,21 +60,36 @@
 		dirs := ctx.Glob(android.PathForSource(ctx, ctx.ModuleDir()).Join(ctx, resourceDir).String(), excludeDirs)
 		for _, dir := range dirs {
 			files := ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), excludeFiles)
+			deps = append(deps, resourceDeps{
+				dir:   dir,
+				files: files,
+			})
+		}
+	}
 
+	return deps
+}
+
+func ResourceDirsToJarArgs(ctx android.ModuleContext,
+	resourceDirs, excludeResourceDirs, excludeResourceFiles []string) (args []string, deps android.Paths) {
+	resDeps := ResourceDirsToFiles(ctx, resourceDirs, excludeResourceDirs, excludeResourceFiles)
+
+	for _, resDep := range resDeps {
+		dir, files := resDep.dir, resDep.files
+
+		if len(files) > 0 {
+			args = append(args, "-C", dir.String())
 			deps = append(deps, files...)
 
-			if len(files) > 0 {
-				args = append(args, "-C", dir.String())
-
-				for _, f := range files {
-					path := f.String()
-					if !strings.HasPrefix(path, dir.String()) {
-						panic(fmt.Errorf("path %q does not start with %q", path, dir))
-					}
-					args = append(args, "-f", pathtools.MatchEscape(path))
+			for _, f := range files {
+				path := f.String()
+				if !strings.HasPrefix(path, dir.String()) {
+					panic(fmt.Errorf("path %q does not start with %q", path, dir))
 				}
+				args = append(args, "-f", pathtools.MatchEscape(path))
 			}
 		}
+
 	}
 
 	return args, deps
diff --git a/java/jdeps.go b/java/jdeps.go
index eff9a31..3734335 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -71,6 +71,8 @@
 		dpInfo.Jars = android.FirstUniqueStrings(dpInfo.Jars)
 		dpInfo.SrcJars = android.FirstUniqueStrings(dpInfo.SrcJars)
 		dpInfo.Paths = android.FirstUniqueStrings(dpInfo.Paths)
+		dpInfo.Static_libs = android.FirstUniqueStrings(dpInfo.Static_libs)
+		dpInfo.Libs = android.FirstUniqueStrings(dpInfo.Libs)
 		moduleInfos[name] = dpInfo
 
 		mkProvider, ok := module.(android.AndroidMkDataProvider)
diff --git a/java/kotlin.go b/java/kotlin.go
index eff5bb5..903c624 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -175,6 +175,7 @@
 
 	var deps android.Paths
 	deps = append(deps, flags.kotlincClasspath...)
+	deps = append(deps, flags.kotlincDeps...)
 	deps = append(deps, srcJars...)
 	deps = append(deps, flags.processorPath...)
 	deps = append(deps, commonSrcFiles...)
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index f9ff982..491ce29 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -42,6 +42,11 @@
 		}
 		`)
 
+	kotlinStdlib := ctx.ModuleForTests("kotlin-stdlib", "android_common").
+		Output("turbine-combined/kotlin-stdlib.jar").Output
+	kotlinAnnotations := ctx.ModuleForTests("kotlin-annotations", "android_common").
+		Output("turbine-combined/kotlin-annotations.jar").Output
+
 	fooKotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
 	fooJavac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	fooJar := ctx.ModuleForTests("foo", "android_common").Output("combined/foo.jar")
@@ -69,6 +74,16 @@
 			fooJar.Inputs.Strings(), fooKotlincClasses.String())
 	}
 
+	if !inList(kotlinStdlib.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinStdlib.String())
+	}
+
+	if !inList(kotlinAnnotations.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinAnnotations.String())
+	}
+
 	if !inList(fooKotlincHeaderClasses.String(), fooHeaderJar.Inputs.Strings()) {
 		t.Errorf("foo header jar inputs %v does not contain %q",
 			fooHeaderJar.Inputs.Strings(), fooKotlincHeaderClasses.String())
@@ -325,6 +340,7 @@
 		java_library {
 			name: "withcompose",
 			srcs: ["a.kt"],
+			plugins: ["plugin"],
 			static_libs: ["androidx.compose.runtime_runtime"],
 		}
 
@@ -332,6 +348,10 @@
 			name: "nocompose",
 			srcs: ["a.kt"],
 		}
+
+		java_plugin {
+			name: "plugin",
+		}
 	`)
 
 	buildOS := result.Config.BuildOS.String()
@@ -346,6 +366,9 @@
 	android.AssertStringDoesContain(t, "missing compose compiler plugin",
 		withCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
 
+	android.AssertStringListContains(t, "missing kapt compose compiler dependency",
+		withCompose.Rule("kapt").Implicits.Strings(), composeCompiler.String())
+
 	android.AssertStringListDoesNotContain(t, "unexpected compose compiler dependency",
 		noCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String())
 
diff --git a/java/lint.go b/java/lint.go
index e97c9c2..22c9ec4 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -75,9 +75,9 @@
 	extraLintCheckJars      android.Paths
 	test                    bool
 	library                 bool
-	minSdkVersion           string
-	targetSdkVersion        string
-	compileSdkVersion       string
+	minSdkVersion           android.ApiLevel
+	targetSdkVersion        android.ApiLevel
+	compileSdkVersion       android.ApiLevel
 	compileSdkKind          android.SdkKind
 	javaLanguageLevel       string
 	kotlinLanguageLevel     string
@@ -300,7 +300,7 @@
 		Text(`echo "<manifest xmlns:android='http://schemas.android.com/apk/res/android'" &&`).
 		Text(`echo "    android:versionCode='1' android:versionName='1' >" &&`).
 		Textf(`echo "  <uses-sdk android:minSdkVersion='%s' android:targetSdkVersion='%s'/>" &&`,
-			l.minSdkVersion, l.targetSdkVersion).
+			l.minSdkVersion.String(), l.targetSdkVersion.String()).
 		Text(`echo "</manifest>"`).
 		Text(") >").Output(manifestPath)
 
@@ -325,7 +325,7 @@
 		return
 	}
 
-	if l.minSdkVersion != l.compileSdkVersion {
+	if l.minSdkVersion.CompareTo(l.compileSdkVersion) == -1 {
 		l.extraMainlineLintErrors = append(l.extraMainlineLintErrors, updatabilityChecks...)
 		_, filtered := android.FilterList(l.properties.Lint.Warning_checks, updatabilityChecks)
 		if len(filtered) != 0 {
@@ -427,7 +427,7 @@
 		FlagWithOutput("--html ", html).
 		FlagWithOutput("--text ", text).
 		FlagWithOutput("--xml ", xml).
-		FlagWithArg("--compile-sdk-version ", l.compileSdkVersion).
+		FlagWithArg("--compile-sdk-version ", l.compileSdkVersion.String()).
 		FlagWithArg("--java-language-level ", l.javaLanguageLevel).
 		FlagWithArg("--kotlin-language-level ", l.kotlinLanguageLevel).
 		FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
@@ -524,10 +524,18 @@
 		return
 	}
 
-	frameworkDocStubs := findModuleOrErr(ctx, "framework-doc-stubs")
-	if frameworkDocStubs == nil {
+	apiVersionsDb := findModuleOrErr(ctx, "api_versions_public")
+	if apiVersionsDb == nil {
 		if !ctx.Config().AllowMissingDependencies() {
-			ctx.Errorf("lint: missing framework-doc-stubs")
+			ctx.Errorf("lint: missing module api_versions_public")
+		}
+		return
+	}
+
+	sdkAnnotations := findModuleOrErr(ctx, "sdk-annotations.zip")
+	if sdkAnnotations == nil {
+		if !ctx.Config().AllowMissingDependencies() {
+			ctx.Errorf("lint: missing module sdk-annotations.zip")
 		}
 		return
 	}
@@ -542,13 +550,13 @@
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
-		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".annotations.zip"),
+		Input:  android.OutputFileForModule(ctx, sdkAnnotations, ""),
 		Output: copiedAnnotationsZipPath(ctx),
 	})
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
-		Input:  android.OutputFileForModule(ctx, frameworkDocStubs, ".api_versions.xml"),
+		Input:  android.OutputFileForModule(ctx, apiVersionsDb, ".api_versions.xml"),
 		Output: copiedAPIVersionsXmlPath(ctx, "api_versions.xml"),
 	})
 
diff --git a/java/lint_defaults.txt b/java/lint_defaults.txt
index 4bc0c5f..1eee354 100644
--- a/java/lint_defaults.txt
+++ b/java/lint_defaults.txt
@@ -28,6 +28,11 @@
 --disable_check SuspiciousImport
 --disable_check UnusedResources
 --disable_check ViewConstructor
+# Disable NewApi checks for the platform since platform is the one that implements
+# the API. This prevents noisy lint warnings like b/228956345#1
+# NewApi checks will continue to be enforced for apex deps since
+# lint.strict_updatability_linting will be true for those Soong modules
+--disable_check NewApi
 
 # Downgrade existing errors to warnings
 --warning_check AppCompatResource                  # 55 occurences in 10 modules
@@ -66,7 +71,6 @@
 --warning_check MissingTvBanner                    # 3 occurences in 3 modules
 --warning_check NamespaceTypo                      # 3 occurences in 3 modules
 --warning_check NetworkSecurityConfig              # 46 occurences in 12 modules
---warning_check NewApi                             # 1996 occurences in 122 modules
 --warning_check NotSibling                         # 15 occurences in 10 modules
 --warning_check ObjectAnimatorBinding              # 14 occurences in 5 modules
 --warning_check OnClick                            # 49 occurences in 21 modules
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 1c2a3ae..6257e49 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -51,6 +51,7 @@
 	var addSourceBootclassPathModule = android.FixtureAddTextFile("source/Android.bp", `
 		java_library {
 			name: "foo",
+			host_supported: true, // verify that b/232106778 is fixed
 			srcs: ["a.java"],
 			system_modules: "none",
 			sdk_version: "none",
diff --git a/java/sdk_library.go b/java/sdk_library.go
index c37ed1a..cd8e875 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -377,6 +377,9 @@
 	// List of Java libraries that will be in the classpath when building the implementation lib
 	Impl_only_libs []string `android:"arch_variant"`
 
+	// List of Java libraries that will included in the implementation lib.
+	Impl_only_static_libs []string `android:"arch_variant"`
+
 	// List of Java libraries that will be in the classpath when building stubs
 	Stub_only_libs []string `android:"arch_variant"`
 
@@ -1346,10 +1349,12 @@
 	visibility := childModuleVisibility(module.sdkLibraryProperties.Impl_library_visibility)
 
 	props := struct {
-		Name       *string
-		Visibility []string
-		Instrument bool
-		Libs       []string
+		Name           *string
+		Visibility     []string
+		Instrument     bool
+		Libs           []string
+		Static_libs    []string
+		Apex_available []string
 	}{
 		Name:       proptools.StringPtr(module.implLibraryModuleName()),
 		Visibility: visibility,
@@ -1358,6 +1363,12 @@
 		// Set the impl_only libs. Note that the module's "Libs" get appended as well, via the
 		// addition of &module.properties below.
 		Libs: module.sdkLibraryProperties.Impl_only_libs,
+		// Set the impl_only static libs. Note that the module's "static_libs" get appended as well, via the
+		// addition of &module.properties below.
+		Static_libs: module.sdkLibraryProperties.Impl_only_static_libs,
+		// Pass the apex_available settings down so that the impl library can be statically
+		// embedded within a library that is added to an APEX. Needed for updatable-media.
+		Apex_available: module.ApexAvailable(),
 	}
 
 	properties := []interface{}{
@@ -1814,8 +1825,9 @@
 		*javaSdkLibraries = append(*javaSdkLibraries, module.BaseModuleName())
 	}
 
-	// Add the impl_only_libs *after* we're done using the Libs prop in submodules.
+	// Add the impl_only_libs and impl_only_static_libs *after* we're done using them in submodules.
 	module.properties.Libs = append(module.properties.Libs, module.sdkLibraryProperties.Impl_only_libs...)
+	module.properties.Static_libs = append(module.properties.Static_libs, module.sdkLibraryProperties.Impl_only_static_libs...)
 }
 
 func (module *SdkLibrary) InitSdkLibraryProperties() {
@@ -2211,8 +2223,23 @@
 	return module.uniqueApexVariations()
 }
 
+// MinSdkVersion - Implements hiddenAPIModule
+func (module *SdkLibraryImport) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	return android.SdkSpecNone
+}
+
+var _ hiddenAPIModule = (*SdkLibraryImport)(nil)
+
 func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) {
-	return module.commonOutputFiles(tag)
+	paths, err := module.commonOutputFiles(tag)
+	if paths != nil || err != nil {
+		return paths, err
+	}
+	if module.implLibraryModule != nil {
+		return module.implLibraryModule.OutputFiles(tag)
+	} else {
+		return nil, nil
+	}
 }
 
 func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index e84eacd..cc83430 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -173,7 +173,7 @@
 	}
 	ok := true
 	for _, mkFile := range files {
-		ok = convertOne(mkFile) && ok
+		ok = convertOne(mkFile, []string{}) && ok
 	}
 
 	if *launcher != "" {
@@ -183,7 +183,7 @@
 		if *inputVariables == "" {
 			quit(fmt.Errorf("the product launcher requires an input variables file"))
 		}
-		if !convertOne(*inputVariables) {
+		if !convertOne(*inputVariables, []string{}) {
 			quit(fmt.Errorf("the product launcher input variables file failed to convert"))
 		}
 
@@ -201,7 +201,7 @@
 		if *inputVariables == "" {
 			quit(fmt.Errorf("the board launcher requires an input variables file"))
 		}
-		if !convertOne(*inputVariables) {
+		if !convertOne(*inputVariables, []string{}) {
 			quit(fmt.Errorf("the board launcher input variables file failed to convert"))
 		}
 		err := writeGenerated(*boardlauncher, mk2rbc.BoardLauncher(
@@ -310,9 +310,13 @@
 // the output hierarchy, or to the stdout.
 // Optionally, recursively convert the files this one includes by
 // $(call inherit-product) or an include statement.
-func convertOne(mkFile string) (ok bool) {
+func convertOne(mkFile string, loadStack []string) (ok bool) {
 	if v, ok := converted[mkFile]; ok {
-		return v != nil
+		if v == nil {
+			fmt.Fprintf(os.Stderr, "Cycle in load graph:\n%s\n%s\n\n", strings.Join(loadStack, "\n"), mkFile)
+			return false
+		}
+		return true
 	}
 	converted[mkFile] = nil
 	defer func() {
@@ -356,6 +360,7 @@
 			return false
 		}
 	}
+	loadStack = append(loadStack, mkFile)
 	ok = true
 	if *recurse {
 		for _, sub := range ss.SubConfigFiles() {
@@ -363,7 +368,7 @@
 			if _, err := os.Stat(sub); os.IsNotExist(err) {
 				continue
 			}
-			ok = convertOne(sub) && ok
+			ok = convertOne(sub, loadStack) && ok
 		}
 	}
 	converted[mkFile] = ss
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 54bb6d1..6a6eb46 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -232,19 +232,18 @@
 }
 
 type variableRefExpr struct {
-	ref       variable
-	isDefined bool
+	ref variable
 }
 
-func NewVariableRefExpr(ref variable, isDefined bool) starlarkExpr {
+func NewVariableRefExpr(ref variable) starlarkExpr {
 	if predefined, ok := ref.(*predefinedVariable); ok {
 		return predefined.value
 	}
-	return &variableRefExpr{ref, isDefined}
+	return &variableRefExpr{ref}
 }
 
 func (v *variableRefExpr) emit(gctx *generationContext) {
-	v.ref.emitGet(gctx, v.isDefined)
+	v.ref.emitGet(gctx)
 }
 
 func (v *variableRefExpr) typ() starlarkType {
@@ -742,8 +741,8 @@
 	return starlarkTypeUnknown
 }
 
-func (_ *badExpr) emitListVarCopy(_ *generationContext) {
-	panic("implement me")
+func (b *badExpr) emitListVarCopy(gctx *generationContext) {
+	b.emit(gctx)
 }
 
 func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index df18243..e59146b 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -80,13 +80,13 @@
 	"copy-files":                           &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
 	"dir":                                  &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
 	"dist-for-goals":                       &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
-	"enforce-product-packages-exist":       &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid},
+	"enforce-product-packages-exist":       &simpleCallParser{name: baseName + ".enforce_product_packages_exist", returnType: starlarkTypeVoid, addHandle: true},
 	"error":                                &makeControlFuncParser{name: baseName + ".mkerror"},
 	"findstring":                           &simpleCallParser{name: baseName + ".findstring", returnType: starlarkTypeInt},
 	"find-copy-subdir-files":               &simpleCallParser{name: baseName + ".find_and_copy", returnType: starlarkTypeList},
 	"filter":                               &simpleCallParser{name: baseName + ".filter", returnType: starlarkTypeList},
 	"filter-out":                           &simpleCallParser{name: baseName + ".filter_out", returnType: starlarkTypeList},
-	"firstword":                            &firstOrLastwordCallParser{isLastWord: false},
+	"firstword":                            &simpleCallParser{name: baseName + ".first_word", returnType: starlarkTypeString},
 	"foreach":                              &foreachCallParser{},
 	"if":                                   &ifCallParser{},
 	"info":                                 &makeControlFuncParser{name: baseName + ".mkinfo"},
@@ -97,7 +97,7 @@
 	"is-product-in-list":                   &isProductInListCallParser{},
 	"is-vendor-board-platform":             &isVendorBoardPlatformCallParser{},
 	"is-vendor-board-qcom":                 &isVendorBoardQcomCallParser{},
-	"lastword":                             &firstOrLastwordCallParser{isLastWord: true},
+	"lastword":                             &simpleCallParser{name: baseName + ".last_word", returnType: starlarkTypeString},
 	"notdir":                               &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString},
 	"math_max":                             &mathMaxOrMinCallParser{function: "max"},
 	"math_min":                             &mathMaxOrMinCallParser{function: "min"},
@@ -116,6 +116,7 @@
 	"subst":    &substCallParser{fname: "subst"},
 	"warning":  &makeControlFuncParser{name: baseName + ".mkwarning"},
 	"word":     &wordCallParser{},
+	"words":    &wordsCallParser{},
 	"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
 }
 
@@ -130,6 +131,14 @@
 	"foreach":                   &foreachCallNodeParser{},
 }
 
+// These look like variables, but are actually functions, and would give
+// undefined variable errors if we converted them as variables. Instead,
+// emit an error instead of converting them.
+var unsupportedFunctions = map[string]bool{
+	"local-generated-sources-dir": true,
+	"local-intermediates-dir":     true,
+}
+
 // These are functions that we don't implement conversions for, but
 // we allow seeing their definitions in the product config files.
 var ignoredDefines = map[string]bool{
@@ -197,17 +206,57 @@
 	return s == "error" || s == "warning" || s == "info"
 }
 
+// varAssignmentScope points to the last assignment for each variable
+// in the current block. It is used during the parsing to chain
+// the assignments to a variable together.
+type varAssignmentScope struct {
+	outer *varAssignmentScope
+	vars  map[string]bool
+}
+
 // Starlark output generation context
 type generationContext struct {
-	buf          strings.Builder
-	starScript   *StarlarkScript
-	indentLevel  int
-	inAssignment bool
-	tracedCount  int
+	buf            strings.Builder
+	starScript     *StarlarkScript
+	indentLevel    int
+	inAssignment   bool
+	tracedCount    int
+	varAssignments *varAssignmentScope
 }
 
 func NewGenerateContext(ss *StarlarkScript) *generationContext {
-	return &generationContext{starScript: ss}
+	return &generationContext{
+		starScript: ss,
+		varAssignments: &varAssignmentScope{
+			outer: nil,
+			vars:  make(map[string]bool),
+		},
+	}
+}
+
+func (gctx *generationContext) pushVariableAssignments() {
+	va := &varAssignmentScope{
+		outer: gctx.varAssignments,
+		vars:  make(map[string]bool),
+	}
+	gctx.varAssignments = va
+}
+
+func (gctx *generationContext) popVariableAssignments() {
+	gctx.varAssignments = gctx.varAssignments.outer
+}
+
+func (gctx *generationContext) hasBeenAssigned(v variable) bool {
+	for va := gctx.varAssignments; va != nil; va = va.outer {
+		if _, ok := va.vars[v.name()]; ok {
+			return true
+		}
+	}
+	return false
+}
+
+func (gctx *generationContext) setHasBeenAssigned(v variable) {
+	gctx.varAssignments.vars[v.name()] = true
 }
 
 // emit returns generated script
@@ -394,14 +443,6 @@
 	nodeLocator    func(pos mkparser.Pos) int
 }
 
-// varAssignmentScope points to the last assignment for each variable
-// in the current block. It is used during the parsing to chain
-// the assignments to a variable together.
-type varAssignmentScope struct {
-	outer *varAssignmentScope
-	vars  map[string]*assignmentNode
-}
-
 // parseContext holds the script we are generating and all the ephemeral data
 // needed during the parsing.
 type parseContext struct {
@@ -415,7 +456,6 @@
 	errorLogger      ErrorLogger
 	tracedVariables  map[string]bool // variables to be traced in the generated script
 	variables        map[string]variable
-	varAssignments   *varAssignmentScope
 	outputDir        string
 	dependentModules map[string]*moduleInfo
 	soongNamespaces  map[string]map[string]bool
@@ -428,6 +468,7 @@
 	predefined := []struct{ name, value string }{
 		{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
 		{"LOCAL_PATH", filepath.Dir(ss.mkFile)},
+		{"MAKEFILE_LIST", ss.mkFile},
 		{"TOPDIR", ""}, // TOPDIR is just set to an empty string in cleanbuild.mk and core.mk
 		// TODO(asmundak): maybe read it from build/make/core/envsetup.mk?
 		{"TARGET_COPY_OUT_SYSTEM", "system"},
@@ -468,7 +509,6 @@
 		typeHints:        make(map[string]starlarkType),
 		atTopOfMakefile:  true,
 	}
-	ctx.pushVarAssignments()
 	for _, item := range predefined {
 		ctx.variables[item.name] = &predefinedVariable{
 			baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
@@ -479,31 +519,6 @@
 	return ctx
 }
 
-func (ctx *parseContext) lastAssignment(v variable) *assignmentNode {
-	for va := ctx.varAssignments; va != nil; va = va.outer {
-		if v, ok := va.vars[v.name()]; ok {
-			return v
-		}
-	}
-	return nil
-}
-
-func (ctx *parseContext) setLastAssignment(v variable, asgn *assignmentNode) {
-	ctx.varAssignments.vars[v.name()] = asgn
-}
-
-func (ctx *parseContext) pushVarAssignments() {
-	va := &varAssignmentScope{
-		outer: ctx.varAssignments,
-		vars:  make(map[string]*assignmentNode),
-	}
-	ctx.varAssignments = va
-}
-
-func (ctx *parseContext) popVarAssignments() {
-	ctx.varAssignments = ctx.varAssignments.outer
-}
-
 func (ctx *parseContext) hasNodes() bool {
 	return ctx.currentNodeIndex < len(ctx.nodes)
 }
@@ -526,7 +541,7 @@
 
 func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
 	// Handle only simple variables
-	if !a.Name.Const() {
+	if !a.Name.Const() || a.Target != nil {
 		return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
 	}
 	name := a.Name.Strings[0]
@@ -537,6 +552,12 @@
 	if strings.HasPrefix(name, "override ") {
 		return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
 	}
+	if name == ".KATI_READONLY" {
+		// Skip assignments to .KATI_READONLY. If it was in the output file, it
+		// would be an error because it would be sorted before the definition of
+		// the variable it's trying to make readonly.
+		return []starlarkNode{}
+	}
 
 	// Soong configuration
 	if strings.HasPrefix(name, soongNsPrefix) {
@@ -551,9 +572,6 @@
 	if lhs.valueType() == starlarkTypeUnknown {
 		// Try to divine variable type from the RHS
 		asgn.value = ctx.parseMakeString(a, a.Value)
-		if xBad, ok := asgn.value.(*badExpr); ok {
-			return []starlarkNode{&exprNode{xBad}}
-		}
 		inferred_type := asgn.value.typ()
 		if inferred_type != starlarkTypeUnknown {
 			lhs.setValueType(inferred_type)
@@ -562,21 +580,19 @@
 	if lhs.valueType() == starlarkTypeList {
 		xConcat, xBad := ctx.buildConcatExpr(a)
 		if xBad != nil {
-			return []starlarkNode{&exprNode{expr: xBad}}
-		}
-		switch len(xConcat.items) {
-		case 0:
-			asgn.value = &listExpr{}
-		case 1:
-			asgn.value = xConcat.items[0]
-		default:
-			asgn.value = xConcat
+			asgn.value = xBad
+		} else {
+			switch len(xConcat.items) {
+			case 0:
+				asgn.value = &listExpr{}
+			case 1:
+				asgn.value = xConcat.items[0]
+			default:
+				asgn.value = xConcat
+			}
 		}
 	} else {
 		asgn.value = ctx.parseMakeString(a, a.Value)
-		if xBad, ok := asgn.value.(*badExpr); ok {
-			return []starlarkNode{&exprNode{expr: xBad}}
-		}
 	}
 
 	if asgn.lhs.valueType() == starlarkTypeString &&
@@ -585,8 +601,6 @@
 		asgn.value = &toStringExpr{expr: asgn.value}
 	}
 
-	asgn.previous = ctx.lastAssignment(lhs)
-	ctx.setLastAssignment(lhs, asgn)
 	switch a.Type {
 	case "=", ":=":
 		asgn.flavor = asgnSet
@@ -808,35 +822,40 @@
 	//       rblf.inherit(handle, _e[0], _e[1])
 	//
 	var matchingPaths []string
-	varPath, ok := pathExpr.(*interpolateExpr)
-	if !ok {
+	var needsWarning = false
+	if interpolate, ok := pathExpr.(*interpolateExpr); ok {
+		pathPattern := []string{interpolate.chunks[0]}
+		for _, chunk := range interpolate.chunks[1:] {
+			if chunk != "" {
+				pathPattern = append(pathPattern, chunk)
+			}
+		}
+		if pathPattern[0] == "" && len(ctx.includeTops) > 0 {
+			// If pattern starts from the top. restrict it to the directories where
+			// we know inherit-product uses dynamically calculated path.
+			for _, p := range ctx.includeTops {
+				pathPattern[0] = p
+				matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
+			}
+		} else {
+			matchingPaths = ctx.findMatchingPaths(pathPattern)
+		}
+		needsWarning = pathPattern[0] == "" && len(ctx.includeTops) == 0
+	} else if len(ctx.includeTops) > 0 {
+		for _, p := range ctx.includeTops {
+			matchingPaths = append(matchingPaths, ctx.findMatchingPaths([]string{p, ""})...)
+		}
+	} else {
 		return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
 	}
 
-	pathPattern := []string{varPath.chunks[0]}
-	for _, chunk := range varPath.chunks[1:] {
-		if chunk != "" {
-			pathPattern = append(pathPattern, chunk)
-		}
-	}
-	if pathPattern[0] == "" && len(ctx.includeTops) > 0 {
-		// If pattern starts from the top. restrict it to the directories where
-		// we know inherit-product uses dynamically calculated path.
-		for _, p := range ctx.includeTops {
-			pathPattern[0] = p
-			matchingPaths = append(matchingPaths, ctx.findMatchingPaths(pathPattern)...)
-		}
-	} else {
-		matchingPaths = ctx.findMatchingPaths(pathPattern)
-	}
 	// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
 	const maxMatchingFiles = 150
 	if len(matchingPaths) > maxMatchingFiles {
 		return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
 	}
 
-	needsWarning := pathPattern[0] == "" && len(ctx.includeTops) == 0
-	res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
+	res := inheritedDynamicModule{pathExpr, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
 	for _, p := range matchingPaths {
 		// A product configuration files discovered dynamically may attempt to inherit
 		// from another one which does not exist in this source tree. Prevent load errors
@@ -886,8 +905,9 @@
 	})
 }
 
-func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) []starlarkNode {
-	return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
+func (ctx *parseContext) handleInclude(v *mkparser.Directive) []starlarkNode {
+	loadAlways := v.Name[0] != '-'
+	return ctx.handleSubConfig(v, ctx.parseMakeString(v, v.Args), loadAlways, func(im inheritedModule) starlarkNode {
 		return &includeNode{im, loadAlways}
 	})
 }
@@ -952,11 +972,8 @@
 func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
 	block := &switchCase{gate: ctx.parseCondition(check)}
 	defer func() {
-		ctx.popVarAssignments()
 		ctx.ifNestLevel--
-
 	}()
-	ctx.pushVarAssignments()
 	ctx.ifNestLevel++
 
 	for ctx.hasNodes() {
@@ -980,7 +997,7 @@
 		if !check.Args.Const() {
 			return ctx.newBadNode(check, "ifdef variable ref too complex: %s", check.Args.Dump())
 		}
-		v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]), false)
+		v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]))
 		if strings.HasSuffix(check.Name, "ndef") {
 			v = &notExpr{v}
 		}
@@ -1068,6 +1085,18 @@
 				return otherOperand
 			}
 		}
+		if otherOperand.typ() == starlarkTypeList {
+			fields := strings.Fields(stringOperand)
+			elements := make([]starlarkExpr, len(fields))
+			for i, s := range fields {
+				elements[i] = &stringLiteralExpr{literal: s}
+			}
+			return &eqExpr{
+				left:  otherOperand,
+				right: &listExpr{elements},
+				isEq:  isEq,
+			}
+		}
 		if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
 			return &eqExpr{
 				left:  otherOperand,
@@ -1113,8 +1142,6 @@
 	switch call.name {
 	case baseName + ".filter":
 		return ctx.parseCompareFilterFuncResult(directive, call, value, isEq)
-	case baseName + ".expand_wildcard":
-		return ctx.parseCompareWildcardFuncResult(directive, call, value, !isEq), true
 	case baseName + ".findstring":
 		return ctx.parseCheckFindstringFuncResult(directive, call, value, !isEq), true
 	case baseName + ".strip":
@@ -1159,22 +1186,6 @@
 	}
 }
 
-func (ctx *parseContext) parseCompareWildcardFuncResult(directive *mkparser.Directive,
-	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
-	if !isEmptyString(xValue) {
-		return ctx.newBadExpr(directive, "wildcard result can be compared only to empty: %s", xValue)
-	}
-	callFunc := baseName + ".file_wildcard_exists"
-	if s, ok := xCall.args[0].(*stringLiteralExpr); ok && !strings.ContainsAny(s.literal, "*?{[") {
-		callFunc = baseName + ".file_exists"
-	}
-	var cc starlarkExpr = &callExpr{name: callFunc, args: xCall.args, returnType: starlarkTypeBool}
-	if !negate {
-		cc = &notExpr{cc}
-	}
-	return cc
-}
-
 func (ctx *parseContext) parseCheckFindstringFuncResult(directive *mkparser.Directive,
 	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
 	if isEmptyString(xValue) {
@@ -1241,7 +1252,7 @@
 		}
 		name = words[0].Dump()
 		if len(words) < 2 {
-			args = &mkparser.MakeString{}
+			args = mkparser.SimpleMakeString("", words[0].Pos())
 		} else {
 			args = words[1]
 		}
@@ -1259,7 +1270,34 @@
 	// Handle only the case where the first (or only) word is constant
 	words := ref.SplitN(" ", 2)
 	if !words[0].Const() {
-		return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		if len(words) == 1 {
+			expr := ctx.parseMakeString(node, ref)
+			return &callExpr{
+				object: &identifierExpr{"cfg"},
+				name:   "get",
+				args: []starlarkExpr{
+					expr,
+					&callExpr{
+						object: &identifierExpr{"g"},
+						name:   "get",
+						args: []starlarkExpr{
+							expr,
+							&stringLiteralExpr{literal: ""},
+						},
+						returnType: starlarkTypeUnknown,
+					},
+				},
+				returnType: starlarkTypeUnknown,
+			}
+		} else {
+			return ctx.newBadExpr(node, "reference is too complex: %s", refDump)
+		}
+	}
+
+	if name, _, ok := ctx.maybeParseFunctionCall(node, ref); ok {
+		if _, unsupported := unsupportedFunctions[name]; unsupported {
+			return ctx.newBadExpr(node, "%s is not supported", refDump)
+		}
 	}
 
 	// If it is a single word, it can be a simple variable
@@ -1293,12 +1331,12 @@
 				args: []starlarkExpr{
 					&stringLiteralExpr{literal: substParts[0]},
 					&stringLiteralExpr{literal: substParts[1]},
-					NewVariableRefExpr(v, ctx.lastAssignment(v) != nil),
+					NewVariableRefExpr(v),
 				},
 			}
 		}
 		if v := ctx.addVariable(refDump); v != nil {
-			return NewVariableRefExpr(v, ctx.lastAssignment(v) != nil)
+			return NewVariableRefExpr(v)
 		}
 		return ctx.newBadExpr(node, "unknown variable %s", refDump)
 	}
@@ -1309,9 +1347,8 @@
 		} else {
 			return ctx.newBadExpr(node, "cannot handle invoking %s", name)
 		}
-	} else {
-		return ctx.newBadExpr(node, "cannot handle %s", refDump)
 	}
+	return ctx.newBadExpr(node, "cannot handle %s", refDump)
 }
 
 type simpleCallParser struct {
@@ -1394,7 +1431,7 @@
 		return ctx.newBadExpr(node, "is-product-in-list requires an argument")
 	}
 	return &inExpr{
-		expr:  &variableRefExpr{ctx.addVariable("TARGET_PRODUCT"), true},
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT")),
 		list:  maybeConvertToStringList(ctx.parseMakeString(node, args)),
 		isNot: false,
 	}
@@ -1407,8 +1444,8 @@
 		return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
 	}
 	return &inExpr{
-		expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-		list:  &variableRefExpr{ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS"), true},
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
+		list:  NewVariableRefExpr(ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS")),
 		isNot: false,
 	}
 }
@@ -1420,8 +1457,8 @@
 		return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
 	}
 	return &inExpr{
-		expr:  &variableRefExpr{ctx.addVariable("TARGET_BOARD_PLATFORM"), false},
-		list:  &variableRefExpr{ctx.addVariable("QCOM_BOARD_PLATFORMS"), true},
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
+		list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS")),
 		isNot: false,
 	}
 }
@@ -1558,11 +1595,21 @@
 		}
 	}
 
-	return &foreachExpr{
+	var result starlarkExpr = &foreachExpr{
 		varName: loopVarName,
 		list:    list,
 		action:  action,
 	}
+
+	if action.typ() == starlarkTypeList {
+		result = &callExpr{
+			name:       baseName + ".flatten_2d_list",
+			args:       []starlarkExpr{result},
+			returnType: starlarkTypeList,
+		}
+	}
+
+	return result
 }
 
 func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
@@ -1587,6 +1634,16 @@
 		for _, n := range a.actions {
 			transformNode(n, transformer)
 		}
+	case *inheritNode:
+		if b, ok := a.module.(inheritedDynamicModule); ok {
+			b.path = b.path.transform(transformer)
+			a.module = b
+		}
+	case *includeNode:
+		if b, ok := a.module.(inheritedDynamicModule); ok {
+			b.path = b.path.transform(transformer)
+			a.module = b
+		}
 	}
 }
 
@@ -1637,9 +1694,11 @@
 	if len(words) != 2 {
 		return ctx.newBadExpr(node, "word function should have 2 arguments")
 	}
-	var index uint64 = 0
+	var index = 0
 	if words[0].Const() {
-		index, _ = strconv.ParseUint(strings.TrimSpace(words[0].Strings[0]), 10, 64)
+		if i, err := strconv.Atoi(strings.TrimSpace(words[0].Strings[0])); err == nil {
+			index = i
+		}
 	}
 	if index < 1 {
 		return ctx.newBadExpr(node, "word index should be constant positive integer")
@@ -1647,35 +1706,40 @@
 	words[1].TrimLeftSpaces()
 	words[1].TrimRightSpaces()
 	array := ctx.parseMakeString(node, words[1])
-	if xBad, ok := array.(*badExpr); ok {
-		return xBad
-	}
-	if array.typ() != starlarkTypeList {
-		array = &callExpr{object: array, name: "split", returnType: starlarkTypeList}
-	}
-	return &indexExpr{array, &intLiteralExpr{int(index - 1)}}
-}
-
-type firstOrLastwordCallParser struct {
-	isLastWord bool
-}
-
-func (p *firstOrLastwordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
-	arg := ctx.parseMakeString(node, args)
-	if bad, ok := arg.(*badExpr); ok {
+	if bad, ok := array.(*badExpr); ok {
 		return bad
 	}
-	index := &intLiteralExpr{0}
-	if p.isLastWord {
-		if v, ok := arg.(*variableRefExpr); ok && v.ref.name() == "MAKEFILE_LIST" {
-			return &stringLiteralExpr{ctx.script.mkFile}
+	if array.typ() != starlarkTypeList {
+		array = &callExpr{
+			name:       baseName + ".words",
+			args:       []starlarkExpr{array},
+			returnType: starlarkTypeList,
 		}
-		index.literal = -1
 	}
-	if arg.typ() == starlarkTypeList {
-		return &indexExpr{arg, index}
+	return &indexExpr{array, &intLiteralExpr{index - 1}}
+}
+
+type wordsCallParser struct{}
+
+func (p *wordsCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	args.TrimLeftSpaces()
+	args.TrimRightSpaces()
+	array := ctx.parseMakeString(node, args)
+	if bad, ok := array.(*badExpr); ok {
+		return bad
 	}
-	return &indexExpr{&callExpr{object: arg, name: "split", returnType: starlarkTypeList}, index}
+	if array.typ() != starlarkTypeList {
+		array = &callExpr{
+			name:       baseName + ".words",
+			args:       []starlarkExpr{array},
+			returnType: starlarkTypeList,
+		}
+	}
+	return &callExpr{
+		name:       "len",
+		args:       []starlarkExpr{array},
+		returnType: starlarkTypeInt,
+	}
 }
 
 func parseIntegerArguments(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString, expectedArgs int) ([]starlarkExpr, error) {
@@ -1759,10 +1823,23 @@
 			}
 		case *mkparser.Comment:
 			return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
+		case *mkparser.Directive:
+			if n.Name == "include" || n.Name == "-include" {
+				return ctx.handleInclude(n)
+			}
+		case *mkparser.Variable:
+			// Technically inherit-product(-if-exists) don't need to be put inside
+			// an eval, but some makefiles do it, presumably because they copy+pasted
+			// from a $(eval include ...)
+			if name, _, ok := ctx.maybeParseFunctionCall(n, n.Name); ok {
+				if name == "inherit-product" || name == "inherit-product-if-exists" {
+					return ctx.handleVariable(n)
+				}
+			}
 		}
 	}
 
-	return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments and comments are supported")}
+	return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
 }
 
 func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
@@ -1822,7 +1899,7 @@
 				result = []starlarkNode{res}
 			}
 		case "include", "-include":
-			result = ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+			result = ctx.handleInclude(x)
 		case "ifeq", "ifneq", "ifdef", "ifndef":
 			result = []starlarkNode{ctx.handleIfBlock(x)}
 		default:
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 96132cc..a09764c 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -117,8 +117,8 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
-  rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
+  cfg["PRODUCT_NAME"] = rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
+  cfg["PRODUCT_NAME"] = rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
 `,
 	},
 	{
@@ -568,14 +568,18 @@
 endif
 ifneq (,$(wildcard foo*.mk))
 endif
+ifeq (foo1.mk foo2.mk barxyz.mk,$(wildcard foo*.mk bar*.mk))
+endif
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if not rblf.file_exists("foo.mk"):
+  if not rblf.expand_wildcard("foo.mk"):
     pass
-  if rblf.file_wildcard_exists("foo*.mk"):
+  if rblf.expand_wildcard("foo*.mk"):
+    pass
+  if rblf.expand_wildcard("foo*.mk bar*.mk") == ["foo1.mk", "foo2.mk", "barxyz.mk"]:
     pass
 `,
 	},
@@ -642,7 +646,7 @@
     pass
   elif not rblf.board_platform_is(g, "copper"):
     pass
-  elif g.get("TARGET_BOARD_PLATFORM", "") not in g["QCOM_BOARD_PLATFORMS"]:
+  elif g.get("TARGET_BOARD_PLATFORM", "") not in g.get("QCOM_BOARD_PLATFORMS", ""):
     pass
   elif g["TARGET_PRODUCT"] in g.get("PLATFORM_LIST", []):
     pass
@@ -665,7 +669,7 @@
     pass
   elif not rblf.board_platform_is(g, "copper"):
     pass
-  elif g.get("TARGET_BOARD_PLATFORM", "") in g["QCOM_BOARD_PLATFORMS"]:
+  elif g.get("TARGET_BOARD_PLATFORM", "") in g.get("QCOM_BOARD_PLATFORMS", ""):
     pass
 `,
 	},
@@ -773,8 +777,8 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.enforce_product_packages_exist("")
-  rblf.enforce_product_packages_exist("foo")
+  rblf.enforce_product_packages_exist(handle, "")
+  rblf.enforce_product_packages_exist(handle, "foo")
   rblf.require_artifacts_in_path(handle, "foo", "bar")
   rblf.require_artifacts_in_path_relaxed(handle, "foo", "bar")
   rblf.mkdist_for_goals(g, "goal", "from:to")
@@ -808,6 +812,10 @@
 PRODUCT_COPY_FILES := $(addprefix pfx-,a b c)
 PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
 PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
+ifeq (1,$(words $(SOME_UNKNOWN_VARIABLE)))
+endif
+ifeq ($(words $(SOME_OTHER_VARIABLE)),$(SOME_INT_VARIABLE))
+endif
 $(info $(patsubst %.pub,$(PRODUCT_NAME)%,$(PRODUCT_ADB_KEYS)))
 $(info $$(dir foo/bar): $(dir foo/bar))
 $(info $(firstword $(PRODUCT_COPY_FILES)))
@@ -830,14 +838,18 @@
   cfg = rblf.cfg(handle)
   cfg["PRODUCT_COPY_FILES"] = rblf.addprefix("pfx-", "a b c")
   cfg["PRODUCT_COPY_FILES"] = rblf.addsuffix(".sff", "a b c")
-  cfg["PRODUCT_NAME"] = ((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " ")).split()[0]
+  cfg["PRODUCT_NAME"] = rblf.words((g.get("TARGET_BOARD_PLATFORM", "")).replace(".", " "))[0]
+  if len(rblf.words(g.get("SOME_UNKNOWN_VARIABLE", ""))) == 1:
+    pass
+  if ("%d" % (len(rblf.words(g.get("SOME_OTHER_VARIABLE", ""))))) == g.get("SOME_INT_VARIABLE", ""):
+    pass
   rblf.mkinfo("product.mk", rblf.mkpatsubst("%.pub", "%s%%" % cfg["PRODUCT_NAME"], g.get("PRODUCT_ADB_KEYS", "")))
   rblf.mkinfo("product.mk", "$(dir foo/bar): %s" % rblf.dir("foo/bar"))
-  rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][0])
-  rblf.mkinfo("product.mk", cfg["PRODUCT_COPY_FILES"][-1])
-  rblf.mkinfo("product.mk", rblf.dir("product.mk"))
-  rblf.mkinfo("product.mk", rblf.dir(cfg["PRODUCT_COPY_FILES"][-1]))
-  rblf.mkinfo("product.mk", rblf.dir((_foobar).split()[-1]))
+  rblf.mkinfo("product.mk", rblf.first_word(cfg["PRODUCT_COPY_FILES"]))
+  rblf.mkinfo("product.mk", rblf.last_word(cfg["PRODUCT_COPY_FILES"]))
+  rblf.mkinfo("product.mk", rblf.dir(rblf.last_word("product.mk")))
+  rblf.mkinfo("product.mk", rblf.dir(rblf.last_word(cfg["PRODUCT_COPY_FILES"])))
+  rblf.mkinfo("product.mk", rblf.dir(rblf.last_word(_foobar)))
   rblf.mkinfo("product.mk", rblf.abspath("foo/bar"))
   rblf.mkinfo("product.mk", rblf.notdir("foo/bar"))
   rblf.soong_config_namespace(g, "snsconfig")
@@ -975,7 +987,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")
-  rblf.mk2rbc_error("product.mk:7", "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config")
+  _x = 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",
@@ -1142,6 +1154,11 @@
 MY_PATH:=foo
 #RBC# include_top vendor/foo1
 $(call inherit-product,$(MY_PATH)/cfg.mk)
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_OTHER_PATH))
+#RBC# include_top vendor/foo1
+$(foreach f,$(MY_MAKEFILES), \
+	$(call inherit-product,$(f)))
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
@@ -1156,6 +1173,21 @@
   if not _varmod_init:
     rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
   rblf.inherit(handle, _varmod, _varmod_init)
+  _entry = {
+    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+  }.get(g.get("MY_OTHER_PATH", ""))
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % (g.get("MY_OTHER_PATH", "")))
+  rblf.inherit(handle, _varmod, _varmod_init)
+  for f in rblf.words(g.get("MY_MAKEFILES", "")):
+    _entry = {
+      "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+    }.get(f)
+    (_varmod, _varmod_init) = _entry if _entry else (None, None)
+    if not _varmod_init:
+      rblf.mkerror("product.mk", "Cannot find %s" % (f))
+    rblf.inherit(handle, _varmod, _varmod_init)
 `,
 	},
 	{
@@ -1242,13 +1274,15 @@
 		desc:   "Ignore make rules",
 		mkname: "product.mk",
 		in: `
+foo: PRIVATE_VARIABLE = some_tool $< $@
 foo: foo.c
 	gcc -o $@ $*`,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  rblf.mk2rbc_error("product.mk:2", "unsupported line rule:       foo: foo.c\n#gcc -o $@ $*")
+  rblf.mk2rbc_error("product.mk:2", "Only simple variables are handled")
+  rblf.mk2rbc_error("product.mk:3", "unsupported line rule:       foo: foo.c\n#gcc -o $@ $*")
 `,
 	},
 	{
@@ -1269,6 +1303,7 @@
 		in: `
 ifeq (,$(call foobar))
 endif
+my_sources := $(local-generated-sources-dir)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
@@ -1276,6 +1311,7 @@
   cfg = rblf.cfg(handle)
   if rblf.mk2rbc_error("build/product.mk:2", "cannot handle invoking foobar"):
     pass
+  _my_sources = rblf.mk2rbc_error("build/product.mk:4", "local-generated-sources-dir is not supported")
 `,
 	},
 	{
@@ -1327,6 +1363,8 @@
 BOOT_KERNEL_MODULES_LIST := foo.ko
 BOOT_KERNEL_MODULES_LIST += bar.ko
 BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+NESTED_LISTS := $(foreach m,$(SOME_VAR),$(BOOT_KERNEL_MODULES_LIST))
+NESTED_LISTS_2 := $(foreach x,$(SOME_VAR),$(foreach y,$(x),prefix$(y)))
 
 FOREACH_WITH_IF := $(foreach module,\
   $(BOOT_KERNEL_MODULES_LIST),\
@@ -1346,6 +1384,8 @@
   g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
   g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
   g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+  g["NESTED_LISTS"] = rblf.flatten_2d_list([g["BOOT_KERNEL_MODULES_LIST"] for m in rblf.words(g.get("SOME_VAR", ""))])
+  g["NESTED_LISTS_2"] = rblf.flatten_2d_list([["prefix%s" % y for y in rblf.words(x)] for x in rblf.words(g.get("SOME_VAR", ""))])
   g["FOREACH_WITH_IF"] = [("" if rblf.filter(module, "foo.ko") else rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)) for module in g["BOOT_KERNEL_MODULES_LIST"]]
   # Same as above, but not assigning it to a variable allows it to be converted to statements
   for module in g["BOOT_KERNEL_MODULES_LIST"]:
@@ -1509,24 +1549,75 @@
 $(eval MY_VAR := foo)
 $(eval # This is a test of eval functions)
 $(eval $(TOO_COMPLICATED) := bar)
+$(eval include foo/font.mk)
+$(eval $(call inherit-product,vendor/foo1/cfg.mk))
+
 $(foreach x,$(MY_LIST_VAR), \
   $(eval PRODUCT_COPY_FILES += foo/bar/$(x):$(TARGET_COPY_OUT_VENDOR)/etc/$(x)) \
-  $(if $(MY_OTHER_VAR),$(eval PRODUCT_COPY_FILES += $(MY_OTHER_VAR):foo/bar/$(x))) \
-)
+  $(if $(MY_OTHER_VAR),$(eval PRODUCT_COPY_FILES += $(MY_OTHER_VAR):foo/bar/$(x))))
 
+$(foreach x,$(MY_LIST_VAR), \
+  $(eval include foo/$(x).mk))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//foo:font.star", _font_init = "init")
+load("//vendor/foo1:cfg.star", _cfg_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_VAR"] = "foo"
+  # This is a test of eval functions
+  rblf.mk2rbc_error("product.mk:5", "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")
+  _font_init(g, handle)
+  rblf.inherit(handle, "vendor/foo1/cfg", _cfg_init)
+  for x in rblf.words(g.get("MY_LIST_VAR", "")):
+    rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+    cfg["PRODUCT_COPY_FILES"] += ("foo/bar/%s:%s/etc/%s" % (x, g.get("TARGET_COPY_OUT_VENDOR", ""), x)).split()
+    if g.get("MY_OTHER_VAR", ""):
+      cfg["PRODUCT_COPY_FILES"] += ("%s:foo/bar/%s" % (g.get("MY_OTHER_VAR", ""), x)).split()
+  for x in rblf.words(g.get("MY_LIST_VAR", "")):
+    _entry = {
+      "foo/font.mk": ("foo/font", _font_init),
+    }.get("foo/%s.mk" % x)
+    (_varmod, _varmod_init) = _entry if _entry else (None, None)
+    if not _varmod_init:
+      rblf.mkerror("product.mk", "Cannot find %s" % ("foo/%s.mk" % x))
+    _varmod_init(g, handle)
+`,
+	},
+	{
+		desc:   ".KATI_READONLY",
+		mkname: "product.mk",
+		in: `
+MY_VAR := foo
+.KATI_READONLY := MY_VAR
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
   g["MY_VAR"] = "foo"
-  # This is a test of eval functions
-  rblf.mk2rbc_error("product.mk:5", "Eval expression too complex; only assignments and comments are supported")
-  for x in rblf.words(g.get("MY_LIST_VAR", "")):
-    rblf.setdefault(handle, "PRODUCT_COPY_FILES")
-    cfg["PRODUCT_COPY_FILES"] += ("foo/bar/%s:%s/etc/%s" % (x, g.get("TARGET_COPY_OUT_VENDOR", ""), x)).split()
-    if g.get("MY_OTHER_VAR", ""):
-      cfg["PRODUCT_COPY_FILES"] += ("%s:foo/bar/%s" % (g.get("MY_OTHER_VAR", ""), x)).split()
+`,
+	},
+	{
+		desc:   "Complicated variable references",
+		mkname: "product.mk",
+		in: `
+MY_VAR := foo
+MY_VAR_2 := MY_VAR
+MY_VAR_3 := $($(MY_VAR_2))
+MY_VAR_4 := $(foo bar)
+MY_VAR_5 := $($(MY_VAR_2) bar)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_VAR"] = "foo"
+  g["MY_VAR_2"] = "MY_VAR"
+  g["MY_VAR_3"] = (cfg).get(g["MY_VAR_2"], (g).get(g["MY_VAR_2"], ""))
+  g["MY_VAR_4"] = rblf.mk2rbc_error("product.mk:5", "cannot handle invoking foo")
+  g["MY_VAR_5"] = rblf.mk2rbc_error("product.mk:6", "reference is too complex: $(MY_VAR_2) bar")
 `,
 	},
 }
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index c0c4c98..a01abd8 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -83,7 +83,7 @@
 }
 
 type inheritedDynamicModule struct {
-	path             interpolateExpr
+	path             starlarkExpr
 	candidateModules []*moduleInfo
 	loadAlways       bool
 	location         ErrorLocation
@@ -120,7 +120,7 @@
 }
 
 func (i inheritedDynamicModule) pathExpr() starlarkExpr {
-	return &i.path
+	return i.path
 }
 
 func (i inheritedDynamicModule) needsLoadCheck() bool {
@@ -196,7 +196,6 @@
 	flavor   assignmentFlavor
 	location ErrorLocation
 	isTraced bool
-	previous *assignmentNode
 }
 
 func (asgn *assignmentNode) emit(gctx *generationContext) {
@@ -209,7 +208,7 @@
 		gctx.newLine()
 		gctx.tracedCount++
 		gctx.writef(`print("%s.%d: %s := ", `, gctx.starScript.mkFile, gctx.tracedCount, asgn.lhs.name())
-		asgn.lhs.emitGet(gctx, true)
+		asgn.lhs.emitGet(gctx)
 		gctx.writef(")")
 	}
 }
@@ -271,6 +270,7 @@
 func (cb *switchCase) emit(gctx *generationContext) {
 	cb.gate.emit(gctx)
 	gctx.indentLevel++
+	gctx.pushVariableAssignments()
 	hasStatements := false
 	for _, node := range cb.nodes {
 		if _, ok := node.(*commentNode); !ok {
@@ -282,6 +282,7 @@
 		gctx.emitPass()
 	}
 	gctx.indentLevel--
+	gctx.popVariableAssignments()
 }
 
 // A single complete if ... elseif ... else ... endif sequences
@@ -302,6 +303,7 @@
 }
 
 func (f *foreachNode) emit(gctx *generationContext) {
+	gctx.pushVariableAssignments()
 	gctx.newLine()
 	gctx.writef("for %s in ", f.varName)
 	f.list.emit(gctx)
@@ -318,4 +320,5 @@
 		gctx.emitPass()
 	}
 	gctx.indentLevel--
+	gctx.popVariableAssignments()
 }
diff --git a/mk2rbc/variable.go b/mk2rbc/variable.go
index be1b174..0a26ed8 100644
--- a/mk2rbc/variable.go
+++ b/mk2rbc/variable.go
@@ -21,9 +21,8 @@
 
 type variable interface {
 	name() string
-	emitGet(gctx *generationContext, isDefined bool)
+	emitGet(gctx *generationContext)
 	emitSet(gctx *generationContext, asgn *assignmentNode)
-	emitDefined(gctx *generationContext)
 	valueType() starlarkType
 	setValueType(t starlarkType)
 	defaultValueString() string
@@ -74,13 +73,11 @@
 
 func (pcv productConfigVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
 	emitAssignment := func() {
-		pcv.emitGet(gctx, true)
-		gctx.write(" = ")
+		gctx.writef("cfg[%q] = ", pcv.nam)
 		asgn.value.emitListVarCopy(gctx)
 	}
 	emitAppend := func() {
-		pcv.emitGet(gctx, true)
-		gctx.write(" += ")
+		gctx.writef("cfg[%q] += ", pcv.nam)
 		value := asgn.value
 		if pcv.valueType() == starlarkTypeString {
 			gctx.writef(`" " + `)
@@ -98,7 +95,7 @@
 	}
 
 	// If we are not sure variable has been assigned before, emit setdefault
-	needsSetDefault := asgn.previous == nil && !pcv.isPreset() && asgn.isSelfReferential()
+	needsSetDefault := !gctx.hasBeenAssigned(&pcv) && !pcv.isPreset() && asgn.isSelfReferential()
 
 	switch asgn.flavor {
 	case asgnSet:
@@ -121,34 +118,30 @@
 		emitAssignment()
 		gctx.indentLevel--
 	}
+
+	gctx.setHasBeenAssigned(&pcv)
 }
 
-func (pcv productConfigVariable) emitGet(gctx *generationContext, isDefined bool) {
-	if isDefined || pcv.isPreset() {
+func (pcv productConfigVariable) emitGet(gctx *generationContext) {
+	if gctx.hasBeenAssigned(&pcv) || pcv.isPreset() {
 		gctx.writef("cfg[%q]", pcv.nam)
 	} else {
 		gctx.writef("cfg.get(%q, %s)", pcv.nam, pcv.defaultValueString())
 	}
 }
 
-func (pcv productConfigVariable) emitDefined(gctx *generationContext) {
-	gctx.writef("cfg.get(%q) != None", pcv.name())
-}
-
 type otherGlobalVariable struct {
 	baseVariable
 }
 
 func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
 	emitAssignment := func() {
-		scv.emitGet(gctx, true)
-		gctx.write(" = ")
+		gctx.writef("g[%q] = ", scv.nam)
 		asgn.value.emitListVarCopy(gctx)
 	}
 
 	emitAppend := func() {
-		scv.emitGet(gctx, true)
-		gctx.write(" += ")
+		gctx.writef("g[%q] += ", scv.nam)
 		value := asgn.value
 		if scv.valueType() == starlarkTypeString {
 			gctx.writef(`" " + `)
@@ -158,7 +151,7 @@
 	}
 
 	// If we are not sure variable has been assigned before, emit setdefault
-	needsSetDefault := asgn.previous == nil && !scv.isPreset() && asgn.isSelfReferential()
+	needsSetDefault := !gctx.hasBeenAssigned(&scv) && !scv.isPreset() && asgn.isSelfReferential()
 
 	switch asgn.flavor {
 	case asgnSet:
@@ -184,28 +177,22 @@
 		emitAssignment()
 		gctx.indentLevel--
 	}
+
+	gctx.setHasBeenAssigned(&scv)
 }
 
-func (scv otherGlobalVariable) emitGet(gctx *generationContext, isDefined bool) {
-	if isDefined || scv.isPreset() {
+func (scv otherGlobalVariable) emitGet(gctx *generationContext) {
+	if gctx.hasBeenAssigned(&scv) || scv.isPreset() {
 		gctx.writef("g[%q]", scv.nam)
 	} else {
 		gctx.writef("g.get(%q, %s)", scv.nam, scv.defaultValueString())
 	}
 }
 
-func (scv otherGlobalVariable) emitDefined(gctx *generationContext) {
-	gctx.writef("g.get(%q) != None", scv.name())
-}
-
 type localVariable struct {
 	baseVariable
 }
 
-func (lv localVariable) emitDefined(gctx *generationContext) {
-	gctx.writef(lv.String())
-}
-
 func (lv localVariable) String() string {
 	return "_" + lv.nam
 }
@@ -216,8 +203,7 @@
 		gctx.writef("%s = ", lv)
 		asgn.value.emitListVarCopy(gctx)
 	case asgnAppend:
-		lv.emitGet(gctx, false)
-		gctx.write(" += ")
+		gctx.writef("%s += ", lv)
 		value := asgn.value
 		if lv.valueType() == starlarkTypeString {
 			gctx.writef(`" " + `)
@@ -227,7 +213,7 @@
 	}
 }
 
-func (lv localVariable) emitGet(gctx *generationContext, _ bool) {
+func (lv localVariable) emitGet(gctx *generationContext) {
 	gctx.writef("%s", lv)
 }
 
@@ -236,7 +222,7 @@
 	value starlarkExpr
 }
 
-func (pv predefinedVariable) emitGet(gctx *generationContext, _ bool) {
+func (pv predefinedVariable) emitGet(gctx *generationContext) {
 	pv.value.emit(gctx)
 }
 
@@ -257,10 +243,6 @@
 	panic(fmt.Errorf("cannot set predefined variable %s to %q", pv.name(), asgn.mkValue.Dump()))
 }
 
-func (pv predefinedVariable) emitDefined(gctx *generationContext) {
-	gctx.write("True")
-}
-
 var localProductConfigVariables = map[string]string{
 	"LOCAL_AUDIO_PRODUCT_PACKAGE":         "PRODUCT_PACKAGES",
 	"LOCAL_AUDIO_PRODUCT_COPY_FILES":      "PRODUCT_COPY_FILES",
diff --git a/multitree/Android.bp b/multitree/Android.bp
new file mode 100644
index 0000000..9b16d20
--- /dev/null
+++ b/multitree/Android.bp
@@ -0,0 +1,19 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-multitree",
+    pkgPath: "android/soong/multitree",
+    deps: [
+        "blueprint",
+        "soong-android",
+    ],
+    srcs: [
+        "api_surface.go",
+        "export.go",
+        "metadata.go",
+        "import.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/multitree/api_surface.go b/multitree/api_surface.go
new file mode 100644
index 0000000..f739a24
--- /dev/null
+++ b/multitree/api_surface.go
@@ -0,0 +1,119 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package multitree
+
+import (
+	"android/soong/android"
+	"fmt"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/multitree")
+)
+
+func init() {
+	RegisterApiSurfaceBuildComponents(android.InitRegistrationContext)
+}
+
+var PrepareForTestWithApiSurface = android.FixtureRegisterWithContext(RegisterApiSurfaceBuildComponents)
+
+func RegisterApiSurfaceBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("api_surface", ApiSurfaceFactory)
+}
+
+type ApiSurface struct {
+	android.ModuleBase
+	ExportableModuleBase
+	properties apiSurfaceProperties
+
+	allOutputs    android.Paths
+	taggedOutputs map[string]android.Paths
+}
+
+type apiSurfaceProperties struct {
+	Contributions []string
+}
+
+func ApiSurfaceFactory() android.Module {
+	module := &ApiSurface{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidModule(module)
+	InitExportableModule(module)
+	return module
+}
+
+func (surface *ApiSurface) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if surface.properties.Contributions != nil {
+		ctx.AddVariationDependencies(nil, nil, surface.properties.Contributions...)
+	}
+
+}
+func (surface *ApiSurface) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	contributionFiles := make(map[string]android.Paths)
+	var allOutputs android.Paths
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		if contribution, ok := child.(ApiContribution); ok {
+			copied := contribution.CopyFilesWithTag(ctx)
+			for tag, files := range copied {
+				contributionFiles[child.Name()+"#"+tag] = files
+			}
+			for _, paths := range copied {
+				allOutputs = append(allOutputs, paths...)
+			}
+			return false // no transitive dependencies
+		}
+		return false
+	})
+
+	// phony target
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   blueprint.Phony,
+		Output: android.PathForPhony(ctx, ctx.ModuleName()),
+		Inputs: allOutputs,
+	})
+
+	surface.allOutputs = allOutputs
+	surface.taggedOutputs = contributionFiles
+}
+
+func (surface *ApiSurface) OutputFiles(tag string) (android.Paths, error) {
+	if tag != "" {
+		return nil, fmt.Errorf("unknown tag: %q", tag)
+	}
+	return surface.allOutputs, nil
+}
+
+func (surface *ApiSurface) TaggedOutputs() map[string]android.Paths {
+	return surface.taggedOutputs
+}
+
+func (surface *ApiSurface) Exportable() bool {
+	return true
+}
+
+var _ android.OutputFileProducer = (*ApiSurface)(nil)
+var _ Exportable = (*ApiSurface)(nil)
+
+type ApiContribution interface {
+	// copy files necessaryt to construct an API surface
+	// For C, it will be map.txt and .h files
+	// For Java, it will be api.txt
+	CopyFilesWithTag(ctx android.ModuleContext) map[string]android.Paths // output paths
+
+	// Generate Android.bp in out/ to use the exported .txt files
+	// GenerateBuildFiles(ctx ModuleContext) Paths //output paths
+}
diff --git a/multitree/export.go b/multitree/export.go
new file mode 100644
index 0000000..aecade5
--- /dev/null
+++ b/multitree/export.go
@@ -0,0 +1,67 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package multitree
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type moduleExportProperty struct {
+	// True if the module is exported to the other components in a multi-tree.
+	// Any components in the multi-tree can import this module to use.
+	Export *bool
+}
+
+type ExportableModuleBase struct {
+	properties moduleExportProperty
+}
+
+type Exportable interface {
+	// Properties for the exporable module.
+	exportableModuleProps() *moduleExportProperty
+
+	// Check if this module can be exported.
+	// If this returns false, the module will not be exported regardless of the 'export' value.
+	Exportable() bool
+
+	// Returns 'true' if this module has 'export: true'
+	// This module will not be exported if it returns 'false' to 'Exportable()' interface even if
+	// it has 'export: true'.
+	IsExported() bool
+
+	// Map from tags to outputs.
+	// Each module can tag their outputs for convenience.
+	TaggedOutputs() map[string]android.Paths
+}
+
+type ExportableModule interface {
+	android.Module
+	android.OutputFileProducer
+	Exportable
+}
+
+func InitExportableModule(module ExportableModule) {
+	module.AddProperties(module.exportableModuleProps())
+}
+
+func (m *ExportableModuleBase) exportableModuleProps() *moduleExportProperty {
+	return &m.properties
+}
+
+func (m *ExportableModuleBase) IsExported() bool {
+	return proptools.Bool(m.properties.Export)
+}
diff --git a/multitree/import.go b/multitree/import.go
new file mode 100644
index 0000000..1e5c421
--- /dev/null
+++ b/multitree/import.go
@@ -0,0 +1,96 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package multitree
+
+import (
+	"android/soong/android"
+)
+
+var (
+	nameSuffix = ".imported"
+)
+
+type MultitreeImportedModuleInterface interface {
+	GetMultitreeImportedModuleName() string
+}
+
+func init() {
+	android.RegisterModuleType("imported_filegroup", importedFileGroupFactory)
+
+	android.PreArchMutators(RegisterMultitreePreArchMutators)
+}
+
+type importedFileGroupProperties struct {
+	// Imported modules from the other components in a multi-tree
+	Imported []string
+}
+
+type importedFileGroup struct {
+	android.ModuleBase
+
+	properties importedFileGroupProperties
+	srcs       android.Paths
+}
+
+func (ifg *importedFileGroup) Name() string {
+	return ifg.BaseModuleName() + nameSuffix
+}
+
+func importedFileGroupFactory() android.Module {
+	module := &importedFileGroup{}
+	module.AddProperties(&module.properties)
+
+	android.InitAndroidModule(module)
+	return module
+}
+
+var _ MultitreeImportedModuleInterface = (*importedFileGroup)(nil)
+
+func (ifg *importedFileGroup) GetMultitreeImportedModuleName() string {
+	// The base module name of the imported filegroup is used as the imported module name
+	return ifg.BaseModuleName()
+}
+
+var _ android.SourceFileProducer = (*importedFileGroup)(nil)
+
+func (ifg *importedFileGroup) Srcs() android.Paths {
+	return ifg.srcs
+}
+
+func (ifg *importedFileGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// srcs from this module must not be used. Adding a dot path to avoid the empty
+	// source failure. Still soong returns error when a module wants to build against
+	// this source, which is intended.
+	ifg.srcs = android.PathsForModuleSrc(ctx, []string{"."})
+}
+
+func RegisterMultitreePreArchMutators(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("multitree_imported_rename", MultitreeImportedRenameMutator).Parallel()
+}
+
+func MultitreeImportedRenameMutator(ctx android.BottomUpMutatorContext) {
+	if m, ok := ctx.Module().(MultitreeImportedModuleInterface); ok {
+		name := m.GetMultitreeImportedModuleName()
+		if !ctx.OtherModuleExists(name) {
+			// Provide an empty filegroup not to break the build while updating the metadata.
+			// In other cases, soong will report an error to guide users to run 'm update-meta'
+			// first.
+			if !ctx.Config().TargetMultitreeUpdateMeta() {
+				ctx.ModuleErrorf("\"%s\" filegroup must be imported.\nRun 'm update-meta' first to import the filegroup.", name)
+			}
+			ctx.Rename(name)
+		}
+	}
+}
diff --git a/multitree/metadata.go b/multitree/metadata.go
new file mode 100644
index 0000000..3fd7215
--- /dev/null
+++ b/multitree/metadata.go
@@ -0,0 +1,74 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package multitree
+
+import (
+	"android/soong/android"
+	"encoding/json"
+)
+
+func init() {
+	android.RegisterSingletonType("update-meta", UpdateMetaSingleton)
+}
+
+func UpdateMetaSingleton() android.Singleton {
+	return &updateMetaSingleton{}
+}
+
+type jsonImported struct {
+	FileGroups map[string][]string `json:",omitempty"`
+}
+
+type metadataJsonFlags struct {
+	Imported jsonImported        `json:",omitempty"`
+	Exported map[string][]string `json:",omitempty"`
+}
+
+type updateMetaSingleton struct {
+	importedModules       []string
+	generatedMetadataFile android.OutputPath
+}
+
+func (s *updateMetaSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	metadata := metadataJsonFlags{
+		Imported: jsonImported{
+			FileGroups: make(map[string][]string),
+		},
+		Exported: make(map[string][]string),
+	}
+	ctx.VisitAllModules(func(module android.Module) {
+		if ifg, ok := module.(*importedFileGroup); ok {
+			metadata.Imported.FileGroups[ifg.BaseModuleName()] = ifg.properties.Imported
+		}
+		if e, ok := module.(ExportableModule); ok {
+			if e.IsExported() && e.Exportable() {
+				for tag, files := range e.TaggedOutputs() {
+					// TODO(b/219846705): refactor this to a dictionary
+					metadata.Exported[e.Name()+":"+tag] = append(metadata.Exported[e.Name()+":"+tag], files.Strings()...)
+				}
+			}
+		}
+	})
+	jsonStr, err := json.Marshal(metadata)
+	if err != nil {
+		ctx.Errorf(err.Error())
+	}
+	s.generatedMetadataFile = android.PathForOutput(ctx, "multitree", "metadata.json")
+	android.WriteFileRule(ctx, s.generatedMetadataFile, string(jsonStr))
+}
+
+func (s *updateMetaSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("MULTITREE_METADATA", s.generatedMetadataFile.String())
+}
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index ae96e1f..d1cbd8f 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -36,7 +36,8 @@
 	mergeProvenanceMetaData = pctx.AndroidStaticRule("mergeProvenanceMetaData",
 		blueprint.RuleParams{
 			Command: `rm -rf $out $out.temp && ` +
-				`echo -e "# proto-file: build/soong/provenance/proto/provenance_metadata.proto\n# proto-message: ProvenanceMetaDataList" > $out && ` +
+				`echo "# proto-file: build/soong/provenance/proto/provenance_metadata.proto" > $out && ` +
+				`echo "# proto-message: ProvenanceMetaDataList" >> $out && ` +
 				`touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
 		})
 )
@@ -60,29 +61,32 @@
 }
 
 type provenanceInfoSingleton struct {
+	mergedMetaDataFile android.OutputPath
 }
 
-func (b *provenanceInfoSingleton) GenerateBuildActions(context android.SingletonContext) {
+func (p *provenanceInfoSingleton) GenerateBuildActions(context android.SingletonContext) {
 	allMetaDataFiles := make([]android.Path, 0)
 	context.VisitAllModulesIf(moduleFilter, func(module android.Module) {
 		if p, ok := module.(ProvenanceMetadata); ok {
 			allMetaDataFiles = append(allMetaDataFiles, p.ProvenanceMetaDataFile())
 		}
 	})
-	mergedMetaDataFile := android.PathForOutput(context, "provenance_metadata.textproto")
+	p.mergedMetaDataFile = android.PathForOutput(context, "provenance_metadata.textproto")
 	context.Build(pctx, android.BuildParams{
 		Rule:        mergeProvenanceMetaData,
 		Description: "merge provenance metadata",
 		Inputs:      allMetaDataFiles,
-		Output:      mergedMetaDataFile,
+		Output:      p.mergedMetaDataFile,
 	})
 
 	context.Build(pctx, android.BuildParams{
 		Rule:        blueprint.Phony,
 		Description: "phony rule of merge provenance metadata",
-		Inputs:      []android.Path{mergedMetaDataFile},
+		Inputs:      []android.Path{p.mergedMetaDataFile},
 		Output:      android.PathForPhony(context, "provenance_metadata"),
 	})
+
+	context.Phony("droidcore", android.PathForPhony(context, "provenance_metadata"))
 }
 
 func moduleFilter(module android.Module) bool {
@@ -110,3 +114,9 @@
 
 	return artifactMetaDataFile
 }
+
+func (p *provenanceInfoSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droidcore", p.mergedMetaDataFile)
+}
+
+var _ android.SingletonMakeVarsProvider = (*provenanceInfoSingleton)(nil)
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
index b33f911..f3f4d1f 100644
--- a/provenance/tools/gen_provenance_metadata.py
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -16,6 +16,7 @@
 
 import argparse
 import hashlib
+import os.path
 import sys
 
 import google.protobuf.text_format as text_format
@@ -51,6 +52,11 @@
     h.update(artifact_file.read())
   provenance_metadata.artifact_sha256 = h.hexdigest()
 
+  Log("Check if there is attestation for the artifact")
+  attestation_file_name = args.artifact_path + ".intoto.jsonl"
+  if os.path.isfile(attestation_file_name):
+    provenance_metadata.attestation_path = attestation_file_name
+
   text_proto = [
       "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
       "# proto-message: ProvenanceMetaData",
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
index 2fc04bf..1f69b8f 100644
--- a/provenance/tools/gen_provenance_metadata_test.py
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -100,6 +100,11 @@
     artifact_file = tempfile.mktemp()
     with open(artifact_file,"wt") as f:
       f.write(artifact_content)
+
+    attestation_file = artifact_file + ".intoto.jsonl"
+    with open(attestation_file, "wt") as af:
+      af.write("attestation file")
+
     metadata_file = tempfile.mktemp()
     cmd = ["gen_provenance_metadata"]
     cmd.extend(["--module_name", "a"])
@@ -117,9 +122,11 @@
       self.assertEqual(provenance_metadata.artifact_path, artifact_file)
       self.assertEqual(provenance_metadata.artifact_install_path, "b")
       self.assertEqual(provenance_metadata.artifact_sha256, sha256(artifact_content))
+      self.assertEqual(provenance_metadata.attestation_path, attestation_file)
 
     os.remove(artifact_file)
     os.remove(metadata_file)
+    os.remove(attestation_file)
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
\ No newline at end of file
diff --git a/rust/binary.go b/rust/binary.go
index 0dc320e..41110f9 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -128,11 +128,11 @@
 	return Bool(binary.baseCompiler.Properties.Prefer_rlib) || Bool(binary.Properties.Static_executable)
 }
 
-func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (binary *binaryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix()
 	srcPath, _ := srcPathFromModuleSrcs(ctx, binary.baseCompiler.Properties.Srcs)
 	outputFile := android.PathForModuleOut(ctx, fileName)
-	ret := outputFile
+	ret := buildOutput{outputFile: outputFile}
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
@@ -147,8 +147,7 @@
 	}
 	binary.baseCompiler.unstrippedOutputFile = outputFile
 
-	TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile)
-
+	ret.kytheFile = TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile).kytheFile
 	return ret
 }
 
diff --git a/rust/bindgen.go b/rust/bindgen.go
index c2b0512..b4626a0 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -30,7 +30,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r445002"
+	bindgenClangVersion = "clang-r450784d"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
diff --git a/rust/builder.go b/rust/builder.go
index 20ca5db..7dd9dd2 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -83,10 +83,37 @@
 			RspfileContent: "$in",
 		},
 		"outDir")
+
+	// Cross-referencing:
+	_ = pctx.SourcePathVariable("rustExtractor",
+		"prebuilts/build-tools/${config.HostPrebuiltTag}/bin/rust_extractor")
+	_ = pctx.VariableFunc("kytheCorpus",
+		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCorpusName() })
+	_ = pctx.VariableFunc("kytheCuEncoding",
+		func(ctx android.PackageVarContext) string { return ctx.Config().XrefCuEncoding() })
+	_            = pctx.SourcePathVariable("kytheVnames", "build/soong/vnames.json")
+	kytheExtract = pctx.AndroidStaticRule("kythe",
+		blueprint.RuleParams{
+			Command: `KYTHE_CORPUS=${kytheCorpus} ` +
+				`KYTHE_OUTPUT_FILE=$out ` +
+				`KYTHE_VNAMES=$kytheVnames ` +
+				`KYTHE_KZIP_ENCODING=${kytheCuEncoding} ` +
+				`KYTHE_CANONICALIZE_VNAME_PATHS=prefer-relative ` +
+				`$rustExtractor $envVars ` +
+				`$rustcCmd ` +
+				`-C linker=${config.RustLinker} ` +
+				`-C link-args="${crtBegin} ${config.RustLinkerArgs} ${linkFlags} ${crtEnd}" ` +
+				`$in ${libFlags} $rustcFlags`,
+			CommandDeps:    []string{"$rustExtractor", "$kytheVnames"},
+			Rspfile:        "${out}.rsp",
+			RspfileContent: "$in",
+		},
+		"rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars")
 )
 
 type buildOutput struct {
 	outputFile android.Path
+	kytheFile  android.Path
 }
 
 func init() {
@@ -324,6 +351,25 @@
 		},
 	})
 
+	if flags.EmitXrefs {
+		kytheFile := android.PathForModuleOut(ctx, outputFile.Base()+".kzip")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        kytheExtract,
+			Description: "Xref Rust extractor " + main.Rel(),
+			Output:      kytheFile,
+			Inputs:      inputs,
+			Implicits:   implicits,
+			Args: map[string]string{
+				"rustcFlags": strings.Join(rustcFlags, " "),
+				"linkFlags":  strings.Join(linkFlags, " "),
+				"libFlags":   strings.Join(libFlags, " "),
+				"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
+				"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
+				"envVars":    strings.Join(envVars, " "),
+			},
+		})
+		output.kytheFile = kytheFile
+	}
 	return output
 }
 
diff --git a/rust/compiler.go b/rust/compiler.go
index 19499fa..bcd58c8 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -304,6 +304,7 @@
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, ctx.toolchain().ToolchainRustFlags())
 	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, ctx.toolchain().ToolchainLinkFlags())
+	flags.EmitXrefs = ctx.Config().EmitXrefRules()
 
 	if ctx.Host() && !ctx.Windows() {
 		rpathPrefix := `\$$ORIGIN/`
@@ -324,7 +325,7 @@
 	return flags
 }
 
-func (compiler *baseCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (compiler *baseCompiler) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	panic(fmt.Errorf("baseCrater doesn't know how to crate things!"))
 }
 
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 802e1da..7468579 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -8,6 +8,7 @@
 	RustAllowedPaths = []string{
 		"device/google/cuttlefish",
 		"external/adhd",
+		"external/boringssl",
 		"external/crosvm",
 		"external/libchromeos-rs",
 		"external/minijail",
diff --git a/rust/config/global.go b/rust/config/global.go
index 2d5fa99..d11665c 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.59.0"
+	RustDefaultVersion = "1.60.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
@@ -50,6 +50,7 @@
 		"-C force-unwind-tables=yes",
 		// Use v0 mangling to distinguish from C++ symbols
 		"-C symbol-mangling-version=v0",
+		"--color always",
 	}
 
 	deviceGlobalRustFlags = []string{
diff --git a/rust/library.go b/rust/library.go
index 62eaefd..1286549 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -474,8 +474,9 @@
 	return flags
 }
 
-func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
-	var outputFile, ret android.ModuleOutPath
+func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
+	var outputFile android.ModuleOutPath
+	var ret buildOutput
 	var fileName string
 	srcPath := library.srcPath(ctx, deps)
 
@@ -487,19 +488,19 @@
 	if library.rlib() {
 		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
-		ret = outputFile
+		ret.outputFile = outputFile
 	} else if library.dylib() {
 		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
-		ret = outputFile
+		ret.outputFile = outputFile
 	} else if library.static() {
 		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
 		outputFile = android.PathForModuleOut(ctx, fileName)
-		ret = outputFile
+		ret.outputFile = outputFile
 	} else if library.shared() {
 		fileName = library.sharedLibFilename(ctx)
 		outputFile = android.PathForModuleOut(ctx, fileName)
-		ret = outputFile
+		ret.outputFile = outputFile
 	}
 
 	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
@@ -524,13 +525,13 @@
 
 	// Call the appropriate builder for this library type
 	if library.rlib() {
-		TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile)
+		ret.kytheFile = TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile).kytheFile
 	} else if library.dylib() {
-		TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile)
+		ret.kytheFile = TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile).kytheFile
 	} else if library.static() {
-		TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile)
+		ret.kytheFile = TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile).kytheFile
 	} else if library.shared() {
-		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile)
+		ret.kytheFile = TransformSrctoShared(ctx, srcPath, deps, flags, outputFile).kytheFile
 	}
 
 	if library.rlib() || library.dylib() {
@@ -572,7 +573,7 @@
 	return ret
 }
 
-func (library *libraryDecorator) srcPath(ctx ModuleContext, deps PathDeps) android.Path {
+func (library *libraryDecorator) srcPath(ctx ModuleContext, _ PathDeps) android.Path {
 	if library.sourceProvider != nil {
 		// Assume the first source from the source provider is the library entry point.
 		return library.sourceProvider.Srcs()[0]
diff --git a/rust/library_test.go b/rust/library_test.go
index d78dcdd..4633cc7 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -200,23 +200,34 @@
 func TestAutoDeps(t *testing.T) {
 
 	ctx := testRust(t, `
-                rust_library_host {
-                        name: "libbar",
-                        srcs: ["bar.rs"],
-                        crate_name: "bar",
-                }
+		rust_library_host {
+			name: "libbar",
+			srcs: ["bar.rs"],
+			crate_name: "bar",
+		}
+		rust_library_host_rlib {
+			name: "librlib_only",
+			srcs: ["bar.rs"],
+			crate_name: "rlib_only",
+		}
 		rust_library_host {
 			name: "libfoo",
 			srcs: ["foo.rs"],
 			crate_name: "foo",
-                        rustlibs: ["libbar"],
+			rustlibs: [
+				"libbar",
+				"librlib_only",
+			],
 		}
-                rust_ffi_host {
-                        name: "libfoo.ffi",
-                        srcs: ["foo.rs"],
-                        crate_name: "foo",
-                        rustlibs: ["libbar"],
-                }`)
+		rust_ffi_host {
+			name: "libfoo.ffi",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			rustlibs: [
+				"libbar",
+				"librlib_only",
+			],
+		}`)
 
 	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std")
 	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib")
@@ -239,7 +250,9 @@
 		if android.InList("libbar.dylib-std", dyn.Module().(*Module).Properties.AndroidMkRlibs) {
 			t.Errorf("libbar present as rlib dependency in dynamic lib")
 		}
-
+		if !android.InList("librlib_only.dylib-std", dyn.Module().(*Module).Properties.AndroidMkRlibs) {
+			t.Errorf("librlib_only should be selected by rustlibs as an rlib.")
+		}
 	}
 }
 
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 6cdd07d..fe9d0b5 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -145,7 +145,7 @@
 		&prebuilt.Properties)
 }
 
-func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
 	prebuilt.flagExporter.setProvider(ctx)
 
@@ -154,7 +154,7 @@
 		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
 	}
 	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
-	return srcPath
+	return buildOutput{outputFile: srcPath}
 }
 
 func (prebuilt *prebuiltLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags,
@@ -202,7 +202,7 @@
 		&prebuilt.Properties)
 }
 
-func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
 	prebuilt.flagExporter.setProvider(ctx)
 
@@ -211,7 +211,7 @@
 		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
 	}
 	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
-	return srcPath
+	return buildOutput{outputFile: srcPath}
 }
 
 func (prebuilt *prebuiltProcMacroDecorator) rustdoc(ctx ModuleContext, flags Flags,
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index f8a4bbd..832b62c 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -70,14 +70,14 @@
 	return flags
 }
 
-func (procMacro *procMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (procMacro *procMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
 	srcPath, _ := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
-	TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
+	ret := TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
 	procMacro.baseCompiler.unstrippedOutputFile = outputFile
-	return outputFile
+	return ret
 }
 
 func (procMacro *procMacroDecorator) getStem(ctx ModuleContext) string {
diff --git a/rust/rust.go b/rust/rust.go
index 2a7bdcf..48419eb 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -15,6 +15,7 @@
 package rust
 
 import (
+	"android/soong/bloaty"
 	"fmt"
 	"strings"
 
@@ -22,7 +23,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/bloaty"
 	"android/soong/cc"
 	cc_config "android/soong/cc/config"
 	"android/soong/fuzz"
@@ -52,6 +52,7 @@
 	})
 	pctx.Import("android/soong/rust/config")
 	pctx.ImportAs("cc_config", "android/soong/cc/config")
+	android.InitRegistrationContext.RegisterSingletonType("kythe_rust_extract", kytheExtractRustFactory)
 }
 
 type Flags struct {
@@ -64,6 +65,7 @@
 	Toolchain       config.Toolchain
 	Coverage        bool
 	Clippy          bool
+	EmitXrefs       bool // If true, emit rules to aid cross-referencing
 }
 
 type BaseProperties struct {
@@ -161,6 +163,9 @@
 	// Output file to be installed, may be stripped or unstripped.
 	outputFile android.OptionalPath
 
+	// Cross-reference input file
+	kytheFiles android.Paths
+
 	docTimestampFile android.OptionalPath
 
 	hideApexVariantFromMake bool
@@ -394,6 +399,10 @@
 	return false
 }
 
+func (mod *Module) XrefRustFiles() android.Paths {
+	return mod.kytheFiles
+}
+
 type Deps struct {
 	Dylibs          []string
 	Rlibs           []string
@@ -457,7 +466,7 @@
 	cfgFlags(ctx ModuleContext, flags Flags) Flags
 	featureFlags(ctx ModuleContext, flags Flags) Flags
 	compilerProps() []interface{}
-	compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path
+	compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput
 	compilerDeps(ctx DepsContext, deps Deps) Deps
 	crateName() string
 	rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath
@@ -493,6 +502,10 @@
 	exportLinkObjects(...string)
 }
 
+type xref interface {
+	XrefRustFiles() android.Paths
+}
+
 type flagExporter struct {
 	linkDirs    []string
 	linkObjects []string
@@ -904,11 +917,14 @@
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
-		outputFile := mod.compiler.compile(ctx, flags, deps)
+		buildOutput := mod.compiler.compile(ctx, flags, deps)
 		if ctx.Failed() {
 			return
 		}
-		mod.outputFile = android.OptionalPathForPath(outputFile)
+		mod.outputFile = android.OptionalPathForPath(buildOutput.outputFile)
+		if buildOutput.kytheFile != nil {
+			mod.kytheFiles = append(mod.kytheFiles, buildOutput.kytheFile)
+		}
 		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), android.OptionalPathForPath(mod.compiler.unstrippedOutputFilePath()))
 
 		mod.docTimestampFile = mod.compiler.rustdoc(ctx, flags, deps)
@@ -1368,13 +1384,12 @@
 	}
 
 	// rlibs
+	rlibDepVariations = append(rlibDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: rlibVariation})
 	for _, lib := range deps.Rlibs {
 		depTag := rlibDepTag
 		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
 
-		actx.AddVariationDependencies(append(rlibDepVariations, []blueprint.Variation{
-			{Mutator: "rust_libraries", Variation: rlibVariation},
-		}...), depTag, lib)
+		actx.AddVariationDependencies(rlibDepVariations, depTag, lib)
 	}
 
 	// dylibs
@@ -1386,21 +1401,25 @@
 	// rustlibs
 	if deps.Rustlibs != nil && !mod.compiler.Disabled() {
 		autoDep := mod.compiler.(autoDeppable).autoDep(ctx)
-		if autoDep.depTag == rlibDepTag {
-			for _, lib := range deps.Rustlibs {
-				depTag := autoDep.depTag
-				lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
-				actx.AddVariationDependencies(append(rlibDepVariations, []blueprint.Variation{
-					{Mutator: "rust_libraries", Variation: autoDep.variation},
-				}...), depTag, lib)
+		for _, lib := range deps.Rustlibs {
+			if autoDep.depTag == rlibDepTag {
+				// Handle the rlib deptag case
+				addRlibDependency(actx, lib, mod, snapshotInfo, rlibDepVariations)
+			} else {
+				// autoDep.depTag is a dylib depTag. Not all rustlibs may be available as a dylib however.
+				// Check for the existence of the dylib deptag variant. Select it if available,
+				// otherwise select the rlib variant.
+				autoDepVariations := append(commonDepVariations,
+					blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation})
+				if actx.OtherModuleDependencyVariantExists(autoDepVariations, lib) {
+					actx.AddVariationDependencies(autoDepVariations, autoDep.depTag, lib)
+				} else {
+					// If there's no dylib dependency available, try to add the rlib dependency instead.
+					addRlibDependency(actx, lib, mod, snapshotInfo, rlibDepVariations)
+				}
 			}
-		} else {
-			actx.AddVariationDependencies(
-				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation}),
-				autoDep.depTag, deps.Rustlibs...)
 		}
 	}
-
 	// stdlibs
 	if deps.Stdlibs != nil {
 		if mod.compiler.stdLinkage(ctx) == RlibLinkage {
@@ -1476,6 +1495,12 @@
 	actx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), procMacroDepTag, deps.ProcMacros...)
 }
 
+// addRlibDependency will add an rlib dependency, rewriting to the snapshot library if available.
+func addRlibDependency(actx android.BottomUpMutatorContext, lib string, mod *Module, snapshotInfo *cc.SnapshotInfo, variations []blueprint.Variation) {
+	lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+	actx.AddVariationDependencies(variations, rlibDepTag, lib)
+}
+
 func BeginMutator(ctx android.BottomUpMutatorContext) {
 	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled() {
 		mod.beginMutator(ctx)
@@ -1609,6 +1634,25 @@
 	return "", false
 }
 
+func kytheExtractRustFactory() android.Singleton {
+	return &kytheExtractRustSingleton{}
+}
+
+type kytheExtractRustSingleton struct {
+}
+
+func (k kytheExtractRustSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var xrefTargets android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		if rustModule, ok := module.(xref); ok {
+			xrefTargets = append(xrefTargets, rustModule.XrefRustFiles()...)
+		}
+	})
+	if len(xrefTargets) > 0 {
+		ctx.Phony("xref_rust", xrefTargets...)
+	}
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/rust/snapshot_prebuilt.go b/rust/snapshot_prebuilt.go
index dfbc1d1..2f79cc5 100644
--- a/rust/snapshot_prebuilt.go
+++ b/rust/snapshot_prebuilt.go
@@ -69,7 +69,7 @@
 	return module, prebuilt
 }
 
-func (library *snapshotLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+func (library *snapshotLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	var variant string
 	if library.static() {
 		variant = cc.SnapshotStaticSuffix
@@ -85,11 +85,11 @@
 	}
 
 	if !library.MatchesWithDevice(ctx.DeviceConfig()) {
-		return nil
+		return buildOutput{}
 	}
 	outputFile := android.PathForModuleSrc(ctx, *library.properties.Src)
 	library.unstrippedOutputFile = outputFile
-	return outputFile
+	return buildOutput{outputFile: outputFile}
 }
 
 func (library *snapshotLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath {
diff --git a/rust/test.go b/rust/test.go
index 250b765..6e53935 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -15,6 +15,8 @@
 package rust
 
 import (
+	"path/filepath"
+
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -151,9 +153,15 @@
 			ctx.ModuleErrorf("data_lib %q is not a linkable module", depName)
 		}
 		if linkableDep.OutputFile().Valid() {
+			// Copy the output in "lib[64]" so that it's compatible with
+			// the default rpath values.
+			libDir := "lib"
+			if linkableDep.Target().Arch.ArchType.Multilib == "lib64" {
+				libDir = "lib64"
+			}
 			test.data = append(test.data,
 				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
-					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+					RelativeInstallPath: filepath.Join(libDir, linkableDep.RelativeInstallPath())})
 		}
 	})
 
diff --git a/rust/test_test.go b/rust/test_test.go
index 1124176..8906f1c 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -187,12 +187,12 @@
 		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
 	}
 	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
-	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
-		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:lib64/foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:lib64/foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
 	}
-	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:foo/bar/baz") {
-		t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:foo/bar/baz`,"+
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:lib64/foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:lib64/foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
 	}
 	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][2], ":rusty:foo/bar/baz") {
diff --git a/rust/testing.go b/rust/testing.go
index cb98bed..4796f69 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -194,6 +194,7 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+	ctx.RegisterSingletonType("kythe_rust_extract", kytheExtractRustFactory)
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
 	})
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 4c847a1..4773579 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -188,3 +188,8 @@
         "get_clang_version.py",
     ],
 }
+
+sh_binary_host {
+    name: "list_image",
+    src: "list_image.sh",
+}
diff --git a/scripts/hiddenapi/generate_hiddenapi_lists.py b/scripts/hiddenapi/generate_hiddenapi_lists.py
index 35e0948..6546c7f 100755
--- a/scripts/hiddenapi/generate_hiddenapi_lists.py
+++ b/scripts/hiddenapi/generate_hiddenapi_lists.py
@@ -27,6 +27,7 @@
 FLAG_MAX_TARGET_P = 'max-target-p'
 FLAG_MAX_TARGET_Q = 'max-target-q'
 FLAG_MAX_TARGET_R = 'max-target-r'
+FLAG_MAX_TARGET_S = 'max-target-s'
 FLAG_CORE_PLATFORM_API = 'core-platform-api'
 FLAG_PUBLIC_API = 'public-api'
 FLAG_SYSTEM_API = 'system-api'
@@ -41,6 +42,7 @@
     FLAG_MAX_TARGET_P,
     FLAG_MAX_TARGET_Q,
     FLAG_MAX_TARGET_R,
+    FLAG_MAX_TARGET_S,
 ]
 ALL_FLAGS = FLAGS_API_LIST + [
     FLAG_CORE_PLATFORM_API,
diff --git a/scripts/list_image.sh b/scripts/list_image.sh
new file mode 100755
index 0000000..0542fa6
--- /dev/null
+++ b/scripts/list_image.sh
@@ -0,0 +1,51 @@
+#! /bin/bash
+
+# Recursively list Android image directory.
+set -eu
+set -o pipefail
+
+function die() { format=$1; shift; printf "$format\n" "$@"; exit 1; }
+
+# Figure out the filer utility.
+declare filer=
+[[ -z "${ANDROID_HOST_OUT:-}" ]] || filer=${ANDROID_HOST_OUT}/bin/debugfs_static
+if [[ "${1:-}" =~ --debugfs_path=(.*) ]]; then
+  filer=${BASH_REMATCH[1]}
+  shift
+fi
+if [[ -z "${filer:-}" ]]; then
+  maybefiler="$(dirname $0)/debugfs_static"
+  [[ ! -x "$maybefiler" ]] || filer="$maybefiler"
+fi
+
+(( $# >0 )) || die "%s [--debugfs_path=<path>] IMAGE" "$0"
+
+[[ -n "${filer:-}" ]] || die "cannot locate 'debugfs' executable: \
+--debugfs_path= is missing, ANDROID_HOST_OUT is not set, \
+and 'debugfs_static' is not colocated with this script"
+declare -r image="$1"
+
+function dolevel() {
+  printf "%s/\n" "$1"
+  # Each line of the file output consists of 6 fields separated with '/'.
+  # The second one contains the file's attributes, and the fifth its name.
+  $filer -R "ls -l -p $1" "$image" 2>/dev/null |\
+    sed -nr 's|^/.*/(.*)/.*/.*/(.+)/.*/$|\2 \1|p' | LANG=C sort | \
+  while read name attr; do
+    [[ "$name" != '.' && "$name" != '..' ]] || continue
+    path="$1/$name"
+    # If the second char of the attributes is '4', it is a directory.
+    if [[ $attr =~ ^.4 ]]; then
+      dolevel "$path"
+    else
+      printf "%s\n" "$path"
+    fi
+  done
+}
+
+# The filer always prints its version on stderr, so we are going
+# to redirect it to the bit bucket. On the other hand, the filer's
+# return code on error is still 0. Let's run it once to without
+# redirecting stderr to see that there is at least one entry.
+$filer -R "ls -l -p" "$image" | grep -q -m1 -P '^/.*/.*/.*/.*/.+/.*/$'
+dolevel .
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index c150e8c..3dbc22e 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -28,6 +28,8 @@
 from manifest import parse_test_config
 from manifest import write_xml
 
+KNOWN_PREPARERS = ['com.android.tradefed.targetprep.TestAppInstallSetup',
+                   'com.android.tradefed.targetprep.suite.SuiteApkInstaller']
 
 def parse_args():
   """Parse commandline arguments."""
@@ -64,7 +66,7 @@
   tests = get_children_with_tag(test_config, 'target_preparer')
 
   for test in tests:
-    if test.getAttribute('class') == "com.android.tradefed.targetprep.TestAppInstallSetup":
+    if test.getAttribute('class') in KNOWN_PREPARERS:
       options = get_children_with_tag(test, 'option')
       for option in options:
         if option.getAttribute('name') == "test-file-name":
diff --git a/scripts/test_config_fixer_test.py b/scripts/test_config_fixer_test.py
index d00a593..39ce5b3 100644
--- a/scripts/test_config_fixer_test.py
+++ b/scripts/test_config_fixer_test.py
@@ -70,7 +70,7 @@
 class OverwriteTestFileNameTest(unittest.TestCase):
   """ Unit tests for overwrite_test_file_name function """
 
-  test_config = (
+  test_config_test_app_install_setup = (
       '<?xml version="1.0" encoding="utf-8"?>\n'
       '<configuration description="Runs some tests.">\n'
       '    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">\n'
@@ -82,15 +82,38 @@
       '    </test>\n'
       '</configuration>\n')
 
-  def test_all(self):
-    doc = minidom.parseString(self.test_config % ("foo.apk"))
+  test_config_suite_apk_installer = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<configuration description="Runs some tests.">\n'
+      '    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">\n'
+      '        <option name="test-file-name" value="%s"/>\n'
+      '    </target_preparer>\n'
+      '    <test class="com.android.tradefed.testtype.AndroidJUnitTest">\n'
+      '        <option name="package" value="com.android.foo"/>\n'
+      '        <option name="runtime-hint" value="20s"/>\n'
+      '    </test>\n'
+      '</configuration>\n')
+
+  def test_testappinstallsetup(self):
+    doc = minidom.parseString(self.test_config_test_app_install_setup % ("foo.apk"))
 
     test_config_fixer.overwrite_test_file_name(doc, "bar.apk")
     output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.
-    expected = self.test_config % ("bar.apk")
+    expected = self.test_config_test_app_install_setup % ("bar.apk")
+    self.assertEqual(expected, output.getvalue())
+
+  def test_suiteapkinstaller(self):
+    doc = minidom.parseString(self.test_config_suite_apk_installer % ("foo.apk"))
+
+    test_config_fixer.overwrite_test_file_name(doc, "bar.apk")
+    output = io.StringIO()
+    test_config_fixer.write_xml(output, doc)
+
+    # Only the matching package name in a test node should be updated.
+    expected = self.test_config_suite_apk_installer % ("bar.apk")
     self.assertEqual(expected, output.getvalue())
 
 
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index cd63dac..571d214 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -193,114 +193,6 @@
 `))
 }
 
-func TestBasicSdkWithCc(t *testing.T) {
-	result := testSdkWithCc(t, `
-		sdk {
-			name: "mysdk",
-			native_shared_libs: ["sdkmember"],
-		}
-
-		cc_library_shared {
-			name: "sdkmember",
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: ["mysdkapex"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@1",
-			native_shared_libs: ["sdkmember_mysdk@1"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@2",
-			native_shared_libs: ["sdkmember_mysdk@2"],
-		}
-
-		cc_prebuilt_library_shared {
-			name: "sdkmember",
-			srcs: ["libfoo.so"],
-			prefer: false,
-			system_shared_libs: [],
-			stl: "none",
-		}
-
-		cc_prebuilt_library_shared {
-			name: "sdkmember_mysdk@1",
-			sdk_member_name: "sdkmember",
-			srcs: ["libfoo.so"],
-			system_shared_libs: [],
-			stl: "none",
-			// TODO: remove //apex_available:platform
-			apex_available: [
-				"//apex_available:platform",
-				"myapex",
-			],
-		}
-
-		cc_prebuilt_library_shared {
-			name: "sdkmember_mysdk@2",
-			sdk_member_name: "sdkmember",
-			srcs: ["libfoo.so"],
-			system_shared_libs: [],
-			stl: "none",
-			// TODO: remove //apex_available:platform
-			apex_available: [
-				"//apex_available:platform",
-				"myapex2",
-			],
-		}
-
-		cc_library_shared {
-			name: "mycpplib",
-			srcs: ["Test.cpp"],
-			shared_libs: ["sdkmember"],
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [
-				"myapex",
-				"myapex2",
-			],
-		}
-
-		apex {
-			name: "myapex",
-			native_shared_libs: ["mycpplib"],
-			uses_sdks: ["mysdk@1"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-			updatable: false,
-		}
-
-		apex {
-			name: "myapex2",
-			native_shared_libs: ["mycpplib"],
-			uses_sdks: ["mysdk@2"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-			updatable: false,
-		}
-
-		apex {
-			name: "mysdkapex",
-			native_shared_libs: ["sdkmember"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-			updatable: false,
-		}
-	`)
-
-	sdkMemberV1 := result.ModuleForTests("sdkmember_mysdk@1", "android_arm64_armv8-a_shared_apex10000_mysdk_1").Rule("toc").Output
-	sdkMemberV2 := result.ModuleForTests("sdkmember_mysdk@2", "android_arm64_armv8-a_shared_apex10000_mysdk_2").Rule("toc").Output
-
-	cpplibForMyApex := result.ModuleForTests("mycpplib", "android_arm64_armv8-a_shared_apex10000_mysdk_1")
-	cpplibForMyApex2 := result.ModuleForTests("mycpplib", "android_arm64_armv8-a_shared_apex10000_mysdk_2")
-
-	// Depending on the uses_sdks value, different libs are linked
-	ensureListContains(t, pathsToStrings(cpplibForMyApex.Rule("ld").Implicits), sdkMemberV1.String())
-	ensureListContains(t, pathsToStrings(cpplibForMyApex2.Rule("ld").Implicits), sdkMemberV2.String())
-}
-
 // Make sure the sdk can use host specific cc libraries static/shared and both.
 func TestHostSdkWithCc(t *testing.T) {
 	testSdkWithCc(t, `
@@ -2835,11 +2727,6 @@
 		}
 	`)
 
-	// Mixing the snapshot with the source (irrespective of which one is preferred) causes a problem
-	// due to missing variants.
-	// TODO(b/183204176): Remove this and fix the cause.
-	snapshotWithSourceErrorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\QReplaceDependencies could not find identical variant {os:android,image:,arch:arm64_armv8-a,sdk:,link:shared,version:} for module mynativelib\E`)
-
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -2866,7 +2753,5 @@
 arm64/include/Arm64Test.h -> arm64/include/arm64/include/Arm64Test.h
 .intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
 `),
-		snapshotTestErrorHandler(checkSnapshotWithSourcePreferred, snapshotWithSourceErrorHandler),
-		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, snapshotWithSourceErrorHandler),
 	)
 }
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index f0d3b35..9d0c3de 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -71,90 +71,6 @@
 	)
 }
 
-func TestBasicSdkWithJavaLibrary(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForSdkTestWithJava,
-		prepareForSdkTestWithApex,
-	).RunTestWithBp(t, `
-		sdk {
-			name: "mysdk",
-			java_header_libs: ["sdkmember"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@1",
-			java_header_libs: ["sdkmember_mysdk@1"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@2",
-			java_header_libs: ["sdkmember_mysdk@2"],
-		}
-
-		java_library {
-			name: "sdkmember",
-			srcs: ["Test.java"],
-			system_modules: "none",
-			sdk_version: "none",
-			host_supported: true,
-		}
-
-		java_import {
-			name: "sdkmember_mysdk@1",
-			sdk_member_name: "sdkmember",
-			host_supported: true,
-		}
-
-		java_import {
-			name: "sdkmember_mysdk@2",
-			sdk_member_name: "sdkmember",
-			host_supported: true,
-		}
-
-		java_library {
-			name: "myjavalib",
-			srcs: ["Test.java"],
-			libs: ["sdkmember"],
-			system_modules: "none",
-			sdk_version: "none",
-			compile_dex: true,
-			host_supported: true,
-			apex_available: [
-				"myapex",
-				"myapex2",
-			],
-		}
-
-		apex {
-			name: "myapex",
-			java_libs: ["myjavalib"],
-			uses_sdks: ["mysdk@1"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-			updatable: false,
-		}
-
-		apex {
-			name: "myapex2",
-			java_libs: ["myjavalib"],
-			uses_sdks: ["mysdk@2"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-			updatable: false,
-		}
-	`)
-
-	sdkMemberV1 := result.ModuleForTests("sdkmember_mysdk@1", "android_common").Rule("combineJar").Output
-	sdkMemberV2 := result.ModuleForTests("sdkmember_mysdk@2", "android_common").Rule("combineJar").Output
-
-	javalibForMyApex := result.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_1")
-	javalibForMyApex2 := result.ModuleForTests("myjavalib", "android_common_apex10000_mysdk_2")
-
-	// Depending on the uses_sdks value, different libs are linked
-	ensureListContains(t, pathsToStrings(javalibForMyApex.Rule("javac").Implicits), sdkMemberV1.String())
-	ensureListContains(t, pathsToStrings(javalibForMyApex2.Rule("javac").Implicits), sdkMemberV2.String())
-}
-
 func TestSnapshotWithJavaHeaderLibrary(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
@@ -880,6 +796,44 @@
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
 .intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
+		checkInfoContents(`
+[
+  {
+    "@type": "sdk",
+    "@name": "mysdk",
+    "java_header_libs": [
+      "exported-system-module",
+      "system-module"
+    ],
+    "java_sdk_libs": [
+      "myjavalib"
+    ],
+    "java_system_modules": [
+      "my-system-modules"
+    ]
+  },
+  {
+    "@type": "java_library",
+    "@name": "exported-system-module"
+  },
+  {
+    "@type": "java_system_modules",
+    "@name": "my-system-modules",
+    "@deps": [
+      "exported-system-module",
+      "system-module"
+    ]
+  },
+  {
+    "@type": "java_sdk_library",
+    "@name": "myjavalib"
+  },
+  {
+    "@type": "java_library",
+    "@name": "system-module"
+  }
+]
+`),
 	)
 }
 
diff --git a/sdk/sdk.go b/sdk/sdk.go
index 84c9a96..b37eaad 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -39,7 +39,6 @@
 	ctx.RegisterModuleType("sdk", SdkModuleFactory)
 	ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory)
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
 }
 
 type sdk struct {
@@ -76,6 +75,8 @@
 
 	snapshotFile android.OptionalPath
 
+	infoFile android.OptionalPath
+
 	// The builder, preserved for testing.
 	builderForTests *snapshotBuilder
 }
@@ -192,27 +193,32 @@
 		}
 
 		// Generate the snapshot from the member info.
-		p := s.buildSnapshot(ctx, sdkVariants)
-		zip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), p.Base(), p)
-		s.snapshotFile = android.OptionalPathForPath(zip)
+		s.buildSnapshot(ctx, sdkVariants)
 	}
 }
 
 func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
-	if !s.snapshotFile.Valid() {
+	if !s.snapshotFile.Valid() != !s.infoFile.Valid() {
+		panic("Snapshot (%q) and info file (%q) should both be set or neither should be set.")
+	} else if !s.snapshotFile.Valid() {
 		return []android.AndroidMkEntries{}
 	}
 
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "FAKE",
 		OutputFile: s.snapshotFile,
-		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path()),
+		DistFiles:  android.MakeDefaultDistFiles(s.snapshotFile.Path(), s.infoFile.Path()),
 		Include:    "$(BUILD_PHONY_PACKAGE)",
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string) {
 				// Allow the sdk to be built by simply passing its name on the command line.
 				fmt.Fprintln(w, ".PHONY:", s.Name())
 				fmt.Fprintln(w, s.Name()+":", s.snapshotFile.String())
+
+				// Allow the sdk info to be built by simply passing its name on the command line.
+				infoTarget := s.Name() + ".info"
+				fmt.Fprintln(w, ".PHONY:", infoTarget)
+				fmt.Fprintln(w, infoTarget+":", s.infoFile.String())
 			},
 		},
 	}}
@@ -278,20 +284,6 @@
 	ctx.BottomUp("SdkMemberInterVersion", memberInterVersionMutator).Parallel()
 }
 
-// RegisterPostDepsMutators registers post-deps mutators to support modules implementing SdkAware
-// interface and the sdk module type. This function has been made public to be called by tests
-// outside of the sdk package
-func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
-	// These must run AFTER apexMutator. Note that the apex package is imported even though there is
-	// no direct dependency to the package here. sdkDepsMutator sets the SDK requirements from an
-	// APEX to its dependents. Since different versions of the same SDK can be used by different
-	// APEXes, the apex and its dependents (which includes the dependencies to the sdk members)
-	// should have been mutated for the apex before the SDK requirements are set.
-	ctx.TopDown("SdkDepsMutator", sdkDepsMutator).Parallel()
-	ctx.BottomUp("SdkDepsReplaceMutator", sdkDepsReplaceMutator).Parallel()
-	ctx.TopDown("SdkRequirementCheck", sdkRequirementsMutator).Parallel()
-}
-
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 }
@@ -413,103 +405,4 @@
 type sdkAndApexModule interface {
 	android.Module
 	android.DepIsInSameApex
-	android.RequiredSdks
-}
-
-// Step 4: transitively ripple down the SDK requirements from the root modules like APEX to its
-// descendants
-func sdkDepsMutator(mctx android.TopDownMutatorContext) {
-	if parent, ok := mctx.Module().(sdkAndApexModule); ok {
-		// Module types for Mainline modules (e.g. APEX) are expected to implement RequiredSdks()
-		// by reading its own properties like `uses_sdks`.
-		requiredSdks := parent.RequiredSdks()
-		if len(requiredSdks) > 0 {
-			mctx.VisitDirectDeps(func(m android.Module) {
-				// Only propagate required sdks from the apex onto its contents.
-				if dep, ok := m.(android.SdkAware); ok && android.IsDepInSameApex(mctx, parent, dep) {
-					dep.BuildWithSdks(requiredSdks)
-				}
-			})
-		}
-	}
-}
-
-// Step 5: if libfoo.mysdk.11 is in the context where version 11 of mysdk is requested, the
-// versioned module is used instead of the un-versioned (in-development) module libfoo
-func sdkDepsReplaceMutator(mctx android.BottomUpMutatorContext) {
-	if versionedSdkMember, ok := mctx.Module().(android.SdkAware); ok && versionedSdkMember.IsInAnySdk() && versionedSdkMember.IsVersioned() {
-		if sdk := versionedSdkMember.ContainingSdk(); !sdk.Unversioned() {
-			// Only replace dependencies to <sdkmember> with <sdkmember@required-version>
-			// if the depending module requires it. e.g.
-			//      foo -> sdkmember
-			// will be transformed to:
-			//      foo -> sdkmember@1
-			// if and only if foo is a member of an APEX that requires version 1 of the
-			// sdk containing sdkmember.
-			memberName := versionedSdkMember.MemberName()
-
-			// Convert a panic into a normal error to allow it to be more easily tested for. This is a
-			// temporary workaround, once http://b/183204176 has been fixed this can be removed.
-			// TODO(b/183204176): Remove this after fixing.
-			defer func() {
-				if r := recover(); r != nil {
-					mctx.ModuleErrorf("sdkDepsReplaceMutator %s", r)
-				}
-			}()
-
-			// Replace dependencies on sdkmember with a dependency on the current module which
-			// is a versioned prebuilt of the sdkmember if required.
-			mctx.ReplaceDependenciesIf(memberName, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
-				// from - foo
-				// to - sdkmember
-				replace := false
-				if parent, ok := from.(android.RequiredSdks); ok {
-					replace = parent.RequiredSdks().Contains(sdk)
-				}
-				return replace
-			})
-		}
-	}
-}
-
-// Step 6: ensure that the dependencies outside of the APEX are all from the required SDKs
-func sdkRequirementsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(sdkAndApexModule); ok {
-		requiredSdks := m.RequiredSdks()
-		if len(requiredSdks) == 0 {
-			return
-		}
-		mctx.VisitDirectDeps(func(dep android.Module) {
-			tag := mctx.OtherModuleDependencyTag(dep)
-			if tag == android.DefaultsDepTag {
-				// dependency to defaults is always okay
-				return
-			}
-
-			// Ignore the dependency from the unversioned member to any versioned members as an
-			// apex that depends on the unversioned member will not also be depending on a versioned
-			// member.
-			if _, ok := tag.(sdkMemberVersionedDepTag); ok {
-				return
-			}
-
-			// If the dep is outside of the APEX, but is not in any of the required SDKs, we know that the
-			// dep is a violation.
-			if sa, ok := dep.(android.SdkAware); ok {
-				// It is not an error if a dependency that is excluded from the apex due to the tag is not
-				// in one of the required SDKs. That is because all of the existing tags that implement it
-				// do not depend on modules which can or should belong to an sdk_snapshot.
-				if _, ok := tag.(android.ExcludeFromApexContentsTag); ok {
-					// The tag defines a dependency that never requires the child module to be part of the
-					// same apex.
-					return
-				}
-
-				if !m.DepIsInSameApex(mctx, dep) && !requiredSdks.Contains(sa.ContainingSdk()) {
-					mctx.ModuleErrorf("depends on %q (in SDK %q) that isn't part of the required SDKs: %v",
-						sa.Name(), sa.ContainingSdk(), requiredSdks)
-				}
-			}
-		})
-	}
 }
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 83294f6..ccbeb8d 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -37,64 +37,6 @@
 	os.Exit(m.Run())
 }
 
-func TestDepNotInRequiredSdks(t *testing.T) {
-	testSdkError(t, `module "myjavalib".*depends on "otherlib".*that isn't part of the required SDKs:.*`, `
-		sdk {
-			name: "mysdk",
-			java_header_libs: ["sdkmember"],
-		}
-
-		sdk_snapshot {
-			name: "mysdk@1",
-			java_header_libs: ["sdkmember_mysdk_1"],
-		}
-
-		java_import {
-			name: "sdkmember",
-			prefer: false,
-			host_supported: true,
-		}
-
-		java_import {
-			name: "sdkmember_mysdk_1",
-			sdk_member_name: "sdkmember",
-			host_supported: true,
-		}
-
-		java_library {
-			name: "myjavalib",
-			srcs: ["Test.java"],
-			libs: [
-				"sdkmember",
-				"otherlib",
-			],
-			system_modules: "none",
-			sdk_version: "none",
-			compile_dex: true,
-			host_supported: true,
-			apex_available: ["myapex"],
-		}
-
-		// this lib is no in mysdk
-		java_library {
-			name: "otherlib",
-			srcs: ["Test.java"],
-			system_modules: "none",
-			sdk_version: "none",
-			compile_dex: true,
-			host_supported: true,
-		}
-
-		apex {
-			name: "myapex",
-			java_libs: ["myjavalib"],
-			uses_sdks: ["mysdk@1"],
-			key: "myapex.key",
-			certificate: ":myapex.cert",
-		}
-	`)
-}
-
 // Ensure that prebuilt modules have the same effective visibility as the source
 // modules.
 func TestSnapshotVisibility(t *testing.T) {
@@ -321,7 +263,10 @@
 	result := testSdkWithFs(t, sdk, nil)
 
 	CheckSnapshot(t, result, "mysdk", "",
-		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`))
+		checkAllOtherCopyRules(`
+.intermediates/mysdk/common_os/mysdk-current.info -> mysdk-current.info
+.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip
+`))
 }
 
 type EmbeddedPropertiesStruct struct {
diff --git a/sdk/testing.go b/sdk/testing.go
index 062f200..b0f5fdc 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -142,6 +142,7 @@
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
+		infoContents:                 sdk.GetInfoContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
 		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
@@ -402,6 +403,17 @@
 	}
 }
 
+// Check that the snapshot's info contents are ciorrect.
+//
+// Both the expected and actual string are both trimmed before comparing.
+func checkInfoContents(expected string) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		info.t.Helper()
+		android.AssertTrimmedStringEquals(info.t, "info contents do not match",
+			expected, info.infoContents)
+	}
+}
+
 type resultChecker func(t *testing.T, result *android.TestResult)
 
 // snapshotTestPreparer registers a preparer that will be used to customize the specified
@@ -479,6 +491,9 @@
 	// The contents of the versioned Android.bp file
 	androidVersionedBpContents string
 
+	// The contents of the info file.
+	infoContents string
+
 	// The paths, relative to the snapshot root, of all files and directories copied into the
 	// snapshot.
 	snapshotContents []string
diff --git a/sdk/update.go b/sdk/update.go
index 5db604b..71bd042 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -15,6 +15,8 @@
 package sdk
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"sort"
@@ -219,9 +221,19 @@
 				exportedComponentsInfo = ctx.OtherModuleProvider(child, android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
 			}
 
+			var container android.SdkAware
+			if parent != ctx.Module() {
+				container = parent.(android.SdkAware)
+			}
+
 			export := memberTag.ExportMember()
 			s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
-				s, memberType, child.(android.SdkAware), export, exportedComponentsInfo,
+				sdkVariant:             s,
+				memberType:             memberType,
+				variant:                child.(android.SdkAware),
+				container:              container,
+				export:                 export,
+				exportedComponentsInfo: exportedComponentsInfo,
 			})
 
 			// Recurse down into the member's dependencies as it may have dependencies that need to be
@@ -311,7 +323,7 @@
 
 // buildSnapshot is the main function in this source file. It creates rules to copy
 // the contents (header files, stub libraries, etc) into the zip file.
-func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) android.OutputPath {
+func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) {
 
 	// Aggregate all the sdkMemberVariantDep instances from all the sdk variants.
 	hasLicenses := false
@@ -370,9 +382,9 @@
 	// Unversioned modules are not required in that case because the numbered version will be a
 	// finalized version of the snapshot that is intended to be kept separate from the
 	generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
-	snapshotZipFileSuffix := ""
+	snapshotFileSuffix := ""
 	if generateVersioned {
-		snapshotZipFileSuffix = "-" + version
+		snapshotFileSuffix = "-" + version
 	}
 
 	currentBuildRelease := latestBuildRelease()
@@ -489,7 +501,7 @@
 	filesToZip := builder.filesToZip
 
 	// zip them all
-	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotFileSuffix)
 	outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
 	outputDesc := "Building snapshot for " + ctx.ModuleName()
 
@@ -502,7 +514,7 @@
 		zipFile = outputZipFile
 		desc = outputDesc
 	} else {
-		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotFileSuffix)
 		zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
 		desc = "Building intermediate snapshot for " + ctx.ModuleName()
 	}
@@ -527,7 +539,120 @@
 		})
 	}
 
-	return outputZipFile
+	modules := s.generateInfoData(ctx, memberVariantDeps)
+
+	// Output the modules information as pretty printed JSON.
+	info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
+	output, err := json.MarshalIndent(modules, "", "  ")
+	if err != nil {
+		ctx.ModuleErrorf("error generating %q: %s", info, err)
+	}
+	builder.infoContents = string(output)
+	info.generatedContents.UnindentedPrintf("%s", output)
+	info.build(pctx, ctx, nil)
+	infoPath := info.path
+	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath)
+	s.infoFile = android.OptionalPathForPath(installedInfo)
+
+	// Install the zip, making sure that the info file has been installed as well.
+	installedZip := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), outputZipFile.Base(), outputZipFile, installedInfo)
+	s.snapshotFile = android.OptionalPathForPath(installedZip)
+}
+
+type moduleInfo struct {
+	// The type of the module, e.g. java_sdk_library
+	moduleType string
+	// The name of the module.
+	name string
+	// A list of additional dependencies of the module.
+	deps []string
+	// Additional dynamic properties.
+	dynamic map[string]interface{}
+}
+
+func (m *moduleInfo) MarshalJSON() ([]byte, error) {
+	buffer := bytes.Buffer{}
+
+	separator := ""
+	writeObjectPair := func(key string, value interface{}) {
+		buffer.WriteString(fmt.Sprintf("%s%q: ", separator, key))
+		b, err := json.Marshal(value)
+		if err != nil {
+			panic(err)
+		}
+		buffer.Write(b)
+		separator = ","
+	}
+
+	buffer.WriteString("{")
+	writeObjectPair("@type", m.moduleType)
+	writeObjectPair("@name", m.name)
+	if m.deps != nil {
+		writeObjectPair("@deps", m.deps)
+	}
+	for _, k := range android.SortedStringKeys(m.dynamic) {
+		v := m.dynamic[k]
+		writeObjectPair(k, v)
+	}
+	buffer.WriteString("}")
+	return buffer.Bytes(), nil
+}
+
+var _ json.Marshaler = (*moduleInfo)(nil)
+
+// generateInfoData creates a list of moduleInfo structures that will be marshalled into JSON.
+func (s *sdk) generateInfoData(ctx android.ModuleContext, memberVariantDeps []sdkMemberVariantDep) interface{} {
+	modules := []*moduleInfo{}
+	sdkInfo := moduleInfo{
+		moduleType: "sdk",
+		name:       ctx.ModuleName(),
+		dynamic:    map[string]interface{}{},
+	}
+	modules = append(modules, &sdkInfo)
+
+	name2Info := map[string]*moduleInfo{}
+	getModuleInfo := func(module android.Module) *moduleInfo {
+		name := module.Name()
+		info := name2Info[name]
+		if info == nil {
+			moduleType := ctx.OtherModuleType(module)
+			// Remove any suffix added when creating modules dynamically.
+			moduleType = strings.Split(moduleType, "__")[0]
+			info = &moduleInfo{
+				moduleType: moduleType,
+				name:       name,
+			}
+			name2Info[name] = info
+		}
+		return info
+	}
+
+	for _, memberVariantDep := range memberVariantDeps {
+		propertyName := memberVariantDep.memberType.SdkPropertyName()
+		var list []string
+		if v, ok := sdkInfo.dynamic[propertyName]; ok {
+			list = v.([]string)
+		}
+
+		memberName := memberVariantDep.variant.Name()
+		list = append(list, memberName)
+		sdkInfo.dynamic[propertyName] = android.SortedUniqueStrings(list)
+
+		if memberVariantDep.container != nil {
+			containerInfo := getModuleInfo(memberVariantDep.container)
+			containerInfo.deps = android.SortedUniqueStrings(append(containerInfo.deps, memberName))
+		}
+
+		// Make sure that the module info is created for each module.
+		getModuleInfo(memberVariantDep.variant)
+	}
+
+	for _, memberName := range android.SortedStringKeys(name2Info) {
+		info := name2Info[memberName]
+		modules = append(modules, info)
+	}
+
+	return modules
 }
 
 // filterOutComponents removes any item from the deps list that is a component of another item in
@@ -1033,6 +1158,10 @@
 	return contents.content.String()
 }
 
+func (s *sdk) GetInfoContentsForTests() string {
+	return s.builderForTests.infoContents
+}
+
 func (s *sdk) GetUnversionedAndroidBpContentsForTests() string {
 	contents := &generatedContents{}
 	generateFilteredBpContents(contents, s.builderForTests.bpFile, func(module *bpModule) bool {
@@ -1087,6 +1216,8 @@
 
 	// The target build release for which the snapshot is to be generated.
 	targetBuildRelease *buildRelease
+
+	infoContents string
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -1322,6 +1453,11 @@
 	// The variant that is added to the sdk.
 	variant android.SdkAware
 
+	// The optional container of this member, i.e. the module that is depended upon by the sdk
+	// (possibly transitively) and whose dependency on this module is why it was added to the sdk.
+	// Is nil if this a direct dependency of the sdk.
+	container android.SdkAware
+
 	// True if the member should be exported, i.e. accessible, from outside the sdk.
 	export bool
 
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 4f37c2b..74e49aa 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -115,3 +115,57 @@
 }
 
 test_bp2build_generates_all_buildfiles
+
+function test_cc_correctness {
+  setup
+  create_mock_bazel
+
+  mkdir -p a
+  cat > a/Android.bp <<EOF
+cc_object {
+  name: "qq",
+  srcs: ["qq.cc"],
+  bazel_module: {
+    bp2build_available: true,
+  },
+  stl: "none",
+  system_shared_libs: [],
+}
+EOF
+
+  cat > a/qq.cc <<EOF
+#include "qq.h"
+int qq() {
+  return QQ;
+}
+EOF
+
+  cat > a/qq.h <<EOF
+#define QQ 1
+EOF
+
+  run_soong bp2build
+
+  run_bazel build --package_path=out/soong/workspace //a:qq
+  local output_mtime1=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
+
+  run_bazel build --package_path=out/soong/workspace //a:qq
+  local output_mtime2=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
+
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "output changed on null build"
+  fi
+
+  cat > a/qq.h <<EOF
+#define QQ 2
+EOF
+
+  run_bazel build --package_path=out/soong/workspace //a:qq
+  local output_mtime3=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
+
+  if [[ "$output_mtime1" == "$output_mtime3" ]]; then
+    fail "output not changed when included header changed"
+  fi
+}
+
+test_cc_correctness
diff --git a/tests/lib.sh b/tests/lib.sh
index 1bb2df9..7fd970a 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -85,6 +85,7 @@
   copy_directory build/soong
   copy_directory build/make/tools/rbcrun
 
+  symlink_directory prebuilts/sdk
   symlink_directory prebuilts/go
   symlink_directory prebuilts/build-tools
   symlink_directory prebuilts/clang/host
@@ -115,8 +116,10 @@
   copy_directory build/bazel
 
   symlink_directory prebuilts/bazel
+  symlink_directory prebuilts/clang
   symlink_directory prebuilts/jdk
   symlink_directory external/bazel-skylib
+  symlink_directory external/bazelbuild-rules_android
 
   symlink_file WORKSPACE
   symlink_file BUILD
@@ -136,4 +139,5 @@
 
 
 export ALLOW_MISSING_DEPENDENCIES=true
+export ALLOW_BP_UNDER_SYMLINKS=true
 warmup_mock_top
diff --git a/ui/build/build.go b/ui/build/build.go
index d261f89..aadf4af 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -18,6 +18,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"sync"
 	"text/template"
 
 	"android/soong/ui/metrics"
@@ -205,6 +206,8 @@
 		return
 	}
 
+	defer waitForDist(ctx)
+
 	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
 	checkProblematicFiles(ctx)
 
@@ -329,8 +332,18 @@
 	}
 }
 
+var distWaitGroup sync.WaitGroup
+
+// waitForDist waits for all backgrounded distGzipFile and distFile writes to finish
+func waitForDist(ctx Context) {
+	ctx.BeginTrace("soong_ui", "dist")
+	defer ctx.EndTrace()
+
+	distWaitGroup.Wait()
+}
+
 // distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures
-// are printed but non-fatal.
+// are printed but non-fatal. Uses the distWaitGroup func for backgrounding (optimization).
 func distGzipFile(ctx Context, config Config, src string, subDirs ...string) {
 	if !config.Dist() {
 		return
@@ -343,13 +356,17 @@
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
 	}
 
-	if err := gzipFileToDir(src, destDir); err != nil {
-		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
-	}
+	distWaitGroup.Add(1)
+	go func() {
+		defer distWaitGroup.Done()
+		if err := gzipFileToDir(src, destDir); err != nil {
+			ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
+		}
+	}()
 }
 
 // distFile writes a copy of src to the distDir if dist is enabled.  Failures are printed but
-// non-fatal.
+// non-fatal. Uses the distWaitGroup func for backgrounding (optimization).
 func distFile(ctx Context, config Config, src string, subDirs ...string) {
 	if !config.Dist() {
 		return
@@ -362,7 +379,11 @@
 		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
 	}
 
-	if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
-		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
-	}
+	distWaitGroup.Add(1)
+	go func() {
+		defer distWaitGroup.Done()
+		if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
+			ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
+		}
+	}()
 }
diff --git a/ui/build/config.go b/ui/build/config.go
index e271bfc..0092ff1 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -1223,6 +1223,21 @@
 	return "RBE_use_application_default_credentials", "true"
 }
 
+func (c *configImpl) IsGooglerEnvironment() bool {
+	cf := "ANDROID_BUILD_ENVIRONMENT_CONFIG"
+	if v, ok := c.environ.Get(cf); ok {
+		return v == "googler"
+	}
+	return false
+}
+
+func (c *configImpl) GoogleProdCredsExist() bool {
+	if _, err := exec.Command("/usr/bin/prodcertstatus", "--simple_output", "--nocheck_loas").Output(); err != nil {
+		return false
+	}
+	return true
+}
+
 func (c *configImpl) UseRemoteBuild() bool {
 	return c.UseGoma() || c.UseRBE()
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 11311f9..f56964c 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -205,6 +205,9 @@
 		"CCACHE_SLOPPINESS",
 		"CCACHE_BASEDIR",
 		"CCACHE_CPP2",
+
+		// LLVM compiler wrapper options
+		"TOOLCHAIN_RUSAGE_OUTPUT",
 	}
 
 	allVars := append(append([]string{
@@ -242,8 +245,6 @@
 		"BUILD_BROKEN_USES_BUILD_EXECUTABLE",
 		"BUILD_BROKEN_USES_BUILD_FUZZ_TEST",
 		"BUILD_BROKEN_USES_BUILD_HEADER_LIBRARY",
-		"BUILD_BROKEN_USES_BUILD_HOST_DALVIK_JAVA_LIBRARY",
-		"BUILD_BROKEN_USES_BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_HOST_EXECUTABLE",
 		"BUILD_BROKEN_USES_BUILD_HOST_JAVA_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_HOST_PREBUILT",
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 262de3d..4d6ad42 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -64,6 +64,7 @@
 	cacheParams := finder.CacheParams{
 		WorkingDirectory: dir,
 		RootDirs:         []string{"."},
+		FollowSymlinks:   config.environ.IsEnvTrue("ALLOW_BP_UNDER_SYMLINKS"),
 		ExcludeDirs:      []string{".git", ".repo"},
 		PruneFiles:       pruneFiles,
 		IncludeFiles: []string{
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 41de6bd..dab1a9b 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -169,6 +169,9 @@
 			"CCACHE_BASEDIR",
 			"CCACHE_CPP2",
 			"CCACHE_DIR",
+
+			// LLVM compiler wrapper options
+			"TOOLCHAIN_RUSAGE_OUTPUT",
 		}, config.BuildBrokenNinjaUsesEnvVars()...)...)
 	}
 
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 831a80f..b3092ea 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -31,18 +31,25 @@
 	LinuxOnlyPrebuilt bool
 }
 
+// These binaries can be run from $PATH, nonhermetically. There should be as
+// few as possible of these, since this means that the build depends on tools
+// that are not shipped in the source tree and whose behavior is therefore
+// unpredictable.
 var Allowed = PathConfig{
 	Symlink: true,
 	Log:     false,
 	Error:   false,
 }
 
+// This tool is specifically disallowed and calling it will result in an
+// "executable no found" error.
 var Forbidden = PathConfig{
 	Symlink: false,
 	Log:     true,
 	Error:   true,
 }
 
+// This tool is allowed, but access to it will be logged.
 var Log = PathConfig{
 	Symlink: true,
 	Log:     true,
@@ -52,13 +59,16 @@
 // The configuration used if the tool is not listed in the config below.
 // Currently this will create the symlink, but log and error when it's used. In
 // the future, I expect the symlink to be removed, and this will be equivalent
-// to Forbidden.
+// to Forbidden. This applies to every tool not specifically mentioned in the
+// configuration.
 var Missing = PathConfig{
 	Symlink: true,
 	Log:     true,
 	Error:   true,
 }
 
+// This is used for binaries for which we have prebuilt versions, but only for
+// Linux. Thus, their execution from $PATH is only allowed on Mac OS.
 var LinuxOnlyPrebuilt = PathConfig{
 	Symlink:           false,
 	Log:               true,
@@ -73,6 +83,8 @@
 	return Missing
 }
 
+// This list specifies whether a particular binary from $PATH is allowed to be
+// run during the build. For more documentation, see path_interposer.go .
 var Configuration = map[string]PathConfig{
 	"bash":    Allowed,
 	"dd":      Allowed,
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index 8f9a699..78d37b4 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -119,6 +119,7 @@
 }
 
 func stopRBE(ctx Context, config Config) {
+	defer checkProdCreds(ctx, config)
 	cmd := Command(ctx, config, "stopRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd), "-shutdown")
 	output, err := cmd.CombinedOutput()
 	if err != nil {
@@ -131,6 +132,15 @@
 	}
 }
 
+func checkProdCreds(ctx Context, config Config) {
+	if !config.IsGooglerEnvironment() || config.GoogleProdCredsExist() {
+		return
+	}
+	fmt.Fprintln(ctx.Writer, "")
+	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This will result in failing RBE builds in the future, see go/build-fast#authentication.\033[0m")
+	fmt.Fprintln(ctx.Writer, "")
+}
+
 // DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.
 // The protobuf file is created if RBE is enabled and the proxy service has
 // started. The proxy service is shutdown in order to dump the RBE metrics to the
diff --git a/ui/build/soong.go b/ui/build/soong.go
index c7f22f9..8992b4f 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,7 +15,9 @@
 package build
 
 import (
+	"errors"
 	"fmt"
+	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -491,10 +493,14 @@
 
 	ninja("bootstrap", "bootstrap.ninja", targets...)
 
-	var soongBuildMetrics *soong_metrics_proto.SoongBuildMetrics
 	if shouldCollectBuildSoongMetrics(config) {
 		soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
-		logSoongBuildMetrics(ctx, soongBuildMetrics)
+		if soongBuildMetrics != nil {
+			logSoongBuildMetrics(ctx, soongBuildMetrics)
+			if ctx.Metrics != nil {
+				ctx.Metrics.SetSoongBuildMetrics(soongBuildMetrics)
+			}
+		}
 	}
 
 	distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
@@ -504,9 +510,6 @@
 		distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong")
 	}
 
-	if shouldCollectBuildSoongMetrics(config) && ctx.Metrics != nil {
-		ctx.Metrics.SetSoongBuildMetrics(soongBuildMetrics)
-	}
 	if config.JsonModuleGraph() {
 		distGzipFile(ctx, config, config.ModuleGraphFile(), "soong")
 	}
@@ -538,8 +541,12 @@
 
 func loadSoongBuildMetrics(ctx Context, config Config) *soong_metrics_proto.SoongBuildMetrics {
 	soongBuildMetricsFile := filepath.Join(config.LogsDir(), "soong_build_metrics.pb")
-	buf, err := ioutil.ReadFile(soongBuildMetricsFile)
-	if err != nil {
+	buf, err := os.ReadFile(soongBuildMetricsFile)
+	if errors.Is(err, fs.ErrNotExist) {
+		// Soong may not have run during this invocation
+          ctx.Verbosef("Failed to read metrics file, %s: %s", soongBuildMetricsFile, err)
+		return nil
+	} else if err != nil {
 		ctx.Fatalf("Failed to load %s: %s", soongBuildMetricsFile, err)
 	}
 	soongBuildMetrics := &soong_metrics_proto.SoongBuildMetrics{}
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 69f5689..4bc713b 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -14,7 +14,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.27.1
+// 	protoc-gen-go v1.28.0
 // 	protoc        v3.9.1
 // source: metrics.proto
 
@@ -954,9 +954,9 @@
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The build system, eg. Soong or Make.
+	// The build system, e.g. Soong or Make.
 	BuildSystem *ModuleTypeInfo_BuildSystem `protobuf:"varint,1,opt,name=build_system,json=buildSystem,enum=soong_build_metrics.ModuleTypeInfo_BuildSystem,def=0" json:"build_system,omitempty"`
-	// The module type, eg. java_library, cc_binary, and etc.
+	// The module type, e.g. java_library, cc_binary, and etc.
 	ModuleType *string `protobuf:"bytes,2,opt,name=module_type,json=moduleType" json:"module_type,omitempty"`
 	// The number of logical modules.
 	NumOfModules *uint32 `protobuf:"varint,3,opt,name=num_of_modules,json=numOfModules" json:"num_of_modules,omitempty"`
@@ -1142,6 +1142,8 @@
 	MaxHeapSize *uint64 `protobuf:"varint,5,opt,name=max_heap_size,json=maxHeapSize" json:"max_heap_size,omitempty"`
 	// Runtime metrics for soong_build execution.
 	Events []*PerfInfo `protobuf:"bytes,6,rep,name=events" json:"events,omitempty"`
+	// Mixed Builds information
+	MixedBuildsInfo *MixedBuildsInfo `protobuf:"bytes,7,opt,name=mixed_builds_info,json=mixedBuildsInfo" json:"mixed_builds_info,omitempty"`
 }
 
 func (x *SoongBuildMetrics) Reset() {
@@ -1218,6 +1220,13 @@
 	return nil
 }
 
+func (x *SoongBuildMetrics) GetMixedBuildsInfo() *MixedBuildsInfo {
+	if x != nil {
+		return x.MixedBuildsInfo
+	}
+	return nil
+}
+
 type ExpConfigFetcher struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1287,6 +1296,63 @@
 	return 0
 }
 
+type MixedBuildsInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Modules that are enabled for Mixed Builds.
+	MixedBuildEnabledModules []string `protobuf:"bytes,1,rep,name=mixed_build_enabled_modules,json=mixedBuildEnabledModules" json:"mixed_build_enabled_modules,omitempty"`
+	// Modules that are not enabled for MixedBuilds
+	MixedBuildDisabledModules []string `protobuf:"bytes,2,rep,name=mixed_build_disabled_modules,json=mixedBuildDisabledModules" json:"mixed_build_disabled_modules,omitempty"`
+}
+
+func (x *MixedBuildsInfo) Reset() {
+	*x = MixedBuildsInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MixedBuildsInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MixedBuildsInfo) ProtoMessage() {}
+
+func (x *MixedBuildsInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[10]
+	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 MixedBuildsInfo.ProtoReflect.Descriptor instead.
+func (*MixedBuildsInfo) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *MixedBuildsInfo) GetMixedBuildEnabledModules() []string {
+	if x != nil {
+		return x.MixedBuildEnabledModules
+	}
+	return nil
+}
+
+func (x *MixedBuildsInfo) GetMixedBuildDisabledModules() []string {
+	if x != nil {
+		return x.MixedBuildDisabledModules
+	}
+	return nil
+}
+
 var File_metrics_proto protoreflect.FileDescriptor
 
 var file_metrics_proto_rawDesc = []byte{
@@ -1491,7 +1557,7 @@
 	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
 	0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65,
 	0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52,
-	0x04, 0x63, 0x75, 0x6a, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x42,
+	0x04, 0x63, 0x75, 0x6a, 0x73, 0x22, 0xcc, 0x02, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x42,
 	0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d,
 	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x6f,
 	0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74,
@@ -1507,22 +1573,36 @@
 	0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6f,
 	0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
 	0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e,
-	0x74, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x45, 0x78,
-	0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-	0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x34, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x43, 0x4f,
-	0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47,
-	0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x42, 0x28, 0x5a,
-	0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75,
-	0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-	0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x74, 0x73, 0x12, 0x50, 0x0a, 0x11, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c,
+	0x64, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e,
+	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2e, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73,
+	0x49, 0x6e, 0x66, 0x6f, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x73, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
+	0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
+	0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x34, 0x0a, 0x0c, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f,
+	0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4e, 0x46,
+	0x49, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x22,
+	0x91, 0x01, 0x0a, 0x0f, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x1b, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x18, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42,
+	0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c,
+	0x64, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x19, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42,
+	0x75, 0x69, 0x6c, 0x64, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x73, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f,
+	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
@@ -1538,7 +1618,7 @@
 }
 
 var file_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
-var file_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
+var file_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
 var file_metrics_proto_goTypes = []interface{}{
 	(MetricsBase_BuildVariant)(0),       // 0: soong_build_metrics.MetricsBase.BuildVariant
 	(MetricsBase_Arch)(0),               // 1: soong_build_metrics.MetricsBase.Arch
@@ -1554,6 +1634,7 @@
 	(*CriticalUserJourneysMetrics)(nil), // 11: soong_build_metrics.CriticalUserJourneysMetrics
 	(*SoongBuildMetrics)(nil),           // 12: soong_build_metrics.SoongBuildMetrics
 	(*ExpConfigFetcher)(nil),            // 13: soong_build_metrics.ExpConfigFetcher
+	(*MixedBuildsInfo)(nil),             // 14: soong_build_metrics.MixedBuildsInfo
 }
 var file_metrics_proto_depIdxs = []int32{
 	0,  // 0: soong_build_metrics.MetricsBase.target_build_variant:type_name -> soong_build_metrics.MetricsBase.BuildVariant
@@ -1575,12 +1656,13 @@
 	4,  // 16: soong_build_metrics.CriticalUserJourneyMetrics.metrics:type_name -> soong_build_metrics.MetricsBase
 	10, // 17: soong_build_metrics.CriticalUserJourneysMetrics.cujs:type_name -> soong_build_metrics.CriticalUserJourneyMetrics
 	7,  // 18: soong_build_metrics.SoongBuildMetrics.events:type_name -> soong_build_metrics.PerfInfo
-	3,  // 19: soong_build_metrics.ExpConfigFetcher.status:type_name -> soong_build_metrics.ExpConfigFetcher.ConfigStatus
-	20, // [20:20] is the sub-list for method output_type
-	20, // [20:20] is the sub-list for method input_type
-	20, // [20:20] is the sub-list for extension type_name
-	20, // [20:20] is the sub-list for extension extendee
-	0,  // [0:20] is the sub-list for field type_name
+	14, // 19: soong_build_metrics.SoongBuildMetrics.mixed_builds_info:type_name -> soong_build_metrics.MixedBuildsInfo
+	3,  // 20: soong_build_metrics.ExpConfigFetcher.status:type_name -> soong_build_metrics.ExpConfigFetcher.ConfigStatus
+	21, // [21:21] is the sub-list for method output_type
+	21, // [21:21] is the sub-list for method input_type
+	21, // [21:21] is the sub-list for extension type_name
+	21, // [21:21] is the sub-list for extension extendee
+	0,  // [0:21] is the sub-list for field type_name
 }
 
 func init() { file_metrics_proto_init() }
@@ -1709,6 +1791,18 @@
 				return nil
 			}
 		}
+		file_metrics_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MixedBuildsInfo); 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{
@@ -1716,7 +1810,7 @@
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_metrics_proto_rawDesc,
 			NumEnums:      4,
-			NumMessages:   10,
+			NumMessages:   11,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 814eb67..51dd523 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -200,10 +200,10 @@
     SOONG = 1;
     MAKE = 2;
   }
-  // The build system, eg. Soong or Make.
+  // The build system, e.g. Soong or Make.
   optional BuildSystem build_system = 1 [default = UNKNOWN];
 
-  // The module type, eg. java_library, cc_binary, and etc.
+  // The module type, e.g. java_library, cc_binary, and etc.
   optional string module_type = 2;
 
   // The number of logical modules.
@@ -241,6 +241,9 @@
 
   // Runtime metrics for soong_build execution.
   repeated PerfInfo events = 6;
+
+  // Mixed Builds information
+  optional MixedBuildsInfo mixed_builds_info = 7;
 }
 
 message ExpConfigFetcher {
@@ -261,3 +264,25 @@
   // Time, in microseconds, taken by the expconfigfetcher
   optional uint64 micros = 3;
 }
+
+message MixedBuildsInfo{
+  // Modules may be listed below as both enabled for Mixed Builds
+  // and disabled for Mixed Builds. This implies that some variants
+  // of the module are handled by Bazel in a Mixed Build, and other
+  // variants of the same module are handled by Soong.
+
+  // Modules that are enabled for Mixed Builds.
+  repeated string mixed_build_enabled_modules = 1;
+
+  // Modules that are not currently eligible to be handled
+  // by Bazel in a Mixed Build.
+  // Note that not all modules exempt from Bazel handling are
+  // listed. This list includes only modules which are of a
+  // Mixed-Build supported module type but are nevertheless not
+  // handled by Bazel. This may occur due to being present in
+  // the mixed build denylist, or as part of an unsupported
+  // mixed build variant type such as Windows.
+
+  // Modules that are not enabled for MixedBuilds
+  repeated string mixed_build_disabled_modules = 2;
+}