[automerger skipped] [conflict] Add additional directories from which env config can be loaded am: ef073be177 am: cc5020c964 am: 8f012ef4bf am: 749dacacda am: 3b9f9fe1eb am: fbd40f18a9 am: d43d94c657 -s ours

am skip reason: Merged-In I9af83687d4eaeee1ffb0f88a750cfeb7c6d2bafb with SHA-1 7b7dca4316 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/build/soong/+/18915321

Change-Id: Ia5ef281e3b4565a82a9aa2f758f5964aeda3e9d2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 8f7f3e2..63de015 100644
--- a/Android.bp
+++ b/Android.bp
@@ -46,28 +46,11 @@
 // C static libraries extracted from the gcc toolchain
 //
 
-toolchain_library {
-    name: "libwinpthread",
-    host_supported: true,
-    enabled: false,
-    target: {
-        windows: {
-            enabled: true,
-        },
-        windows_x86: {
-            src: "prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/lib32/libwinpthread.a",
-        },
-        windows_x86_64: {
-            src: "prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8/x86_64-w64-mingw32/lib/libwinpthread.a",
-        },
-    },
-    notice: ":mingw-libwinpthread-notice",
-}
-
 kernel_headers {
     name: "device_kernel_headers",
     vendor: true,
     recovery_available: true,
+    min_sdk_version: "apex_inherit",
 }
 
 cc_genrule {
@@ -78,6 +61,9 @@
         linux_bionic: {
             enabled: true,
         },
+        linux_musl: {
+            enabled: false,
+        },
         linux_glibc: {
             enabled: false,
         },
@@ -92,13 +78,16 @@
 }
 
 cc_genrule {
-    name: "host_bionic_linker_flags",
+    name: "host_bionic_linker_script",
     host_supported: true,
     device_supported: false,
     target: {
         linux_bionic: {
             enabled: true,
         },
+        linux_musl: {
+            enabled: false,
+        },
         linux_glibc: {
             enabled: false,
         },
@@ -107,12 +96,37 @@
         },
     },
     tools: ["extract_linker"],
-    cmd: "$(location) -f $(out) $(in)",
+    cmd: "$(location) -T $(out) $(in)",
     srcs: [":linker"],
-    out: ["linker.flags"],
+    out: ["linker.script"],
 }
 
 // Instantiate the dex_bootjars singleton module.
 dex_bootjars {
     name: "dex_bootjars",
 }
+
+// Pseudo-test that's run on checkbuilds to ensure that get_clang_version can
+// parse cc/config/global.go.
+genrule {
+    name: "get_clang_version_test",
+    cmd: "$(location get_clang_version) > $(out)",
+    tools: ["get_clang_version"],
+    srcs: ["cc/config/global.go"],
+    out: ["clang-prebuilts-version.txt"],
+}
+
+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/METADATA b/METADATA
new file mode 100644
index 0000000..caa2a95
--- /dev/null
+++ b/METADATA
@@ -0,0 +1 @@
+name: "Android"
diff --git a/OWNERS b/OWNERS
index e851bf7..0662016 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,18 +1,31 @@
 # This file is included by several other projects as the list of people
 # approving build related projects.
 
-ahumesky@google.com
+# AMER
+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
-jingwen@google.com
+jobredeaux@google.com
 joeo@google.com
-jungjw@google.com
-lberki@google.com
-ruperts@google.com
+lamontjones@google.com
+spandandas@google.com
+tradical@google.com
+usta@google.com
+vinhdaitran@google.com
+weiwli@google.com
+yudiliu@google.com
 
-# To expedite LON reviews
+# APAC
+jingwen@google.com
+
+# EMEA
 hansson@google.com
+lberki@google.com
 paulduffin@google.com
diff --git a/README.md b/README.md
index b7e93f4..caffd3d 100644
--- a/README.md
+++ b/README.md
@@ -33,8 +33,9 @@
 Every module must have a `name` property, and the value must be unique across
 all Android.bp files.
 
-For a list of valid module types and their properties see
-[$OUT_DIR/soong/docs/soong_build.html](https://ci.android.com/builds/latest/branches/aosp-build-tools/targets/linux/view/soong_build.html).
+The list of valid module types and their properties can be generated by calling
+`m soong_docs`. It will be written to `$OUT_DIR/soong/docs/soong_build.html`.
+This list for the current version of Soong can be found [here](https://ci.android.com/builds/latest/branches/aosp-build-tools/targets/linux/view/soong_build.html).
 
 ### File lists
 
@@ -187,7 +188,7 @@
 
 #### Namespaces
 
-A presense of the `soong_namespace {..}` in an Android.bp file defines a
+The presence of the `soong_namespace {..}` in an Android.bp file defines a
 **namespace**. For instance, having
 
 ```
@@ -213,8 +214,8 @@
 
 A module name's **scope** is the smallest namespace containing it. Suppose a
 source tree has `device/my` and `device/my/display` namespaces. If `libfoo`
-module is defined in `device/co/display/lib/Android.bp`, its namespace is
-`device/co/display`.
+module is defined in `device/my/display/lib/Android.bp`, its namespace is
+`device/my/display`.
 
 The name uniqueness thus means that module's name is unique within its scope. In
 other words, "//_scope_:_name_" is globally unique module reference, e.g,
@@ -451,15 +452,9 @@
 
 The values of the variables can be set from a product's `BoardConfig.mk` file:
 ```
-SOONG_CONFIG_NAMESPACES += acme
-SOONG_CONFIG_acme += \
-    board \
-    feature \
-    width \
-
-SOONG_CONFIG_acme_board := soc_a
-SOONG_CONFIG_acme_feature := true
-SOONG_CONFIG_acme_width := 200
+$(call soong_config_set,acme,board,soc_a)
+$(call soong_config_set,acme,feature,true)
+$(call soong_config_set,acme,width,200)
 ```
 
 The `acme_cc_defaults` module type can be used anywhere after the definition in
@@ -555,6 +550,26 @@
 and produces build rules.  The build rules are collected by blueprint and
 written to a [ninja](http://ninja-build.org) build file.
 
+## Environment Variables Config File
+
+Soong can optionally load environment variables from a pre-specified
+configuration file during startup. These environment variables can be used
+to control the behavior of the build. For example, these variables can determine
+whether remote-execution should be used for the build or not.
+
+The `ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR` environment variable specifies the
+directory in which the config file should be searched for. The
+`ANDROID_BUILD_ENVIRONMENT_CONFIG` variable determines the name of the config
+file to be searched for within the config directory. For example, the following
+build comand will load `ENV_VAR_1` and `ENV_VAR_2` environment variables from
+the `example_config.json` file inside the `build/soong` directory.
+
+```
+ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR=build/soong \
+  ANDROID_BUILD_ENVIRONMENT_CONFIG=example_config \
+  build/soong/soong_ui.bash
+```
+
 ## Other documentation
 
 * [Best Practices](docs/best_practices.md)
@@ -565,27 +580,41 @@
 
 ## Developing for Soong
 
-To load Soong code in a Go-aware IDE, create a directory outside your android tree and then:
-```bash
-apt install bindfs
-export GOPATH=<path to the directory you created>
-build/soong/scripts/setup_go_workspace_for_soong.sh
-```
+To load the code of Soong in IntelliJ:
 
-This will bind mount the Soong source directories into the directory in the layout expected by
-the IDE.
-
+* File -> Open, open the `build/soong` directory. It will be opened as a new
+  project.
+* File -> Settings, then Languages & Frameworks -> Go -> GOROOT, then set it to
+  `prebuilts/go/linux-x86`
+* File -> Project Structure, then, Project Settings -> Modules, then Add
+  Content Root, then add the `build/blueprint` directory.
+* Optional: also add the `external/golang-protobuf` directory. In practice,
+  IntelliJ seems to work well enough without this, too.
 ### Running Soong in a debugger
 
-To run the soong_build process in a debugger, install `dlv` and then start the build with
-`SOONG_DELVE=<listen addr>` in the environment.
+To make `soong_build` wait for a debugger connection, install `dlv` and then
+start the build with `SOONG_DELVE=<listen addr>` in the environment.
 For example:
 ```bash
-SOONG_DELVE=:1234 m nothing
+SOONG_DELVE=:5006 m nothing
 ```
-and then in another terminal:
+
+To make `soong_ui` wait for a debugger connection, use the `SOONG_UI_DELVE`
+variable:
+
 ```
-dlv connect :1234
+SOONG_UI_DELVE=:5006 m nothing
+```
+
+
+setting or unsetting `SOONG_DELVE` causes a recompilation of `soong_build`. This
+is because in order to debug the binary, it needs to be built with debug
+symbols.
+
+To test the debugger connection, run this command:
+
+```
+dlv connect :5006
 ```
 
 If you see an error:
@@ -600,6 +629,21 @@
 sudo sysctl -w kernel.yama.ptrace_scope=0
 ```
 
+To connect to the process using IntelliJ:
+
+* Run -> Edit Configurations...
+* Choose "Go Remote" on the left
+* Click on the "+" buttion on the top-left
+* Give it a nice name and set "Host" to localhost and "Port" to the port in the
+  environment variable
+
+Debugging works far worse than debugging Java, but is sometimes useful.
+
+Sometimes the `dlv` process hangs on connection. A symptom of this is `dlv`
+spinning a core or two. In that case, `kill -9` `dlv` and try again.
+Anecdotally, it _feels_ like waiting a minute after the start of `soong_build`
+helps.
+
 ## Contact
 
 Email android-building@googlegroups.com (external) for any questions, or see
diff --git a/android/Android.bp b/android/Android.bp
index 5d0f2b9..d583703 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -8,6 +8,7 @@
     deps: [
         "blueprint",
         "blueprint-bootstrap",
+        "blueprint-metrics",
         "sbox_proto",
         "soong",
         "soong-android-soongconfig",
@@ -16,7 +17,15 @@
         "soong-remoteexec",
         "soong-response",
         "soong-shared",
+        "soong-starlark-format",
         "soong-ui-metrics_proto",
+        "soong-android-allowlists",
+
+        "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
+
+        // Only used for tests.
+        "androidmk-parser",
     ],
     srcs: [
         "androidmk.go",
@@ -27,7 +36,9 @@
         "bazel.go",
         "bazel_handler.go",
         "bazel_paths.go",
+        "buildinfo_prop.go",
         "config.go",
+        "config_bp2build.go",
         "csuite_config.go",
         "deapexer.go",
         "defaults.go",
@@ -42,6 +53,7 @@
         "image.go",
         "license.go",
         "license_kind.go",
+        "license_metadata.go",
         "license_sdk_member.go",
         "licenses.go",
         "makefile_goal.go",
@@ -64,7 +76,6 @@
         "prebuilt.go",
         "prebuilt_build_tool.go",
         "proto.go",
-        "queryview.go",
         "register.go",
         "rule_builder.go",
         "sandbox.go",
@@ -79,7 +90,6 @@
         "util.go",
         "variable.go",
         "visibility.go",
-        "writedocs.go",
     ],
     testSrcs: [
         "android_test.go",
@@ -89,6 +99,7 @@
         "bazel_handler_test.go",
         "bazel_test.go",
         "config_test.go",
+        "config_bp2build_test.go",
         "csuite_config_test.go",
         "defaults_test.go",
         "depset_test.go",
@@ -110,6 +121,8 @@
         "paths_test.go",
         "prebuilt_test.go",
         "rule_builder_test.go",
+        "sdk_version_test.go",
+        "sdk_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
         "util_test.go",
diff --git a/cmd/host_bionic_inject/Android.bp b/android/allowlists/Android.bp
similarity index 73%
copy from cmd/host_bionic_inject/Android.bp
copy to android/allowlists/Android.bp
index 16bc179..05cffc1 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/android/allowlists/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -16,9 +16,10 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+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..4fd4f13
--- /dev/null
+++ b/android/allowlists/allowlists.go
@@ -0,0 +1,418 @@
+// 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/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,
+		"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,
+	}
+
+	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
+
+		// 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
+
+		// 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 557e7ba..5c715b4 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -25,15 +25,16 @@
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
+	"runtime"
 	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
+	"github.com/google/blueprint/pathtools"
 )
 
 func init() {
@@ -113,7 +114,7 @@
 	// If true, the module is skipped and does not appear on the final Android-<product name>.mk
 	// file. Useful when a module needs to be skipped conditionally.
 	Disabled bool
-	// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_prebuilt.mk
+	// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk
 	// If not set, $(BUILD_SYSTEM)/prebuilt.mk is used.
 	Include string
 	// Required modules that need to be built and included in the final build output when building
@@ -147,6 +148,14 @@
 	// without worrying about the variables being mixed up in the actual mk file.
 	// 3. Makes troubleshooting and spotting errors easier.
 	entryOrder []string
+
+	// Provides data typically stored by Context objects that are commonly needed by
+	//AndroidMkEntries objects.
+	entryContext AndroidMkEntriesContext
+}
+
+type AndroidMkEntriesContext interface {
+	Config() Config
 }
 
 type AndroidMkExtraEntriesContext interface {
@@ -407,10 +416,19 @@
 				}
 			}
 
+			ext := filepath.Ext(dest)
+			suffix := ""
 			if dist.Suffix != nil {
-				ext := filepath.Ext(dest)
-				suffix := *dist.Suffix
-				dest = strings.TrimSuffix(dest, ext) + suffix + ext
+				suffix = *dist.Suffix
+			}
+
+			productString := ""
+			if dist.Append_artifact_with_product != nil && *dist.Append_artifact_with_product {
+				productString = fmt.Sprintf("_%s", a.entryContext.Config().DeviceProduct())
+			}
+
+			if suffix != "" || productString != "" {
+				dest = strings.TrimSuffix(dest, ext) + suffix + productString + ext
 			}
 
 			if dist.Dir != nil {
@@ -473,12 +491,15 @@
 	ModuleDir(module blueprint.Module) string
 	Config() Config
 	ModuleProvider(module blueprint.Module, provider blueprint.ProviderKey) interface{}
+	ModuleHasProvider(module blueprint.Module, provider blueprint.ProviderKey) bool
 }
 
 func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
+	a.entryContext = ctx
 	a.EntryMap = make(map[string][]string)
-	amod := mod.(Module).base()
-	name := amod.BaseModuleName()
+	amod := mod.(Module)
+	base := amod.base()
+	name := base.BaseModuleName()
 	if a.OverrideName != "" {
 		name = a.OverrideName
 	}
@@ -486,9 +507,9 @@
 	if a.Include == "" {
 		a.Include = "$(BUILD_PREBUILT)"
 	}
-	a.Required = append(a.Required, amod.commonProperties.Required...)
-	a.Host_required = append(a.Host_required, amod.commonProperties.Host_required...)
-	a.Target_required = append(a.Target_required, amod.commonProperties.Target_required...)
+	a.Required = append(a.Required, amod.RequiredModuleNames()...)
+	a.Host_required = append(a.Host_required, amod.HostRequiredModuleNames()...)
+	a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
 
 	for _, distString := range a.GetDistForGoals(mod) {
 		fmt.Fprintf(&a.header, distString)
@@ -499,14 +520,14 @@
 	// Collect make variable assignment entries.
 	a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
 	a.SetString("LOCAL_MODULE", name+a.SubName)
-	a.AddStrings("LOCAL_LICENSE_KINDS", amod.commonProperties.Effective_license_kinds...)
-	a.AddStrings("LOCAL_LICENSE_CONDITIONS", amod.commonProperties.Effective_license_conditions...)
-	a.AddStrings("LOCAL_NOTICE_FILE", amod.commonProperties.Effective_license_text.Strings()...)
+	a.AddStrings("LOCAL_LICENSE_KINDS", base.commonProperties.Effective_license_kinds...)
+	a.AddStrings("LOCAL_LICENSE_CONDITIONS", base.commonProperties.Effective_license_conditions...)
+	a.AddStrings("LOCAL_NOTICE_FILE", base.commonProperties.Effective_license_text.Strings()...)
 	// TODO(b/151177513): Does this code need to set LOCAL_MODULE_IS_CONTAINER ?
-	if amod.commonProperties.Effective_package_name != nil {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *amod.commonProperties.Effective_package_name)
-	} else if len(amod.commonProperties.Effective_licenses) > 0 {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(amod.commonProperties.Effective_licenses, " "))
+	if base.commonProperties.Effective_package_name != nil {
+		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *base.commonProperties.Effective_package_name)
+	} else if len(base.commonProperties.Effective_licenses) > 0 {
+		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(base.commonProperties.Effective_licenses, " "))
 	}
 	a.SetString("LOCAL_MODULE_CLASS", a.Class)
 	a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
@@ -514,31 +535,41 @@
 	a.AddStrings("LOCAL_HOST_REQUIRED_MODULES", a.Host_required...)
 	a.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", a.Target_required...)
 
+	// If the install rule was generated by Soong tell Make about it.
+	if len(base.katiInstalls) > 0 {
+		// Assume the primary install file is last since it probably needs to depend on any other
+		// installed files.  If that is not the case we can add a method to specify the primary
+		// installed file.
+		a.SetPath("LOCAL_SOONG_INSTALLED_MODULE", base.katiInstalls[len(base.katiInstalls)-1].to)
+		a.SetString("LOCAL_SOONG_INSTALL_PAIRS", base.katiInstalls.BuiltInstalled())
+		a.SetPaths("LOCAL_SOONG_INSTALL_SYMLINKS", base.katiSymlinks.InstallPaths().Paths())
+	}
+
 	if am, ok := mod.(ApexModule); ok {
 		a.SetBoolIfTrue("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", am.NotAvailableForPlatform())
 	}
 
-	archStr := amod.Arch().ArchType.String()
+	archStr := base.Arch().ArchType.String()
 	host := false
-	switch amod.Os().Class {
+	switch base.Os().Class {
 	case Host:
-		if amod.Target().HostCross {
+		if base.Target().HostCross {
 			// Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common.
-			if amod.Arch().ArchType != Common {
+			if base.Arch().ArchType != Common {
 				a.SetString("LOCAL_MODULE_HOST_CROSS_ARCH", archStr)
 			}
 		} else {
 			// Make cannot identify LOCAL_MODULE_HOST_ARCH:= common.
-			if amod.Arch().ArchType != Common {
+			if base.Arch().ArchType != Common {
 				a.SetString("LOCAL_MODULE_HOST_ARCH", archStr)
 			}
 		}
 		host = true
 	case Device:
 		// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
-		if amod.Arch().ArchType != Common {
-			if amod.Target().NativeBridge {
-				hostArchStr := amod.Target().NativeBridgeHostArchName
+		if base.Arch().ArchType != Common {
+			if base.Target().NativeBridge {
+				hostArchStr := base.Target().NativeBridgeHostArchName
 				if hostArchStr != "" {
 					a.SetString("LOCAL_MODULE_TARGET_ARCH", hostArchStr)
 				}
@@ -547,31 +578,31 @@
 			}
 		}
 
-		if !amod.InRamdisk() && !amod.InVendorRamdisk() {
-			a.AddPaths("LOCAL_FULL_INIT_RC", amod.initRcPaths)
+		if !base.InVendorRamdisk() {
+			a.AddPaths("LOCAL_FULL_INIT_RC", base.initRcPaths)
 		}
-		if len(amod.vintfFragmentsPaths) > 0 {
-			a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", amod.vintfFragmentsPaths)
+		if len(base.vintfFragmentsPaths) > 0 {
+			a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", base.vintfFragmentsPaths)
 		}
-		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(amod.commonProperties.Proprietary))
-		if Bool(amod.commonProperties.Vendor) || Bool(amod.commonProperties.Soc_specific) {
+		a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(base.commonProperties.Proprietary))
+		if Bool(base.commonProperties.Vendor) || Bool(base.commonProperties.Soc_specific) {
 			a.SetString("LOCAL_VENDOR_MODULE", "true")
 		}
-		a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(amod.commonProperties.Device_specific))
-		a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(amod.commonProperties.Product_specific))
-		a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(amod.commonProperties.System_ext_specific))
-		if amod.commonProperties.Owner != nil {
-			a.SetString("LOCAL_MODULE_OWNER", *amod.commonProperties.Owner)
+		a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(base.commonProperties.Device_specific))
+		a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(base.commonProperties.Product_specific))
+		a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(base.commonProperties.System_ext_specific))
+		if base.commonProperties.Owner != nil {
+			a.SetString("LOCAL_MODULE_OWNER", *base.commonProperties.Owner)
 		}
 	}
 
-	if len(amod.noticeFiles) > 0 {
-		a.SetString("LOCAL_NOTICE_FILE", strings.Join(amod.noticeFiles.Strings(), " "))
+	if len(base.noticeFiles) > 0 {
+		a.SetString("LOCAL_NOTICE_FILE", strings.Join(base.noticeFiles.Strings(), " "))
 	}
 
 	if host {
-		makeOs := amod.Os().String()
-		if amod.Os() == Linux || amod.Os() == LinuxBionic {
+		makeOs := base.Os().String()
+		if base.Os() == Linux || base.Os() == LinuxBionic || base.Os() == LinuxMusl {
 			makeOs = "linux"
 		}
 		a.SetString("LOCAL_MODULE_HOST_OS", makeOs)
@@ -579,10 +610,10 @@
 	}
 
 	prefix := ""
-	if amod.ArchSpecific() {
-		switch amod.Os().Class {
+	if base.ArchSpecific() {
+		switch base.Os().Class {
 		case Host:
-			if amod.Target().HostCross {
+			if base.Target().HostCross {
 				prefix = "HOST_CROSS_"
 			} else {
 				prefix = "HOST_"
@@ -592,11 +623,16 @@
 
 		}
 
-		if amod.Arch().ArchType != ctx.Config().Targets[amod.Os()][0].Arch.ArchType {
+		if base.Arch().ArchType != ctx.Config().Targets[base.Os()][0].Arch.ArchType {
 			prefix = "2ND_" + prefix
 		}
 	}
 
+	if ctx.ModuleHasProvider(mod, LicenseMetadataProvider) {
+		licenseMetadata := ctx.ModuleProvider(mod, LicenseMetadataProvider).(*LicenseMetadataInfo)
+		a.SetPath("LOCAL_SOONG_LICENSE_METADATA", licenseMetadata.LicenseMetadataPath)
+	}
+
 	extraCtx := &androidMkExtraEntriesContext{
 		ctx: ctx,
 		mod: mod,
@@ -678,7 +714,7 @@
 	})
 }
 
-func translateAndroidMk(ctx SingletonContext, mkFile string, mods []blueprint.Module) error {
+func translateAndroidMk(ctx SingletonContext, absMkFile string, mods []blueprint.Module) error {
 	buf := &bytes.Buffer{}
 
 	fmt.Fprintln(buf, "LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))")
@@ -687,7 +723,7 @@
 	for _, mod := range mods {
 		err := translateAndroidMkModule(ctx, buf, mod)
 		if err != nil {
-			os.Remove(mkFile)
+			os.Remove(absMkFile)
 			return err
 		}
 
@@ -707,27 +743,7 @@
 		fmt.Fprintf(buf, "STATS.SOONG_MODULE_TYPE.%s := %d\n", mod_type, typeStats[mod_type])
 	}
 
-	// Don't write to the file if it hasn't changed
-	if _, err := os.Stat(absolutePath(mkFile)); !os.IsNotExist(err) {
-		if data, err := ioutil.ReadFile(absolutePath(mkFile)); err == nil {
-			matches := buf.Len() == len(data)
-
-			if matches {
-				for i, value := range buf.Bytes() {
-					if value != data[i] {
-						matches = false
-						break
-					}
-				}
-			}
-
-			if matches {
-				return nil
-			}
-		}
-	}
-
-	return ioutil.WriteFile(absolutePath(mkFile), buf.Bytes(), 0666)
+	return pathtools.WriteFileIfChanged(absMkFile, buf.Bytes(), 0666)
 }
 
 func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.Module) error {
@@ -836,6 +852,7 @@
 		case "*aidl.aidlApi": // writes non-custom before adding .phony
 		case "*aidl.aidlMapping": // writes non-custom before adding .phony
 		case "*android.customModule": // appears in tests only
+		case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
 		case "*apex.apexBundle": // license properties written
 		case "*bpf.bpf": // license properties written (both for module and objs)
 		case "*genrule.Module": // writes non-custom before adding .phony
@@ -845,7 +862,7 @@
 		case "*selinux.selinuxContextsModule": // license properties written
 		case "*sysprop.syspropLibrary": // license properties written
 		default:
-			if ctx.Config().IsEnvTrue("ANDROID_REQUIRE_LICENSES") {
+			if !ctx.Config().IsEnvFalse("ANDROID_REQUIRE_LICENSES") {
 				return fmt.Errorf("custom make rules not allowed for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), ctx.ModuleName(mod))
 			}
 		}
@@ -894,6 +911,10 @@
 	return nil
 }
 
+func ShouldSkipAndroidMkProcessing(module Module) bool {
+	return shouldSkipAndroidMkProcessing(module.base())
+}
+
 func shouldSkipAndroidMkProcessing(module *ModuleBase) bool {
 	if !module.commonProperties.NamespaceExportedToMake {
 		// TODO(jeffrygaston) do we want to validate that there are no modules being
@@ -901,6 +922,18 @@
 		return true
 	}
 
+	// On Mac, only expose host darwin modules to Make, as that's all we claim to support.
+	// In reality, some of them depend on device-built (Java) modules, so we can't disable all
+	// device modules in Soong, but we can hide them from Make (and thus the build user interface)
+	if runtime.GOOS == "darwin" && module.Os() != Darwin {
+		return true
+	}
+
+	// Only expose the primary Darwin target, as Make does not understand Darwin+Arm64
+	if module.Os() == Darwin && module.Target().HostCross {
+		return true
+	}
+
 	return !module.Enabled() ||
 		module.commonProperties.HideFromMake ||
 		// Make does not understand LinuxBionic
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 8eda9b2..caf11f1 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"io"
 	"reflect"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -147,6 +148,9 @@
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
 			ctx.RegisterModuleType("custom", customModuleFactory)
 		}),
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.DeviceProduct = proptools.StringPtr("bar")
+		}),
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
@@ -155,6 +159,11 @@
 }
 
 func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
+	if runtime.GOOS == "darwin" {
+		// Device modules are not exported on Mac, so this test doesn't work.
+		t.SkipNow()
+	}
+
 	bp := `
 	custom {
 		name: "foo",
@@ -394,6 +403,25 @@
 			},
 		})
 
+	testHelper(t, "append-artifact-with-product", `
+			custom {
+				name: "foo",
+				dist: {
+					targets: ["my_goal"],
+					append_artifact_with_product: true,
+				}
+			}
+`, &distContributions{
+		copiesForGoals: []*copiesForGoals{
+			{
+				goals: "my_goal",
+				copies: []distCopy{
+					distCopyForTest("one.out", "one_bar.out"),
+				},
+			},
+		},
+	})
+
 	testHelper(t, "dists-with-tag", `
 			custom {
 				name: "foo",
diff --git a/android/apex.go b/android/apex.go
index 4618fe9..63cdaaf 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -54,8 +54,9 @@
 	// True if this module comes from an updatable apexBundle.
 	Updatable bool
 
-	// The list of SDK modules that the containing apexBundle depends on.
-	RequiredSdks SdkRefs
+	// True if this module can use private platform APIs. Only non-updatable APEX can set this
+	// to true.
+	UsePlatformApis bool
 
 	// 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
@@ -87,16 +88,25 @@
 
 var ApexInfoProvider = blueprint.NewMutatorProvider(ApexInfo{}, "apex")
 
+func (i ApexInfo) AddJSONData(d *map[string]interface{}) {
+	(*d)["Apex"] = map[string]interface{}{
+		"ApexVariationName": i.ApexVariationName,
+		"MinSdkVersion":     i.MinSdkVersion,
+		"InApexModules":     i.InApexModules,
+		"InApexVariants":    i.InApexVariants,
+		"ForPrebuiltApex":   i.ForPrebuiltApex,
+	}
+}
+
 // mergedName gives the name of the alias variation that will be used when multiple apex variations
 // of a module can be deduped into one variation. For example, if libfoo is included in both apex.a
 // and apex.b, and if the two APEXes have the same min_sdk_version (say 29), then libfoo doesn't
 // have to be built twice, but only once. In that case, the two apex variations apex.a and apex.b
-// are configured to have the same alias variation named apex29.
+// are configured to have the same alias variation named apex29. Whether platform APIs is allowed
+// or not also matters; if two APEXes don't have the same allowance, they get different names and
+// 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
 }
 
@@ -527,6 +537,9 @@
 			merged[index].InApexModules = append(merged[index].InApexModules, apexInfo.InApexModules...)
 			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
 			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
+			// Platform APIs is allowed for this module only when all APEXes containing
+			// the module are with `use_platform_apis: true`.
+			merged[index].UsePlatformApis = merged[index].UsePlatformApis && apexInfo.UsePlatformApis
 		} else {
 			seen[mergedName] = len(merged)
 			apexInfo.ApexVariationName = mergedName
@@ -831,7 +844,6 @@
 	}
 	return list
 }(map[string]int{
-	"adbd":                                                     30,
 	"android.net.ipsec.ike":                                    30,
 	"androidx.annotation_annotation-nodeps":                    29,
 	"androidx.arch.core_core-common-nodeps":                    29,
@@ -856,24 +868,8 @@
 	"kotlinx-coroutines-android-nodeps":                        30,
 	"kotlinx-coroutines-core":                                  28,
 	"kotlinx-coroutines-core-nodeps":                           30,
-	"libadb_crypto":                                            30,
-	"libadb_pairing_auth":                                      30,
-	"libadb_pairing_connection":                                30,
-	"libadb_pairing_server":                                    30,
-	"libadb_protos":                                            30,
-	"libadb_tls_connection":                                    30,
-	"libadbconnection_client":                                  30,
-	"libadbconnection_server":                                  30,
-	"libadbd_core":                                             30,
-	"libadbd_services":                                         30,
-	"libadbd":                                                  30,
-	"libapp_processes_protos_lite":                             30,
-	"libasyncio":                                               30,
 	"libbrotli":                                                30,
-	"libbuildversion":                                          30,
 	"libcrypto_static":                                         30,
-	"libcrypto_utils":                                          30,
-	"libdiagnose_usb":                                          30,
 	"libeigen":                                                 30,
 	"liblz4":                                                   30,
 	"libmdnssd":                                                30,
@@ -883,7 +879,6 @@
 	"libprocpartition":                                         30,
 	"libprotobuf-java-lite":                                    30,
 	"libprotoutil":                                             30,
-	"libsync":                                                  30,
 	"libtextclassifier_hash_headers":                           30,
 	"libtextclassifier_hash_static":                            30,
 	"libtflite_kernel_utils":                                   30,
@@ -903,16 +898,18 @@
 //
 // Return true if the `to` module should be visited, false otherwise.
 type PayloadDepsCallback func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool
+type WalkPayloadDepsFunc func(ctx ModuleContext, do PayloadDepsCallback)
 
-// UpdatableModule represents updatable APEX/APK
-type UpdatableModule interface {
+// ModuleWithMinSdkVersionCheck represents a module that implements min_sdk_version checks
+type ModuleWithMinSdkVersionCheck interface {
 	Module
-	WalkPayloadDeps(ctx ModuleContext, do PayloadDepsCallback)
+	MinSdkVersion(ctx EarlyModuleContext) SdkSpec
+	CheckMinSdkVersion(ctx ModuleContext)
 }
 
 // CheckMinSdkVersion checks if every dependency of an updatable module sets min_sdk_version
 // accordingly
-func CheckMinSdkVersion(m UpdatableModule, ctx ModuleContext, minSdkVersion ApiLevel) {
+func CheckMinSdkVersion(ctx ModuleContext, minSdkVersion ApiLevel, walk WalkPayloadDepsFunc) {
 	// do not enforce min_sdk_version for host
 	if ctx.Host() {
 		return
@@ -928,7 +925,7 @@
 		return
 	}
 
-	m.WalkPayloadDeps(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
+	walk(ctx, func(ctx ModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
 		if externalDep {
 			// external deps are outside the payload boundary, which is "stable"
 			// interface. We don't have to check min_sdk_version for external
@@ -938,6 +935,14 @@
 		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
 			return false
 		}
+		if m, ok := to.(ModuleWithMinSdkVersionCheck); ok {
+			// This dependency performs its own min_sdk_version check, just make sure it sets min_sdk_version
+			// to trigger the check.
+			if !m.MinSdkVersion(ctx).Specified() {
+				ctx.OtherModuleErrorf(m, "must set min_sdk_version")
+			}
+			return false
+		}
 		if err := to.ShouldSupportSdkVersion(ctx, minSdkVersion); err != nil {
 			toName := ctx.OtherModuleName(to)
 			if ver, ok := minSdkVersionAllowlist[toName]; !ok || ver.GreaterThan(minSdkVersion) {
diff --git a/android/apex_test.go b/android/apex_test.go
index e112369..0bf4c9c 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -33,10 +33,10 @@
 		{
 			name: "single",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, 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, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, 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, 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, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", uncheckedFinalApiLevel(30), 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, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000", FutureApiLevel, 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, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, 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, 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,45 @@
 			},
 		},
 		{
-			name: "don't merge sdks",
+			name: "don't merge when for prebuilt_apex",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []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, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_2", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"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_baz_2"},
-				{"foo", "apex10000_baz_1"},
+				{"bar", "apex10000"},
+				{"foo", "apex10000"},
 			},
 		},
 		{
-			name: "don't merge when for prebuilt_apex",
+			name: "merge different UsePlatformApis but don't allow using platform api",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, nil, []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, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
+				{"foo", FutureApiLevel, false, false, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, true, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
-				{"baz", FutureApiLevel, true, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, false, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+			},
+			wantAliases: [][2]string{
+				{"bar", "apex10000"},
+				{"foo", "apex10000"},
+			},
+		},
+		{
+			name: "merge same UsePlatformApis and allow using platform api",
+			in: []ApexInfo{
+				{"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, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
diff --git a/android/api_levels.go b/android/api_levels.go
index a337371..2013730 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -18,6 +18,9 @@
 	"encoding/json"
 	"fmt"
 	"strconv"
+
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
 )
 
 func init() {
@@ -177,6 +180,10 @@
 // libandroid_support.
 var FirstNonLibAndroidSupportVersion = uncheckedFinalApiLevel(21)
 
+// LastWithoutModuleLibCoreSystemModules is the last API level where prebuilts/sdk does not contain
+// a core-for-system-modules.jar for the module-lib API scope.
+var LastWithoutModuleLibCoreSystemModules = uncheckedFinalApiLevel(31)
+
 // If the `raw` input is the codename of an API level has been finalized, this
 // function returns the API level number associated with that API level. If the
 // input is *not* a finalized codename, the input is returned unmodified.
@@ -188,8 +195,8 @@
 // * "30" -> "30"
 // * "R" -> "30"
 // * "S" -> "S"
-func ReplaceFinalizedCodenames(ctx PathContext, raw string) string {
-	num, ok := getFinalCodenamesMap(ctx.Config())[raw]
+func ReplaceFinalizedCodenames(config Config, raw string) string {
+	num, ok := getFinalCodenamesMap(config)[raw]
 	if !ok {
 		return raw
 	}
@@ -197,7 +204,7 @@
 	return strconv.Itoa(num)
 }
 
-// Converts the given string `raw` to an ApiLevel, possibly returning an error.
+// ApiLevelFromUser converts the given string `raw` to an ApiLevel, possibly returning an error.
 //
 // `raw` must be non-empty. Passing an empty string results in a panic.
 //
@@ -212,6 +219,12 @@
 // Inputs that are not "current", known previews, or convertible to an integer
 // will return an error.
 func ApiLevelFromUser(ctx PathContext, raw string) (ApiLevel, error) {
+	return ApiLevelFromUserWithConfig(ctx.Config(), raw)
+}
+
+// ApiLevelFromUserWithConfig implements ApiLevelFromUser, see comments for
+// ApiLevelFromUser for more details.
+func ApiLevelFromUserWithConfig(config Config, raw string) (ApiLevel, error) {
 	if raw == "" {
 		panic("API level string must be non-empty")
 	}
@@ -220,13 +233,13 @@
 		return FutureApiLevel, nil
 	}
 
-	for _, preview := range ctx.Config().PreviewApiLevels() {
+	for _, preview := range config.PreviewApiLevels() {
 		if raw == preview.String() {
 			return preview, nil
 		}
 	}
 
-	canonical := ReplaceFinalizedCodenames(ctx, raw)
+	canonical := ReplaceFinalizedCodenames(config, raw)
 	asInt, err := strconv.Atoi(canonical)
 	if err != nil {
 		return NoneApiLevel, fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", canonical)
@@ -236,6 +249,27 @@
 	return apiLevel, nil
 }
 
+// ApiLevelForTest returns an ApiLevel constructed from the supplied raw string.
+//
+// This only supports "current" and numeric levels, code names are not supported.
+func ApiLevelForTest(raw string) ApiLevel {
+	if raw == "" {
+		panic("API level string must be non-empty")
+	}
+
+	if raw == "current" {
+		return FutureApiLevel
+	}
+
+	asInt, err := strconv.Atoi(raw)
+	if err != nil {
+		panic(fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", raw))
+	}
+
+	apiLevel := uncheckedFinalApiLevel(asInt)
+	return apiLevel
+}
+
 // Converts an API level string `raw` into an ApiLevel in the same method as
 // `ApiLevelFromUser`, but the input is assumed to have no errors and any errors
 // will panic instead of returning an error.
@@ -273,24 +307,25 @@
 func getFinalCodenamesMap(config Config) map[string]int {
 	return config.Once(finalCodenamesMapKey, func() interface{} {
 		apiLevelsMap := map[string]int{
-			"G":     9,
-			"I":     14,
-			"J":     16,
-			"J-MR1": 17,
-			"J-MR2": 18,
-			"K":     19,
-			"L":     21,
-			"L-MR1": 22,
-			"M":     23,
-			"N":     24,
-			"N-MR1": 25,
-			"O":     26,
-			"O-MR1": 27,
-			"P":     28,
-			"Q":     29,
-			"R":     30,
-			"S":     31,
-			"S-V2":  32,
+			"G":        9,
+			"I":        14,
+			"J":        16,
+			"J-MR1":    17,
+			"J-MR2":    18,
+			"K":        19,
+			"L":        21,
+			"L-MR1":    22,
+			"M":        23,
+			"N":        24,
+			"N-MR1":    25,
+			"O":        26,
+			"O-MR1":    27,
+			"P":        28,
+			"Q":        29,
+			"R":        30,
+			"S":        31,
+			"S-V2":     32,
+			"Tiramisu": 33,
 		}
 
 		// TODO: Differentiate "current" and "future".
@@ -314,27 +349,28 @@
 
 var apiLevelsMapKey = NewOnceKey("ApiLevelsMap")
 
-func getApiLevelsMap(config Config) map[string]int {
+func GetApiLevelsMap(config Config) map[string]int {
 	return config.Once(apiLevelsMapKey, func() interface{} {
 		apiLevelsMap := map[string]int{
-			"G":     9,
-			"I":     14,
-			"J":     16,
-			"J-MR1": 17,
-			"J-MR2": 18,
-			"K":     19,
-			"L":     21,
-			"L-MR1": 22,
-			"M":     23,
-			"N":     24,
-			"N-MR1": 25,
-			"O":     26,
-			"O-MR1": 27,
-			"P":     28,
-			"Q":     29,
-			"R":     30,
-			"S":     31,
-			"S-V2":  32,
+			"G":        9,
+			"I":        14,
+			"J":        16,
+			"J-MR1":    17,
+			"J-MR2":    18,
+			"K":        19,
+			"L":        21,
+			"L-MR1":    22,
+			"M":        23,
+			"N":        24,
+			"N-MR1":    25,
+			"O":        26,
+			"O-MR1":    27,
+			"P":        28,
+			"Q":        29,
+			"R":        30,
+			"S":        31,
+			"S-V2":     32,
+			"Tiramisu": 33,
 		}
 		for i, codename := range config.PlatformVersionActiveCodenames() {
 			apiLevelsMap[codename] = previewAPILevelBase + i
@@ -345,7 +381,25 @@
 }
 
 func (a *apiLevelsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	apiLevelsMap := getApiLevelsMap(ctx.Config())
+	apiLevelsMap := GetApiLevelsMap(ctx.Config())
 	apiLevelsJson := GetApiLevelsJson(ctx)
 	createApiLevelsJson(ctx, apiLevelsJson, apiLevelsMap)
 }
+
+func printApiLevelsStarlarkDict(config Config) string {
+	apiLevelsMap := GetApiLevelsMap(config)
+	valDict := make(map[string]string, len(apiLevelsMap))
+	for k, v := range apiLevelsMap {
+		valDict[k] = strconv.Itoa(v)
+	}
+	return starlark_fmt.PrintDict(valDict, 0)
+}
+
+func StarlarkApiLevelConfigs(config Config) string {
+	return fmt.Sprintf(bazel.GeneratedBazelFileWarning+`
+_api_levels = %s
+
+api_levels = _api_levels
+`, printApiLevelsStarlarkDict(config),
+	)
+}
diff --git a/android/arch.go b/android/arch.go
index 6af047c..cbf77c7 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -21,6 +21,9 @@
 	"runtime"
 	"strings"
 
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/proptools"
@@ -166,7 +169,7 @@
 	return archType
 }
 
-// ArchTypeList returns the a slice copy of the 4 supported ArchTypes for arm,
+// ArchTypeList returns a slice copy of the 4 supported ArchTypes for arm,
 // arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
@@ -253,7 +256,7 @@
 // Linux returns true if the OS uses the Linux kernel, i.e. if the OS is Android or is Linux
 // with or without the Bionic libc runtime.
 func (os OsType) Linux() bool {
-	return os == Android || os == Linux || os == LinuxBionic
+	return os == Android || os == Linux || os == LinuxBionic || os == LinuxMusl
 }
 
 // newOsType constructs an OsType and adds it to the global lists.
@@ -289,28 +292,6 @@
 	return NoOsType
 }
 
-// BuildOs returns the OsType for the OS that the build is running on.
-var BuildOs = func() OsType {
-	switch runtime.GOOS {
-	case "linux":
-		return Linux
-	case "darwin":
-		return Darwin
-	default:
-		panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
-	}
-}()
-
-// BuildArch returns the ArchType for the CPU that the build is running on.
-var BuildArch = func() ArchType {
-	switch runtime.GOARCH {
-	case "amd64":
-		return X86_64
-	default:
-		panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
-	}
-}()
-
 var (
 	// osTypeList contains a list of all the supported OsTypes, including ones not supported
 	// by the current build host or the target device.
@@ -325,8 +306,10 @@
 	NoOsType OsType
 	// Linux is the OS for the Linux kernel plus the glibc runtime.
 	Linux = newOsType("linux_glibc", Host, false, X86, X86_64)
+	// LinuxMusl is the OS for the Linux kernel plus the musl runtime.
+	LinuxMusl = newOsType("linux_musl", Host, false, X86, X86_64)
 	// Darwin is the OS for MacOS/Darwin host machines.
-	Darwin = newOsType("darwin", Host, false, X86_64)
+	Darwin = newOsType("darwin", Host, false, Arm64, X86_64)
 	// LinuxBionic is the OS for the Linux kernel plus the Bionic libc runtime, but without the
 	// rest of Android.
 	LinuxBionic = newOsType("linux_bionic", Host, false, Arm64, X86_64)
@@ -335,8 +318,6 @@
 	// Android is the OS for target devices that run all of Android, including the Linux kernel
 	// and the Bionic libc runtime.
 	Android = newOsType("android", Device, false, Arm, Arm64, X86, X86_64)
-	// Fuchsia is the OS for target devices that run Fuchsia.
-	Fuchsia = newOsType("fuchsia", Device, false, Arm64, X86_64)
 
 	// CommonOS is a pseudo OSType for a common OS variant, which is OsType agnostic and which
 	// has dependencies on all the OS variants.
@@ -428,7 +409,7 @@
 
 	// addPathDepsForProps does not descend into sub structs, so we need to descend into the
 	// arch-specific properties ourselves
-	properties := []interface{}{}
+	var properties []interface{}
 	for _, archProperties := range m.archProperties {
 		for _, archProps := range archProperties {
 			archPropValues := reflect.ValueOf(archProps).Elem()
@@ -586,6 +567,8 @@
 	return variants
 }
 
+var DarwinUniversalVariantTag = archDepTag{name: "darwin universal binary"}
+
 // archMutator splits a module into a variant for each Target requested by the module.  Target selection
 // for a module is in three levels, OsClass, multilib, and then Target.
 // OsClass selection is determined by:
@@ -651,8 +634,7 @@
 	image := base.commonProperties.ImageVariation
 	// Filter NativeBridge targets unless they are explicitly supported.
 	// Skip creating native bridge variants for non-core modules.
-	if os == Android &&
-		!(Bool(base.commonProperties.Native_bridge_supported) && image == CoreVariation) {
+	if os == Android && !(base.IsNativeBridgeSupported() && image == CoreVariation) {
 
 		var targets []Target
 		for _, t := range osTargets {
@@ -673,7 +655,7 @@
 	prefer32 := os == Windows
 
 	// Determine the multilib selection for this module.
-	multilib, extraMultilib := decodeMultilib(base, os.Class)
+	multilib, extraMultilib := decodeMultilib(base, os)
 
 	// Convert the multilib selection into a list of Targets.
 	targets, err := decodeMultilibTargets(multilib, osTargets, prefer32)
@@ -717,6 +699,21 @@
 	for i, m := range modules {
 		addTargetProperties(m, targets[i], multiTargets, i == 0)
 		m.base().setArchProperties(mctx)
+
+		// Install support doesn't understand Darwin+Arm64
+		if os == Darwin && targets[i].HostCross {
+			m.base().commonProperties.SkipInstall = true
+		}
+	}
+
+	// Create a dependency for Darwin Universal binaries from the primary to secondary
+	// architecture. The module itself will be responsible for calling lipo to merge the outputs.
+	if os == Darwin {
+		if multilib == "darwin_universal" && len(modules) == 2 {
+			mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[1], modules[0])
+		} else if multilib == "darwin_universal_common_first" && len(modules) == 3 {
+			mctx.AddInterVariantDependency(DarwinUniversalVariantTag, modules[2], modules[1])
+		}
 	}
 }
 
@@ -733,9 +730,9 @@
 // multilib from the factory's call to InitAndroidArchModule if none was set.  For modules that
 // called InitAndroidMultiTargetsArchModule it always returns "common" for multilib, and returns
 // the actual multilib in extraMultilib.
-func decodeMultilib(base *ModuleBase, class OsClass) (multilib, extraMultilib string) {
+func decodeMultilib(base *ModuleBase, os OsType) (multilib, extraMultilib string) {
 	// First check the "android.compile_multilib" or "host.compile_multilib" properties.
-	switch class {
+	switch os.Class {
 	case Device:
 		multilib = String(base.commonProperties.Target.Android.Compile_multilib)
 	case Host:
@@ -753,6 +750,26 @@
 	}
 
 	if base.commonProperties.UseTargetVariants {
+		// Darwin has the concept of "universal binaries" which is implemented in Soong by
+		// building both x86_64 and arm64 variants, and having select module types know how to
+		// merge the outputs of their corresponding variants together into a final binary. Most
+		// module types don't need to understand this logic, as we only build a small portion
+		// of the tree for Darwin, and only module types writing macho files need to do the
+		// merging.
+		//
+		// This logic is not enabled for:
+		//  "common", as it's not an arch-specific variant
+		//  "32", as Darwin never has a 32-bit variant
+		//  !UseTargetVariants, as the module has opted into handling the arch-specific logic on
+		//    its own.
+		if os == Darwin && multilib != "common" && multilib != "32" {
+			if multilib == "common_first" {
+				multilib = "darwin_universal_common_first"
+			} else {
+				multilib = "darwin_universal"
+			}
+		}
+
 		return multilib, ""
 	} else {
 		// For app modules a single arch variant will be created per OS class which is expected to handle all the
@@ -813,7 +830,7 @@
 	const maxArchTypeNameSize = 500
 
 	// Convert the type to a new set of types that contains only the arch-specific properties
-	// (those that are tagged with `android:"arch_specific"`), and sharded into multiple types
+	// (those that are tagged with `android:"arch_variant"`), and sharded into multiple types
 	// to keep the runtime-generated names under the limit.
 	propShards, _ := proptools.FilterPropertyStructSharded(props, maxArchTypeNameSize, filterArchStruct)
 
@@ -848,6 +865,10 @@
 				archVariant := variantReplacer.Replace(archVariant)
 				variants = append(variants, proptools.FieldNameForProperty(archVariant))
 			}
+			for _, cpuVariant := range cpuVariants[arch] {
+				cpuVariant := variantReplacer.Replace(cpuVariant)
+				variants = append(variants, proptools.FieldNameForProperty(cpuVariant))
+			}
 			for _, feature := range archFeatures[arch] {
 				feature := variantReplacer.Replace(feature)
 				variants = append(variants, proptools.FieldNameForProperty(feature))
@@ -885,7 +906,10 @@
 			"Android64",
 			"Android32",
 			"Bionic",
+			"Glibc",
+			"Musl",
 			"Linux",
+			"Host_linux",
 			"Not_windows",
 			"Arm_on_x86",
 			"Arm_on_x86_64",
@@ -897,21 +921,40 @@
 
 			// Add the OS/Arch combinations, e.g. "android_arm64".
 			for _, archType := range osArchTypeMap[os] {
-				targets = append(targets, os.Field+"_"+archType.Name)
+				targets = append(targets, GetCompoundTargetField(os, archType))
 
-				// Also add the special "linux_<arch>" and "bionic_<arch>" property structs.
+				// Also add the special "linux_<arch>", "bionic_<arch>" , "glibc_<arch>", and
+				// "musl_<arch>" property structs.
 				if os.Linux() {
 					target := "Linux_" + archType.Name
 					if !InList(target, targets) {
 						targets = append(targets, target)
 					}
 				}
+				if os.Linux() && os.Class == Host {
+					target := "Host_linux_" + archType.Name
+					if !InList(target, targets) {
+						targets = append(targets, target)
+					}
+				}
 				if os.Bionic() {
 					target := "Bionic_" + archType.Name
 					if !InList(target, targets) {
 						targets = append(targets, target)
 					}
 				}
+				if os == Linux {
+					target := "Glibc_" + archType.Name
+					if !InList(target, targets) {
+						targets = append(targets, target)
+					}
+				}
+				if os == LinuxMusl {
+					target := "Musl_" + archType.Name
+					if !InList(target, targets) {
+						targets = append(targets, target)
+					}
+				}
 			}
 		}
 
@@ -953,6 +996,8 @@
 		if len(values) > 0 && values[0] != "path" {
 			panic(fmt.Errorf("unknown tags %q in field %q", values, prefix+field.Name))
 		} else if len(values) == 1 {
+			// FIXME(b/200678898): This assumes that the only tag type when there's
+			// `android:"arch_variant"` is `android` itself and thus clobbers others
 			field.Tag = reflect.StructTag(`android:"` + strings.Join(values, ",") + `"`)
 		} else {
 			field.Tag = ``
@@ -973,10 +1018,11 @@
 
 	base := m.base()
 
-	// Store the original list of top level property structs
-	base.generalProperties = m.GetProperties()
+	if len(base.archProperties) != 0 {
+		panic(fmt.Errorf("module %s already has archProperties", m.Name()))
+	}
 
-	for _, properties := range base.generalProperties {
+	getStructType := func(properties interface{}) reflect.Type {
 		propertiesValue := reflect.ValueOf(properties)
 		t := propertiesValue.Type()
 		if propertiesValue.Kind() != reflect.Ptr {
@@ -986,10 +1032,14 @@
 
 		propertiesValue = propertiesValue.Elem()
 		if propertiesValue.Kind() != reflect.Struct {
-			panic(fmt.Errorf("properties must be a pointer to a struct, got %T",
+			panic(fmt.Errorf("properties must be a pointer to a struct, got a pointer to %T",
 				propertiesValue.Interface()))
 		}
+		return t
+	}
 
+	for _, properties := range m.GetProperties() {
+		t := getStructType(properties)
 		// Get or create the arch-specific property struct types for this property struct type.
 		archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} {
 			return createArchPropTypeDesc(t)
@@ -1009,9 +1059,6 @@
 		m.AddProperties(archProperties...)
 	}
 
-	// Update the list of properties that can be set by a defaults module or a call to
-	// AppendMatchingProperties or PrependMatchingProperties.
-	base.customizableProperties = m.GetProperties()
 }
 
 func maybeBlueprintEmbed(src reflect.Value) reflect.Value {
@@ -1055,24 +1102,28 @@
 // Returns the immediate child of the input property struct that corresponds to
 // the sub-property "field".
 func getChildPropertyStruct(ctx ArchVariantContext,
-	src reflect.Value, field, userFriendlyField string) reflect.Value {
+	src reflect.Value, field, userFriendlyField string) (reflect.Value, bool) {
 
 	// Step into non-nil pointers to structs in the src value.
 	if src.Kind() == reflect.Ptr {
 		if src.IsNil() {
-			return src
+			return reflect.Value{}, false
 		}
 		src = src.Elem()
 	}
 
 	// Find the requested field in the src struct.
-	src = src.FieldByName(proptools.FieldNameForProperty(field))
-	if !src.IsValid() {
+	child := src.FieldByName(proptools.FieldNameForProperty(field))
+	if !child.IsValid() {
 		ctx.ModuleErrorf("field %q does not exist", userFriendlyField)
-		return src
+		return reflect.Value{}, false
 	}
 
-	return src
+	if child.IsZero() {
+		return reflect.Value{}, false
+	}
+
+	return child, true
 }
 
 // Squash the appropriate OS-specific property structs into the matching top level property structs
@@ -1080,8 +1131,8 @@
 func (m *ModuleBase) setOSProperties(ctx BottomUpMutatorContext) {
 	os := m.commonProperties.CompileOS
 
-	for i := range m.generalProperties {
-		genProps := m.generalProperties[i]
+	for i := range m.archProperties {
+		genProps := m.GetProperties()[i]
 		if m.archProperties[i] == nil {
 			continue
 		}
@@ -1099,8 +1150,9 @@
 			if os.Class == Host {
 				field := "Host"
 				prefix := "target.host"
-				hostProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, hostProperties)
+				if hostProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, hostProperties)
+				}
 			}
 
 			// Handle target OS generalities of the form:
@@ -1112,15 +1164,41 @@
 			if os.Linux() {
 				field := "Linux"
 				prefix := "target.linux"
-				linuxProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, linuxProperties)
+				if linuxProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, linuxProperties)
+				}
+			}
+
+			if os.Linux() && os.Class == Host {
+				field := "Host_linux"
+				prefix := "target.host_linux"
+				if linuxProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, linuxProperties)
+				}
 			}
 
 			if os.Bionic() {
 				field := "Bionic"
 				prefix := "target.bionic"
-				bionicProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, bionicProperties)
+				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, bionicProperties)
+				}
+			}
+
+			if os == Linux {
+				field := "Glibc"
+				prefix := "target.glibc"
+				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, bionicProperties)
+				}
+			}
+
+			if os == LinuxMusl {
+				field := "Musl"
+				prefix := "target.musl"
+				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, bionicProperties)
+				}
 			}
 
 			// Handle target OS properties in the form:
@@ -1137,14 +1215,16 @@
 			// },
 			field := os.Field
 			prefix := "target." + os.Name
-			osProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-			mergePropertyStruct(ctx, genProps, osProperties)
+			if osProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+				mergePropertyStruct(ctx, genProps, osProperties)
+			}
 
 			if os.Class == Host && os != Windows {
 				field := "Not_windows"
 				prefix := "target.not_windows"
-				notWindowsProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, notWindowsProperties)
+				if notWindowsProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, notWindowsProperties)
+				}
 			}
 
 			// Handle 64-bit device properties in the form:
@@ -1164,13 +1244,15 @@
 				if ctx.Config().Android64() {
 					field := "Android64"
 					prefix := "target.android64"
-					android64Properties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-					mergePropertyStruct(ctx, genProps, android64Properties)
+					if android64Properties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+						mergePropertyStruct(ctx, genProps, android64Properties)
+					}
 				} else {
 					field := "Android32"
 					prefix := "target.android32"
-					android32Properties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-					mergePropertyStruct(ctx, genProps, android32Properties)
+					if android32Properties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+						mergePropertyStruct(ctx, genProps, android32Properties)
+					}
 				}
 			}
 		}
@@ -1186,12 +1268,11 @@
 // },
 // This struct will also contain sub-structs containing to the architecture/CPU
 // variants and features that themselves contain properties specific to those.
-func getArchTypeStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) reflect.Value {
+func getArchTypeStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) (reflect.Value, bool) {
 	archPropValues := reflect.ValueOf(archProperties).Elem()
 	archProp := archPropValues.FieldByName("Arch").Elem()
 	prefix := "arch." + archType.Name
-	archStruct := getChildPropertyStruct(ctx, archProp, archType.Name, prefix)
-	return archStruct
+	return getChildPropertyStruct(ctx, archProp, archType.Name, prefix)
 }
 
 // Returns the struct containing the properties specific to a given multilib
@@ -1201,11 +1282,14 @@
 //         key: value,
 //     },
 // },
-func getMultilibStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) reflect.Value {
+func getMultilibStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) (reflect.Value, bool) {
 	archPropValues := reflect.ValueOf(archProperties).Elem()
 	multilibProp := archPropValues.FieldByName("Multilib").Elem()
-	multilibProperties := getChildPropertyStruct(ctx, multilibProp, archType.Multilib, "multilib."+archType.Multilib)
-	return multilibProperties
+	return getChildPropertyStruct(ctx, multilibProp, archType.Multilib, "multilib."+archType.Multilib)
+}
+
+func GetCompoundTargetField(os OsType, arch ArchType) string {
+	return os.Field + "_" + arch.Name
 }
 
 // Returns the structs corresponding to the properties specific to the given
@@ -1219,58 +1303,64 @@
 	archType := arch.ArchType
 
 	if arch.ArchType != Common {
-		archStruct := getArchTypeStruct(ctx, archProperties, arch.ArchType)
-		result = append(result, archStruct)
+		archStruct, ok := getArchTypeStruct(ctx, archProperties, arch.ArchType)
+		if ok {
+			result = append(result, archStruct)
 
-		// Handle arch-variant-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         variant: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		v := variantReplacer.Replace(arch.ArchVariant)
-		if v != "" {
-			prefix := "arch." + archType.Name + "." + v
-			variantProperties := getChildPropertyStruct(ctx, archStruct, v, prefix)
-			result = append(result, variantProperties)
-		}
+			// Handle arch-variant-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         variant: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			v := variantReplacer.Replace(arch.ArchVariant)
+			if v != "" {
+				prefix := "arch." + archType.Name + "." + v
+				if variantProperties, ok := getChildPropertyStruct(ctx, archStruct, v, prefix); ok {
+					result = append(result, variantProperties)
+				}
+			}
 
-		// Handle cpu-variant-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         variant: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		if arch.CpuVariant != arch.ArchVariant {
-			c := variantReplacer.Replace(arch.CpuVariant)
-			if c != "" {
-				prefix := "arch." + archType.Name + "." + c
-				cpuVariantProperties := getChildPropertyStruct(ctx, archStruct, c, prefix)
-				result = append(result, cpuVariantProperties)
+			// Handle cpu-variant-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         variant: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			if arch.CpuVariant != arch.ArchVariant {
+				c := variantReplacer.Replace(arch.CpuVariant)
+				if c != "" {
+					prefix := "arch." + archType.Name + "." + c
+					if cpuVariantProperties, ok := getChildPropertyStruct(ctx, archStruct, c, prefix); ok {
+						result = append(result, cpuVariantProperties)
+					}
+				}
+			}
+
+			// Handle arch-feature-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         feature: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			for _, feature := range arch.ArchFeatures {
+				prefix := "arch." + archType.Name + "." + feature
+				if featureProperties, ok := getChildPropertyStruct(ctx, archStruct, feature, prefix); ok {
+					result = append(result, featureProperties)
+				}
 			}
 		}
 
-		// Handle arch-feature-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         feature: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		for _, feature := range arch.ArchFeatures {
-			prefix := "arch." + archType.Name + "." + feature
-			featureProperties := getChildPropertyStruct(ctx, archStruct, feature, prefix)
-			result = append(result, featureProperties)
+		if multilibProperties, ok := getMultilibStruct(ctx, archProperties, archType); ok {
+			result = append(result, multilibProperties)
 		}
 
-		multilibProperties := getMultilibStruct(ctx, archProperties, archType)
-		result = append(result, multilibProperties)
-
 		// Handle combined OS-feature and arch specific properties in the form:
 		// target: {
 		//     bionic_x86: {
@@ -1280,15 +1370,17 @@
 		if os.Linux() {
 			field := "Linux_" + arch.ArchType.Name
 			userFriendlyField := "target.linux_" + arch.ArchType.Name
-			linuxProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, linuxProperties)
+			if linuxProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, linuxProperties)
+			}
 		}
 
 		if os.Bionic() {
 			field := "Bionic_" + archType.Name
 			userFriendlyField := "target.bionic_" + archType.Name
-			bionicProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, bionicProperties)
+			if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, bionicProperties)
+			}
 		}
 
 		// Handle combined OS and arch specific properties in the form:
@@ -1306,10 +1398,27 @@
 		//         key: value,
 		//     },
 		// },
-		field := os.Field + "_" + archType.Name
+		field := GetCompoundTargetField(os, archType)
 		userFriendlyField := "target." + os.Name + "_" + archType.Name
-		osArchProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-		result = append(result, osArchProperties)
+		if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+			result = append(result, osArchProperties)
+		}
+
+		if os == Linux {
+			field := "Glibc_" + archType.Name
+			userFriendlyField := "target.glibc_" + "_" + archType.Name
+			if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, osArchProperties)
+			}
+		}
+
+		if os == LinuxMusl {
+			field := "Musl_" + archType.Name
+			userFriendlyField := "target.musl_" + "_" + archType.Name
+			if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, osArchProperties)
+			}
+		}
 	}
 
 	// Handle arm on x86 properties in the form:
@@ -1326,21 +1435,24 @@
 			hasArmAndroidArch(ctx.Config().Targets[Android])) {
 			field := "Arm_on_x86"
 			userFriendlyField := "target.arm_on_x86"
-			armOnX86Properties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, armOnX86Properties)
+			if armOnX86Properties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, armOnX86Properties)
+			}
 		}
 		if arch.ArchType == X86_64 && (hasArmAbi(arch) ||
 			hasArmAndroidArch(ctx.Config().Targets[Android])) {
 			field := "Arm_on_x86_64"
 			userFriendlyField := "target.arm_on_x86_64"
-			armOnX8664Properties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, armOnX8664Properties)
+			if armOnX8664Properties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, armOnX8664Properties)
+			}
 		}
 		if os == Android && nativeBridgeEnabled {
 			userFriendlyField := "Native_bridge"
 			prefix := "target.native_bridge"
-			nativeBridgeProperties := getChildPropertyStruct(ctx, targetProp, userFriendlyField, prefix)
-			result = append(result, nativeBridgeProperties)
+			if nativeBridgeProperties, ok := getChildPropertyStruct(ctx, targetProp, userFriendlyField, prefix); ok {
+				result = append(result, nativeBridgeProperties)
+			}
 		}
 	}
 
@@ -1353,8 +1465,8 @@
 	arch := m.Arch()
 	os := m.Os()
 
-	for i := range m.generalProperties {
-		genProps := m.generalProperties[i]
+	for i := range m.archProperties {
+		genProps := m.GetProperties()[i]
 		if m.archProperties[i] == nil {
 			continue
 		}
@@ -1371,6 +1483,34 @@
 	}
 }
 
+// determineBuildOS stores the OS and architecture used for host targets used during the build into
+// config based on the runtime OS and architecture determined by Go and the product configuration.
+func determineBuildOS(config *config) {
+	config.BuildOS = func() OsType {
+		switch runtime.GOOS {
+		case "linux":
+			if Bool(config.productVariables.HostMusl) {
+				return LinuxMusl
+			}
+			return Linux
+		case "darwin":
+			return Darwin
+		default:
+			panic(fmt.Sprintf("unsupported OS: %s", runtime.GOOS))
+		}
+	}()
+
+	config.BuildArch = func() ArchType {
+		switch runtime.GOARCH {
+		case "amd64":
+			return X86_64
+		default:
+			panic(fmt.Sprintf("unsupported Arch: %s", runtime.GOARCH))
+		}
+	}()
+
+}
+
 // Convert the arch product variables into a list of targets for each OsType.
 func decodeTargetProductVariables(config *config) (map[OsType][]Target, error) {
 	variables := config.productVariables
@@ -1378,23 +1518,32 @@
 	targets := make(map[OsType][]Target)
 	var targetErr error
 
-	addTarget := func(os OsType, archName string, archVariant, cpuVariant *string, abi []string,
-		nativeBridgeEnabled NativeBridgeSupport, nativeBridgeHostArchName *string,
-		nativeBridgeRelativePath *string) {
+	type targetConfig struct {
+		os                       OsType
+		archName                 string
+		archVariant              *string
+		cpuVariant               *string
+		abi                      []string
+		nativeBridgeEnabled      NativeBridgeSupport
+		nativeBridgeHostArchName *string
+		nativeBridgeRelativePath *string
+	}
+
+	addTarget := func(target targetConfig) {
 		if targetErr != nil {
 			return
 		}
 
-		arch, err := decodeArch(os, archName, archVariant, cpuVariant, abi)
+		arch, err := decodeArch(target.os, target.archName, target.archVariant, target.cpuVariant, target.abi)
 		if err != nil {
 			targetErr = err
 			return
 		}
-		nativeBridgeRelativePathStr := String(nativeBridgeRelativePath)
-		nativeBridgeHostArchNameStr := String(nativeBridgeHostArchName)
+		nativeBridgeRelativePathStr := String(target.nativeBridgeRelativePath)
+		nativeBridgeHostArchNameStr := String(target.nativeBridgeHostArchName)
 
 		// Use guest arch as relative install path by default
-		if nativeBridgeEnabled && nativeBridgeRelativePathStr == "" {
+		if target.nativeBridgeEnabled && nativeBridgeRelativePathStr == "" {
 			nativeBridgeRelativePathStr = arch.ArchType.String()
 		}
 
@@ -1402,11 +1551,11 @@
 		// the currently configured build machine (either because the OS is different or because of
 		// the unsupported arch)
 		hostCross := false
-		if os.Class == Host {
+		if target.os.Class == Host {
 			var osSupported bool
-			if os == BuildOs {
+			if target.os == config.BuildOS {
 				osSupported = true
-			} else if BuildOs.Linux() && os.Linux() {
+			} else if config.BuildOS.Linux() && target.os.Linux() {
 				// LinuxBionic and Linux are compatible
 				osSupported = true
 			} else {
@@ -1428,11 +1577,11 @@
 			}
 		}
 
-		targets[os] = append(targets[os],
+		targets[target.os] = append(targets[target.os],
 			Target{
-				Os:                       os,
+				Os:                       target.os,
 				Arch:                     arch,
-				NativeBridge:             nativeBridgeEnabled,
+				NativeBridge:             target.nativeBridgeEnabled,
 				NativeBridgeHostArchName: nativeBridgeHostArchNameStr,
 				NativeBridgeRelativePath: nativeBridgeRelativePathStr,
 				HostCross:                hostCross,
@@ -1444,11 +1593,11 @@
 	}
 
 	// The primary host target, which must always exist.
-	addTarget(BuildOs, *variables.HostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
+	addTarget(targetConfig{os: config.BuildOS, archName: *variables.HostArch, nativeBridgeEnabled: NativeBridgeDisabled})
 
 	// An optional secondary host target.
 	if variables.HostSecondaryArch != nil && *variables.HostSecondaryArch != "" {
-		addTarget(BuildOs, *variables.HostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
+		addTarget(targetConfig{os: config.BuildOS, archName: *variables.HostSecondaryArch, nativeBridgeEnabled: NativeBridgeDisabled})
 	}
 
 	// Optional cross-compiled host targets, generally Windows.
@@ -1463,50 +1612,65 @@
 		}
 
 		// The primary cross-compiled host target.
-		addTarget(crossHostOs, *variables.CrossHostArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
+		addTarget(targetConfig{os: crossHostOs, archName: *variables.CrossHostArch, nativeBridgeEnabled: NativeBridgeDisabled})
 
 		// An optional secondary cross-compiled host target.
 		if variables.CrossHostSecondaryArch != nil && *variables.CrossHostSecondaryArch != "" {
-			addTarget(crossHostOs, *variables.CrossHostSecondaryArch, nil, nil, nil, NativeBridgeDisabled, nil, nil)
+			addTarget(targetConfig{os: crossHostOs, archName: *variables.CrossHostSecondaryArch, nativeBridgeEnabled: NativeBridgeDisabled})
 		}
 	}
 
 	// Optional device targets
 	if variables.DeviceArch != nil && *variables.DeviceArch != "" {
-		var target = Android
-		if Bool(variables.Fuchsia) {
-			target = Fuchsia
-		}
-
 		// The primary device target.
-		addTarget(target, *variables.DeviceArch, variables.DeviceArchVariant,
-			variables.DeviceCpuVariant, variables.DeviceAbi, NativeBridgeDisabled, nil, nil)
+		addTarget(targetConfig{
+			os:                  Android,
+			archName:            *variables.DeviceArch,
+			archVariant:         variables.DeviceArchVariant,
+			cpuVariant:          variables.DeviceCpuVariant,
+			abi:                 variables.DeviceAbi,
+			nativeBridgeEnabled: NativeBridgeDisabled,
+		})
 
 		// An optional secondary device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" {
-			addTarget(Android, *variables.DeviceSecondaryArch,
-				variables.DeviceSecondaryArchVariant, variables.DeviceSecondaryCpuVariant,
-				variables.DeviceSecondaryAbi, NativeBridgeDisabled, nil, nil)
+			addTarget(targetConfig{
+				os:                  Android,
+				archName:            *variables.DeviceSecondaryArch,
+				archVariant:         variables.DeviceSecondaryArchVariant,
+				cpuVariant:          variables.DeviceSecondaryCpuVariant,
+				abi:                 variables.DeviceSecondaryAbi,
+				nativeBridgeEnabled: NativeBridgeDisabled,
+			})
 		}
 
 		// An optional NativeBridge device target.
 		if variables.NativeBridgeArch != nil && *variables.NativeBridgeArch != "" {
-			addTarget(Android, *variables.NativeBridgeArch,
-				variables.NativeBridgeArchVariant, variables.NativeBridgeCpuVariant,
-				variables.NativeBridgeAbi, NativeBridgeEnabled, variables.DeviceArch,
-				variables.NativeBridgeRelativePath)
+			addTarget(targetConfig{
+				os:                       Android,
+				archName:                 *variables.NativeBridgeArch,
+				archVariant:              variables.NativeBridgeArchVariant,
+				cpuVariant:               variables.NativeBridgeCpuVariant,
+				abi:                      variables.NativeBridgeAbi,
+				nativeBridgeEnabled:      NativeBridgeEnabled,
+				nativeBridgeHostArchName: variables.DeviceArch,
+				nativeBridgeRelativePath: variables.NativeBridgeRelativePath,
+			})
 		}
 
 		// An optional secondary NativeBridge device target.
 		if variables.DeviceSecondaryArch != nil && *variables.DeviceSecondaryArch != "" &&
 			variables.NativeBridgeSecondaryArch != nil && *variables.NativeBridgeSecondaryArch != "" {
-			addTarget(Android, *variables.NativeBridgeSecondaryArch,
-				variables.NativeBridgeSecondaryArchVariant,
-				variables.NativeBridgeSecondaryCpuVariant,
-				variables.NativeBridgeSecondaryAbi,
-				NativeBridgeEnabled,
-				variables.DeviceSecondaryArch,
-				variables.NativeBridgeSecondaryRelativePath)
+			addTarget(targetConfig{
+				os:                       Android,
+				archName:                 *variables.NativeBridgeSecondaryArch,
+				archVariant:              variables.NativeBridgeSecondaryArchVariant,
+				cpuVariant:               variables.NativeBridgeSecondaryCpuVariant,
+				abi:                      variables.NativeBridgeSecondaryAbi,
+				nativeBridgeEnabled:      NativeBridgeEnabled,
+				nativeBridgeHostArchName: variables.DeviceSecondaryArch,
+				nativeBridgeRelativePath: variables.NativeBridgeSecondaryRelativePath,
+			})
 		}
 	}
 
@@ -1542,32 +1706,35 @@
 	abi         []string
 }
 
-// getNdkAbisConfig returns a list of archConfigs for the ABIs supported by the NDK.
+// getNdkAbisConfig returns the list of archConfigs that are used for bulding
+// the API stubs and static libraries that are included in the NDK. These are
+// built *without Neon*, because non-Neon is still supported and building these
+// with Neon will break those users.
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
-		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
 		{"arm64", "armv8-a-branchprot", "", []string{"arm64-v8a"}},
-		{"x86", "", "", []string{"x86"}},
+		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
 		{"x86_64", "", "", []string{"x86_64"}},
+		{"x86", "", "", []string{"x86"}},
 	}
 }
 
 // getAmlAbisConfig returns a list of archConfigs for the ABIs supported by mainline modules.
 func getAmlAbisConfig() []archConfig {
 	return []archConfig{
-		{"arm", "armv7-a-neon", "", []string{"armeabi-v7a"}},
 		{"arm64", "armv8-a", "", []string{"arm64-v8a"}},
-		{"x86", "", "", []string{"x86"}},
+		{"arm", "armv7-a-neon", "", []string{"armeabi-v7a"}},
 		{"x86_64", "", "", []string{"x86_64"}},
+		{"x86", "", "", []string{"x86"}},
 	}
 }
 
 // decodeArchSettings converts a list of archConfigs into a list of Targets for the given OsType.
-func decodeArchSettings(os OsType, archConfigs []archConfig) ([]Target, error) {
+func decodeAndroidArchSettings(archConfigs []archConfig) ([]Target, error) {
 	var ret []Target
 
 	for _, config := range archConfigs {
-		arch, err := decodeArch(os, config.arch, &config.archVariant,
+		arch, err := decodeArch(Android, config.arch, &config.archVariant,
 			&config.cpuVariant, config.abi)
 		if err != nil {
 			return nil, err
@@ -1607,6 +1774,18 @@
 		a.CpuVariant = ""
 	}
 
+	if a.ArchVariant != "" {
+		if validArchVariants := archVariants[archType]; !InList(a.ArchVariant, validArchVariants) {
+			return Arch{}, fmt.Errorf("[%q] unknown arch variant %q, support variants: %q", archType, a.ArchVariant, validArchVariants)
+		}
+	}
+
+	if a.CpuVariant != "" {
+		if validCpuVariants := cpuVariants[archType]; !InList(a.CpuVariant, validCpuVariants) {
+			return Arch{}, fmt.Errorf("[%q] unknown cpu variant %q, support variants: %q", archType, a.CpuVariant, validCpuVariants)
+		}
+	}
+
 	// Filter empty ABIs out of the list.
 	for i := 0; i < len(a.Abi); i++ {
 		if a.Abi[i] == "" {
@@ -1615,14 +1794,9 @@
 		}
 	}
 
-	if a.ArchVariant == "" {
-		// Set ArchFeatures from the default arch features.
-		if featureMap, ok := defaultArchFeatureMap[os]; ok {
-			a.ArchFeatures = featureMap[archType]
-		}
-	} else {
-		// Set ArchFeatures from the arch type.
-		if featureMap, ok := archFeatureMap[archType]; ok {
+	// Set ArchFeatures from the arch type. for Android OS, other os-es do not specify features
+	if os == Android {
+		if featureMap, ok := androidArchFeatureMap[archType]; ok {
 			a.ArchFeatures = featureMap[a.ArchVariant]
 		}
 	}
@@ -1658,10 +1832,10 @@
 	return ret
 }
 
-// firstTarget takes a list of Targets and a list of multilib values and returns a list of Targets
+// FirstTarget takes a list of Targets and a list of multilib values and returns a list of Targets
 // that contains zero or one Target for each OsType, selecting the one that matches the earliest
 // filter.
-func firstTarget(targets []Target, filters ...string) []Target {
+func FirstTarget(targets []Target, filters ...string) []Target {
 	// find the first target from each OS
 	var ret []Target
 	hasHost := false
@@ -1691,9 +1865,9 @@
 	case "common_first":
 		buildTargets = getCommonTargets(targets)
 		if prefer32 {
-			buildTargets = append(buildTargets, firstTarget(targets, "lib32", "lib64")...)
+			buildTargets = append(buildTargets, FirstTarget(targets, "lib32", "lib64")...)
 		} else {
-			buildTargets = append(buildTargets, firstTarget(targets, "lib64", "lib32")...)
+			buildTargets = append(buildTargets, FirstTarget(targets, "lib64", "lib32")...)
 		}
 	case "both":
 		if prefer32 {
@@ -1709,17 +1883,26 @@
 		buildTargets = filterMultilibTargets(targets, "lib64")
 	case "first":
 		if prefer32 {
-			buildTargets = firstTarget(targets, "lib32", "lib64")
+			buildTargets = FirstTarget(targets, "lib32", "lib64")
 		} else {
-			buildTargets = firstTarget(targets, "lib64", "lib32")
+			buildTargets = FirstTarget(targets, "lib64", "lib32")
 		}
 	case "first_prefer32":
-		buildTargets = firstTarget(targets, "lib32", "lib64")
+		buildTargets = FirstTarget(targets, "lib32", "lib64")
 	case "prefer32":
 		buildTargets = filterMultilibTargets(targets, "lib32")
 		if len(buildTargets) == 0 {
 			buildTargets = filterMultilibTargets(targets, "lib64")
 		}
+	case "darwin_universal":
+		buildTargets = filterMultilibTargets(targets, "lib64")
+		// Reverse the targets so that the first architecture can depend on the second
+		// architecture module in order to merge the outputs.
+		reverseSliceInPlace(buildTargets)
+	case "darwin_universal_common_first":
+		archTargets := filterMultilibTargets(targets, "lib64")
+		reverseSliceInPlace(archTargets)
+		buildTargets = append(getCommonTargets(targets), archTargets...)
 	default:
 		return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", "prefer32" or "first_prefer32" found %q`,
 			multilib)
@@ -1859,45 +2042,60 @@
 	PropertyErrorf(property, fmt string, args ...interface{})
 }
 
-// GetArchProperties returns a map of architectures to the values of the
-// properties of the 'propertySet' struct that are specific to that architecture.
+// ArchVariantProperties represents a map of arch-variant config strings to a property interface{}.
+type ArchVariantProperties map[string]interface{}
+
+// ConfigurationAxisToArchVariantProperties represents a map of bazel.ConfigurationAxis to
+// ArchVariantProperties, such that each independent arch-variant axis maps to the
+// configs/properties for that axis.
+type ConfigurationAxisToArchVariantProperties map[bazel.ConfigurationAxis]ArchVariantProperties
+
+// GetArchVariantProperties returns a ConfigurationAxisToArchVariantProperties where the
+// arch-variant properties correspond to the values of the properties of the 'propertySet' struct
+// that are specific to that axis/configuration. Each axis is independent, containing
+// non-overlapping configs that correspond to the various "arch-variant" support, at this time:
+//    arches (including multilib)
+//    oses
+//    arch+os combinations
 //
-// For example, passing a struct { Foo bool, Bar string } will return an
-// interface{} that can be type asserted back into the same struct, containing
-// the arch specific property value specified by the module if defined.
+// For example, passing a struct { Foo bool, Bar string } will return an interface{} that can be
+// type asserted back into the same struct, containing the config-specific property value specified
+// by the module if defined.
 //
 // Arch-specific properties may come from an arch stanza or a multilib stanza; properties
 // in these stanzas are combined.
 // For example: `arch: { x86: { Foo: ["bar"] } }, multilib: { lib32: {` Foo: ["baz"] } }`
 // will result in `Foo: ["bar", "baz"]` being returned for architecture x86, if the given
 // propertyset contains `Foo []string`.
-func (m *ModuleBase) GetArchProperties(ctx ArchVariantContext, propertySet interface{}) map[ArchType]interface{} {
+func (m *ModuleBase) GetArchVariantProperties(ctx ArchVariantContext, propertySet interface{}) ConfigurationAxisToArchVariantProperties {
 	// Return value of the arch types to the prop values for that arch.
-	archToProp := map[ArchType]interface{}{}
+	axisToProps := ConfigurationAxisToArchVariantProperties{}
 
 	// Nothing to do for non-arch-specific modules.
 	if !m.ArchSpecific() {
-		return archToProp
+		return axisToProps
 	}
 
 	dstType := reflect.ValueOf(propertySet).Type()
 	var archProperties []interface{}
 
 	// First find the property set in the module that corresponds to the requested
-	// one. m.archProperties[i] corresponds to m.generalProperties[i].
-	for i, generalProp := range m.generalProperties {
+	// one. m.archProperties[i] corresponds to m.GetProperties()[i].
+	for i, generalProp := range m.GetProperties() {
 		srcType := reflect.ValueOf(generalProp).Type()
 		if srcType == dstType {
 			archProperties = m.archProperties[i]
+			axisToProps[bazel.NoConfigAxis] = ArchVariantProperties{"": generalProp}
 			break
 		}
 	}
 
 	if archProperties == nil {
 		// This module does not have the property set requested
-		return archToProp
+		return axisToProps
 	}
 
+	archToProp := ArchVariantProperties{}
 	// For each arch type (x86, arm64, etc.)
 	for _, arch := range ArchTypeList() {
 		// Arch properties are sometimes sharded (see createArchPropTypeDesc() ).
@@ -1905,108 +2103,185 @@
 		// input one that contains the data specific to that arch.
 		propertyStructs := make([]reflect.Value, 0)
 		for _, archProperty := range archProperties {
-			archTypeStruct := getArchTypeStruct(ctx, archProperty, arch)
-			multilibStruct := getMultilibStruct(ctx, archProperty, arch)
-			propertyStructs = append(propertyStructs, archTypeStruct, multilibStruct)
+			archTypeStruct, ok := getArchTypeStruct(ctx, archProperty, arch)
+			if ok {
+				propertyStructs = append(propertyStructs, archTypeStruct)
+			}
+			multilibStruct, ok := getMultilibStruct(ctx, archProperty, arch)
+			if ok {
+				propertyStructs = append(propertyStructs, multilibStruct)
+			}
 		}
 
 		// Create a new instance of the requested property set
 		value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
 
-		// Merge all the structs together
-		for _, propertyStruct := range propertyStructs {
-			mergePropertyStruct(ctx, value, propertyStruct)
-		}
-
-		archToProp[arch] = value
+		archToProp[arch.Name] = mergeStructs(ctx, propertyStructs, value)
 	}
+	axisToProps[bazel.ArchConfigurationAxis] = archToProp
 
-	return archToProp
-}
+	osToProp := ArchVariantProperties{}
+	archOsToProp := ArchVariantProperties{}
 
-// GetTargetProperties returns a map of OS target (e.g. android, windows) to the
-// values of the properties of the 'dst' struct that are specific to that OS
-// target.
-//
-// For example, passing a struct { Foo bool, Bar string } will return an
-// interface{} that can be type asserted back into the same struct, containing
-// the os-specific property value specified by the module if defined.
-//
-// While this looks similar to GetArchProperties, the internal representation of
-// the properties have a slightly different layout to warrant a standalone
-// lookup function.
-func (m *ModuleBase) GetTargetProperties(dst interface{}) map[OsType]interface{} {
-	// Return value of the arch types to the prop values for that arch.
-	osToProp := map[OsType]interface{}{}
+	linuxStructs := getTargetStructs(ctx, archProperties, "Linux")
+	bionicStructs := getTargetStructs(ctx, archProperties, "Bionic")
+	hostStructs := getTargetStructs(ctx, archProperties, "Host")
+	hostLinuxStructs := getTargetStructs(ctx, archProperties, "Host_linux")
+	hostNotWindowsStructs := getTargetStructs(ctx, archProperties, "Not_windows")
 
-	// Nothing to do for non-OS/arch-specific modules.
-	if !m.ArchSpecific() {
-		return osToProp
-	}
-
-	// archProperties has the type of [][]interface{}. Looks complicated, so
-	// let's explain this step by step.
-	//
-	// Loop over the outer index, which determines the property struct that
-	// contains a matching set of properties in dst that we're interested in.
-	// For example, BaseCompilerProperties or BaseLinkerProperties.
-	for i := range m.archProperties {
-		if m.archProperties[i] == nil {
+	// For android, linux, ...
+	for _, os := range osTypeList {
+		if os == CommonOS {
+			// It looks like this OS value is not used in Blueprint files
 			continue
 		}
+		osStructs := make([]reflect.Value, 0)
 
-		// Iterate over the supported OS types
-		for _, os := range osTypeList {
-			// e.g android, linux_bionic
-			field := os.Field
+		osSpecificStructs := getTargetStructs(ctx, archProperties, os.Field)
+		if os.Class == Host {
+			osStructs = append(osStructs, hostStructs...)
+		}
+		if os.Linux() {
+			osStructs = append(osStructs, linuxStructs...)
+		}
+		if os.Bionic() {
+			osStructs = append(osStructs, bionicStructs...)
+		}
+		if os.Linux() && os.Class == Host {
+			osStructs = append(osStructs, hostLinuxStructs...)
+		}
 
-			// If it's not nil, loop over the inner index, which determines the arch variant
-			// of the prop type. In an Android.bp file, this is like looping over:
-			//
-			// target: { android: { key: value, ... }, linux_bionic: { key: value, ... } }
-			for _, archProperties := range m.archProperties[i] {
-				archPropValues := reflect.ValueOf(archProperties).Elem()
+		if os == LinuxMusl {
+			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Musl")...)
+		}
+		if os == Linux {
+			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Glibc")...)
+		}
 
-				// This is the archPropRoot struct. Traverse into the Targetnested struct.
-				src := archPropValues.FieldByName("Target").Elem()
+		osStructs = append(osStructs, osSpecificStructs...)
 
-				// Step into non-nil pointers to structs in the src value.
-				if src.Kind() == reflect.Ptr {
-					if src.IsNil() {
-						continue
-					}
-					src = src.Elem()
-				}
+		if os.Class == Host && os != Windows {
+			osStructs = append(osStructs, hostNotWindowsStructs...)
+		}
+		osToProp[os.Name] = mergeStructs(ctx, osStructs, propertySet)
 
-				// Find the requested field (e.g. android, linux_bionic) in the src struct.
-				src = src.FieldByName(field)
+		// For arm, x86, ...
+		for _, arch := range osArchTypeMap[os] {
+			osArchStructs := make([]reflect.Value, 0)
 
-				// Validation steps. We want valid non-nil pointers to structs.
-				if !src.IsValid() || src.IsNil() {
-					continue
-				}
-
-				if src.Kind() != reflect.Ptr || src.Elem().Kind() != reflect.Struct {
-					continue
-				}
-
-				// Clone the destination prop, since we want a unique prop struct per arch.
-				dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()
-
-				// Copy the located property struct into the cloned destination property struct.
-				err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
-				if err != nil {
-					// This is fine, it just means the src struct doesn't match.
-					continue
-				}
-
-				// Found the prop for the os, you have.
-				osToProp[os] = dstClone
-
-				// Go to the next prop.
-				break
+			// Auto-combine with Linux_ and Bionic_ targets. This potentially results in
+			// repetition and select() bloat, but use of Linux_* and Bionic_* targets is rare.
+			// TODO(b/201423152): Look into cleanup.
+			if os.Linux() {
+				targetField := "Linux_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
 			}
+			if os.Bionic() {
+				targetField := "Bionic_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
+			}
+			if os == LinuxMusl {
+				targetField := "Musl_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
+			}
+			if os == Linux {
+				targetField := "Glibc_" + arch.Name
+				targetStructs := getTargetStructs(ctx, archProperties, targetField)
+				osArchStructs = append(osArchStructs, targetStructs...)
+			}
+
+			targetField := GetCompoundTargetField(os, arch)
+			targetName := fmt.Sprintf("%s_%s", os.Name, arch.Name)
+			targetStructs := getTargetStructs(ctx, archProperties, targetField)
+			osArchStructs = append(osArchStructs, targetStructs...)
+
+			archOsToProp[targetName] = mergeStructs(ctx, osArchStructs, propertySet)
 		}
 	}
-	return osToProp
+
+	axisToProps[bazel.OsConfigurationAxis] = osToProp
+	axisToProps[bazel.OsArchConfigurationAxis] = archOsToProp
+	return axisToProps
+}
+
+// Returns a struct matching the propertySet interface, containing properties specific to the targetName
+// For example, given these arguments:
+//    propertySet = BaseCompilerProperties
+//    targetName = "android_arm"
+// And given this Android.bp fragment:
+//    target:
+//       android_arm: {
+//          srcs: ["foo.c"],
+//       }
+//       android_arm64: {
+//          srcs: ["bar.c"],
+//      }
+//    }
+// This would return a BaseCompilerProperties with BaseCompilerProperties.Srcs = ["foo.c"]
+func getTargetStructs(ctx ArchVariantContext, archProperties []interface{}, targetName string) []reflect.Value {
+	var propertyStructs []reflect.Value
+	for _, archProperty := range archProperties {
+		archPropValues := reflect.ValueOf(archProperty).Elem()
+		targetProp := archPropValues.FieldByName("Target").Elem()
+		targetStruct, ok := getChildPropertyStruct(ctx, targetProp, targetName, targetName)
+		if ok {
+			propertyStructs = append(propertyStructs, targetStruct)
+		} else {
+			return []reflect.Value{}
+		}
+	}
+
+	return propertyStructs
+}
+
+func mergeStructs(ctx ArchVariantContext, propertyStructs []reflect.Value, propertySet interface{}) interface{} {
+	// Create a new instance of the requested property set
+	value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
+
+	// Merge all the structs together
+	for _, propertyStruct := range propertyStructs {
+		mergePropertyStruct(ctx, value, propertyStruct)
+	}
+
+	return value
+}
+
+func printArchTypeStarlarkDict(dict map[ArchType][]string) string {
+	valDict := make(map[string]string, len(dict))
+	for k, v := range dict {
+		valDict[k.String()] = starlark_fmt.PrintStringList(v, 1)
+	}
+	return starlark_fmt.PrintDict(valDict, 0)
+}
+
+func printArchTypeNestedStarlarkDict(dict map[ArchType]map[string][]string) string {
+	valDict := make(map[string]string, len(dict))
+	for k, v := range dict {
+		valDict[k.String()] = starlark_fmt.PrintStringListDict(v, 1)
+	}
+	return starlark_fmt.PrintDict(valDict, 0)
+}
+
+func StarlarkArchConfigurations() string {
+	return fmt.Sprintf(`
+_arch_to_variants = %s
+
+_arch_to_cpu_variants = %s
+
+_arch_to_features = %s
+
+_android_arch_feature_for_arch_variant = %s
+
+arch_to_variants = _arch_to_variants
+arch_to_cpu_variants = _arch_to_cpu_variants
+arch_to_features = _arch_to_features
+android_arch_feature_for_arch_variants = _android_arch_feature_for_arch_variant
+`, printArchTypeStarlarkDict(archVariants),
+		printArchTypeStarlarkDict(cpuVariants),
+		printArchTypeStarlarkDict(archFeatures),
+		printArchTypeNestedStarlarkDict(androidArchFeatureMap),
+	)
 }
diff --git a/android/arch_list.go b/android/arch_list.go
index d68a0d1..cbf8e7a 100644
--- a/android/arch_list.go
+++ b/android/arch_list.go
@@ -14,46 +14,18 @@
 
 package android
 
-import "fmt"
-
 var archVariants = map[ArchType][]string{
 	Arm: {
 		"armv7-a",
 		"armv7-a-neon",
 		"armv8-a",
 		"armv8-2a",
-		"cortex-a7",
-		"cortex-a8",
-		"cortex-a9",
-		"cortex-a15",
-		"cortex-a53",
-		"cortex-a53-a57",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"krait",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
 	},
 	Arm64: {
-		"armv8_a",
-		"armv8_a_branchprot",
-		"armv8_2a",
+		"armv8-a",
+		"armv8-a-branchprot",
+		"armv8-2a",
 		"armv8-2a-dotprod",
-		"cortex-a53",
-		"cortex-a55",
-		"cortex-a72",
-		"cortex-a73",
-		"cortex-a75",
-		"cortex-a76",
-		"kryo",
-		"kryo385",
-		"exynos-m1",
-		"exynos-m2",
 	},
 	X86: {
 		"amberlake",
@@ -87,6 +59,41 @@
 	},
 }
 
+var cpuVariants = map[ArchType][]string{
+	Arm: {
+		"cortex-a7",
+		"cortex-a8",
+		"cortex-a9",
+		"cortex-a15",
+		"cortex-a53",
+		"cortex-a53.a57",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"krait",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	Arm64: {
+		"cortex-a53",
+		"cortex-a55",
+		"cortex-a72",
+		"cortex-a73",
+		"cortex-a75",
+		"cortex-a76",
+		"kryo",
+		"kryo385",
+		"exynos-m1",
+		"exynos-m2",
+	},
+	X86:    {},
+	X86_64: {},
+}
+
 var archFeatures = map[ArchType][]string{
 	Arm: {
 		"neon",
@@ -119,7 +126,7 @@
 	},
 }
 
-var archFeatureMap = map[ArchType]map[string][]string{
+var androidArchFeatureMap = map[ArchType]map[string][]string{
 	Arm: {
 		"armv7-a-neon": {
 			"neon",
@@ -270,6 +277,13 @@
 		},
 	},
 	X86_64: {
+		"" /*default */ : {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"popcnt",
+		},
 		"amberlake": {
 			"ssse3",
 			"sse4",
@@ -389,23 +403,3 @@
 		},
 	},
 }
-
-var defaultArchFeatureMap = map[OsType]map[ArchType][]string{}
-
-// RegisterDefaultArchVariantFeatures is called by files that define Toolchains to specify the
-// arch features that are available for the default arch variant.  It must be called from an
-// init() function.
-func RegisterDefaultArchVariantFeatures(os OsType, arch ArchType, features ...string) {
-	checkCalledFromInit()
-
-	for _, feature := range features {
-		if !InList(feature, archFeatures[arch]) {
-			panic(fmt.Errorf("Invalid feature %q for arch %q variant \"\"", feature, arch))
-		}
-	}
-
-	if defaultArchFeatureMap[os] == nil {
-		defaultArchFeatureMap[os] = make(map[ArchType][]string)
-	}
-	defaultArchFeatureMap[os][arch] = features
-}
diff --git a/android/arch_test.go b/android/arch_test.go
index 3aa4779..dd0b115 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -473,3 +473,185 @@
 		})
 	}
 }
+
+type testArchPropertiesModule struct {
+	ModuleBase
+	properties struct {
+		A []string `android:"arch_variant"`
+	}
+}
+
+func (testArchPropertiesModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+func TestArchProperties(t *testing.T) {
+	bp := `
+		module {
+			name: "foo",
+			a: ["root"],
+			arch: {
+				arm: {
+					a:  ["arm"],
+				},
+				arm64: {
+					a:  ["arm64"],
+				},
+				x86: { a:  ["x86"] },
+				x86_64: { a:  ["x86_64"] },
+			},
+			multilib: {
+				lib32: { a:  ["lib32"] },
+				lib64: { a:  ["lib64"] },
+			},
+			target: {
+				bionic: { a:  ["bionic"] },
+				host: { a: ["host"] },
+				android: { a:  ["android"] },
+				glibc: { a:  ["glibc"] },
+				musl: { a:  ["musl"] },
+				linux_bionic: { a:  ["linux_bionic"] },
+				linux: { a:  ["linux"] },
+				host_linux: { a: ["host_linux"] },
+				linux_glibc: { a:  ["linux_glibc"] },
+				linux_musl: { a:  ["linux_musl"] },
+				windows: { a:  ["windows"], enabled: true },
+				darwin: { a:  ["darwin"] },
+				not_windows: { a:  ["not_windows"] },
+				android32: { a:  ["android32"] },
+				android64: { a:  ["android64"] },
+				android_arm: { a:  ["android_arm"] },
+				android_arm64: { a:  ["android_arm64"] },
+				linux_x86: { a:  ["linux_x86"] },
+				linux_x86_64: { a:  ["linux_x86_64"] },
+				linux_glibc_x86: { a:  ["linux_glibc_x86"] },
+				linux_glibc_x86_64: { a:  ["linux_glibc_x86_64"] },
+				linux_musl_x86: { a:  ["linux_musl_x86"] },
+				linux_musl_x86_64: { a:  ["linux_musl_x86_64"] },
+				darwin_x86_64: { a:  ["darwin_x86_64"] },
+				windows_x86: { a:  ["windows_x86"] },
+				windows_x86_64: { a:  ["windows_x86_64"] },
+			},
+		}
+	`
+
+	type result struct {
+		module   string
+		variant  string
+		property []string
+	}
+
+	testCases := []struct {
+		name     string
+		goOS     string
+		preparer FixturePreparer
+		results  []result
+	}{
+		{
+			name: "default",
+			results: []result{
+				{
+					module:   "foo",
+					variant:  "android_arm64_armv8-a",
+					property: []string{"root", "linux", "bionic", "android", "android64", "arm64", "lib64", "android_arm64"},
+				},
+				{
+					module:   "foo",
+					variant:  "android_arm_armv7-a-neon",
+					property: []string{"root", "linux", "bionic", "android", "android64", "arm", "lib32", "android_arm"},
+				},
+			},
+		},
+		{
+			name: "linux",
+			goOS: "linux",
+			results: []result{
+				{
+					module:   "foo",
+					variant:  "linux_glibc_x86_64",
+					property: []string{"root", "host", "linux", "host_linux", "glibc", "linux_glibc", "not_windows", "x86_64", "lib64", "linux_x86_64", "linux_glibc_x86_64"},
+				},
+				{
+					module:   "foo",
+					variant:  "linux_glibc_x86",
+					property: []string{"root", "host", "linux", "host_linux", "glibc", "linux_glibc", "not_windows", "x86", "lib32", "linux_x86", "linux_glibc_x86"},
+				},
+			},
+		},
+		{
+			name: "windows",
+			goOS: "linux",
+			preparer: FixtureModifyConfig(func(config Config) {
+				config.Targets[Windows] = []Target{
+					{Windows, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", true},
+					{Windows, Arch{ArchType: X86}, NativeBridgeDisabled, "", "", true},
+				}
+			}),
+			results: []result{
+				{
+					module:   "foo",
+					variant:  "windows_x86_64",
+					property: []string{"root", "host", "windows", "x86_64", "lib64", "windows_x86_64"},
+				},
+				{
+					module:   "foo",
+					variant:  "windows_x86",
+					property: []string{"root", "host", "windows", "x86", "lib32", "windows_x86"},
+				},
+			},
+		},
+		{
+			name:     "linux_musl",
+			goOS:     "linux",
+			preparer: FixtureModifyConfig(modifyTestConfigForMusl),
+			results: []result{
+				{
+					module:   "foo",
+					variant:  "linux_musl_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_musl", "not_windows", "x86", "lib32", "linux_x86", "linux_musl_x86"},
+				},
+			},
+		},
+		{
+			name: "darwin",
+			goOS: "darwin",
+			results: []result{
+				{
+					module:   "foo",
+					variant:  "darwin_x86_64",
+					property: []string{"root", "host", "darwin", "not_windows", "x86_64", "lib64", "darwin_x86_64"},
+				},
+			},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.goOS != "" && tt.goOS != runtime.GOOS {
+				t.Skipf("test requires runtime.GOOS==%s, got %s", tt.goOS, runtime.GOOS)
+			}
+			result := GroupFixturePreparers(
+				PrepareForTestWithArchMutator,
+				OptionalFixturePreparer(tt.preparer),
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("module", func() Module {
+						module := &testArchPropertiesModule{}
+						module.AddProperties(&module.properties)
+						InitAndroidArchModule(module, HostAndDeviceDefault, MultilibBoth)
+						return module
+					})
+				}),
+			).RunTestWithBp(t, bp)
+
+			for _, want := range tt.results {
+				t.Run(want.module+"_"+want.variant, func(t *testing.T) {
+					got := result.ModuleForTests(want.module, want.variant).Module().(*testArchPropertiesModule).properties.A
+					AssertArrayString(t, "arch mutator property", want.property, got)
+				})
+			}
+		})
+	}
+}
diff --git a/android/bazel.go b/android/bazel.go
index 9621f3e..4ef8d78 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"bufio"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
@@ -22,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 {
@@ -39,6 +50,10 @@
 	// To opt-out a module, set bazel_module: { bp2build_available: false }
 	// To defer the default setting for the directory, do not set the value.
 	Bp2build_available *bool
+
+	// CanConvertToBazel is set via InitBazelModule to indicate that a module type can be converted to
+	// Bazel with Bp2build.
+	CanConvertToBazel bool `blueprint:"mutated"`
 }
 
 // Properties contains common module properties for Bazel migration purposes.
@@ -48,10 +63,30 @@
 	Bazel_module bazelModuleProperties
 }
 
+// namespacedVariableProperties is a map from a string representing a Soong
+// config variable namespace, like "android" or "vendor_name" to a slice of
+// pointer to a struct containing a single field called Soong_config_variables
+// whose value mirrors the structure in the Blueprint file.
+type namespacedVariableProperties map[string][]interface{}
+
 // BazelModuleBase contains the property structs with metadata for modules which can be converted to
 // Bazel.
 type BazelModuleBase struct {
 	bazelProperties properties
+
+	// namespacedVariableProperties is used for soong_config_module_type support
+	// in bp2build. Soong config modules allow users to set module properties
+	// based on custom product variables defined in Android.bp files. These
+	// variables are namespaced to prevent clobbering, especially when set from
+	// Makefiles.
+	namespacedVariableProperties namespacedVariableProperties
+
+	// baseModuleType is set when this module was created from a module type
+	// defined by a soong_config_module_type. Every soong_config_module_type
+	// "wraps" another module type, e.g. a soong_config_module_type can wrap a
+	// cc_defaults to a custom_cc_defaults, or cc_binary to a custom_cc_binary.
+	// This baseModuleType is set to the wrapped module type.
+	baseModuleType string
 }
 
 // Bazelable is specifies the interface for modules that can be converted to Bazel.
@@ -60,9 +95,24 @@
 	HasHandcraftedLabel() bool
 	HandcraftedLabel() string
 	GetBazelLabel(ctx BazelConversionPathContext, module blueprint.Module) string
-	ConvertWithBp2build(ctx BazelConversionPathContext) bool
+	ShouldConvertWithBp2build(ctx BazelConversionContext) bool
+	shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool
 	GetBazelBuildFileContents(c Config, path, name string) (string, error)
-	ConvertedToBazel(ctx BazelConversionPathContext) bool
+	ConvertWithBp2build(ctx TopDownMutatorContext)
+
+	// namespacedVariableProps is a map from a soong config variable namespace
+	// (e.g. acme, android) to a map of interfaces{}, which are really
+	// reflect.Struct pointers, representing the value of the
+	// soong_config_variables property of a module. The struct pointer is the
+	// one with the single member called Soong_config_variables, which itself is
+	// a struct containing fields for each supported feature in that namespace.
+	//
+	// The reason for using an slice of interface{} is to support defaults
+	// propagation of the struct pointers.
+	namespacedVariableProps() namespacedVariableProperties
+	setNamespacedVariableProps(props namespacedVariableProperties)
+	BaseModuleType() string
+	SetBaseModuleType(baseModuleType string)
 }
 
 // BazelModule is a lightweight wrapper interface around Module for Bazel-convertible modules.
@@ -75,6 +125,7 @@
 // properties.
 func InitBazelModule(module BazelModule) {
 	module.AddProperties(module.bazelProps())
+	module.bazelProps().Bazel_module.CanConvertToBazel = true
 }
 
 // bazelProps returns the Bazel properties for the given BazelModuleBase.
@@ -82,6 +133,22 @@
 	return &b.bazelProperties
 }
 
+func (b *BazelModuleBase) namespacedVariableProps() namespacedVariableProperties {
+	return b.namespacedVariableProperties
+}
+
+func (b *BazelModuleBase) setNamespacedVariableProps(props namespacedVariableProperties) {
+	b.namespacedVariableProperties = props
+}
+
+func (b *BazelModuleBase) BaseModuleType() string {
+	return b.baseModuleType
+}
+
+func (b *BazelModuleBase) SetBaseModuleType(baseModuleType string) {
+	b.baseModuleType = baseModuleType
+}
+
 // HasHandcraftedLabel returns whether this module has a handcrafted Bazel label.
 func (b *BazelModuleBase) HasHandcraftedLabel() bool {
 	return b.bazelProperties.Bazel_module.Label != nil
@@ -97,242 +164,283 @@
 	if b.HasHandcraftedLabel() {
 		return b.HandcraftedLabel()
 	}
-	if b.ConvertWithBp2build(ctx) {
+	if b.ShouldConvertWithBp2build(ctx) {
 		return bp2buildModuleLabel(ctx, module)
 	}
 	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
-
-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: false.
-	// allows modules to opt-in.
-	Bp2BuildDefaultFalse
-)
-
-var (
-	// Do not write BUILD files for these directories
-	// NOTE: this is not recursive
-	bp2buildDoNotWriteBuildFileList = []string{
-		// Don't generate these BUILD files - because external BUILD files already exist
-		"external/boringssl",
-		"external/brotli",
-		"external/dagger2",
-		"external/flatbuffers",
-		"external/gflags",
-		"external/google-fruit",
-		"external/grpc-grpc",
-		"external/grpc-grpc/test/core/util",
-		"external/grpc-grpc/test/cpp/common",
-		"external/grpc-grpc/third_party/address_sorting",
-		"external/nanopb-c",
-		"external/nos/host/generic",
-		"external/nos/host/generic/libnos",
-		"external/nos/host/generic/libnos/generator",
-		"external/nos/host/generic/libnos_datagram",
-		"external/nos/host/generic/libnos_transport",
-		"external/nos/host/generic/nugget/proto",
-		"external/perfetto",
-		"external/protobuf",
-		"external/rust/cxx",
-		"external/rust/cxx/demo",
-		"external/ruy",
-		"external/tensorflow",
-		"external/tensorflow/tensorflow/lite",
-		"external/tensorflow/tensorflow/lite/java",
-		"external/tensorflow/tensorflow/lite/kernels",
-		"external/tflite-support",
-		"external/tinyalsa_new",
-		"external/wycheproof",
-		"external/libyuv",
-	}
-
+type bp2BuildConversionAllowlist struct {
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
-	bp2buildDefaultConfig = Bp2BuildConfig{
-		"bionic":                Bp2BuildDefaultTrueRecursively,
-		"external/gwp_asan":     Bp2BuildDefaultTrueRecursively,
-		"system/core/libcutils": Bp2BuildDefaultTrueRecursively,
-		"system/core/property_service/libpropertyinfoparser": Bp2BuildDefaultTrueRecursively,
-		"system/libbase":                  Bp2BuildDefaultTrueRecursively,
-		"system/logging/liblog":           Bp2BuildDefaultTrueRecursively,
-		"external/jemalloc_new":           Bp2BuildDefaultTrueRecursively,
-		"external/fmtlib":                 Bp2BuildDefaultTrueRecursively,
-		"external/arm-optimized-routines": Bp2BuildDefaultTrueRecursively,
-		"external/scudo":                  Bp2BuildDefaultTrueRecursively,
-	}
+	defaultConfig allowlists.Bp2BuildConfig
+
+	// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
+	// in the synthetic Bazel workspace.
+	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
+	// conversion.
+	//
+	// A module can either be in this list or its directory allowlisted entirely
+	// in bp2buildDefaultConfig, but not both at the same time.
+	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.
+	moduleTypeAlwaysConvert map[string]bool
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
-	bp2buildModuleDoNotConvertList = []string{
-		// Things that transitively depend on unconverted libc_* modules.
-		"libc_nopthread", // http://b/186821550, cc_library_static, depends on //bionic/libc:libc_bionic_ndk (http://b/186822256)
-		//                                                     also depends on //bionic/libc:libc_tzcode (http://b/186822591)
-		//                                                     also depends on //bionic/libc:libstdc++ (http://b/186822597)
-		"libc_common",        // http://b/186821517, cc_library_static, depends on //bionic/libc:libc_nopthread (http://b/186821550)
-		"libc_common_static", // http://b/186824119, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-		"libc_common_shared", // http://b/186824118, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-		"libc_nomalloc",      // http://b/186825031, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-
-		"libbionic_spawn_benchmark", // http://b/186824595, cc_library_static, depends on //external/google-benchmark (http://b/186822740)
-		//                                                                also depends on //system/logging/liblog:liblog (http://b/186822772)
-
-		"libc_malloc_debug",           // http://b/186824339, cc_library_static, depends on //system/libbase:libbase (http://b/186823646)
-		"libc_malloc_debug_backtrace", // http://b/186824112, cc_library_static, depends on //external/libcxxabi:libc++demangle (http://b/186823773)
-
-		"libcutils",         // http://b/186827426, cc_library, depends on //system/core/libprocessgroup:libprocessgroup_headers (http://b/186826841)
-		"libcutils_sockets", // http://b/186826853, cc_library, depends on //system/libbase:libbase (http://b/186826479)
-
-		"liblinker_debuggerd_stub", // http://b/186824327, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                               also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                               also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_main", // http://b/186825989, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                     also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                     also depends on//system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_malloc", // http://b/186826466, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                       also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                       also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"libc_jemalloc_wrapper", // http://b/187012490, cc_library_static, depends on //external/jemalloc_new:libjemalloc5 (http://b/186828626)
-		"libc_ndk",              // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
-		"libc",                  // http://b/183064430, cc_library, depends on //external/jemalloc_new:libjemalloc5 (http://b/186828626)
-		"libc_bionic_ndk",       // http://b/186822256, cc_library_static, fatal error: 'generated_android_ids.h' file not found
-		"libc_malloc_hooks",     // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
-		"libm",                  // http://b/183064661, cc_library, math.h:25:16: error: unexpected token in argument list
-
-		// http://b/186823769: Needs C++ STL support, includes from unconverted standard libraries in //external/libcxx
-		// c++_static
-		"libbase_ndk", // http://b/186826477, cc_library, no such target '//build/bazel/platforms/os:darwin' when --platforms //build/bazel/platforms:android_x86 is added
-		// libcxx
-		"libBionicBenchmarksUtils", // cc_library_static, fatal error: 'map' file not found, from libcxx
-		"fmtlib",                   // cc_library_static, fatal error: 'cassert' file not found, from libcxx
-		"libbase",                  // http://b/186826479, cc_library, fatal error: 'memory' file not found, from libcxx
-
-		// http://b/186024507: Includes errors because of the system_shared_libs default value.
-		// Missing -isystem bionic/libc/include through the libc/libm/libdl
-		// default dependencies if system_shared_libs is unset.
-		"liblog",                 // http://b/186822772: cc_library, 'sys/cdefs.h' file not found
-		"libjemalloc5_jet",       // cc_library, 'sys/cdefs.h' file not found
-		"libseccomp_policy",      // http://b/186476753: cc_library, 'linux/filter.h' not found
-		"note_memtag_heap_async", // http://b/185127353: cc_library_static, error: feature.h not found
-		"note_memtag_heap_sync",  // http://b/185127353: cc_library_static, error: feature.h not found
-
-		// Tests. Handle later.
-		"libbionic_tests_headers_posix", // http://b/186024507, cc_library_static, sched.h, time.h not found
-		"libjemalloc5_integrationtest",
-		"libjemalloc5_stresstestlib",
-		"libjemalloc5_unittest",
-	}
+	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{
-		"libstdc++", // http://b/186822597, cc_library, ld.lld: error: undefined symbol: __errno
-	}
+	ccLibraryStaticOnly map[string]bool
 
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
-	mixedBuildsDisabledList = []string{
-		"libc_netbsd",                      // lberki@, cc_library_static, version script assignment of 'LIBC_PRIVATE' to symbol 'SHA1Final' failed: symbol not defined
-		"libc_openbsd",                     // ruperts@, cc_library_static, OK for bp2build but error: duplicate symbol: strcpy for mixed builds
-		"libsystemproperties",              // cparsons@, cc_library_static, wrong include paths
-		"libpropertyinfoparser",            // cparsons@, cc_library_static, wrong include paths
-		"libarm-optimized-routines-string", // jingwen@, cc_library_static, OK for bp2build but b/186615213 (asflags not handled in  bp2build), version script assignment of 'LIBC' to symbol 'memcmp' failed: symbol not defined (also for memrchr, strnlen)
-		"fmtlib_ndk",                       // http://b/187040371, cc_library_static, OK for bp2build but format-inl.h:11:10: fatal error: 'cassert' file not found for mixed builds
-	}
+	mixedBuildsDisabled map[string]bool
+}
 
-	// Used for quicker lookups
-	bp2buildDoNotWriteBuildFile = map[string]bool{}
-	bp2buildModuleDoNotConvert  = map[string]bool{}
-	bp2buildCcLibraryStaticOnly = map[string]bool{}
-	mixedBuildsDisabled         = map[string]bool{}
-)
-
-func init() {
-	for _, moduleName := range bp2buildDoNotWriteBuildFileList {
-		bp2buildDoNotWriteBuildFile[moduleName] = 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{},
 	}
 }
 
-func GenerateCcLibraryStaticOnly(ctx BazelConversionPathContext) bool {
-	return bp2buildCcLibraryStaticOnly[ctx.Module().Name()]
+// 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
 }
 
-func ShouldWriteBuildFileForDir(dir string) bool {
-	if _, ok := bp2buildDoNotWriteBuildFile[dir]; ok {
-		return false
-	} else {
+// 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 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 {
+	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 allowlist.keepExistingBuildFile {
+		if recursive {
+			if strings.HasPrefix(dir, prefix+"/") {
+				return true
+			}
+		}
+	}
+	// Default
+	return false
 }
 
 // MixedBuildsEnabled checks that a module is ready to be replaced by a
 // converted or handcrafted Bazel target.
-func (b *BazelModuleBase) MixedBuildsEnabled(ctx BazelConversionPathContext) bool {
+func (b *BazelModuleBase) MixedBuildsEnabled(ctx ModuleContext) bool {
+	if ctx.Os() == Windows {
+		// Windows toolchains are not currently supported.
+		return false
+	}
+	if !ctx.Module().Enabled() {
+		return false
+	}
 	if !ctx.Config().BazelContext.BazelEnabled() {
 		return false
 	}
-	if len(b.GetBazelLabel(ctx, ctx.Module())) == 0 {
+	if !convertedToBazel(ctx, ctx.Module()) {
 		return false
 	}
-	if GenerateCcLibraryStaticOnly(ctx) {
+
+	if GenerateCcLibraryStaticOnly(ctx.Module().Name()) {
 		// Don't use partially-converted cc_library targets in mixed builds,
 		// since mixed builds would generally rely on both static and shared
 		// variants of a cc_library.
 		return false
 	}
-	return !mixedBuildsDisabled[ctx.Module().Name()]
+	return !bp2buildAllowlist.mixedBuildsDisabled[ctx.Module().Name()]
 }
 
-// ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
-func (b *BazelModuleBase) ConvertWithBp2build(ctx BazelConversionPathContext) bool {
-	if bp2buildModuleDoNotConvert[ctx.Module().Name()] {
+// ConvertedToBazel returns whether this module has been converted (with bp2build or manually) to Bazel.
+func convertedToBazel(ctx BazelConversionContext, module blueprint.Module) bool {
+	b, ok := module.(Bazelable)
+	if !ok {
+		return false
+	}
+	return b.shouldConvertWithBp2build(ctx, module) || b.HasHandcraftedLabel()
+}
+
+// ShouldConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build
+func (b *BazelModuleBase) ShouldConvertWithBp2build(ctx BazelConversionContext) bool {
+	return b.shouldConvertWithBp2build(ctx, ctx.Module())
+}
+
+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
+}
+
+func (b *BazelModuleBase) shouldConvertWithBp2build(ctx bazelOtherModuleContext, module blueprint.Module) bool {
+	if !b.bazelProps().Bazel_module.CanConvertToBazel {
 		return false
 	}
 
-	// Ensure that the module type of this module has a bp2build converter. This
-	// prevents mixed builds from using auto-converted modules just by matching
-	// the package dir; it also has to have a bp2build mutator as well.
-	if ctx.Config().bp2buildModuleTypeConfig[ctx.ModuleType()] == false {
+	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 == 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
 	}
 
-	packagePath := ctx.ModuleDir()
-	config := ctx.Config().bp2buildPackageConfig
+	if allowlist.moduleDoNotConvert[moduleName] {
+		if moduleNameAllowed {
+			ctx.ModuleErrorf("a module cannot be in moduleDoNotConvert and also be in moduleAlwaysConvert")
+		}
+		return false
+	}
+
+	if allowlistConvert && shouldKeepExistingBuildFileForDir(allowlist, packagePath) {
+		if moduleNameAllowed {
+			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.
-	propValue := b.bazelProperties.Bazel_module.Bp2build_available
-	if bp2buildDefaultTrueRecursively(packagePath, config) {
+	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.
 		return proptools.BoolDefault(propValue, true)
 	}
 
 	// Allow modules to explicitly opt-in.
-	return proptools.BoolDefault(propValue, false)
+	return proptools.BoolDefault(propValue, allowlistConvert)
 }
 
 // bp2buildDefaultTrueRecursively checks that the package contains a prefix from the
@@ -345,15 +453,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
-
-	// Return exact matches in the config.
-	if config[packagePath] == Bp2BuildDefaultTrueRecursively {
-		return true
-	}
-	if config[packagePath] == Bp2BuildDefaultFalse {
-		return 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] == 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.
@@ -361,15 +470,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
@@ -394,8 +503,35 @@
 	return string(data[:]), nil
 }
 
-// ConvertedToBazel returns whether this module has been converted to Bazel, whether automatically
-// or manually
-func (b *BazelModuleBase) ConvertedToBazel(ctx BazelConversionPathContext) bool {
-	return b.ConvertWithBp2build(ctx) || b.HasHandcraftedLabel()
+func registerBp2buildConversionMutator(ctx RegisterMutatorsContext) {
+	ctx.TopDown("bp2build_conversion", convertWithBp2build).Parallel()
+}
+
+func convertWithBp2build(ctx TopDownMutatorContext) {
+	bModule, ok := ctx.Module().(Bazelable)
+	if !ok || !bModule.shouldConvertWithBp2build(ctx, ctx.Module()) {
+		return
+	}
+
+	bModule.ConvertWithBp2build(ctx)
+}
+
+// GetMainClassInManifest scans the manifest file specified in filepath and returns
+// the value of attribute Main-Class in the manifest file if it exists, or returns error.
+// WARNING: this is for bp2build converters of java_* modules only.
+func GetMainClassInManifest(c Config, filepath string) (string, error) {
+	file, err := c.fs.Open(filepath)
+	if err != nil {
+		return "", err
+	}
+	defer file.Close()
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.HasPrefix(line, "Main-Class:") {
+			return strings.TrimSpace(line[len("Main-Class:"):]), nil
+		}
+	}
+
+	return "", errors.New("Main-Class is not found.")
 }
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 8cddbb2..d851a98 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -27,11 +27,9 @@
 	"sync"
 
 	"android/soong/bazel/cquery"
-
-	"github.com/google/blueprint/bootstrap"
+	"android/soong/shared"
 
 	"android/soong/bazel"
-	"android/soong/shared"
 )
 
 type cqueryRequest interface {
@@ -50,24 +48,44 @@
 	StarlarkFunctionBody() string
 }
 
+// Portion of cquery map key to describe target configuration.
+type configKey struct {
+	arch   string
+	osType OsType
+}
+
 // Map key to describe bazel cquery requests.
 type cqueryKey struct {
 	label       string
 	requestType cqueryRequest
-	archType    ArchType
+	configKey   configKey
+}
+
+// bazelHandler is the interface for a helper object related to deferring to Bazel for
+// processing a module (during Bazel mixed builds). Individual module types should define
+// their own bazel handler if they support deferring to Bazel.
+type BazelHandler interface {
+	// Issue query to Bazel to retrieve information about Bazel's view of the current module.
+	// If Bazel returns this information, set module properties on the current module to reflect
+	// the returned information.
+	// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
+	GenerateBazelBuildActions(ctx ModuleContext, label string) bool
 }
 
 type BazelContext interface {
-	// The below methods involve queuing cquery requests to be later invoked
+	// The methods below involve queuing cquery requests to be later invoked
 	// by bazel. If any of these methods return (_, false), then the request
 	// has been queued to be run later.
 
 	// Returns result files built by building the given bazel target label.
-	GetOutputFiles(label string, archType ArchType) ([]string, bool)
+	GetOutputFiles(label string, cfgKey configKey) ([]string, bool)
 
 	// TODO(cparsons): Other cquery-related methods should be added here.
 	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
-	GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error)
+	GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error)
+
+	// Returns the executable binary resultant from building together the python sources
+	GetPythonBinary(label string, cfgKey configKey) (string, bool)
 
 	// ** End cquery methods
 
@@ -94,7 +112,7 @@
 	bazelPath    string
 	outputBase   string
 	workspaceDir string
-	buildDir     string
+	soongOutDir  string
 	metricsDir   string
 }
 
@@ -123,20 +141,26 @@
 type MockBazelContext struct {
 	OutputBaseDir string
 
-	LabelToOutputFiles map[string][]string
-	LabelToCcInfo      map[string]cquery.CcInfo
+	LabelToOutputFiles  map[string][]string
+	LabelToCcInfo       map[string]cquery.CcInfo
+	LabelToPythonBinary map[string]string
 }
 
-func (m MockBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
+func (m MockBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
 	result, ok := m.LabelToOutputFiles[label]
 	return result, ok
 }
 
-func (m MockBazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
+func (m MockBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
 	result, ok := m.LabelToCcInfo[label]
 	return result, ok, nil
 }
 
+func (m MockBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
+	result, ok := m.LabelToPythonBinary[label]
+	return result, ok
+}
+
 func (m MockBazelContext) InvokeBazel() error {
 	panic("unimplemented")
 }
@@ -153,8 +177,8 @@
 
 var _ BazelContext = MockBazelContext{}
 
-func (bazelCtx *bazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, archType)
+func (bazelCtx *bazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
+	rawString, ok := bazelCtx.cquery(label, cquery.GetOutputFiles, cfgKey)
 	var ret []string
 	if ok {
 		bazelOutput := strings.TrimSpace(rawString)
@@ -163,8 +187,8 @@
 	return ret, ok
 }
 
-func (bazelCtx *bazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
-	result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, archType)
+func (bazelCtx *bazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
+	result, ok := bazelCtx.cquery(label, cquery.GetCcInfo, cfgKey)
 	if !ok {
 		return cquery.CcInfo{}, ok, nil
 	}
@@ -174,15 +198,25 @@
 	return ret, ok, err
 }
 
-func (n noopBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
+func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
+	rawString, ok := bazelCtx.cquery(label, cquery.GetPythonBinary, cfgKey)
+	var ret string
+	if ok {
+		bazelOutput := strings.TrimSpace(rawString)
+		ret = cquery.GetPythonBinary.ParseResult(bazelOutput)
+	}
+	return ret, ok
+}
+
+func (n noopBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, bool) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetCcInfo(label string, archType ArchType) (cquery.CcInfo, bool, error) {
+func (n noopBazelContext) GetCcInfo(label string, cfgKey configKey) (cquery.CcInfo, bool, error) {
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool) {
+func (n noopBazelContext) GetPythonBinary(label string, cfgKey configKey) (string, bool) {
 	panic("unimplemented")
 }
 
@@ -205,7 +239,7 @@
 func NewBazelContext(c *config) (BazelContext, error) {
 	// TODO(cparsons): Assess USE_BAZEL=1 instead once "mixed Soong/Bazel builds"
 	// are production ready.
-	if c.Getenv("USE_BAZEL_ANALYSIS") != "1" {
+	if !c.IsEnvTrue("USE_BAZEL_ANALYSIS") {
 		return noopBazelContext{}, nil
 	}
 
@@ -222,7 +256,7 @@
 
 func bazelPathsFromConfig(c *config) (*bazelPaths, error) {
 	p := bazelPaths{
-		buildDir: c.buildDir,
+		soongOutDir: c.soongOutDir,
 	}
 	missingEnvVars := []string{}
 	if len(c.Getenv("BAZEL_HOME")) > 1 {
@@ -271,8 +305,8 @@
 // returns (result, true). If the request is queued but no results are available,
 // then returns ("", false).
 func (context *bazelContext) cquery(label string, requestType cqueryRequest,
-	archType ArchType) (string, bool) {
-	key := cqueryKey{label, requestType, archType}
+	cfgKey configKey) (string, bool) {
+	key := cqueryKey{label, requestType, cfgKey}
 	if result, ok := context.results[key]; ok {
 		return result, true
 	} else {
@@ -321,7 +355,16 @@
 // the invocation returned an error code.
 func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
 	extraFlags ...string) (string, string, error) {
-	cmdFlags := []string{"--output_base=" + absolutePath(paths.outputBase), command.command}
+	cmdFlags := []string{
+		// --noautodetect_server_javabase has the practical consequence of preventing Bazel from
+		// attempting to download rules_java, which is incompatible with
+		// --experimental_repository_disable_download set further below.
+		// rules_java is also not needed until mixed builds start building java targets.
+		// TODO(b/197958133): Once rules_java is pulled into AOSP, remove this flag.
+		"--noautodetect_server_javabase",
+		"--output_base=" + absolutePath(paths.outputBase),
+		command.command,
+	}
 	cmdFlags = append(cmdFlags, command.expression)
 	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(paths, runName))
 
@@ -333,7 +376,7 @@
 	// The actual platform values here may be overridden by configuration
 	// transitions from the buildroot.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_x86_64"))
+		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_target"))
 	cmdFlags = append(cmdFlags,
 		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"))
 	// This should be parameterized on the host OS, but let's restrict to linux
@@ -350,7 +393,10 @@
 	bazelCmd.Env = append(os.Environ(),
 		"HOME="+paths.homeDir,
 		pwdPrefix(),
-		"BUILD_DIR="+absolutePath(paths.buildDir),
+		"BUILD_DIR="+absolutePath(paths.soongOutDir),
+		// Make OUT_DIR absolute here so tools/bazel.sh uses the correct
+		// OUT_DIR at <root>/out, instead of <root>/out/soong/workspace/out.
+		"OUT_DIR="+absolutePath(paths.outDir()),
 		// Disables local host detection of gcc; toolchain information is defined
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
@@ -375,7 +421,7 @@
 
 def _config_node_transition_impl(settings, attr):
     return {
-        "//command_line_option:platforms": "@//build/bazel/platforms:android_%s" % attr.arch,
+        "//command_line_option:platforms": "@//build/bazel/platforms:%s_%s" % (attr.os, attr.arch),
     }
 
 _config_node_transition = transition(
@@ -393,7 +439,8 @@
     implementation = _passthrough_rule_impl,
     attrs = {
         "arch" : attr.string(mandatory = True),
-        "deps" : attr.label_list(cfg = _config_node_transition),
+        "os"   : attr.string(mandatory = True),
+        "deps" : attr.label_list(cfg = _config_node_transition, allow_files = True),
         "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
     },
 )
@@ -444,27 +491,35 @@
 	configNodeFormatString := `
 config_node(name = "%s",
     arch = "%s",
+    os = "%s",
     deps = [%s],
 )
 `
 
 	configNodesSection := ""
 
-	labelsByArch := map[string][]string{}
+	labelsByConfig := map[string][]string{}
 	for val, _ := range context.requests {
 		labelString := fmt.Sprintf("\"@%s\"", val.label)
-		archString := getArchString(val)
-		labelsByArch[archString] = append(labelsByArch[archString], labelString)
+		configString := getConfigString(val)
+		labelsByConfig[configString] = append(labelsByConfig[configString], labelString)
 	}
 
-	configNodeLabels := []string{}
-	for archString, labels := range labelsByArch {
-		configNodeLabels = append(configNodeLabels, fmt.Sprintf("\":%s\"", archString))
+	allLabels := []string{}
+	for configString, labels := range labelsByConfig {
+		configTokens := strings.Split(configString, "|")
+		if len(configTokens) != 2 {
+			panic(fmt.Errorf("Unexpected config string format: %s", configString))
+		}
+		archString := configTokens[0]
+		osString := configTokens[1]
+		targetString := fmt.Sprintf("%s_%s", osString, archString)
+		allLabels = append(allLabels, fmt.Sprintf("\":%s\"", targetString))
 		labelsString := strings.Join(labels, ",\n            ")
-		configNodesSection += fmt.Sprintf(configNodeFormatString, archString, archString, labelsString)
+		configNodesSection += fmt.Sprintf(configNodeFormatString, targetString, archString, osString, labelsString)
 	}
 
-	return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(configNodeLabels, ",\n            ")))
+	return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(allLabels, ",\n            ")))
 }
 
 func indent(original string) string {
@@ -506,7 +561,7 @@
     return id_string + ">>" + %s(target)
 `
 
-	for requestType, _ := range requestTypeToCqueryIdEntries {
+	for requestType := range requestTypeToCqueryIdEntries {
 		labelMapName := requestType.Name() + "_Labels"
 		functionName := requestType.Name() + "_Fn"
 		labelRegistrationMapSection += fmt.Sprintf(mapDeclarationFormatString,
@@ -529,7 +584,15 @@
 %s
 
 def get_arch(target):
+  # TODO(b/199363072): filegroups and file targets aren't associated with any
+  # specific platform architecture in mixed builds. This is consistent with how
+  # Soong treats filegroups, but it may not be the case with manually-written
+  # filegroup BUILD targets.
   buildoptions = build_options(target)
+  if buildoptions == None:
+    # File targets do not have buildoptions. File targets aren't associated with
+    #  any specific platform architecture in mixed builds, so use the host.
+    return "x86_64|linux"
   platforms = build_options(target)["//command_line_option:platforms"]
   if len(platforms) != 1:
     # An individual configured target should have only one platform architecture.
@@ -539,10 +602,13 @@
   platform_name = build_options(target)["//command_line_option:platforms"][0].name
   if platform_name == "host":
     return "HOST"
-  elif not platform_name.startswith("android_"):
-    fail("expected platform name of the form 'android_<arch>', but was " + str(platforms))
+  elif platform_name.startswith("android_"):
+    return platform_name[len("android_"):] + "|" + platform_name[:len("android_")-1]
+  elif platform_name.startswith("linux_"):
+    return platform_name[len("linux_"):] + "|" + platform_name[:len("linux_")-1]
+  else:
+    fail("expected platform name of the form 'android_<arch>' or 'linux_<arch>', but was " + str(platforms))
     return "UNKNOWN"
-  return platform_name[len("android_"):]
 
 def format(target):
   id_string = str(target.label) + "|" + get_arch(target)
@@ -561,19 +627,24 @@
 // Returns a path containing build-related metadata required for interfacing
 // with Bazel. Example: out/soong/bazel.
 func (p *bazelPaths) intermediatesDir() string {
-	return filepath.Join(p.buildDir, "bazel")
+	return filepath.Join(p.soongOutDir, "bazel")
 }
 
 // Returns the path where the contents of the @soong_injection repository live.
 // It is used by Soong to tell Bazel things it cannot over the command line.
 func (p *bazelPaths) injectedFilesDir() string {
-	return filepath.Join(p.buildDir, "soong_injection")
+	return filepath.Join(p.soongOutDir, bazel.SoongInjectionDirName)
 }
 
 // Returns the path of the synthetic Bazel workspace that contains a symlink
 // forest composed the whole source tree and BUILD files generated by bp2build.
 func (p *bazelPaths) syntheticWorkspaceDir() string {
-	return filepath.Join(p.buildDir, "workspace")
+	return filepath.Join(p.soongOutDir, "workspace")
+}
+
+// Returns the path to the top level out dir ($OUT_DIR).
+func (p *bazelPaths) outDir() string {
+	return filepath.Dir(p.soongOutDir)
 }
 
 // Issues commands to Bazel to receive results for all cquery requests
@@ -593,7 +664,12 @@
 	if err != nil {
 		return err
 	}
-
+	if metricsDir := context.paths.BazelMetricsDir(); metricsDir != "" {
+		err = os.MkdirAll(metricsDir, 0777)
+		if err != nil {
+			return err
+		}
+	}
 	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666)
 	if err != nil {
 		return err
@@ -619,11 +695,12 @@
 	if err != nil {
 		return err
 	}
+
 	buildrootLabel := "@soong_injection//mixed_builds:buildroot"
 	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
 		context.paths,
 		bazel.CqueryBuildRootRunName,
-		bazelCommand{"cquery", fmt.Sprintf("kind(rule, deps(%s))", buildrootLabel)},
+		bazelCommand{"cquery", fmt.Sprintf("deps(%s, 2)", buildrootLabel)},
 		"--output=starlark",
 		"--starlark:file="+absolutePath(cqueryFileRelpath))
 	err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"),
@@ -644,9 +721,9 @@
 		}
 	}
 
-	for val, _ := range context.requests {
+	for val := range context.requests {
 		if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok {
-			context.results[val] = string(cqueryResult)
+			context.results[val] = cqueryResult
 		} else {
 			return fmt.Errorf("missing result for bazel target %s. query output: [%s], cquery err: [%s]",
 				getCqueryId(val), cqueryOutput, cqueryErr)
@@ -715,7 +792,7 @@
 
 	// Add ninja file dependencies for files which all bazel invocations require.
 	bazelBuildList := absolutePath(filepath.Join(
-		filepath.Dir(bootstrap.CmdlineArgs.ModuleListFile), "bazel.list"))
+		filepath.Dir(ctx.Config().moduleListFile), "bazel.list"))
 	ctx.AddNinjaFileDeps(bazelBuildList)
 
 	data, err := ioutil.ReadFile(bazelBuildList)
@@ -734,8 +811,26 @@
 		}
 		rule := NewRuleBuilder(pctx, ctx)
 		cmd := rule.Command()
-		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ && %s",
-			ctx.Config().BazelContext.OutputBase(), buildStatement.Command))
+
+		// cd into Bazel's execution root, which is the action cwd.
+		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ &&", ctx.Config().BazelContext.OutputBase()))
+
+		// Remove old outputs, as some actions might not rerun if the outputs are detected.
+		if len(buildStatement.OutputPaths) > 0 {
+			cmd.Text("rm -f")
+			for _, outputPath := range buildStatement.OutputPaths {
+				cmd.Text(outputPath)
+			}
+			cmd.Text("&&")
+		}
+
+		for _, pair := range buildStatement.Env {
+			// Set per-action env variables, if any.
+			cmd.Flag(pair.Key + "=" + pair.Value)
+		}
+
+		// The actual Bazel action.
+		cmd.Text(" " + buildStatement.Command)
 
 		for _, outputPath := range buildStatement.OutputPaths {
 			cmd.ImplicitOutput(PathForBazelOut(ctx, outputPath))
@@ -748,6 +843,10 @@
 			cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
 		}
 
+		for _, symlinkPath := range buildStatement.SymlinkPaths {
+			cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath))
+		}
+
 		// This is required to silence warnings pertaining to unexpected timestamps. Particularly,
 		// some Bazel builtins (such as files in the bazel_tools directory) have far-future
 		// timestamps. Without restat, Ninja would emit warnings that the input files of a
@@ -759,14 +858,27 @@
 }
 
 func getCqueryId(key cqueryKey) string {
-	return key.label + "|" + getArchString(key)
+	return key.label + "|" + getConfigString(key)
 }
 
-func getArchString(key cqueryKey) string {
-	arch := key.archType.Name
-	if len(arch) > 0 {
-		return arch
-	} else {
-		return "x86_64"
+func getConfigString(key cqueryKey) string {
+	arch := key.configKey.arch
+	if len(arch) == 0 || arch == "common" {
+		// Use host platform, which is currently hardcoded to be x86_64.
+		arch = "x86_64"
+	}
+	os := key.configKey.osType.Name
+	if len(os) == 0 || os == "common_os" || os == "linux_glibc" {
+		// Use host OS, which is currently hardcoded to be linux.
+		os = "linux"
+	}
+	return arch + "|" + os
+}
+
+func GetConfigKey(ctx ModuleContext) configKey {
+	return configKey{
+		// use string because Arch is not a valid key in go
+		arch:   ctx.Arch().String(),
+		osType: ctx.Os(),
 	}
 }
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index f1fabec..e5cff90 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -9,11 +9,11 @@
 
 func TestRequestResultsAfterInvokeBazel(t *testing.T) {
 	label := "//foo:bar"
-	arch := Arm64
+	cfg := configKey{"arm64_armv8-a", Android}
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "cquery", expression: "kind(rule, deps(@soong_injection//mixed_builds:buildroot))"}: `//foo:bar|arm64>>out/foo/bar.txt`,
+		bazelCommand{command: "cquery", expression: "deps(@soong_injection//mixed_builds:buildroot, 2)"}: `//foo:bar|arm64_armv8-a|android>>out/foo/bar.txt`,
 	})
-	g, ok := bazelContext.GetOutputFiles(label, arch)
+	g, ok := bazelContext.GetOutputFiles(label, cfg)
 	if ok {
 		t.Errorf("Did not expect cquery results prior to running InvokeBazel(), but got %s", g)
 	}
@@ -21,7 +21,7 @@
 	if err != nil {
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
-	g, ok = bazelContext.GetOutputFiles(label, arch)
+	g, ok = bazelContext.GetOutputFiles(label, cfg)
 	if !ok {
 		t.Errorf("Expected cquery results after running InvokeBazel(), but got none")
 	} else if w := []string{"out/foo/bar.txt"}; !reflect.DeepEqual(w, g) {
@@ -101,7 +101,7 @@
 func testBazelContext(t *testing.T, bazelCommandResults map[bazelCommand]string) (*bazelContext, string) {
 	t.Helper()
 	p := bazelPaths{
-		buildDir:     t.TempDir(),
+		soongOutDir:  t.TempDir(),
 		outputBase:   "outputbase",
 		workspaceDir: "workspace_dir",
 	}
@@ -114,5 +114,5 @@
 		bazelRunner: runner,
 		paths:       &p,
 		requests:    map[cqueryKey]bool{},
-	}, p.buildDir
+	}, p.soongOutDir
 }
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index f4b2a7c..fa10f62 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -15,11 +15,12 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
 	"path/filepath"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/pathtools"
 )
@@ -67,32 +68,73 @@
 //   cannot be resolved,the function will panic. This is often due to the dependency not being added
 //   via an AddDependency* method.
 
+// A minimal context interface to check if a module should be converted by bp2build,
+// with functions containing information to match against allowlists and denylists.
+// If a module is deemed to be convertible by bp2build, then it should rely on a
+// BazelConversionPathContext for more functions for dep/path features.
+type BazelConversionContext interface {
+	Config() Config
+
+	Module() Module
+	OtherModuleType(m blueprint.Module) string
+	OtherModuleName(m blueprint.Module) string
+	OtherModuleDir(m blueprint.Module) string
+	ModuleErrorf(format string, args ...interface{})
+}
+
 // A subset of the ModuleContext methods which are sufficient to resolve references to paths/deps in
 // order to form a Bazel-compatible label for conversion.
 type BazelConversionPathContext interface {
 	EarlyModulePathContext
+	BazelConversionContext
 
+	ModuleErrorf(fmt string, args ...interface{})
+	PropertyErrorf(property, fmt string, args ...interface{})
 	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
-	Module() Module
-	ModuleType() string
-	OtherModuleName(m blueprint.Module) string
-	OtherModuleDir(m blueprint.Module) string
+	ModuleFromName(name string) (blueprint.Module, bool)
+	AddUnconvertedBp2buildDep(string)
+	AddMissingBp2buildDep(dep string)
 }
 
 // BazelLabelForModuleDeps expects a list of reference to other modules, ("<module>"
 // or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
 // module within the given ctx.
 func BazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
+	return BazelLabelForModuleDepsWithFn(ctx, modules, BazelModuleLabel)
+}
+
+// BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in
+// the list), and excludes (modules to exclude from the list). Both of these should contain
+// references to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label
+// list which corresponds to dependencies on the module within the given ctx, and the excluded
+// dependencies.  Prebuilt dependencies will be appended with _alwayslink so they can be handled as
+// whole static libraries.
+func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, BazelModuleLabel)
+}
+
+// BazelLabelForModuleDepsWithFn expects a list of reference to other modules, ("<module>"
+// or ":<module>") and applies moduleToLabelFn to determine and return a Bazel-compatible label
+// which corresponds to dependencies on the module within the given ctx.
+func BazelLabelForModuleDepsWithFn(ctx BazelConversionPathContext, modules []string,
+	moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList {
 	var labels bazel.LabelList
+	// In some cases, a nil string list is different than an explicitly empty list.
+	if len(modules) == 0 && modules != nil {
+		labels.Includes = []bazel.Label{}
+		return labels
+	}
 	for _, module := range modules {
 		bpText := module
 		if m := SrcIsModule(module); m == "" {
 			module = ":" + module
 		}
 		if m, t := SrcIsModuleWithTag(module); m != "" {
-			l := getOtherModuleLabel(ctx, m, t)
-			l.OriginalModuleName = bpText
-			labels.Includes = append(labels.Includes, l)
+			l := getOtherModuleLabel(ctx, m, t, moduleToLabelFn)
+			if l != nil {
+				l.OriginalModuleName = bpText
+				labels.Includes = append(labels.Includes, *l)
+			}
 		} else {
 			ctx.ModuleErrorf("%q, is not a module reference", module)
 		}
@@ -100,8 +142,36 @@
 	return labels
 }
 
+// BazelLabelForModuleDepsExcludesWithFn expects two lists: modules (containing modules to include in the
+// list), and excludes (modules to exclude from the list). Both of these should contain references
+// to other modules, ("<module>" or ":<module>"). It applies moduleToLabelFn to determine and return a
+// Bazel-compatible label list which corresponds to dependencies on the module within the given ctx, and
+// the excluded dependencies.
+func BazelLabelForModuleDepsExcludesWithFn(ctx BazelConversionPathContext, modules, excludes []string,
+	moduleToLabelFn func(BazelConversionPathContext, blueprint.Module) string) bazel.LabelList {
+	moduleLabels := BazelLabelForModuleDepsWithFn(ctx, RemoveListFromList(modules, excludes), moduleToLabelFn)
+	if len(excludes) == 0 {
+		return moduleLabels
+	}
+	excludeLabels := BazelLabelForModuleDepsWithFn(ctx, excludes, moduleToLabelFn)
+	return bazel.LabelList{
+		Includes: moduleLabels.Includes,
+		Excludes: excludeLabels.Includes,
+	}
+}
+
 func BazelLabelForModuleSrcSingle(ctx BazelConversionPathContext, path string) bazel.Label {
-	return BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes[0]
+	if srcs := BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 {
+		return srcs[0]
+	}
+	return bazel.Label{}
+}
+
+func BazelLabelForModuleDepSingle(ctx BazelConversionPathContext, path string) bazel.Label {
+	if srcs := BazelLabelForModuleDepsExcludes(ctx, []string{path}, []string(nil)).Includes; len(srcs) > 0 {
+		return srcs[0]
+	}
+	return bazel.Label{}
 }
 
 // BazelLabelForModuleSrc expects a list of path (relative to local module directory) and module
@@ -219,6 +289,16 @@
 	return newPaths
 }
 
+// Converts root-relative Paths to a list of bazel.Label relative to the module in ctx.
+func RootToModuleRelativePaths(ctx BazelConversionPathContext, paths Paths) []bazel.Label {
+	var newPaths []bazel.Label
+	for _, path := range PathsWithModuleSrcSubDir(ctx, paths, "") {
+		s := path.Rel()
+		newPaths = append(newPaths, bazel.Label{Label: s})
+	}
+	return newPaths
+}
+
 // expandSrcsForBazel returns bazel.LabelList with paths rooted from the module's local source
 // directory and Bazel target labels, excluding those included in the excludes argument (which
 // should already be expanded to resolve references to Soong-modules). Valid elements of paths
@@ -257,10 +337,10 @@
 
 	for _, p := range paths {
 		if m, tag := SrcIsModuleWithTag(p); m != "" {
-			l := getOtherModuleLabel(ctx, m, tag)
-			if !InList(l.Label, expandedExcludes) {
+			l := getOtherModuleLabel(ctx, m, tag, BazelModuleLabel)
+			if l != nil && !InList(l.Label, expandedExcludes) {
 				l.OriginalModuleName = fmt.Sprintf(":%s", m)
-				labels.Includes = append(labels.Includes, l)
+				labels.Includes = append(labels.Includes, *l)
 			}
 		} else {
 			var expandedPaths []bazel.Label
@@ -268,12 +348,7 @@
 				// e.g. turn "math/*.c" in
 				// external/arm-optimized-routines to external/arm-optimized-routines/math/*.c
 				rootRelativeGlobPath := pathForModuleSrc(ctx, p).String()
-				globbedPaths := GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes)
-				globbedPaths = PathsWithModuleSrcSubDir(ctx, globbedPaths, "")
-				for _, path := range globbedPaths {
-					s := path.Rel()
-					expandedPaths = append(expandedPaths, bazel.Label{Label: s})
-				}
+				expandedPaths = RootToModuleRelativePaths(ctx, GlobFiles(ctx, rootRelativeGlobPath, rootRelativeExpandedExcludes))
 			} else {
 				if !InList(p, expandedExcludes) {
 					expandedPaths = append(expandedPaths, bazel.Label{Label: p})
@@ -288,41 +363,57 @@
 // getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
 // module. The label will be relative to the current directory if appropriate. The dependency must
 // already be resolved by either deps mutator or path deps mutator.
-func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string) bazel.Label {
-	m, _ := ctx.GetDirectDep(dep)
+func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string,
+	labelFromModule func(BazelConversionPathContext, blueprint.Module) string) *bazel.Label {
+	m, _ := ctx.ModuleFromName(dep)
+	// The module was not found in an Android.bp file, this is often due to:
+	//		* a limited manifest
+	//		* a required module not being converted from Android.mk
 	if m == nil {
-		panic(fmt.Errorf(`Cannot get direct dep %q of %q.
-		This is likely because it was not added via AddDependency().
-		This may be due a mutator skipped during bp2build.`, dep, ctx.Module().Name()))
+		ctx.AddMissingBp2buildDep(dep)
+		return &bazel.Label{
+			Label: ":" + dep + "__BP2BUILD__MISSING__DEP",
+		}
 	}
-	otherLabel := bazelModuleLabel(ctx, m, tag)
-	label := bazelModuleLabel(ctx, ctx.Module(), "")
+	if !convertedToBazel(ctx, m) {
+		ctx.AddUnconvertedBp2buildDep(dep)
+	}
+	label := BazelModuleLabel(ctx, ctx.Module())
+	otherLabel := labelFromModule(ctx, m)
+
+	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
+
 	if samePackage(label, otherLabel) {
 		otherLabel = bazelShortLabel(otherLabel)
 	}
 
-	return bazel.Label{
+	return &bazel.Label{
 		Label: otherLabel,
 	}
 }
 
-func bazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module, tag string) string {
+func BazelModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
 	// TODO(b/165114590): Convert tag (":name{.tag}") to corresponding Bazel implicit output targets.
-	b, ok := module.(Bazelable)
-	// TODO(b/181155349): perhaps return an error here if the module can't be/isn't being converted
-	if !ok || !b.ConvertedToBazel(ctx) {
+	if !convertedToBazel(ctx, module) {
 		return bp2buildModuleLabel(ctx, module)
 	}
+	b, _ := module.(Bazelable)
 	return b.GetBazelLabel(ctx, module)
 }
 
 func bazelShortLabel(label string) string {
 	i := strings.Index(label, ":")
+	if i == -1 {
+		panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
+	}
 	return label[i:]
 }
 
 func bazelPackage(label string) string {
 	i := strings.Index(label, ":")
+	if i == -1 {
+		panic(fmt.Errorf("Could not find the ':' character in '%s', expected a fully qualified label.", label))
+	}
 	return label[0:i]
 }
 
@@ -330,7 +421,7 @@
 	return bazelPackage(label1) == bazelPackage(label2)
 }
 
-func bp2buildModuleLabel(ctx BazelConversionPathContext, module blueprint.Module) string {
+func bp2buildModuleLabel(ctx BazelConversionContext, module blueprint.Module) string {
 	moduleName := ctx.OtherModuleName(module)
 	moduleDir := ctx.OtherModuleDir(module)
 	return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
@@ -341,9 +432,19 @@
 	OutputPath
 }
 
+// ensure BazelOutPath implements Path
 var _ Path = BazelOutPath{}
+
+// ensure BazelOutPath implements genPathProvider
+var _ genPathProvider = BazelOutPath{}
+
+// ensure BazelOutPath implements objPathProvider
 var _ objPathProvider = BazelOutPath{}
 
+func (p BazelOutPath) genPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleGenPath {
+	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
+}
+
 func (p BazelOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
@@ -359,7 +460,7 @@
 	}
 
 	outputPath := OutputPath{basePath{"", ""},
-		ctx.Config().buildDir,
+		ctx.Config().soongOutDir,
 		ctx.Config().BazelContext.OutputBase()}
 
 	return BazelOutPath{
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 24fc522..ba95c5a 100644
--- a/android/config.go
+++ b/android/config.go
@@ -24,6 +24,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"reflect"
 	"runtime"
 	"strconv"
 	"strings"
@@ -35,7 +36,9 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android/soongconfig"
+	"android/soong/bazel"
 	"android/soong/remoteexec"
+	"android/soong/starlark_fmt"
 )
 
 // Bool re-exports proptools.Bool for the android package.
@@ -65,21 +68,29 @@
 	*config
 }
 
-// BuildDir returns the build output directory for the configuration.
-func (c Config) BuildDir() string {
-	return c.buildDir
+// SoongOutDir returns the build output directory for the configuration.
+func (c Config) SoongOutDir() string {
+	return c.soongOutDir
 }
 
-func (c Config) NinjaBuildDir() string {
-	return c.buildDir
+func (c Config) OutDir() string {
+	return c.outDir
+}
+
+func (c Config) RunGoTests() bool {
+	return c.runGoTests
 }
 
 func (c Config) DebugCompilation() bool {
 	return false // Never compile Go code in the main build for debugging
 }
 
-func (c Config) SrcDir() string {
-	return c.srcDir
+func (c Config) Subninjas() []string {
+	return []string{}
+}
+
+func (c Config) PrimaryBuilderInvocations() []bootstrap.PrimaryBuilderInvocation {
+	return []bootstrap.PrimaryBuilderInvocation{}
 }
 
 // A DeviceConfig object represents the configuration for a particular device
@@ -107,6 +118,12 @@
 
 	ProductVariablesFileName string
 
+	// BuildOS stores the OsType for the OS that the build is running on.
+	BuildOS OsType
+
+	// BuildArch stores the ArchType for the CPU that the build is running on.
+	BuildArch ArchType
+
 	Targets                  map[OsType][]Target
 	BuildOSTarget            Target // the Target for tools run on the build machine
 	BuildOSCommonTarget      Target // the Target for common (java) tools run on the build machine
@@ -119,10 +136,12 @@
 
 	deviceConfig *deviceConfig
 
-	srcDir         string // the path of the root source directory
-	buildDir       string // the path of the build output directory
+	outDir         string // The output directory (usually out/)
+	soongOutDir    string
 	moduleListFile string // the path to the file which lists blueprint files to parse.
 
+	runGoTests bool
+
 	env       map[string]string
 	envLock   sync.Mutex
 	envDeps   map[string]string
@@ -135,13 +154,12 @@
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
 
-	stopBefore bootstrap.StopBefore
-
 	fs         pathtools.FileSystem
 	mockBpList string
 
-	bp2buildPackageConfig    Bp2BuildConfig
-	bp2buildModuleTypeConfig map[string]bool
+	runningAsBp2Build              bool
+	bp2buildPackageConfig          bp2BuildConversionAllowlist
+	Bp2buildSoongConfigDefinitions soongconfig.Bp2BuildSoongConfigDefinitions
 
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
@@ -169,7 +187,7 @@
 
 // loadFromConfigFile loads and decodes configuration options from a JSON file
 // in the current working directory.
-func loadFromConfigFile(configurable jsonConfigurable, filename string) error {
+func loadFromConfigFile(configurable *productVariables, filename string) error {
 	// Try to open the file
 	configFileReader, err := os.Open(filename)
 	defer configFileReader.Close()
@@ -194,13 +212,34 @@
 		}
 	}
 
-	// No error
-	return nil
+	if Bool(configurable.GcovCoverage) && Bool(configurable.ClangCoverage) {
+		return fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
+	}
+
+	configurable.Native_coverage = proptools.BoolPtr(
+		Bool(configurable.GcovCoverage) ||
+			Bool(configurable.ClangCoverage))
+
+	// when Platform_sdk_final is true (or PLATFORM_VERSION_CODENAME is REL), use Platform_sdk_version;
+	// if false (pre-released version, for example), use Platform_sdk_codename.
+	if Bool(configurable.Platform_sdk_final) {
+		if configurable.Platform_sdk_version != nil {
+			configurable.Platform_sdk_version_or_codename =
+				proptools.StringPtr(strconv.Itoa(*(configurable.Platform_sdk_version)))
+		} else {
+			return fmt.Errorf("Platform_sdk_version cannot be pointed by a NULL pointer")
+		}
+	} else {
+		configurable.Platform_sdk_version_or_codename =
+			proptools.StringPtr(String(configurable.Platform_sdk_codename))
+	}
+
+	return saveToBazelConfigFile(configurable, filepath.Dir(filename))
 }
 
 // atomically writes the config file in case two copies of soong_build are running simultaneously
 // (for example, docs generation and ninja manifest generation)
-func saveToConfigFile(config jsonConfigurable, filename string) error {
+func saveToConfigFile(config *productVariables, filename string) error {
 	data, err := json.MarshalIndent(&config, "", "    ")
 	if err != nil {
 		return fmt.Errorf("cannot marshal config data: %s", err.Error())
@@ -229,13 +268,71 @@
 	return nil
 }
 
+func saveToBazelConfigFile(config *productVariables, outDir string) error {
+	dir := filepath.Join(outDir, bazel.SoongInjectionDirName, "product_config")
+	err := createDirIfNonexistent(dir, os.ModePerm)
+	if err != nil {
+		return fmt.Errorf("Could not create dir %s: %s", dir, err)
+	}
+
+	nonArchVariantProductVariables := []string{}
+	archVariantProductVariables := []string{}
+	p := variableProperties{}
+	t := reflect.TypeOf(p.Product_variables)
+	for i := 0; i < t.NumField(); i++ {
+		f := t.Field(i)
+		nonArchVariantProductVariables = append(nonArchVariantProductVariables, strings.ToLower(f.Name))
+		if proptools.HasTag(f, "android", "arch_variant") {
+			archVariantProductVariables = append(archVariantProductVariables, strings.ToLower(f.Name))
+		}
+	}
+
+	nonArchVariantProductVariablesJson := starlark_fmt.PrintStringList(nonArchVariantProductVariables, 0)
+	if err != nil {
+		return fmt.Errorf("cannot marshal product variable data: %s", err.Error())
+	}
+
+	archVariantProductVariablesJson := starlark_fmt.PrintStringList(archVariantProductVariables, 0)
+	if err != nil {
+		return fmt.Errorf("cannot marshal arch variant product variable data: %s", err.Error())
+	}
+
+	configJson, err := json.MarshalIndent(&config, "", "    ")
+	if err != nil {
+		return fmt.Errorf("cannot marshal config data: %s", err.Error())
+	}
+
+	bzl := []string{
+		bazel.GeneratedBazelFileWarning,
+		fmt.Sprintf(`_product_vars = json.decode("""%s""")`, configJson),
+		fmt.Sprintf(`_product_var_constraints = %s`, nonArchVariantProductVariablesJson),
+		fmt.Sprintf(`_arch_variant_product_var_constraints = %s`, archVariantProductVariablesJson),
+		"\n", `
+product_vars = _product_vars
+product_var_constraints = _product_var_constraints
+arch_variant_product_var_constraints = _arch_variant_product_var_constraints
+`,
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "product_variables.bzl"), []byte(strings.Join(bzl, "\n")), 0644)
+	if err != nil {
+		return fmt.Errorf("Could not write .bzl config file %s", err)
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "BUILD"), []byte(bazel.GeneratedBazelFileWarning), 0644)
+	if err != nil {
+		return fmt.Errorf("Could not write BUILD config file %s", err)
+	}
+
+	return nil
+}
+
 // NullConfig returns a mostly empty Config for use by standalone tools like dexpreopt_gen that
 // use the android package.
-func NullConfig(buildDir string) Config {
+func NullConfig(outDir, soongOutDir string) Config {
 	return Config{
 		config: &config{
-			buildDir: buildDir,
-			fs:       pathtools.OsFs,
+			outDir:      outDir,
+			soongOutDir: soongOutDir,
+			fs:          pathtools.OsFs,
 		},
 	}
 }
@@ -253,21 +350,24 @@
 
 	config := &config{
 		productVariables: productVariables{
-			DeviceName:                        stringPtr("test_device"),
-			Platform_sdk_version:              intPtr(30),
-			Platform_sdk_codename:             stringPtr("S"),
-			Platform_version_active_codenames: []string{"S"},
-			DeviceSystemSdkVersions:           []string{"14", "15"},
-			Platform_systemsdk_versions:       []string{"29", "30"},
-			AAPTConfig:                        []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"},
-			AAPTPreferredConfig:               stringPtr("xhdpi"),
-			AAPTCharacteristics:               stringPtr("nosdcard"),
-			AAPTPrebuiltDPI:                   []string{"xhdpi", "xxhdpi"},
-			UncompressPrivAppDex:              boolPtr(true),
-			ShippingApiLevel:                  stringPtr("30"),
+			DeviceName:                          stringPtr("test_device"),
+			DeviceProduct:                       stringPtr("test_product"),
+			Platform_sdk_version:                intPtr(30),
+			Platform_sdk_codename:               stringPtr("S"),
+			Platform_base_sdk_extension_version: intPtr(1),
+			Platform_version_active_codenames:   []string{"S", "Tiramisu"},
+			DeviceSystemSdkVersions:             []string{"14", "15"},
+			Platform_systemsdk_versions:         []string{"29", "30"},
+			AAPTConfig:                          []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"},
+			AAPTPreferredConfig:                 stringPtr("xhdpi"),
+			AAPTCharacteristics:                 stringPtr("nosdcard"),
+			AAPTPrebuiltDPI:                     []string{"xhdpi", "xxhdpi"},
+			UncompressPrivAppDex:                boolPtr(true),
+			ShippingApiLevel:                    stringPtr("30"),
 		},
 
-		buildDir:     buildDir,
+		outDir:       buildDir,
+		soongOutDir:  filepath.Join(buildDir, "soong"),
 		captureBuild: true,
 		env:          envCopy,
 
@@ -284,26 +384,11 @@
 
 	config.mockFileSystem(bp, fs)
 
-	config.bp2buildModuleTypeConfig = map[string]bool{}
+	determineBuildOS(config)
 
 	return Config{config}
 }
 
-func fuchsiaTargets() map[OsType][]Target {
-	return map[OsType][]Target{
-		Fuchsia: {
-			{Fuchsia, Arch{ArchType: Arm64, ArchVariant: "", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
-		},
-		BuildOs: {
-			{BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", false},
-		},
-	}
-}
-
-var PrepareForTestSetDeviceToFuchsia = FixtureModifyConfig(func(config Config) {
-	config.Targets = fuchsiaTargets()
-})
-
 func modifyTestConfigToSupportArchMutator(testConfig Config) {
 	config := testConfig.config
 
@@ -312,26 +397,39 @@
 			{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
 			{Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", "", false},
 		},
-		BuildOs: []Target{
-			{BuildOs, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", false},
-			{BuildOs, Arch{ArchType: X86}, NativeBridgeDisabled, "", "", false},
+		config.BuildOS: []Target{
+			{config.BuildOS, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", false},
+			{config.BuildOS, Arch{ArchType: X86}, NativeBridgeDisabled, "", "", false},
 		},
 	}
 
 	if runtime.GOOS == "darwin" {
-		config.Targets[BuildOs] = config.Targets[BuildOs][:1]
+		config.Targets[config.BuildOS] = config.Targets[config.BuildOS][:1]
 	}
 
-	config.BuildOSTarget = config.Targets[BuildOs][0]
-	config.BuildOSCommonTarget = getCommonTargets(config.Targets[BuildOs])[0]
+	config.BuildOSTarget = config.Targets[config.BuildOS][0]
+	config.BuildOSCommonTarget = getCommonTargets(config.Targets[config.BuildOS])[0]
 	config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0]
-	config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
+	config.AndroidFirstDeviceTarget = FirstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	config.TestProductVariables.DeviceArch = proptools.StringPtr("arm64")
 	config.TestProductVariables.DeviceArchVariant = proptools.StringPtr("armv8-a")
 	config.TestProductVariables.DeviceSecondaryArch = proptools.StringPtr("arm")
 	config.TestProductVariables.DeviceSecondaryArchVariant = proptools.StringPtr("armv7-a-neon")
 }
 
+func modifyTestConfigForMusl(config Config) {
+	delete(config.Targets, config.BuildOS)
+	config.productVariables.HostMusl = boolPtr(true)
+	determineBuildOS(config.config)
+	config.Targets[config.BuildOS] = []Target{
+		{config.BuildOS, Arch{ArchType: X86_64}, NativeBridgeDisabled, "", "", false},
+		{config.BuildOS, Arch{ArchType: X86}, NativeBridgeDisabled, "", "", false},
+	}
+
+	config.BuildOSTarget = config.Targets[config.BuildOS][0]
+	config.BuildOSCommonTarget = getCommonTargets(config.Targets[config.BuildOS])[0]
+}
+
 // TestArchConfig returns a Config object suitable for using for tests that
 // need to run the arch mutator.
 func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
@@ -345,7 +443,7 @@
 // multiple runs in the same program execution is carried over (such as Bazel
 // context or environment deps).
 func ConfigForAdditionalRun(c Config) (Config, error) {
-	newConfig, err := NewConfig(c.srcDir, c.buildDir, c.moduleListFile, c.env)
+	newConfig, err := NewConfig(c.moduleListFile, c.runGoTests, c.outDir, c.soongOutDir, c.env)
 	if err != nil {
 		return Config{}, err
 	}
@@ -356,15 +454,16 @@
 
 // NewConfig creates a new Config object. The srcDir argument specifies the path
 // to the root source directory. It also loads the config file, if found.
-func NewConfig(srcDir, buildDir string, moduleListFile string, availableEnv map[string]string) (Config, error) {
+func NewConfig(moduleListFile string, runGoTests bool, outDir, soongOutDir string, availableEnv map[string]string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
-		ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName),
+		ProductVariablesFileName: filepath.Join(soongOutDir, productVariablesFileName),
 
 		env: availableEnv,
 
-		srcDir:            srcDir,
-		buildDir:          buildDir,
+		outDir:            outDir,
+		soongOutDir:       soongOutDir,
+		runGoTests:        runGoTests,
 		multilibConflicts: make(map[ArchType]bool),
 
 		moduleListFile: moduleListFile,
@@ -377,12 +476,12 @@
 
 	// Soundness check of the build and source directories. This won't catch strange
 	// configurations with symlinks, but at least checks the obvious case.
-	absBuildDir, err := filepath.Abs(buildDir)
+	absBuildDir, err := filepath.Abs(soongOutDir)
 	if err != nil {
 		return Config{}, err
 	}
 
-	absSrcDir, err := filepath.Abs(srcDir)
+	absSrcDir, err := filepath.Abs(".")
 	if err != nil {
 		return Config{}, err
 	}
@@ -397,11 +496,13 @@
 		return Config{}, err
 	}
 
-	KatiEnabledMarkerFile := filepath.Join(buildDir, ".soong.kati_enabled")
+	KatiEnabledMarkerFile := filepath.Join(soongOutDir, ".soong.kati_enabled")
 	if _, err := os.Stat(absolutePath(KatiEnabledMarkerFile)); err == nil {
 		config.katiEnabled = true
 	}
 
+	determineBuildOS(config)
+
 	// Sets up the map of target OSes to the finer grained compilation targets
 	// that are configured from the product variables.
 	targets, err := decodeTargetProductVariables(config)
@@ -420,7 +521,7 @@
 	}
 
 	if archConfig != nil {
-		androidTargets, err := decodeArchSettings(Android, archConfig)
+		androidTargets, err := decodeAndroidArchSettings(archConfig)
 		if err != nil {
 			return Config{}, err
 		}
@@ -439,26 +540,17 @@
 	config.Targets = targets
 
 	// Compilation targets for host tools.
-	config.BuildOSTarget = config.Targets[BuildOs][0]
-	config.BuildOSCommonTarget = getCommonTargets(config.Targets[BuildOs])[0]
+	config.BuildOSTarget = config.Targets[config.BuildOS][0]
+	config.BuildOSCommonTarget = getCommonTargets(config.Targets[config.BuildOS])[0]
 
 	// Compilation targets for Android.
 	if len(config.Targets[Android]) > 0 {
 		config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0]
-		config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
+		config.AndroidFirstDeviceTarget = FirstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	if Bool(config.productVariables.GcovCoverage) && Bool(config.productVariables.ClangCoverage) {
-		return Config{}, fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
-	}
-
-	config.productVariables.Native_coverage = proptools.BoolPtr(
-		Bool(config.productVariables.GcovCoverage) ||
-			Bool(config.productVariables.ClangCoverage))
-
 	config.BazelContext, err = NewBazelContext(config)
-	config.bp2buildPackageConfig = bp2buildDefaultConfig
-	config.bp2buildModuleTypeConfig = make(map[string]bool)
+	config.bp2buildPackageConfig = bp2buildAllowlist
 
 	return Config{config}, err
 }
@@ -480,7 +572,7 @@
 	pathsToParse := []string{}
 	for candidate := range mockFS {
 		base := filepath.Base(candidate)
-		if base == "Blueprints" || base == "Android.bp" {
+		if base == "Android.bp" {
 			pathsToParse = append(pathsToParse, candidate)
 		}
 	}
@@ -493,43 +585,37 @@
 	c.mockBpList = blueprint.MockModuleListFile
 }
 
-func (c *config) StopBefore() bootstrap.StopBefore {
-	return c.stopBefore
-}
-
-// SetStopBefore configures soong_build to exit earlier at a specific point.
-func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) {
-	c.stopBefore = stopBefore
-}
-
 func (c *config) SetAllowMissingDependencies() {
 	c.productVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
 }
 
-var _ bootstrap.ConfigStopBefore = (*config)(nil)
-
 // BlueprintToolLocation returns the directory containing build system tools
 // from Blueprint, like soong_zip and merge_zips.
-func (c *config) BlueprintToolLocation() string {
-	return filepath.Join(c.buildDir, "host", c.PrebuiltOS(), "bin")
+func (c *config) HostToolDir() string {
+	if c.KatiEnabled() {
+		return filepath.Join(c.outDir, "host", c.PrebuiltOS(), "bin")
+	} else {
+		return filepath.Join(c.soongOutDir, "host", c.PrebuiltOS(), "bin")
+	}
 }
 
-var _ bootstrap.ConfigBlueprintToolLocation = (*config)(nil)
-
 func (c *config) HostToolPath(ctx PathContext, tool string) Path {
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "bin", tool)
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false, tool)
+	return path
 }
 
-func (c *config) HostJNIToolPath(ctx PathContext, path string) Path {
+func (c *config) HostJNIToolPath(ctx PathContext, lib string) Path {
 	ext := ".so"
 	if runtime.GOOS == "darwin" {
 		ext = ".dylib"
 	}
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "lib64", path+ext)
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "lib64", false, lib+ext)
+	return path
 }
 
-func (c *config) HostJavaToolPath(ctx PathContext, path string) Path {
-	return PathForOutput(ctx, "host", c.PrebuiltOS(), "framework", path)
+func (c *config) HostJavaToolPath(ctx PathContext, tool string) Path {
+	path := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "framework", false, tool)
+	return path
 }
 
 // PrebuiltOS returns the name of the host OS used in prebuilts directories.
@@ -546,7 +632,7 @@
 
 // GoRoot returns the path to the root directory of the Go toolchain.
 func (c *config) GoRoot() string {
-	return fmt.Sprintf("%s/prebuilts/go/%s", c.srcDir, c.PrebuiltOS())
+	return fmt.Sprintf("prebuilts/go/%s", c.PrebuiltOS())
 }
 
 // PrebuiltBuildTool returns the path to a tool in the prebuilts directory containing
@@ -638,6 +724,15 @@
 	return *c.productVariables.DeviceName
 }
 
+// DeviceProduct returns the current product target. There could be multiple of
+// these per device type.
+//
+// NOTE: Do not base conditional logic on this value. It may break product
+//       inheritance.
+func (c *config) DeviceProduct() string {
+	return *c.productVariables.DeviceProduct
+}
+
 func (c *config) DeviceResourceOverlays() []string {
 	return c.productVariables.DeviceResourceOverlays
 }
@@ -658,6 +753,14 @@
 	return String(c.productVariables.Platform_sdk_codename)
 }
 
+func (c *config) PlatformSdkExtensionVersion() int {
+	return *c.productVariables.Platform_sdk_extension_version
+}
+
+func (c *config) PlatformBaseSdkExtensionVersion() int {
+	return *c.productVariables.Platform_base_sdk_extension_version
+}
+
 func (c *config) PlatformSecurityPatch() string {
 	return String(c.productVariables.Platform_security_patch)
 }
@@ -674,8 +777,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 {
@@ -698,6 +805,16 @@
 	return levels
 }
 
+func (c *config) LatestPreviewApiLevel() ApiLevel {
+	level := NoneApiLevel
+	for _, l := range c.PreviewApiLevels() {
+		if l.GreaterThan(level) {
+			level = l
+		}
+	}
+	return level
+}
+
 func (c *config) AllSupportedApiLevels() []ApiLevel {
 	var levels []ApiLevel
 	levels = append(levels, c.FinalApiLevels()...)
@@ -789,7 +906,13 @@
 // Returns true if building apps that aren't bundled with the platform.
 // UnbundledBuild() is always true when this is true.
 func (c *config) UnbundledBuildApps() bool {
-	return Bool(c.productVariables.Unbundled_build_apps)
+	return len(c.productVariables.Unbundled_build_apps) > 0
+}
+
+// Returns true if building image that aren't bundled with the platform.
+// UnbundledBuild() is always true when this is true.
+func (c *config) UnbundledBuildImage() bool {
+	return Bool(c.productVariables.Unbundled_build_image)
 }
 
 // Returns true if building modules against prebuilt SDKs.
@@ -797,15 +920,6 @@
 	return Bool(c.productVariables.Always_use_prebuilt_sdks)
 }
 
-// Returns true if the boot jars check should be skipped.
-func (c *config) SkipBootJarsCheck() bool {
-	return Bool(c.productVariables.Skip_boot_jars_check)
-}
-
-func (c *config) Fuchsia() bool {
-	return Bool(c.productVariables.Fuchsia)
-}
-
 func (c *config) MinimizeJavaDebugInfo() bool {
 	return Bool(c.productVariables.MinimizeJavaDebugInfo) && !Bool(c.productVariables.Eng)
 }
@@ -818,8 +932,13 @@
 	return Bool(c.productVariables.Eng)
 }
 
+// DevicePrimaryArchType returns the ArchType for the first configured device architecture, or
+// Common if there are no device architectures.
 func (c *config) DevicePrimaryArchType() ArchType {
-	return c.Targets[Android][0].Arch.ArchType
+	if androidTargets := c.Targets[Android]; len(androidTargets) > 0 {
+		return androidTargets[0].Arch.ArchType
+	}
+	return Common
 }
 
 func (c *config) SanitizeHost() []string {
@@ -1120,10 +1239,6 @@
 	return c.config.productVariables.DeviceKernelHeaders
 }
 
-func (c *deviceConfig) SamplingPGO() bool {
-	return Bool(c.config.productVariables.SamplingPGO)
-}
-
 // JavaCoverageEnabledForPath returns whether Java code coverage is enabled for
 // path. Coverage is enabled by default when the product variable
 // JavaCoveragePaths is empty. If JavaCoveragePaths is not empty, coverage is
@@ -1155,6 +1270,10 @@
 	return Bool(c.config.productVariables.ClangCoverage)
 }
 
+func (c *deviceConfig) ClangCoverageContinuousMode() bool {
+	return Bool(c.config.productVariables.ClangCoverageContinuousMode)
+}
+
 func (c *deviceConfig) GcovCoverageEnabled() bool {
 	return Bool(c.config.productVariables.GcovCoverage)
 }
@@ -1179,6 +1298,10 @@
 	return coverage
 }
 
+func (c *deviceConfig) AfdoAdditionalProfileDirs() []string {
+	return c.config.productVariables.AfdoAdditionalProfileDirs
+}
+
 func (c *deviceConfig) PgoAdditionalProfileDirs() []string {
 	return c.config.productVariables.PgoAdditionalProfileDirs
 }
@@ -1241,6 +1364,10 @@
 	return "", false
 }
 
+func (c *deviceConfig) ApexGlobalMinSdkVersionOverride() string {
+	return String(c.config.productVariables.ApexGlobalMinSdkVersionOverride)
+}
+
 func (c *config) IntegerOverflowDisabledForPath(path string) bool {
 	if len(c.productVariables.IntegerOverflowExcludePaths) == 0 {
 		return false
@@ -1395,6 +1522,14 @@
 	return String(c.config.productVariables.PlatformSepolicyVersion)
 }
 
+func (c *deviceConfig) TotSepolicyVersion() string {
+	return String(c.config.productVariables.TotSepolicyVersion)
+}
+
+func (c *deviceConfig) PlatformSepolicyCompatVersions() []string {
+	return c.config.productVariables.PlatformSepolicyCompatVersions
+}
+
 func (c *deviceConfig) BoardSepolicyVers() string {
 	if ver := String(c.config.productVariables.BoardSepolicyVers); ver != "" {
 		return ver
@@ -1402,10 +1537,42 @@
 	return c.PlatformSepolicyVersion()
 }
 
+func (c *deviceConfig) BoardPlatVendorPolicy() []string {
+	return c.config.productVariables.BoardPlatVendorPolicy
+}
+
 func (c *deviceConfig) BoardReqdMaskPolicy() []string {
 	return c.config.productVariables.BoardReqdMaskPolicy
 }
 
+func (c *deviceConfig) BoardSystemExtPublicPrebuiltDirs() []string {
+	return c.config.productVariables.BoardSystemExtPublicPrebuiltDirs
+}
+
+func (c *deviceConfig) BoardSystemExtPrivatePrebuiltDirs() []string {
+	return c.config.productVariables.BoardSystemExtPrivatePrebuiltDirs
+}
+
+func (c *deviceConfig) BoardProductPublicPrebuiltDirs() []string {
+	return c.config.productVariables.BoardProductPublicPrebuiltDirs
+}
+
+func (c *deviceConfig) BoardProductPrivatePrebuiltDirs() []string {
+	return c.config.productVariables.BoardProductPrivatePrebuiltDirs
+}
+
+func (c *deviceConfig) SystemExtSepolicyPrebuiltApiDir() string {
+	return String(c.config.productVariables.SystemExtSepolicyPrebuiltApiDir)
+}
+
+func (c *deviceConfig) ProductSepolicyPrebuiltApiDir() string {
+	return String(c.config.productVariables.ProductSepolicyPrebuiltApiDir)
+}
+
+func (c *deviceConfig) IsPartnerTrebleSepolicyTestEnabled() bool {
+	return c.SystemExtSepolicyPrebuiltApiDir() != "" || c.ProductSepolicyPrebuiltApiDir() != ""
+}
+
 func (c *deviceConfig) DirectedVendorSnapshot() bool {
 	return c.config.productVariables.DirectedVendorSnapshot
 }
@@ -1478,6 +1645,10 @@
 		c.config.productVariables.RecoverySnapshotDirsIncluded)
 }
 
+func (c *deviceConfig) HostFakeSnapshotEnabled() bool {
+	return c.config.productVariables.HostFakeSnapshotEnabled
+}
+
 func (c *deviceConfig) ShippingApiLevel() ApiLevel {
 	if c.config.productVariables.ShippingApiLevel == nil {
 		return NoneApiLevel
@@ -1502,6 +1673,10 @@
 	return c.config.productVariables.BuildBrokenVendorPropertyNamespace
 }
 
+func (c *deviceConfig) BuildBrokenInputDir(name string) bool {
+	return InList(name, c.config.productVariables.BuildBrokenInputDirModules)
+}
+
 func (c *deviceConfig) RequiresInsecureExecmemForSwiftshader() bool {
 	return c.config.productVariables.RequiresInsecureExecmemForSwiftshader
 }
@@ -1514,6 +1689,18 @@
 	return c.config.productVariables.SepolicySplit
 }
 
+func (c *deviceConfig) SepolicyFreezeTestExtraDirs() []string {
+	return c.config.productVariables.SepolicyFreezeTestExtraDirs
+}
+
+func (c *deviceConfig) SepolicyFreezeTestExtraPrebuiltDirs() []string {
+	return c.config.productVariables.SepolicyFreezeTestExtraPrebuiltDirs
+}
+
+func (c *deviceConfig) GenerateAidlNdkPlatformBackend() bool {
+	return c.config.productVariables.GenerateAidlNdkPlatformBackend
+}
+
 // The ConfiguredJarList struct provides methods for handling a list of (apex, jar) pairs.
 // Such lists are used in the build system for things like bootclasspath jars or system server jars.
 // The apex part is either an apex name, or a special names "platform" or "system_ext". Jar is a
@@ -1602,6 +1789,20 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// Append a list of (apex, jar) pairs to the list.
+func (l *ConfiguredJarList) AppendList(other *ConfiguredJarList) ConfiguredJarList {
+	apexes := make([]string, 0, l.Len()+other.Len())
+	jars := make([]string, 0, l.Len()+other.Len())
+
+	apexes = append(apexes, l.apexes...)
+	jars = append(jars, l.jars...)
+
+	apexes = append(apexes, other.apexes...)
+	jars = append(jars, other.jars...)
+
+	return ConfiguredJarList{apexes, jars}
+}
+
 // RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
 func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
 	apexes := make([]string, 0, l.Len())
@@ -1618,8 +1819,9 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
-// Filter keeps the entries if a jar appears in the given list of jars to keep; returns a new list.
-func (l *ConfiguredJarList) Filter(jarsToKeep []string) ConfiguredJarList {
+// Filter keeps the entries if a jar appears in the given list of jars to keep. Returns a new list
+// and any remaining jars that are not on this list.
+func (l *ConfiguredJarList) Filter(jarsToKeep []string) (ConfiguredJarList, []string) {
 	var apexes []string
 	var jars []string
 
@@ -1630,7 +1832,7 @@
 		}
 	}
 
-	return ConfiguredJarList{apexes, jars}
+	return ConfiguredJarList{apexes, jars}, RemoveListFromList(jarsToKeep, jars)
 }
 
 // CopyOfJars returns a copy of the list of strings containing jar module name
@@ -1812,18 +2014,23 @@
 func (c *config) BootJars() []string {
 	return c.Once(earlyBootJarsKey, func() interface{} {
 		list := c.productVariables.BootJars.CopyOfJars()
-		return append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
+		return append(list, c.productVariables.ApexBootJars.CopyOfJars()...)
 	}).([]string)
 }
 
-func (c *config) NonUpdatableBootJars() ConfiguredJarList {
+func (c *config) NonApexBootJars() ConfiguredJarList {
 	return c.productVariables.BootJars
 }
 
-func (c *config) UpdatableBootJars() ConfiguredJarList {
-	return c.productVariables.UpdatableBootJars
+func (c *config) ApexBootJars() ConfiguredJarList {
+	return c.productVariables.ApexBootJars
 }
 
 func (c *config) RBEWrapper() string {
 	return c.GetenvWithDefault("RBE_WRAPPER", remoteexec.DefaultWrapperPath)
 }
+
+// UseHostMusl returns true if the host target has been configured to build against musl libc.
+func (c *config) UseHostMusl() bool {
+	return Bool(c.productVariables.HostMusl)
+}
diff --git a/android/config_bp2build.go b/android/config_bp2build.go
new file mode 100644
index 0000000..748be62
--- /dev/null
+++ b/android/config_bp2build.go
@@ -0,0 +1,487 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"reflect"
+	"regexp"
+	"sort"
+	"strings"
+
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
+
+	"github.com/google/blueprint"
+)
+
+// BazelVarExporter is a collection of configuration variables that can be exported for use in Bazel rules
+type BazelVarExporter interface {
+	// asBazel expands strings of configuration variables into their concrete values
+	asBazel(Config, ExportedStringVariables, ExportedStringListVariables, ExportedConfigDependingVariables) []bazelConstant
+}
+
+// ExportedVariables is a collection of interdependent configuration variables
+type ExportedVariables struct {
+	// Maps containing toolchain variables that are independent of the
+	// environment variables of the build.
+	exportedStringVars         ExportedStringVariables
+	exportedStringListVars     ExportedStringListVariables
+	exportedStringListDictVars ExportedStringListDictVariables
+
+	exportedVariableReferenceDictVars ExportedVariableReferenceDictVariables
+
+	/// Maps containing variables that are dependent on the build config.
+	exportedConfigDependingVars ExportedConfigDependingVariables
+
+	pctx PackageContext
+}
+
+// NewExportedVariables creats an empty ExportedVariables struct with non-nil maps
+func NewExportedVariables(pctx PackageContext) ExportedVariables {
+	return ExportedVariables{
+		exportedStringVars:                ExportedStringVariables{},
+		exportedStringListVars:            ExportedStringListVariables{},
+		exportedStringListDictVars:        ExportedStringListDictVariables{},
+		exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{},
+		exportedConfigDependingVars:       ExportedConfigDependingVariables{},
+		pctx:                              pctx,
+	}
+}
+
+func (ev ExportedVariables) asBazel(config Config,
+	stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := []bazelConstant{}
+	ret = append(ret, ev.exportedStringVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	ret = append(ret, ev.exportedStringListVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	ret = append(ret, ev.exportedStringListDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	// Note: ExportedVariableReferenceDictVars collections can only contain references to other variables and must be printed last
+	ret = append(ret, ev.exportedVariableReferenceDictVars.asBazel(config, stringVars, stringListVars, cfgDepVars)...)
+	return ret
+}
+
+// ExportStringStaticVariable declares a static string variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringStaticVariable(name string, value string) {
+	ev.pctx.StaticVariable(name, value)
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringListStaticVariable declares a static variable and exports it to
+// Bazel's toolchain.
+func (ev ExportedVariables) ExportStringListStaticVariable(name string, value []string) {
+	ev.pctx.StaticVariable(name, strings.Join(value, " "))
+	ev.exportedStringListVars.set(name, value)
+}
+
+// ExportVariableConfigMethod declares a variable whose value is evaluated at
+// runtime via a function with access to the Config and exports it to Bazel's
+// toolchain.
+func (ev ExportedVariables) ExportVariableConfigMethod(name string, method interface{}) blueprint.Variable {
+	ev.exportedConfigDependingVars.set(name, method)
+	return ev.pctx.VariableConfigMethod(name, method)
+}
+
+// ExportSourcePathVariable declares a static "source path" variable and exports
+// it to Bazel's toolchain.
+func (ev ExportedVariables) ExportSourcePathVariable(name string, value string) {
+	ev.pctx.SourcePathVariable(name, value)
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportVariableFuncVariable declares a variable whose value is evaluated at
+// runtime via a function and exports it to Bazel's toolchain.
+func (ev ExportedVariables) ExportVariableFuncVariable(name string, f func() string) {
+	ev.exportedConfigDependingVars.set(name, func(config Config) string {
+		return f()
+	})
+	ev.pctx.VariableFunc(name, func(PackageVarContext) string {
+		return f()
+	})
+}
+
+// ExportString only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportString(name string, value string) {
+	ev.exportedStringVars.set(name, value)
+}
+
+// ExportStringList only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringList(name string, value []string) {
+	ev.exportedStringListVars.set(name, value)
+}
+
+// ExportStringListDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportStringListDict(name string, value map[string][]string) {
+	ev.exportedStringListDictVars.set(name, value)
+}
+
+// ExportVariableReferenceDict only exports a variable to Bazel, but does not declare it in Soong
+func (ev ExportedVariables) ExportVariableReferenceDict(name string, value map[string]string) {
+	ev.exportedVariableReferenceDictVars.set(name, value)
+}
+
+// ExportedConfigDependingVariables is a mapping of variable names to functions
+// of type func(config Config) string which return the runtime-evaluated string
+// value of a particular variable
+type ExportedConfigDependingVariables map[string]interface{}
+
+func (m ExportedConfigDependingVariables) set(k string, v interface{}) {
+	m[k] = v
+}
+
+// Ensure that string s has no invalid characters to be generated into the bzl file.
+func validateCharacters(s string) string {
+	for _, c := range []string{`\n`, `"`, `\`} {
+		if strings.Contains(s, c) {
+			panic(fmt.Errorf("%s contains illegal character %s", s, c))
+		}
+	}
+	return s
+}
+
+type bazelConstant struct {
+	variableName       string
+	internalDefinition string
+	sortLast           bool
+}
+
+// ExportedStringVariables is a mapping of variable names to string values
+type ExportedStringVariables map[string]string
+
+func (m ExportedStringVariables) set(k string, v string) {
+	m[k] = v
+}
+
+func (m ExportedStringVariables) asBazel(config Config,
+	stringVars ExportedStringVariables, stringListVars ExportedStringListVariables, cfgDepVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, variableValue := range m {
+		expandedVar, err := expandVar(config, variableValue, stringVars, stringListVars, cfgDepVars)
+		if err != nil {
+			panic(fmt.Errorf("error expanding config variable %s: %s", k, err))
+		}
+		if len(expandedVar) > 1 {
+			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
+		}
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: fmt.Sprintf(`"%s"`, validateCharacters(expandedVar[0])),
+		})
+	}
+	return ret
+}
+
+// ExportedStringListVariables is a mapping of variable names to a list of strings
+type ExportedStringListVariables map[string][]string
+
+func (m ExportedStringListVariables) set(k string, v []string) {
+	m[k] = v
+}
+
+func (m ExportedStringListVariables) asBazel(config Config,
+	stringScope ExportedStringVariables, stringListScope ExportedStringListVariables,
+	exportedVars ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	// For each exported variable, recursively expand elements in the variableValue
+	// list to ensure that interpolated variables are expanded according to their values
+	// in the variable scope.
+	for k, variableValue := range m {
+		var expandedVars []string
+		for _, v := range variableValue {
+			expandedVar, err := expandVar(config, v, stringScope, stringListScope, exportedVars)
+			if err != nil {
+				panic(fmt.Errorf("Error expanding config variable %s=%s: %s", k, v, err))
+			}
+			expandedVars = append(expandedVars, expandedVar...)
+		}
+		// Assign the list as a bzl-private variable; this variable will be exported
+		// out through a constants struct later.
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: starlark_fmt.PrintStringList(expandedVars, 0),
+		})
+	}
+	return ret
+}
+
+// ExportedStringListDictVariables is a mapping from variable names to a
+// dictionary which maps keys to lists of strings
+type ExportedStringListDictVariables map[string]map[string][]string
+
+func (m ExportedStringListDictVariables) set(k string, v map[string][]string) {
+	m[k] = v
+}
+
+// Since dictionaries are not supported in Ninja, we do not expand variables for dictionaries
+func (m ExportedStringListDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for k, dict := range m {
+		ret = append(ret, bazelConstant{
+			variableName:       k,
+			internalDefinition: starlark_fmt.PrintStringListDict(dict, 0),
+		})
+	}
+	return ret
+}
+
+// ExportedVariableReferenceDictVariables is a mapping from variable names to a
+// dictionary which references previously defined variables. This is used to
+// create a Starlark output such as:
+// 		string_var1 = "string1
+// 		var_ref_dict_var1 = {
+// 			"key1": string_var1
+// 		}
+// This type of variable collection must be expanded last so that it recognizes
+// previously defined variables.
+type ExportedVariableReferenceDictVariables map[string]map[string]string
+
+func (m ExportedVariableReferenceDictVariables) set(k string, v map[string]string) {
+	m[k] = v
+}
+
+func (m ExportedVariableReferenceDictVariables) asBazel(_ Config, _ ExportedStringVariables,
+	_ ExportedStringListVariables, _ ExportedConfigDependingVariables) []bazelConstant {
+	ret := make([]bazelConstant, 0, len(m))
+	for n, dict := range m {
+		for k, v := range dict {
+			matches, err := variableReference(v)
+			if err != nil {
+				panic(err)
+			} else if !matches.matches {
+				panic(fmt.Errorf("Expected a variable reference, got %q", v))
+			} else if len(matches.fullVariableReference) != len(v) {
+				panic(fmt.Errorf("Expected only a variable reference, got %q", v))
+			}
+			dict[k] = "_" + matches.variable
+		}
+		ret = append(ret, bazelConstant{
+			variableName:       n,
+			internalDefinition: starlark_fmt.PrintDict(dict, 0),
+			sortLast:           true,
+		})
+	}
+	return ret
+}
+
+// BazelToolchainVars expands an ExportedVariables collection and returns a string
+// of formatted Starlark variable definitions
+func BazelToolchainVars(config Config, exportedVars ExportedVariables) string {
+	results := exportedVars.asBazel(
+		config,
+		exportedVars.exportedStringVars,
+		exportedVars.exportedStringListVars,
+		exportedVars.exportedConfigDependingVars,
+	)
+
+	sort.Slice(results, func(i, j int) bool {
+		if results[i].sortLast != results[j].sortLast {
+			return !results[i].sortLast
+		}
+		return results[i].variableName < results[j].variableName
+	})
+
+	definitions := make([]string, 0, len(results))
+	constants := make([]string, 0, len(results))
+	for _, b := range results {
+		definitions = append(definitions,
+			fmt.Sprintf("_%s = %s", b.variableName, b.internalDefinition))
+		constants = append(constants,
+			fmt.Sprintf("%[1]s%[2]s = _%[2]s,", starlark_fmt.Indention(1), b.variableName))
+	}
+
+	// Build the exported constants struct.
+	ret := bazel.GeneratedBazelFileWarning
+	ret += "\n\n"
+	ret += strings.Join(definitions, "\n\n")
+	ret += "\n\n"
+	ret += "constants = struct(\n"
+	ret += strings.Join(constants, "\n")
+	ret += "\n)"
+
+	return ret
+}
+
+type match struct {
+	matches               bool
+	fullVariableReference string
+	variable              string
+}
+
+func variableReference(input string) (match, error) {
+	// e.g. "${ExternalCflags}"
+	r := regexp.MustCompile(`\${(?:config\.)?([a-zA-Z0-9_]+)}`)
+
+	matches := r.FindStringSubmatch(input)
+	if len(matches) == 0 {
+		return match{}, nil
+	}
+	if len(matches) != 2 {
+		return match{}, fmt.Errorf("Expected to only match 1 subexpression in %s, got %d", input, len(matches)-1)
+	}
+	return match{
+		matches:               true,
+		fullVariableReference: matches[0],
+		// Index 1 of FindStringSubmatch contains the subexpression match
+		// (variable name) of the capture group.
+		variable: matches[1],
+	}, nil
+}
+
+// expandVar recursively expand interpolated variables in the exportedVars scope.
+//
+// We're using a string slice to track the seen variables to avoid
+// stackoverflow errors with infinite recursion. it's simpler to use a
+// string slice than to handle a pass-by-referenced map, which would make it
+// quite complex to track depth-first interpolations. It's also unlikely the
+// interpolation stacks are deep (n > 1).
+func expandVar(config Config, toExpand string, stringScope ExportedStringVariables,
+	stringListScope ExportedStringListVariables, exportedVars ExportedConfigDependingVariables) ([]string, error) {
+
+	// Internal recursive function.
+	var expandVarInternal func(string, map[string]bool) (string, error)
+	expandVarInternal = func(toExpand string, seenVars map[string]bool) (string, error) {
+		var ret string
+		remainingString := toExpand
+		for len(remainingString) > 0 {
+			matches, err := variableReference(remainingString)
+			if err != nil {
+				panic(err)
+			}
+			if !matches.matches {
+				return ret + remainingString, nil
+			}
+			matchIndex := strings.Index(remainingString, matches.fullVariableReference)
+			ret += remainingString[:matchIndex]
+			remainingString = remainingString[matchIndex+len(matches.fullVariableReference):]
+
+			variable := matches.variable
+			// toExpand contains a variable.
+			if _, ok := seenVars[variable]; ok {
+				return ret, fmt.Errorf(
+					"Unbounded recursive interpolation of variable: %s", variable)
+			}
+			// A map is passed-by-reference. Create a new map for
+			// this scope to prevent variables seen in one depth-first expansion
+			// to be also treated as "seen" in other depth-first traversals.
+			newSeenVars := map[string]bool{}
+			for k := range seenVars {
+				newSeenVars[k] = true
+			}
+			newSeenVars[variable] = true
+			if unexpandedVars, ok := stringListScope[variable]; ok {
+				expandedVars := []string{}
+				for _, unexpandedVar := range unexpandedVars {
+					expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+					if err != nil {
+						return ret, err
+					}
+					expandedVars = append(expandedVars, expandedVar)
+				}
+				ret += strings.Join(expandedVars, " ")
+			} else if unexpandedVar, ok := stringScope[variable]; ok {
+				expandedVar, err := expandVarInternal(unexpandedVar, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else if unevaluatedVar, ok := exportedVars[variable]; ok {
+				evalFunc := reflect.ValueOf(unevaluatedVar)
+				validateVariableMethod(variable, evalFunc)
+				evaluatedResult := evalFunc.Call([]reflect.Value{reflect.ValueOf(config)})
+				evaluatedValue := evaluatedResult[0].Interface().(string)
+				expandedVar, err := expandVarInternal(evaluatedValue, newSeenVars)
+				if err != nil {
+					return ret, err
+				}
+				ret += expandedVar
+			} else {
+				return "", fmt.Errorf("Unbound config variable %s", variable)
+			}
+		}
+		return ret, nil
+	}
+	var ret []string
+	stringFields := splitStringKeepingQuotedSubstring(toExpand, ' ')
+	for _, v := range stringFields {
+		val, err := expandVarInternal(v, map[string]bool{})
+		if err != nil {
+			return ret, err
+		}
+		ret = append(ret, val)
+	}
+
+	return ret, nil
+}
+
+// splitStringKeepingQuotedSubstring splits a string on a provided separator,
+// but it will not split substrings inside unescaped double quotes. If the double
+// quotes are escaped, then the returned string will only include the quote, and
+// not the escape.
+func splitStringKeepingQuotedSubstring(s string, delimiter byte) []string {
+	var ret []string
+	quote := byte('"')
+
+	var substring []byte
+	quoted := false
+	escaped := false
+
+	for i := range s {
+		if !quoted && s[i] == delimiter {
+			ret = append(ret, string(substring))
+			substring = []byte{}
+			continue
+		}
+
+		characterIsEscape := i < len(s)-1 && s[i] == '\\' && s[i+1] == quote
+		if characterIsEscape {
+			escaped = true
+			continue
+		}
+
+		if s[i] == quote {
+			if !escaped {
+				quoted = !quoted
+			}
+			escaped = false
+		}
+
+		substring = append(substring, s[i])
+	}
+
+	ret = append(ret, string(substring))
+
+	return ret
+}
+
+func validateVariableMethod(name string, methodValue reflect.Value) {
+	methodType := methodValue.Type()
+	if methodType.Kind() != reflect.Func {
+		panic(fmt.Errorf("method given for variable %s is not a function",
+			name))
+	}
+	if n := methodType.NumIn(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d inputs (should be 1)",
+			name, n))
+	}
+	if n := methodType.NumOut(); n != 1 {
+		panic(fmt.Errorf("method for variable %s has %d outputs (should be 1)",
+			name, n))
+	}
+	if kind := methodType.Out(0).Kind(); kind != reflect.String {
+		panic(fmt.Errorf("method for variable %s does not return a string",
+			name))
+	}
+}
diff --git a/android/config_bp2build_test.go b/android/config_bp2build_test.go
new file mode 100644
index 0000000..1a0ba7b
--- /dev/null
+++ b/android/config_bp2build_test.go
@@ -0,0 +1,454 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"android/soong/bazel"
+	"testing"
+)
+
+func TestExpandVars(t *testing.T) {
+	android_arm64_config := TestConfig("out", nil, "", nil)
+	android_arm64_config.BuildOS = Android
+	android_arm64_config.BuildArch = Arm64
+
+	testCases := []struct {
+		description     string
+		config          Config
+		stringScope     ExportedStringVariables
+		stringListScope ExportedStringListVariables
+		configVars      ExportedConfigDependingVariables
+		toExpand        string
+		expectedValues  []string
+	}{
+		{
+			description:    "no expansion for non-interpolated value",
+			toExpand:       "foo",
+			expectedValues: []string{"foo"},
+		},
+		{
+			description: "single level expansion for string var",
+			stringScope: ExportedStringVariables{
+				"foo": "bar",
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "single level expansion with short-name for string var",
+			stringScope: ExportedStringVariables{
+				"foo": "bar",
+			},
+			toExpand:       "${config.foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "single level expansion string list var",
+			stringListScope: ExportedStringListVariables{
+				"foo": []string{"bar"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "mixed level expansion for string list var",
+			stringScope: ExportedStringVariables{
+				"foo": "${bar}",
+				"qux": "hello",
+			},
+			stringListScope: ExportedStringListVariables{
+				"bar": []string{"baz", "${qux}"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz hello"},
+		},
+		{
+			description: "double level expansion",
+			stringListScope: ExportedStringListVariables{
+				"foo": []string{"${bar}"},
+				"bar": []string{"baz"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz"},
+		},
+		{
+			description: "double level expansion with a literal",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b}", "c"},
+				"b": []string{"d"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d c"},
+		},
+		{
+			description: "double level expansion, with two variables in a string",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"d"},
+				"c": []string{"e"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d e"},
+		},
+		{
+			description: "triple level expansion with two variables in a string",
+			stringListScope: ExportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"${c}", "${d}"},
+				"c": []string{"${d}"},
+				"d": []string{"foo"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"foo foo foo"},
+		},
+		{
+			description: "expansion with config depending vars",
+			configVars: ExportedConfigDependingVariables{
+				"a": func(c Config) string { return c.BuildOS.String() },
+				"b": func(c Config) string { return c.BuildArch.String() },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${a}-${b}",
+			expectedValues: []string{"android-arm64"},
+		},
+		{
+			description: "double level multi type expansion",
+			stringListScope: ExportedStringListVariables{
+				"platform": []string{"${os}-${arch}"},
+				"const":    []string{"const"},
+			},
+			configVars: ExportedConfigDependingVariables{
+				"os":   func(c Config) string { return c.BuildOS.String() },
+				"arch": func(c Config) string { return c.BuildArch.String() },
+				"foo":  func(c Config) string { return "foo" },
+			},
+			config:         android_arm64_config,
+			toExpand:       "${const}/${platform}/${foo}",
+			expectedValues: []string{"const/android-arm64/foo"},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			output, _ := expandVar(testCase.config, testCase.toExpand, testCase.stringScope, testCase.stringListScope, testCase.configVars)
+			if len(output) != len(testCase.expectedValues) {
+				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
+			}
+			for i, actual := range output {
+				expectedValue := testCase.expectedValues[i]
+				if actual != expectedValue {
+					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
+				}
+			}
+		})
+	}
+}
+
+func TestBazelToolchainVars(t *testing.T) {
+	testCases := []struct {
+		name        string
+		config      Config
+		vars        ExportedVariables
+		expectedOut string
+	}{
+		{
+			name: "exports strings",
+			vars: ExportedVariables{
+				exportedStringVars: ExportedStringVariables{
+					"a": "b",
+					"c": "d",
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = "b"
+
+_c = "d"
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports string lists",
+			vars: ExportedVariables{
+				exportedStringListVars: ExportedStringListVariables{
+					"a": []string{"b1", "b2"},
+					"c": []string{"d1", "d2"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = [
+    "b1",
+    "b2",
+]
+
+_c = [
+    "d1",
+    "d2",
+]
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports string lists dicts",
+			vars: ExportedVariables{
+				exportedStringListDictVars: ExportedStringListDictVariables{
+					"a": map[string][]string{"b1": {"b2"}},
+					"c": map[string][]string{"d1": {"d2"}},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "b1": ["b2"],
+}
+
+_c = {
+    "d1": ["d2"],
+}
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "exports dict with var refs",
+			vars: ExportedVariables{
+				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+					"a": map[string]string{"b1": "${b2}"},
+					"c": map[string]string{"d1": "${config.d2}"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "b1": _b2,
+}
+
+_c = {
+    "d1": _d2,
+}
+
+constants = struct(
+    a = _a,
+    c = _c,
+)`,
+		},
+		{
+			name: "sorts across types with variable references last",
+			vars: ExportedVariables{
+				exportedStringVars: ExportedStringVariables{
+					"b": "b-val",
+					"d": "d-val",
+				},
+				exportedStringListVars: ExportedStringListVariables{
+					"c": []string{"c-val"},
+					"e": []string{"e-val"},
+				},
+				exportedStringListDictVars: ExportedStringListDictVariables{
+					"a": map[string][]string{"a1": {"a2"}},
+					"f": map[string][]string{"f1": {"f2"}},
+				},
+				exportedVariableReferenceDictVars: ExportedVariableReferenceDictVariables{
+					"aa": map[string]string{"b1": "${b}"},
+					"cc": map[string]string{"d1": "${config.d}"},
+				},
+			},
+			expectedOut: bazel.GeneratedBazelFileWarning + `
+
+_a = {
+    "a1": ["a2"],
+}
+
+_b = "b-val"
+
+_c = ["c-val"]
+
+_d = "d-val"
+
+_e = ["e-val"]
+
+_f = {
+    "f1": ["f2"],
+}
+
+_aa = {
+    "b1": _b,
+}
+
+_cc = {
+    "d1": _d,
+}
+
+constants = struct(
+    a = _a,
+    b = _b,
+    c = _c,
+    d = _d,
+    e = _e,
+    f = _f,
+    aa = _aa,
+    cc = _cc,
+)`,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			out := BazelToolchainVars(tc.config, tc.vars)
+			if out != tc.expectedOut {
+				t.Errorf("Expected \n%s, got \n%s", tc.expectedOut, out)
+			}
+		})
+	}
+}
+
+func TestSplitStringKeepingQuotedSubstring(t *testing.T) {
+	testCases := []struct {
+		description string
+		s           string
+		delimiter   byte
+		split       []string
+	}{
+		{
+			description: "empty string returns single empty string",
+			s:           "",
+			delimiter:   ' ',
+			split: []string{
+				"",
+			},
+		},
+		{
+			description: "string with single space returns two empty strings",
+			s:           " ",
+			delimiter:   ' ',
+			split: []string{
+				"",
+				"",
+			},
+		},
+		{
+			description: "string with two spaces returns three empty strings",
+			s:           "  ",
+			delimiter:   ' ',
+			split: []string{
+				"",
+				"",
+				"",
+			},
+		},
+		{
+			description: "string with four words returns four word string",
+			s:           "hello world with words",
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				"world",
+				"with",
+				"words",
+			},
+		},
+		{
+			description: "string with words and nested quote returns word strings and quote string",
+			s:           `hello "world with" words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world with"`,
+				"words",
+			},
+		},
+		{
+			description: "string with escaped quote inside real quotes",
+			s:           `hello \"world "with\" words"`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world`,
+				`"with" words"`,
+			},
+		},
+		{
+			description: "string with words and escaped quotes returns word strings",
+			s:           `hello \"world with\" words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world`,
+				`with"`,
+				"words",
+			},
+		},
+		{
+			description: "string which is single quoted substring returns only substring",
+			s:           `"hello world with words"`,
+			delimiter:   ' ',
+			split: []string{
+				`"hello world with words"`,
+			},
+		},
+		{
+			description: "string starting with quote returns quoted string",
+			s:           `"hello world with" words`,
+			delimiter:   ' ',
+			split: []string{
+				`"hello world with"`,
+				"words",
+			},
+		},
+		{
+			description: "string with starting quote and no ending quote returns quote to end of string",
+			s:           `hello "world with words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world with words`,
+			},
+		},
+		{
+			description: "quoted string is treated as a single \"word\" unless separated by delimiter",
+			s:           `hello "world"with words`,
+			delimiter:   ' ',
+			split: []string{
+				"hello",
+				`"world"with`,
+				"words",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			split := splitStringKeepingQuotedSubstring(tc.s, tc.delimiter)
+			if len(split) != len(tc.split) {
+				t.Fatalf("number of split string elements (%d) differs from expected (%d): split string (%v), expected (%v)",
+					len(split), len(tc.split), split, tc.split,
+				)
+			}
+			for i := range split {
+				if split[i] != tc.split[i] {
+					t.Errorf("split string element (%d), %v, differs from expected, %v", i, split[i], tc.split[i])
+				}
+			}
+		})
+	}
+}
diff --git a/android/deapexer.go b/android/deapexer.go
index de3f635..6a93f60 100644
--- a/android/deapexer.go
+++ b/android/deapexer.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"strings"
+
 	"github.com/google/blueprint"
 )
 
@@ -69,11 +71,18 @@
 
 // The information exported by the `deapexer` module, access it using `DeapxerInfoProvider`.
 type DeapexerInfo struct {
+	apexModuleName string
+
 	// map from the name of an exported file from a prebuilt_apex to the path to that file. The
 	// exported file name is the apex relative path, e.g. javalib/core-libart.jar.
 	//
 	// See Prebuilt.ApexInfoMutator for more information.
-	exports map[string]Path
+	exports map[string]WritablePath
+}
+
+// ApexModuleName returns the name of the APEX module that provided the info.
+func (i DeapexerInfo) ApexModuleName() string {
+	return i.apexModuleName
 }
 
 // PrebuiltExportPath provides the path, or nil if not available, of a file exported from the
@@ -82,7 +91,7 @@
 // The exported file is identified by the apex relative path, e.g. "javalib/core-libart.jar".
 //
 // See apex/deapexer.go for more information.
-func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) Path {
+func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) WritablePath {
 	path := i.exports[apexRelativePath]
 	return path
 }
@@ -95,9 +104,10 @@
 // for use with a prebuilt_apex module.
 //
 // See apex/deapexer.go for more information.
-func NewDeapexerInfo(exports map[string]Path) DeapexerInfo {
+func NewDeapexerInfo(apexModuleName string, exports map[string]WritablePath) DeapexerInfo {
 	return DeapexerInfo{
-		exports: exports,
+		apexModuleName: apexModuleName,
+		exports:        exports,
 	}
 }
 
@@ -133,3 +143,61 @@
 	// Method that differentiates this interface from others.
 	RequiresFilesFromPrebuiltApex()
 }
+
+// FindDeapexerProviderForModule searches through the direct dependencies of the current context
+// module for a DeapexerTag dependency and returns its DeapexerInfo. If a single nonambiguous
+// deapexer module isn't found then errors are reported with ctx.ModuleErrorf and nil is returned.
+func FindDeapexerProviderForModule(ctx ModuleContext) *DeapexerInfo {
+	var di *DeapexerInfo
+	ctx.VisitDirectDepsWithTag(DeapexerTag, func(m Module) {
+		c := ctx.OtherModuleProvider(m, DeapexerProvider).(DeapexerInfo)
+		p := &c
+		if di != nil {
+			// If two DeapexerInfo providers have been found then check if they are
+			// equivalent. If they are then use the selected one, otherwise fail.
+			if selected := equivalentDeapexerInfoProviders(di, p); selected != nil {
+				di = selected
+				return
+			}
+			ctx.ModuleErrorf("Multiple installable prebuilt APEXes provide ambiguous deapexers: %s and %s",
+				di.ApexModuleName(), p.ApexModuleName())
+		}
+		di = p
+	})
+	if di != nil {
+		return di
+	}
+	ai := ctx.Provider(ApexInfoProvider).(ApexInfo)
+	ctx.ModuleErrorf("No prebuilt APEX provides a deapexer module for APEX variant %s", ai.ApexVariationName)
+	return nil
+}
+
+// removeCompressedApexSuffix removes the _compressed suffix from the name if present.
+func removeCompressedApexSuffix(name string) string {
+	return strings.TrimSuffix(name, "_compressed")
+}
+
+// equivalentDeapexerInfoProviders checks to make sure that the two DeapexerInfo structures are
+// equivalent.
+//
+// At the moment <x> and <x>_compressed APEXes are treated as being equivalent.
+//
+// If they are not equivalent then this returns nil, otherwise, this returns the DeapexerInfo that
+// should be used by the build, which is always the uncompressed one. That ensures that the behavior
+// of the build is not dependent on which prebuilt APEX is visited first.
+func equivalentDeapexerInfoProviders(p1 *DeapexerInfo, p2 *DeapexerInfo) *DeapexerInfo {
+	n1 := removeCompressedApexSuffix(p1.ApexModuleName())
+	n2 := removeCompressedApexSuffix(p2.ApexModuleName())
+
+	// If the names don't match then they are not equivalent.
+	if n1 != n2 {
+		return nil
+	}
+
+	// Select the uncompressed APEX.
+	if n1 == removeCompressedApexSuffix(n1) {
+		return p1
+	} else {
+		return p2
+	}
+}
diff --git a/android/defaults.go b/android/defaults.go
index aacfbac..8b121f6 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -89,14 +89,13 @@
 var _ Defaultable = (*DefaultableModuleBase)(nil)
 
 func InitDefaultableModule(module DefaultableModule) {
-	if module.(Module).base().module == nil {
+	if module.base().module == nil {
 		panic("InitAndroidModule must be called before InitDefaultableModule")
 	}
-	module.setProperties(module.(Module).GetProperties(), module.(Module).base().variableProperties)
+
+	module.setProperties(module.GetProperties(), module.base().variableProperties)
 
 	module.AddProperties(module.defaults())
-
-	module.base().customizableProperties = module.GetProperties()
 }
 
 // A restricted subset of context methods, similar to LoadHookContext.
@@ -104,6 +103,7 @@
 	EarlyModuleContext
 
 	CreateModule(ModuleFactory, ...interface{}) Module
+	AddMissingDependencies(missingDeps []string)
 }
 
 type DefaultableHook func(ctx DefaultableHookContext)
@@ -117,6 +117,11 @@
 
 type DefaultsModuleBase struct {
 	DefaultableModuleBase
+
+	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
+	// target. This is primarily useful for modules that were architecture specific and instead are
+	// handled in Bazel as a select().
+	BazelModuleBase
 }
 
 // The common pattern for defaults modules is to register separate instances of
@@ -159,6 +164,7 @@
 type DefaultsModule interface {
 	Module
 	Defaults
+	Bazelable
 }
 
 func (d *DefaultsModuleBase) properties() []interface{} {
@@ -169,8 +175,11 @@
 	return d.defaultableVariableProperties
 }
 
-func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {
-}
+func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {}
+
+// ConvertWithBp2build to fulfill Bazelable interface; however, at this time defaults module are
+// *NOT* converted with bp2build
+func (defaultable *DefaultsModuleBase) ConvertWithBp2build(ctx TopDownMutatorContext) {}
 
 func InitDefaultsModule(module DefaultsModule) {
 	commonProperties := &commonProperties{}
@@ -181,6 +190,8 @@
 		&ApexProperties{},
 		&distProperties{})
 
+	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
+	InitBazelModule(module)
 	initAndroidModuleBase(module)
 	initProductVariableModule(module)
 	initArchModule(module)
@@ -207,15 +218,64 @@
 	// The applicable licenses property for defaults is 'licenses'.
 	setPrimaryLicensesProperty(module, "licenses", &commonProperties.Licenses)
 
-	base.module = module
 }
 
 var _ Defaults = (*DefaultsModuleBase)(nil)
 
+// applyNamespacedVariableDefaults only runs in bp2build mode for
+// defaultable/defaults modules. Its purpose is to merge namespaced product
+// variable props from defaults deps, even if those defaults are custom module
+// types created from soong_config_module_type, e.g. one that's wrapping a
+// cc_defaults or java_defaults.
+func applyNamespacedVariableDefaults(defaultDep Defaults, ctx TopDownMutatorContext) {
+	var dep, b Bazelable
+
+	dep, ok := defaultDep.(Bazelable)
+	if !ok {
+		if depMod, ok := defaultDep.(Module); ok {
+			// Track that this dependency hasn't been converted to bp2build yet.
+			ctx.AddUnconvertedBp2buildDep(depMod.Name())
+			return
+		} else {
+			panic("Expected default dep to be a Module.")
+		}
+	}
+
+	b, ok = ctx.Module().(Bazelable)
+	if !ok {
+		return
+	}
+
+	// namespacedVariableProps is a map from namespaces (e.g. acme, android,
+	// vendor_foo) to a slice of soong_config_variable struct pointers,
+	// containing properties for that particular module.
+	src := dep.namespacedVariableProps()
+	dst := b.namespacedVariableProps()
+	if dst == nil {
+		dst = make(namespacedVariableProperties)
+	}
+
+	// Propagate all soong_config_variable structs from the dep. We'll merge the
+	// actual property values later in variable.go.
+	for namespace := range src {
+		if dst[namespace] == nil {
+			dst[namespace] = []interface{}{}
+		}
+		for _, i := range src[namespace] {
+			dst[namespace] = append(dst[namespace], i)
+		}
+	}
+
+	b.setNamespacedVariableProps(dst)
+}
+
 func (defaultable *DefaultableModuleBase) applyDefaults(ctx TopDownMutatorContext,
 	defaultsList []Defaults) {
 
 	for _, defaults := range defaultsList {
+		if ctx.Config().runningAsBp2Build {
+			applyNamespacedVariableDefaults(defaults, ctx)
+		}
 		for _, prop := range defaultable.defaultableProperties {
 			if prop == defaultable.defaultableVariableProperties {
 				defaultable.applyDefaultVariableProperties(ctx, defaults, prop)
diff --git a/android/defs.go b/android/defs.go
index b3ff376..362b382 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -52,10 +52,10 @@
 	// A copy rule.
 	Cp = pctx.AndroidStaticRule("Cp",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out",
+			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out$extraCmds",
 			Description: "cp $out",
 		},
-		"cpFlags")
+		"cpFlags", "extraCmds")
 
 	// A copy rule that only updates the output if it changed.
 	CpIfChanged = pctx.AndroidStaticRule("CpIfChanged",
@@ -68,10 +68,10 @@
 
 	CpExecutable = pctx.AndroidStaticRule("CpExecutable",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out && chmod +x $out",
+			Command:     "rm -f $out && cp $cpPreserveSymlinks $cpFlags $in $out && chmod +x $out$extraCmds",
 			Description: "cp $out",
 		},
-		"cpFlags")
+		"cpFlags", "extraCmds")
 
 	// A timestamp touch rule.
 	Touch = pctx.AndroidStaticRule("Touch",
@@ -188,6 +188,15 @@
 	buildWriteFileRule(ctx, outputFile, content)
 }
 
+func CatFileRule(ctx BuilderContext, paths Paths, outputFile WritablePath) {
+	ctx.Build(pctx, BuildParams{
+		Rule:        Cat,
+		Inputs:      paths,
+		Output:      outputFile,
+		Description: "combine files to " + outputFile.Base(),
+	})
+}
+
 // shellUnescape reverses proptools.ShellEscape
 func shellUnescape(s string) string {
 	// Remove leading and trailing quotes if present
diff --git a/android/filegroup.go b/android/filegroup.go
index fc6850e..50356d1 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -15,57 +15,66 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"strings"
+
+	"android/soong/bazel"
+
+	"github.com/google/blueprint"
 )
 
 func init() {
 	RegisterModuleType("filegroup", FileGroupFactory)
-	RegisterBp2BuildMutator("filegroup", FilegroupBp2Build)
 }
 
 var PrepareForTestWithFilegroup = FixtureRegisterWithContext(func(ctx RegistrationContext) {
 	ctx.RegisterModuleType("filegroup", FileGroupFactory)
 })
 
+// IsFilegroup checks that a module is a filegroup type
+func IsFilegroup(ctx bazel.OtherModuleContext, m blueprint.Module) bool {
+	return ctx.OtherModuleType(m) == "filegroup"
+}
+
 // https://docs.bazel.build/versions/master/be/general.html#filegroup
 type bazelFilegroupAttributes struct {
 	Srcs bazel.LabelListAttribute
 }
 
-type bazelFilegroup struct {
-	BazelTargetModuleBase
-	bazelFilegroupAttributes
-}
-
-func BazelFileGroupFactory() Module {
-	module := &bazelFilegroup{}
-	module.AddProperties(&module.bazelFilegroupAttributes)
-	InitBazelTargetModule(module)
-	return module
-}
-
-func (bfg *bazelFilegroup) Name() string {
-	return bfg.BaseModuleName()
-}
-
-func (bfg *bazelFilegroup) GenerateAndroidBuildActions(ctx ModuleContext) {}
-
-func FilegroupBp2Build(ctx TopDownMutatorContext) {
-	fg, ok := ctx.Module().(*fileGroup)
-	if !ok || !fg.ConvertWithBp2build(ctx) {
-		return
-	}
-
+// ConvertWithBp2build performs bp2build conversion of filegroup
+func (fg *fileGroup) ConvertWithBp2build(ctx TopDownMutatorContext) {
 	srcs := bazel.MakeLabelListAttribute(
 		BazelLabelForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs))
+
+	// For Bazel compatibility, don't generate the filegroup if there is only 1
+	// source file, and that the source file is named the same as the module
+	// itself. In Bazel, eponymous filegroups like this would be an error.
+	//
+	// Instead, dependents on this single-file filegroup can just depend
+	// on the file target, instead of rule target, directly.
+	//
+	// You may ask: what if a filegroup has multiple files, and one of them
+	// shares the name? The answer: we haven't seen that in the wild, and
+	// should lock Soong itself down to prevent the behavior. For now,
+	// we raise an error if bp2build sees this problem.
+	for _, f := range srcs.Value.Includes {
+		if f.Label == fg.Name() {
+			if len(srcs.Value.Includes) > 1 {
+				ctx.ModuleErrorf("filegroup '%s' cannot contain a file with the same name", fg.Name())
+			}
+			return
+		}
+	}
+
 	attrs := &bazelFilegroupAttributes{
 		Srcs: srcs,
 	}
 
-	props := bazel.BazelTargetModuleProperties{Rule_class: "filegroup"}
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "filegroup",
+		Bzl_load_location: "//build/bazel/rules:filegroup.bzl",
+	}
 
-	ctx.CreateBazelTargetModule(BazelFileGroupFactory, fg.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, CommonAttributes{Name: fg.Name()}, attrs)
 }
 
 type fileGroupProperties struct {
@@ -105,15 +114,24 @@
 	return module
 }
 
-func (fg *fileGroup) generateBazelBuildActions(ctx ModuleContext) bool {
+func (fg *fileGroup) maybeGenerateBazelBuildActions(ctx ModuleContext) {
 	if !fg.MixedBuildsEnabled(ctx) {
-		return false
+		return
+	}
+
+	archVariant := ctx.Arch().String()
+	osVariant := ctx.Os()
+	if len(fg.Srcs()) == 1 && fg.Srcs()[0].Base() == fg.Name() {
+		// This will be a regular file target, not filegroup, in Bazel.
+		// See FilegroupBp2Build for more information.
+		archVariant = Common.String()
+		osVariant = CommonOS
 	}
 
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), ctx.Arch().ArchType)
+	filePaths, ok := bazelCtx.GetOutputFiles(fg.GetBazelLabel(ctx, fg), configKey{archVariant, osVariant})
 	if !ok {
-		return false
+		return
 	}
 
 	bazelOuts := make(Paths, 0, len(filePaths))
@@ -123,19 +141,15 @@
 	}
 
 	fg.srcs = bazelOuts
-
-	return true
 }
 
 func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
-	if fg.generateBazelBuildActions(ctx) {
-		return
-	}
-
 	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
 	if fg.properties.Path != nil {
 		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
 	}
+
+	fg.maybeGenerateBazelBuildActions(ctx)
 }
 
 func (fg *fileGroup) Srcs() Paths {
diff --git a/android/fixture.go b/android/fixture.go
index fd051a7..728f031 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -834,7 +834,7 @@
 func (r *TestResult) NormalizePathForTesting(path Path) string {
 	pathContext := PathContextForTesting(r.Config)
 	pathAsString := path.String()
-	if rel, isRel := MaybeRel(pathContext, r.Config.BuildDir(), pathAsString); isRel {
+	if rel, isRel := MaybeRel(pathContext, r.Config.SoongOutDir(), pathAsString); isRel {
 		return rel
 	}
 	return pathAsString
diff --git a/android/hooks.go b/android/hooks.go
index 85fc081..5e3a4a7 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -15,7 +15,10 @@
 package android
 
 import (
+	"fmt"
+	"path"
 	"reflect"
+	"runtime"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -65,10 +68,10 @@
 	return l.bp.ModuleFactories()
 }
 
-func (l *loadHookContext) AppendProperties(props ...interface{}) {
+func (l *loadHookContext) appendPrependHelper(props []interface{},
+	extendFn func([]interface{}, interface{}, proptools.ExtendPropertyFilterFunc) error) {
 	for _, p := range props {
-		err := proptools.AppendMatchingProperties(l.Module().base().customizableProperties,
-			p, nil)
+		err := extendFn(l.Module().base().GetProperties(), p, nil)
 		if err != nil {
 			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
 				l.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
@@ -78,24 +81,29 @@
 		}
 	}
 }
+func (l *loadHookContext) AppendProperties(props ...interface{}) {
+	l.appendPrependHelper(props, proptools.AppendMatchingProperties)
+}
 
 func (l *loadHookContext) PrependProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.PrependMatchingProperties(l.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				l.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
-	}
+	l.appendPrependHelper(props, proptools.PrependMatchingProperties)
 }
 
 func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
 	inherited := []interface{}{&l.Module().base().commonProperties}
-	module := l.bp.CreateModule(ModuleFactoryAdaptor(factory), append(inherited, props...)...).(Module)
+
+	var typeName string
+	if typeNameLookup, ok := ModuleTypeByFactory()[reflect.ValueOf(factory)]; ok {
+		typeName = typeNameLookup
+	} else {
+		factoryPtr := reflect.ValueOf(factory).Pointer()
+		factoryFunc := runtime.FuncForPC(factoryPtr)
+		filePath, _ := factoryFunc.FileLine(factoryPtr)
+		typeName = fmt.Sprintf("%s_%s", path.Base(filePath), factoryFunc.Name())
+	}
+	typeName = typeName + "_loadHookModule"
+
+	module := l.bp.CreateModule(ModuleFactoryAdaptor(factory), typeName, append(inherited, props...)...).(Module)
 
 	if l.Module().base().variableProperties != nil && module.base().variableProperties != nil {
 		src := l.Module().base().variableProperties
diff --git a/android/image.go b/android/image.go
index 66101be..bc6b8cd 100644
--- a/android/image.go
+++ b/android/image.go
@@ -43,10 +43,9 @@
 	// its variation.
 	ExtraImageVariations(ctx BaseModuleContext) []string
 
-	// SetImageVariation will be passed a newly created recovery variant of the module.  ModuleBase implements
-	// SetImageVariation, most module types will not need to override it, and those that do must call the
-	// overridden method.  Implementors of SetImageVariation must be careful to modify the module argument
-	// and not the receiver.
+	// SetImageVariation is called for each newly created image variant. The receiver is the original
+	// module, "variation" is the name of the newly created variant and "module" is the newly created
+	// variant itself.
 	SetImageVariation(ctx BaseModuleContext, variation string, module Module)
 }
 
diff --git a/android/license.go b/android/license.go
index 8bfd3ba..ebee055 100644
--- a/android/license.go
+++ b/android/license.go
@@ -63,7 +63,7 @@
 func (m *licenseModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	// license modules have no licenses, but license_kinds must refer to license_kind modules
 	mergeStringProps(&m.base().commonProperties.Effective_licenses, ctx.ModuleName())
-	mergePathProps(&m.base().commonProperties.Effective_license_text, PathsForModuleSrc(ctx, m.properties.License_text)...)
+	namePathProps(&m.base().commonProperties.Effective_license_text, m.properties.Package_name, PathsForModuleSrc(ctx, m.properties.License_text)...)
 	for _, module := range ctx.GetDirectDepsWithTag(licenseKindTag) {
 		if lk, ok := module.(*licenseKindModule); ok {
 			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, lk.properties.Conditions...)
@@ -80,9 +80,6 @@
 	base := module.base()
 	module.AddProperties(&base.nameProperties, &module.properties)
 
-	base.generalProperties = module.GetProperties()
-	base.customizableProperties = module.GetProperties()
-
 	// The visibility property needs to be checked and parsed by the visibility module.
 	setPrimaryVisibilityProperty(module, "visibility", &module.properties.Visibility)
 
diff --git a/android/license_kind.go b/android/license_kind.go
index ddecd77..838dedd 100644
--- a/android/license_kind.go
+++ b/android/license_kind.go
@@ -53,9 +53,6 @@
 	base := module.base()
 	module.AddProperties(&base.nameProperties, &module.properties)
 
-	base.generalProperties = module.GetProperties()
-	base.customizableProperties = module.GetProperties()
-
 	// The visibility property needs to be checked and parsed by the visibility module.
 	setPrimaryVisibilityProperty(module, "visibility", &module.properties.Visibility)
 
diff --git a/android/license_kind_test.go b/android/license_kind_test.go
index 1f09568..7a909a6 100644
--- a/android/license_kind_test.go
+++ b/android/license_kind_test.go
@@ -14,38 +14,38 @@
 	{
 		name: "license_kind must not accept licenses property",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_license",
 					licenses: ["other_license"],
 				}`),
 		},
 		expectedErrors: []string{
-			`top/Blueprints:4:14: unrecognized property "licenses"`,
+			`top/Android.bp:4:14: unrecognized property "licenses"`,
 		},
 	},
 	{
 		name: "bad license_kind",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_notice",
 					conditions: ["notice"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_license {
 					name: "other_notice",
 					license_kinds: ["notice"],
 				}`),
 		},
 		expectedErrors: []string{
-			`other/Blueprints:2:5: "other_notice" depends on undefined module "notice"`,
+			`other/Android.bp:2:5: "other_notice" depends on undefined module "notice"`,
 		},
 	},
 	{
 		name: "good license kind",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_by_exception_only",
 					conditions: ["by_exception_only"],
@@ -55,7 +55,7 @@
 					name: "top_proprietary",
 					license_kinds: ["top_by_exception_only"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_license {
 					name: "other_proprietary",
 					license_kinds: ["top_proprietary"],
@@ -65,7 +65,7 @@
 	{
 		name: "multiple license kinds",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_notice",
 					conditions: ["notice"],
@@ -85,7 +85,7 @@
 					name: "top_proprietary",
 					license_kinds: ["top_by_exception_only"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_license {
 					name: "other_rule",
 					license_kinds: ["top_by_exception_only"],
diff --git a/android/license_metadata.go b/android/license_metadata.go
new file mode 100644
index 0000000..48c1383
--- /dev/null
+++ b/android/license_metadata.go
@@ -0,0 +1,254 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	_ = pctx.HostBinToolVariable("licenseMetadataCmd", "build_license_metadata")
+
+	licenseMetadataRule = pctx.AndroidStaticRule("licenseMetadataRule", blueprint.RuleParams{
+		Command:        "${licenseMetadataCmd} -o $out @${out}.rsp",
+		CommandDeps:    []string{"${licenseMetadataCmd}"},
+		Rspfile:        "${out}.rsp",
+		RspfileContent: "${args}",
+	}, "args")
+)
+
+func buildLicenseMetadata(ctx ModuleContext, licenseMetadataFile WritablePath) {
+	base := ctx.Module().base()
+
+	if !base.Enabled() {
+		return
+	}
+
+	if exemptFromRequiredApplicableLicensesProperty(ctx.Module()) {
+		return
+	}
+
+	var outputFiles Paths
+	if outputFileProducer, ok := ctx.Module().(OutputFileProducer); ok {
+		outputFiles, _ = outputFileProducer.OutputFiles("")
+		outputFiles = PathsIfNonNil(outputFiles...)
+	}
+
+	isContainer := isContainerFromFileExtensions(base.installFiles, outputFiles)
+
+	var allDepMetadataFiles Paths
+	var allDepMetadataArgs []string
+	var allDepOutputFiles Paths
+	var allDepMetadataDepSets []*PathsDepSet
+
+	ctx.VisitDirectDepsBlueprint(func(bpdep blueprint.Module) {
+		dep, _ := bpdep.(Module)
+		if dep == nil {
+			return
+		}
+		if !dep.Enabled() {
+			return
+		}
+
+		if ctx.OtherModuleHasProvider(dep, LicenseMetadataProvider) {
+			info := ctx.OtherModuleProvider(dep, LicenseMetadataProvider).(*LicenseMetadataInfo)
+			allDepMetadataFiles = append(allDepMetadataFiles, info.LicenseMetadataPath)
+			if isContainer || IsInstallDepNeeded(ctx.OtherModuleDependencyTag(dep)) {
+				allDepMetadataDepSets = append(allDepMetadataDepSets, info.LicenseMetadataDepSet)
+			}
+
+			depAnnotations := licenseAnnotationsFromTag(ctx.OtherModuleDependencyTag(dep))
+
+			allDepMetadataArgs = append(allDepMetadataArgs, info.LicenseMetadataPath.String()+depAnnotations)
+
+			if depInstallFiles := dep.base().installFiles; len(depInstallFiles) > 0 {
+				allDepOutputFiles = append(allDepOutputFiles, depInstallFiles.Paths()...)
+			} else if depOutputFiles, err := outputFilesForModule(ctx, dep, ""); err == nil {
+				depOutputFiles = PathsIfNonNil(depOutputFiles...)
+				allDepOutputFiles = append(allDepOutputFiles, depOutputFiles...)
+			}
+		}
+	})
+
+	allDepMetadataFiles = SortedUniquePaths(allDepMetadataFiles)
+	sort.Strings(allDepMetadataArgs)
+	allDepOutputFiles = SortedUniquePaths(allDepOutputFiles)
+
+	var orderOnlyDeps Paths
+	var args []string
+
+	if t := ctx.ModuleType(); t != "" {
+		args = append(args,
+			"-mt "+proptools.NinjaAndShellEscape(t))
+	}
+
+	args = append(args,
+		"-r "+proptools.NinjaAndShellEscape(ctx.ModuleDir()),
+		"-mc UNKNOWN")
+
+	if p := base.commonProperties.Effective_package_name; p != nil {
+		args = append(args,
+			`-p "`+proptools.NinjaAndShellEscape(*p)+`"`)
+	}
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_kinds), "-k "))
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_conditions), "-c "))
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_text.Strings()), "-n "))
+
+	if isContainer {
+		transitiveDeps := newPathsDepSet(nil, allDepMetadataDepSets).ToList()
+		args = append(args,
+			JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(transitiveDeps.Strings()), "-d "))
+		orderOnlyDeps = append(orderOnlyDeps, transitiveDeps...)
+	} else {
+		args = append(args,
+			JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(allDepMetadataArgs), "-d "))
+		orderOnlyDeps = append(orderOnlyDeps, allDepMetadataFiles...)
+	}
+
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(allDepOutputFiles.Strings()), "-s "))
+
+	// Install map
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.licenseInstallMap), "-m "))
+
+	// Built files
+	if len(outputFiles) > 0 {
+		args = append(args,
+			JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(outputFiles.Strings()), "-t "))
+	} else {
+		args = append(args, fmt.Sprintf("-t //%s:%s", ctx.ModuleDir(), ctx.ModuleName()))
+	}
+
+	// Installed files
+	args = append(args,
+		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.installFiles.Strings()), "-i "))
+
+	if isContainer {
+		args = append(args, "--is_container")
+	}
+
+	ctx.Build(pctx, BuildParams{
+		Rule:        licenseMetadataRule,
+		Output:      licenseMetadataFile,
+		OrderOnly:   orderOnlyDeps,
+		Description: "license metadata",
+		Args: map[string]string{
+			"args": strings.Join(args, " "),
+		},
+	})
+
+	ctx.SetProvider(LicenseMetadataProvider, &LicenseMetadataInfo{
+		LicenseMetadataPath:   licenseMetadataFile,
+		LicenseMetadataDepSet: newPathsDepSet(Paths{licenseMetadataFile}, allDepMetadataDepSets),
+	})
+}
+
+func isContainerFromFileExtensions(installPaths InstallPaths, builtPaths Paths) bool {
+	var paths Paths
+	if len(installPaths) > 0 {
+		paths = installPaths.Paths()
+	} else {
+		paths = builtPaths
+	}
+
+	for _, path := range paths {
+		switch path.Ext() {
+		case ".zip", ".tar", ".tgz", ".tar.gz", ".img", ".srcszip", ".apex":
+			return true
+		}
+	}
+
+	return false
+}
+
+// LicenseMetadataProvider is used to propagate license metadata paths between modules.
+var LicenseMetadataProvider = blueprint.NewProvider(&LicenseMetadataInfo{})
+
+// LicenseMetadataInfo stores the license metadata path for a module.
+type LicenseMetadataInfo struct {
+	LicenseMetadataPath   Path
+	LicenseMetadataDepSet *PathsDepSet
+}
+
+// licenseAnnotationsFromTag returns the LicenseAnnotations for a tag (if any) converted into
+// a string, or an empty string if there are none.
+func licenseAnnotationsFromTag(tag blueprint.DependencyTag) string {
+	if annoTag, ok := tag.(LicenseAnnotationsDependencyTag); ok {
+		annos := annoTag.LicenseAnnotations()
+		if len(annos) > 0 {
+			annoStrings := make([]string, len(annos))
+			for i, s := range annos {
+				annoStrings[i] = string(s)
+			}
+			return ":" + strings.Join(annoStrings, ",")
+		}
+	}
+	return ""
+}
+
+// LicenseAnnotationsDependencyTag is implemented by dependency tags in order to provide a
+// list of license dependency annotations.
+type LicenseAnnotationsDependencyTag interface {
+	LicenseAnnotations() []LicenseAnnotation
+}
+
+// LicenseAnnotation is an enum of annotations that can be applied to dependencies for propagating
+// license information.
+type LicenseAnnotation string
+
+const (
+	// LicenseAnnotationSharedDependency should be returned by LicenseAnnotations implementations
+	// of dependency tags when the usage of the dependency is dynamic, for example a shared library
+	// linkage for native modules or as a classpath library for java modules.
+	//
+	// Dependency tags that need to always return LicenseAnnotationSharedDependency
+	// can embed LicenseAnnotationSharedDependencyTag to implement LicenseAnnotations.
+	LicenseAnnotationSharedDependency LicenseAnnotation = "dynamic"
+
+	// LicenseAnnotationToolchain should be returned by LicenseAnnotations implementations of
+	// dependency tags when the dependency is used as a toolchain.
+	//
+	// Dependency tags that need to always return LicenseAnnotationToolchain
+	// can embed LicenseAnnotationToolchainDependencyTag to implement LicenseAnnotations.
+	LicenseAnnotationToolchain LicenseAnnotation = "toolchain"
+)
+
+// LicenseAnnotationSharedDependencyTag can be embedded in a dependency tag to implement
+// LicenseAnnotations that always returns LicenseAnnotationSharedDependency.
+type LicenseAnnotationSharedDependencyTag struct{}
+
+func (LicenseAnnotationSharedDependencyTag) LicenseAnnotations() []LicenseAnnotation {
+	return []LicenseAnnotation{LicenseAnnotationSharedDependency}
+}
+
+// LicenseAnnotationToolchainDependencyTag can be embedded in a dependency tag to implement
+// LicenseAnnotations that always returns LicenseAnnotationToolchain.
+type LicenseAnnotationToolchainDependencyTag struct{}
+
+func (LicenseAnnotationToolchainDependencyTag) LicenseAnnotations() []LicenseAnnotation {
+	return []LicenseAnnotation{LicenseAnnotationToolchain}
+}
diff --git a/android/license_sdk_member.go b/android/license_sdk_member.go
index cd36ed6..b17defe 100644
--- a/android/license_sdk_member.go
+++ b/android/license_sdk_member.go
@@ -31,9 +31,9 @@
 	SdkMemberTypeBase
 }
 
-func (l *licenseSdkMemberType) AddDependencies(mctx BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
+func (l *licenseSdkMemberType) AddDependencies(ctx SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
 	// Add dependencies onto the license module from the sdk module.
-	mctx.AddDependency(mctx.Module(), dependencyTag, names...)
+	ctx.AddDependency(ctx.Module(), dependencyTag, names...)
 }
 
 func (l *licenseSdkMemberType) IsInstance(module Module) bool {
@@ -90,7 +90,10 @@
 	// Populate the properties from the variant.
 	l := variant.(*licenseModule)
 	p.License_kinds = l.properties.License_kinds
-	p.License_text = l.base().commonProperties.Effective_license_text
+	p.License_text = make(Paths, 0, len(l.base().commonProperties.Effective_license_text))
+	for _, np := range l.base().commonProperties.Effective_license_text {
+		p.License_text = append(p.License_text, np.Path)
+	}
 }
 
 func (p *licenseSdkMemberProperties) AddToPropertySet(ctx SdkMemberContext, propertySet BpPropertySet) {
diff --git a/android/license_test.go b/android/license_test.go
index 26b33c3..7222cd7 100644
--- a/android/license_test.go
+++ b/android/license_test.go
@@ -27,7 +27,7 @@
 	{
 		name: "license must not accept licenses property",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license {
 					name: "top_license",
 					visibility: ["//visibility:private"],
@@ -35,13 +35,13 @@
 				}`),
 		},
 		expectedErrors: []string{
-			`top/Blueprints:5:14: unrecognized property "licenses"`,
+			`top/Android.bp:5:14: unrecognized property "licenses"`,
 		},
 	},
 	{
 		name: "private license",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_notice",
 					conditions: ["notice"],
@@ -53,27 +53,27 @@
 					license_kinds: ["top_notice"],
 					visibility: ["//visibility:private"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				rule {
 					name: "arule",
 					licenses: ["top_allowed_as_notice"],
 				}`),
-			"yetmore/Blueprints": []byte(`
+			"yetmore/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_allowed_as_notice"],
 				}`),
 		},
 		expectedErrors: []string{
-			`other/Blueprints:2:5: module "arule": depends on //top:top_allowed_as_notice ` +
+			`other/Android.bp:2:5: module "arule": depends on //top:top_allowed_as_notice ` +
 				`which is not visible to this module`,
-			`yetmore/Blueprints:2:5: module "//yetmore": depends on //top:top_allowed_as_notice ` +
+			`yetmore/Android.bp:2:5: module "//yetmore": depends on //top:top_allowed_as_notice ` +
 				`which is not visible to this module`,
 		},
 	},
 	{
 		name: "must reference license_kind module",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				rule {
 					name: "top_by_exception_only",
 				}
@@ -85,14 +85,14 @@
 				}`),
 		},
 		expectedErrors: []string{
-			`top/Blueprints:6:5: module "top_proprietary": license_kinds property ` +
+			`top/Android.bp:6:5: module "top_proprietary": license_kinds property ` +
 				`"top_by_exception_only" is not a license_kind module`,
 		},
 	},
 	{
 		name: "license_kind module must exist",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license {
 					name: "top_notice_allowed",
 					license_kinds: ["top_notice"],
@@ -100,13 +100,13 @@
 				}`),
 		},
 		expectedErrors: []string{
-			`top/Blueprints:2:5: "top_notice_allowed" depends on undefined module "top_notice"`,
+			`top/Android.bp:2:5: "top_notice_allowed" depends on undefined module "top_notice"`,
 		},
 	},
 	{
 		name: "public license",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_by_exception_only",
 					conditions: ["by_exception_only"],
@@ -118,12 +118,12 @@
 					license_kinds: ["top_by_exception_only"],
 					visibility: ["//visibility:public"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				rule {
 					name: "arule",
 					licenses: ["top_proprietary"],
 				}`),
-			"yetmore/Blueprints": []byte(`
+			"yetmore/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_proprietary"],
 				}`),
@@ -132,7 +132,7 @@
 	{
 		name: "multiple licenses",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_proprietary"],
 				}
@@ -162,12 +162,12 @@
 					name: "myrule",
 					licenses: ["top_allowed_as_notice", "top_proprietary"]
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				rule {
 					name: "arule",
 					licenses: ["top_proprietary"],
 				}`),
-			"yetmore/Blueprints": []byte(`
+			"yetmore/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_proprietary"],
 				}`),
diff --git a/android/licenses.go b/android/licenses.go
index 464ba49..bd14b26 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -48,7 +48,7 @@
 
 	// License modules, i.e. modules depended upon via a licensesTag, must be automatically added to
 	// any sdk/module_exports to which their referencing module is a member.
-	_ SdkMemberTypeDependencyTag = licensesTag
+	_ SdkMemberDependencyTag = licensesTag
 )
 
 // Describes the property provided by a module to reference applicable licenses.
@@ -213,7 +213,7 @@
 				m.base().commonProperties.Effective_package_name = l.properties.Package_name
 			}
 			mergeStringProps(&m.base().commonProperties.Effective_licenses, module.base().commonProperties.Effective_licenses...)
-			mergePathProps(&m.base().commonProperties.Effective_license_text, module.base().commonProperties.Effective_license_text...)
+			mergeNamedPathProps(&m.base().commonProperties.Effective_license_text, module.base().commonProperties.Effective_license_text...)
 			mergeStringProps(&m.base().commonProperties.Effective_license_kinds, module.base().commonProperties.Effective_license_kinds...)
 			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, module.base().commonProperties.Effective_license_conditions...)
 		} else {
@@ -239,10 +239,24 @@
 	*prop = SortedUniqueStrings(*prop)
 }
 
-// Update a property Path array with a distinct union of its values and a list of new values.
-func mergePathProps(prop *Paths, values ...Path) {
+// Update a property NamedPath array with a distinct union of its values and a list of new values.
+func namePathProps(prop *NamedPaths, name *string, values ...Path) {
+	if name == nil {
+		for _, value := range values {
+			*prop = append(*prop, NamedPath{value, ""})
+		}
+	} else {
+		for _, value := range values {
+			*prop = append(*prop, NamedPath{value, *name})
+		}
+	}
+	*prop = SortedUniqueNamedPaths(*prop)
+}
+
+// Update a property NamedPath array with a distinct union of its values and a list of new values.
+func mergeNamedPathProps(prop *NamedPaths, values ...NamedPath) {
 	*prop = append(*prop, values...)
-	*prop = SortedUniquePaths(*prop)
+	*prop = SortedUniqueNamedPaths(*prop)
 }
 
 // Get the licenses property falling back to the package default.
@@ -253,7 +267,7 @@
 
 	primaryProperty := module.base().primaryLicensesProperty
 	if primaryProperty == nil {
-		if ctx.Config().IsEnvTrue("ANDROID_REQUIRE_LICENSES") {
+		if !ctx.Config().IsEnvFalse("ANDROID_REQUIRE_LICENSES") {
 			ctx.ModuleErrorf("module type %q must have an applicable licenses property", ctx.OtherModuleType(module))
 		}
 		return nil
@@ -293,7 +307,7 @@
 	case "*android.soongConfigModuleTypeModule": // creates aliases for modules with licenses
 	case "*android.soongConfigModuleTypeImport": // creates aliases for modules with licenses
 	case "*android.soongConfigStringVariableDummyModule": // used for creating aliases
-	case "*android.SoongConfigBoolVariableDummyModule": // used for creating aliases
+	case "*android.soongConfigBoolVariableDummyModule": // used for creating aliases
 	default:
 		return false
 	}
@@ -308,3 +322,19 @@
 }
 
 var LicenseInfoProvider = blueprint.NewProvider(LicenseInfo{})
+
+func init() {
+	RegisterMakeVarsProvider(pctx, licensesMakeVarsProvider)
+}
+
+func licensesMakeVarsProvider(ctx MakeVarsContext) {
+	ctx.Strict("BUILD_LICENSE_METADATA",
+		ctx.Config().HostToolPath(ctx, "build_license_metadata").String())
+	ctx.Strict("HTMLNOTICE", ctx.Config().HostToolPath(ctx, "htmlnotice").String())
+	ctx.Strict("XMLNOTICE", ctx.Config().HostToolPath(ctx, "xmlnotice").String())
+	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/licenses_test.go b/android/licenses_test.go
index 8503310..8a81e12 100644
--- a/android/licenses_test.go
+++ b/android/licenses_test.go
@@ -20,7 +20,7 @@
 	{
 		name: "invalid module type without licenses property",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_bad_module {
 					name: "libexample",
 				}`),
@@ -30,7 +30,7 @@
 	{
 		name: "license must exist",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					licenses: ["notice"],
@@ -41,7 +41,7 @@
 	{
 		name: "all good",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "notice",
 					conditions: ["shownotice"],
@@ -58,12 +58,12 @@
 					name: "libexample1",
 					licenses: ["top_Apache2"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					licenses: ["top_Apache2"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					licenses: ["top_Apache2"],
@@ -90,9 +90,9 @@
 			"libother":    []string{"shownotice"},
 		},
 		effectiveNotices: map[string][]string{
-			"libexample1": []string{"top/LICENSE", "top/NOTICE"},
-			"libnested":   []string{"top/LICENSE", "top/NOTICE"},
-			"libother":    []string{"top/LICENSE", "top/NOTICE"},
+			"libexample1": []string{"top/LICENSE:topDog", "top/NOTICE:topDog"},
+			"libnested":   []string{"top/LICENSE:topDog", "top/NOTICE:topDog"},
+			"libother":    []string{"top/LICENSE:topDog", "top/NOTICE:topDog"},
 		},
 	},
 
@@ -101,7 +101,7 @@
 		// Check that licenses is the union of the defaults modules.
 		name: "defaults union, basic",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license_kind {
 					name: "top_notice",
 					conditions: ["notice"],
@@ -125,7 +125,7 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				license_kind {
 					name: "nested_notice",
 					conditions: ["notice"],
@@ -140,7 +140,7 @@
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -174,7 +174,7 @@
 	{
 		name: "defaults union, multiple defaults",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				license {
 					name: "top",
 				}
@@ -194,7 +194,7 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				license {
 					name: "top_nested",
 					license_text: ["LICENSE.txt"],
@@ -203,7 +203,7 @@
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				license {
 					name: "other",
 				}
@@ -211,7 +211,7 @@
 					name: "libother",
 					deps: ["libexample"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -251,7 +251,7 @@
 	{
 		name: "defaults_licenses invalid",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "top_defaults",
 					licenses: ["notice"],
@@ -262,7 +262,7 @@
 	{
 		name: "defaults_licenses overrides package default",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["by_exception_only"],
 				}
@@ -298,7 +298,7 @@
 	{
 		name: "package default_applicable_licenses must exist",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["notice"],
 				}`),
@@ -309,7 +309,7 @@
 		// This test relies on the default licenses being legacy_public.
 		name: "package default_applicable_licenses property used when no licenses specified",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_notice"],
 				}
@@ -320,7 +320,7 @@
 				mock_library {
 					name: "libexample",
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -338,7 +338,7 @@
 	{
 		name: "package default_applicable_licenses not inherited to subpackages",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["top_notice"],
 				}
@@ -348,7 +348,7 @@
 				mock_library {
 					name: "libexample",
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				package {
 					default_applicable_licenses: ["outsider"],
 				}
@@ -356,11 +356,11 @@
 				mock_library {
 					name: "libnested",
 				}`),
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				license {
 					name: "outsider",
 				}
@@ -385,7 +385,7 @@
 	{
 		name: "verify that prebuilt dependencies are included",
 		fs: map[string][]byte{
-			"prebuilts/Blueprints": []byte(`
+			"prebuilts/Android.bp": []byte(`
 				license {
 					name: "prebuilt"
 				}
@@ -394,7 +394,7 @@
 					licenses: ["prebuilt"],
 				}`),
 			"top/sources/source_file": nil,
-			"top/sources/Blueprints": []byte(`
+			"top/sources/Android.bp": []byte(`
 				license {
 					name: "top_sources"
 				}
@@ -403,7 +403,7 @@
 					licenses: ["top_sources"],
 				}`),
 			"top/other/source_file": nil,
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				source {
 					name: "other",
 					deps: [":module"],
@@ -419,7 +419,7 @@
 	{
 		name: "verify that prebuilt dependencies are ignored for licenses reasons (preferred)",
 		fs: map[string][]byte{
-			"prebuilts/Blueprints": []byte(`
+			"prebuilts/Android.bp": []byte(`
 				license {
 					name: "prebuilt"
 				}
@@ -429,7 +429,7 @@
 					prefer: true,
 				}`),
 			"top/sources/source_file": nil,
-			"top/sources/Blueprints": []byte(`
+			"top/sources/Android.bp": []byte(`
 				license {
 					name: "top_sources"
 				}
@@ -438,7 +438,7 @@
 					licenses: ["top_sources"],
 				}`),
 			"top/other/source_file": nil,
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				source {
 					name: "other",
 					deps: [":module"],
@@ -779,9 +779,6 @@
 	base := m.base()
 	m.AddProperties(&base.nameProperties, &m.properties)
 
-	base.generalProperties = m.GetProperties()
-	base.customizableProperties = m.GetProperties()
-
 	// The default_visibility property needs to be checked and parsed by the visibility module during
 	// its checking and parsing phases so make it the primary visibility property.
 	setPrimaryVisibilityProperty(m, "visibility", &m.properties.Visibility)
diff --git a/android/makevars.go b/android/makevars.go
index 40c0ccd..a74185a 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -17,6 +17,8 @@
 import (
 	"bytes"
 	"fmt"
+	"path/filepath"
+	"runtime"
 	"sort"
 	"strings"
 
@@ -140,15 +142,19 @@
 
 var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
 
+func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider {
+	return config.Once(singletonMakeVarsProvidersKey, func() interface{} {
+		return &[]makeVarsProvider{}
+	}).(*[]makeVarsProvider)
+}
+
 // registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
 // the list of MakeVarsProviders to run.
 func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
 	// Singletons are registered on the Context and may be different between different Contexts,
 	// for example when running multiple tests.  Store the SingletonMakeVarsProviders in the
 	// Config so they are attached to the Context.
-	singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} {
-		return &[]makeVarsProvider{}
-	}).(*[]makeVarsProvider)
+	singletonMakeVarsProviders := getSingletonMakevarsProviders(config)
 
 	*singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
 		makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
@@ -173,7 +179,9 @@
 	return &makeVarsSingleton{}
 }
 
-type makeVarsSingleton struct{}
+type makeVarsSingleton struct {
+	installsForTesting []byte
+}
 
 type makeVarsProvider struct {
 	pctx PackageContext
@@ -222,6 +230,9 @@
 	lateOutFile := absolutePath(PathForOutput(ctx,
 		"late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
 
+	installsFile := absolutePath(PathForOutput(ctx,
+		"installs"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
+
 	if ctx.Failed() {
 		return
 	}
@@ -229,9 +240,11 @@
 	var vars []makeVarsVariable
 	var dists []dist
 	var phonies []phony
+	var katiInstalls []katiInstall
+	var katiSymlinks []katiInstall
 
 	providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
-	providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
+	providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...)
 
 	for _, provider := range providers {
 		mctx := &makeVarsContext{
@@ -258,6 +271,11 @@
 			phonies = append(phonies, mctx.phonies...)
 			dists = append(dists, mctx.dists...)
 		}
+
+		if m.ExportedToMake() {
+			katiInstalls = append(katiInstalls, m.base().katiInstalls...)
+			katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...)
+		}
 	})
 
 	if ctx.Failed() {
@@ -297,6 +315,12 @@
 		ctx.Errorf(err.Error())
 	}
 
+	installsBytes := s.writeInstalls(katiInstalls, katiSymlinks)
+	if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil {
+		ctx.Errorf(err.Error())
+	}
+
+	s.installsForTesting = installsBytes
 }
 
 func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
@@ -398,6 +422,7 @@
 	fmt.Fprintln(buf)
 
 	for _, dist := range dists {
+		fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " "))
 		fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
 			strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
 	}
@@ -405,6 +430,95 @@
 	return buf.Bytes()
 }
 
+// writeInstalls writes the list of install rules generated by Soong to a makefile.  The rules
+// are exported to Make instead of written directly to the ninja file so that main.mk can add
+// the dependencies from the `required` property that are hard to resolve in Soong.
+func (s *makeVarsSingleton) writeInstalls(installs, symlinks []katiInstall) []byte {
+	buf := &bytes.Buffer{}
+
+	fmt.Fprint(buf, `# Autogenerated file
+
+# Values written by Soong to generate install rules that can be amended by Kati.
+
+
+`)
+
+	preserveSymlinksFlag := "-d"
+	if runtime.GOOS == "darwin" {
+		preserveSymlinksFlag = "-R"
+	}
+
+	for _, install := range installs {
+		// Write a rule for each install request in the form:
+		//  to: from [ deps ] [ | order only deps ]
+		//       cp -f -d $< $@ [ && chmod +x $@ ]
+		fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String())
+		for _, dep := range install.implicitDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		if extraFiles := install.extraFiles; extraFiles != nil {
+			fmt.Fprintf(buf, " %s", extraFiles.zip.String())
+		}
+		if len(install.orderOnlyDeps) > 0 {
+			fmt.Fprintf(buf, " |")
+		}
+		for _, dep := range install.orderOnlyDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		fmt.Fprintln(buf)
+		fmt.Fprintln(buf, "\t@echo \"Install $@\"")
+		fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag)
+		if install.executable {
+			fmt.Fprintf(buf, "\tchmod +x $@\n")
+		}
+		if extraFiles := install.extraFiles; extraFiles != nil {
+			fmt.Fprintf(buf, "\t( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} ) || \\\n", extraFiles.dir.String(), extraFiles.zip.String())
+			fmt.Fprintf(buf, "\t  ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n")
+		}
+		fmt.Fprintln(buf)
+	}
+
+	for _, symlink := range symlinks {
+		fmt.Fprintf(buf, "%s:", symlink.to.String())
+		if symlink.from != nil {
+			// The symlink doesn't need updating when the target is modified, but we sometimes
+			// have a dependency on a symlink to a binary instead of to the binary directly, and
+			// the mtime of the symlink must be updated when the binary is modified, so use a
+			// normal dependency here instead of an order-only dependency.
+			fmt.Fprintf(buf, " %s", symlink.from.String())
+		}
+		for _, dep := range symlink.implicitDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		if len(symlink.orderOnlyDeps) > 0 {
+			fmt.Fprintf(buf, " |")
+		}
+		for _, dep := range symlink.orderOnlyDeps {
+			fmt.Fprintf(buf, " %s", dep.String())
+		}
+		fmt.Fprintln(buf)
+
+		fromStr := ""
+		if symlink.from != nil {
+			rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String())
+			if err != nil {
+				panic(fmt.Errorf("failed to find relative path for symlink from %q to %q: %w",
+					symlink.from.String(), symlink.to.String(), err))
+			}
+			fromStr = rel
+		} else {
+			fromStr = symlink.absFrom
+		}
+
+		fmt.Fprintln(buf, "\t@echo \"Symlink $@\"")
+		fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr)
+		fmt.Fprintln(buf)
+		fmt.Fprintln(buf)
+	}
+
+	return buf.Bytes()
+}
+
 func (c *makeVarsContext) DeviceConfig() DeviceConfig {
 	return DeviceConfig{c.Config().deviceConfig}
 }
diff --git a/android/metrics.go b/android/metrics.go
index b7aee54..9038bde 100644
--- a/android/metrics.go
+++ b/android/metrics.go
@@ -18,7 +18,8 @@
 	"io/ioutil"
 	"runtime"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/google/blueprint/metrics"
+	"google.golang.org/protobuf/proto"
 
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 )
@@ -55,7 +56,7 @@
 	})
 }
 
-func collectMetrics(config Config) *soong_metrics_proto.SoongBuildMetrics {
+func collectMetrics(config Config, eventHandler metrics.EventHandler) *soong_metrics_proto.SoongBuildMetrics {
 	metrics := &soong_metrics_proto.SoongBuildMetrics{}
 
 	soongMetrics := ReadSoongMetrics(config)
@@ -68,11 +69,21 @@
 	metrics.TotalAllocCount = proto.Uint64(memStats.Mallocs)
 	metrics.TotalAllocSize = proto.Uint64(memStats.TotalAlloc)
 
+	for _, event := range eventHandler.CompletedEvents() {
+		perfInfo := soong_metrics_proto.PerfInfo{
+			Description: proto.String(event.Id),
+			Name:        proto.String("soong_build"),
+			StartTime:   proto.Uint64(uint64(event.Start.UnixNano())),
+			RealTime:    proto.Uint64(event.RuntimeNanoseconds()),
+		}
+		metrics.Events = append(metrics.Events, &perfInfo)
+	}
+
 	return metrics
 }
 
-func WriteMetrics(config Config, metricsFile string) error {
-	metrics := collectMetrics(config)
+func WriteMetrics(config Config, eventHandler metrics.EventHandler, metricsFile string) error {
+	metrics := collectMetrics(config, eventHandler)
 
 	buf, err := proto.Marshal(metrics)
 	if err != nil {
diff --git a/android/module.go b/android/module.go
index f745a4a..7285a2f 100644
--- a/android/module.go
+++ b/android/module.go
@@ -15,15 +15,19 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
+	"net/url"
 	"os"
 	"path"
 	"path/filepath"
+	"reflect"
 	"regexp"
+	"sort"
 	"strings"
 	"text/scanner"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -223,6 +227,8 @@
 	// the first DependencyTag.
 	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
 
+	ModuleFromName(name string) (blueprint.Module, bool)
+
 	// VisitDirectDepsBlueprint calls visit for each direct dependency.  If there are multiple
 	// direct dependencies on the same module visit will be called multiple times on that module
 	// and OtherModuleDependencyTag will return a different tag for each.
@@ -233,8 +239,8 @@
 
 	// VisitDirectDeps calls visit for each direct dependency.  If there are multiple
 	// direct dependencies on the same module visit will be called multiple times on that module
-	// and OtherModuleDependencyTag will return a different tag for each.  It skips any
-	// dependencies that are not an android.Module.
+	// and OtherModuleDependencyTag will return a different tag for each.  It raises an error if any of the
+	// dependencies are not an android.Module.
 	//
 	// The Module passed to the visit function should not be retained outside of the visit
 	// function, it may be invalidated by future mutators.
@@ -263,7 +269,7 @@
 	//
 	// The Modules passed to the visit function should not be retained outside of the visit function, they may be
 	// invalidated by future mutators.
-	WalkDeps(visit func(Module, Module) bool)
+	WalkDeps(visit func(child, parent Module) bool)
 
 	// WalkDepsBlueprint calls visit for each transitive dependency, traversing the dependency
 	// tree in top down order.  visit may be called multiple times for the same (child, parent)
@@ -314,6 +320,12 @@
 
 	AddMissingDependencies(missingDeps []string)
 
+	// AddUnconvertedBp2buildDep stores module name of a direct dependency that was not converted via bp2build
+	AddUnconvertedBp2buildDep(dep string)
+
+	// AddMissingBp2buildDep stores the module name of a direct dependency that was not found.
+	AddMissingBp2buildDep(dep string)
+
 	Target() Target
 	TargetPrimary() bool
 
@@ -325,7 +337,6 @@
 	Host() bool
 	Device() bool
 	Darwin() bool
-	Fuchsia() bool
 	Windows() bool
 	Debug() bool
 	PrimaryArch() bool
@@ -344,8 +355,18 @@
 	// Deprecated: use ModuleContext.Build instead.
 	ModuleBuild(pctx PackageContext, params ModuleBuildParams)
 
+	// Returns a list of paths expanded from globs and modules referenced using ":module" syntax.  The property must
+	// be tagged with `android:"path" to support automatic source module dependency resolution.
+	//
+	// Deprecated: use PathsForModuleSrc or PathsForModuleSrcExcludes instead.
 	ExpandSources(srcFiles, excludes []string) Paths
+
+	// Returns a single path expanded from globs and modules referenced using ":module" syntax.  The property must
+	// be tagged with `android:"path" to support automatic source module dependency resolution.
+	//
+	// Deprecated: use PathForModuleSrc instead.
 	ExpandSource(srcFile, prop string) Path
+
 	ExpandOptionalSource(srcFile *string, prop string) OptionalPath
 
 	// InstallExecutable creates a rule to copy srcPath to name in the installPath directory,
@@ -366,6 +387,16 @@
 	// for which IsInstallDepNeeded returns true.
 	InstallFile(installPath InstallPath, name string, srcPath Path, deps ...Path) InstallPath
 
+	// InstallFileWithExtraFilesZip creates a rule to copy srcPath to name in the installPath
+	// directory, and also unzip a zip file containing extra files to install into the same
+	// directory.
+	//
+	// The installed file will be returned by FilesToInstall(), and the PackagingSpec for the
+	// installed file will be returned by PackagingSpecs() on this module or by
+	// TransitivePackagingSpecs() on modules that depend on this module through dependency tags
+	// for which IsInstallDepNeeded returns true.
+	InstallFileWithExtraFilesZip(installPath InstallPath, name string, srcPath Path, extraZip Path, deps ...Path) InstallPath
+
 	// InstallSymlink creates a rule to create a symlink from src srcPath to name in the installPath
 	// directory.
 	//
@@ -403,7 +434,7 @@
 	InstallInDebugRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
-	InstallBypassMake() bool
+	InstallInVendor() bool
 	InstallForceOS() (*OsType, *ArchType)
 
 	RequiredModuleNames() []string
@@ -425,6 +456,10 @@
 	// GetMissingDependencies returns the list of dependencies that were passed to AddDependencies or related methods,
 	// but do not exist.
 	GetMissingDependencies() []string
+
+	// LicenseMetadataFile returns the path where the license metadata for this module will be
+	// generated.
+	LicenseMetadataFile() Path
 }
 
 type Module interface {
@@ -452,6 +487,14 @@
 	Enabled() bool
 	Target() Target
 	MultiTargets() []Target
+
+	// ImageVariation returns the image variation of this module.
+	//
+	// The returned structure has its Mutator field set to "image" and its Variation field set to the
+	// image variation, e.g. recovery, ramdisk, etc.. The Variation field is "" for host modules and
+	// device modules that have no image variation.
+	ImageVariation() blueprint.Variation
+
 	Owner() string
 	InstallInData() bool
 	InstallInTestcases() bool
@@ -461,7 +504,7 @@
 	InstallInDebugRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
-	InstallBypassMake() bool
+	InstallInVendor() bool
 	InstallForceOS() (*OsType, *ArchType)
 	HideFromMake()
 	IsHideFromMake() bool
@@ -473,10 +516,18 @@
 	InitRc() Paths
 	VintfFragments() Paths
 	NoticeFiles() Paths
+	EffectiveLicenseFiles() Paths
 
 	AddProperties(props ...interface{})
 	GetProperties() []interface{}
 
+	// IsConvertedByBp2build returns whether this module was converted via bp2build
+	IsConvertedByBp2build() bool
+	// Bp2buildTargets returns the target(s) generated for Bazel via bp2build for this module
+	Bp2buildTargets() []bp2buildInfo
+	GetUnconvertedBp2buildDeps() []string
+	GetMissingBp2buildDeps() []string
+
 	BuildParamsForTests() []BuildParams
 	RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams
 	VariablesForTests() map[string]string
@@ -502,62 +553,6 @@
 	TransitivePackagingSpecs() []PackagingSpec
 }
 
-// BazelTargetModule is a lightweight wrapper interface around Module for
-// bp2build conversion purposes.
-//
-// In bp2build's bootstrap.Main execution, Soong runs an alternate pipeline of
-// mutators that creates BazelTargetModules from regular Module objects,
-// performing the mapping from Soong properties to Bazel rule attributes in the
-// process. This process may optionally create additional BazelTargetModules,
-// resulting in a 1:many mapping.
-//
-// bp2build.Codegen is then responsible for visiting all modules in the graph,
-// filtering for BazelTargetModules, and code-generating BUILD targets from
-// them.
-type BazelTargetModule interface {
-	Module
-
-	bazelTargetModuleProperties() *bazel.BazelTargetModuleProperties
-	SetBazelTargetModuleProperties(props bazel.BazelTargetModuleProperties)
-
-	RuleClass() string
-	BzlLoadLocation() string
-}
-
-// InitBazelTargetModule is a wrapper function that decorates BazelTargetModule
-// with property structs containing metadata for bp2build conversion.
-func InitBazelTargetModule(module BazelTargetModule) {
-	module.AddProperties(module.bazelTargetModuleProperties())
-	InitAndroidModule(module)
-}
-
-// BazelTargetModuleBase contains the property structs with metadata for
-// bp2build conversion.
-type BazelTargetModuleBase struct {
-	ModuleBase
-	Properties bazel.BazelTargetModuleProperties
-}
-
-// bazelTargetModuleProperties getter.
-func (btmb *BazelTargetModuleBase) bazelTargetModuleProperties() *bazel.BazelTargetModuleProperties {
-	return &btmb.Properties
-}
-
-// SetBazelTargetModuleProperties setter for BazelTargetModuleProperties
-func (btmb *BazelTargetModuleBase) SetBazelTargetModuleProperties(props bazel.BazelTargetModuleProperties) {
-	btmb.Properties = props
-}
-
-// RuleClass returns the rule class for this Bazel target
-func (b *BazelTargetModuleBase) RuleClass() string {
-	return b.bazelTargetModuleProperties().Rule_class
-}
-
-// BzlLoadLocation returns the rule class for this Bazel target
-func (b *BazelTargetModuleBase) BzlLoadLocation() string {
-	return b.bazelTargetModuleProperties().Bzl_load_location
-}
-
 // Qualified id for a module
 type qualifiedModuleName struct {
 	// The package (i.e. directory) in which the module is defined, without trailing /
@@ -618,6 +613,12 @@
 	// A suffix to add to the artifact file name (before any extension).
 	Suffix *string `android:"arch_variant"`
 
+	// If true, then the artifact file will be appended with _<product name>. For
+	// example, if the product is coral and the module is an android_app module
+	// of name foo, then the artifact would be foo_coral.apk. If false, there is
+	// no change to the artifact file name.
+	Append_artifact_with_product *bool `android:"arch_variant"`
+
 	// A string tag to select the OutputFiles associated with the tag.
 	//
 	// If no tag is specified then it will select the default dist paths provided
@@ -627,6 +628,53 @@
 	Tag *string `android:"arch_variant"`
 }
 
+// NamedPath associates a path with a name. e.g. a license text path with a package name
+type NamedPath struct {
+	Path Path
+	Name string
+}
+
+// String returns an escaped string representing the `NamedPath`.
+func (p NamedPath) String() string {
+	if len(p.Name) > 0 {
+		return p.Path.String() + ":" + url.QueryEscape(p.Name)
+	}
+	return p.Path.String()
+}
+
+// NamedPaths describes a list of paths each associated with a name.
+type NamedPaths []NamedPath
+
+// Strings returns a list of escaped strings representing each `NamedPath` in the list.
+func (l NamedPaths) Strings() []string {
+	result := make([]string, 0, len(l))
+	for _, p := range l {
+		result = append(result, p.String())
+	}
+	return result
+}
+
+// SortedUniqueNamedPaths modifies `l` in place to return the sorted unique subset.
+func SortedUniqueNamedPaths(l NamedPaths) NamedPaths {
+	if len(l) == 0 {
+		return l
+	}
+	sort.Slice(l, func(i, j int) bool {
+		return l[i].String() < l[j].String()
+	})
+	k := 0
+	for i := 1; i < len(l); i++ {
+		if l[i].String() == l[k].String() {
+			continue
+		}
+		k++
+		if k < i {
+			l[k] = l[i]
+		}
+	}
+	return l[:k+1]
+}
+
 type nameProperties struct {
 	// The name of the module.  Must be unique across all modules.
 	Name *string
@@ -695,7 +743,7 @@
 	// Override of module name when reporting licenses
 	Effective_package_name *string `blueprint:"mutated"`
 	// Notice files
-	Effective_license_text Paths `blueprint:"mutated"`
+	Effective_license_text NamedPaths `blueprint:"mutated"`
 	// License names
 	Effective_license_kinds []string `blueprint:"mutated"`
 	// License conditions
@@ -864,6 +912,35 @@
 	// for example "" for core or "recovery" for recovery.  It will often be set to one of the
 	// constants in image.go, but can also be set to a custom value by individual module types.
 	ImageVariation string `blueprint:"mutated"`
+
+	// Information about _all_ bp2build targets generated by this module. Multiple targets are
+	// supported as Soong handles some things within a single target that we may choose to split into
+	// multiple targets, e.g. renderscript, protos, yacc within a cc module.
+	Bp2buildInfo []bp2buildInfo `blueprint:"mutated"`
+
+	// UnconvertedBp2buildDep stores the module names of direct dependency that were not converted to
+	// Bazel
+	UnconvertedBp2buildDeps []string `blueprint:"mutated"`
+
+	// MissingBp2buildDep stores the module names of direct dependency that were not found
+	MissingBp2buildDeps []string `blueprint:"mutated"`
+}
+
+// CommonAttributes represents the common Bazel attributes from which properties
+// in `commonProperties` are translated/mapped; such properties are annotated in
+// a list their corresponding attribute. It is embedded within `bp2buildInfo`.
+type CommonAttributes struct {
+	// Soong nameProperties -> Bazel name
+	Name string
+	// Data mapped from: Required
+	Data bazel.LabelListAttribute
+}
+
+// constraintAttributes represents Bazel attributes pertaining to build constraints,
+// which make restrict building a Bazel target for some set of platforms.
+type constraintAttributes struct {
+	// Constraint values this target can be built for.
+	Target_compatible_with bazel.LabelListAttribute
 }
 
 type distProperties struct {
@@ -962,10 +1039,13 @@
 	// Device is built by default. Host and HostCross are not supported.
 	DeviceSupported = deviceSupported | deviceDefault
 
-	// Device is built by default. Host and HostCross are supported.
+	// By default, _only_ device variant is built. Device variant can be disabled with `device_supported: false`
+	// Host and HostCross are disabled by default and can be enabled with `host_supported: true`
 	HostAndDeviceSupported = hostSupported | hostCrossSupported | deviceSupported | deviceDefault
 
 	// Host, HostCross, and Device are built by default.
+	// Building Device can be disabled with `device_supported: false`
+	// Building Host and HostCross can be disabled with `host_supported: false`
 	HostAndDeviceDefault = hostSupported | hostCrossSupported | hostDefault |
 		deviceSupported | deviceDefault
 
@@ -1019,9 +1099,6 @@
 
 	initProductVariableModule(m)
 
-	base.generalProperties = m.GetProperties()
-	base.customizableProperties = m.GetProperties()
-
 	// The default_visibility property needs to be checked and parsed by the visibility module during
 	// its checking and parsing phases so make it the primary visibility property.
 	setPrimaryVisibilityProperty(m, "visibility", &base.commonProperties.Visibility)
@@ -1083,6 +1160,122 @@
 	m.base().commonProperties.CreateCommonOSVariant = true
 }
 
+func (attrs *CommonAttributes) fillCommonBp2BuildModuleAttrs(ctx *topDownMutatorContext,
+	enabledPropertyOverrides bazel.BoolAttribute) constraintAttributes {
+	// Assert passed-in attributes include Name
+	name := attrs.Name
+	if len(name) == 0 {
+		ctx.ModuleErrorf("CommonAttributes in fillCommonBp2BuildModuleAttrs expects a `.Name`!")
+	}
+
+	mod := ctx.Module().base()
+	props := &mod.commonProperties
+
+	depsToLabelList := func(deps []string) bazel.LabelListAttribute {
+		return bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, deps))
+	}
+
+	data := &attrs.Data
+
+	required := depsToLabelList(props.Required)
+	archVariantProps := mod.GetArchVariantProperties(ctx, &commonProperties{})
+
+	var enabledProperty bazel.BoolAttribute
+	if props.Enabled != nil {
+		enabledProperty.Value = props.Enabled
+	}
+
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*commonProperties); ok {
+				required.SetSelectValue(axis, config, depsToLabelList(archProps.Required).Value)
+				if archProps.Enabled != nil {
+					enabledProperty.SetSelectValue(axis, config, archProps.Enabled)
+				}
+			}
+		}
+	}
+
+	if enabledPropertyOverrides.Value != nil {
+		enabledProperty.Value = enabledPropertyOverrides.Value
+	}
+	for _, axis := range enabledPropertyOverrides.SortedConfigurationAxes() {
+		configToBools := enabledPropertyOverrides.ConfigurableValues[axis]
+		for cfg, val := range configToBools {
+			enabledProperty.SetSelectValue(axis, cfg, &val)
+		}
+	}
+
+	productConfigEnabledLabels := []bazel.Label{}
+	if !proptools.BoolDefault(enabledProperty.Value, true) {
+		// If the module is not enabled by default, then we can check if a
+		// product variable enables it
+		productConfigEnabledLabels = productVariableConfigEnableLabels(ctx)
+
+		if len(productConfigEnabledLabels) > 0 {
+			// In this case, an existing product variable configuration overrides any
+			// module-level `enable: false` definition
+			newValue := true
+			enabledProperty.Value = &newValue
+		}
+	}
+
+	productConfigEnabledAttribute := bazel.MakeLabelListAttribute(bazel.LabelList{
+		productConfigEnabledLabels, nil,
+	})
+
+	moduleSupportsDevice := mod.commonProperties.HostOrDeviceSupported&deviceSupported == deviceSupported
+	if mod.commonProperties.HostOrDeviceSupported != NeitherHostNorDeviceSupported && !moduleSupportsDevice {
+		enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, Android.Name, proptools.BoolPtr(false))
+	}
+
+	platformEnabledAttribute, err := enabledProperty.ToLabelListAttribute(
+		bazel.LabelList{[]bazel.Label{bazel.Label{Label: "@platforms//:incompatible"}}, nil},
+		bazel.LabelList{[]bazel.Label{}, nil})
+	if err != nil {
+		ctx.ModuleErrorf("Error processing platform enabled attribute: %s", err)
+	}
+
+	data.Append(required)
+
+	constraints := constraintAttributes{}
+	moduleEnableConstraints := bazel.LabelListAttribute{}
+	moduleEnableConstraints.Append(platformEnabledAttribute)
+	moduleEnableConstraints.Append(productConfigEnabledAttribute)
+	constraints.Target_compatible_with = moduleEnableConstraints
+
+	return constraints
+}
+
+// Check product variables for `enabled: true` flag override.
+// Returns a list of the constraint_value targets who enable this override.
+func productVariableConfigEnableLabels(ctx *topDownMutatorContext) []bazel.Label {
+	productVariableProps := ProductVariableProperties(ctx)
+	productConfigEnablingTargets := []bazel.Label{}
+	const propName = "Enabled"
+	if productConfigProps, exists := productVariableProps[propName]; exists {
+		for productConfigProp, prop := range productConfigProps {
+			flag, ok := prop.(*bool)
+			if !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
+			}
+
+			if *flag {
+				axis := productConfigProp.ConfigurationAxis()
+				targetLabel := axis.SelectKey(productConfigProp.SelectKey())
+				productConfigEnablingTargets = append(productConfigEnablingTargets, bazel.Label{
+					Label: targetLabel,
+				})
+			} else {
+				// TODO(b/210546943): handle negative case where `enabled: false`
+				ctx.ModuleErrorf("`enabled: false` is not currently supported for configuration variables. See b/210546943", proptools.PropertyNameForField(propName))
+			}
+		}
+	}
+
+	return productConfigEnablingTargets
+}
+
 // A ModuleBase object contains the properties that are common to all Android
 // modules.  It should be included as an anonymous field in every module
 // struct definition.  InitAndroidModule should then be called from the module's
@@ -1111,7 +1304,7 @@
 //         }
 //     }
 //
-//     func NewMyModule() android.Module) {
+//     func NewMyModule() android.Module {
 //         m := &myModule{}
 //         m.AddProperties(&m.properties)
 //         android.InitAndroidModule(m)
@@ -1135,17 +1328,14 @@
 	distProperties          distProperties
 	variableProperties      interface{}
 	hostAndDeviceProperties hostAndDeviceProperties
-	generalProperties       []interface{}
 
-	// Arch specific versions of structs in generalProperties. The outer index
-	// has the same order as generalProperties as initialized in
-	// InitAndroidArchModule, and the inner index chooses the props specific to
-	// the architecture. The interface{} value is an archPropRoot that is
-	// filled with arch specific values by the arch mutator.
+	// Arch specific versions of structs in GetProperties() prior to
+	// initialization in InitAndroidArchModule, lets call it `generalProperties`.
+	// The outer index has the same order as generalProperties and the inner index
+	// chooses the props specific to the architecture. The interface{} value is an
+	// archPropRoot that is filled with arch specific values by the arch mutator.
 	archProperties [][]interface{}
 
-	customizableProperties []interface{}
-
 	// Properties specific to the Blueprint to BUILD migration.
 	bazelTargetModuleProperties bazel.BazelTargetModuleProperties
 
@@ -1166,7 +1356,10 @@
 	packagingSpecs       []PackagingSpec
 	packagingSpecsDepSet *packagingSpecsDepSet
 	noticeFiles          Paths
-	phonies              map[string]Paths
+	// katiInstalls tracks the install rules that were created by Soong but are being exported
+	// to Make to convert to ninja rules so that Make can add additional dependencies.
+	katiInstalls katiInstalls
+	katiSymlinks katiInstalls
 
 	// The files to copy to the dist as explicitly specified in the .bp file.
 	distFiles TaggedDistFiles
@@ -1188,12 +1381,198 @@
 
 	initRcPaths         Paths
 	vintfFragmentsPaths Paths
+
+	// set of dependency module:location mappings used to populate the license metadata for
+	// apex containers.
+	licenseInstallMap []string
+
+	// The path to the generated license metadata file for the module.
+	licenseMetadataFile WritablePath
+}
+
+// A struct containing all relevant information about a Bazel target converted via bp2build.
+type bp2buildInfo struct {
+	Dir             string
+	BazelProps      bazel.BazelTargetModuleProperties
+	CommonAttrs     CommonAttributes
+	ConstraintAttrs constraintAttributes
+	Attrs           interface{}
+}
+
+// TargetName returns the Bazel target name of a bp2build converted target.
+func (b bp2buildInfo) TargetName() string {
+	return b.CommonAttrs.Name
+}
+
+// TargetPackage returns the Bazel package of a bp2build converted target.
+func (b bp2buildInfo) TargetPackage() string {
+	return b.Dir
+}
+
+// BazelRuleClass returns the Bazel rule class of a bp2build converted target.
+func (b bp2buildInfo) BazelRuleClass() string {
+	return b.BazelProps.Rule_class
+}
+
+// BazelRuleLoadLocation returns the location of the  Bazel rule of a bp2build converted target.
+// This may be empty as native Bazel rules do not need to be loaded.
+func (b bp2buildInfo) BazelRuleLoadLocation() string {
+	return b.BazelProps.Bzl_load_location
+}
+
+// BazelAttributes returns the Bazel attributes of a bp2build converted target.
+func (b bp2buildInfo) BazelAttributes() []interface{} {
+	return []interface{}{&b.CommonAttrs, &b.ConstraintAttrs, b.Attrs}
+}
+
+func (m *ModuleBase) addBp2buildInfo(info bp2buildInfo) {
+	m.commonProperties.Bp2buildInfo = append(m.commonProperties.Bp2buildInfo, info)
+}
+
+// IsConvertedByBp2build returns whether this module was converted via bp2build.
+func (m *ModuleBase) IsConvertedByBp2build() bool {
+	return len(m.commonProperties.Bp2buildInfo) > 0
+}
+
+// Bp2buildTargets returns the Bazel targets bp2build generated for this module.
+func (m *ModuleBase) Bp2buildTargets() []bp2buildInfo {
+	return m.commonProperties.Bp2buildInfo
+}
+
+// AddUnconvertedBp2buildDep stores module name of a dependency that was not converted to Bazel.
+func (b *baseModuleContext) AddUnconvertedBp2buildDep(dep string) {
+	unconvertedDeps := &b.Module().base().commonProperties.UnconvertedBp2buildDeps
+	*unconvertedDeps = append(*unconvertedDeps, dep)
+}
+
+// AddMissingBp2buildDep stores module name of a dependency that was not found in a Android.bp file.
+func (b *baseModuleContext) AddMissingBp2buildDep(dep string) {
+	missingDeps := &b.Module().base().commonProperties.MissingBp2buildDeps
+	*missingDeps = append(*missingDeps, dep)
+}
+
+// GetUnconvertedBp2buildDeps returns the list of module names of this module's direct dependencies that
+// were not converted to Bazel.
+func (m *ModuleBase) GetUnconvertedBp2buildDeps() []string {
+	return FirstUniqueStrings(m.commonProperties.UnconvertedBp2buildDeps)
+}
+
+// GetMissingBp2buildDeps eturns the list of module names that were not found in Android.bp files.
+func (m *ModuleBase) GetMissingBp2buildDeps() []string {
+	return FirstUniqueStrings(m.commonProperties.MissingBp2buildDeps)
+}
+
+func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
+	(*d)["Android"] = map[string]interface{}{
+		// Properties set in Blueprint or in blueprint of a defaults modules
+		"SetProperties": m.propertiesWithValues(),
+	}
+}
+
+type propInfo struct {
+	Name   string
+	Type   string
+	Value  string
+	Values []string
+}
+
+func (m *ModuleBase) propertiesWithValues() []propInfo {
+	var info []propInfo
+	props := m.GetProperties()
+
+	var propsWithValues func(name string, v reflect.Value)
+	propsWithValues = func(name string, v reflect.Value) {
+		kind := v.Kind()
+		switch kind {
+		case reflect.Ptr, reflect.Interface:
+			if v.IsNil() {
+				return
+			}
+			propsWithValues(name, v.Elem())
+		case reflect.Struct:
+			if v.IsZero() {
+				return
+			}
+			for i := 0; i < v.NumField(); i++ {
+				namePrefix := name
+				sTyp := v.Type().Field(i)
+				if proptools.ShouldSkipProperty(sTyp) {
+					continue
+				}
+				if name != "" && !strings.HasSuffix(namePrefix, ".") {
+					namePrefix += "."
+				}
+				if !proptools.IsEmbedded(sTyp) {
+					namePrefix += sTyp.Name
+				}
+				sVal := v.Field(i)
+				propsWithValues(namePrefix, sVal)
+			}
+		case reflect.Array, reflect.Slice:
+			if v.IsNil() {
+				return
+			}
+			elKind := v.Type().Elem().Kind()
+			info = append(info, propInfo{Name: name, Type: elKind.String() + " " + kind.String(), Values: sliceReflectionValue(v)})
+		default:
+			info = append(info, propInfo{Name: name, Type: kind.String(), Value: reflectionValue(v)})
+		}
+	}
+
+	for _, p := range props {
+		propsWithValues("", reflect.ValueOf(p).Elem())
+	}
+	sort.Slice(info, func(i, j int) bool {
+		return info[i].Name < info[j].Name
+	})
+	return info
+}
+
+func reflectionValue(value reflect.Value) string {
+	switch value.Kind() {
+	case reflect.Bool:
+		return fmt.Sprintf("%t", value.Bool())
+	case reflect.Int64:
+		return fmt.Sprintf("%d", value.Int())
+	case reflect.String:
+		return fmt.Sprintf("%s", value.String())
+	case reflect.Struct:
+		if value.IsZero() {
+			return "{}"
+		}
+		length := value.NumField()
+		vals := make([]string, length, length)
+		for i := 0; i < length; i++ {
+			sTyp := value.Type().Field(i)
+			if proptools.ShouldSkipProperty(sTyp) {
+				continue
+			}
+			name := sTyp.Name
+			vals[i] = fmt.Sprintf("%s: %s", name, reflectionValue(value.Field(i)))
+		}
+		return fmt.Sprintf("%s{%s}", value.Type(), strings.Join(vals, ", "))
+	case reflect.Array, reflect.Slice:
+		vals := sliceReflectionValue(value)
+		return fmt.Sprintf("[%s]", strings.Join(vals, ", "))
+	}
+	return ""
+}
+
+func sliceReflectionValue(value reflect.Value) []string {
+	length := value.Len()
+	vals := make([]string, length, length)
+	for i := 0; i < length; i++ {
+		vals[i] = reflectionValue(value.Index(i))
+	}
+	return vals
 }
 
 func (m *ModuleBase) ComponentDepsMutator(BottomUpMutatorContext) {}
 
 func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {}
 
+// AddProperties "registers" the provided props
+// each value in props MUST be a pointer to a struct
 func (m *ModuleBase) AddProperties(props ...interface{}) {
 	m.registerProps = append(m.registerProps, props...)
 }
@@ -1203,7 +1582,30 @@
 }
 
 func (m *ModuleBase) BuildParamsForTests() []BuildParams {
-	return m.buildParams
+	// Expand the references to module variables like $flags[0-9]*,
+	// so we do not need to change many existing unit tests.
+	// This looks like undoing the shareFlags optimization in cc's
+	// transformSourceToObj, and should only affects unit tests.
+	vars := m.VariablesForTests()
+	buildParams := append([]BuildParams(nil), m.buildParams...)
+	for i, _ := range buildParams {
+		newArgs := make(map[string]string)
+		for k, v := range buildParams[i].Args {
+			newArgs[k] = v
+			// Replaces both ${flags1} and $flags1 syntax.
+			if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
+				if value, found := vars[v[2:len(v)-1]]; found {
+					newArgs[k] = value
+				}
+			} else if strings.HasPrefix(v, "$") {
+				if value, found := vars[v[1:]]; found {
+					newArgs[k] = value
+				}
+			}
+		}
+		buildParams[i].Args = newArgs
+	}
+	return buildParams
 }
 
 func (m *ModuleBase) RuleParamsForTests() map[blueprint.Rule]blueprint.RuleParams {
@@ -1501,13 +1903,21 @@
 	return m.commonProperties.NamespaceExportedToMake
 }
 
+func (m *ModuleBase) EffectiveLicenseFiles() Paths {
+	result := make(Paths, 0, len(m.commonProperties.Effective_license_text))
+	for _, p := range m.commonProperties.Effective_license_text {
+		result = append(result, p.Path)
+	}
+	return result
+}
+
 // computeInstallDeps finds the installed paths of all dependencies that have a dependency
 // tag that is annotated as needing installation via the IsInstallDepNeeded method.
 func (m *ModuleBase) computeInstallDeps(ctx ModuleContext) ([]*installPathsDepSet, []*packagingSpecsDepSet) {
 	var installDeps []*installPathsDepSet
 	var packagingSpecs []*packagingSpecsDepSet
 	ctx.VisitDirectDeps(func(dep Module) {
-		if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(dep)) && !dep.IsHideFromMake() {
+		if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(dep)) && !dep.IsHideFromMake() && !dep.IsSkipInstall() {
 			installDeps = append(installDeps, dep.base().installFilesDepSet)
 			packagingSpecs = append(packagingSpecs, dep.base().packagingSpecsDepSet)
 		}
@@ -1560,11 +1970,11 @@
 	return Bool(m.commonProperties.Recovery)
 }
 
-func (m *ModuleBase) InstallInRoot() bool {
-	return false
+func (m *ModuleBase) InstallInVendor() bool {
+	return Bool(m.commonProperties.Vendor)
 }
 
-func (m *ModuleBase) InstallBypassMake() bool {
+func (m *ModuleBase) InstallInRoot() bool {
 	return false
 }
 
@@ -1637,13 +2047,29 @@
 	return append(Paths{}, m.vintfFragmentsPaths...)
 }
 
+func (m *ModuleBase) CompileMultilib() *string {
+	return m.base().commonProperties.Compile_multilib
+}
+
+// SetLicenseInstallMap stores the set of dependency module:location mappings for files in an
+// apex container for use when generation the license metadata file.
+func (m *ModuleBase) SetLicenseInstallMap(installMap []string) {
+	m.licenseInstallMap = append(m.licenseInstallMap, installMap...)
+}
+
 func (m *ModuleBase) generateModuleTarget(ctx ModuleContext) {
 	var allInstalledFiles InstallPaths
 	var allCheckbuildFiles Paths
 	ctx.VisitAllModuleVariants(func(module Module) {
 		a := module.base()
 		allInstalledFiles = append(allInstalledFiles, a.installFiles...)
-		allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...)
+		// A module's -checkbuild phony targets should
+		// not be created if the module is not exported to make.
+		// Those could depend on the build target and fail to compile
+		// for the current build target.
+		if !ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(a) {
+			allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...)
+		}
 	})
 
 	var deps Paths
@@ -1765,6 +2191,8 @@
 		variables:         make(map[string]string),
 	}
 
+	m.licenseMetadataFile = PathForModuleOut(ctx, "meta_lic")
+
 	dependencyInstallFiles, dependencyPackagingSpecs := m.computeInstallDeps(ctx)
 	// set m.installFilesDepSet to only the transitive dependencies to be used as the dependencies
 	// of installed files of this module.  It will be replaced by a depset including the installed
@@ -1876,9 +2304,8 @@
 		m.installFiles = append(m.installFiles, ctx.installFiles...)
 		m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
 		m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
-		for k, v := range ctx.phonies {
-			m.phonies[k] = append(m.phonies[k], v...)
-		}
+		m.katiInstalls = append(m.katiInstalls, ctx.katiInstalls...)
+		m.katiSymlinks = append(m.katiSymlinks, ctx.katiSymlinks...)
 	} else if ctx.Config().AllowMissingDependencies() {
 		// If the module is not enabled it will not create any build rules, nothing will call
 		// ctx.GetMissingDependencies(), and blueprint will consider the missing dependencies to be unhandled
@@ -1897,6 +2324,8 @@
 	m.installFilesDepSet = newInstallPathsDepSet(m.installFiles, dependencyInstallFiles)
 	m.packagingSpecsDepSet = newPackagingSpecsDepSet(m.packagingSpecs, dependencyPackagingSpecs)
 
+	buildLicenseMetadata(ctx, m.licenseMetadataFile)
+
 	m.buildParams = ctx.buildParams
 	m.ruleParams = ctx.ruleParams
 	m.variables = ctx.variables
@@ -1943,18 +2372,18 @@
 	return GlobFiles(e, globPattern, excludes)
 }
 
-func (b *earlyModuleContext) IsSymlink(path Path) bool {
-	fileInfo, err := b.config.fs.Lstat(path.String())
+func (e *earlyModuleContext) IsSymlink(path Path) bool {
+	fileInfo, err := e.config.fs.Lstat(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
 	}
 	return fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
 }
 
-func (b *earlyModuleContext) Readlink(path Path) string {
-	dest, err := b.config.fs.Readlink(path.String())
+func (e *earlyModuleContext) Readlink(path Path) string {
+	dest, err := e.config.fs.Readlink(path.String())
 	if err != nil {
-		b.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
+		e.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
 	}
 	return dest
 }
@@ -2013,8 +2442,13 @@
 	tagPath  []blueprint.DependencyTag
 
 	strictVisitDeps bool // If true, enforce that all dependencies are enabled
+
+	bazelConversionMode bool
 }
 
+func (b *baseModuleContext) BazelConversionMode() bool {
+	return b.bazelConversionMode
+}
 func (b *baseModuleContext) OtherModuleName(m blueprint.Module) string {
 	return b.bp.OtherModuleName(m)
 }
@@ -2071,12 +2505,58 @@
 	module          Module
 	phonies         map[string]Paths
 
+	katiInstalls []katiInstall
+	katiSymlinks []katiInstall
+
 	// For tests
 	buildParams []BuildParams
 	ruleParams  map[blueprint.Rule]blueprint.RuleParams
 	variables   map[string]string
 }
 
+// katiInstall stores a request from Soong to Make to create an install rule.
+type katiInstall struct {
+	from          Path
+	to            InstallPath
+	implicitDeps  Paths
+	orderOnlyDeps Paths
+	executable    bool
+	extraFiles    *extraFilesZip
+
+	absFrom string
+}
+
+type extraFilesZip struct {
+	zip Path
+	dir InstallPath
+}
+
+type katiInstalls []katiInstall
+
+// BuiltInstalled returns the katiInstalls in the form used by $(call copy-many-files) in Make, a
+// space separated list of from:to tuples.
+func (installs katiInstalls) BuiltInstalled() string {
+	sb := strings.Builder{}
+	for i, install := range installs {
+		if i != 0 {
+			sb.WriteRune(' ')
+		}
+		sb.WriteString(install.from.String())
+		sb.WriteRune(':')
+		sb.WriteString(install.to.String())
+	}
+	return sb.String()
+}
+
+// InstallPaths returns the install path of each entry.
+func (installs katiInstalls) InstallPaths() InstallPaths {
+	paths := make(InstallPaths, 0, len(installs))
+	for _, install := range installs {
+		paths = append(paths, install.to)
+	}
+	return paths
+}
+
 func (m *moduleContext) ninjaError(params BuildParams, err error) (PackageContext, BuildParams) {
 	return pctx, BuildParams{
 		Rule:            ErrorRule,
@@ -2257,7 +2737,7 @@
 	}
 
 	if aModule == nil {
-		b.ModuleErrorf("module %q not an android module", b.OtherModuleName(module))
+		b.ModuleErrorf("module %q (%#v) not an android module", b.OtherModuleName(module), tag)
 		return nil
 	}
 
@@ -2354,6 +2834,17 @@
 	return b.getDirectDepFirstTag(name)
 }
 
+func (b *baseModuleContext) ModuleFromName(name string) (blueprint.Module, bool) {
+	if !b.BazelConversionMode() {
+		panic("cannot call ModuleFromName if not in bazel conversion mode")
+	}
+	if moduleName, _ := SrcIsModuleWithTag(name); moduleName != "" {
+		return b.bp.ModuleFromName(moduleName)
+	} else {
+		return b.bp.ModuleFromName(name)
+	}
+}
+
 func (b *baseModuleContext) VisitDirectDepsBlueprint(visit func(blueprint.Module)) {
 	b.bp.VisitDirectDeps(visit)
 }
@@ -2368,8 +2859,8 @@
 
 func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) {
 	b.bp.VisitDirectDeps(func(module blueprint.Module) {
-		if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
-			if b.bp.OtherModuleDependencyTag(aModule) == tag {
+		if b.bp.OtherModuleDependencyTag(module) == tag {
+			if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 				visit(aModule)
 			}
 		}
@@ -2544,10 +3035,6 @@
 	return b.os == Darwin
 }
 
-func (b *baseModuleContext) Fuchsia() bool {
-	return b.os == Fuchsia
-}
-
 func (b *baseModuleContext) Windows() bool {
 	return b.os == Windows
 }
@@ -2618,14 +3105,14 @@
 	return m.module.InstallInRoot()
 }
 
-func (m *moduleContext) InstallBypassMake() bool {
-	return m.module.InstallBypassMake()
-}
-
 func (m *moduleContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.module.InstallForceOS()
 }
 
+func (m *moduleContext) InstallInVendor() bool {
+	return m.module.InstallInVendor()
+}
+
 func (m *moduleContext) skipInstall() bool {
 	if m.module.base().commonProperties.SkipInstall {
 		return true
@@ -2642,23 +3129,25 @@
 		return true
 	}
 
-	if m.Device() {
-		if m.Config().KatiEnabled() && !m.InstallBypassMake() {
-			return true
-		}
-	}
-
 	return false
 }
 
 func (m *moduleContext) InstallFile(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, deps, false)
+	return m.installFile(installPath, name, srcPath, deps, false, nil)
 }
 
 func (m *moduleContext) InstallExecutable(installPath InstallPath, name string, srcPath Path,
 	deps ...Path) InstallPath {
-	return m.installFile(installPath, name, srcPath, deps, true)
+	return m.installFile(installPath, name, srcPath, deps, true, nil)
+}
+
+func (m *moduleContext) InstallFileWithExtraFilesZip(installPath InstallPath, name string, srcPath Path,
+	extraZip Path, deps ...Path) InstallPath {
+	return m.installFile(installPath, name, srcPath, deps, false, &extraFilesZip{
+		zip: extraZip,
+		dir: installPath,
+	})
 }
 
 func (m *moduleContext) PackageFile(installPath InstallPath, name string, srcPath Path) PackagingSpec {
@@ -2667,17 +3156,21 @@
 }
 
 func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
+	licenseFiles := m.Module().EffectiveLicenseFiles()
 	spec := PackagingSpec{
-		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
-		srcPath:          srcPath,
-		symlinkTarget:    "",
-		executable:       executable,
+		relPathInPackage:      Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:               srcPath,
+		symlinkTarget:         "",
+		executable:            executable,
+		effectiveLicenseFiles: &licenseFiles,
+		partition:             fullInstallPath.partition,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
 }
 
-func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path, executable bool) InstallPath {
+func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []Path,
+	executable bool, extraZip *extraFilesZip) InstallPath {
 
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
@@ -2695,20 +3188,45 @@
 			orderOnlyDeps = deps
 		}
 
-		rule := Cp
-		if executable {
-			rule = CpExecutable
-		}
+		if m.Config().KatiEnabled() {
+			// When creating the install rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiInstalls = append(m.katiInstalls, katiInstall{
+				from:          srcPath,
+				to:            fullInstallPath,
+				implicitDeps:  implicitDeps,
+				orderOnlyDeps: orderOnlyDeps,
+				executable:    executable,
+				extraFiles:    extraZip,
+			})
+		} else {
+			rule := Cp
+			if executable {
+				rule = CpExecutable
+			}
 
-		m.Build(pctx, BuildParams{
-			Rule:        rule,
-			Description: "install " + fullInstallPath.Base(),
-			Output:      fullInstallPath,
-			Input:       srcPath,
-			Implicits:   implicitDeps,
-			OrderOnly:   orderOnlyDeps,
-			Default:     !m.Config().KatiEnabled(),
-		})
+			extraCmds := ""
+			if extraZip != nil {
+				extraCmds += fmt.Sprintf(" && ( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} )",
+					extraZip.dir.String(), extraZip.zip.String())
+				extraCmds += " || ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )"
+				implicitDeps = append(implicitDeps, extraZip.zip)
+			}
+
+			m.Build(pctx, BuildParams{
+				Rule:        rule,
+				Description: "install " + fullInstallPath.Base(),
+				Output:      fullInstallPath,
+				Input:       srcPath,
+				Implicits:   implicitDeps,
+				OrderOnly:   orderOnlyDeps,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"extraCmds": extraCmds,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
@@ -2730,16 +3248,30 @@
 	}
 	if !m.skipInstall() {
 
-		m.Build(pctx, BuildParams{
-			Rule:        Symlink,
-			Description: "install symlink " + fullInstallPath.Base(),
-			Output:      fullInstallPath,
-			Input:       srcPath,
-			Default:     !m.Config().KatiEnabled(),
-			Args: map[string]string{
-				"fromPath": relPath,
-			},
-		})
+		if m.Config().KatiEnabled() {
+			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+				from: srcPath,
+				to:   fullInstallPath,
+			})
+		} else {
+			// The symlink doesn't need updating when the target is modified, but we sometimes
+			// have a dependency on a symlink to a binary instead of to the binary directly, and
+			// the mtime of the symlink must be updated when the binary is modified, so use a
+			// normal dependency here instead of an order-only dependency.
+			m.Build(pctx, BuildParams{
+				Rule:        Symlink,
+				Description: "install symlink " + fullInstallPath.Base(),
+				Output:      fullInstallPath,
+				Input:       srcPath,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"fromPath": relPath,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
@@ -2750,6 +3282,7 @@
 		srcPath:          nil,
 		symlinkTarget:    relPath,
 		executable:       false,
+		partition:        fullInstallPath.partition,
 	})
 
 	return fullInstallPath
@@ -2762,15 +3295,25 @@
 	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
 	if !m.skipInstall() {
-		m.Build(pctx, BuildParams{
-			Rule:        Symlink,
-			Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
-			Output:      fullInstallPath,
-			Default:     !m.Config().KatiEnabled(),
-			Args: map[string]string{
-				"fromPath": absPath,
-			},
-		})
+		if m.Config().KatiEnabled() {
+			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+			// makefile instead of directly to the ninja file so that main.mk can add the
+			// dependencies from the `required` property that are hard to resolve in Soong.
+			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+				absFrom: absPath,
+				to:      fullInstallPath,
+			})
+		} else {
+			m.Build(pctx, BuildParams{
+				Rule:        Symlink,
+				Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
+				Output:      fullInstallPath,
+				Default:     !m.Config().KatiEnabled(),
+				Args: map[string]string{
+					"fromPath": absPath,
+				},
+			})
+		}
 
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
@@ -2780,6 +3323,7 @@
 		srcPath:          nil,
 		symlinkTarget:    absPath,
 		executable:       false,
+		partition:        fullInstallPath.partition,
 	})
 
 	return fullInstallPath
@@ -2793,42 +3337,105 @@
 	return m.bp
 }
 
-// SrcIsModule decodes module references in the format ":name" into the module name, or empty string if the input
-// was not a module reference.
-func SrcIsModule(s string) (module string) {
-	if len(s) > 1 && s[0] == ':' {
-		return s[1:]
-	}
-	return ""
+func (m *moduleContext) LicenseMetadataFile() Path {
+	return m.module.base().licenseMetadataFile
 }
 
-// SrcIsModule decodes module references in the format ":name{.tag}" into the module name and tag, ":name" into the
-// module name and an empty string for the tag, or empty strings if the input was not a module reference.
+// SrcIsModule decodes module references in the format ":unqualified-name" or "//namespace:name"
+// into the module name, or empty string if the input was not a module reference.
+func SrcIsModule(s string) (module string) {
+	if len(s) > 1 {
+		if s[0] == ':' {
+			module = s[1:]
+			if !isUnqualifiedModuleName(module) {
+				// The module name should be unqualified but is not so do not treat it as a module.
+				module = ""
+			}
+		} else if s[0] == '/' && s[1] == '/' {
+			module = s
+		}
+	}
+	return module
+}
+
+// SrcIsModuleWithTag decodes module references in the format ":unqualified-name{.tag}" or
+// "//namespace:name{.tag}" into the module name and tag, ":unqualified-name" or "//namespace:name"
+// into the module name and an empty string for the tag, or empty strings if the input was not a
+// module reference.
 func SrcIsModuleWithTag(s string) (module, tag string) {
-	if len(s) > 1 && s[0] == ':' {
-		module = s[1:]
-		if tagStart := strings.IndexByte(module, '{'); tagStart > 0 {
-			if module[len(module)-1] == '}' {
-				tag = module[tagStart+1 : len(module)-1]
-				module = module[:tagStart]
-				return module, tag
+	if len(s) > 1 {
+		if s[0] == ':' {
+			module = s[1:]
+		} else if s[0] == '/' && s[1] == '/' {
+			module = s
+		}
+
+		if module != "" {
+			if tagStart := strings.IndexByte(module, '{'); tagStart > 0 {
+				if module[len(module)-1] == '}' {
+					tag = module[tagStart+1 : len(module)-1]
+					module = module[:tagStart]
+				}
+			}
+
+			if s[0] == ':' && !isUnqualifiedModuleName(module) {
+				// The module name should be unqualified but is not so do not treat it as a module.
+				module = ""
+				tag = ""
 			}
 		}
-		return module, ""
 	}
-	return "", ""
+
+	return module, tag
 }
 
+// isUnqualifiedModuleName makes sure that the supplied module is an unqualified module name, i.e.
+// does not contain any /.
+func isUnqualifiedModuleName(module string) bool {
+	return strings.IndexByte(module, '/') == -1
+}
+
+// sourceOrOutputDependencyTag is the dependency tag added automatically by pathDepsMutator for any
+// module reference in a property annotated with `android:"path"` or passed to ExtractSourceDeps
+// or ExtractSourcesDeps.
+//
+// If uniquely identifies the dependency that was added as it contains both the module name used to
+// add the dependency as well as the tag. That makes it very simple to find the matching dependency
+// in GetModuleFromPathDep as all it needs to do is find the dependency whose tag matches the tag
+// used to add it. It does not need to check that the module name as returned by one of
+// Module.Name(), BaseModuleContext.OtherModuleName() or ModuleBase.BaseModuleName() matches the
+// name supplied in the tag. That means it does not need to handle differences in module names
+// caused by prebuilt_ prefix, or fully qualified module names.
 type sourceOrOutputDependencyTag struct {
 	blueprint.BaseDependencyTag
+
+	// The name of the module.
+	moduleName string
+
+	// The tag that will be passed to the module's OutputFileProducer.OutputFiles(tag) method.
 	tag string
 }
 
-func sourceOrOutputDepTag(tag string) blueprint.DependencyTag {
-	return sourceOrOutputDependencyTag{tag: tag}
+func sourceOrOutputDepTag(moduleName, tag string) blueprint.DependencyTag {
+	return sourceOrOutputDependencyTag{moduleName: moduleName, tag: tag}
 }
 
-var SourceDepTag = sourceOrOutputDepTag("")
+// IsSourceDepTag returns true if the supplied blueprint.DependencyTag is one that was used to add
+// dependencies by either ExtractSourceDeps, ExtractSourcesDeps or automatically for properties
+// tagged with `android:"path"`.
+func IsSourceDepTag(depTag blueprint.DependencyTag) bool {
+	_, ok := depTag.(sourceOrOutputDependencyTag)
+	return ok
+}
+
+// IsSourceDepTagWithOutputTag returns true if the supplied blueprint.DependencyTag is one that was
+// used to add dependencies by either ExtractSourceDeps, ExtractSourcesDeps or automatically for
+// properties tagged with `android:"path"` AND it was added using a module reference of
+// :moduleName{outputTag}.
+func IsSourceDepTagWithOutputTag(depTag blueprint.DependencyTag, outputTag string) bool {
+	t, ok := depTag.(sourceOrOutputDependencyTag)
+	return ok && t.tag == outputTag
+}
 
 // Adds necessary dependencies to satisfy filegroup or generated sources modules listed in srcFiles
 // using ":module" syntax, if any.
@@ -2843,7 +3450,7 @@
 				ctx.ModuleErrorf("found source dependency duplicate: %q!", s)
 			} else {
 				set[s] = true
-				ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
+				ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(m, t), m)
 			}
 		}
 	}
@@ -2856,7 +3463,7 @@
 func ExtractSourceDeps(ctx BottomUpMutatorContext, s *string) {
 	if s != nil {
 		if m, t := SrcIsModuleWithTag(*s); m != "" {
-			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
+			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(m, t), m)
 		}
 	}
 }
@@ -2988,6 +3595,34 @@
 
 type buildTargetSingleton struct{}
 
+func AddAncestors(ctx SingletonContext, dirMap map[string]Paths, mmName func(string) string) ([]string, []string) {
+	// Ensure ancestor directories are in dirMap
+	// Make directories build their direct subdirectories
+	// Returns a slice of all directories and a slice of top-level directories.
+	dirs := SortedStringKeys(dirMap)
+	for _, dir := range dirs {
+		dir := parentDir(dir)
+		for dir != "." && dir != "/" {
+			if _, exists := dirMap[dir]; exists {
+				break
+			}
+			dirMap[dir] = nil
+			dir = parentDir(dir)
+		}
+	}
+	dirs = SortedStringKeys(dirMap)
+	var topDirs []string
+	for _, dir := range dirs {
+		p := parentDir(dir)
+		if p != "." && p != "/" {
+			dirMap[p] = append(dirMap[p], PathForPhony(ctx, mmName(dir)))
+		} else if dir != "." && dir != "/" && dir != "" {
+			topDirs = append(topDirs, dir)
+		}
+	}
+	return SortedStringKeys(dirMap), topDirs
+}
+
 func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) {
 	var checkbuildDeps Paths
 
@@ -3025,26 +3660,7 @@
 		return
 	}
 
-	// Ensure ancestor directories are in modulesInDir
-	dirs := SortedStringKeys(modulesInDir)
-	for _, dir := range dirs {
-		dir := parentDir(dir)
-		for dir != "." && dir != "/" {
-			if _, exists := modulesInDir[dir]; exists {
-				break
-			}
-			modulesInDir[dir] = nil
-			dir = parentDir(dir)
-		}
-	}
-
-	// Make directories build their direct subdirectories
-	for _, dir := range dirs {
-		p := parentDir(dir)
-		if p != "." && p != "/" {
-			modulesInDir[p] = append(modulesInDir[p], PathForPhony(ctx, mmTarget(dir)))
-		}
-	}
+	dirs, _ := AddAncestors(ctx, modulesInDir, mmTarget)
 
 	// Create a MODULES-IN-<directory> target that depends on all modules in a directory, and
 	// depends on the MODULES-IN-* targets of all of its subdirectories that contain Android.bp
@@ -3119,6 +3735,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/module_test.go b/android/module_test.go
index 9ac9291..77ef146 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"path/filepath"
+	"runtime"
 	"testing"
 )
 
@@ -55,6 +57,27 @@
 			},
 			wantModule: "foo:bar",
 		},
+		{
+			name: "fully qualified",
+			args: args{
+				s: "//foo:bar",
+			},
+			wantModule: "//foo:bar",
+		},
+		{
+			name: "fully qualified with tag",
+			args: args{
+				s: "//foo:bar{.tag}",
+			},
+			wantModule: "//foo:bar{.tag}",
+		},
+		{
+			name: "invalid unqualified name",
+			args: args{
+				s: ":foo/bar",
+			},
+			wantModule: "",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -128,6 +151,35 @@
 			},
 			wantModule: "foo.bar}",
 		},
+		{
+			name: "fully qualified",
+			args: args{
+				s: "//foo:bar",
+			},
+			wantModule: "//foo:bar",
+		},
+		{
+			name: "fully qualified with tag",
+			args: args{
+				s: "//foo:bar{.tag}",
+			},
+			wantModule: "//foo:bar",
+			wantTag:    ".tag",
+		},
+		{
+			name: "invalid unqualified name",
+			args: args{
+				s: ":foo/bar",
+			},
+			wantModule: "",
+		},
+		{
+			name: "invalid unqualified name with tag",
+			args: args{
+				s: ":foo/bar{.tag}",
+			},
+			wantModule: "",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -150,16 +202,23 @@
 }
 
 func (m *depsModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	outputFile := PathForModuleOut(ctx, ctx.ModuleName())
+	ctx.Build(pctx, BuildParams{
+		Rule:   Touch,
+		Output: outputFile,
+	})
+	installFile := ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+	ctx.InstallSymlink(PathForModuleInstall(ctx, "symlinks"), ctx.ModuleName(), installFile)
 }
 
 func (m *depsModule) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
 }
 
 func depsModuleFactory() Module {
 	m := &depsModule{}
 	m.AddProperties(&m.props)
-	InitAndroidModule(m)
+	InitAndroidArchModule(m, HostAndDeviceDefault, MultilibCommon)
 	return m
 }
 
@@ -270,3 +329,585 @@
 		ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(expectedErrs)).
 		RunTestWithBp(t, bp)
 }
+
+func TestInstall(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+	).RunTestWithBp(t, bp)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	installRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) TestingBuildParams {
+		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) TestingBuildParams {
+		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name))
+	}
+
+	assertInputs := func(params TestingBuildParams, inputs ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", Paths(inputs).Strings(),
+			append(PathsIfNonNil(params.Input), params.Inputs...).Strings())
+	}
+
+	assertImplicits := func(params TestingBuildParams, implicits ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected implicit dependencies", Paths(implicits).Strings(),
+			append(PathsIfNonNil(params.Implicit), params.Implicits...).Strings())
+	}
+
+	assertOrderOnlys := func(params TestingBuildParams, orderonlys ...Path) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", Paths(orderonlys).Strings(),
+			params.OrderOnly.Strings())
+	}
+
+	// Check host install rule dependencies
+	assertInputs(hostInstallRule("foo"), hostOutputRule("foo").Output)
+	assertImplicits(hostInstallRule("foo"),
+		hostInstallRule("bar").Output,
+		hostSymlinkRule("bar").Output,
+		hostInstallRule("baz").Output,
+		hostSymlinkRule("baz").Output,
+		hostInstallRule("qux").Output,
+		hostSymlinkRule("qux").Output,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an
+	// order-only dependency, so that the tool gets updated when the symlink is depended on.
+	assertInputs(hostSymlinkRule("foo"), hostInstallRule("foo").Output)
+	assertImplicits(hostSymlinkRule("foo"))
+	assertOrderOnlys(hostSymlinkRule("foo"))
+
+	// Check device install rule dependencies
+	assertInputs(installRule("foo"), outputRule("foo").Output)
+	assertImplicits(installRule("foo"))
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").Output,
+		symlinkRule("bar").Output,
+		installRule("baz").Output,
+		symlinkRule("baz").Output,
+		installRule("qux").Output,
+		symlinkRule("qux").Output,
+	)
+
+	// Check device symlink rule dependencies.  Device symlinks could use an order-only dependency,
+	// but the current implementation uses a normal dependency.
+	assertInputs(symlinkRule("foo"), installRule("foo").Output)
+	assertImplicits(symlinkRule("foo"))
+	assertOrderOnlys(symlinkRule("foo"))
+}
+
+func TestInstallKatiEnabled(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+	bp := `
+		deps {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		deps {
+			name: "bar",
+			deps: ["baz", "qux"],
+		}
+
+		deps {
+			name: "baz",
+			deps: ["qux"],
+		}
+
+		deps {
+			name: "qux",
+		}
+	`
+
+	result := GroupFixturePreparers(
+		prepareForModuleTests,
+		PrepareForTestWithArchMutator,
+		FixtureModifyConfig(SetKatiEnabledForTests),
+		PrepareForTestWithMakevars,
+	).RunTestWithBp(t, bp)
+
+	rules := result.InstallMakeRulesForTesting(t)
+
+	module := func(name string, host bool) TestingModule {
+		variant := "android_common"
+		if host {
+			variant = result.Config.BuildOSCommonTarget.String()
+		}
+		return result.ModuleForTests(name, variant)
+	}
+
+	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
+
+	ruleForOutput := func(output string) InstallMakeRule {
+		for _, rule := range rules {
+			if rule.Target == output {
+				return rule
+			}
+		}
+		t.Fatalf("no make install rule for %s", output)
+		return InstallMakeRule{}
+	}
+
+	installRule := func(name string) InstallMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system", name))
+	}
+
+	symlinkRule := func(name string) InstallMakeRule {
+		return ruleForOutput(filepath.Join("out/target/product/test_device/system/symlinks", name))
+	}
+
+	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
+
+	hostInstallRule := func(name string) InstallMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86", name))
+	}
+
+	hostSymlinkRule := func(name string) InstallMakeRule {
+		return ruleForOutput(filepath.Join("out/host/linux-x86/symlinks", name))
+	}
+
+	assertDeps := func(rule InstallMakeRule, deps ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected inputs", deps, rule.Deps)
+	}
+
+	assertOrderOnlys := func(rule InstallMakeRule, orderonlys ...string) {
+		t.Helper()
+		AssertArrayString(t, "expected orderonly dependencies", orderonlys, rule.OrderOnlyDeps)
+	}
+
+	// Check host install rule dependencies
+	assertDeps(hostInstallRule("foo"),
+		hostOutputRule("foo").Output.String(),
+		hostInstallRule("bar").Target,
+		hostSymlinkRule("bar").Target,
+		hostInstallRule("baz").Target,
+		hostSymlinkRule("baz").Target,
+		hostInstallRule("qux").Target,
+		hostSymlinkRule("qux").Target,
+	)
+	assertOrderOnlys(hostInstallRule("foo"))
+
+	// Check host symlink rule dependencies.  Host symlinks must use a normal dependency, not an
+	// order-only dependency, so that the tool gets updated when the symlink is depended on.
+	assertDeps(hostSymlinkRule("foo"), hostInstallRule("foo").Target)
+	assertOrderOnlys(hostSymlinkRule("foo"))
+
+	// Check device install rule dependencies
+	assertDeps(installRule("foo"), outputRule("foo").Output.String())
+	assertOrderOnlys(installRule("foo"),
+		installRule("bar").Target,
+		symlinkRule("bar").Target,
+		installRule("baz").Target,
+		symlinkRule("baz").Target,
+		installRule("qux").Target,
+		symlinkRule("qux").Target,
+	)
+
+	// Check device symlink rule dependencies.  Device symlinks could use an order-only dependency,
+	// but the current implementation uses a normal dependency.
+	assertDeps(symlinkRule("foo"), installRule("foo").Target)
+	assertOrderOnlys(symlinkRule("foo"))
+}
+
+type PropsTestModuleEmbedded struct {
+	Embedded_prop *string
+}
+
+type StructInSlice struct {
+	G string
+	H bool
+	I []string
+}
+
+type propsTestModule struct {
+	ModuleBase
+	DefaultableModuleBase
+	props struct {
+		A string `android:"arch_variant"`
+		B *bool
+		C []string
+	}
+	otherProps struct {
+		PropsTestModuleEmbedded
+
+		D      *int64
+		Nested struct {
+			E *string
+		}
+		F *string `blueprint:"mutated"`
+
+		Slice_of_struct []StructInSlice
+	}
+}
+
+func propsTestModuleFactory() Module {
+	module := &propsTestModule{}
+	module.AddProperties(&module.props, &module.otherProps)
+	InitAndroidArchModule(module, HostAndDeviceSupported, MultilibBoth)
+	InitDefaultableModule(module)
+	return module
+}
+
+type propsTestModuleDefaults struct {
+	ModuleBase
+	DefaultsModuleBase
+}
+
+func propsTestModuleDefaultsFactory() Module {
+	defaults := &propsTestModuleDefaults{}
+	module := propsTestModule{}
+	defaults.AddProperties(&module.props, &module.otherProps)
+	InitDefaultsModule(defaults)
+	return defaults
+}
+
+func (p *propsTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	str := "abc"
+	p.otherProps.F = &str
+}
+
+func TestUsedProperties(t *testing.T) {
+	testCases := []struct {
+		desc          string
+		bp            string
+		expectedProps []propInfo
+	}{
+		{
+			desc: "only name",
+			bp: `test {
+			name: "foo",
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "some props",
+			bp: `test {
+			name: "foo",
+			a: "abc",
+			b: true,
+			d: 123,
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "A", Type: "string", Value: "abc"},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "D", Type: "int64", Value: "123"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "unused non-pointer prop",
+			bp: `test {
+			name: "foo",
+			b: true,
+			d: 123,
+		}
+	`,
+			expectedProps: []propInfo{
+				// for non-pointer cannot distinguish between unused and intentionally set to empty
+				propInfo{Name: "A", Type: "string", Value: ""},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "D", Type: "int64", Value: "123"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "nested props",
+			bp: `test {
+			name: "foo",
+			nested: {
+				e: "abc",
+			}
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Nested.E", Type: "string", Value: "abc"},
+			},
+		},
+		{
+			desc: "arch props",
+			bp: `test {
+			name: "foo",
+			arch: {
+				x86_64: {
+					a: "abc",
+				},
+			}
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "abc"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "embedded props",
+			bp: `test {
+			name: "foo",
+			embedded_prop: "a",
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+			},
+		},
+		{
+			desc: "struct slice",
+			bp: `test {
+			name: "foo",
+			slice_of_struct: [
+				{
+					g: "abc",
+					h: false,
+					i: ["baz"],
+				},
+				{
+					g: "def",
+					h: true,
+					i: [],
+				},
+			]
+		}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Slice_of_struct", Type: "struct slice", Values: []string{
+					`android.StructInSlice{G: abc, H: false, I: [baz]}`,
+					`android.StructInSlice{G: def, H: true, I: []}`,
+				}},
+			},
+		},
+		{
+			desc: "defaults",
+			bp: `
+test_defaults {
+	name: "foo_defaults",
+	a: "a",
+	b: true,
+	c: ["default_c"],
+	embedded_prop:"a",
+	arch: {
+		x86_64: {
+			a: "x86_64 a",
+		},
+	},
+}
+test {
+	name: "foo",
+	defaults: ["foo_defaults"],
+	c: ["c"],
+	nested: {
+		e: "nested e",
+	},
+	target: {
+		linux: {
+			a: "a",
+		},
+	},
+}
+	`,
+			expectedProps: []propInfo{
+				propInfo{Name: "A", Type: "string", Value: "a"},
+				propInfo{Name: "Arch.X86_64.A", Type: "string", Value: "x86_64 a"},
+				propInfo{Name: "B", Type: "bool", Value: "true"},
+				propInfo{Name: "C", Type: "string slice", Values: []string{"default_c", "c"}},
+				propInfo{Name: "Defaults", Type: "string slice", Values: []string{"foo_defaults"}},
+				propInfo{Name: "Embedded_prop", Type: "string", Value: "a"},
+				propInfo{Name: "Name", Type: "string", Value: "foo"},
+				propInfo{Name: "Nested.E", Type: "string", Value: "nested e"},
+				propInfo{Name: "Target.Linux.A", Type: "string", Value: "a"},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			result := GroupFixturePreparers(
+				PrepareForTestWithAllowMissingDependencies,
+				PrepareForTestWithDefaults,
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("test", propsTestModuleFactory)
+					ctx.RegisterModuleType("test_defaults", propsTestModuleDefaultsFactory)
+				}),
+				FixtureWithRootAndroidBp(tc.bp),
+			).RunTest(t)
+
+			foo := result.ModuleForTests("foo", "").Module().base()
+
+			AssertDeepEquals(t, "foo ", tc.expectedProps, foo.propertiesWithValues())
+
+		})
+	}
+}
+
+func TestSortedUniqueNamedPaths(t *testing.T) {
+	type np struct {
+		path, name string
+	}
+	makePaths := func(l []np) NamedPaths {
+		result := make(NamedPaths, 0, len(l))
+		for _, p := range l {
+			result = append(result, NamedPath{PathForTesting(p.path), p.name})
+		}
+		return result
+	}
+
+	tests := []struct {
+		name        string
+		in          []np
+		expectedOut []np
+	}{
+		{
+			name:        "empty",
+			in:          []np{},
+			expectedOut: []np{},
+		},
+		{
+			name: "all_same",
+			in: []np{
+				{"a.txt", "A"},
+				{"a.txt", "A"},
+				{"a.txt", "A"},
+				{"a.txt", "A"},
+				{"a.txt", "A"},
+			},
+			expectedOut: []np{
+				{"a.txt", "A"},
+			},
+		},
+		{
+			name: "same_path_different_names",
+			in: []np{
+				{"a.txt", "C"},
+				{"a.txt", "A"},
+				{"a.txt", "D"},
+				{"a.txt", "B"},
+				{"a.txt", "E"},
+			},
+			expectedOut: []np{
+				{"a.txt", "A"},
+				{"a.txt", "B"},
+				{"a.txt", "C"},
+				{"a.txt", "D"},
+				{"a.txt", "E"},
+			},
+		},
+		{
+			name: "different_paths_same_name",
+			in: []np{
+				{"b/b.txt", "A"},
+				{"a/a.txt", "A"},
+				{"a/txt", "A"},
+				{"b", "A"},
+				{"a/b/d", "A"},
+			},
+			expectedOut: []np{
+				{"a/a.txt", "A"},
+				{"a/b/d", "A"},
+				{"a/txt", "A"},
+				{"b/b.txt", "A"},
+				{"b", "A"},
+			},
+		},
+		{
+			name: "all_different",
+			in: []np{
+				{"b/b.txt", "A"},
+				{"a/a.txt", "B"},
+				{"a/txt", "D"},
+				{"b", "C"},
+				{"a/b/d", "E"},
+			},
+			expectedOut: []np{
+				{"a/a.txt", "B"},
+				{"a/b/d", "E"},
+				{"a/txt", "D"},
+				{"b/b.txt", "A"},
+				{"b", "C"},
+			},
+		},
+		{
+			name: "some_different",
+			in: []np{
+				{"b/b.txt", "A"},
+				{"a/a.txt", "B"},
+				{"a/txt", "D"},
+				{"a/b/d", "E"},
+				{"b", "C"},
+				{"a/a.txt", "B"},
+				{"a/b/d", "E"},
+			},
+			expectedOut: []np{
+				{"a/a.txt", "B"},
+				{"a/b/d", "E"},
+				{"a/txt", "D"},
+				{"b/b.txt", "A"},
+				{"b", "C"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := SortedUniqueNamedPaths(makePaths(tt.in))
+			expected := makePaths(tt.expectedOut)
+			t.Logf("actual: %v", actual)
+			t.Logf("expected: %v", expected)
+			AssertDeepEquals(t, "SortedUniqueNamedPaths ", expected, actual)
+		})
+	}
+}
diff --git a/android/mutator.go b/android/mutator.go
index 365bf29..739e4ee 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -15,10 +15,9 @@
 package android
 
 import (
-	"android/soong/bazel"
-	"fmt"
 	"reflect"
-	"strings"
+
+	"android/soong/bazel"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -34,12 +33,12 @@
 //   continue on to GenerateAndroidBuildActions
 
 // RegisterMutatorsForBazelConversion is a alternate registration pipeline for bp2build. Exported for testing.
-func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators, depsMutators, bp2buildMutators []RegisterMutatorFunc) {
+func RegisterMutatorsForBazelConversion(ctx *Context, preArchMutators []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{
 		bazelConversionMode: true,
 	}
 
-	bp2buildPreArchMutators = append([]RegisterMutatorFunc{
+	bp2buildMutators := append([]RegisterMutatorFunc{
 		RegisterNamespaceMutator,
 		RegisterDefaultsPreArchMutators,
 		// TODO(b/165114590): this is required to resolve deps that are only prebuilts, but we should
@@ -47,20 +46,7 @@
 		RegisterPrebuiltsPreArchMutators,
 	},
 		preArchMutators...)
-
-	for _, f := range bp2buildPreArchMutators {
-		f(mctx)
-	}
-
-	bp2buildDepsMutators = append([]RegisterMutatorFunc{
-		registerDepsMutatorBp2Build,
-		registerPathDepsMutator,
-		registerBp2buildArchPathDepsMutator,
-	}, depsMutators...)
-
-	for _, f := range bp2buildDepsMutators {
-		f(mctx)
-	}
+	bp2buildMutators = append(bp2buildMutators, registerBp2buildConversionMutator)
 
 	// Register bp2build mutators
 	for _, f := range bp2buildMutators {
@@ -226,19 +212,12 @@
 }
 
 var bp2buildPreArchMutators = []RegisterMutatorFunc{}
-var bp2buildDepsMutators = []RegisterMutatorFunc{}
-var bp2buildMutators = map[string]RegisterMutatorFunc{}
 
-// RegisterBp2BuildMutator registers specially crafted mutators for
-// converting Blueprint/Android modules into special modules that can
-// be code-generated into Bazel BUILD targets.
-//
-// TODO(b/178068862): bring this into TestContext.
-func RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
-	f := func(ctx RegisterMutatorsContext) {
-		ctx.TopDown(moduleType, m)
-	}
-	bp2buildMutators[moduleType] = f
+// A minimal context for Bp2build conversion
+type Bp2buildMutatorContext interface {
+	BazelConversionPathContext
+
+	CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
 }
 
 // PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
@@ -247,12 +226,6 @@
 	bp2buildPreArchMutators = append(bp2buildPreArchMutators, f)
 }
 
-// DepsBp2BuildMutators adds mutators to be register for converting Android Blueprint modules into
-// Bazel BUILD targets that should run prior to conversion to resolve dependencies.
-func DepsBp2BuildMutators(f RegisterMutatorFunc) {
-	bp2buildDepsMutators = append(bp2buildDepsMutators, f)
-}
-
 type BaseMutatorContext interface {
 	BaseModuleContext
 
@@ -262,6 +235,9 @@
 	// Rename all variants of a module.  The new name is not visible to calls to ModuleName,
 	// AddDependency or OtherModuleName until after this mutator pass is complete.
 	Rename(name string)
+
+	// BazelConversionMode returns whether this mutator is being run as part of Bazel Conversion.
+	BazelConversionMode() bool
 }
 
 type TopDownMutator func(TopDownMutatorContext)
@@ -277,7 +253,15 @@
 	// factory method, just like in CreateModule, but also requires
 	// BazelTargetModuleProperties containing additional metadata for the
 	// bp2build codegenerator.
-	CreateBazelTargetModule(ModuleFactory, string, bazel.BazelTargetModuleProperties, interface{}) BazelTargetModule
+	CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
+
+	// CreateBazelTargetModuleWithRestrictions creates a BazelTargetModule by calling the
+	// factory method, just like in CreateModule, but also requires
+	// BazelTargetModuleProperties containing additional metadata for the
+	// bp2build codegenerator. The generated target is restricted to only be buildable for certain
+	// platforms, as dictated by a given bool attribute: the target will not be buildable in
+	// any platform for which this bool attribute is false.
+	CreateBazelTargetModuleWithRestrictions(bazel.BazelTargetModuleProperties, CommonAttributes, interface{}, bazel.BoolAttribute)
 }
 
 type topDownMutatorContext struct {
@@ -339,13 +323,13 @@
 	// AddVariationDependencies adds deps as dependencies of the current module, but uses the variations
 	// argument to select which variant of the dependency to use.  It returns a slice of modules for
 	// each dependency (some entries may be nil).  A variant of the dependency must exist that matches
-	// the all of the non-local variations of the current module, plus the variations argument.
+	// all the non-local variations of the current module, plus the variations argument.
 	//
 	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
 	// new dependencies have had the current mutator called on them.  If the mutator is not
 	// parallel this method does not affect the ordering of the current mutator pass, but will
 	// be ordered correctly for all future mutator passes.
-	AddVariationDependencies([]blueprint.Variation, blueprint.DependencyTag, ...string) []blueprint.Module
+	AddVariationDependencies(variations []blueprint.Variation, tag blueprint.DependencyTag, names ...string) []blueprint.Module
 
 	// AddFarVariationDependencies adds deps as dependencies of the current module, but uses the
 	// variations argument to select which variant of the dependency to use.  It returns a slice of
@@ -403,26 +387,24 @@
 	// variant of the current module.  The value should not be modified after being passed to
 	// SetVariationProvider.
 	SetVariationProvider(module blueprint.Module, provider blueprint.ProviderKey, value interface{})
-
-	// BazelConversionMode returns whether this mutator is being run as part of Bazel Conversion.
-	BazelConversionMode() bool
 }
 
 type bottomUpMutatorContext struct {
 	bp blueprint.BottomUpMutatorContext
 	baseModuleContext
-	finalPhase          bool
-	bazelConversionMode bool
+	finalPhase bool
 }
 
 func bottomUpMutatorContextFactory(ctx blueprint.BottomUpMutatorContext, a Module,
 	finalPhase, bazelConversionMode bool) BottomUpMutatorContext {
 
+	moduleContext := a.base().baseModuleContextFactory(ctx)
+	moduleContext.bazelConversionMode = bazelConversionMode
+
 	return &bottomUpMutatorContext{
-		bp:                  ctx,
-		baseModuleContext:   a.base().baseModuleContextFactory(ctx),
-		finalPhase:          finalPhase,
-		bazelConversionMode: bazelConversionMode,
+		bp:                ctx,
+		baseModuleContext: a.base().baseModuleContextFactory(ctx),
+		finalPhase:        finalPhase,
 	}
 }
 
@@ -455,9 +437,11 @@
 func (x *registerMutatorsContext) TopDown(name string, m TopDownMutator) MutatorHandle {
 	f := func(ctx blueprint.TopDownMutatorContext) {
 		if a, ok := ctx.Module().(Module); ok {
+			moduleContext := a.base().baseModuleContextFactory(ctx)
+			moduleContext.bazelConversionMode = x.bazelConversionMode
 			actx := &topDownMutatorContext{
 				bp:                ctx,
-				baseModuleContext: a.base().baseModuleContextFactory(ctx),
+				baseModuleContext: moduleContext,
 			}
 			m(actx)
 		}
@@ -523,54 +507,35 @@
 }
 
 func (t *topDownMutatorContext) CreateBazelTargetModule(
-	factory ModuleFactory,
-	name string,
 	bazelProps bazel.BazelTargetModuleProperties,
-	attrs interface{}) BazelTargetModule {
-	if strings.HasPrefix(name, bazel.BazelTargetModuleNamePrefix) {
-		panic(fmt.Errorf(
-			"The %s name prefix is added automatically, do not set it manually: %s",
-			bazel.BazelTargetModuleNamePrefix,
-			name))
-	}
-	name = bazel.BazelTargetModuleNamePrefix + name
-	nameProp := struct {
-		Name *string
-	}{
-		Name: &name,
-	}
-
-	b := t.createModuleWithoutInheritance(factory, &nameProp, attrs).(BazelTargetModule)
-	b.SetBazelTargetModuleProperties(bazelProps)
-	return b
+	commonAttrs CommonAttributes,
+	attrs interface{}) {
+	t.createBazelTargetModule(bazelProps, commonAttrs, attrs, bazel.BoolAttribute{})
 }
 
-func (t *topDownMutatorContext) AppendProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.AppendMatchingProperties(t.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
-	}
+func (t *topDownMutatorContext) CreateBazelTargetModuleWithRestrictions(
+	bazelProps bazel.BazelTargetModuleProperties,
+	commonAttrs CommonAttributes,
+	attrs interface{},
+	enabledProperty bazel.BoolAttribute) {
+	t.createBazelTargetModule(bazelProps, commonAttrs, attrs, enabledProperty)
 }
 
-func (t *topDownMutatorContext) PrependProperties(props ...interface{}) {
-	for _, p := range props {
-		err := proptools.PrependMatchingProperties(t.Module().base().customizableProperties,
-			p, nil)
-		if err != nil {
-			if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-				t.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
-			} else {
-				panic(err)
-			}
-		}
+func (t *topDownMutatorContext) createBazelTargetModule(
+	bazelProps bazel.BazelTargetModuleProperties,
+	commonAttrs CommonAttributes,
+	attrs interface{},
+	enabledProperty bazel.BoolAttribute) {
+	constraintAttributes := commonAttrs.fillCommonBp2BuildModuleAttrs(t, enabledProperty)
+	mod := t.Module()
+	info := bp2buildInfo{
+		Dir:             t.OtherModuleDir(mod),
+		BazelProps:      bazelProps,
+		CommonAttrs:     commonAttrs,
+		ConstraintAttrs: constraintAttributes,
+		Attrs:           attrs,
 	}
+	mod.base().addBp2buildInfo(info)
 }
 
 // android.topDownMutatorContext either has to embed blueprint.TopDownMutatorContext, in which case every method that
@@ -726,7 +691,3 @@
 func (b *bottomUpMutatorContext) SetVariationProvider(module blueprint.Module, provider blueprint.ProviderKey, value interface{}) {
 	b.bp.SetVariationProvider(module, provider, value)
 }
-
-func (b *bottomUpMutatorContext) BazelConversionMode() bool {
-	return b.bazelConversionMode
-}
diff --git a/android/namespace.go b/android/namespace.go
index d137636..fc7bc29 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -27,7 +27,11 @@
 )
 
 func init() {
-	RegisterModuleType("soong_namespace", NamespaceFactory)
+	registerNamespaceBuildComponents(InitRegistrationContext)
+}
+
+func registerNamespaceBuildComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("soong_namespace", NamespaceFactory)
 }
 
 // threadsafe sorted list
@@ -70,7 +74,7 @@
 
 // A NameResolver implements blueprint.NameInterface, and implements the logic to
 // find a module from namespaces based on a query string.
-// A query string can be a module name or can be be "//namespace_path:module_path"
+// A query string can be a module name or can be "//namespace_path:module_path"
 type NameResolver struct {
 	rootNamespace *Namespace
 
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 08e221a..ea399da 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -636,13 +636,12 @@
 	result := GroupFixturePreparers(
 		FixtureModifyContext(func(ctx *TestContext) {
 			ctx.RegisterModuleType("test_module", newTestModule)
-			ctx.RegisterModuleType("soong_namespace", NamespaceFactory)
 			ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
-			ctx.PreArchMutators(RegisterNamespaceMutator)
 			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
 				ctx.BottomUp("rename", renameMutator)
 			})
 		}),
+		PrepareForTestWithNamespace,
 		bps.AddToFixture(),
 	).
 		// Ignore errors for now so tests can check them later.
diff --git a/android/neverallow.go b/android/neverallow.go
index af072cd..aa47bca 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"path/filepath"
 	"reflect"
 	"regexp"
@@ -27,7 +28,7 @@
 // "neverallow" rules for the build system.
 //
 // This allows things which aren't related to the build system and are enforced
-// for sanity, in progress code refactors, or policy to be expressed in a
+// against assumptions, in progress code refactors, or policy to be expressed in a
 // straightforward away disjoint from implementations and tests which should
 // work regardless of these restrictions.
 //
@@ -55,6 +56,8 @@
 	AddNeverAllowRules(createCcSdkVariantRules()...)
 	AddNeverAllowRules(createUncompressDexRules()...)
 	AddNeverAllowRules(createMakefileGoalRules()...)
+	AddNeverAllowRules(createInitFirstStageRules()...)
+	AddNeverAllowRules(createProhibitFrameworkAccessRules()...)
 }
 
 // Add a NeverAllow rule to the set of rules to apply.
@@ -132,7 +135,6 @@
 		NeverAllow().
 			Without("name", "libhidlbase-combined-impl").
 			Without("name", "libhidlbase").
-			Without("name", "libhidlbase_pgo").
 			With("product_variables.enforce_vintf_manifest.cflags", "*").
 			Because("manifest enforcement should be independent of ."),
 
@@ -149,9 +151,10 @@
 
 func createJavaDeviceForHostRules() []Rule {
 	javaDeviceForHostProjectsAllowedList := []string{
+		"development/build",
 		"external/guava",
 		"external/robolectric-shadows",
-		"framework/layoutlib",
+		"frameworks/layoutlib",
 	}
 
 	return []Rule{
@@ -217,6 +220,24 @@
 	}
 }
 
+func createInitFirstStageRules() []Rule {
+	return []Rule{
+		NeverAllow().
+			Without("name", "init_first_stage").
+			With("install_in_root", "true").
+			Because("install_in_root is only for init_first_stage."),
+	}
+}
+
+func createProhibitFrameworkAccessRules() []Rule {
+	return []Rule{
+		NeverAllow().
+			With("libs", "framework").
+			WithoutMatcher("sdk_version", Regexp("(core_.*|^$)")).
+			Because("framework can't be used when building against SDK"),
+	}
+}
+
 func neverallowMutator(ctx BottomUpMutatorContext) {
 	m, ok := ctx.Module().(Module)
 	if !ok {
@@ -250,10 +271,6 @@
 			continue
 		}
 
-		if !n.appliesToBootclasspathJar(ctx) {
-			continue
-		}
-
 		ctx.ModuleErrorf("violates " + n.String())
 	}
 }
@@ -341,6 +358,20 @@
 	matcher ValueMatcher
 }
 
+func (r *ruleProperty) String() string {
+	return fmt.Sprintf("%q matches: %s", strings.Join(r.fields, "."), r.matcher)
+}
+
+type ruleProperties []ruleProperty
+
+func (r ruleProperties) String() string {
+	var s []string
+	for _, r := range r {
+		s = append(s, r.String())
+	}
+	return strings.Join(s, " ")
+}
+
 // A NeverAllow rule.
 type Rule interface {
 	In(path ...string) Rule
@@ -355,8 +386,6 @@
 
 	NotModuleType(types ...string) Rule
 
-	BootclasspathJar() Rule
-
 	With(properties, value string) Rule
 
 	WithMatcher(properties string, matcher ValueMatcher) Rule
@@ -382,8 +411,8 @@
 	moduleTypes       []string
 	unlessModuleTypes []string
 
-	props       []ruleProperty
-	unlessProps []ruleProperty
+	props       ruleProperties
+	unlessProps ruleProperties
 
 	onlyBootclasspathJar bool
 }
@@ -393,16 +422,19 @@
 	return &rule{directDeps: make(map[string]bool)}
 }
 
+// In adds path(s) where this rule applies.
 func (r *rule) In(path ...string) Rule {
 	r.paths = append(r.paths, cleanPaths(path)...)
 	return r
 }
 
+// NotIn adds path(s) to that this rule does not apply to.
 func (r *rule) NotIn(path ...string) Rule {
 	r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...)
 	return r
 }
 
+// InDirectDeps adds dep(s) that are not allowed with this rule.
 func (r *rule) InDirectDeps(deps ...string) Rule {
 	for _, d := range deps {
 		r.directDeps[d] = true
@@ -410,25 +442,30 @@
 	return r
 }
 
+// WithOsClass adds osClass(es) that this rule applies to.
 func (r *rule) WithOsClass(osClasses ...OsClass) Rule {
 	r.osClasses = append(r.osClasses, osClasses...)
 	return r
 }
 
+// ModuleType adds type(s) that this rule applies to.
 func (r *rule) ModuleType(types ...string) Rule {
 	r.moduleTypes = append(r.moduleTypes, types...)
 	return r
 }
 
+// NotModuleType adds type(s) that this rule does not apply to..
 func (r *rule) NotModuleType(types ...string) Rule {
 	r.unlessModuleTypes = append(r.unlessModuleTypes, types...)
 	return r
 }
 
+// With specifies property/value combinations that are restricted for this rule.
 func (r *rule) With(properties, value string) Rule {
 	return r.WithMatcher(properties, selectMatcher(value))
 }
 
+// WithMatcher specifies property/matcher combinations that are restricted for this rule.
 func (r *rule) WithMatcher(properties string, matcher ValueMatcher) Rule {
 	r.props = append(r.props, ruleProperty{
 		fields:  fieldNamesForProperties(properties),
@@ -437,10 +474,12 @@
 	return r
 }
 
+// Without specifies property/value combinations that this rule does not apply to.
 func (r *rule) Without(properties, value string) Rule {
 	return r.WithoutMatcher(properties, selectMatcher(value))
 }
 
+// Without specifies property/matcher combinations that this rule does not apply to.
 func (r *rule) WithoutMatcher(properties string, matcher ValueMatcher) Rule {
 	r.unlessProps = append(r.unlessProps, ruleProperty{
 		fields:  fieldNamesForProperties(properties),
@@ -456,49 +495,45 @@
 	return &equalMatcher{expected: expected}
 }
 
+// Because specifies a reason for this rule.
 func (r *rule) Because(reason string) Rule {
 	r.reason = reason
 	return r
 }
 
-func (r *rule) BootclasspathJar() Rule {
-	r.onlyBootclasspathJar = true
-	return r
-}
-
 func (r *rule) String() string {
-	s := "neverallow"
-	for _, v := range r.paths {
-		s += " dir:" + v + "*"
+	s := []string{"neverallow requirements. Not allowed:"}
+	if len(r.paths) > 0 {
+		s = append(s, fmt.Sprintf("in dirs: %q", r.paths))
 	}
-	for _, v := range r.unlessPaths {
-		s += " -dir:" + v + "*"
+	if len(r.moduleTypes) > 0 {
+		s = append(s, fmt.Sprintf("module types: %q", r.moduleTypes))
 	}
-	for _, v := range r.moduleTypes {
-		s += " type:" + v
+	if len(r.props) > 0 {
+		s = append(s, fmt.Sprintf("properties matching: %s", r.props))
 	}
-	for _, v := range r.unlessModuleTypes {
-		s += " -type:" + v
+	if len(r.directDeps) > 0 {
+		s = append(s, fmt.Sprintf("dep(s): %q", SortedStringKeys(r.directDeps)))
 	}
-	for _, v := range r.props {
-		s += " " + strings.Join(v.fields, ".") + v.matcher.String()
+	if len(r.osClasses) > 0 {
+		s = append(s, fmt.Sprintf("os class(es): %q", r.osClasses))
 	}
-	for _, v := range r.unlessProps {
-		s += " -" + strings.Join(v.fields, ".") + v.matcher.String()
+	if len(r.unlessPaths) > 0 {
+		s = append(s, fmt.Sprintf("EXCEPT in dirs: %q", r.unlessPaths))
 	}
-	for k := range r.directDeps {
-		s += " deps:" + k
+	if len(r.unlessModuleTypes) > 0 {
+		s = append(s, fmt.Sprintf("EXCEPT module types: %q", r.unlessModuleTypes))
 	}
-	for _, v := range r.osClasses {
-		s += " os:" + v.String()
-	}
-	if r.onlyBootclasspathJar {
-		s += " inBcp"
+	if len(r.unlessProps) > 0 {
+		s = append(s, fmt.Sprintf("EXCEPT properties matching: %q", r.unlessProps))
 	}
 	if len(r.reason) != 0 {
-		s += " which is restricted because " + r.reason
+		s = append(s, " which is restricted because "+r.reason)
 	}
-	return s
+	if len(s) == 1 {
+		s[0] = "neverallow requirements (empty)"
+	}
+	return strings.Join(s, "\n\t")
 }
 
 func (r *rule) appliesToPath(dir string) bool {
@@ -523,14 +558,6 @@
 	return matches
 }
 
-func (r *rule) appliesToBootclasspathJar(ctx BottomUpMutatorContext) bool {
-	if !r.onlyBootclasspathJar {
-		return true
-	}
-
-	return InList(ctx.ModuleName(), ctx.Config().BootJars())
-}
-
 func (r *rule) appliesToOsClass(osClass OsClass) bool {
 	if len(r.osClasses) == 0 {
 		return true
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 35aadd8..86f1a37 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"regexp"
 	"testing"
 
 	"github.com/google/blueprint"
@@ -55,7 +56,37 @@
 				}`),
 		},
 		expectedErrors: []string{
-			`module "libother": violates neverallow deps:not_allowed_in_direct_deps`,
+			regexp.QuoteMeta("module \"libother\": violates neverallow requirements. Not allowed:\n\tdep(s): [\"not_allowed_in_direct_deps\"]"),
+		},
+	},
+	{
+		name: "multiple constraints",
+		rules: []Rule{
+			NeverAllow().
+				InDirectDeps("not_allowed_in_direct_deps").
+				In("other").
+				ModuleType("cc_library").
+				NotIn("top").
+				NotModuleType("cc_binary"),
+		},
+		fs: map[string][]byte{
+			"top/Android.bp": []byte(`
+				cc_library {
+					name: "not_allowed_in_direct_deps",
+				}`),
+			"other/Android.bp": []byte(`
+				cc_library {
+					name: "libother",
+					static_libs: ["not_allowed_in_direct_deps"],
+				}`),
+		},
+		expectedErrors: []string{
+			regexp.QuoteMeta(`module "libother": violates neverallow requirements. Not allowed:
+	in dirs: ["other/"]
+	module types: ["cc_library"]
+	dep(s): ["not_allowed_in_direct_deps"]
+	EXCEPT in dirs: ["top/"]
+	EXCEPT module types: ["cc_binary"]`),
 		},
 	},
 
@@ -296,6 +327,21 @@
 			"Only boot images may be imported as a makefile goal.",
 		},
 	},
+	// Tests for the rule prohibiting the use of framework
+	{
+		name: "prohibit framework",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				java_library {
+					name: "foo",
+					libs: ["framework"],
+					sdk_version: "current",
+				}`),
+		},
+		expectedErrors: []string{
+			"framework can't be used when building against SDK",
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
diff --git a/android/notices.go b/android/notices.go
index 07cf3e4..b9c1682 100644
--- a/android/notices.go
+++ b/android/notices.go
@@ -15,88 +15,97 @@
 package android
 
 import (
+	"fmt"
 	"path/filepath"
-
-	"github.com/google/blueprint"
+	"strings"
 )
 
-func init() {
-	pctx.SourcePathVariable("merge_notices", "build/soong/scripts/mergenotice.py")
-	pctx.SourcePathVariable("generate_notice", "build/soong/scripts/generate-notice-files.py")
-
-	pctx.HostBinToolVariable("minigzip", "minigzip")
-}
-
-type NoticeOutputs struct {
-	Merged       OptionalPath
-	TxtOutput    OptionalPath
-	HtmlOutput   OptionalPath
-	HtmlGzOutput OptionalPath
-}
-
-var (
-	mergeNoticesRule = pctx.AndroidStaticRule("mergeNoticesRule", blueprint.RuleParams{
-		Command:     `${merge_notices} --output $out $in`,
-		CommandDeps: []string{"${merge_notices}"},
-		Description: "merge notice files into $out",
-	})
-
-	generateNoticeRule = pctx.AndroidStaticRule("generateNoticeRule", blueprint.RuleParams{
-		Command: `rm -rf $$(dirname $txtOut) $$(dirname $htmlOut) $$(dirname $out) && ` +
-			`mkdir -p $$(dirname $txtOut) $$(dirname $htmlOut)  $$(dirname $out) && ` +
-			`${generate_notice} --text-output $txtOut --html-output $htmlOut -t "$title" -s $inputDir && ` +
-			`${minigzip} -c $htmlOut > $out`,
-		CommandDeps: []string{"${generate_notice}", "${minigzip}"},
-		Description: "produce notice file $out",
-	}, "txtOut", "htmlOut", "title", "inputDir")
-)
-
-func MergeNotices(ctx ModuleContext, mergedNotice WritablePath, noticePaths []Path) {
-	ctx.Build(pctx, BuildParams{
-		Rule:        mergeNoticesRule,
-		Description: "merge notices",
-		Inputs:      noticePaths,
-		Output:      mergedNotice,
-	})
-}
-
-func BuildNoticeOutput(ctx ModuleContext, installPath InstallPath, installFilename string,
-	noticePaths []Path) NoticeOutputs {
-	// Merge all NOTICE files into one.
-	// TODO(jungjw): We should just produce a well-formatted NOTICE.html file in a single pass.
-	//
-	// generate-notice-files.py, which processes the merged NOTICE file, has somewhat strict rules
-	// about input NOTICE file paths.
-	// 1. Their relative paths to the src root become their NOTICE index titles. We want to use
-	// on-device paths as titles, and so output the merged NOTICE file the corresponding location.
-	// 2. They must end with .txt extension. Otherwise, they're ignored.
-	noticeRelPath := InstallPathToOnDevicePath(ctx, installPath.Join(ctx, installFilename+".txt"))
-	mergedNotice := PathForModuleOut(ctx, filepath.Join("NOTICE_FILES/src", noticeRelPath))
-	MergeNotices(ctx, mergedNotice, noticePaths)
-
-	// Transform the merged NOTICE file into a gzipped HTML file.
-	txtOuptut := PathForModuleOut(ctx, "NOTICE_txt", "NOTICE.txt")
-	htmlOutput := PathForModuleOut(ctx, "NOTICE_html", "NOTICE.html")
-	htmlGzOutput := PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
-	title := "Notices for " + ctx.ModuleName()
-	ctx.Build(pctx, BuildParams{
-		Rule:            generateNoticeRule,
-		Description:     "generate notice output",
-		Input:           mergedNotice,
-		Output:          htmlGzOutput,
-		ImplicitOutputs: WritablePaths{txtOuptut, htmlOutput},
-		Args: map[string]string{
-			"txtOut":   txtOuptut.String(),
-			"htmlOut":  htmlOutput.String(),
-			"title":    title,
-			"inputDir": PathForModuleOut(ctx, "NOTICE_FILES/src").String(),
-		},
-	})
-
-	return NoticeOutputs{
-		Merged:       OptionalPathForPath(mergedNotice),
-		TxtOutput:    OptionalPathForPath(txtOuptut),
-		HtmlOutput:   OptionalPathForPath(htmlOutput),
-		HtmlGzOutput: OptionalPathForPath(htmlGzOutput),
+func modulesOutputDirs(ctx BuilderContext, modules ...Module) []string {
+	dirs := make([]string, 0, len(modules))
+	for _, module := range modules {
+		paths, err := outputFilesForModule(ctx, module, "")
+		if err != nil {
+			continue
+		}
+		for _, path := range paths {
+			if path != nil {
+				dirs = append(dirs, filepath.Dir(path.String()))
+			}
+		}
 	}
+	return SortedUniqueStrings(dirs)
+}
+
+func modulesLicenseMetadata(ctx BuilderContext, modules ...Module) Paths {
+	result := make(Paths, 0, len(modules))
+	for _, module := range modules {
+		if mf := module.base().licenseMetadataFile; mf != nil {
+			result = append(result, mf)
+		}
+	}
+	return result
+}
+
+// buildNoticeOutputFromLicenseMetadata writes out a notice file.
+func buildNoticeOutputFromLicenseMetadata(
+	ctx BuilderContext, tool, ruleName string, outputFile WritablePath,
+	libraryName string, stripPrefix []string, modules ...Module) {
+	depsFile := outputFile.ReplaceExtension(ctx, strings.TrimPrefix(outputFile.Ext()+".d", "."))
+	rule := NewRuleBuilder(pctx, ctx)
+	if len(modules) == 0 {
+		if mctx, ok := ctx.(ModuleContext); ok {
+			modules = []Module{mctx.Module()}
+		} else {
+			panic(fmt.Errorf("%s %q needs a module to generate the notice for", ruleName, libraryName))
+		}
+	}
+	if libraryName == "" {
+		libraryName = modules[0].Name()
+	}
+	cmd := rule.Command().
+		BuiltTool(tool).
+		FlagWithOutput("-o ", outputFile).
+		FlagWithDepFile("-d ", depsFile)
+	if len(stripPrefix) > 0 {
+		cmd = cmd.FlagForEachArg("--strip_prefix ", stripPrefix)
+	}
+	outputs := modulesOutputDirs(ctx, modules...)
+	if len(outputs) > 0 {
+		cmd = cmd.FlagForEachArg("--strip_prefix ", outputs)
+	}
+	if libraryName != "" {
+		cmd = cmd.FlagWithArg("--product ", libraryName)
+	}
+	cmd = cmd.Inputs(modulesLicenseMetadata(ctx, modules...))
+	rule.Build(ruleName, "container notice file")
+}
+
+// BuildNoticeTextOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeTextOutputFromLicenseMetadata(
+	ctx BuilderContext, outputFile WritablePath, ruleName, libraryName string,
+	stripPrefix []string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "textnotice", "text_notice_"+ruleName,
+		outputFile, libraryName, stripPrefix, modules...)
+}
+
+// BuildNoticeHtmlOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeHtmlOutputFromLicenseMetadata(
+	ctx BuilderContext, outputFile WritablePath, ruleName, libraryName string,
+	stripPrefix []string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "htmlnotice", "html_notice_"+ruleName,
+		outputFile, libraryName, stripPrefix, modules...)
+}
+
+// BuildNoticeXmlOutputFromLicenseMetadata writes out a notice text file based
+// on the license metadata files for the input `modules` defaulting to the
+// current context module if none given.
+func BuildNoticeXmlOutputFromLicenseMetadata(
+	ctx BuilderContext, outputFile WritablePath, ruleName, libraryName string,
+	stripPrefix []string, modules ...Module) {
+	buildNoticeOutputFromLicenseMetadata(ctx, "xmlnotice", "xml_notice_"+ruleName,
+		outputFile, libraryName, stripPrefix, modules...)
 }
diff --git a/android/override_module.go b/android/override_module.go
index 0a7e294..51e74d4 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -213,7 +213,6 @@
 // to keep them in this order and not put any order mutators between them.
 func RegisterOverridePostDepsMutators(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("override_deps", overrideModuleDepsMutator).Parallel()
-	ctx.TopDown("register_override", registerOverrideMutator).Parallel()
 	ctx.BottomUp("perform_override", performOverrideMutator).Parallel()
 	// overridableModuleDepsMutator calls OverridablePropertiesDepsMutator so that overridable modules can
 	// add deps from overridable properties.
@@ -253,20 +252,11 @@
 				return
 			}
 		})
-		ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)
-	}
-}
-
-// Visits the base module added as a dependency above, checks the module type, and registers the
-// overriding module.
-func registerOverrideMutator(ctx TopDownMutatorContext) {
-	ctx.VisitDirectDepsWithTag(overrideBaseDepTag, func(base Module) {
-		if o, ok := base.(OverridableModule); ok {
+		baseModule := ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)[0]
+		if o, ok := baseModule.(OverridableModule); ok {
 			o.addOverride(ctx.Module().(OverrideModule))
-		} else {
-			ctx.PropertyErrorf("base", "unsupported base module type")
 		}
-	})
+	}
 }
 
 // Now, goes through all overridable modules, finds all modules overriding them, creates a local
@@ -305,7 +295,7 @@
 }
 
 func overridableModuleDepsMutator(ctx BottomUpMutatorContext) {
-	if b, ok := ctx.Module().(OverridableModule); ok {
+	if b, ok := ctx.Module().(OverridableModule); ok && b.Enabled() {
 		b.OverridablePropertiesDepsMutator(ctx)
 	}
 }
diff --git a/android/package_ctx.go b/android/package_ctx.go
index c19debb..f354db8 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/remoteexec"
 )
@@ -173,7 +174,7 @@
 // package-scoped variable's initialization.
 func (p PackageContext) HostBinToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostToolPath(ctx, path).String())
 	})
 }
 
@@ -183,7 +184,7 @@
 // package-scoped variable's initialization.
 func (p PackageContext) HostJNIToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostJNIToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostJNIToolPath(ctx, path).String())
 	})
 }
 
@@ -193,7 +194,7 @@
 // part of a package-scoped variable's initialization.
 func (p PackageContext) HostJavaToolVariable(name, path string) blueprint.Variable {
 	return p.VariableFunc(name, func(ctx PackageVarContext) string {
-		return ctx.Config().HostJavaToolPath(ctx, path).String()
+		return proptools.NinjaAndShellEscape(ctx.Config().HostJavaToolPath(ctx, path).String())
 	})
 }
 
diff --git a/android/package_test.go b/android/package_test.go
index 3bd30cc..65c4240 100644
--- a/android/package_test.go
+++ b/android/package_test.go
@@ -11,9 +11,9 @@
 }{
 	// Package default_visibility handling is tested in visibility_test.go
 	{
-		name: "package must not accept visibility and name properties",
+		name: "package must not accept visibility, name or licenses properties",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					name: "package",
 					visibility: ["//visibility:private"],
@@ -21,21 +21,21 @@
 				}`),
 		},
 		expectedErrors: []string{
-			`top/Blueprints:5:14: unrecognized property "licenses"`,
-			`top/Blueprints:3:10: unrecognized property "name"`,
-			`top/Blueprints:4:16: unrecognized property "visibility"`,
+			`top/Android.bp:5:14: unrecognized property "licenses"`,
+			`top/Android.bp:3:10: unrecognized property "name"`,
+			`top/Android.bp:4:16: unrecognized property "visibility"`,
 		},
 	},
 	{
 		name: "multiple packages in separate directories",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				package {
 				}`),
-			"other/nested/Blueprints": []byte(`
+			"other/nested/Android.bp": []byte(`
 				package {
 				}`),
 		},
@@ -43,13 +43,12 @@
 	{
 		name: "package must not be specified more than once per package",
 		fs: map[string][]byte{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:private"],
 					default_applicable_licenses: ["license"],
 				}
-
-			        package {
+				package {
 				}`),
 		},
 		expectedErrors: []string{
diff --git a/android/packaging.go b/android/packaging.go
index 9065826..ecd84a2 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -38,6 +38,10 @@
 
 	// Whether relPathInPackage should be marked as executable or not
 	executable bool
+
+	effectiveLicenseFiles *Paths
+
+	partition string
 }
 
 // Get file name of installed package
@@ -54,6 +58,21 @@
 	return p.relPathInPackage
 }
 
+func (p *PackagingSpec) SetRelPathInPackage(relPathInPackage string) {
+	p.relPathInPackage = relPathInPackage
+}
+
+func (p *PackagingSpec) EffectiveLicenseFiles() Paths {
+	if p.effectiveLicenseFiles == nil {
+		return Paths{}
+	}
+	return *p.effectiveLicenseFiles
+}
+
+func (p *PackagingSpec) Partition() string {
+	return p.partition
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
@@ -63,11 +82,14 @@
 	// be copied to a zip in CopyDepsToZip, `depTag` should implement PackagingItem marker interface.
 	AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
 
+	// GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
+	GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
+
 	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
 	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
 	// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
 	// etc.) from the extracted files
-	CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) []string
+	CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) []string
 }
 
 // PackagingBase provides basic functionality for packaging dependencies. A module is expected to
@@ -198,7 +220,7 @@
 	}
 }
 
-// Returns transitive PackagingSpecs from deps
+// See PackageModule.GatherPackagingSpecs
 func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
 	ctx.VisitDirectDeps(func(child Module) {
@@ -214,18 +236,12 @@
 	return m
 }
 
-// See PackageModule.CopyDepsToZip
-func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
-	m := p.GatherPackagingSpecs(ctx)
-	builder := NewRuleBuilder(pctx, ctx)
-
-	dir := PathForModuleOut(ctx, ".zip")
-	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
-	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
-
+// CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
+// entries into the specified directory.
+func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
 	seenDir := make(map[string]bool)
-	for _, k := range SortedStringKeys(m) {
-		ps := m[k]
+	for _, k := range SortedStringKeys(specs) {
+		ps := specs[k]
 		destPath := dir.Join(ctx, ps.relPathInPackage).String()
 		destDir := filepath.Dir(destPath)
 		entries = append(entries, ps.relPathInPackage)
@@ -243,6 +259,18 @@
 		}
 	}
 
+	return entries
+}
+
+// See PackageModule.CopyDepsToZip
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, specs map[string]PackagingSpec, zipOut WritablePath) (entries []string) {
+	builder := NewRuleBuilder(pctx, ctx)
+
+	dir := PathForModuleOut(ctx, ".zip")
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(dir.String())
+	entries = p.CopySpecsToDir(ctx, builder, specs, dir)
+
 	builder.Command().
 		BuiltTool("soong_zip").
 		FlagWithOutput("-o ", zipOut).
diff --git a/android/packaging_test.go b/android/packaging_test.go
index f91dc5d..91ac1f3 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -18,13 +18,15 @@
 	"testing"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // Module to be packaged
 type componentTestModule struct {
 	ModuleBase
 	props struct {
-		Deps []string
+		Deps         []string
+		Skip_install *bool
 	}
 }
 
@@ -49,6 +51,9 @@
 	builtFile := PathForModuleOut(ctx, m.Name())
 	dir := ctx.Target().Arch.ArchType.Multilib
 	installDir := PathForModuleInstall(ctx, dir)
+	if proptools.Bool(m.props.Skip_install) {
+		m.SkipInstall()
+	}
 	ctx.InstallFile(installDir, m.Name(), builtFile)
 }
 
@@ -90,7 +95,7 @@
 
 func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	zipFile := PathForModuleOut(ctx, "myzip.zip")
-	m.entries = m.CopyDepsToZip(ctx, zipFile)
+	m.entries = m.CopyDepsToZip(ctx, m.GatherPackagingSpecs(ctx), zipFile)
 }
 
 func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
@@ -365,3 +370,31 @@
 		}
 		`, []string{"lib64/foo"})
 }
+
+func TestPackagingWithSkipInstallDeps(t *testing.T) {
+	// package -[dep]-> foo -[dep]-> bar      -[dep]-> baz
+	//                  OK           SKIPPED
+	multiTarget := false
+	runPackagingTest(t, multiTarget,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+			deps: ["baz"],
+			skip_install: true,
+		}
+
+		component {
+			name: "baz",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo"})
+}
diff --git a/android/path_properties.go b/android/path_properties.go
index 4446773..fdc4d91 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -33,7 +33,7 @@
 // The pathDepsMutator automatically adds dependencies on any module that is listed with the
 // ":module" module reference syntax in a property that is tagged with `android:"path"`.
 func pathDepsMutator(ctx BottomUpMutatorContext) {
-	props := ctx.Module().base().generalProperties
+	props := ctx.Module().base().GetProperties()
 	addPathDepsForProps(ctx, props)
 }
 
@@ -51,7 +51,7 @@
 	// Add dependencies to anything that is a module reference.
 	for _, s := range pathProperties {
 		if m, t := SrcIsModuleWithTag(s); m != "" {
-			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(t), m)
+			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(m, t), m)
 		}
 	}
 }
diff --git a/android/path_properties_test.go b/android/path_properties_test.go
index 568f868..07b4869 100644
--- a/android/path_properties_test.go
+++ b/android/path_properties_test.go
@@ -63,7 +63,8 @@
 
 	if p.props.Foo != "" {
 		// Make sure there is only one dependency on a module listed in a property present in multiple property structs
-		if ctx.GetDirectDepWithTag(SrcIsModule(p.props.Foo), sourceOrOutputDepTag("")) == nil {
+		m := SrcIsModule(p.props.Foo)
+		if GetModuleFromPathDep(ctx, m, "") == nil {
 			ctx.ModuleErrorf("GetDirectDepWithTag failed")
 		}
 	}
diff --git a/android/paths.go b/android/paths.go
index fb75117..e7829b9 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -20,6 +20,7 @@
 	"os"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -88,7 +89,8 @@
 // the Path methods that rely on module dependencies having been resolved.
 type ModuleWithDepsPathContext interface {
 	EarlyModulePathContext
-	GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module
+	VisitDirectDepsBlueprint(visit func(blueprint.Module))
+	OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
 }
 
 // ModuleMissingDepsPathContext is a subset of *ModuleContext methods required by
@@ -110,7 +112,6 @@
 	InstallInDebugRamdisk() bool
 	InstallInRecovery() bool
 	InstallInRoot() bool
-	InstallBypassMake() bool
 	InstallForceOS() (*OsType, *ArchType)
 }
 
@@ -184,13 +185,13 @@
 	// A standard build has the following structure:
 	//   ../top/
 	//          out/ - make install files go here.
-	//          out/soong - this is the buildDir passed to NewTestConfig()
+	//          out/soong - this is the soongOutDir passed to NewTestConfig()
 	//          ... - the source files
 	//
 	// This function converts a path so that it appears relative to the ../top/ directory, i.e.
-	// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
+	// * Make install paths, which have the pattern "soongOutDir/../<path>" are converted into the top
 	//   relative path "out/<path>"
-	// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
+	// * Soong install paths and other writable paths, which have the pattern "soongOutDir/<path>" are
 	//   converted into the top relative path "out/soong/<path>".
 	// * Source paths are already relative to the top.
 	// * Phony paths are not relative to anything.
@@ -209,7 +210,7 @@
 	Path
 
 	// return the path to the build directory.
-	getBuildDir() string
+	getSoongOutDir() string
 
 	// the writablePath method doesn't directly do anything,
 	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
@@ -261,38 +262,56 @@
 
 // OptionalPath is a container that may or may not contain a valid Path.
 type OptionalPath struct {
-	valid bool
-	path  Path
+	path          Path   // nil if invalid.
+	invalidReason string // Not applicable if path != nil. "" if the reason is unknown.
 }
 
 // OptionalPathForPath returns an OptionalPath containing the path.
 func OptionalPathForPath(path Path) OptionalPath {
-	if path == nil {
-		return OptionalPath{}
-	}
-	return OptionalPath{valid: true, path: path}
+	return OptionalPath{path: path}
+}
+
+// InvalidOptionalPath returns an OptionalPath that is invalid with the given reason.
+func InvalidOptionalPath(reason string) OptionalPath {
+
+	return OptionalPath{invalidReason: reason}
 }
 
 // Valid returns whether there is a valid path
 func (p OptionalPath) Valid() bool {
-	return p.valid
+	return p.path != nil
 }
 
 // Path returns the Path embedded in this OptionalPath. You must be sure that
 // there is a valid path, since this method will panic if there is not.
 func (p OptionalPath) Path() Path {
-	if !p.valid {
-		panic("Requesting an invalid path")
+	if p.path == nil {
+		msg := "Requesting an invalid path"
+		if p.invalidReason != "" {
+			msg += ": " + p.invalidReason
+		}
+		panic(msg)
 	}
 	return p.path
 }
 
+// InvalidReason returns the reason that the optional path is invalid, or "" if it is valid.
+func (p OptionalPath) InvalidReason() string {
+	if p.path != nil {
+		return ""
+	}
+	if p.invalidReason == "" {
+		return "unknown"
+	}
+	return p.invalidReason
+}
+
 // AsPaths converts the OptionalPath into Paths.
 //
 // It returns nil if this is not valid, or a single length slice containing the Path embedded in
 // this OptionalPath.
 func (p OptionalPath) AsPaths() Paths {
-	if !p.valid {
+	if p.path == nil {
 		return nil
 	}
 	return Paths{p.path}
@@ -301,7 +320,7 @@
 // RelativeToTop returns an OptionalPath with the path that was embedded having been replaced by the
 // result of calling Path.RelativeToTop on it.
 func (p OptionalPath) RelativeToTop() OptionalPath {
-	if !p.valid {
+	if p.path == nil {
 		return p
 	}
 	p.path = p.path.RelativeToTop()
@@ -310,7 +329,7 @@
 
 // String returns the string version of the Path, or "" if it isn't valid.
 func (p OptionalPath) String() string {
-	if p.valid {
+	if p.path != nil {
 		return p.path.String()
 	} else {
 		return ""
@@ -386,6 +405,13 @@
 	return PathsForModuleSrcExcludes(ctx, paths, nil)
 }
 
+type SourceInput struct {
+	Context      ModuleMissingDepsPathContext
+	Paths        []string
+	ExcludePaths []string
+	IncludeDirs  bool
+}
+
 // PathsForModuleSrcExcludes returns a Paths{} containing the resolved references in paths, minus
 // those listed in excludes. Elements of paths and excludes are resolved as:
 // * filepath, relative to local module directory, resolves as a filepath relative to the local
@@ -404,12 +430,21 @@
 //     missing dependencies
 //   * otherwise, a ModuleError is thrown.
 func PathsForModuleSrcExcludes(ctx ModuleMissingDepsPathContext, paths, excludes []string) Paths {
-	ret, missingDeps := PathsAndMissingDepsForModuleSrcExcludes(ctx, paths, excludes)
-	if ctx.Config().AllowMissingDependencies() {
-		ctx.AddMissingDependencies(missingDeps)
+	return PathsRelativeToModuleSourceDir(SourceInput{
+		Context:      ctx,
+		Paths:        paths,
+		ExcludePaths: excludes,
+		IncludeDirs:  true,
+	})
+}
+
+func PathsRelativeToModuleSourceDir(input SourceInput) Paths {
+	ret, missingDeps := PathsAndMissingDepsRelativeToModuleSourceDir(input)
+	if input.Context.Config().AllowMissingDependencies() {
+		input.Context.AddMissingDependencies(missingDeps)
 	} else {
 		for _, m := range missingDeps {
-			ctx.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m)
+			input.Context.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m)
 		}
 	}
 	return ret
@@ -442,11 +477,18 @@
 	return ret
 }
 
+// PathForGoBinary returns the path to the installed location of a bootstrap_go_binary module.
+func PathForGoBinary(ctx PathContext, goBinary bootstrap.GoBinaryTool) Path {
+	goBinaryInstallDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "bin", false)
+	rel := Rel(ctx, goBinaryInstallDir.String(), goBinary.InstallPath())
+	return goBinaryInstallDir.Join(ctx, rel)
+}
+
 // Expands Paths to a SourceFileProducer or OutputFileProducer module dependency referenced via ":name" or ":name{.tag}" syntax.
 // If the dependency is not found, a missingErrorDependency is returned.
 // If the module dependency is not a SourceFileProducer or OutputFileProducer, appropriate errors will be returned.
 func getPathsFromModuleDep(ctx ModuleWithDepsPathContext, path, moduleName, tag string) (Paths, error) {
-	module := ctx.GetDirectDepWithTag(moduleName, sourceOrOutputDepTag(tag))
+	module := GetModuleFromPathDep(ctx, moduleName, tag)
 	if module == nil {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
@@ -462,11 +504,8 @@
 	} else if tag != "" {
 		return nil, fmt.Errorf("path dependency %q is not an output file producing module", path)
 	} else if goBinary, ok := module.(bootstrap.GoBinaryTool); ok {
-		if rel, err := filepath.Rel(PathForOutput(ctx).String(), goBinary.InstallPath()); err == nil {
-			return Paths{PathForOutput(ctx, rel).WithoutRel()}, nil
-		} else {
-			return nil, fmt.Errorf("cannot find output path for %q: %w", goBinary.InstallPath(), err)
-		}
+		goBinaryPath := PathForGoBinary(ctx, goBinary)
+		return Paths{goBinaryPath}, nil
 	} else if srcProducer, ok := module.(SourceFileProducer); ok {
 		return srcProducer.Srcs(), nil
 	} else {
@@ -474,6 +513,39 @@
 	}
 }
 
+// GetModuleFromPathDep will return the module that was added as a dependency automatically for
+// properties tagged with `android:"path"` or manually using ExtractSourceDeps or
+// ExtractSourcesDeps.
+//
+// The moduleName and tag supplied to this should be the values returned from SrcIsModuleWithTag.
+// Or, if no tag is expected then the moduleName should be the value returned by  SrcIsModule and
+// the tag must be "".
+//
+// If tag is "" then the returned module will be the dependency that was added for ":moduleName".
+// Otherwise, it is the dependency that was added for ":moduleName{tag}".
+func GetModuleFromPathDep(ctx ModuleWithDepsPathContext, moduleName, tag string) blueprint.Module {
+	var found blueprint.Module
+	// The sourceOrOutputDepTag uniquely identifies the module dependency as it contains both the
+	// module name and the tag. Dependencies added automatically for properties tagged with
+	// `android:"path"` are deduped so are guaranteed to be unique. It is possible for duplicate
+	// dependencies to be added manually using ExtractSourcesDeps or ExtractSourceDeps but even then
+	// it will always be the case that the dependencies will be identical, i.e. the same tag and same
+	// moduleName referring to the same dependency module.
+	//
+	// It does not matter whether the moduleName is a fully qualified name or if the module
+	// dependency is a prebuilt module. All that matters is the same information is supplied to
+	// create the tag here as was supplied to create the tag when the dependency was added so that
+	// this finds the matching dependency module.
+	expectedTag := sourceOrOutputDepTag(moduleName, tag)
+	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
+		depTag := ctx.OtherModuleDependencyTag(module)
+		if depTag == expectedTag {
+			found = module
+		}
+	})
+	return found
+}
+
 // PathsAndMissingDepsForModuleSrcExcludes returns a Paths{} containing the resolved references in
 // paths, minus those listed in excludes. Elements of paths and excludes are resolved as:
 // * filepath, relative to local module directory, resolves as a filepath relative to the local
@@ -487,23 +559,31 @@
 // Properties passed as the paths argument must have been annotated with struct tag
 // `android:"path"` so that dependencies on SourceFileProducer modules will have already been handled by the
 // path_deps mutator.
-func PathsAndMissingDepsForModuleSrcExcludes(ctx ModuleWithDepsPathContext, paths, excludes []string) (Paths, []string) {
-	prefix := pathForModuleSrc(ctx).String()
+func PathsAndMissingDepsForModuleSrcExcludes(ctx ModuleMissingDepsPathContext, paths, excludes []string) (Paths, []string) {
+	return PathsAndMissingDepsRelativeToModuleSourceDir(SourceInput{
+		Context:      ctx,
+		Paths:        paths,
+		ExcludePaths: excludes,
+		IncludeDirs:  true,
+	})
+}
+
+func PathsAndMissingDepsRelativeToModuleSourceDir(input SourceInput) (Paths, []string) {
+	prefix := pathForModuleSrc(input.Context).String()
 
 	var expandedExcludes []string
-	if excludes != nil {
-		expandedExcludes = make([]string, 0, len(excludes))
+	if input.ExcludePaths != nil {
+		expandedExcludes = make([]string, 0, len(input.ExcludePaths))
 	}
 
 	var missingExcludeDeps []string
-
-	for _, e := range excludes {
+	for _, e := range input.ExcludePaths {
 		if m, t := SrcIsModuleWithTag(e); m != "" {
-			modulePaths, err := getPathsFromModuleDep(ctx, e, m, t)
+			modulePaths, err := getPathsFromModuleDep(input.Context, e, m, t)
 			if m, ok := err.(missingDependencyError); ok {
 				missingExcludeDeps = append(missingExcludeDeps, m.missingDeps...)
 			} else if err != nil {
-				reportPathError(ctx, err)
+				reportPathError(input.Context, err)
 			} else {
 				expandedExcludes = append(expandedExcludes, modulePaths.Strings()...)
 			}
@@ -512,19 +592,24 @@
 		}
 	}
 
-	if paths == nil {
+	if input.Paths == nil {
 		return nil, missingExcludeDeps
 	}
 
 	var missingDeps []string
 
-	expandedSrcFiles := make(Paths, 0, len(paths))
-	for _, s := range paths {
-		srcFiles, err := expandOneSrcPath(ctx, s, expandedExcludes)
+	expandedSrcFiles := make(Paths, 0, len(input.Paths))
+	for _, s := range input.Paths {
+		srcFiles, err := expandOneSrcPath(sourcePathInput{
+			context:          input.Context,
+			path:             s,
+			expandedExcludes: expandedExcludes,
+			includeDirs:      input.IncludeDirs,
+		})
 		if depErr, ok := err.(missingDependencyError); ok {
 			missingDeps = append(missingDeps, depErr.missingDeps...)
 		} else if err != nil {
-			reportPathError(ctx, err)
+			reportPathError(input.Context, err)
 		}
 		expandedSrcFiles = append(expandedSrcFiles, srcFiles...)
 	}
@@ -540,44 +625,59 @@
 	return "missing dependencies: " + strings.Join(e.missingDeps, ", ")
 }
 
+type sourcePathInput struct {
+	context          ModuleWithDepsPathContext
+	path             string
+	expandedExcludes []string
+	includeDirs      bool
+}
+
 // Expands one path string to Paths rooted from the module's local source
 // directory, excluding those listed in the expandedExcludes.
 // Expands globs, references to SourceFileProducer or OutputFileProducer modules using the ":name" and ":name{.tag}" syntax.
-func expandOneSrcPath(ctx ModuleWithDepsPathContext, sPath string, expandedExcludes []string) (Paths, error) {
+func expandOneSrcPath(input sourcePathInput) (Paths, error) {
 	excludePaths := func(paths Paths) Paths {
-		if len(expandedExcludes) == 0 {
+		if len(input.expandedExcludes) == 0 {
 			return paths
 		}
 		remainder := make(Paths, 0, len(paths))
 		for _, p := range paths {
-			if !InList(p.String(), expandedExcludes) {
+			if !InList(p.String(), input.expandedExcludes) {
 				remainder = append(remainder, p)
 			}
 		}
 		return remainder
 	}
-	if m, t := SrcIsModuleWithTag(sPath); m != "" {
-		modulePaths, err := getPathsFromModuleDep(ctx, sPath, m, t)
+	if m, t := SrcIsModuleWithTag(input.path); m != "" {
+		modulePaths, err := getPathsFromModuleDep(input.context, input.path, m, t)
 		if err != nil {
 			return nil, err
 		} else {
 			return excludePaths(modulePaths), nil
 		}
-	} else if pathtools.IsGlob(sPath) {
-		paths := GlobFiles(ctx, pathForModuleSrc(ctx, sPath).String(), expandedExcludes)
-		return PathsWithModuleSrcSubDir(ctx, paths, ""), nil
 	} else {
-		p := pathForModuleSrc(ctx, sPath)
-		if exists, _, err := ctx.Config().fs.Exists(p.String()); err != nil {
-			ReportPathErrorf(ctx, "%s: %s", p, err.Error())
-		} else if !exists && !ctx.Config().TestAllowNonExistentPaths {
-			ReportPathErrorf(ctx, "module source path %q does not exist", p)
-		}
+		p := pathForModuleSrc(input.context, input.path)
+		if pathtools.IsGlob(input.path) {
+			paths := GlobFiles(input.context, p.String(), input.expandedExcludes)
+			return PathsWithModuleSrcSubDir(input.context, paths, ""), nil
+		} else {
+			if exists, _, err := input.context.Config().fs.Exists(p.String()); err != nil {
+				ReportPathErrorf(input.context, "%s: %s", p, err.Error())
+			} else if !exists && !input.context.Config().TestAllowNonExistentPaths {
+				ReportPathErrorf(input.context, "module source path %q does not exist", p)
+			} else if !input.includeDirs {
+				if isDir, err := input.context.Config().fs.IsDir(p.String()); exists && err != nil {
+					ReportPathErrorf(input.context, "%s: %s", p, err.Error())
+				} else if isDir {
+					ReportPathErrorf(input.context, "module source path %q is a directory", p)
+				}
+			}
 
-		if InList(p.String(), expandedExcludes) {
-			return nil, nil
+			if InList(p.String(), input.expandedExcludes) {
+				return nil, nil
+			}
+			return Paths{p}, nil
 		}
-		return Paths{p}, nil
 	}
 }
 
@@ -587,7 +687,7 @@
 // It intended for use in globs that only list files that exist, so it allows '$' in
 // filenames.
 func pathsForModuleSrcFromFullPath(ctx EarlyModulePathContext, paths []string, incDirs bool) Paths {
-	prefix := filepath.Join(ctx.Config().srcDir, ctx.ModuleDir()) + "/"
+	prefix := ctx.ModuleDir() + "/"
 	if prefix == "./" {
 		prefix = ""
 	}
@@ -623,7 +723,7 @@
 	}
 	// Use Glob so that if the default doesn't exist, a dependency is added so that when it
 	// is created, we're run again.
-	path := filepath.Join(ctx.Config().srcDir, ctx.ModuleDir(), def)
+	path := filepath.Join(ctx.ModuleDir(), def)
 	return Glob(ctx, path, nil)
 }
 
@@ -951,13 +1051,13 @@
 // code that is embedding ninja variables in paths
 func safePathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validateSafePath(pathComponents...)
-	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
+	ret := SourcePath{basePath{p, ""}, "."}
 	if err != nil {
 		return ret, err
 	}
 
 	// absolute path already checked by validateSafePath
-	if strings.HasPrefix(ret.String(), ctx.Config().buildDir) {
+	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
 		return ret, fmt.Errorf("source path %q is in output", ret.String())
 	}
 
@@ -967,13 +1067,13 @@
 // pathForSource creates a SourcePath from pathComponents, but does not check that it exists.
 func pathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validatePath(pathComponents...)
-	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
+	ret := SourcePath{basePath{p, ""}, "."}
 	if err != nil {
 		return ret, err
 	}
 
 	// absolute path already checked by validatePath
-	if strings.HasPrefix(ret.String(), ctx.Config().buildDir) {
+	if strings.HasPrefix(ret.String(), ctx.Config().soongOutDir) {
 		return ret, fmt.Errorf("source path %q is in output", ret.String())
 	}
 
@@ -1042,6 +1142,7 @@
 	path, err := pathForSource(ctx, pathComponents...)
 	if err != nil {
 		reportPathError(ctx, err)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 
@@ -1056,7 +1157,7 @@
 		return OptionalPath{}
 	}
 	if !exists {
-		return OptionalPath{}
+		return InvalidOptionalPath(path.String() + " does not exist")
 	}
 	return OptionalPathForPath(path)
 }
@@ -1092,6 +1193,7 @@
 		relDir = srcPath.path
 	} else {
 		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
+		// No need to put the error message into the returned path since it has been reported already.
 		return OptionalPath{}
 	}
 	dir := filepath.Join(p.srcDir, p.path, relDir)
@@ -1105,7 +1207,7 @@
 		return OptionalPath{}
 	}
 	if len(paths) == 0 {
-		return OptionalPath{}
+		return InvalidOptionalPath(dir + " does not exist")
 	}
 	relPath := Rel(ctx, p.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
@@ -1115,8 +1217,8 @@
 type OutputPath struct {
 	basePath
 
-	// The soong build directory, i.e. Config.BuildDir()
-	buildDir string
+	// The soong build directory, i.e. Config.SoongOutDir()
+	soongOutDir string
 
 	fullPath string
 }
@@ -1132,8 +1234,8 @@
 	return p
 }
 
-func (p OutputPath) getBuildDir() string {
-	return p.buildDir
+func (p OutputPath) getSoongOutDir() string {
+	return p.soongOutDir
 }
 
 func (p OutputPath) RelativeToTop() Path {
@@ -1141,8 +1243,8 @@
 }
 
 func (p OutputPath) outputPathRelativeToTop() OutputPath {
-	p.fullPath = StringPathRelativeToTop(p.buildDir, p.fullPath)
-	p.buildDir = OutSoongDir
+	p.fullPath = StringPathRelativeToTop(p.soongOutDir, p.fullPath)
+	p.soongOutDir = OutSoongDir
 	return p
 }
 
@@ -1183,12 +1285,12 @@
 	if err != nil {
 		reportPathError(ctx, err)
 	}
-	fullPath := filepath.Join(ctx.Config().buildDir, path)
+	fullPath := filepath.Join(ctx.Config().soongOutDir, path)
 	path = fullPath[len(fullPath)-len(path):]
-	return OutputPath{basePath{path, ""}, ctx.Config().buildDir, fullPath}
+	return OutputPath{basePath{path, ""}, ctx.Config().soongOutDir, fullPath}
 }
 
-// PathsForOutput returns Paths rooted from buildDir
+// PathsForOutput returns Paths rooted from soongOutDir
 func PathsForOutput(ctx PathContext, paths []string) WritablePaths {
 	ret := make(WritablePaths, len(paths))
 	for i, path := range paths {
@@ -1252,11 +1354,12 @@
 // PathForModuleSrc returns a Path representing the paths... under the
 // module's local source directory.
 func PathForModuleSrc(ctx ModuleMissingDepsPathContext, pathComponents ...string) Path {
-	p, err := validatePath(pathComponents...)
-	if err != nil {
-		reportPathError(ctx, err)
-	}
-	paths, err := expandOneSrcPath(ctx, p, nil)
+	// Just join the components textually just to make sure that it does not corrupt a fully qualified
+	// module reference, e.g. if the pathComponents is "://other:foo" then using filepath.Join() or
+	// validatePath() will corrupt it, e.g. replace "//" with "/". If the path is not a module
+	// reference then it will be validated by expandOneSrcPath anyway when it calls expandOneSrcPath.
+	p := strings.Join(pathComponents, string(filepath.Separator))
+	paths, err := expandOneSrcPath(sourcePathInput{context: ctx, path: p, includeDirs: true})
 	if err != nil {
 		if depErr, ok := err.(missingDependencyError); ok {
 			if ctx.Config().AllowMissingDependencies() {
@@ -1371,14 +1474,11 @@
 func PathForVndkRefAbiDump(ctx ModuleInstallPathContext, version, fileName string,
 	isNdk, isLlndkOrVndk, isGzip bool) OptionalPath {
 
-	arches := ctx.DeviceConfig().Arches()
-	if len(arches) == 0 {
-		panic("device build with no primary arch")
-	}
-	currentArch := ctx.Arch()
-	archNameAndVariant := currentArch.ArchType.String()
-	if currentArch.ArchVariant != "" {
-		archNameAndVariant += "_" + currentArch.ArchVariant
+	currentArchType := ctx.Arch().ArchType
+	primaryArchType := ctx.Config().DevicePrimaryArchType()
+	archName := currentArchType.String()
+	if currentArchType != primaryArchType {
+		archName += "_" + primaryArchType.String()
 	}
 
 	var dirName string
@@ -1400,7 +1500,7 @@
 	}
 
 	return ExistentPathForSource(ctx, "prebuilts", "abi-dumps", dirName,
-		version, binderBitness, archNameAndVariant, "source-based",
+		version, binderBitness, archName, "source-based",
 		fileName+ext)
 }
 
@@ -1508,13 +1608,15 @@
 type InstallPath struct {
 	basePath
 
-	// The soong build directory, i.e. Config.BuildDir()
-	buildDir string
+	// The soong build directory, i.e. Config.SoongOutDir()
+	soongOutDir string
 
 	// partitionDir is the part of the InstallPath that is automatically determined according to the context.
 	// For example, it is host/<os>-<arch> for host modules, and target/product/<device>/<partition> for device modules.
 	partitionDir string
 
+	partition string
+
 	// makePath indicates whether this path is for Soong (false) or Make (true).
 	makePath bool
 }
@@ -1529,12 +1631,12 @@
 
 func (p InstallPath) RelativeToTop() Path {
 	ensureTestOnly()
-	p.buildDir = OutSoongDir
+	p.soongOutDir = OutSoongDir
 	return p
 }
 
-func (p InstallPath) getBuildDir() string {
-	return p.buildDir
+func (p InstallPath) getSoongOutDir() string {
+	return p.soongOutDir
 }
 
 func (p InstallPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
@@ -1549,9 +1651,9 @@
 func (p InstallPath) String() string {
 	if p.makePath {
 		// Make path starts with out/ instead of out/soong.
-		return filepath.Join(p.buildDir, "../", p.path)
+		return filepath.Join(p.soongOutDir, "../", p.path)
 	} else {
-		return filepath.Join(p.buildDir, p.path)
+		return filepath.Join(p.soongOutDir, p.path)
 	}
 }
 
@@ -1560,9 +1662,9 @@
 // The ./soong is dropped if the install path is for Make.
 func (p InstallPath) PartitionDir() string {
 	if p.makePath {
-		return filepath.Join(p.buildDir, "../", p.partitionDir)
+		return filepath.Join(p.soongOutDir, "../", p.partitionDir)
 	} else {
-		return filepath.Join(p.buildDir, p.partitionDir)
+		return filepath.Join(p.soongOutDir, p.partitionDir)
 	}
 }
 
@@ -1581,8 +1683,8 @@
 	return p
 }
 
-// ToMakePath returns a new InstallPath that points to Make's install directory instead of Soong's,
-// i.e. out/ instead of out/soong/.
+// Deprecated: ToMakePath is a noop, PathForModuleInstall always returns Make paths when building
+// embedded in Make.
 func (p InstallPath) ToMakePath() InstallPath {
 	p.makePath = true
 	return p
@@ -1591,6 +1693,24 @@
 // PathForModuleInstall returns a Path representing the install path for the
 // module appended with paths...
 func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
+	os, arch := osAndArch(ctx)
+	partition := modulePartition(ctx, os)
+	return makePathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
+}
+
+// PathForHostDexInstall returns an InstallPath representing the install path for the
+// module appended with paths...
+func PathForHostDexInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
+	return makePathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "", ctx.Debug(), pathComponents...)
+}
+
+// PathForModuleInPartitionInstall is similar to PathForModuleInstall but partition is provided by the caller
+func PathForModuleInPartitionInstall(ctx ModuleInstallPathContext, partition string, pathComponents ...string) InstallPath {
+	os, arch := osAndArch(ctx)
+	return makePathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
+}
+
+func osAndArch(ctx ModuleInstallPathContext) (OsType, ArchType) {
 	os := ctx.Os()
 	arch := ctx.Arch().ArchType
 	forceOS, forceArch := ctx.InstallForceOS()
@@ -1600,14 +1720,11 @@
 	if forceArch != nil {
 		arch = *forceArch
 	}
-	partition := modulePartition(ctx, os)
+	return os, arch
+}
 
-	ret := pathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
-
-	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
-		ret = ret.ToMakePath()
-	}
-
+func makePathForInstall(ctx ModuleInstallPathContext, os OsType, arch ArchType, partition string, debug bool, pathComponents ...string) InstallPath {
+	ret := pathForInstall(ctx, os, arch, partition, debug, pathComponents...)
 	return ret
 }
 
@@ -1620,7 +1737,7 @@
 		partionPaths = []string{"target", "product", ctx.Config().DeviceName(), partition}
 	} else {
 		osName := os.String()
-		if os == Linux {
+		if os == Linux || os == LinuxMusl {
 			// instead of linux_glibc
 			osName = "linux"
 		}
@@ -1646,9 +1763,13 @@
 
 	base := InstallPath{
 		basePath:     basePath{partionPath, ""},
-		buildDir:     ctx.Config().buildDir,
+		soongOutDir:  ctx.Config().soongOutDir,
 		partitionDir: partionPath,
-		makePath:     false,
+		partition:    partition,
+	}
+
+	if ctx.Config().KatiEnabled() {
+		base.makePath = true
 	}
 
 	return base.Join(ctx, pathComponents...)
@@ -1657,7 +1778,7 @@
 func pathForNdkOrSdkInstall(ctx PathContext, prefix string, paths []string) InstallPath {
 	base := InstallPath{
 		basePath:     basePath{prefix, ""},
-		buildDir:     ctx.Config().buildDir,
+		soongOutDir:  ctx.Config().soongOutDir,
 		partitionDir: prefix,
 		makePath:     false,
 	}
@@ -1673,8 +1794,7 @@
 }
 
 func InstallPathToOnDevicePath(ctx PathContext, path InstallPath) string {
-	rel := Rel(ctx, PathForOutput(ctx, "target", "product", ctx.Config().DeviceName()).String(), path.String())
-
+	rel := Rel(ctx, strings.TrimSuffix(path.PartitionDir(), path.partition), path.String())
 	return "/" + rel
 }
 
@@ -1803,7 +1923,7 @@
 
 func (p PhonyPath) writablePath() {}
 
-func (p PhonyPath) getBuildDir() string {
+func (p PhonyPath) getSoongOutDir() string {
 	// A phone path cannot contain any / so cannot be relative to the build directory.
 	return ""
 }
@@ -1925,10 +2045,6 @@
 	return m.inRoot
 }
 
-func (m testModuleInstallPathContext) InstallBypassMake() bool {
-	return false
-}
-
 func (m testModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.forceOS, m.forceArch
 }
@@ -1981,7 +2097,12 @@
 // Writes a file to the output directory.  Attempting to write directly to the output directory
 // will fail due to the sandbox of the soong_build process.
 func WriteFileToOutputDir(path WritablePath, data []byte, perm os.FileMode) error {
-	return ioutil.WriteFile(absolutePath(path.String()), data, perm)
+	absPath := absolutePath(path.String())
+	err := os.MkdirAll(filepath.Dir(absPath), 0777)
+	if err != nil {
+		return err
+	}
+	return ioutil.WriteFile(absPath, data, perm)
 }
 
 func RemoveAllOutputDir(path WritablePath) error {
@@ -1990,6 +2111,10 @@
 
 func CreateOutputDirIfNonexistent(path WritablePath, perm os.FileMode) error {
 	dir := absolutePath(path.String())
+	return createDirIfNonexistent(dir, perm)
+}
+
+func createDirIfNonexistent(dir string, perm os.FileMode) error {
 	if _, err := os.Stat(dir); os.IsNotExist(err) {
 		return os.MkdirAll(dir, os.ModePerm)
 	} else {
@@ -1997,6 +2122,9 @@
 	}
 }
 
+// absolutePath is deliberately private so that Soong's Go plugins can't use it to find and
+// read arbitrary files without going through the methods in the current package that track
+// dependencies.
 func absolutePath(path string) string {
 	if filepath.IsAbs(path) {
 		return path
@@ -2040,3 +2168,45 @@
 	}
 	return ret
 }
+
+var thirdPartyDirPrefixExceptions = []*regexp.Regexp{
+	regexp.MustCompile("^vendor/[^/]*google[^/]*/"),
+	regexp.MustCompile("^hardware/google/"),
+	regexp.MustCompile("^hardware/interfaces/"),
+	regexp.MustCompile("^hardware/libhardware[^/]*/"),
+	regexp.MustCompile("^hardware/ril/"),
+}
+
+func IsThirdPartyPath(path string) bool {
+	thirdPartyDirPrefixes := []string{"external/", "vendor/", "hardware/"}
+
+	if HasAnyPrefix(path, thirdPartyDirPrefixes) {
+		for _, prefix := range thirdPartyDirPrefixExceptions {
+			if prefix.MatchString(path) {
+				return false
+			}
+		}
+		return true
+	}
+	return false
+}
+
+// PathsDepSet is a thin type-safe wrapper around the generic depSet.  It always uses
+// topological order.
+type PathsDepSet struct {
+	depSet
+}
+
+// newPathsDepSet returns an immutable PathsDepSet with the given direct and
+// transitive contents.
+func newPathsDepSet(direct Paths, transitive []*PathsDepSet) *PathsDepSet {
+	return &PathsDepSet{*newDepSet(TOPOLOGICAL, direct, transitive)}
+}
+
+// ToList returns the PathsDepSet flattened to a list in topological order.
+func (d *PathsDepSet) ToList() Paths {
+	if d == nil {
+		return nil
+	}
+	return d.depSet.ToList().(Paths)
+}
diff --git a/android/paths_test.go b/android/paths_test.go
index 6f5d79e..2f87977 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -137,26 +137,35 @@
 
 func TestOptionalPath(t *testing.T) {
 	var path OptionalPath
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(nil)
-	checkInvalidOptionalPath(t, path)
+	checkInvalidOptionalPath(t, path, "unknown")
+
+	path = InvalidOptionalPath("foo")
+	checkInvalidOptionalPath(t, path, "foo")
+
+	path = InvalidOptionalPath("")
+	checkInvalidOptionalPath(t, path, "unknown")
 
 	path = OptionalPathForPath(PathForTesting("path"))
 	checkValidOptionalPath(t, path, "path")
 }
 
-func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
+func checkInvalidOptionalPath(t *testing.T, path OptionalPath, expectedInvalidReason string) {
 	t.Helper()
 	if path.Valid() {
-		t.Errorf("Uninitialized OptionalPath should not be valid")
+		t.Errorf("Invalid OptionalPath should not be valid")
+	}
+	if path.InvalidReason() != expectedInvalidReason {
+		t.Errorf("Wrong invalid reason: expected %q, got %q", expectedInvalidReason, path.InvalidReason())
 	}
 	if path.String() != "" {
-		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
+		t.Errorf("Invalid OptionalPath String() should return \"\", not %q", path.String())
 	}
 	paths := path.AsPaths()
 	if len(paths) != 0 {
-		t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths)
+		t.Errorf("Invalid OptionalPath AsPaths() should return empty Paths, not %q", paths)
 	}
 	defer func() {
 		if r := recover(); r == nil {
@@ -171,6 +180,9 @@
 	if !path.Valid() {
 		t.Errorf("Initialized OptionalPath should not be invalid")
 	}
+	if path.InvalidReason() != "" {
+		t.Errorf("Initialized OptionalPath should not have an invalid reason, got: %q", path.InvalidReason())
+	}
 	if path.String() != expectedString {
 		t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
 	}
@@ -981,7 +993,7 @@
 		{
 			name:     "in out dir",
 			buildDir: "out",
-			src:      "out/a/b/c",
+			src:      "out/soong/a/b/c",
 			err:      "is in output",
 		},
 	}
@@ -1125,6 +1137,12 @@
 	rels []string
 	src  string
 	rel  string
+
+	// Make test specific preparations to the test fixture.
+	preparer FixturePreparer
+
+	// A test specific error handler.
+	errorHandler FixtureErrorHandler
 }
 
 func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) {
@@ -1157,14 +1175,23 @@
 				"foo/src_special/$": nil,
 			}
 
+			errorHandler := test.errorHandler
+			if errorHandler == nil {
+				errorHandler = FixtureExpectsNoErrors
+			}
+
 			result := GroupFixturePreparers(
 				FixtureRegisterWithContext(func(ctx RegistrationContext) {
 					ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
 					ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
-					ctx.RegisterModuleType("filegroup", FileGroupFactory)
 				}),
+				PrepareForTestWithFilegroup,
+				PrepareForTestWithNamespace,
 				mockFS.AddToFixture(),
-			).RunTest(t)
+				OptionalFixturePreparer(test.preparer),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTest(t)
 
 			m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
 
@@ -1333,6 +1360,73 @@
 			src: "foo/src_special/$",
 			rel: "src_special/$",
 		},
+		{
+			// This test makes sure that an unqualified module name cannot contain characters that make
+			// it appear as a qualified module name.
+			name: "output file provider, invalid fully qualified name",
+			bp: `
+			test {
+				name: "foo",
+				src: "://other:b",
+				srcs: ["://other:c"],
+			}`,
+			preparer: FixtureAddTextFile("other/Android.bp", `
+				soong_namespace {}
+
+				output_file_provider {
+					name: "b",
+					outs: ["gen/b"],
+				}
+
+				output_file_provider {
+					name: "c",
+					outs: ["gen/c"],
+				}
+			`),
+			src:  "foo/:/other:b",
+			rel:  ":/other:b",
+			srcs: []string{"foo/:/other:c"},
+			rels: []string{":/other:c"},
+		},
+		{
+			name: "output file provider, missing fully qualified name",
+			bp: `
+			test {
+				name: "foo",
+				src: "//other:b",
+				srcs: ["//other:c"],
+			}`,
+			errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{
+				`"foo" depends on undefined module "//other:b"`,
+				`"foo" depends on undefined module "//other:c"`,
+			}),
+		},
+		{
+			name: "output file provider, fully qualified name",
+			bp: `
+			test {
+				name: "foo",
+				src: "//other:b",
+				srcs: ["//other:c"],
+			}`,
+			src:  "out/soong/.intermediates/other/b/gen/b",
+			rel:  "gen/b",
+			srcs: []string{"out/soong/.intermediates/other/c/gen/c"},
+			rels: []string{"gen/c"},
+			preparer: FixtureAddTextFile("other/Android.bp", `
+				soong_namespace {}
+
+				output_file_provider {
+					name: "b",
+					outs: ["gen/b"],
+				}
+
+				output_file_provider {
+					name: "c",
+					outs: ["gen/c"],
+				}
+			`),
+		},
 	}
 
 	testPathForModuleSrc(t, tests)
@@ -1392,7 +1486,8 @@
 		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
 	})
 	t.Run("install for make", func(t *testing.T) {
-		p := PathForModuleInstall(ctx, "install/path").ToMakePath()
+		p := PathForModuleInstall(ctx, "install/path")
+		p.makePath = true
 		AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
 	})
 	t.Run("output", func(t *testing.T) {
@@ -1406,14 +1501,12 @@
 	t.Run("mixture", func(t *testing.T) {
 		paths := Paths{
 			PathForModuleInstall(ctx, "install/path"),
-			PathForModuleInstall(ctx, "install/path").ToMakePath(),
 			PathForOutput(ctx, "output/path"),
 			PathForSource(ctx, "source/path"),
 		}
 
 		expected := []string{
 			"out/soong/target/product/test_device/system/install/path",
-			"out/target/product/test_device/system/install/path",
 			"out/soong/output/path",
 			"source/path",
 		}
@@ -1431,7 +1524,7 @@
 	fmt.Println(p.Rel(), p2.Rel())
 
 	// Output:
-	// out/system/framework/boot.art out/system/framework/boot.oat
+	// out/soong/system/framework/boot.art out/soong/system/framework/boot.oat
 	// boot.art boot.oat
 }
 
@@ -1445,7 +1538,7 @@
 	fmt.Println(p.Rel(), p2.Rel())
 
 	// Output:
-	// out/system/framework/boot.art out/system/framework/oat/arm/boot.vdex
+	// out/soong/system/framework/boot.art out/soong/system/framework/oat/arm/boot.vdex
 	// boot.art oat/arm/boot.vdex
 }
 
diff --git a/android/prebuilt.go b/android/prebuilt.go
index f3493bd..5843487 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -61,6 +61,16 @@
 	// a matching name.
 	Prefer *bool `android:"arch_variant"`
 
+	// When specified this names a Soong config variable that controls the prefer property.
+	//
+	// If the value of the named Soong config variable is true then prefer is set to false and vice
+	// versa. If the Soong config variable is not set then it defaults to false, so prefer defaults
+	// to true.
+	//
+	// If specified then the prefer property is ignored in favor of the value of the Soong config
+	// variable.
+	Use_source_config_var *ConfigVarProperties
+
 	SourceExists bool `blueprint:"mutated"`
 	UsePrebuilt  bool `blueprint:"mutated"`
 
@@ -68,10 +78,29 @@
 	PrebuiltRenamedToSource bool `blueprint:"mutated"`
 }
 
+// Properties that can be used to select a Soong config variable.
+type ConfigVarProperties struct {
+	// Allow instances of this struct to be used as a property value in a BpPropertySet.
+	BpPrintableBase
+
+	// The name of the configuration namespace.
+	//
+	// As passed to add_soong_config_namespace in Make.
+	Config_namespace *string
+
+	// The name of the configuration variable.
+	//
+	// As passed to add_soong_config_var_value in Make.
+	Var_name *string
+}
+
 type Prebuilt struct {
 	properties PrebuiltProperties
 
-	srcsSupplier     PrebuiltSrcsSupplier
+	// nil if the prebuilt has no srcs property at all. See InitPrebuiltModuleWithoutSrcs.
+	srcsSupplier PrebuiltSrcsSupplier
+
+	// "-" if the prebuilt has no srcs property at all. See InitPrebuiltModuleWithoutSrcs.
 	srcsPropertyName string
 }
 
@@ -151,6 +180,22 @@
 // Return the src value or nil if it is not available.
 type PrebuiltSrcsSupplier func(ctx BaseModuleContext, prebuilt Module) []string
 
+func initPrebuiltModuleCommon(module PrebuiltInterface) *Prebuilt {
+	p := module.Prebuilt()
+	module.AddProperties(&p.properties)
+	return p
+}
+
+// Initialize the module as a prebuilt module that has no dedicated property that lists its
+// sources. SingleSourcePathFromSupplier should not be called for this module.
+//
+// This is the case e.g. for header modules, which provides the headers in source form
+// regardless whether they are prebuilt or not.
+func InitPrebuiltModuleWithoutSrcs(module PrebuiltInterface) {
+	p := initPrebuiltModuleCommon(module)
+	p.srcsPropertyName = "-"
+}
+
 // Initialize the module as a prebuilt module that uses the provided supplier to access the
 // prebuilt sources of the module.
 //
@@ -164,10 +209,6 @@
 // The provided property name is used to provide helpful error messages in the event that
 // a problem arises, e.g. calling SingleSourcePath() when more than one source is provided.
 func InitPrebuiltModuleWithSrcSupplier(module PrebuiltInterface, srcsSupplier PrebuiltSrcsSupplier, srcsPropertyName string) {
-	p := module.Prebuilt()
-	module.AddProperties(&p.properties)
-	module.base().customizableProperties = module.GetProperties()
-
 	if srcsSupplier == nil {
 		panic(fmt.Errorf("srcsSupplier must not be nil"))
 	}
@@ -175,6 +216,7 @@
 		panic(fmt.Errorf("srcsPropertyName must not be empty"))
 	}
 
+	p := initPrebuiltModuleCommon(module)
 	p.srcsSupplier = srcsSupplier
 	p.srcsPropertyName = srcsPropertyName
 }
@@ -214,7 +256,7 @@
 			value = value.Elem()
 		}
 		if value.Kind() != reflect.String {
-			panic(fmt.Errorf("prebuilt src field %q should be a string or a pointer to one but was %d %q", srcPropertyName, value.Kind(), value))
+			panic(fmt.Errorf("prebuilt src field %q in %T in module %s should be a string or a pointer to one but was %v", srcField, srcProps, module, value))
 		}
 		src := value.String()
 		if src == "" {
@@ -267,6 +309,54 @@
 	return nil
 }
 
+// PrebuiltGetPreferred returns the module that is preferred for the given
+// module. That is either the module itself or the prebuilt counterpart that has
+// taken its place. The given module must be a direct dependency of the current
+// context module, and it must be the source module if both source and prebuilt
+// exist.
+//
+// This function is for use on dependencies after PrebuiltPostDepsMutator has
+// run - any dependency that is registered before that will already reference
+// the right module. This function is only safe to call after all mutators that
+// may call CreateVariations, e.g. in GenerateAndroidBuildActions.
+func PrebuiltGetPreferred(ctx BaseModuleContext, module Module) Module {
+	if !module.IsReplacedByPrebuilt() {
+		return module
+	}
+	if IsModulePrebuilt(module) {
+		// If we're given a prebuilt then assume there's no source module around.
+		return module
+	}
+
+	sourceModDepFound := false
+	var prebuiltMod Module
+
+	ctx.WalkDeps(func(child, parent Module) bool {
+		if prebuiltMod != nil {
+			return false
+		}
+		if parent == ctx.Module() {
+			// First level: Only recurse if the module is found as a direct dependency.
+			sourceModDepFound = child == module
+			return sourceModDepFound
+		}
+		// Second level: Follow PrebuiltDepTag to the prebuilt.
+		if t := ctx.OtherModuleDependencyTag(child); t == PrebuiltDepTag {
+			prebuiltMod = child
+		}
+		return false
+	})
+
+	if prebuiltMod == nil {
+		if !sourceModDepFound {
+			panic(fmt.Errorf("Failed to find source module as a direct dependency: %s", module))
+		} else {
+			panic(fmt.Errorf("Failed to find prebuilt for source module: %s", module))
+		}
+	}
+	return prebuiltMod
+}
+
 func RegisterPrebuiltsPreArchMutators(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("prebuilt_rename", PrebuiltRenameMutator).Parallel()
 }
@@ -310,7 +400,7 @@
 func PrebuiltSelectModuleMutator(ctx TopDownMutatorContext) {
 	m := ctx.Module()
 	if p := GetEmbeddedPrebuilt(m); p != nil {
-		if p.srcsSupplier == nil {
+		if p.srcsSupplier == nil && p.srcsPropertyName == "" {
 			panic(fmt.Errorf("prebuilt module did not have InitPrebuiltModule called on it"))
 		}
 		if !p.properties.SourceExists {
@@ -364,12 +454,18 @@
 		return false
 	}
 
-	// TODO: use p.Properties.Name and ctx.ModuleDir to override preference
-	if Bool(p.properties.Prefer) {
+	// If source is not available or is disabled then always use the prebuilt.
+	if source == nil || !source.Enabled() {
 		return true
 	}
 
-	return source == nil || !source.Enabled()
+	// If the use_source_config_var property is set then it overrides the prefer property setting.
+	if configVar := p.properties.Use_source_config_var; configVar != nil {
+		return !ctx.Config().VendorConfig(proptools.String(configVar.Config_namespace)).Bool(proptools.String(configVar.Var_name))
+	}
+
+	// TODO: use p.Properties.Name and ctx.ModuleDir to override preference
+	return Bool(p.properties.Prefer)
 }
 
 func (p *Prebuilt) SourceExists() bool {
diff --git a/android/prebuilt_build_tool.go b/android/prebuilt_build_tool.go
index 516d042..e5edf91 100644
--- a/android/prebuilt_build_tool.go
+++ b/android/prebuilt_build_tool.go
@@ -86,7 +86,7 @@
 
 func (t *prebuiltBuildTool) MakeVars(ctx MakeVarsModuleContext) {
 	if makeVar := String(t.properties.Export_to_make_var); makeVar != "" {
-		if t.Target().Os != BuildOs {
+		if t.Target().Os != ctx.Config().BuildOS {
 			return
 		}
 		ctx.StrictRaw(makeVar, t.toolPath.String())
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 23524a5..fa40d1f 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -21,279 +21,362 @@
 	"github.com/google/blueprint"
 )
 
-var prebuiltsTests = []struct {
-	name      string
-	replaceBp bool // modules is added to default bp boilerplate if false.
-	modules   string
-	prebuilt  []OsType
-}{
-	{
-		name: "no prebuilt",
-		modules: `
-			source {
-				name: "bar",
-			}`,
-		prebuilt: nil,
-	},
-	{
-		name: "no source prebuilt not preferred",
-		modules: `
-			prebuilt {
-				name: "bar",
-				prefer: false,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name: "no source prebuilt preferred",
-		modules: `
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name: "prebuilt not preferred",
-		modules: `
-			source {
-				name: "bar",
-			}
+func TestPrebuilts(t *testing.T) {
+	buildOS := TestArchConfig(t.TempDir(), nil, "", nil).BuildOS
 
-			prebuilt {
-				name: "bar",
-				prefer: false,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: nil,
-	},
-	{
-		name: "prebuilt preferred",
-		modules: `
-			source {
-				name: "bar",
-			}
+	var prebuiltsTests = []struct {
+		name      string
+		replaceBp bool // modules is added to default bp boilerplate if false.
+		modules   string
+		prebuilt  []OsType
+		preparer  FixturePreparer
+	}{
+		{
+			name: "no prebuilt",
+			modules: `
+				source {
+					name: "bar",
+				}`,
+			prebuilt: nil,
+		},
+		{
+			name: "no source prebuilt not preferred",
+			modules: `
+				prebuilt {
+					name: "bar",
+					prefer: false,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "no source prebuilt preferred",
+			modules: `
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt not preferred",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name: "prebuilt no file not preferred",
-		modules: `
-			source {
-				name: "bar",
-			}
+				prebuilt {
+					name: "bar",
+					prefer: false,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: nil,
+		},
+		{
+			name: "prebuilt preferred",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			prebuilt {
-				name: "bar",
-				prefer: false,
-			}`,
-		prebuilt: nil,
-	},
-	{
-		name: "prebuilt no file preferred",
-		modules: `
-			source {
-				name: "bar",
-			}
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt no file not preferred",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			prebuilt {
-				name: "bar",
-				prefer: true,
-			}`,
-		prebuilt: nil,
-	},
-	{
-		name: "prebuilt file from filegroup preferred",
-		modules: `
-			filegroup {
-				name: "fg",
-				srcs: ["prebuilt_file"],
-			}
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: [":fg"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name: "prebuilt module for device only",
-		modules: `
-			source {
-				name: "bar",
-			}
+				prebuilt {
+					name: "bar",
+					prefer: false,
+				}`,
+			prebuilt: nil,
+		},
+		{
+			name: "prebuilt no file preferred",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			prebuilt {
-				name: "bar",
-				host_supported: false,
-				prefer: true,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android},
-	},
-	{
-		name: "prebuilt file for host only",
-		modules: `
-			source {
-				name: "bar",
-			}
+				prebuilt {
+					name: "bar",
+					prefer: true,
+				}`,
+			prebuilt: nil,
+		},
+		{
+			name: "prebuilt file from filegroup preferred",
+			modules: `
+				filegroup {
+					name: "fg",
+					srcs: ["prebuilt_file"],
+				}
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: [":fg"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt module for device only",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				target: {
-					host: {
-						srcs: ["prebuilt_file"],
-					},
-				},
-			}`,
-		prebuilt: []OsType{BuildOs},
-	},
-	{
-		name: "prebuilt override not preferred",
-		modules: `
-			source {
-				name: "baz",
-			}
+				prebuilt {
+					name: "bar",
+					host_supported: false,
+					prefer: true,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android},
+		},
+		{
+			name: "prebuilt file for host only",
+			modules: `
+				source {
+					name: "bar",
+				}
 
-			override_source {
-				name: "bar",
-				base: "baz",
-			}
-
-			prebuilt {
-				name: "bar",
-				prefer: false,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: nil,
-	},
-	{
-		name: "prebuilt override preferred",
-		modules: `
-			source {
-				name: "baz",
-			}
-
-			override_source {
-				name: "bar",
-				base: "baz",
-			}
-
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name:      "prebuilt including default-disabled OS",
-		replaceBp: true,
-		modules: `
-			source {
-				name: "foo",
-				deps: [":bar"],
-				target: {
-					windows: {
-						enabled: true,
-					},
-				},
-			}
-
-			source {
-				name: "bar",
-				target: {
-					windows: {
-						enabled: true,
-					},
-				},
-			}
-
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-				target: {
-					windows: {
-						enabled: true,
-					},
-				},
-			}`,
-		prebuilt: []OsType{Android, BuildOs, Windows},
-	},
-	{
-		name:      "fall back to source for default-disabled OS",
-		replaceBp: true,
-		modules: `
-			source {
-				name: "foo",
-				deps: [":bar"],
-				target: {
-					windows: {
-						enabled: true,
-					},
-				},
-			}
-
-			source {
-				name: "bar",
-				target: {
-					windows: {
-						enabled: true,
-					},
-				},
-			}
-
-			prebuilt {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-	{
-		name:      "prebuilt properties customizable",
-		replaceBp: true,
-		modules: `
-			source {
-				name: "foo",
-				deps: [":bar"],
-			}
-
-			soong_config_module_type {
-				name: "prebuilt_with_config",
-				module_type: "prebuilt",
-				config_namespace: "any_namespace",
-				bool_variables: ["bool_var"],
-				properties: ["prefer"],
-			}
-
-			prebuilt_with_config {
-				name: "bar",
-				prefer: true,
-				srcs: ["prebuilt_file"],
-				soong_config_variables: {
-					bool_var: {
-						prefer: false,
-						conditions_default: {
-							prefer: true,
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					target: {
+						host: {
+							srcs: ["prebuilt_file"],
 						},
 					},
-				},
-			}`,
-		prebuilt: []OsType{Android, BuildOs},
-	},
-}
+				}`,
+			prebuilt: []OsType{buildOS},
+		},
+		{
+			name: "prebuilt override not preferred",
+			modules: `
+				source {
+					name: "baz",
+				}
 
-func TestPrebuilts(t *testing.T) {
+				override_source {
+					name: "bar",
+					base: "baz",
+				}
+
+				prebuilt {
+					name: "bar",
+					prefer: false,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: nil,
+		},
+		{
+			name: "prebuilt override preferred",
+			modules: `
+				source {
+					name: "baz",
+				}
+
+				override_source {
+					name: "bar",
+					base: "baz",
+				}
+
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name:      "prebuilt including default-disabled OS",
+			replaceBp: true,
+			modules: `
+				source {
+					name: "foo",
+					deps: [":bar"],
+					target: {
+						windows: {
+							enabled: true,
+						},
+					},
+				}
+
+				source {
+					name: "bar",
+					target: {
+						windows: {
+							enabled: true,
+						},
+					},
+				}
+
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+					target: {
+						windows: {
+							enabled: true,
+						},
+					},
+				}`,
+			prebuilt: []OsType{Android, buildOS, Windows},
+		},
+		{
+			name:      "fall back to source for default-disabled OS",
+			replaceBp: true,
+			modules: `
+				source {
+					name: "foo",
+					deps: [":bar"],
+					target: {
+						windows: {
+							enabled: true,
+						},
+					},
+				}
+
+				source {
+					name: "bar",
+					target: {
+						windows: {
+							enabled: true,
+						},
+					},
+				}
+
+				prebuilt {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name:      "prebuilt properties customizable",
+			replaceBp: true,
+			modules: `
+				source {
+					name: "foo",
+					deps: [":bar"],
+				}
+
+				soong_config_module_type {
+					name: "prebuilt_with_config",
+					module_type: "prebuilt",
+					config_namespace: "any_namespace",
+					bool_variables: ["bool_var"],
+					properties: ["prefer"],
+				}
+
+				prebuilt_with_config {
+					name: "bar",
+					prefer: true,
+					srcs: ["prebuilt_file"],
+					soong_config_variables: {
+						bool_var: {
+							prefer: false,
+							conditions_default: {
+								prefer: true,
+							},
+						},
+					},
+				}`,
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt use_source_config_var={acme, use_source} - no var specified",
+			modules: `
+				source {
+					name: "bar",
+				}
+
+				prebuilt {
+					name: "bar",
+					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
+					srcs: ["prebuilt_file"],
+				}`,
+			// When use_source_env is specified then it will use the prebuilt by default if the environment
+			// variable is not set.
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=false",
+			modules: `
+				source {
+					name: "bar",
+				}
+
+				prebuilt {
+					name: "bar",
+					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
+					srcs: ["prebuilt_file"],
+				}`,
+			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+				variables.VendorVars = map[string]map[string]string{
+					"acme": {
+						"use_source": "false",
+					},
+				}
+			}),
+			// Setting the environment variable named in use_source_env to false will cause the prebuilt to
+			// be used.
+			prebuilt: []OsType{Android, buildOS},
+		},
+		{
+			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true",
+			modules: `
+				source {
+					name: "bar",
+				}
+
+				prebuilt {
+					name: "bar",
+					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
+					srcs: ["prebuilt_file"],
+				}`,
+			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+				variables.VendorVars = map[string]map[string]string{
+					"acme": {
+						"use_source": "true",
+					},
+				}
+			}),
+			// Setting the environment variable named in use_source_env to true will cause the source to be
+			// used.
+			prebuilt: nil,
+		},
+		{
+			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true, no source",
+			modules: `
+				prebuilt {
+					name: "bar",
+					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
+					srcs: ["prebuilt_file"],
+				}`,
+			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+				variables.VendorVars = map[string]map[string]string{
+					"acme": {
+						"use_source": "true",
+					},
+				}
+			}),
+			// Although the environment variable says to use source there is no source available.
+			prebuilt: []OsType{Android, buildOS},
+		},
+	}
+
 	fs := MockFS{
 		"prebuilt_file": nil,
 		"source_file":   nil,
@@ -329,6 +412,7 @@
 				}),
 				fs.AddToFixture(),
 				FixtureRegisterWithContext(registerTestPrebuiltModules),
+				OptionalFixturePreparer(test.preparer),
 			).RunTestWithBp(t, bp)
 
 			for _, variant := range result.ModuleVariantsForTests("foo") {
@@ -426,9 +510,9 @@
 	ctx.RegisterModuleType("prebuilt", newPrebuiltModule)
 	ctx.RegisterModuleType("source", newSourceModule)
 	ctx.RegisterModuleType("override_source", newOverrideSourceModule)
-	ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-	ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-	ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+	ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 }
 
 type prebuiltModule struct {
diff --git a/android/proto.go b/android/proto.go
index 0be7893..c3759f8 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -15,12 +15,26 @@
 package android
 
 import (
+	"android/soong/bazel"
+	"regexp"
 	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
+const (
+	canonicalPathFromRootDefault = true
+)
+
+var (
+	// ignoring case, checks for proto or protos as an independent word in the name, whether at the
+	// beginning, end, or middle. e.g. "proto.foo", "bar-protos", "baz_proto_srcs" would all match
+	filegroupLikelyProtoPattern = regexp.MustCompile("(?i)(^|[^a-z])proto(s)?([^a-z]|$)")
+
+	ProtoSrcLabelPartition = bazel.LabelPartition{Extensions: []string{".proto"}, LabelMapper: isProtoFilegroup}
+)
+
 // TODO(ccross): protos are often used to communicate between multiple modules.  If the only
 // way to convert a proto to source is to reference it as a source file, and external modules cannot
 // reference source files in other modules, then every module that owns a proto file will need to
@@ -90,7 +104,7 @@
 		Flags:                 flags,
 		Deps:                  deps,
 		OutTypeFlag:           protoOutFlag,
-		CanonicalPathFromRoot: proptools.BoolDefault(p.Proto.Canonical_path_from_root, true),
+		CanonicalPathFromRoot: proptools.BoolDefault(p.Proto.Canonical_path_from_root, canonicalPathFromRootDefault),
 		Dir:                   PathForModuleGen(ctx, "proto"),
 		SubDir:                PathForModuleGen(ctx, "proto", ctx.ModuleDir()),
 	}
@@ -146,3 +160,66 @@
 	rule.Command().
 		BuiltTool("dep_fixer").Flag(depFile.String())
 }
+
+// Bp2buildProtoInfo contains information necessary to pass on to language specific conversion.
+type Bp2buildProtoInfo struct {
+	Type *string
+	Name string
+}
+
+type protoAttrs struct {
+	Srcs                bazel.LabelListAttribute
+	Strip_import_prefix *string
+}
+
+// Bp2buildProtoProperties converts proto properties, creating a proto_library and returning the
+// information necessary for language-specific handling.
+func Bp2buildProtoProperties(ctx Bp2buildMutatorContext, m *ModuleBase, srcs bazel.LabelListAttribute) (Bp2buildProtoInfo, bool) {
+	var info Bp2buildProtoInfo
+	if srcs.IsEmpty() {
+		return info, false
+	}
+
+	info.Name = m.Name() + "_proto"
+	attrs := protoAttrs{
+		Srcs: srcs,
+	}
+
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &ProtoProperties{}) {
+		for _, rawProps := range configToProps {
+			var props *ProtoProperties
+			var ok bool
+			if props, ok = rawProps.(*ProtoProperties); !ok {
+				ctx.ModuleErrorf("Could not cast ProtoProperties to expected type")
+			}
+			if axis == bazel.NoConfigAxis {
+				info.Type = props.Proto.Type
+
+				if !proptools.BoolDefault(props.Proto.Canonical_path_from_root, canonicalPathFromRootDefault) {
+					// an empty string indicates to strips the package path
+					path := ""
+					attrs.Strip_import_prefix = &path
+				}
+			} else if props.Proto.Type != info.Type && props.Proto.Type != nil {
+				ctx.ModuleErrorf("Cannot handle arch-variant types for protos at this time.")
+			}
+		}
+	}
+
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
+		CommonAttributes{Name: info.Name},
+		&attrs)
+
+	return info, true
+}
+
+func isProtoFilegroup(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
+	m, exists := ctx.ModuleFromName(label.OriginalModuleName)
+	labelStr := label.Label
+	if !exists || !IsFilegroup(ctx, m) {
+		return labelStr, false
+	}
+	likelyProtos := filegroupLikelyProtoPattern.MatchString(label.OriginalModuleName)
+	return labelStr, likelyProtos
+}
diff --git a/android/queryview.go b/android/queryview.go
deleted file mode 100644
index 224652e..0000000
--- a/android/queryview.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/google/blueprint"
-)
-
-// The Bazel QueryView singleton is responsible for generating the Ninja actions
-// for calling the soong_build primary builder in the main build.ninja file.
-func init() {
-	RegisterSingletonType("bazel_queryview", BazelQueryViewSingleton)
-}
-
-// BazelQueryViewSingleton is the singleton responsible for registering the
-// soong_build build statement that will convert the Soong module graph after
-// applying *all* mutators, enabing the feature to query the final state of the
-// Soong graph. This mode is meant for querying the build graph state, and not meant
-// for generating BUILD files to be checked in.
-func BazelQueryViewSingleton() Singleton {
-	return &bazelQueryViewSingleton{}
-}
-
-// BazelConverterSingleton is the singleton responsible for registering the soong_build
-// build statement that will convert the Soong module graph by applying an alternate
-// pipeline of mutators, with the goal of reaching semantic equivalence between the original
-// Blueprint and final BUILD files. Using this mode, the goal is to be able to
-// build with these BUILD files directly in the source tree.
-func BazelConverterSingleton() Singleton {
-	return &bazelConverterSingleton{}
-}
-
-type bazelQueryViewSingleton struct{}
-type bazelConverterSingleton struct{}
-
-func generateBuildActionsForBazelConversion(ctx SingletonContext, converterMode bool) {
-	name := "queryview"
-	descriptionTemplate := "[EXPERIMENTAL, PRE-PRODUCTION] Creating the Bazel QueryView workspace with %s at $outDir"
-
-	// Create a build and rule statement, using the Bazel QueryView's WORKSPACE
-	// file as the output file marker.
-	var deps Paths
-	moduleListFilePath := pathForBuildToolDep(ctx, ctx.Config().moduleListFile)
-	deps = append(deps, moduleListFilePath)
-	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().ProductVariablesFileName))
-
-	bazelQueryViewDirectory := PathForOutput(ctx, name)
-	bazelQueryViewWorkspaceFile := bazelQueryViewDirectory.Join(ctx, "WORKSPACE")
-	primaryBuilder := primaryBuilderPath(ctx)
-	bazelQueryView := ctx.Rule(pctx, "bazelQueryView",
-		blueprint.RuleParams{
-			Command: fmt.Sprintf(
-				`rm -rf "${outDir}/"* && `+
-					`mkdir -p "${outDir}" && `+
-					`echo WORKSPACE: $$(cat "%s") > "${outDir}/.queryview-depfile.d" && `+
-					`BUILDER="%s" && `+
-					`echo BUILDER=$$BUILDER && `+
-					`cd "$$(dirname "$$BUILDER")" && `+
-					`echo PWD=$$PWD && `+
-					`ABSBUILDER="$$PWD/$$(basename "$$BUILDER")" && `+
-					`echo ABSBUILDER=$$ABSBUILDER && `+
-					`cd / && `+
-					`env -i "$$ABSBUILDER" --bazel_queryview_dir "${outDir}" "%s"`,
-				moduleListFilePath.String(), // Use the contents of Android.bp.list as the depfile.
-				primaryBuilder.String(),
-				strings.Join(os.Args[1:], "\" \""),
-			),
-			CommandDeps: []string{primaryBuilder.String()},
-			Description: fmt.Sprintf(
-				descriptionTemplate,
-				primaryBuilder.Base()),
-			Deps:    blueprint.DepsGCC,
-			Depfile: "${outDir}/.queryview-depfile.d",
-		},
-		"outDir")
-
-	ctx.Build(pctx, BuildParams{
-		Rule:   bazelQueryView,
-		Output: bazelQueryViewWorkspaceFile,
-		Inputs: deps,
-		Args: map[string]string{
-			"outDir": bazelQueryViewDirectory.String(),
-		},
-	})
-
-	// Add a phony target for generating the workspace
-	ctx.Phony(name, bazelQueryViewWorkspaceFile)
-}
-
-func (c *bazelQueryViewSingleton) GenerateBuildActions(ctx SingletonContext) {
-	generateBuildActionsForBazelConversion(ctx, false)
-}
-
-func (c *bazelConverterSingleton) GenerateBuildActions(ctx SingletonContext) {
-	generateBuildActionsForBazelConversion(ctx, true)
-}
diff --git a/android/register.go b/android/register.go
index 4c8088d..c505833 100644
--- a/android/register.go
+++ b/android/register.go
@@ -31,7 +31,7 @@
 type sortableComponent interface {
 	// componentName returns the name of the component.
 	//
-	// Uniquely identifies the components within the set of components used at runtimr and during
+	// Uniquely identifies the components within the set of components used at runtime and during
 	// tests.
 	componentName() string
 
@@ -59,6 +59,7 @@
 
 var moduleTypes []moduleType
 var moduleTypesForDocs = map[string]reflect.Value{}
+var moduleTypeByFactory = map[reflect.Value]string{}
 
 type singleton struct {
 	// True if this should be registered as a pre-singleton, false otherwise.
@@ -140,6 +141,7 @@
 // RegisterModuleType was a lambda.
 func RegisterModuleTypeForDocs(name string, factory reflect.Value) {
 	moduleTypesForDocs[name] = factory
+	moduleTypeByFactory[factory] = name
 }
 
 func RegisterSingletonType(name string, factory SingletonFactory) {
@@ -161,6 +163,10 @@
 	return ctx
 }
 
+func (ctx *Context) SetRunningAsBp2build() {
+	ctx.config.runningAsBp2Build = true
+}
+
 // RegisterForBazelConversion registers an alternate shadow pipeline of
 // singletons, module types and mutators to register for converting Blueprint
 // files to semantically equivalent BUILD files.
@@ -174,13 +180,7 @@
 		t.register(ctx)
 	}
 
-	bp2buildMutatorList := []RegisterMutatorFunc{}
-	for t, f := range bp2buildMutators {
-		ctx.config.bp2buildModuleTypeConfig[t] = true
-		bp2buildMutatorList = append(bp2buildMutatorList, f)
-	}
-
-	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators, bp2buildDepsMutators, bp2buildMutatorList)
+	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
 // Register the pipeline of singletons, module types, and mutators for
@@ -192,15 +192,6 @@
 		t.register(ctx)
 	}
 
-	if ctx.config.BazelContext.BazelEnabled() {
-		// Hydrate the configuration of bp2build-enabled module types. This is
-		// required as a signal to identify which modules should be deferred to
-		// Bazel in mixed builds, if it is enabled.
-		for t, _ := range bp2buildMutators {
-			ctx.config.bp2buildModuleTypeConfig[t] = true
-		}
-	}
-
 	mutators := collateGloballyRegisteredMutators()
 	mutators.registerAll(ctx)
 
@@ -239,6 +230,10 @@
 	return moduleTypesForDocs
 }
 
+func ModuleTypeByFactory() map[reflect.Value]string {
+	return moduleTypeByFactory
+}
+
 // Interface for registering build components.
 //
 // Provided to allow registration of build components to be shared between the runtime
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 2507c4c..11da36c 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -22,9 +22,10 @@
 	"strings"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
 
 	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/remoteexec"
@@ -100,12 +101,7 @@
 }
 
 // Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat.
-//
-// Restat is not compatible with Sbox()
 func (r *RuleBuilder) Restat() *RuleBuilder {
-	if r.sbox {
-		panic("Restat() is not compatible with Sbox()")
-	}
 	r.restat = true
 	return r
 }
@@ -140,8 +136,6 @@
 // point to a location where sbox's manifest will be written and must be outside outputDir. sbox
 // will ensure that all outputs have been written, and will discard any output files that were not
 // specified.
-//
-// Sbox is not compatible with Restat()
 func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
 	if r.sbox {
 		panic("Sbox() may not be called more than once")
@@ -149,9 +143,6 @@
 	if len(r.commands) > 0 {
 		panic("Sbox() may not be called after Command()")
 	}
-	if r.restat {
-		panic("Sbox() is not compatible with Restat()")
-	}
 	r.sbox = true
 	r.outDir = outputDir
 	r.sboxManifestPath = manifestPath
@@ -469,7 +460,7 @@
 
 func (r *RuleBuilder) depFileMergerCmd(depFiles WritablePaths) *RuleBuilderCommand {
 	return r.Command().
-		BuiltTool("dep_fixer").
+		builtToolWithoutDeps("dep_fixer").
 		Inputs(depFiles.Paths())
 }
 
@@ -621,7 +612,11 @@
 		}
 
 		// Create a rule to write the manifest as a the textproto.
-		WriteFileRule(r.ctx, r.sboxManifestPath, proto.MarshalTextString(&manifest))
+		pbText, err := prototext.Marshal(&manifest)
+		if err != nil {
+			ReportPathErrorf(r.ctx, "sbox manifest failed to marshal: %q", err)
+		}
+		WriteFileRule(r.ctx, r.sboxManifestPath, string(pbText))
 
 		// Generate a new string to use as the command line of the sbox rule.  This uses
 		// a RuleBuilderCommand as a convenience method of building the command line, then
@@ -631,11 +626,14 @@
 				ctx: r.ctx,
 			},
 		}
-		sboxCmd.Text("rm -rf").Output(r.outDir)
-		sboxCmd.Text("&&")
-		sboxCmd.BuiltTool("sbox").
-			Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
-			Flag("--manifest").Input(r.sboxManifestPath)
+		sboxCmd.builtToolWithoutDeps("sbox").
+			FlagWithArg("--sandbox-path ", shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
+			FlagWithArg("--output-dir ", r.outDir.String()).
+			FlagWithInput("--manifest ", r.sboxManifestPath)
+
+		if r.restat {
+			sboxCmd.Flag("--write-if-changed")
+		}
 
 		// Replace the command string, and add the sbox tool and manifest textproto to the
 		// dependencies of the final sbox rule.
@@ -764,16 +762,25 @@
 	paths Paths
 }
 
+func checkPathNotNil(path Path) {
+	if path == nil {
+		panic("rule_builder paths cannot be nil")
+	}
+}
+
 func (c *RuleBuilderCommand) addInput(path Path) string {
+	checkPathNotNil(path)
 	c.inputs = append(c.inputs, path)
 	return c.PathForInput(path)
 }
 
 func (c *RuleBuilderCommand) addImplicit(path Path) {
+	checkPathNotNil(path)
 	c.implicits = append(c.implicits, path)
 }
 
 func (c *RuleBuilderCommand) addOrderOnly(path Path) {
+	checkPathNotNil(path)
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
@@ -819,10 +826,11 @@
 
 func sboxPathForToolRel(ctx BuilderContext, path Path) string {
 	// Errors will be handled in RuleBuilder.Build where we have a context to report them
-	relOut, isRelOut, _ := maybeRelErr(PathForOutput(ctx, "host", ctx.Config().PrebuiltOS()).String(), path.String())
-	if isRelOut {
-		// The tool is in the output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
-		return filepath.Join(sboxToolsSubDir, "out", relOut)
+	toolDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "", false)
+	relOutSoong, isRelOutSoong, _ := maybeRelErr(toolDir.String(), path.String())
+	if isRelOutSoong {
+		// The tool is in the Soong output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
+		return filepath.Join(sboxToolsSubDir, "out", relOutSoong)
 	}
 	// The tool is in the source directory, it will be copied to __SBOX_OUT_DIR__/tools/src
 	return filepath.Join(sboxToolsSubDir, "src", path.String())
@@ -999,19 +1007,23 @@
 // Tool adds the specified tool path to the command line.  The path will be also added to the dependencies returned by
 // RuleBuilder.Tools.
 func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c.Text(c.PathForTool(path))
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTools(paths Paths) *RuleBuilderCommand {
-	c.tools = append(c.tools, paths...)
+	for _, path := range paths {
+		c.ImplicitTool(path)
+	}
 	return c
 }
 
@@ -1021,6 +1033,19 @@
 // It is equivalent to:
 //  cmd.Tool(ctx.Config().HostToolPath(ctx, tool))
 func (c *RuleBuilderCommand) BuiltTool(tool string) *RuleBuilderCommand {
+	if c.rule.ctx.Config().UseHostMusl() {
+		// If the host is using musl, assume that the tool was built against musl libc and include
+		// libc_musl.so in the sandbox.
+		// TODO(ccross): if we supported adding new dependencies during GenerateAndroidBuildActions
+		// this could be a dependency + TransitivePackagingSpecs.
+		c.ImplicitTool(c.rule.ctx.Config().HostJNIToolPath(c.rule.ctx, "libc_musl"))
+	}
+	return c.builtToolWithoutDeps(tool)
+}
+
+// builtToolWithoutDeps is similar to BuiltTool, but doesn't add any dependencies.  It is used
+// internally by RuleBuilder for helper tools that are known to be compiled statically.
+func (c *RuleBuilderCommand) builtToolWithoutDeps(tool string) *RuleBuilderCommand {
 	return c.Tool(c.rule.ctx.Config().HostToolPath(c.rule.ctx, tool))
 }
 
@@ -1088,6 +1113,7 @@
 // Validation adds the specified input path to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validation(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.validations = append(c.validations, path)
 	return c
 }
@@ -1095,13 +1121,16 @@
 // Validations adds the specified input paths to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validations(paths Paths) *RuleBuilderCommand {
-	c.validations = append(c.validations, paths...)
+	for _, path := range paths {
+		c.Validation(path)
+	}
 	return c
 }
 
 // Output adds the specified output path to the command line.  The path will also be added to the outputs returned by
 // RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.outputs = append(c.outputs, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1128,6 +1157,7 @@
 // line, and causes RuleBuilder.Build file to set the depfile flag for ninja.  If multiple depfiles are added to
 // commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
 func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.depFiles = append(c.depFiles, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1150,6 +1180,7 @@
 // will be a symlink instead of a regular file. Does not modify the command
 // line.
 func (c *RuleBuilderCommand) ImplicitSymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.ImplicitOutput(path)
 }
@@ -1167,6 +1198,7 @@
 // SymlinkOutput declares the specified path as an output that will be a symlink
 // instead of a regular file. Modifies the command line.
 func (c *RuleBuilderCommand) SymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.Output(path)
 }
@@ -1266,7 +1298,7 @@
 	t.Helper()
 	content := ContentFromFileRuleForTests(t, params)
 	manifest := sbox_proto.Manifest{}
-	err := proto.UnmarshalText(content, &manifest)
+	err := prototext.Unmarshal([]byte(content), &manifest)
 	if err != nil {
 		t.Fatalf("failed to unmarshal manifest: %s", err.Error())
 	}
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index feee90f..86647eb 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -64,10 +64,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "ld a.o b.o -o out/linked && echo success"
+	// commands: "ld a.o b.o -o out/soong/linked && echo success"
 	// tools: ["ld"]
 	// inputs: ["a.o" "b.o"]
-	// outputs: ["out/linked"]
+	// outputs: ["out/soong/linked"]
 }
 
 func ExampleRuleBuilder_SymlinkOutputs() {
@@ -79,7 +79,7 @@
 		Tool(PathForSource(ctx, "ln")).
 		FlagWithInput("-s ", PathForTesting("a.o")).
 		SymlinkOutput(PathForOutput(ctx, "a"))
-	rule.Command().Text("cp out/a out/b").
+	rule.Command().Text("cp out/soong/a out/soong/b").
 		ImplicitSymlinkOutput(PathForOutput(ctx, "b"))
 
 	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
@@ -89,11 +89,11 @@
 	fmt.Printf("symlink_outputs: %q\n", rule.SymlinkOutputs())
 
 	// Output:
-	// commands: "ln -s a.o out/a && cp out/a out/b"
+	// commands: "ln -s a.o out/soong/a && cp out/soong/a out/soong/b"
 	// tools: ["ln"]
 	// inputs: ["a.o"]
-	// outputs: ["out/a" "out/b"]
-	// symlink_outputs: ["out/a" "out/b"]
+	// outputs: ["out/soong/a" "out/soong/b"]
+	// symlink_outputs: ["out/soong/a" "out/soong/b"]
 }
 
 func ExampleRuleBuilder_Temporary() {
@@ -117,10 +117,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "cp a out/b && cp out/b out/c"
+	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c"
 	// tools: ["cp"]
 	// inputs: ["a"]
-	// outputs: ["out/c"]
+	// outputs: ["out/soong/c"]
 }
 
 func ExampleRuleBuilder_DeleteTemporaryFiles() {
@@ -145,10 +145,10 @@
 	fmt.Printf("outputs: %q\n", rule.Outputs())
 
 	// Output:
-	// commands: "cp a out/b && cp out/b out/c && rm -f out/b"
+	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c && rm -f out/soong/b"
 	// tools: ["cp"]
 	// inputs: ["a"]
-	// outputs: ["out/c"]
+	// outputs: ["out/soong/c"]
 }
 
 func ExampleRuleBuilder_Installs() {
@@ -168,7 +168,7 @@
 	fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String())
 
 	// Output:
-	// rule.Installs().String() = "out/linked:/bin/linked out/linked:/sbin/linked"
+	// rule.Installs().String() = "out/soong/linked:/bin/linked out/soong/linked:/sbin/linked"
 }
 
 func ExampleRuleBuilderCommand() {
@@ -271,7 +271,7 @@
 		FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")).
 		String())
 	// Output:
-	// javac @out/foo.rsp
+	// javac @out/soong/foo.rsp
 }
 
 func ExampleRuleBuilderCommand_String() {
@@ -371,15 +371,15 @@
 		addCommands(rule)
 
 		wantCommands := []string{
-			"out_local/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/module/depfile " +
-				"FlagWithInput=input FlagWithOutput=out_local/module/output FlagWithRspFileInputList=out_local/rsp " +
-				"Input out_local/module/Output out_local/module/SymlinkOutput Text Tool after command2 old cmd",
-			"command2 out_local/module/depfile2 input2 out_local/module/output2 tool2",
-			"command3 input3 out_local/module/output2 out_local/module/output3 input3 out_local/module/output2",
+			"out_local/soong/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/soong/module/depfile " +
+				"FlagWithInput=input FlagWithOutput=out_local/soong/module/output FlagWithRspFileInputList=out_local/soong/rsp " +
+				"Input out_local/soong/module/Output out_local/soong/module/SymlinkOutput Text Tool after command2 old cmd",
+			"command2 out_local/soong/module/depfile2 input2 out_local/soong/module/output2 tool2",
+			"command3 input3 out_local/soong/module/output2 out_local/soong/module/output3 input3 out_local/soong/module/output2",
 		}
 
-		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
-			"out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2"
+		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
+			"out_local/soong/module/DepFile out_local/soong/module/depfile out_local/soong/module/ImplicitDepFile out_local/soong/module/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -403,13 +403,13 @@
 		wantCommands := []string{
 			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
 				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
-				"FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
 				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
 			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
 			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
 		}
 
-		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
+		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -433,7 +433,7 @@
 		wantCommands := []string{
 			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
 				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
-				"FlagWithRspFileInputList=out_local/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
 				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
 			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
 			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
@@ -678,32 +678,32 @@
 	})
 	t.Run("sbox", func(t *testing.T) {
 		outDir := "out/soong/.intermediates/foo_sbox"
-		outFile := filepath.Join(outDir, "gen/foo_sbox")
-		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+		sboxOutDir := filepath.Join(outDir, "gen")
+		outFile := filepath.Join(sboxOutDir, "foo_sbox")
+		depFile := filepath.Join(sboxOutDir, "foo_sbox.d")
 		rspFile := filepath.Join(outDir, "rsp")
 		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
 
-		cmd := `rm -rf ` + outDir + `/gen && ` +
-			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
 		module := result.ModuleForTests("foo_sbox", "")
 		check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
 			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("sbox_inputs", func(t *testing.T) {
 		outDir := "out/soong/.intermediates/foo_sbox_inputs"
-		outFile := filepath.Join(outDir, "gen/foo_sbox_inputs")
-		depFile := filepath.Join(outDir, "gen/foo_sbox_inputs.d")
+		sboxOutDir := filepath.Join(outDir, "gen")
+		outFile := filepath.Join(sboxOutDir, "foo_sbox_inputs")
+		depFile := filepath.Join(sboxOutDir, "foo_sbox_inputs.d")
 		rspFile := filepath.Join(outDir, "rsp")
 		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
 
-		cmd := `rm -rf ` + outDir + `/gen && ` +
-			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
 
 		module := result.ModuleForTests("foo_sbox_inputs", "")
 		check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
diff --git a/android/sdk.go b/android/sdk.go
index 42c8ffa..3a56240 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"fmt"
 	"sort"
 	"strings"
 
@@ -22,22 +23,8 @@
 	"github.com/google/blueprint/proptools"
 )
 
-// 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 {
-	// The set of SDKs required by an APEX and its contents.
-	RequiredSdks() SdkRefs
-}
-
-// Provided to improve code navigation with the IDE.
+// sdkAwareWithoutModule is provided simply to improve code navigation with the IDE.
 type sdkAwareWithoutModule interface {
-	RequiredSdks
-
 	// SdkMemberComponentName will return the name to use for a component of this module based on the
 	// base name of this module.
 	//
@@ -78,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
@@ -147,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
 }
@@ -205,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) {
@@ -233,69 +206,85 @@
 	return false
 }
 
-// Provide support for generating the build rules which will build the snapshot.
+// SnapshotBuilder provides support for generating the build rules which will build the snapshot.
 type SnapshotBuilder interface {
-	// Copy src to the dest (which is a snapshot relative path) and add the dest
-	// to the zip
+	// CopyToSnapshot generates a rule that will copy the src to the dest (which is a snapshot
+	// relative path) and add the dest to the zip.
 	CopyToSnapshot(src Path, dest string)
 
-	// Unzip the supplied zip into the snapshot relative directory destDir.
+	// EmptyFile returns the path to an empty file.
+	//
+	// This can be used by sdk member types that need to create an empty file in the snapshot, simply
+	// pass the value returned from this to the CopyToSnapshot() method.
+	EmptyFile() Path
+
+	// UnzipToSnapshot generates a rule that will unzip the supplied zip into the snapshot relative
+	// directory destDir.
 	UnzipToSnapshot(zipPath Path, destDir string)
 
-	// Add a new prebuilt module to the snapshot. The returned module
-	// must be populated with the module type specific properties. The following
-	// properties will be automatically populated.
+	// AddPrebuiltModule adds a new prebuilt module to the snapshot.
+	//
+	// It is intended to be called from SdkMemberType.AddPrebuiltModule which can add module type
+	// specific properties that are not variant specific. The following properties will be
+	// automatically populated before returning.
 	//
 	// * name
 	// * sdk_member_name
 	// * prefer
 	//
-	// This will result in two Soong modules being generated in the Android. One
-	// that is versioned, coupled to the snapshot version and marked as
-	// prefer=true. And one that is not versioned, not marked as prefer=true and
-	// will only be used if the equivalently named non-prebuilt module is not
-	// present.
+	// Properties that are variant specific will be handled by SdkMemberProperties structure.
+	//
+	// Each module created by this method can be output to the generated Android.bp file in two
+	// different forms, depending on the setting of the SOONG_SDK_SNAPSHOT_VERSION build property.
+	// The two forms are:
+	// 1. A versioned Soong module that is referenced from a corresponding similarly versioned
+	//    snapshot module.
+	// 2. An unversioned Soong module that.
+	//
+	// See sdk/update.go for more information.
 	AddPrebuiltModule(member SdkMember, moduleType string) BpModule
 
-	// The property tag to use when adding a property to a BpModule that contains
-	// references to other sdk members. Using this will ensure that the reference
-	// is correctly output for both versioned and unversioned prebuilts in the
-	// snapshot.
+	// SdkMemberReferencePropertyTag returns a property tag to use when adding a property to a
+	// BpModule that contains references to other sdk members.
 	//
-	// "required: true" means that the property must only contain references
-	// to other members of the sdk. Passing a reference to a module that is not a
-	// member of the sdk will result in a build error.
+	// Using this will ensure that the reference is correctly output for both versioned and
+	// unversioned prebuilts in the snapshot.
 	//
-	// "required: false" means that the property can contain references to modules
-	// that are either members or not members of the sdk. If a reference is to a
-	// module that is a non member then the reference is left unchanged, i.e. it
-	// is not transformed as references to members are.
+	// "required: true" means that the property must only contain references to other members of the
+	// sdk. Passing a reference to a module that is not a member of the sdk will result in a build
+	// error.
 	//
-	// The handling of the member names is dependent on whether it is an internal or
-	// exported member. An exported member is one whose name is specified in one of
-	// the member type specific properties. An internal member is one that is added
-	// due to being a part of an exported (or other internal) member and is not itself
-	// an exported member.
+	// "required: false" means that the property can contain references to modules that are either
+	// members or not members of the sdk. If a reference is to a module that is a non member then the
+	// reference is left unchanged, i.e. it is not transformed as references to members are.
+	//
+	// The handling of the member names is dependent on whether it is an internal or exported member.
+	// An exported member is one whose name is specified in one of the member type specific
+	// properties. An internal member is one that is added due to being a part of an exported (or
+	// other internal) member and is not itself an exported member.
 	//
 	// Member names are handled as follows:
-	// * When creating the unversioned form of the module the name is left unchecked
-	//   unless the member is internal in which case it is transformed into an sdk
-	//   specific name, i.e. by prefixing with the sdk name.
+	// * When creating the unversioned form of the module the name is left unchecked unless the member
+	//   is internal in which case it is transformed into an sdk specific name, i.e. by prefixing with
+	//   the sdk name.
 	//
-	// * When creating the versioned form of the module the name is transformed into
-	//   a versioned sdk specific name, i.e. by prefixing with the sdk name and
-	//   suffixing with the version.
+	// * When creating the versioned form of the module the name is transformed into a versioned sdk
+	//   specific name, i.e. by prefixing with the sdk name and suffixing with the version.
 	//
 	// e.g.
 	// bpPropertySet.AddPropertyWithTag("libs", []string{"member1", "member2"}, builder.SdkMemberReferencePropertyTag(true))
 	SdkMemberReferencePropertyTag(required bool) BpPropertyTag
 }
 
+// BpPropertyTag is a marker interface that can be associated with properties in a BpPropertySet to
+// provide additional information which can be used to customize their behavior.
 type BpPropertyTag interface{}
 
-// A set of properties for use in a .bp file.
+// BpPropertySet is a set of properties for use in a .bp file.
 type BpPropertySet interface {
-	// Add a property, the value can be one of the following types:
+	// AddProperty adds a property.
+	//
+	// The value can be one of the following types:
 	// * string
 	// * array of the above
 	// * bool
@@ -320,18 +309,18 @@
 	// * Otherwise, if a property exists with the name then it is an error.
 	AddProperty(name string, value interface{})
 
-	// Add a property with an associated tag
+	// AddPropertyWithTag adds a property with an associated property tag.
 	AddPropertyWithTag(name string, value interface{}, tag BpPropertyTag)
 
-	// Add a property set with the specified name and return so that additional
-	// properties can be added.
+	// AddPropertySet adds a property set with the specified name and returns it so that additional
+	// properties can be added to it.
 	AddPropertySet(name string) BpPropertySet
 
-	// Add comment for property (or property set).
+	// AddCommentForProperty adds a comment for the named property (or property set).
 	AddCommentForProperty(name, text string)
 }
 
-// A .bp module definition.
+// BpModule represents a module definition in a .bp file.
 type BpModule interface {
 	BpPropertySet
 
@@ -342,19 +331,254 @@
 	Name() string
 }
 
-// An individual member of the SDK, includes all of the variants that the SDK
-// requires.
+// BpPrintable is a marker interface that must be implemented by any struct that is added as a
+// property value.
+type BpPrintable interface {
+	bpPrintable()
+}
+
+// BpPrintableBase must be embedded within any struct that is added as a
+// property value.
+type BpPrintableBase struct {
+}
+
+func (b BpPrintableBase) bpPrintable() {
+}
+
+var _ BpPrintable = BpPrintableBase{}
+
+// sdkRegisterable defines the interface that must be implemented by objects that can be registered
+// in an sdkRegistry.
+type sdkRegisterable interface {
+	// SdkPropertyName returns the name of the corresponding property on an sdk module.
+	SdkPropertyName() string
+}
+
+// sdkRegistry provides support for registering and retrieving objects that define properties for
+// use by sdk and module_exports module types.
+type sdkRegistry struct {
+	// The list of registered objects sorted by property name.
+	list []sdkRegisterable
+}
+
+// copyAndAppend creates a new sdkRegistry that includes all the traits registered in
+// this registry plus the supplied trait.
+func (r *sdkRegistry) copyAndAppend(registerable sdkRegisterable) *sdkRegistry {
+	oldList := r.list
+
+	// Make sure that list does not already contain the property. Uses a simple linear search instead
+	// of a binary search even though the list is sorted. That is because the number of items in the
+	// list is small and so not worth the overhead of a binary search.
+	found := false
+	newPropertyName := registerable.SdkPropertyName()
+	for _, r := range oldList {
+		if r.SdkPropertyName() == newPropertyName {
+			found = true
+			break
+		}
+	}
+	if found {
+		names := []string{}
+		for _, r := range oldList {
+			names = append(names, r.SdkPropertyName())
+		}
+		panic(fmt.Errorf("duplicate properties found, %q already exists in %q", newPropertyName, names))
+	}
+
+	// Copy the slice just in case this is being read while being modified, e.g. when testing.
+	list := make([]sdkRegisterable, 0, len(oldList)+1)
+	list = append(list, oldList...)
+	list = append(list, registerable)
+
+	// Sort the registered objects by their property name to ensure that registry order has no effect
+	// on behavior.
+	sort.Slice(list, func(i1, i2 int) bool {
+		t1 := list[i1]
+		t2 := list[i2]
+
+		return t1.SdkPropertyName() < t2.SdkPropertyName()
+	})
+
+	// Create a new registry so the pointer uniquely identifies the set of registered types.
+	return &sdkRegistry{
+		list: list,
+	}
+}
+
+// registeredObjects returns the list of registered instances.
+func (r *sdkRegistry) registeredObjects() []sdkRegisterable {
+	return r.list
+}
+
+// uniqueOnceKey returns a key that uniquely identifies this instance and can be used with
+// OncePer.Once
+func (r *sdkRegistry) uniqueOnceKey() OnceKey {
+	// Use the pointer to the registry as the unique key. The pointer is used because it is guaranteed
+	// to uniquely identify the contained list. The list itself cannot be used as slices are not
+	// comparable. Using the pointer does mean that two separate registries with identical lists would
+	// have different keys and so cause whatever information is cached to be created multiple times.
+	// However, that is not an issue in practice as it should not occur outside tests. Constructing a
+	// string representation of the list to use instead would avoid that but is an unnecessary
+	// complication that provides no significant benefit.
+	return NewCustomOnceKey(r)
+}
+
+// SdkMemberTrait represents a trait that members of an sdk module can contribute to the sdk
+// snapshot.
+//
+// A trait is simply a characteristic of sdk member that is not required by default which may be
+// required for some members but not others. Traits can cause additional information to be output
+// to the sdk snapshot or replace the default information exported for a member with something else.
+// e.g.
+// * By default cc libraries only export the default image variants to the SDK. However, for some
+//   members it may be necessary to export specific image variants, e.g. vendor, or recovery.
+// * By default cc libraries export all the configured architecture variants except for the native
+//   bridge architecture variants. However, for some members it may be necessary to export the
+//   native bridge architecture variants as well.
+// * By default cc libraries export the platform variant (i.e. sdk:). However, for some members it
+//   may be necessary to export the sdk variant (i.e. sdk:sdk).
+//
+// A sdk can request a module to provide no traits, one trait or a collection of traits. The exact
+// behavior of a trait is determined by how SdkMemberType implementations handle the traits. A trait
+// could be specific to one SdkMemberType or many. Some trait combinations could be incompatible.
+//
+// The sdk module type will create a special traits structure that contains a property for each
+// trait registered with RegisterSdkMemberTrait(). The property names are those returned from
+// SdkPropertyName(). Each property contains a list of modules that are required to have that trait.
+// e.g. something like this:
+//
+//   sdk {
+//     name: "sdk",
+//     ...
+//     traits: {
+//       recovery_image: ["module1", "module4", "module5"],
+//       native_bridge: ["module1", "module2"],
+//       native_sdk: ["module1", "module3"],
+//       ...
+//     },
+//     ...
+//   }
+type SdkMemberTrait interface {
+	// SdkPropertyName returns the name of the traits property on an sdk module.
+	SdkPropertyName() string
+}
+
+var _ sdkRegisterable = (SdkMemberTrait)(nil)
+
+// SdkMemberTraitBase is the base struct that must be embedded within any type that implements
+// SdkMemberTrait.
+type SdkMemberTraitBase struct {
+	// PropertyName is the name of the property
+	PropertyName string
+}
+
+func (b *SdkMemberTraitBase) SdkPropertyName() string {
+	return b.PropertyName
+}
+
+// SdkMemberTraitSet is a set of SdkMemberTrait instances.
+type SdkMemberTraitSet interface {
+	// Empty returns true if this set is empty.
+	Empty() bool
+
+	// Contains returns true if this set contains the specified trait.
+	Contains(trait SdkMemberTrait) bool
+
+	// Subtract returns a new set containing all elements of this set except for those in the
+	// other set.
+	Subtract(other SdkMemberTraitSet) SdkMemberTraitSet
+
+	// String returns a string representation of the set and its contents.
+	String() string
+}
+
+func NewSdkMemberTraitSet(traits []SdkMemberTrait) SdkMemberTraitSet {
+	if len(traits) == 0 {
+		return EmptySdkMemberTraitSet()
+	}
+
+	m := sdkMemberTraitSet{}
+	for _, trait := range traits {
+		m[trait] = true
+	}
+	return m
+}
+
+func EmptySdkMemberTraitSet() SdkMemberTraitSet {
+	return (sdkMemberTraitSet)(nil)
+}
+
+type sdkMemberTraitSet map[SdkMemberTrait]bool
+
+var _ SdkMemberTraitSet = (sdkMemberTraitSet{})
+
+func (s sdkMemberTraitSet) Empty() bool {
+	return len(s) == 0
+}
+
+func (s sdkMemberTraitSet) Contains(trait SdkMemberTrait) bool {
+	return s[trait]
+}
+
+func (s sdkMemberTraitSet) Subtract(other SdkMemberTraitSet) SdkMemberTraitSet {
+	if other.Empty() {
+		return s
+	}
+
+	var remainder []SdkMemberTrait
+	for trait, _ := range s {
+		if !other.Contains(trait) {
+			remainder = append(remainder, trait)
+		}
+	}
+
+	return NewSdkMemberTraitSet(remainder)
+}
+
+func (s sdkMemberTraitSet) String() string {
+	list := []string{}
+	for trait, _ := range s {
+		list = append(list, trait.SdkPropertyName())
+	}
+	sort.Strings(list)
+	return fmt.Sprintf("[%s]", strings.Join(list, ","))
+}
+
+var registeredSdkMemberTraits = &sdkRegistry{}
+
+// RegisteredSdkMemberTraits returns a OnceKey and a sorted list of registered traits.
+//
+// The key uniquely identifies the array of traits and can be used with OncePer.Once() to cache
+// information derived from the array of traits.
+func RegisteredSdkMemberTraits() (OnceKey, []SdkMemberTrait) {
+	registerables := registeredSdkMemberTraits.registeredObjects()
+	traits := make([]SdkMemberTrait, len(registerables))
+	for i, registerable := range registerables {
+		traits[i] = registerable.(SdkMemberTrait)
+	}
+	return registeredSdkMemberTraits.uniqueOnceKey(), traits
+}
+
+// RegisterSdkMemberTrait registers an SdkMemberTrait object to allow them to be used in the
+// module_exports, module_exports_snapshot, sdk and sdk_snapshot module types.
+func RegisterSdkMemberTrait(trait SdkMemberTrait) {
+	registeredSdkMemberTraits = registeredSdkMemberTraits.copyAndAppend(trait)
+}
+
+// SdkMember is an individual member of the SDK.
+//
+// It includes all of the variants that the SDK depends upon.
 type SdkMember interface {
-	// The name of the member.
+	// Name returns the name of the member.
 	Name() string
 
-	// All the variants required by the SDK.
+	// Variants returns all the variants of this module depended upon by the SDK.
 	Variants() []SdkAware
 }
 
-// SdkMemberTypeDependencyTag is the interface that a tag must implement in order to allow the
+// SdkMemberDependencyTag is the interface that a tag must implement in order to allow the
 // dependent module to be automatically added to the sdk.
-type SdkMemberTypeDependencyTag interface {
+type SdkMemberDependencyTag interface {
 	blueprint.DependencyTag
 
 	// SdkMemberType returns the SdkMemberType that will be used to automatically add the child module
@@ -379,7 +603,7 @@
 	ExportMember() bool
 }
 
-var _ SdkMemberTypeDependencyTag = (*sdkMemberDependencyTag)(nil)
+var _ SdkMemberDependencyTag = (*sdkMemberDependencyTag)(nil)
 var _ ReplaceSourceWithPrebuilt = (*sdkMemberDependencyTag)(nil)
 
 type sdkMemberDependencyTag struct {
@@ -396,20 +620,20 @@
 	return t.export
 }
 
-// Prevent dependencies from the sdk/module_exports onto their members from being
-// replaced with a preferred prebuilt.
+// ReplaceSourceWithPrebuilt prevents dependencies from the sdk/module_exports onto their members
+// from being replaced with a preferred prebuilt.
 func (t *sdkMemberDependencyTag) ReplaceSourceWithPrebuilt() bool {
 	return false
 }
 
-// DependencyTagForSdkMemberType creates an SdkMemberTypeDependencyTag that will cause any
+// DependencyTagForSdkMemberType creates an SdkMemberDependencyTag that will cause any
 // dependencies added by the tag to be added to the sdk as the specified SdkMemberType and exported
 // (or not) as specified by the export parameter.
-func DependencyTagForSdkMemberType(memberType SdkMemberType, export bool) SdkMemberTypeDependencyTag {
+func DependencyTagForSdkMemberType(memberType SdkMemberType, export bool) SdkMemberDependencyTag {
 	return &sdkMemberDependencyTag{memberType: memberType, export: export}
 }
 
-// Interface that must be implemented for every type that can be a member of an
+// SdkMemberType is the interface that must be implemented for every type that can be a member of an
 // sdk.
 //
 // The basic implementation should look something like this, where ModuleType is
@@ -430,43 +654,43 @@
 //    ...methods...
 //
 type SdkMemberType interface {
-	// The name of the member type property on an sdk module.
+	// SdkPropertyName returns the name of the member type property on an sdk module.
 	SdkPropertyName() string
 
 	// RequiresBpProperty returns true if this member type requires its property to be usable within
 	// an Android.bp file.
 	RequiresBpProperty() bool
 
-	// True if the member type supports the sdk/sdk_snapshot, false otherwise.
+	// UsableWithSdkAndSdkSnapshot returns true if the member type supports the sdk/sdk_snapshot,
+	// false otherwise.
 	UsableWithSdkAndSdkSnapshot() bool
 
-	// Return true if prebuilt host artifacts may be specific to the host OS. Only
-	// applicable to modules where HostSupported() is true. If this is true,
-	// snapshots will list each host OS variant explicitly and disable all other
-	// host OS'es.
+	// IsHostOsDependent returns true if prebuilt host artifacts may be specific to the host OS. Only
+	// applicable to modules where HostSupported() is true. If this is true, snapshots will list each
+	// host OS variant explicitly and disable all other host OS'es.
 	IsHostOsDependent() bool
 
-	// Add dependencies from the SDK module to all the module variants the member
-	// type contributes to the SDK. `names` is the list of module names given in
-	// the member type property (as returned by SdkPropertyName()) in the SDK
-	// module. The exact set of variants required is determined by the SDK and its
-	// properties. The dependencies must be added with the supplied tag.
+	// AddDependencies adds dependencies from the SDK module to all the module variants the member
+	// type contributes to the SDK. `names` is the list of module names given in the member type
+	// property (as returned by SdkPropertyName()) in the SDK module. The exact set of variants
+	// required is determined by the SDK and its properties. The dependencies must be added with the
+	// supplied tag.
 	//
 	// The BottomUpMutatorContext provided is for the SDK module.
-	AddDependencies(mctx BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string)
+	AddDependencies(ctx SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string)
 
-	// Return true if the supplied module is an instance of this member type.
+	// IsInstance returns true if the supplied module is an instance of this member type.
 	//
-	// This is used to check the type of each variant before added to the
-	// SdkMember. Returning false will cause an error to be logged expaining that
-	// the module is not allowed in whichever sdk property it was added.
+	// This is used to check the type of each variant before added to the SdkMember. Returning false
+	// will cause an error to be logged explaining that the module is not allowed in whichever sdk
+	// property it was added.
 	IsInstance(module Module) bool
 
 	// UsesSourceModuleTypeInSnapshot returns true when the AddPrebuiltModule() method returns a
 	// source module type.
 	UsesSourceModuleTypeInSnapshot() bool
 
-	// Add a prebuilt module that the sdk will populate.
+	// AddPrebuiltModule is called to add a prebuilt module that the sdk will populate.
 	//
 	// The sdk module code generates the snapshot as follows:
 	//
@@ -503,11 +727,32 @@
 	//
 	AddPrebuiltModule(ctx SdkMemberContext, member SdkMember) BpModule
 
-	// Create a structure into which variant specific properties can be added.
+	// CreateVariantPropertiesStruct creates a structure into which variant specific properties can be
+	// added.
 	CreateVariantPropertiesStruct() SdkMemberProperties
+
+	// SupportedTraits returns the set of traits supported by this member type.
+	SupportedTraits() SdkMemberTraitSet
 }
 
-// Base type for SdkMemberType implementations.
+var _ sdkRegisterable = (SdkMemberType)(nil)
+
+// SdkDependencyContext provides access to information needed by the SdkMemberType.AddDependencies()
+// implementations.
+type SdkDependencyContext interface {
+	BottomUpMutatorContext
+
+	// RequiredTraits returns the set of SdkMemberTrait instances that the sdk requires the named
+	// member to provide.
+	RequiredTraits(name string) SdkMemberTraitSet
+
+	// RequiresTrait returns true if the sdk requires the member with the supplied name to provide the
+	// supplied trait.
+	RequiresTrait(name string, trait SdkMemberTrait) bool
+}
+
+// SdkMemberTypeBase is the base type for SdkMemberType implementations and must be embedded in any
+// struct that implements SdkMemberType.
 type SdkMemberTypeBase struct {
 	PropertyName string
 
@@ -522,6 +767,9 @@
 	// module type in its SdkMemberType.AddPrebuiltModule() method. That prevents the sdk snapshot
 	// code from automatically adding a prefer: true flag.
 	UseSourceModuleTypeInSnapshot bool
+
+	// The list of supported traits.
+	Traits []SdkMemberTrait
 }
 
 func (b *SdkMemberTypeBase) SdkPropertyName() string {
@@ -544,73 +792,57 @@
 	return b.UseSourceModuleTypeInSnapshot
 }
 
-// Encapsulates the information about registered SdkMemberTypes.
-type SdkMemberTypesRegistry struct {
-	// The list of types sorted by property name.
-	list []SdkMemberType
-
-	// The key that uniquely identifies this registry instance.
-	key OnceKey
+func (b *SdkMemberTypeBase) SupportedTraits() SdkMemberTraitSet {
+	return NewSdkMemberTraitSet(b.Traits)
 }
 
-func (r *SdkMemberTypesRegistry) copyAndAppend(memberType SdkMemberType) *SdkMemberTypesRegistry {
-	oldList := r.list
+// registeredModuleExportsMemberTypes is the set of registered SdkMemberTypes for module_exports
+// modules.
+var registeredModuleExportsMemberTypes = &sdkRegistry{}
 
-	// Copy the slice just in case this is being read while being modified, e.g. when testing.
-	list := make([]SdkMemberType, 0, len(oldList)+1)
-	list = append(list, oldList...)
-	list = append(list, memberType)
+// registeredSdkMemberTypes is the set of registered registeredSdkMemberTypes for sdk modules.
+var registeredSdkMemberTypes = &sdkRegistry{}
 
-	// Sort the member types by their property name to ensure that registry order has no effect
-	// on behavior.
-	sort.Slice(list, func(i1, i2 int) bool {
-		t1 := list[i1]
-		t2 := list[i2]
-
-		return t1.SdkPropertyName() < t2.SdkPropertyName()
-	})
-
-	// Generate a key that identifies the slice of SdkMemberTypes by joining the property names
-	// from all the SdkMemberType .
-	var properties []string
-	for _, t := range list {
-		properties = append(properties, t.SdkPropertyName())
-	}
-	key := NewOnceKey(strings.Join(properties, "|"))
-
-	// Create a new registry so the pointer uniquely identifies the set of registered types.
-	return &SdkMemberTypesRegistry{
-		list: list,
-		key:  key,
-	}
-}
-
-func (r *SdkMemberTypesRegistry) RegisteredTypes() []SdkMemberType {
-	return r.list
-}
-
-func (r *SdkMemberTypesRegistry) UniqueOnceKey() OnceKey {
-	// Use the pointer to the registry as the unique key.
-	return NewCustomOnceKey(r)
-}
-
-// The set of registered SdkMemberTypes, one for sdk module and one for module_exports.
-var ModuleExportsMemberTypes = &SdkMemberTypesRegistry{}
-var SdkMemberTypes = &SdkMemberTypesRegistry{}
-
-// Register an SdkMemberType object to allow them to be used in the sdk and sdk_snapshot module
+// RegisteredSdkMemberTypes returns a OnceKey and a sorted list of registered types.
+//
+// If moduleExports is true then the slice of types includes all registered types that can be used
+// with the module_exports and module_exports_snapshot module types. Otherwise, the slice of types
+// only includes those registered types that can be used with the sdk and sdk_snapshot module
 // types.
+//
+// The key uniquely identifies the array of types and can be used with OncePer.Once() to cache
+// information derived from the array of types.
+func RegisteredSdkMemberTypes(moduleExports bool) (OnceKey, []SdkMemberType) {
+	var registry *sdkRegistry
+	if moduleExports {
+		registry = registeredModuleExportsMemberTypes
+	} else {
+		registry = registeredSdkMemberTypes
+	}
+
+	registerables := registry.registeredObjects()
+	types := make([]SdkMemberType, len(registerables))
+	for i, registerable := range registerables {
+		types[i] = registerable.(SdkMemberType)
+	}
+	return registry.uniqueOnceKey(), types
+}
+
+// RegisterSdkMemberType registers an SdkMemberType object to allow them to be used in the
+// module_exports, module_exports_snapshot and (depending on the value returned from
+// SdkMemberType.UsableWithSdkAndSdkSnapshot) the sdk and sdk_snapshot module types.
 func RegisterSdkMemberType(memberType SdkMemberType) {
 	// All member types are usable with module_exports.
-	ModuleExportsMemberTypes = ModuleExportsMemberTypes.copyAndAppend(memberType)
+	registeredModuleExportsMemberTypes = registeredModuleExportsMemberTypes.copyAndAppend(memberType)
 
 	// Only those that explicitly indicate it are usable with sdk.
 	if memberType.UsableWithSdkAndSdkSnapshot() {
-		SdkMemberTypes = SdkMemberTypes.copyAndAppend(memberType)
+		registeredSdkMemberTypes = registeredSdkMemberTypes.copyAndAppend(memberType)
 	}
 }
 
-// Base structure for all implementations of SdkMemberProperties.
+// SdkMemberPropertiesBase is the base structure for all implementations of SdkMemberProperties and
+// must be embedded in any struct that implements SdkMemberProperties.
 //
 // Contains common properties that apply across many different member types.
 type SdkMemberPropertiesBase struct {
@@ -637,7 +869,7 @@
 	Compile_multilib string `android:"arch_variant"`
 }
 
-// The os prefix to use for any file paths in the sdk.
+// OsPrefix returns the os prefix to use for any file paths in the sdk.
 //
 // Is an empty string if the member only provides variants for a single os type, otherwise
 // is the OsType.Name.
@@ -653,39 +885,54 @@
 	return b
 }
 
-// Interface to be implemented on top of a structure that contains variant specific
-// information.
+// SdkMemberProperties is the interface to be implemented on top of a structure that contains
+// variant specific information.
 //
-// Struct fields that are capitalized are examined for common values to extract. Fields
-// that are not capitalized are assumed to be arch specific.
+// Struct fields that are capitalized are examined for common values to extract. Fields that are not
+// capitalized are assumed to be arch specific.
 type SdkMemberProperties interface {
-	// Access the base structure.
+	// Base returns the base structure.
 	Base() *SdkMemberPropertiesBase
 
-	// Populate this structure with information from the variant.
+	// PopulateFromVariant populates this structure with information from a module variant.
+	//
+	// It will typically be called once for each variant of a member module that the SDK depends upon.
 	PopulateFromVariant(ctx SdkMemberContext, variant Module)
 
-	// Add the information from this structure to the property set.
+	// AddToPropertySet adds the information from this structure to the property set.
+	//
+	// This will be called for each instance of this structure on which the PopulateFromVariant method
+	// was called and also on a number of different instances of this structure into which properties
+	// common to one or more variants have been copied. Therefore, implementations of this must handle
+	// the case when this structure is only partially populated.
 	AddToPropertySet(ctx SdkMemberContext, propertySet BpPropertySet)
 }
 
-// Provides access to information common to a specific member.
+// SdkMemberContext provides access to information common to a specific member.
 type SdkMemberContext interface {
 
-	// The module context of the sdk common os variant which is creating the snapshot.
+	// SdkModuleContext returns the module context of the sdk common os variant which is creating the
+	// snapshot.
+	//
+	// This is common to all members of the sdk and is not specific to the member being processed.
+	// If information about the member being processed needs to be obtained from this ModuleContext it
+	// must be obtained using one of the OtherModule... methods not the Module... methods.
 	SdkModuleContext() ModuleContext
 
-	// The builder of the snapshot.
+	// SnapshotBuilder the builder of the snapshot.
 	SnapshotBuilder() SnapshotBuilder
 
-	// The type of the member.
+	// MemberType returns the type of the member currently being processed.
 	MemberType() SdkMemberType
 
-	// The name of the member.
+	// Name returns the name of the member currently being processed.
 	//
 	// Provided for use by sdk members to create a member specific location within the snapshot
 	// into which to copy the prebuilt files.
 	Name() string
+
+	// RequiresTrait returns true if this member is expected to provide the specified trait.
+	RequiresTrait(trait SdkMemberTrait) bool
 }
 
 // ExportedComponentsInfo contains information about the components that this module exports to an
diff --git a/android/sdk_test.go b/android/sdk_test.go
new file mode 100644
index 0000000..51aeb31
--- /dev/null
+++ b/android/sdk_test.go
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import "testing"
+
+type testSdkRegisterable struct {
+	name string
+}
+
+func (t *testSdkRegisterable) SdkPropertyName() string {
+	return t.name
+}
+
+var _ sdkRegisterable = &testSdkRegisterable{}
+
+func TestSdkRegistry(t *testing.T) {
+	alpha := &testSdkRegisterable{"alpha"}
+	beta := &testSdkRegisterable{"beta"}
+	betaDup := &testSdkRegisterable{"beta"}
+
+	// Make sure that an empty registry is empty.
+	emptyRegistry := &sdkRegistry{}
+	AssertDeepEquals(t, "emptyRegistry should be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects())
+
+	// Add beta to the empty registry to create another registry, check that it contains beta and make
+	// sure that it does not affect the creating registry.
+	registry1 := emptyRegistry.copyAndAppend(beta)
+	AssertDeepEquals(t, "emptyRegistry should still be empty", ([]sdkRegisterable)(nil), emptyRegistry.registeredObjects())
+	AssertDeepEquals(t, "registry1 should contain beta", []sdkRegisterable{beta}, registry1.registeredObjects())
+
+	// Add alpha to the registry containing beta to create another registry, check that it contains
+	// alpha,beta (in order) and make sure that it does not affect the creating registry.
+	registry2 := registry1.copyAndAppend(alpha)
+	AssertDeepEquals(t, "registry1 should still contain beta", []sdkRegisterable{beta}, registry1.registeredObjects())
+	AssertDeepEquals(t, "registry2 should contain alpha,beta", []sdkRegisterable{alpha, beta}, registry2.registeredObjects())
+
+	AssertPanicMessageContains(t, "duplicate beta should be detected", `"beta" already exists in ["alpha" "beta"]`, func() {
+		registry2.copyAndAppend(betaDup)
+	})
+}
diff --git a/android/sdk_version.go b/android/sdk_version.go
index c6c75a3..2004c92 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -117,7 +117,7 @@
 	return false
 }
 
-// PrebuiltSdkAvailableForUnbundledBuilt tells whether this SdkSpec can have a prebuilt SDK
+// PrebuiltSdkAvailableForUnbundledBuild tells whether this SdkSpec can have a prebuilt SDK
 // that can be used for unbundled builds.
 func (s SdkSpec) PrebuiltSdkAvailableForUnbundledBuild() bool {
 	// "", "none", and "core_platform" are not available for unbundled build
@@ -157,7 +157,7 @@
 		return ctx.Config().AlwaysUsePrebuiltSdks()
 	} else if !s.ApiLevel.IsPreview() {
 		// validation check
-		if s.Kind != SdkPublic && s.Kind != SdkSystem && s.Kind != SdkTest && s.Kind != SdkModule {
+		if s.Kind != SdkPublic && s.Kind != SdkSystem && s.Kind != SdkTest && s.Kind != SdkModule && s.Kind != SdkSystemServer {
 			panic(fmt.Errorf("prebuilt SDK is not not available for SdkKind=%q", s.Kind))
 			return false
 		}
@@ -212,6 +212,10 @@
 )
 
 func SdkSpecFrom(ctx EarlyModuleContext, str string) SdkSpec {
+	return SdkSpecFromWithConfig(ctx.Config(), str)
+}
+
+func SdkSpecFromWithConfig(config Config, str string) SdkSpec {
 	switch str {
 	// special cases first
 	case "":
@@ -252,7 +256,7 @@
 			return SdkSpec{SdkInvalid, NoneApiLevel, str}
 		}
 
-		apiLevel, err := ApiLevelFromUser(ctx, versionString)
+		apiLevel, err := ApiLevelFromUserWithConfig(config, versionString)
 		if err != nil {
 			return SdkSpec{SdkInvalid, apiLevel, str}
 		}
diff --git a/android/sdk_version_test.go b/android/sdk_version_test.go
new file mode 100644
index 0000000..ec81782
--- /dev/null
+++ b/android/sdk_version_test.go
@@ -0,0 +1,89 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"testing"
+)
+
+func TestSdkSpecFrom(t *testing.T) {
+	testCases := []struct {
+		input    string
+		expected string
+	}{
+		{
+			input:    "",
+			expected: "private_current",
+		},
+		{
+			input:    "none",
+			expected: "none_(no version)",
+		},
+		{
+			input:    "core_platform",
+			expected: "core_platform_current",
+		},
+		{
+			input:    "_",
+			expected: "invalid_(no version)",
+		},
+		{
+			input:    "_31",
+			expected: "invalid_(no version)",
+		},
+		{
+			input:    "system_R",
+			expected: "system_30",
+		},
+		{
+			input:    "test_31",
+			expected: "test_31",
+		},
+		{
+			input:    "module_current",
+			expected: "module-lib_current",
+		},
+		{
+			input:    "31",
+			expected: "public_31",
+		},
+		{
+			input:    "S",
+			expected: "public_31",
+		},
+		{
+			input:    "current",
+			expected: "public_current",
+		},
+		{
+			input:    "Tiramisu",
+			expected: "public_Tiramisu",
+		},
+	}
+
+	config := NullConfig("", "")
+
+	config.productVariables = productVariables{
+		Platform_sdk_version:              intPtr(31),
+		Platform_sdk_codename:             stringPtr("Tiramisu"),
+		Platform_version_active_codenames: []string{"Tiramisu"},
+	}
+
+	for _, tc := range testCases {
+		if got := SdkSpecFromWithConfig(config, tc.input).String(); tc.expected != got {
+			t.Errorf("Expected %v, got %v", tc.expected, got)
+		}
+	}
+}
diff --git a/android/singleton.go b/android/singleton.go
index bb6614d..7ff96c9 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -54,10 +54,10 @@
 
 	RequireNinjaVersion(major, minor, micro int)
 
-	// SetNinjaBuildDir sets the value of the top-level "builddir" Ninja variable
+	// SetOutDir sets the value of the top-level "builddir" Ninja variable
 	// that controls where Ninja stores its build log files.  This value can be
 	// set at most one time for a single build, later calls are ignored.
-	SetNinjaBuildDir(pctx PackageContext, value string)
+	SetOutDir(pctx PackageContext, value string)
 
 	// Eval takes a string with embedded ninja variables, and returns a string
 	// with all of the variables recursively expanded. Any variables references
@@ -180,8 +180,8 @@
 	addPhony(s.Config(), name, deps...)
 }
 
-func (s *singletonContextAdaptor) SetNinjaBuildDir(pctx PackageContext, value string) {
-	s.SingletonContext.SetNinjaBuildDir(pctx.PackageContext, value)
+func (s *singletonContextAdaptor) SetOutDir(pctx PackageContext, value string) {
+	s.SingletonContext.SetOutDir(pctx.PackageContext, value)
 }
 
 func (s *singletonContextAdaptor) Eval(pctx PackageContext, ninjaStr string) (string, error) {
diff --git a/android/singleton_module_test.go b/android/singleton_module_test.go
index eb5554c..9d98478 100644
--- a/android/singleton_module_test.go
+++ b/android/singleton_module_test.go
@@ -46,8 +46,8 @@
 	PrepareForTestWithAndroidMk,
 	FixtureRegisterWithContext(func(ctx RegistrationContext) {
 		ctx.RegisterSingletonModuleType("test_singleton_module", testSingletonModuleFactory)
-		ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
 	}),
+	PrepareForTestWithMakevars,
 )
 
 func TestSingletonModule(t *testing.T) {
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 289e910..bd73645 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -31,10 +31,10 @@
 )
 
 func init() {
-	RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-	RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-	RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-	RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+	RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+	RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+	RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+	RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 }
 
 type soongConfigModuleTypeImport struct {
@@ -122,15 +122,10 @@
 //     }
 //
 // If an acme BoardConfig.mk file contained:
-//
-//     SOONG_CONFIG_NAMESPACES += acme
-//     SOONG_CONFIG_acme += \
-//         board \
-//         feature \
-//
-//     SOONG_CONFIG_acme_board := soc_a
-//     SOONG_CONFIG_acme_feature := true
-//     SOONG_CONFIG_acme_width := 200
+//     $(call add_sonng_config_namespace, acme)
+//     $(call add_soong_config_var_value, acme, board, soc_a)
+//     $(call add_soong_config_var_value, acme, feature, true)
+//     $(call add_soong_config_var_value, acme, width, 200)
 //
 // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
 //
@@ -158,7 +153,7 @@
 // Then libacme_foo would build with cflags:
 //   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
 
-func soongConfigModuleTypeImportFactory() Module {
+func SoongConfigModuleTypeImportFactory() Module {
 	module := &soongConfigModuleTypeImport{}
 
 	module.AddProperties(&module.properties)
@@ -184,6 +179,7 @@
 
 type soongConfigModuleTypeModule struct {
 	ModuleBase
+	BazelModuleBase
 	properties soongconfig.ModuleTypeProperties
 }
 
@@ -267,7 +263,7 @@
 //     SOONG_CONFIG_acme_width := 200
 //
 // Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
-func soongConfigModuleTypeFactory() Module {
+func SoongConfigModuleTypeFactory() Module {
 	module := &soongConfigModuleTypeModule{}
 
 	module.AddProperties(&module.properties)
@@ -301,7 +297,7 @@
 
 // soong_config_string_variable defines a variable and a set of possible string values for use
 // in a soong_config_module_type definition.
-func soongConfigStringVariableDummyFactory() Module {
+func SoongConfigStringVariableDummyFactory() Module {
 	module := &soongConfigStringVariableDummyModule{}
 	module.AddProperties(&module.properties, &module.stringProperties)
 	initAndroidModuleBase(module)
@@ -310,7 +306,7 @@
 
 // soong_config_string_variable defines a variable with true or false values for use
 // in a soong_config_module_type definition.
-func soongConfigBoolVariableDummyFactory() Module {
+func SoongConfigBoolVariableDummyFactory() Module {
 	module := &soongConfigBoolVariableDummyModule{}
 	module.AddProperties(&module.properties)
 	initAndroidModuleBase(module)
@@ -329,6 +325,9 @@
 func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
 func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
 
+// importModuleTypes registers the module factories for a list of module types defined
+// in an Android.bp file. These module factories are scoped for the current Android.bp
+// file only.
 func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
 	from = filepath.Clean(from)
 	if filepath.Ext(from) != ".bp" {
@@ -379,8 +378,12 @@
 			ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
 			return (map[string]blueprint.ModuleFactory)(nil)
 		}
+		defer r.Close()
 
 		mtDef, errs := soongconfig.Parse(r, from)
+		if ctx.Config().runningAsBp2Build {
+			ctx.Config().Bp2buildSoongConfigDefinitions.AddVars(*mtDef)
+		}
 
 		if len(errs) > 0 {
 			reportErrors(ctx, from, errs...)
@@ -394,7 +397,7 @@
 		for name, moduleType := range mtDef.ModuleTypes {
 			factory := globalModuleTypes[moduleType.BaseModuleType]
 			if factory != nil {
-				factories[name] = soongConfigModuleFactory(factory, moduleType)
+				factories[name] = configModuleFactory(factory, moduleType, ctx.Config().runningAsBp2Build)
 			} else {
 				reportErrors(ctx, from,
 					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
@@ -409,20 +412,39 @@
 	}).(map[string]blueprint.ModuleFactory)
 }
 
-// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
-// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
-// variables.
-func soongConfigModuleFactory(factory blueprint.ModuleFactory,
-	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
-
+// configModuleFactory takes an existing soongConfigModuleFactory and a
+// ModuleType to create a new ModuleFactory that uses a custom loadhook.
+func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType, bp2build bool) blueprint.ModuleFactory {
 	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
-	if conditionalFactoryProps.IsValid() {
-		return func() (blueprint.Module, []interface{}) {
-			module, props := factory()
+	if !conditionalFactoryProps.IsValid() {
+		return factory
+	}
 
-			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
-			props = append(props, conditionalProps.Interface())
+	return func() (blueprint.Module, []interface{}) {
+		module, props := factory()
+		conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
+		props = append(props, conditionalProps.Interface())
 
+		if bp2build {
+			// The loadhook is different for bp2build, since we don't want to set a specific
+			// set of property values based on a vendor var -- we want __all of them__ to
+			// generate select statements, so we put the entire soong_config_variables
+			// struct, together with the namespace representing those variables, while
+			// creating the custom module with the factory.
+			AddLoadHook(module, func(ctx LoadHookContext) {
+				if m, ok := module.(Bazelable); ok {
+					m.SetBaseModuleType(moduleType.BaseModuleType)
+					// Instead of applying all properties, keep the entire conditionalProps struct as
+					// part of the custom module so dependent modules can create the selects accordingly
+					m.setNamespacedVariableProps(namespacedVariableProperties{
+						moduleType.ConfigNamespace: []interface{}{conditionalProps.Interface()},
+					})
+				}
+			})
+		} else {
+			// Regular Soong operation wraps the existing module factory with a
+			// conditional on Soong config variables by reading the product
+			// config variables from Make.
 			AddLoadHook(module, func(ctx LoadHookContext) {
 				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
 				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
@@ -434,10 +456,7 @@
 					ctx.AppendProperties(ps)
 				}
 			})
-
-			return module, props
 		}
-	} else {
-		return factory
+		return module, props
 	}
 }
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 8f252d9..ceb8e45 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -60,7 +60,7 @@
 			module_type: "test",
 			config_namespace: "acme",
 			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
-			bool_variables: ["feature2", "unused_feature"],
+			bool_variables: ["feature2", "unused_feature", "always_true"],
 			value_variables: ["size", "unused_size"],
 			properties: ["cflags", "srcs", "defaults"],
 		}
@@ -148,6 +148,11 @@
 			cflags: ["DEFAULT_B"],
 		}
 
+		test_defaults {
+			name: "foo_defaults_always_true",
+			cflags: ["DEFAULT_ALWAYS_TRUE"],
+		}
+
 		acme_test {
 			name: "foo_with_defaults",
 			cflags: ["-DGENERIC"],
@@ -176,6 +181,15 @@
 				FEATURE3: {
 					cflags: ["-DFEATURE3"],
 				},
+				always_true: {
+					defaults: ["foo_defaults_always_true"],
+					conditions_default: {
+						// verify that conditions_default is skipped if the
+						// soong config variable is true by specifying a
+						// non-existent module in conditions_default
+						defaults: ["//nonexistent:defaults"],
+					}
+				},
 			},
 		}
     `
@@ -205,6 +219,7 @@
 						"unused_feature":    "true", // unused
 						"unused_size":       "1",    // unused
 						"unused_string_var": "a",    // unused
+						"always_true":       "true",
 					},
 				}),
 				fooExpectedFlags: []string{
@@ -217,6 +232,7 @@
 				},
 				fooDefaultsExpectedFlags: []string{
 					"DEFAULT_A",
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 					"-DSIZE=42",
@@ -227,7 +243,10 @@
 			{
 				name: "empty_prop_for_string_var",
 				preparer: fixtureForVendorVars(map[string]map[string]string{
-					"acme": {"board": "soc_c"}}),
+					"acme": {
+						"board":       "soc_c",
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -236,6 +255,7 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
@@ -243,7 +263,10 @@
 			{
 				name: "unused_string_var",
 				preparer: fixtureForVendorVars(map[string]map[string]string{
-					"acme": {"board": "soc_d"}}),
+					"acme": {
+						"board":       "soc_d",
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -253,14 +276,18 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
 			},
 
 			{
-				name:     "conditions_default",
-				preparer: fixtureForVendorVars(map[string]map[string]string{}),
+				name: "conditions_default",
+				preparer: fixtureForVendorVars(map[string]map[string]string{
+					"acme": {
+						"always_true": "true",
+					}}),
 				fooExpectedFlags: []string{
 					"DEFAULT",
 					"-DGENERIC",
@@ -270,6 +297,7 @@
 					"-DF1_CONDITIONS_DEFAULT",
 				},
 				fooDefaultsExpectedFlags: []string{
+					"DEFAULT_ALWAYS_TRUE",
 					"DEFAULT",
 					"-DGENERIC",
 				},
@@ -282,10 +310,10 @@
 					tc.preparer,
 					PrepareForTestWithDefaults,
 					FixtureRegisterWithContext(func(ctx RegistrationContext) {
-						ctx.RegisterModuleType("soong_config_module_type_import", soongConfigModuleTypeImportFactory)
-						ctx.RegisterModuleType("soong_config_module_type", soongConfigModuleTypeFactory)
-						ctx.RegisterModuleType("soong_config_string_variable", soongConfigStringVariableDummyFactory)
-						ctx.RegisterModuleType("soong_config_bool_variable", soongConfigBoolVariableDummyFactory)
+						ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+						ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+						ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+						ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
 						ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
 						ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
 					}),
@@ -313,6 +341,91 @@
 	})
 }
 
+func TestNonExistentPropertyInSoongConfigModule(t *testing.T) {
+	bp := `
+		soong_config_module_type {
+			name: "acme_test",
+			module_type: "test",
+			config_namespace: "acme",
+			bool_variables: ["feature1"],
+			properties: ["made_up_property"],
+		}
+
+		acme_test {
+			name: "foo",
+			cflags: ["-DGENERIC"],
+			soong_config_variables: {
+				feature1: {
+					made_up_property: true,
+				},
+			},
+		}
+    `
+
+	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
+		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.VendorVars = vars
+		})
+	}
+
+	GroupFixturePreparers(
+		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
+		PrepareForTestWithDefaults,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+			ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+			ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+			ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
+			ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
+			ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
+		// TODO(b/171232169): improve the error message for non-existent properties
+		`unrecognized property "soong_config_variables`,
+	})).RunTest(t)
+}
+
+func TestDuplicateStringValueInSoongConfigStringVariable(t *testing.T) {
+	bp := `
+		soong_config_string_variable {
+			name: "board",
+			values: ["soc_a", "soc_b", "soc_c", "soc_a"],
+		}
+
+		soong_config_module_type {
+			name: "acme_test",
+			module_type: "test",
+			config_namespace: "acme",
+			variables: ["board"],
+			properties: ["cflags", "srcs", "defaults"],
+		}
+    `
+
+	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
+		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.VendorVars = vars
+		})
+	}
+
+	GroupFixturePreparers(
+		fixtureForVendorVars(map[string]map[string]string{"acme": {"feature1": "1"}}),
+		PrepareForTestWithDefaults,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory)
+			ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory)
+			ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory)
+			ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory)
+			ctx.RegisterModuleType("test_defaults", soongConfigTestDefaultsModuleFactory)
+			ctx.RegisterModuleType("test", soongConfigTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	).ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern([]string{
+		// TODO(b/171232169): improve the error message for non-existent properties
+		`Android.bp: soong_config_string_variable: values property error: duplicate value: "soc_a"`,
+	})).RunTest(t)
+}
+
 func testConfigWithVendorVars(buildDir, bp string, fs map[string][]byte, vendorVars map[string]map[string]string) Config {
 	config := TestConfig(buildDir, nil, bp, fs)
 
diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp
index e7fa5a0..8fe1ff1 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -9,6 +9,8 @@
         "blueprint",
         "blueprint-parser",
         "blueprint-proptools",
+        "soong-bazel",
+        "soong-starlark-format",
     ],
     srcs: [
         "config.go",
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index 34b180d..212b752 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -20,15 +20,18 @@
 	"reflect"
 	"sort"
 	"strings"
+	"sync"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/parser"
 	"github.com/google/blueprint/proptools"
+
+	"android/soong/starlark_fmt"
 )
 
 const conditionsDefault = "conditions_default"
 
-var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
+var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
 
 // loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
 // result so each file is only parsed once.
@@ -176,10 +179,14 @@
 		return []error{fmt.Errorf("values property must be set")}
 	}
 
+	vals := make(map[string]bool, len(stringProps.Values))
 	for _, name := range stringProps.Values {
 		if err := checkVariableName(name); err != nil {
 			return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
+		} else if _, ok := vals[name]; ok {
+			return []error{fmt.Errorf("soong_config_string_variable: values property error: duplicate value: %q", name)}
 		}
+		vals[name] = true
 	}
 
 	v.variables[base.variable] = &stringVariable{
@@ -230,6 +237,105 @@
 	variables map[string]soongConfigVariable
 }
 
+// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
+// string vars, bool vars and value vars created by every
+// soong_config_module_type in this build.
+type Bp2BuildSoongConfigDefinitions struct {
+	// varCache contains a cache of string variables namespace + property
+	// The same variable may be used in multiple module types (for example, if need support
+	// for cc_default and java_default), only need to process once
+	varCache map[string]bool
+
+	StringVars map[string][]string
+	BoolVars   map[string]bool
+	ValueVars  map[string]bool
+}
+
+var bp2buildSoongConfigVarsLock sync.Mutex
+
+// SoongConfigVariablesForBp2build extracts information from a
+// SoongConfigDefinition that bp2build needs to generate constraint settings and
+// values for, in order to migrate soong_config_module_type usages to Bazel.
+func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) {
+	// In bp2build mode, this method is called concurrently in goroutines from
+	// loadhooks while parsing soong_config_module_type, so add a mutex to
+	// prevent concurrent map writes. See b/207572723
+	bp2buildSoongConfigVarsLock.Lock()
+	defer bp2buildSoongConfigVarsLock.Unlock()
+
+	if defs.StringVars == nil {
+		defs.StringVars = make(map[string][]string)
+	}
+	if defs.BoolVars == nil {
+		defs.BoolVars = make(map[string]bool)
+	}
+	if defs.ValueVars == nil {
+		defs.ValueVars = make(map[string]bool)
+	}
+	if defs.varCache == nil {
+		defs.varCache = make(map[string]bool)
+	}
+	for _, moduleType := range mtDef.ModuleTypes {
+		for _, v := range moduleType.Variables {
+			key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
+
+			// The same variable may be used in multiple module types (for example, if need support
+			// for cc_default and java_default), only need to process once
+			if _, keyInCache := defs.varCache[key]; keyInCache {
+				continue
+			} else {
+				defs.varCache[key] = true
+			}
+
+			if strVar, ok := v.(*stringVariable); ok {
+				for _, value := range strVar.values {
+					defs.StringVars[key] = append(defs.StringVars[key], value)
+				}
+			} else if _, ok := v.(*boolVariable); ok {
+				defs.BoolVars[key] = true
+			} else if _, ok := v.(*valueVariable); ok {
+				defs.ValueVars[key] = true
+			} else {
+				panic(fmt.Errorf("Unsupported variable type: %+v", v))
+			}
+		}
+	}
+}
+
+// This is a copy of the one available in soong/android/util.go, but depending
+// on the android package causes a cyclic dependency. A refactoring here is to
+// extract common utils out from android/utils.go for other packages like this.
+func sortedStringKeys(m interface{}) []string {
+	v := reflect.ValueOf(m)
+	if v.Kind() != reflect.Map {
+		panic(fmt.Sprintf("%#v is not a map", m))
+	}
+	keys := v.MapKeys()
+	s := make([]string, 0, len(keys))
+	for _, key := range keys {
+		s = append(s, key.String())
+	}
+	sort.Strings(s)
+	return s
+}
+
+// String emits the Soong config variable definitions as Starlark dictionaries.
+func (defs Bp2BuildSoongConfigDefinitions) String() string {
+	ret := ""
+	ret += "soong_config_bool_variables = "
+	ret += starlark_fmt.PrintBoolDict(defs.BoolVars, 0)
+	ret += "\n\n"
+
+	ret += "soong_config_value_variables = "
+	ret += starlark_fmt.PrintBoolDict(defs.ValueVars, 0)
+	ret += "\n\n"
+
+	ret += "soong_config_string_variables = "
+	ret += starlark_fmt.PrintStringListDict(defs.StringVars, 0)
+
+	return ret
+}
+
 // CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
 // property layout for the Soong config variables, with each possible value an interface{} that
 // contains a nil pointer to another newly constructed type that contains the affectable properties.
@@ -271,12 +377,12 @@
 	}
 
 	typ := reflect.StructOf([]reflect.StructField{{
-		Name: soongConfigProperty,
+		Name: SoongConfigProperty,
 		Type: reflect.StructOf(fields),
 	}})
 
 	props := reflect.New(typ)
-	structConditions := props.Elem().FieldByName(soongConfigProperty)
+	structConditions := props.Elem().FieldByName(SoongConfigProperty)
 
 	for i, c := range moduleType.Variables {
 		c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
@@ -415,7 +521,7 @@
 // soong_config_variables are expected to be in the same order as moduleType.Variables.
 func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
 	var ret []interface{}
-	props = props.Elem().FieldByName(soongConfigProperty)
+	props = props.Elem().FieldByName(SoongConfigProperty)
 	for i, c := range moduleType.Variables {
 		if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
 			return nil, err
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
index 48cdfe7..a7800e8 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -364,3 +364,116 @@
 		}
 	}
 }
+
+func Test_Bp2BuildSoongConfigDefinitions(t *testing.T) {
+	testCases := []struct {
+		desc     string
+		defs     Bp2BuildSoongConfigDefinitions
+		expected string
+	}{
+		{
+			desc: "all empty",
+			defs: Bp2BuildSoongConfigDefinitions{},
+			expected: `soong_config_bool_variables = {}
+
+soong_config_value_variables = {}
+
+soong_config_string_variables = {}`}, {
+			desc: "only bool",
+			defs: Bp2BuildSoongConfigDefinitions{
+				BoolVars: map[string]bool{
+					"bool_var": true,
+				},
+			},
+			expected: `soong_config_bool_variables = {
+    "bool_var": True,
+}
+
+soong_config_value_variables = {}
+
+soong_config_string_variables = {}`}, {
+			desc: "only value vars",
+			defs: Bp2BuildSoongConfigDefinitions{
+				ValueVars: map[string]bool{
+					"value_var": true,
+				},
+			},
+			expected: `soong_config_bool_variables = {}
+
+soong_config_value_variables = {
+    "value_var": True,
+}
+
+soong_config_string_variables = {}`}, {
+			desc: "only string vars",
+			defs: Bp2BuildSoongConfigDefinitions{
+				StringVars: map[string][]string{
+					"string_var": []string{
+						"choice1",
+						"choice2",
+						"choice3",
+					},
+				},
+			},
+			expected: `soong_config_bool_variables = {}
+
+soong_config_value_variables = {}
+
+soong_config_string_variables = {
+    "string_var": [
+        "choice1",
+        "choice2",
+        "choice3",
+    ],
+}`}, {
+			desc: "all vars",
+			defs: Bp2BuildSoongConfigDefinitions{
+				BoolVars: map[string]bool{
+					"bool_var_one": true,
+				},
+				ValueVars: map[string]bool{
+					"value_var_one": true,
+					"value_var_two": true,
+				},
+				StringVars: map[string][]string{
+					"string_var_one": []string{
+						"choice1",
+						"choice2",
+						"choice3",
+					},
+					"string_var_two": []string{
+						"foo",
+						"bar",
+					},
+				},
+			},
+			expected: `soong_config_bool_variables = {
+    "bool_var_one": True,
+}
+
+soong_config_value_variables = {
+    "value_var_one": True,
+    "value_var_two": True,
+}
+
+soong_config_string_variables = {
+    "string_var_one": [
+        "choice1",
+        "choice2",
+        "choice3",
+    ],
+    "string_var_two": [
+        "foo",
+        "bar",
+    ],
+}`},
+	}
+	for _, test := range testCases {
+		t.Run(test.desc, func(t *testing.T) {
+			actual := test.defs.String()
+			if actual != test.expected {
+				t.Errorf("Expected:\n%s\nbut got:\n%s", test.expected, actual)
+			}
+		})
+	}
+}
diff --git a/android/test_asserts.go b/android/test_asserts.go
index edeb408..064f656 100644
--- a/android/test_asserts.go
+++ b/android/test_asserts.go
@@ -77,14 +77,14 @@
 // StringPathRelativeToTop on the actual string path.
 func AssertStringPathRelativeToTopEquals(t *testing.T, message string, config Config, expected string, actual string) {
 	t.Helper()
-	AssertStringEquals(t, message, expected, StringPathRelativeToTop(config.buildDir, actual))
+	AssertStringEquals(t, message, expected, StringPathRelativeToTop(config.soongOutDir, actual))
 }
 
 // AssertStringPathsRelativeToTopEquals checks if the expected value is equal to the result of
 // calling StringPathsRelativeToTop on the actual string paths.
 func AssertStringPathsRelativeToTopEquals(t *testing.T, message string, config Config, expected []string, actual []string) {
 	t.Helper()
-	AssertDeepEquals(t, message, expected, StringPathsRelativeToTop(config.buildDir, actual))
+	AssertDeepEquals(t, message, expected, StringPathsRelativeToTop(config.soongOutDir, actual))
 }
 
 // AssertErrorMessageEquals checks if the error is not nil and has the expected message. If it does
diff --git a/android/test_suites.go b/android/test_suites.go
index 6b7b909..55e1da7 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -60,7 +60,7 @@
 	for _, module := range SortedStringKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, BuildOs, X86, "testcases", false).ToMakePath()
+	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases", false)
 
 	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
diff --git a/android/testing.go b/android/testing.go
index b36f62c..ac02db9 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"bytes"
 	"fmt"
 	"path/filepath"
 	"regexp"
@@ -23,6 +24,8 @@
 	"sync"
 	"testing"
 
+	mkparser "android/soong/androidmk/parser"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -110,6 +113,15 @@
 	FixtureAddFile("build/soong/licenses/LICENSE", nil),
 )
 
+var PrepareForTestWithNamespace = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	registerNamespaceBuildComponents(ctx)
+	ctx.PreArchMutators(RegisterNamespaceMutator)
+})
+
+var PrepareForTestWithMakevars = FixtureRegisterWithContext(func(ctx RegistrationContext) {
+	ctx.RegisterSingletonType("makevars", makeVarsSingletonFunc)
+})
+
 // Test fixture preparer that will register most java build components.
 //
 // Singletons and mutators should only be added here if they are needed for a majority of java
@@ -166,9 +178,9 @@
 
 type TestContext struct {
 	*Context
-	preArch, preDeps, postDeps, finalDeps           []RegisterMutatorFunc
-	bp2buildPreArch, bp2buildDeps, bp2buildMutators []RegisterMutatorFunc
-	NameResolver                                    *NameResolver
+	preArch, preDeps, postDeps, finalDeps []RegisterMutatorFunc
+	bp2buildPreArch, bp2buildMutators     []RegisterMutatorFunc
+	NameResolver                          *NameResolver
 
 	// The list of pre-singletons and singletons registered for the test.
 	preSingletons, singletons sortableComponents
@@ -199,32 +211,16 @@
 	ctx.finalDeps = append(ctx.finalDeps, f)
 }
 
-func (ctx *TestContext) RegisterBp2BuildConfig(config Bp2BuildConfig) {
+func (ctx *TestContext) RegisterBp2BuildConfig(config bp2BuildConversionAllowlist) {
 	ctx.config.bp2buildPackageConfig = config
 }
 
-// RegisterBp2BuildMutator registers a BazelTargetModule mutator for converting a module
-// type to the equivalent Bazel target.
-func (ctx *TestContext) RegisterBp2BuildMutator(moduleType string, m func(TopDownMutatorContext)) {
-	f := func(ctx RegisterMutatorsContext) {
-		ctx.TopDown(moduleType, m)
-	}
-	ctx.config.bp2buildModuleTypeConfig[moduleType] = true
-	ctx.bp2buildMutators = append(ctx.bp2buildMutators, f)
-}
-
 // PreArchBp2BuildMutators adds mutators to be register for converting Android Blueprint modules
 // into Bazel BUILD targets that should run prior to deps and conversion.
 func (ctx *TestContext) PreArchBp2BuildMutators(f RegisterMutatorFunc) {
 	ctx.bp2buildPreArch = append(ctx.bp2buildPreArch, f)
 }
 
-// DepsBp2BuildMutators adds mutators to be register for converting Android Blueprint modules into
-// Bazel BUILD targets that should run prior to conversion to resolve dependencies.
-func (ctx *TestContext) DepsBp2BuildMutators(f RegisterMutatorFunc) {
-	ctx.bp2buildDeps = append(ctx.bp2buildDeps, f)
-}
-
 // registeredComponentOrder defines the order in which a sortableComponent type is registered at
 // runtime and provides support for reordering the components registered for a test in the same
 // way.
@@ -459,7 +455,8 @@
 
 // RegisterForBazelConversion prepares a test context for bp2build conversion.
 func (ctx *TestContext) RegisterForBazelConversion() {
-	RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch, ctx.bp2buildDeps, ctx.bp2buildMutators)
+	ctx.SetRunningAsBp2build()
+	RegisterMutatorsForBazelConversion(ctx.Context, ctx.bp2buildPreArch)
 }
 
 func (ctx *TestContext) ParseFileList(rootDir string, filePaths []string) (deps []string, errs []error) {
@@ -492,6 +489,66 @@
 	ctx.preSingletons = append(ctx.preSingletons, newPreSingleton(name, factory))
 }
 
+// ModuleVariantForTests selects a specific variant of the module with the given
+// name by matching the variations map against the variations of each module
+// variant. A module variant matches the map if every variation that exists in
+// both have the same value. Both the module and the map are allowed to have
+// extra variations that the other doesn't have. Panics if not exactly one
+// module variant matches.
+func (ctx *TestContext) ModuleVariantForTests(name string, matchVariations map[string]string) TestingModule {
+	modules := []Module{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if ctx.ModuleName(m) == name {
+			am := m.(Module)
+			amMut := am.base().commonProperties.DebugMutators
+			amVar := am.base().commonProperties.DebugVariations
+			matched := true
+			for i, mut := range amMut {
+				if wantedVar, found := matchVariations[mut]; found && amVar[i] != wantedVar {
+					matched = false
+					break
+				}
+			}
+			if matched {
+				modules = append(modules, am)
+			}
+		}
+	})
+
+	if len(modules) == 0 {
+		// Show all the modules or module variants that do exist.
+		var allModuleNames []string
+		var allVariants []string
+		ctx.VisitAllModules(func(m blueprint.Module) {
+			allModuleNames = append(allModuleNames, ctx.ModuleName(m))
+			if ctx.ModuleName(m) == name {
+				allVariants = append(allVariants, m.(Module).String())
+			}
+		})
+
+		if len(allVariants) == 0 {
+			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
+		} else {
+			sort.Strings(allVariants)
+			panic(fmt.Errorf("failed to find module %q matching %v. All variants:\n  %s",
+				name, matchVariations, strings.Join(allVariants, "\n  ")))
+		}
+	}
+
+	if len(modules) > 1 {
+		moduleStrings := []string{}
+		for _, m := range modules {
+			moduleStrings = append(moduleStrings, m.String())
+		}
+		sort.Strings(moduleStrings)
+		panic(fmt.Errorf("module %q has more than one variant that match %v:\n  %s",
+			name, matchVariations, strings.Join(moduleStrings, "\n  ")))
+	}
+
+	return newTestingModule(ctx.config, modules[0])
+}
+
 func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
 	var module Module
 	ctx.VisitAllModules(func(m blueprint.Module) {
@@ -510,12 +567,11 @@
 				allVariants = append(allVariants, ctx.ModuleSubDir(m))
 			}
 		})
-		sort.Strings(allModuleNames)
 		sort.Strings(allVariants)
 
 		if len(allVariants) == 0 {
 			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
-				name, strings.Join(allModuleNames, "\n  ")))
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
 		} else {
 			panic(fmt.Errorf("failed to find module %q variant %q. All variants:\n  %s",
 				name, variant, strings.Join(allVariants, "\n  ")))
@@ -553,6 +609,62 @@
 		"\nall singletons: %v", name, allSingletonNames))
 }
 
+type InstallMakeRule struct {
+	Target        string
+	Deps          []string
+	OrderOnlyDeps []string
+}
+
+func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []InstallMakeRule {
+	var rules []InstallMakeRule
+	for _, node := range nodes {
+		if mkParserRule, ok := node.(*mkparser.Rule); ok {
+			var rule InstallMakeRule
+
+			if targets := mkParserRule.Target.Words(); len(targets) == 0 {
+				t.Fatalf("no targets for rule %s", mkParserRule.Dump())
+			} else if len(targets) > 1 {
+				t.Fatalf("unsupported multiple targets for rule %s", mkParserRule.Dump())
+			} else if !targets[0].Const() {
+				t.Fatalf("unsupported non-const target for rule %s", mkParserRule.Dump())
+			} else {
+				rule.Target = normalizeStringRelativeToTop(config, targets[0].Value(nil))
+			}
+
+			prereqList := &rule.Deps
+			for _, prereq := range mkParserRule.Prerequisites.Words() {
+				if !prereq.Const() {
+					t.Fatalf("unsupported non-const prerequisite for rule %s", mkParserRule.Dump())
+				}
+
+				if prereq.Value(nil) == "|" {
+					prereqList = &rule.OrderOnlyDeps
+					continue
+				}
+
+				*prereqList = append(*prereqList, normalizeStringRelativeToTop(config, prereq.Value(nil)))
+			}
+
+			rules = append(rules, rule)
+		}
+	}
+
+	return rules
+}
+
+func (ctx *TestContext) InstallMakeRulesForTesting(t *testing.T) []InstallMakeRule {
+	installs := ctx.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting
+	buf := bytes.NewBuffer(append([]byte(nil), installs...))
+	parser := mkparser.NewParser("makevars", buf)
+
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		t.Fatalf("error parsing install rules: %s", errs[0])
+	}
+
+	return parseMkRules(t, ctx.config, nodes)
+}
+
 func (ctx *TestContext) Config() Config {
 	return ctx.config
 }
@@ -665,15 +777,15 @@
 // containing at most one instance of the temporary build directory at the start of the path while
 // this assumes that there can be any number at any position.
 func normalizeStringRelativeToTop(config Config, s string) string {
-	// The buildDir usually looks something like: /tmp/testFoo2345/001
+	// The soongOutDir usually looks something like: /tmp/testFoo2345/001
 	//
-	// Replace any usage of the buildDir with out/soong, e.g. replace "/tmp/testFoo2345/001" with
+	// Replace any usage of the soongOutDir with out/soong, e.g. replace "/tmp/testFoo2345/001" with
 	// "out/soong".
-	outSoongDir := filepath.Clean(config.buildDir)
+	outSoongDir := filepath.Clean(config.soongOutDir)
 	re := regexp.MustCompile(`\Q` + outSoongDir + `\E\b`)
 	s = re.ReplaceAllString(s, "out/soong")
 
-	// Replace any usage of the buildDir/.. with out, e.g. replace "/tmp/testFoo2345" with
+	// Replace any usage of the soongOutDir/.. with out, e.g. replace "/tmp/testFoo2345" with
 	// "out". This must come after the previous replacement otherwise this would replace
 	// "/tmp/testFoo2345/001" with "out/001" instead of "out/soong".
 	outDir := filepath.Dir(outSoongDir)
@@ -732,25 +844,27 @@
 	return p
 }
 
-func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) TestingBuildParams {
+func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) (TestingBuildParams, []string) {
+	var searchedDescriptions []string
 	for _, p := range b.provider.BuildParamsForTests() {
+		searchedDescriptions = append(searchedDescriptions, p.Description)
 		if strings.Contains(p.Description, desc) {
-			return b.newTestingBuildParams(p)
+			return b.newTestingBuildParams(p), searchedDescriptions
 		}
 	}
-	return TestingBuildParams{}
+	return TestingBuildParams{}, searchedDescriptions
 }
 
 func (b baseTestingComponent) buildParamsFromDescription(desc string) TestingBuildParams {
-	p := b.maybeBuildParamsFromDescription(desc)
+	p, searchedDescriptions := b.maybeBuildParamsFromDescription(desc)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find description %q", desc))
+		panic(fmt.Errorf("couldn't find description %q\nall descriptions:\n%s", desc, strings.Join(searchedDescriptions, "\n")))
 	}
 	return p
 }
 
 func (b baseTestingComponent) maybeBuildParamsFromOutput(file string) (TestingBuildParams, []string) {
-	var searchedOutputs []string
+	searchedOutputs := WritablePaths(nil)
 	for _, p := range b.provider.BuildParamsForTests() {
 		outputs := append(WritablePaths(nil), p.Outputs...)
 		outputs = append(outputs, p.ImplicitOutputs...)
@@ -761,10 +875,17 @@
 			if f.String() == file || f.Rel() == file || PathRelativeToTop(f) == file {
 				return b.newTestingBuildParams(p), nil
 			}
-			searchedOutputs = append(searchedOutputs, f.Rel())
+			searchedOutputs = append(searchedOutputs, f)
 		}
 	}
-	return TestingBuildParams{}, searchedOutputs
+
+	formattedOutputs := []string{}
+	for _, f := range searchedOutputs {
+		formattedOutputs = append(formattedOutputs,
+			fmt.Sprintf("%s (rel=%s)", PathRelativeToTop(f), f.Rel()))
+	}
+
+	return TestingBuildParams{}, formattedOutputs
 }
 
 func (b baseTestingComponent) buildParamsFromOutput(file string) TestingBuildParams {
@@ -804,7 +925,8 @@
 // MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string.  Returns an empty
 // BuildParams if no rule is found.
 func (b baseTestingComponent) MaybeDescription(desc string) TestingBuildParams {
-	return b.maybeBuildParamsFromDescription(desc)
+	p, _ := b.maybeBuildParamsFromDescription(desc)
+	return p
 }
 
 // Description finds a call to ctx.Build with BuildParams.Description set to a the given string.  Panics if no rule is
@@ -992,7 +1114,7 @@
 	}
 	p := path.String()
 	if w, ok := path.(WritablePath); ok {
-		rel, err := filepath.Rel(w.getBuildDir(), p)
+		rel, err := filepath.Rel(w.getSoongOutDir(), p)
 		if err != nil {
 			panic(err)
 		}
diff --git a/android/util.go b/android/util.go
index a0394f6..47c4583 100644
--- a/android/util.go
+++ b/android/util.go
@@ -65,52 +65,55 @@
 	return buf.String()
 }
 
-// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
-// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
-func SortedIntKeys(m interface{}) []int {
-	v := reflect.ValueOf(m)
-	if v.Kind() != reflect.Map {
-		panic(fmt.Sprintf("%#v is not a map", m))
-	}
-	keys := v.MapKeys()
-	s := make([]int, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, int(key.Int()))
-	}
-	sort.Ints(s)
-	return s
-}
-
-// SorterStringKeys returns the keys of the given string-keyed map in the ascending order
+// SorterStringKeys returns the keys of the given string-keyed map in the ascending order.
 func SortedStringKeys(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
 		panic(fmt.Sprintf("%#v is not a map", m))
 	}
-	keys := v.MapKeys()
-	s := make([]string, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, key.String())
+	if v.Len() == 0 {
+		return nil
+	}
+	iter := v.MapRange()
+	s := make([]string, 0, v.Len())
+	for iter.Next() {
+		s = append(s, iter.Key().String())
 	}
 	sort.Strings(s)
 	return s
 }
 
-// SortedStringMapValues returns the values of the string-values map in the ascending order
-func SortedStringMapValues(m interface{}) []string {
+// stringValues returns the values of the given string-valued map in randomized map order.
+func stringValues(m interface{}) []string {
 	v := reflect.ValueOf(m)
 	if v.Kind() != reflect.Map {
 		panic(fmt.Sprintf("%#v is not a map", m))
 	}
-	keys := v.MapKeys()
-	s := make([]string, 0, len(keys))
-	for _, key := range keys {
-		s = append(s, v.MapIndex(key).String())
+	if v.Len() == 0 {
+		return nil
 	}
+	iter := v.MapRange()
+	s := make([]string, 0, v.Len())
+	for iter.Next() {
+		s = append(s, iter.Value().String())
+	}
+	return s
+}
+
+// SortedStringValues returns the values of the given string-valued map in the ascending order.
+func SortedStringValues(m interface{}) []string {
+	s := stringValues(m)
 	sort.Strings(s)
 	return s
 }
 
+// SortedUniqueStringValues returns the values of the given string-valued map in the ascending order
+// with duplicates removed.
+func SortedUniqueStringValues(m interface{}) []string {
+	s := stringValues(m)
+	return SortedUniqueStrings(s)
+}
+
 // IndexList returns the index of the first occurrence of the given string in the list or -1
 func IndexList(s string, list []string) int {
 	for i, l := range list {
diff --git a/android/util_test.go b/android/util_test.go
index 09bec01..9b9253b 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -640,3 +640,117 @@
 		})
 	}
 }
+
+func TestSortedStringKeys(t *testing.T) {
+	testCases := []struct {
+		name     string
+		in       interface{}
+		expected []string
+	}{
+		{
+			name:     "nil",
+			in:       map[string]string(nil),
+			expected: nil,
+		},
+		{
+			name:     "empty",
+			in:       map[string]string{},
+			expected: nil,
+		},
+		{
+			name:     "simple",
+			in:       map[string]string{"a": "foo", "b": "bar"},
+			expected: []string{"a", "b"},
+		},
+		{
+			name:     "interface values",
+			in:       map[string]interface{}{"a": nil, "b": nil},
+			expected: []string{"a", "b"},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			got := SortedStringKeys(tt.in)
+			if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
+				t.Errorf("wanted %q, got %q", w, g)
+			}
+		})
+	}
+}
+
+func TestSortedStringValues(t *testing.T) {
+	testCases := []struct {
+		name     string
+		in       interface{}
+		expected []string
+	}{
+		{
+			name:     "nil",
+			in:       map[string]string(nil),
+			expected: nil,
+		},
+		{
+			name:     "empty",
+			in:       map[string]string{},
+			expected: nil,
+		},
+		{
+			name:     "simple",
+			in:       map[string]string{"foo": "a", "bar": "b"},
+			expected: []string{"a", "b"},
+		},
+		{
+			name:     "duplicates",
+			in:       map[string]string{"foo": "a", "bar": "b", "baz": "b"},
+			expected: []string{"a", "b", "b"},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			got := SortedStringValues(tt.in)
+			if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
+				t.Errorf("wanted %q, got %q", w, g)
+			}
+		})
+	}
+}
+
+func TestSortedUniqueStringValues(t *testing.T) {
+	testCases := []struct {
+		name     string
+		in       interface{}
+		expected []string
+	}{
+		{
+			name:     "nil",
+			in:       map[string]string(nil),
+			expected: nil,
+		},
+		{
+			name:     "empty",
+			in:       map[string]string{},
+			expected: nil,
+		},
+		{
+			name:     "simple",
+			in:       map[string]string{"foo": "a", "bar": "b"},
+			expected: []string{"a", "b"},
+		},
+		{
+			name:     "duplicates",
+			in:       map[string]string{"foo": "a", "bar": "b", "baz": "b"},
+			expected: []string{"a", "b"},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			got := SortedUniqueStringValues(tt.in)
+			if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
+				t.Errorf("wanted %q, got %q", w, g)
+			}
+		})
+	}
+}
diff --git a/android/variable.go b/android/variable.go
index 741d9ae..373891a 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -20,6 +20,9 @@
 	"runtime"
 	"strings"
 
+	"android/soong/android/soongconfig"
+	"android/soong/bazel"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -40,6 +43,19 @@
 		Platform_sdk_version struct {
 			Asflags []string
 			Cflags  []string
+			Cmd     *string
+		}
+
+		Platform_sdk_version_or_codename struct {
+			Java_resource_dirs []string
+		}
+
+		Platform_sdk_extension_version struct {
+			Cmd *string
+		}
+
+		Platform_version_name struct {
+			Base_dir *string
 		}
 
 		// unbundled_build is a catch-all property to annotate modules that don't build in one or
@@ -53,6 +69,8 @@
 			Shared_libs         []string `android:"arch_variant"`
 			Whole_static_libs   []string `android:"arch_variant"`
 			Exclude_static_libs []string `android:"arch_variant"`
+			Srcs                []string `android:"arch_variant"`
+			Header_libs         []string `android:"arch_variant"`
 		} `android:"arch_variant"`
 
 		Malloc_zero_contents struct {
@@ -101,11 +119,17 @@
 				Keep_symbols                 *bool
 				Keep_symbols_and_debug_frame *bool
 			}
+			Static_libs       []string
+			Whole_static_libs []string
+			Shared_libs       []string
+
+			Cmdline []string
+
 			Srcs         []string
 			Exclude_srcs []string
 		}
 
-		// eng is true for -eng builds, and can be used to turn on additionaly heavyweight debugging
+		// eng is true for -eng builds, and can be used to turn on additional heavyweight debugging
 		// features.
 		Eng struct {
 			Cflags   []string
@@ -164,7 +188,10 @@
 	Platform_version_name                     *string  `json:",omitempty"`
 	Platform_sdk_version                      *int     `json:",omitempty"`
 	Platform_sdk_codename                     *string  `json:",omitempty"`
+	Platform_sdk_version_or_codename          *string  `json:",omitempty"`
 	Platform_sdk_final                        *bool    `json:",omitempty"`
+	Platform_sdk_extension_version            *int     `json:",omitempty"`
+	Platform_base_sdk_extension_version       *int     `json:",omitempty"`
 	Platform_version_active_codenames         []string `json:",omitempty"`
 	Platform_vndk_version                     *string  `json:",omitempty"`
 	Platform_systemsdk_versions               []string `json:",omitempty"`
@@ -172,8 +199,10 @@
 	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"`
 	DeviceArch                            *string  `json:",omitempty"`
 	DeviceArchVariant                     *string  `json:",omitempty"`
 	DeviceCpuVariant                      *string  `json:",omitempty"`
@@ -203,6 +232,7 @@
 
 	HostArch          *string `json:",omitempty"`
 	HostSecondaryArch *string `json:",omitempty"`
+	HostMusl          *bool   `json:",omitempty"`
 
 	CrossHost              *string `json:",omitempty"`
 	CrossHostArch          *string `json:",omitempty"`
@@ -222,37 +252,38 @@
 
 	AppsDefaultVersionName *string `json:",omitempty"`
 
-	Allow_missing_dependencies   *bool `json:",omitempty"`
-	Unbundled_build              *bool `json:",omitempty"`
-	Unbundled_build_apps         *bool `json:",omitempty"`
-	Always_use_prebuilt_sdks     *bool `json:",omitempty"`
-	Skip_boot_jars_check         *bool `json:",omitempty"`
-	Malloc_not_svelte            *bool `json:",omitempty"`
-	Malloc_zero_contents         *bool `json:",omitempty"`
-	Malloc_pattern_fill_contents *bool `json:",omitempty"`
-	Safestack                    *bool `json:",omitempty"`
-	HostStaticBinaries           *bool `json:",omitempty"`
-	Binder32bit                  *bool `json:",omitempty"`
-	UseGoma                      *bool `json:",omitempty"`
-	UseRBE                       *bool `json:",omitempty"`
-	UseRBEJAVAC                  *bool `json:",omitempty"`
-	UseRBER8                     *bool `json:",omitempty"`
-	UseRBED8                     *bool `json:",omitempty"`
-	Debuggable                   *bool `json:",omitempty"`
-	Eng                          *bool `json:",omitempty"`
-	Treble_linker_namespaces     *bool `json:",omitempty"`
-	Enforce_vintf_manifest       *bool `json:",omitempty"`
-	Uml                          *bool `json:",omitempty"`
-	Arc                          *bool `json:",omitempty"`
-	MinimizeJavaDebugInfo        *bool `json:",omitempty"`
+	Allow_missing_dependencies   *bool    `json:",omitempty"`
+	Unbundled_build              *bool    `json:",omitempty"`
+	Unbundled_build_apps         []string `json:",omitempty"`
+	Unbundled_build_image        *bool    `json:",omitempty"`
+	Always_use_prebuilt_sdks     *bool    `json:",omitempty"`
+	Skip_boot_jars_check         *bool    `json:",omitempty"`
+	Malloc_not_svelte            *bool    `json:",omitempty"`
+	Malloc_zero_contents         *bool    `json:",omitempty"`
+	Malloc_pattern_fill_contents *bool    `json:",omitempty"`
+	Safestack                    *bool    `json:",omitempty"`
+	HostStaticBinaries           *bool    `json:",omitempty"`
+	Binder32bit                  *bool    `json:",omitempty"`
+	UseGoma                      *bool    `json:",omitempty"`
+	UseRBE                       *bool    `json:",omitempty"`
+	UseRBEJAVAC                  *bool    `json:",omitempty"`
+	UseRBER8                     *bool    `json:",omitempty"`
+	UseRBED8                     *bool    `json:",omitempty"`
+	Debuggable                   *bool    `json:",omitempty"`
+	Eng                          *bool    `json:",omitempty"`
+	Treble_linker_namespaces     *bool    `json:",omitempty"`
+	Enforce_vintf_manifest       *bool    `json:",omitempty"`
+	Uml                          *bool    `json:",omitempty"`
+	Arc                          *bool    `json:",omitempty"`
+	MinimizeJavaDebugInfo        *bool    `json:",omitempty"`
 
 	Check_elf_files *bool `json:",omitempty"`
 
 	UncompressPrivAppDex             *bool    `json:",omitempty"`
 	ModulesLoadedByPrivilegedModules []string `json:",omitempty"`
 
-	BootJars          ConfiguredJarList `json:",omitempty"`
-	UpdatableBootJars ConfiguredJarList `json:",omitempty"`
+	BootJars     ConfiguredJarList `json:",omitempty"`
+	ApexBootJars ConfiguredJarList `json:",omitempty"`
 
 	IntegerOverflowExcludePaths []string `json:",omitempty"`
 
@@ -274,18 +305,17 @@
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
 
-	SamplingPGO *bool `json:",omitempty"`
-
 	JavaCoveragePaths        []string `json:",omitempty"`
 	JavaCoverageExcludePaths []string `json:",omitempty"`
 
-	GcovCoverage               *bool    `json:",omitempty"`
-	ClangCoverage              *bool    `json:",omitempty"`
-	NativeCoveragePaths        []string `json:",omitempty"`
-	NativeCoverageExcludePaths []string `json:",omitempty"`
+	GcovCoverage                *bool    `json:",omitempty"`
+	ClangCoverage               *bool    `json:",omitempty"`
+	NativeCoveragePaths         []string `json:",omitempty"`
+	NativeCoverageExcludePaths  []string `json:",omitempty"`
+	ClangCoverageContinuousMode *bool    `json:",omitempty"`
 
 	// Set by NewConfig
-	Native_coverage *bool
+	Native_coverage *bool `json:",omitempty"`
 
 	SanitizeHost       []string `json:",omitempty"`
 	SanitizeDevice     []string `json:",omitempty"`
@@ -298,15 +328,14 @@
 
 	Override_rs_driver *string `json:",omitempty"`
 
-	Fuchsia *bool `json:",omitempty"`
-
 	DeviceKernelHeaders []string `json:",omitempty"`
 
 	ExtraVndkVersions []string `json:",omitempty"`
 
 	NamespacesToExport []string `json:",omitempty"`
 
-	PgoAdditionalProfileDirs []string `json:",omitempty"`
+	AfdoAdditionalProfileDirs []string `json:",omitempty"`
+	PgoAdditionalProfileDirs  []string `json:",omitempty"`
 
 	VndkUseCoreVariant         *bool `json:",omitempty"`
 	VndkSnapshotBuildArtifacts *bool `json:",omitempty"`
@@ -321,16 +350,28 @@
 	VendorSnapshotDirsExcluded   []string `json:",omitempty"`
 	RecoverySnapshotDirsExcluded []string `json:",omitempty"`
 	RecoverySnapshotDirsIncluded []string `json:",omitempty"`
+	HostFakeSnapshotEnabled      bool     `json:",omitempty"`
 
-	BoardVendorSepolicyDirs      []string `json:",omitempty"`
-	BoardOdmSepolicyDirs         []string `json:",omitempty"`
-	BoardReqdMaskPolicy          []string `json:",omitempty"`
-	SystemExtPublicSepolicyDirs  []string `json:",omitempty"`
-	SystemExtPrivateSepolicyDirs []string `json:",omitempty"`
-	BoardSepolicyM4Defs          []string `json:",omitempty"`
+	BoardVendorSepolicyDirs           []string `json:",omitempty"`
+	BoardOdmSepolicyDirs              []string `json:",omitempty"`
+	BoardReqdMaskPolicy               []string `json:",omitempty"`
+	BoardPlatVendorPolicy             []string `json:",omitempty"`
+	BoardSystemExtPublicPrebuiltDirs  []string `json:",omitempty"`
+	BoardSystemExtPrivatePrebuiltDirs []string `json:",omitempty"`
+	BoardProductPublicPrebuiltDirs    []string `json:",omitempty"`
+	BoardProductPrivatePrebuiltDirs   []string `json:",omitempty"`
+	SystemExtPublicSepolicyDirs       []string `json:",omitempty"`
+	SystemExtPrivateSepolicyDirs      []string `json:",omitempty"`
+	BoardSepolicyM4Defs               []string `json:",omitempty"`
 
 	BoardSepolicyVers       *string `json:",omitempty"`
 	PlatformSepolicyVersion *string `json:",omitempty"`
+	TotSepolicyVersion      *string `json:",omitempty"`
+
+	SystemExtSepolicyPrebuiltApiDir *string `json:",omitempty"`
+	ProductSepolicyPrebuiltApiDir   *string `json:",omitempty"`
+
+	PlatformSepolicyCompatVersions []string `json:",omitempty"`
 
 	VendorVars map[string]map[string]string `json:",omitempty"`
 
@@ -349,6 +390,8 @@
 	CertificateOverrides         []string `json:",omitempty"`
 	PackageNameOverrides         []string `json:",omitempty"`
 
+	ApexGlobalMinSdkVersionOverride *string `json:",omitempty"`
+
 	EnforceSystemCertificate          *bool    `json:",omitempty"`
 	EnforceSystemCertificateAllowList []string `json:",omitempty"`
 
@@ -383,9 +426,10 @@
 
 	ShippingApiLevel *string `json:",omitempty"`
 
-	BuildBrokenEnforceSyspropOwner     bool `json:",omitempty"`
-	BuildBrokenTrebleSyspropNeverallow bool `json:",omitempty"`
-	BuildBrokenVendorPropertyNamespace bool `json:",omitempty"`
+	BuildBrokenEnforceSyspropOwner     bool     `json:",omitempty"`
+	BuildBrokenTrebleSyspropNeverallow bool     `json:",omitempty"`
+	BuildBrokenVendorPropertyNamespace bool     `json:",omitempty"`
+	BuildBrokenInputDirModules         []string `json:",omitempty"`
 
 	BuildDebugfsRestrictionsEnabled bool `json:",omitempty"`
 
@@ -394,6 +438,11 @@
 	SelinuxIgnoreNeverallows bool `json:",omitempty"`
 
 	SepolicySplit bool `json:",omitempty"`
+
+	SepolicyFreezeTestExtraDirs         []string `json:",omitempty"`
+	SepolicyFreezeTestExtraPrebuiltDirs []string `json:",omitempty"`
+
+	GenerateAidlNdkPlatformBackend bool `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
@@ -422,6 +471,7 @@
 		HostArch:                   stringPtr("x86_64"),
 		HostSecondaryArch:          stringPtr("x86"),
 		DeviceName:                 stringPtr("generic_arm64"),
+		DeviceProduct:              stringPtr("aosp_arm-eng"),
 		DeviceArch:                 stringPtr("arm64"),
 		DeviceArchVariant:          stringPtr("armv8-a"),
 		DeviceCpuVariant:           stringPtr("generic"),
@@ -441,8 +491,8 @@
 		Malloc_pattern_fill_contents: boolPtr(false),
 		Safestack:                    boolPtr(false),
 
-		BootJars:          ConfiguredJarList{apexes: []string{}, jars: []string{}},
-		UpdatableBootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}},
+		BootJars:     ConfiguredJarList{apexes: []string{}, jars: []string{}},
+		ApexBootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}},
 	}
 
 	if runtime.GOOS == "linux" {
@@ -460,35 +510,418 @@
 // ProductConfigProperty contains the information for a single property (may be a struct) paired
 // with the appropriate ProductConfigVariable.
 type ProductConfigProperty struct {
-	ProductConfigVariable string
-	Property              interface{}
+	// The name of the product variable, e.g. "safestack", "malloc_not_svelte",
+	// "board"
+	Name string
+
+	// Namespace of the variable, if this is a soong_config_module_type variable
+	// e.g. "acme", "ANDROID", "vendor_name"
+	Namespace string
+
+	// Unique configuration to identify this product config property (i.e. a
+	// primary key), as just using the product variable name is not sufficient.
+	//
+	// For product variables, this is the product variable name + optional
+	// archvariant information. e.g.
+	//
+	// product_variables: {
+	//     foo: {
+	//         cflags: ["-Dfoo"],
+	//     },
+	// },
+	//
+	// FullConfig would be "foo".
+	//
+	// target: {
+	//     android: {
+	//         product_variables: {
+	//             foo: {
+	//                 cflags: ["-Dfoo-android"],
+	//             },
+	//         },
+	//     },
+	// },
+	//
+	// FullConfig would be "foo-android".
+	//
+	// For soong config variables, this is the namespace + product variable name
+	// + value of the variable, if applicable. The value can also be
+	// conditions_default.
+	//
+	// e.g.
+	//
+	// soong_config_variables: {
+	//     feature1: {
+	//         conditions_default: {
+	//             cflags: ["-DDEFAULT1"],
+	//         },
+	//         cflags: ["-DFEATURE1"],
+	//     },
+	// }
+	//
+	// where feature1 is created in the "acme" namespace, so FullConfig would be
+	// "acme__feature1" and "acme__feature1__conditions_default".
+	//
+	// e.g.
+	//
+	// soong_config_variables: {
+	//     board: {
+	//         soc_a: {
+	//             cflags: ["-DSOC_A"],
+	//         },
+	//         soc_b: {
+	//             cflags: ["-DSOC_B"],
+	//         },
+	//         soc_c: {},
+	//         conditions_default: {
+	//             cflags: ["-DSOC_DEFAULT"]
+	//         },
+	//     },
+	// }
+	//
+	// where board is created in the "acme" namespace, so FullConfig would be
+	// "acme__board__soc_a", "acme__board__soc_b", and
+	// "acme__board__conditions_default"
+	FullConfig string
 }
 
-// ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that
-// all it all product variable-specific versions of a property are easily accessed together
-type ProductConfigProperties map[string][]ProductConfigProperty
+func (p *ProductConfigProperty) AlwaysEmit() bool {
+	return p.Namespace != ""
+}
+
+func (p *ProductConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
+	if p.Namespace == "" {
+		return bazel.ProductVariableConfigurationAxis(p.FullConfig)
+	} else {
+		// Soong config variables can be uniquely identified by the namespace
+		// (e.g. acme, android) and the product variable name (e.g. board, size)
+		return bazel.ProductVariableConfigurationAxis(p.Namespace + "__" + p.Name)
+	}
+}
+
+// SelectKey returns the literal string that represents this variable in a BUILD
+// select statement.
+func (p *ProductConfigProperty) SelectKey() string {
+	if p.Namespace == "" {
+		return strings.ToLower(p.FullConfig)
+	}
+
+	if p.FullConfig == bazel.ConditionsDefaultConfigKey {
+		return bazel.ConditionsDefaultConfigKey
+	}
+
+	value := p.FullConfig
+	if value == p.Name {
+		value = ""
+	}
+
+	// e.g. acme__feature1, android__board__soc_a
+	selectKey := strings.ToLower(strings.Join([]string{p.Namespace, p.Name}, "__"))
+	if value != "" {
+		selectKey = strings.ToLower(strings.Join([]string{selectKey, value}, "__"))
+	}
+
+	return selectKey
+}
+
+// ProductConfigProperties is a map of maps to group property values according
+// their property name and the product config variable they're set under.
+//
+// The outer map key is the name of the property, like "cflags".
+//
+// The inner map key is a ProductConfigProperty, which is a struct of product
+// variable name, namespace, and the "full configuration" of the product
+// variable.
+//
+// e.g. product variable name: board, namespace: acme, full config: vendor_chip_foo
+//
+// The value of the map is the interface{} representing the value of the
+// property, like ["-DDEFINES"] for cflags.
+type ProductConfigProperties map[string]map[ProductConfigProperty]interface{}
 
 // ProductVariableProperties returns a ProductConfigProperties containing only the properties which
 // have been set for the module in the given context.
-func ProductVariableProperties(ctx ProductConfigContext) ProductConfigProperties {
+func ProductVariableProperties(ctx BazelConversionPathContext) ProductConfigProperties {
 	module := ctx.Module()
 	moduleBase := module.base()
 
 	productConfigProperties := ProductConfigProperties{}
 
-	if moduleBase.variableProperties == nil {
-		return productConfigProperties
+	if moduleBase.variableProperties != nil {
+		productVariablesProperty := proptools.FieldNameForProperty("product_variables")
+		productVariableValues(
+			productVariablesProperty,
+			moduleBase.variableProperties,
+			"",
+			"",
+			&productConfigProperties)
+
+		for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
+			for config, props := range configToProps {
+				// GetArchVariantProperties is creating an instance of the requested type
+				// and productVariablesValues expects an interface, so no need to cast
+				productVariableValues(
+					productVariablesProperty,
+					props,
+					"",
+					config,
+					&productConfigProperties)
+			}
+		}
 	}
 
-	variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName("Product_variables")
+	if m, ok := module.(Bazelable); ok && m.namespacedVariableProps() != nil {
+		for namespace, namespacedVariableProps := range m.namespacedVariableProps() {
+			for _, namespacedVariableProp := range namespacedVariableProps {
+				productVariableValues(
+					soongconfig.SoongConfigProperty,
+					namespacedVariableProp,
+					namespace,
+					"",
+					&productConfigProperties)
+			}
+		}
+	}
+
+	productConfigProperties.zeroValuesForNamespacedVariables()
+
+	return productConfigProperties
+}
+
+// zeroValuesForNamespacedVariables ensures that selects that contain __only__
+// conditions default values have zero values set for the other non-default
+// values for that select statement.
+//
+// If the ProductConfigProperties map contains these items, as parsed from the .bp file:
+//
+// library_linking_strategy: {
+//     prefer_static: {
+//         static_libs: [
+//             "lib_a",
+//             "lib_b",
+//         ],
+//     },
+//     conditions_default: {
+//         shared_libs: [
+//             "lib_a",
+//             "lib_b",
+//         ],
+//     },
+// },
+//
+// Static_libs {Library_linking_strategy ANDROID prefer_static} [lib_a lib_b]
+// Shared_libs {Library_linking_strategy ANDROID conditions_default} [lib_a lib_b]
+//
+// We need to add this:
+//
+// Shared_libs {Library_linking_strategy ANDROID prefer_static} []
+//
+// so that the following gets generated for the "dynamic_deps" attribute,
+// instead of putting lib_a and lib_b directly into dynamic_deps without a
+// select:
+//
+// dynamic_deps = select({
+//     "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+//     "//conditions:default": [
+//         "//foo/bar:lib_a",
+//         "//foo/bar:lib_b",
+//     ],
+// }),
+func (props *ProductConfigProperties) zeroValuesForNamespacedVariables() {
+	// A map of product config properties to the zero values of their respective
+	// property value.
+	zeroValues := make(map[ProductConfigProperty]interface{})
+
+	// A map of prop names (e.g. cflags) to product config properties where the
+	// (prop name, ProductConfigProperty) tuple contains a non-conditions_default key.
+	//
+	// e.g.
+	//
+	// prefer_static: {
+	//     static_libs: [
+	//         "lib_a",
+	//         "lib_b",
+	//     ],
+	// },
+	// conditions_default: {
+	//     shared_libs: [
+	//         "lib_a",
+	//         "lib_b",
+	//     ],
+	// },
+	//
+	// The tuple of ("static_libs", prefer_static) would be in this map.
+	hasNonDefaultValue := make(map[string]map[ProductConfigProperty]bool)
+
+	// Iterate over all added soong config variables.
+	for propName, v := range *props {
+		for p, intf := range v {
+			if p.Namespace == "" {
+				// If there's no namespace, this isn't a soong config variable,
+				// i.e. this is a product variable. product variables have no
+				// conditions_defaults, so skip them.
+				continue
+			}
+			if p.FullConfig == bazel.ConditionsDefaultConfigKey {
+				// Skip conditions_defaults.
+				continue
+			}
+			if hasNonDefaultValue[propName] == nil {
+				hasNonDefaultValue[propName] = make(map[ProductConfigProperty]bool)
+				hasNonDefaultValue[propName][p] = false
+			}
+			// Create the zero value of the variable.
+			if _, exists := zeroValues[p]; !exists {
+				zeroValue := reflect.Zero(reflect.ValueOf(intf).Type()).Interface()
+				if zeroValue == nil {
+					panic(fmt.Errorf("Expected non-nil zero value for product/config variable %+v\n", intf))
+				}
+				zeroValues[p] = zeroValue
+			}
+			hasNonDefaultValue[propName][p] = true
+		}
+	}
+
+	for propName := range *props {
+		for p, zeroValue := range zeroValues {
+			// Ignore variables that already have a non-default value for that axis
+			if exists, _ := hasNonDefaultValue[propName][p]; !exists {
+				// fmt.Println(propName, p.Namespace, p.Name, p.FullConfig, zeroValue)
+				// Insert the zero value for this propname + product config value.
+				props.AddProductConfigProperty(
+					propName,
+					p.Namespace,
+					p.Name,
+					p.FullConfig,
+					zeroValue,
+				)
+			}
+		}
+	}
+}
+
+func (p *ProductConfigProperties) AddProductConfigProperty(
+	propertyName, namespace, productVariableName, config string, property interface{}) {
+	if (*p)[propertyName] == nil {
+		(*p)[propertyName] = make(map[ProductConfigProperty]interface{})
+	}
+
+	productConfigProp := ProductConfigProperty{
+		Namespace:  namespace,           // e.g. acme, android
+		Name:       productVariableName, // e.g. size, feature1, feature2, FEATURE3, board
+		FullConfig: config,              // e.g. size, feature1-x86, size__conditions_default
+	}
+
+	if existing, ok := (*p)[propertyName][productConfigProp]; ok && namespace != "" {
+		switch dst := existing.(type) {
+		case []string:
+			if src, ok := property.([]string); ok {
+				dst = append(dst, src...)
+				(*p)[propertyName][productConfigProp] = dst
+			}
+		default:
+			panic(fmt.Errorf("TODO: handle merging value %s", existing))
+		}
+	} else {
+		(*p)[propertyName][productConfigProp] = property
+	}
+}
+
+var (
+	conditionsDefaultField string = proptools.FieldNameForProperty(bazel.ConditionsDefaultConfigKey)
+)
+
+// maybeExtractConfigVarProp attempts to read this value as a config var struct
+// wrapped by interfaces and ptrs. If it's not the right type, the second return
+// value is false.
+func maybeExtractConfigVarProp(v reflect.Value) (reflect.Value, bool) {
+	if v.Kind() == reflect.Interface {
+		// The conditions_default value can be either
+		// 1) an ptr to an interface of a struct (bool config variables and product variables)
+		// 2) an interface of 1) (config variables with nested structs, like string vars)
+		v = v.Elem()
+	}
+	if v.Kind() != reflect.Ptr {
+		return v, false
+	}
+	v = reflect.Indirect(v)
+	if v.Kind() == reflect.Interface {
+		// Extract the struct from the interface
+		v = v.Elem()
+	}
+
+	if !v.IsValid() {
+		return v, false
+	}
+
+	if v.Kind() != reflect.Struct {
+		return v, false
+	}
+	return v, true
+}
+
+func (productConfigProperties *ProductConfigProperties) AddProductConfigProperties(namespace, suffix string, variableValues reflect.Value) {
+	// variableValues can either be a product_variables or
+	// soong_config_variables struct.
+	//
+	// Example of product_variables:
+	//
+	// product_variables: {
+	//     malloc_not_svelte: {
+	//         shared_libs: ["malloc_not_svelte_shared_lib"],
+	//         whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
+	//         exclude_static_libs: [
+	//             "malloc_not_svelte_static_lib_excludes",
+	//             "malloc_not_svelte_whole_static_lib_excludes",
+	//         ],
+	//     },
+	// },
+	//
+	// Example of soong_config_variables:
+	//
+	// soong_config_variables: {
+	//      feature1: {
+	//        	conditions_default: {
+	//               ...
+	//          },
+	//          cflags: ...
+	//      },
+	//      feature2: {
+	//          cflags: ...
+	//        	conditions_default: {
+	//               ...
+	//          },
+	//      },
+	//      board: {
+	//         soc_a: {
+	//             ...
+	//         },
+	//         soc_a: {
+	//             ...
+	//         },
+	//         soc_c: {},
+	//         conditions_default: {
+	//              ...
+	//         },
+	//      },
+	// }
 	for i := 0; i < variableValues.NumField(); i++ {
+		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
+		productVariableName := variableValues.Type().Field(i).Name
+
 		variableValue := variableValues.Field(i)
 		// Check if any properties were set for the module
 		if variableValue.IsZero() {
+			// e.g. feature1: {}, malloc_not_svelte: {}
 			continue
 		}
-		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
-		productVariableName := variableValues.Type().Field(i).Name
+
+		// Unlike product variables, config variables require a few more
+		// indirections to extract the struct from the reflect.Value.
+		if v, ok := maybeExtractConfigVarProp(variableValue); ok {
+			variableValue = v
+		}
+
 		for j := 0; j < variableValue.NumField(); j++ {
 			property := variableValue.Field(j)
 			// If the property wasn't set, no need to pass it along
@@ -498,15 +931,72 @@
 
 			// e.g. Asflags, Cflags, Enabled, etc.
 			propertyName := variableValue.Type().Field(j).Name
-			productConfigProperties[propertyName] = append(productConfigProperties[propertyName],
-				ProductConfigProperty{
-					ProductConfigVariable: productVariableName,
-					Property:              property.Interface(),
-				})
+
+			if v, ok := maybeExtractConfigVarProp(property); ok {
+				// The field is a struct, which is used by:
+				// 1) soong_config_string_variables
+				//
+				// soc_a: {
+				//     cflags: ...,
+				// }
+				//
+				// soc_b: {
+				//     cflags: ...,
+				// }
+				//
+				// 2) conditions_default structs for all soong config variable types.
+				//
+				// conditions_default: {
+				//     cflags: ...,
+				//     static_libs: ...
+				// }
+				field := v
+				for k := 0; k < field.NumField(); k++ {
+					// Iterate over fields of this struct prop.
+					if field.Field(k).IsZero() {
+						continue
+					}
+					// config can also be "conditions_default".
+					config := proptools.PropertyNameForField(propertyName)
+					actualPropertyName := field.Type().Field(k).Name
+
+					productConfigProperties.AddProductConfigProperty(
+						actualPropertyName,  // e.g. cflags, static_libs
+						namespace,           // e.g. acme, android
+						productVariableName, // e.g. size, feature1, FEATURE2, board
+						config,
+						field.Field(k).Interface(), // e.g. ["-DDEFAULT"], ["foo", "bar"]
+					)
+				}
+			} else if property.Kind() != reflect.Interface {
+				// If not an interface, then this is not a conditions_default or
+				// a struct prop. That is, this is a regular product variable,
+				// or a bool/value config variable.
+				config := productVariableName + suffix
+				productConfigProperties.AddProductConfigProperty(
+					propertyName,
+					namespace,
+					productVariableName,
+					config,
+					property.Interface(),
+				)
+			}
 		}
 	}
+}
 
-	return productConfigProperties
+// productVariableValues uses reflection to convert a property struct for
+// product_variables and soong_config_variables to structs that can be generated
+// as select statements.
+func productVariableValues(
+	fieldName string, variableProps interface{}, namespace, suffix string, productConfigProperties *ProductConfigProperties) {
+	if suffix != "" {
+		suffix = "-" + suffix
+	}
+
+	// variableValues represent the product_variables or soong_config_variables struct.
+	variableValues := reflect.ValueOf(variableProps).Elem().FieldByName(fieldName)
+	productConfigProperties.AddProductConfigProperties(namespace, suffix, variableValues)
 }
 
 func VariableMutator(mctx BottomUpMutatorContext) {
@@ -558,7 +1048,7 @@
 
 	printfIntoProperties(ctx, prefix, productVariablePropertyValue, variableValue)
 
-	err := proptools.AppendMatchingProperties(m.generalProperties,
+	err := proptools.AppendMatchingProperties(m.GetProperties(),
 		productVariablePropertyValue.Addr().Interface(), nil)
 	if err != nil {
 		if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
diff --git a/android/visibility_test.go b/android/visibility_test.go
index ffd7909..714c92a 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -16,7 +16,7 @@
 	{
 		name: "invalid visibility: empty list",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [],
@@ -27,7 +27,7 @@
 	{
 		name: "invalid visibility: empty rule",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [""],
@@ -38,7 +38,7 @@
 	{
 		name: "invalid visibility: unqualified",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["target"],
@@ -49,7 +49,7 @@
 	{
 		name: "invalid visibility: empty namespace",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//"],
@@ -60,7 +60,7 @@
 	{
 		name: "invalid visibility: empty module",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [":"],
@@ -71,7 +71,7 @@
 	{
 		name: "invalid visibility: empty namespace and module",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//:"],
@@ -82,7 +82,7 @@
 	{
 		name: "//visibility:unknown",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:unknown"],
@@ -93,7 +93,7 @@
 	{
 		name: "//visibility:xxx mixed",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:public", "//namespace"],
@@ -114,7 +114,7 @@
 	{
 		name: "//visibility:legacy_public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:legacy_public"],
@@ -130,7 +130,7 @@
 		// the current directory, a nested directory and a directory in a separate tree.
 		name: "//visibility:public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:public"],
@@ -140,12 +140,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -157,7 +157,7 @@
 		// directory only.
 		name: "//visibility:private",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//visibility:private"],
@@ -167,12 +167,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -189,7 +189,7 @@
 		// Verify that :__pkg__ allows the module to be referenced from the current directory only.
 		name: ":__pkg__",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [":__pkg__"],
@@ -199,12 +199,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -222,7 +222,7 @@
 		// the top/nested directory only, not a subdirectory of top/nested and not peak directory.
 		name: "//top/nested",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//top/nested"],
@@ -232,17 +232,17 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"top/nested/again/Blueprints": []byte(`
+			"top/nested/again/Android.bp": []byte(`
 				mock_library {
 					name: "libnestedagain",
 					deps: ["libexample"],
 				}`),
-			"peak/Blueprints": []byte(`
+			"peak/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -260,7 +260,7 @@
 		// and sub directories but nowhere else.
 		name: ":__subpackages__",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: [":__subpackages__"],
@@ -270,12 +270,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"peak/other/Blueprints": []byte(`
+			"peak/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -291,7 +291,7 @@
 		// directory and sub directories but nowhere else.
 		name: "//top/nested:__subpackages__",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//top/nested:__subpackages__", "//other"],
@@ -301,12 +301,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -322,7 +322,7 @@
 		// the current directory, top/nested and peak and all its subpackages.
 		name: `["//top/nested", "//peak:__subpackages__"]`,
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//top/nested", "//peak:__subpackages__"],
@@ -332,12 +332,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"peak/other/Blueprints": []byte(`
+			"peak/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -348,7 +348,7 @@
 		// Verify that //vendor... cannot be used outside vendor apart from //vendor:__subpackages__
 		name: `//vendor`,
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//vendor:__subpackages__"],
@@ -358,13 +358,13 @@
 					name: "libsamepackage",
 					visibility: ["//vendor/apps/AcmeSettings"],
 				}`),
-			"vendor/Blueprints": []byte(`
+			"vendor/Android.bp": []byte(`
 				mock_library {
 					name: "libvendorexample",
 					deps: ["libexample"],
 					visibility: ["//vendor/nested"],
 				}`),
-			"vendor/nested/Blueprints": []byte(`
+			"vendor/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libvendornested",
 					deps: ["libexample", "libvendorexample"],
@@ -382,7 +382,7 @@
 		// Check that visibility is the union of the defaults modules.
 		name: "defaults union, basic",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//other"],
@@ -396,17 +396,17 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -420,7 +420,7 @@
 	{
 		name: "defaults union, multiple defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults_1",
 					visibility: ["//other"],
@@ -437,17 +437,17 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -461,7 +461,7 @@
 	{
 		name: "//visibility:public mixed with other in defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:public", "//namespace"],
@@ -479,7 +479,7 @@
 	{
 		name: "//visibility:public overriding defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//namespace"],
@@ -489,7 +489,7 @@
 					visibility: ["//visibility:public"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -502,7 +502,7 @@
 	{
 		name: "//visibility:public mixed with other from different defaults 1",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults_1",
 					visibility: ["//namespace"],
@@ -515,7 +515,7 @@
 					name: "libexample",
 					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -525,7 +525,7 @@
 	{
 		name: "//visibility:public mixed with other from different defaults 2",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults_1",
 					visibility: ["//visibility:public"],
@@ -538,7 +538,7 @@
 					name: "libexample",
 					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -548,7 +548,7 @@
 	{
 		name: "//visibility:private in defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private"],
@@ -561,12 +561,12 @@
 					name: "libsamepackage",
 					deps: ["libexample"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -582,7 +582,7 @@
 	{
 		name: "//visibility:private mixed with other in defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private", "//namespace"],
@@ -600,7 +600,7 @@
 	{
 		name: "//visibility:private overriding defaults",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//namespace"],
@@ -619,7 +619,7 @@
 	{
 		name: "//visibility:private in defaults overridden",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private"],
@@ -638,7 +638,7 @@
 	{
 		name: "//visibility:private override //visibility:public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:public"],
@@ -656,7 +656,7 @@
 	{
 		name: "//visibility:public override //visibility:private",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private"],
@@ -674,7 +674,7 @@
 	{
 		name: "//visibility:override must be first in the list",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_library {
 					name: "libexample",
 					visibility: ["//other", "//visibility:override", "//namespace"],
@@ -687,7 +687,7 @@
 	{
 		name: "//visibility:override discards //visibility:private",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private"],
@@ -698,7 +698,7 @@
 					visibility: ["//visibility:override", "//other"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
@@ -708,7 +708,7 @@
 	{
 		name: "//visibility:override discards //visibility:public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:public"],
@@ -719,12 +719,12 @@
 					visibility: ["//visibility:override", "//other"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
 				}`),
-			"namespace/Blueprints": []byte(`
+			"namespace/Android.bp": []byte(`
 				mock_library {
 					name: "libnamespace",
 					deps: ["libexample"],
@@ -737,7 +737,7 @@
 	{
 		name: "//visibility:override discards defaults supplied rules",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//namespace"],
@@ -748,12 +748,12 @@
 					visibility: ["//visibility:override", "//other"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"other/Blueprints": []byte(`
+			"other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libexample"],
 				}`),
-			"namespace/Blueprints": []byte(`
+			"namespace/Android.bp": []byte(`
 				mock_library {
 					name: "libnamespace",
 					deps: ["libexample"],
@@ -766,7 +766,7 @@
 	{
 		name: "//visibility:override can override //visibility:public with //visibility:private",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:public"],
@@ -776,7 +776,7 @@
 					visibility: ["//visibility:override", "//visibility:private"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"namespace/Blueprints": []byte(`
+			"namespace/Android.bp": []byte(`
 				mock_library {
 					name: "libnamespace",
 					deps: ["libexample"],
@@ -789,7 +789,7 @@
 	{
 		name: "//visibility:override can override //visibility:private with //visibility:public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults",
 					visibility: ["//visibility:private"],
@@ -799,7 +799,7 @@
 					visibility: ["//visibility:override", "//visibility:public"],
 					defaults: ["libexample_defaults"],
 				}`),
-			"namespace/Blueprints": []byte(`
+			"namespace/Android.bp": []byte(`
 				mock_library {
 					name: "libnamespace",
 					deps: ["libexample"],
@@ -809,7 +809,7 @@
 	{
 		name: "//visibility:private mixed with itself",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "libexample_defaults_1",
 					visibility: ["//visibility:private"],
@@ -823,7 +823,7 @@
 					visibility: ["//visibility:private"],
 					defaults: ["libexample_defaults_1", "libexample_defaults_2"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -839,7 +839,7 @@
 	{
 		name: "defaults_visibility invalid",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_defaults {
 					name: "top_defaults",
 					defaults_visibility: ["//visibility:invalid"],
@@ -852,7 +852,7 @@
 	{
 		name: "defaults_visibility overrides package default",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:private"],
 				}
@@ -860,7 +860,7 @@
 					name: "top_defaults",
 					defaults_visibility: ["//visibility:public"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					defaults: ["top_defaults"],
@@ -872,7 +872,7 @@
 	{
 		name: "package default_visibility property is checked",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:invalid"],
 				}`),
@@ -883,7 +883,7 @@
 		// This test relies on the default visibility being legacy_public.
 		name: "package default_visibility property used when no visibility specified",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:private"],
 				}
@@ -891,7 +891,7 @@
 				mock_library {
 					name: "libexample",
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -905,7 +905,7 @@
 	{
 		name: "package default_visibility public does not override visibility private",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:public"],
 				}
@@ -914,7 +914,7 @@
 					name: "libexample",
 					visibility: ["//visibility:private"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -928,7 +928,7 @@
 	{
 		name: "package default_visibility private does not override visibility public",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:private"],
 				}
@@ -937,7 +937,7 @@
 					name: "libexample",
 					visibility: ["//visibility:public"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -947,7 +947,7 @@
 	{
 		name: "package default_visibility :__subpackages__",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: [":__subpackages__"],
 				}
@@ -955,12 +955,12 @@
 				mock_library {
 					name: "libexample",
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample"],
@@ -974,7 +974,7 @@
 	{
 		name: "package default_visibility inherited to subpackages",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//outsider"],
 				}
@@ -983,12 +983,12 @@
 					name: "libexample",
           visibility: [":__subpackages__"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libexample"],
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libexample", "libnested"],
@@ -1002,11 +1002,11 @@
 	{
 		name: "package default_visibility inherited to subpackages",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				package {
 					default_visibility: ["//visibility:private"],
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				package {
 					default_visibility: ["//outsider"],
 				}
@@ -1014,11 +1014,11 @@
 				mock_library {
 					name: "libnested",
 				}`),
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 				}`),
-			"outsider/Blueprints": []byte(`
+			"outsider/Android.bp": []byte(`
 				mock_library {
 					name: "liboutsider",
 					deps: ["libother", "libnested"],
@@ -1032,19 +1032,19 @@
 	{
 		name: "verify that prebuilt dependencies are ignored for visibility reasons (not preferred)",
 		fs: MockFS{
-			"prebuilts/Blueprints": []byte(`
+			"prebuilts/Android.bp": []byte(`
 				prebuilt {
 					name: "module",
 					visibility: ["//top/other"],
 				}`),
 			"top/sources/source_file": nil,
-			"top/sources/Blueprints": []byte(`
+			"top/sources/Android.bp": []byte(`
 				source {
 					name: "module",
 					visibility: ["//top/other"],
 				}`),
 			"top/other/source_file": nil,
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				source {
 					name: "other",
 					deps: [":module"],
@@ -1054,20 +1054,20 @@
 	{
 		name: "verify that prebuilt dependencies are ignored for visibility reasons (preferred)",
 		fs: MockFS{
-			"prebuilts/Blueprints": []byte(`
+			"prebuilts/Android.bp": []byte(`
 				prebuilt {
 					name: "module",
 					visibility: ["//top/other"],
 					prefer: true,
 				}`),
 			"top/sources/source_file": nil,
-			"top/sources/Blueprints": []byte(`
+			"top/sources/Android.bp": []byte(`
 				source {
 					name: "module",
 					visibility: ["//top/other"],
 				}`),
 			"top/other/source_file": nil,
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				source {
 					name: "other",
 					deps: [":module"],
@@ -1077,7 +1077,7 @@
 	{
 		name: "ensure visibility properties are checked for correctness",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_parent {
 					name: "parent",
 					visibility: ["//top/nested"],
@@ -1094,7 +1094,7 @@
 	{
 		name: "invalid visibility added to child detected during gather phase",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_parent {
 					name: "parent",
 					visibility: ["//top/nested"],
@@ -1116,7 +1116,7 @@
 	{
 		name: "automatic visibility inheritance enabled",
 		fs: MockFS{
-			"top/Blueprints": []byte(`
+			"top/Android.bp": []byte(`
 				mock_parent {
 					name: "parent",
 					visibility: ["//top/nested"],
@@ -1125,12 +1125,12 @@
 						visibility: ["//top/other"],
 					},
 				}`),
-			"top/nested/Blueprints": []byte(`
+			"top/nested/Android.bp": []byte(`
 				mock_library {
 					name: "libnested",
 					deps: ["libchild"],
 				}`),
-			"top/other/Blueprints": []byte(`
+			"top/other/Android.bp": []byte(`
 				mock_library {
 					name: "libother",
 					deps: ["libchild"],
diff --git a/android/writedocs.go b/android/writedocs.go
deleted file mode 100644
index 67b9aa3..0000000
--- a/android/writedocs.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2015 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package android
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/google/blueprint"
-)
-
-func init() {
-	RegisterSingletonType("writedocs", DocsSingleton)
-}
-
-func DocsSingleton() Singleton {
-	return &docsSingleton{}
-}
-
-type docsSingleton struct{}
-
-func primaryBuilderPath(ctx SingletonContext) Path {
-	buildDir := absolutePath(ctx.Config().BuildDir())
-	binary := absolutePath(os.Args[0])
-	primaryBuilder, err := filepath.Rel(buildDir, binary)
-	if err != nil {
-		ctx.Errorf("path to primary builder %q is not in build dir %q (%q)",
-			os.Args[0], ctx.Config().BuildDir(), err)
-	}
-
-	return PathForOutput(ctx, primaryBuilder)
-}
-
-func (c *docsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	var deps Paths
-	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().moduleListFile))
-	deps = append(deps, pathForBuildToolDep(ctx, ctx.Config().ProductVariablesFileName))
-
-	// The dexpreopt configuration may not exist, but if it does, it's a dependency
-	// of soong_build.
-	dexpreoptConfigPath := ctx.Config().DexpreoptGlobalConfigPath(ctx)
-	if dexpreoptConfigPath.Valid() {
-		deps = append(deps, dexpreoptConfigPath.Path())
-	}
-
-	// Generate build system docs for the primary builder.  Generating docs reads the source
-	// files used to build the primary builder, but that dependency will be picked up through
-	// the dependency on the primary builder itself.  There are no dependencies on the
-	// Blueprints files, as any relevant changes to the Blueprints files would have caused
-	// a rebuild of the primary builder.
-	docsFile := PathForOutput(ctx, "docs", "soong_build.html")
-	primaryBuilder := primaryBuilderPath(ctx)
-	soongDocs := ctx.Rule(pctx, "soongDocs",
-		blueprint.RuleParams{
-			Command: fmt.Sprintf("rm -f ${outDir}/* && %s --soong_docs %s %s",
-				primaryBuilder.String(),
-				docsFile.String(),
-				"\""+strings.Join(os.Args[1:], "\" \"")+"\""),
-			CommandDeps: []string{primaryBuilder.String()},
-			Description: fmt.Sprintf("%s docs $out", primaryBuilder.Base()),
-		},
-		"outDir")
-
-	ctx.Build(pctx, BuildParams{
-		Rule:   soongDocs,
-		Output: docsFile,
-		Inputs: deps,
-		Args: map[string]string{
-			"outDir": PathForOutput(ctx, "docs").String(),
-		},
-	})
-
-	// Add a phony target for building the documentation
-	ctx.Phony("soong_docs", docsFile)
-}
diff --git a/android_sdk/Android.bp b/android_sdk/Android.bp
new file mode 100644
index 0000000..e686d59
--- /dev/null
+++ b/android_sdk/Android.bp
@@ -0,0 +1,22 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-android-sdk",
+    pkgPath: "android/soong/android_sdk",
+    deps: [
+        "blueprint",
+        "soong",
+        "soong-android",
+        "soong-cc",
+        "soong-cc-config",
+    ],
+    srcs: [
+        "sdk_repo_host.go",
+    ],
+    testSrcs: [
+        "sdk_repo_host_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/android_sdk/sdk_repo_host.go b/android_sdk/sdk_repo_host.go
new file mode 100644
index 0000000..280dae8
--- /dev/null
+++ b/android_sdk/sdk_repo_host.go
@@ -0,0 +1,292 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android_sdk
+
+import (
+	"fmt"
+	"io"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/cc/config"
+)
+
+var pctx = android.NewPackageContext("android/soong/android_sdk")
+
+func init() {
+	registerBuildComponents(android.InitRegistrationContext)
+}
+
+func registerBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_sdk_repo_host", SdkRepoHostFactory)
+}
+
+type sdkRepoHost struct {
+	android.ModuleBase
+	android.PackagingBase
+
+	properties sdkRepoHostProperties
+
+	outputBaseName string
+	outputFile     android.OptionalPath
+}
+
+type remapProperties struct {
+	From string
+	To   string
+}
+
+type sdkRepoHostProperties struct {
+	// The top level directory to use for the SDK repo.
+	Base_dir *string
+
+	// List of src:dst mappings to rename files from `deps`.
+	Deps_remap []remapProperties `android:"arch_variant"`
+
+	// List of zip files to merge into the SDK repo.
+	Merge_zips []string `android:"arch_variant,path"`
+
+	// List of sources to include into the SDK repo. These are usually raw files, filegroups,
+	// or genrules, as most built modules should be referenced via `deps`.
+	Srcs []string `android:"arch_variant,path"`
+
+	// List of files to strip. This should be a list of files, not modules. This happens after
+	// `deps_remap` and `merge_zips` are applied, but before the `base_dir` is added.
+	Strip_files []string `android:"arch_variant"`
+}
+
+// android_sdk_repo_host defines an Android SDK repo containing host tools.
+//
+// This implementation is trying to be a faithful reproduction of how these sdk-repos were produced
+// in the Make system, which may explain some of the oddities (like `strip_files` not being
+// automatic)
+func SdkRepoHostFactory() android.Module {
+	return newSdkRepoHostModule()
+}
+
+func newSdkRepoHostModule() *sdkRepoHost {
+	s := &sdkRepoHost{}
+	s.AddProperties(&s.properties)
+	android.InitPackageModule(s)
+	android.InitAndroidMultiTargetsArchModule(s, android.HostSupported, android.MultilibCommon)
+	return s
+}
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	android.PackagingItemAlwaysDepTag
+}
+
+// TODO(b/201696252): Evaluate whether licenses should be propagated through this dependency.
+func (d dependencyTag) PropagateLicenses() bool {
+	return false
+}
+
+var depTag = dependencyTag{}
+
+func (s *sdkRepoHost) DepsMutator(ctx android.BottomUpMutatorContext) {
+	s.AddDeps(ctx, depTag)
+}
+
+func (s *sdkRepoHost) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	dir := android.PathForModuleOut(ctx, "zip")
+	outputZipFile := dir.Join(ctx, "output.zip")
+	builder := android.NewRuleBuilder(pctx, ctx).
+		Sbox(dir, android.PathForModuleOut(ctx, "out.sbox.textproto")).
+		SandboxInputs()
+
+	// Get files from modules listed in `deps`
+	packageSpecs := s.GatherPackagingSpecs(ctx)
+
+	// Handle `deps_remap` renames
+	err := remapPackageSpecs(packageSpecs, s.properties.Deps_remap)
+	if err != nil {
+		ctx.PropertyErrorf("deps_remap", "%s", err.Error())
+	}
+
+	s.CopySpecsToDir(ctx, builder, packageSpecs, dir)
+
+	noticeFile := android.PathForModuleOut(ctx, "NOTICES.txt")
+	android.BuildNoticeTextOutputFromLicenseMetadata(
+		ctx, noticeFile, "", "",
+		[]string{
+			android.PathForModuleInstall(ctx, "sdk-repo").String() + "/",
+			outputZipFile.String(),
+		})
+	builder.Command().Text("cp").
+		Input(noticeFile).
+		Text(filepath.Join(dir.String(), "NOTICE.txt"))
+
+	// Handle `merge_zips` by extracting their contents into our tmpdir
+	for _, zip := range android.PathsForModuleSrc(ctx, s.properties.Merge_zips) {
+		builder.Command().
+			Text("unzip").
+			Flag("-DD").
+			Flag("-q").
+			FlagWithArg("-d ", dir.String()).
+			Input(zip)
+	}
+
+	// Copy files from `srcs` into our tmpdir
+	for _, src := range android.PathsForModuleSrc(ctx, s.properties.Srcs) {
+		builder.Command().
+			Text("cp").Input(src).Flag(dir.Join(ctx, src.Rel()).String())
+	}
+
+	// Handle `strip_files` by calling the necessary strip commands
+	//
+	// Note: this stripping logic was copied over from the old Make implementation
+	// It's not using the same flags as the regular stripping support, nor does it
+	// support the array of per-module stripping options. It would be nice if we
+	// pulled the stripped versions from the CC modules, but that doesn't exist
+	// for host tools today. (And not all the things we strip are CC modules today)
+	if ctx.Darwin() {
+		macStrip := config.MacStripPath(ctx)
+		for _, strip := range s.properties.Strip_files {
+			builder.Command().
+				Text(macStrip).Flag("-x").
+				Flag(dir.Join(ctx, strip).String())
+		}
+	} else {
+		llvmStrip := config.ClangPath(ctx, "bin/llvm-strip")
+		llvmLib := config.ClangPath(ctx, "lib64/libc++.so.1")
+		for _, strip := range s.properties.Strip_files {
+			cmd := builder.Command().Tool(llvmStrip).ImplicitTool(llvmLib)
+			if !ctx.Windows() {
+				cmd.Flag("-x")
+			}
+			cmd.Flag(dir.Join(ctx, strip).String())
+		}
+	}
+
+	// Fix up the line endings of all text files. This also removes executable permissions.
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("-name '*.aidl' -o -name '*.css' -o -name '*.html' -o -name '*.java'").
+		Flag("-o -name '*.js' -o -name '*.prop' -o -name '*.template'").
+		Flag("-o -name '*.txt' -o -name '*.windows' -o -name '*.xml' -print0").
+		// Using -n 500 for xargs to limit the max number of arguments per call to line_endings
+		// to 500. This avoids line_endings failing with "arguments too long".
+		Text("| xargs -0 -n 500 ").
+		BuiltTool("line_endings").
+		Flag("unix")
+
+	// Exclude some file types (roughly matching sdk.exclude.atree)
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("'('").
+		Flag("-name '.*' -o -name '*~' -o -name 'Makefile' -o -name 'Android.mk' -o").
+		Flag("-name '.*.swp' -o -name '.DS_Store' -o -name '*.pyc' -o -name 'OWNERS' -o").
+		Flag("-name 'MODULE_LICENSE_*' -o -name '*.ezt' -o -name 'Android.bp'").
+		Flag("')' -print0").
+		Text("| xargs -0 -r rm -rf")
+	builder.Command().
+		Text("find").
+		Flag(dir.String()).
+		Flag("-name '_*' ! -name '__*' -print0").
+		Text("| xargs -0 -r rm -rf")
+
+	if ctx.Windows() {
+		// Fix EOL chars to make window users happy
+		builder.Command().
+			Text("find").
+			Flag(dir.String()).
+			Flag("-maxdepth 2 -name '*.bat' -type f -print0").
+			Text("| xargs -0 -r unix2dos")
+	}
+
+	// Zip up our temporary directory as the sdk-repo
+	builder.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", outputZipFile).
+		FlagWithArg("-P ", proptools.StringDefault(s.properties.Base_dir, ".")).
+		FlagWithArg("-C ", dir.String()).
+		FlagWithArg("-D ", dir.String())
+	builder.Command().Text("rm").Flag("-rf").Text(dir.String())
+
+	builder.Build("build_sdk_repo", "Creating sdk-repo-"+s.BaseModuleName())
+
+	osName := ctx.Os().String()
+	if osName == "linux_glibc" {
+		osName = "linux"
+	}
+	name := fmt.Sprintf("sdk-repo-%s-%s", osName, s.BaseModuleName())
+
+	s.outputBaseName = name
+	s.outputFile = android.OptionalPathForPath(outputZipFile)
+	ctx.InstallFile(android.PathForModuleInstall(ctx, "sdk-repo"), name+".zip", outputZipFile)
+}
+
+func (s *sdkRepoHost) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			fmt.Fprintln(w, ".PHONY:", name, "sdk_repo", "sdk-repo-"+name)
+			fmt.Fprintln(w, "sdk_repo", "sdk-repo-"+name+":", strings.Join(s.FilesToInstall().Strings(), " "))
+
+			fmt.Fprintf(w, "$(call dist-for-goals,sdk_repo sdk-repo-%s,%s:%s-$(FILE_NAME_TAG).zip)\n\n", s.BaseModuleName(), s.outputFile.String(), s.outputBaseName)
+		},
+	}
+}
+
+func remapPackageSpecs(specs map[string]android.PackagingSpec, remaps []remapProperties) error {
+	for _, remap := range remaps {
+		for path, spec := range specs {
+			if match, err := pathtools.Match(remap.From, path); err != nil {
+				return fmt.Errorf("Error parsing %q: %v", remap.From, err)
+			} else if match {
+				newPath := remap.To
+				if pathtools.IsGlob(remap.From) {
+					rel, err := filepath.Rel(constantPartOfPattern(remap.From), path)
+					if err != nil {
+						return fmt.Errorf("Error handling %q", path)
+					}
+					newPath = filepath.Join(remap.To, rel)
+				}
+				delete(specs, path)
+				spec.SetRelPathInPackage(newPath)
+				specs[newPath] = spec
+			}
+		}
+	}
+	return nil
+}
+
+func constantPartOfPattern(pattern string) string {
+	ret := ""
+	for pattern != "" {
+		var first string
+		first, pattern = splitFirst(pattern)
+		if pathtools.IsGlob(first) {
+			return ret
+		}
+		ret = filepath.Join(ret, first)
+	}
+	return ret
+}
+
+func splitFirst(path string) (string, string) {
+	i := strings.IndexRune(path, filepath.Separator)
+	if i < 0 {
+		return path, ""
+	}
+	return path[:i], path[i+1:]
+}
diff --git a/android_sdk/sdk_repo_host_test.go b/android_sdk/sdk_repo_host_test.go
new file mode 100644
index 0000000..0688921
--- /dev/null
+++ b/android_sdk/sdk_repo_host_test.go
@@ -0,0 +1,124 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android_sdk
+
+import (
+	"fmt"
+	"runtime"
+	"sort"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+
+	"github.com/google/blueprint/pathtools"
+)
+
+var fixture = android.GroupFixturePreparers(
+	android.PrepareForIntegrationTestWithAndroid,
+	cc.PrepareForIntegrationTestWithCc,
+	android.FixtureRegisterWithContext(registerBuildComponents),
+)
+
+func TestSdkRepoHostDeps(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skipf("Skipping sdk_repo_host testing that is only supported on linux not %s", runtime.GOOS)
+	}
+
+	result := fixture.RunTestWithBp(t, `
+		android_sdk_repo_host {
+			name: "platform-tools",
+		}
+	`)
+
+	// produces "sdk-repo-{OS}-platform-tools.zip"
+	result.ModuleForTests("platform-tools", "linux_glibc_common").Output("sdk-repo-linux-platform-tools.zip")
+}
+
+func TestRemapPackageSpecs(t *testing.T) {
+	testcases := []struct {
+		name   string
+		input  []string
+		remaps []remapProperties
+		output []string
+		err    string
+	}{
+		{
+			name:  "basic remap",
+			input: []string{"a", "c"},
+			remaps: []remapProperties{
+				{From: "a", To: "b"},
+			},
+			output: []string{"b", "c"},
+		},
+		{
+			name:  "non-matching remap",
+			input: []string{"a"},
+			remaps: []remapProperties{
+				{From: "b", To: "c"},
+			},
+			output: []string{"a"},
+		},
+		{
+			name:  "glob",
+			input: []string{"bin/d", "liba.so", "libb.so", "lib/c.so"},
+			remaps: []remapProperties{
+				{From: "lib*.so", To: "lib/"},
+			},
+			output: []string{"bin/d", "lib/c.so", "lib/liba.so", "lib/libb.so"},
+		},
+		{
+			name:  "bad glob",
+			input: []string{"a"},
+			remaps: []remapProperties{
+				{From: "**", To: "./"},
+			},
+			err: fmt.Sprintf("Error parsing \"**\": %v", pathtools.GlobLastRecursiveErr.Error()),
+		},
+		{
+			name:  "globbed dirs",
+			input: []string{"a/b/c"},
+			remaps: []remapProperties{
+				{From: "a/*/c", To: "./"},
+			},
+			output: []string{"b/c"},
+		},
+	}
+
+	for _, test := range testcases {
+		t.Run(test.name, func(t *testing.T) {
+			specs := map[string]android.PackagingSpec{}
+			for _, input := range test.input {
+				spec := android.PackagingSpec{}
+				spec.SetRelPathInPackage(input)
+				specs[input] = spec
+			}
+
+			err := remapPackageSpecs(specs, test.remaps)
+
+			if test.err != "" {
+				android.AssertErrorMessageEquals(t, "", test.err, err)
+			} else {
+				outputs := []string{}
+				for path, spec := range specs {
+					android.AssertStringEquals(t, "path does not match rel path", path, spec.RelPathInPackage())
+					outputs = append(outputs, path)
+				}
+				sort.Strings(outputs)
+				android.AssertArrayString(t, "outputs mismatch", test.output, outputs)
+			}
+		})
+	}
+}
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 5316d7b..954f8d0 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -15,11 +15,13 @@
 package androidmk
 
 import (
-	mkparser "android/soong/androidmk/parser"
 	"fmt"
 	"sort"
+	"strconv"
 	"strings"
 
+	mkparser "android/soong/androidmk/parser"
+
 	bpparser "github.com/google/blueprint/parser"
 )
 
@@ -57,6 +59,7 @@
 	"LOCAL_MODULE_STEM":                    stem,
 	"LOCAL_MODULE_HOST_OS":                 hostOs,
 	"LOCAL_RESOURCE_DIR":                   localizePathList("resource_dirs"),
+	"LOCAL_NOTICE_FILE":                    localizePathList("android_license_files"),
 	"LOCAL_SANITIZE":                       sanitize(""),
 	"LOCAL_SANITIZE_DIAG":                  sanitize("diag."),
 	"LOCAL_STRIP_MODULE":                   strip(),
@@ -66,6 +69,8 @@
 	"LOCAL_MODULE_PATH":                    prebuiltModulePath,
 	"LOCAL_REPLACE_PREBUILT_APK_INSTALLED": prebuiltPreprocessed,
 
+	"LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG": invert("auto_gen_config"),
+
 	// composite functions
 	"LOCAL_MODULE_TAGS": includeVariableIf(bpVariable{"tags", bpparser.ListType}, not(valueDumpEquals("optional"))),
 
@@ -104,12 +109,12 @@
 			"LOCAL_NDK_STL_VARIANT":         "stl",
 			"LOCAL_JAR_MANIFEST":            "manifest",
 			"LOCAL_CERTIFICATE":             "certificate",
+			"LOCAL_CERTIFICATE_LINEAGE":     "lineage",
 			"LOCAL_PACKAGE_NAME":            "name",
 			"LOCAL_MODULE_RELATIVE_PATH":    "relative_install_path",
 			"LOCAL_PROTOC_OPTIMIZE_TYPE":    "proto.type",
 			"LOCAL_MODULE_OWNER":            "owner",
 			"LOCAL_RENDERSCRIPT_TARGET_API": "renderscript.target_api",
-			"LOCAL_NOTICE_FILE":             "notice",
 			"LOCAL_JAVA_LANGUAGE_VERSION":   "java_version",
 			"LOCAL_INSTRUMENTATION_FOR":     "instrumentation_for",
 			"LOCAL_MANIFEST_FILE":           "manifest",
@@ -127,6 +132,8 @@
 			"LOCAL_STATIC_LIBRARIES":              "static_libs",
 			"LOCAL_WHOLE_STATIC_LIBRARIES":        "whole_static_libs",
 			"LOCAL_SYSTEM_SHARED_LIBRARIES":       "system_shared_libs",
+			"LOCAL_USES_LIBRARIES":                "uses_libs",
+			"LOCAL_OPTIONAL_USES_LIBRARIES":       "optional_uses_libs",
 			"LOCAL_ASFLAGS":                       "asflags",
 			"LOCAL_CLANG_ASFLAGS":                 "clang_asflags",
 			"LOCAL_COMPATIBILITY_SUPPORT_FILES":   "data",
@@ -181,6 +188,13 @@
 			"LOCAL_JACK_COVERAGE_EXCLUDE_FILTER": "jacoco.exclude_filter",
 
 			"LOCAL_FULL_LIBS_MANIFEST_FILES": "additional_manifests",
+
+			// will be rewrite later to "license_kinds:" by byfix
+			"LOCAL_LICENSE_KINDS": "android_license_kinds",
+			// will be removed later by byfix
+			// TODO: does this property matter in the license module?
+			"LOCAL_LICENSE_CONDITIONS": "android_license_conditions",
+			"LOCAL_GENERATED_SOURCES":  "generated_sources",
 		})
 
 	addStandardProperties(bpparser.BoolType,
@@ -200,6 +214,7 @@
 			"LOCAL_VENDOR_MODULE":              "vendor",
 			"LOCAL_ODM_MODULE":                 "device_specific",
 			"LOCAL_PRODUCT_MODULE":             "product_specific",
+			"LOCAL_PRODUCT_SERVICES_MODULE":    "product_specific",
 			"LOCAL_SYSTEM_EXT_MODULE":          "system_ext_specific",
 			"LOCAL_EXPORT_PACKAGE_RESOURCES":   "export_package_resources",
 			"LOCAL_PRIVILEGED_MODULE":          "privileged",
@@ -218,6 +233,8 @@
 			"LOCAL_IS_UNIT_TEST": "unit_test",
 
 			"LOCAL_ENFORCE_USES_LIBRARIES": "enforce_uses_libs",
+
+			"LOCAL_CHECK_ELF_FILES": "check_elf_files",
 		})
 }
 
@@ -607,6 +624,16 @@
 	return err
 }
 
+// Assigns a given boolean value to a given variable in the result bp file. See
+// setVariable documentation for more information about prefix and name.
+func makeBlueprintBoolAssignment(ctx variableAssignmentContext, prefix, name string, value bool) error {
+	expressionValue, err := stringToBoolValue(strconv.FormatBool(value))
+	if err == nil {
+		err = setVariable(ctx.file, false, prefix, name, expressionValue, true)
+	}
+	return err
+}
+
 // If variable is a literal variable name, return the name, otherwise return ""
 func varLiteralName(variable mkparser.Variable) string {
 	if len(variable.Name.Variables) == 0 {
@@ -631,9 +658,19 @@
 	varname := ""
 	fixed := ""
 	val := ctx.mkvalue
+
 	if len(val.Variables) == 1 && varLiteralName(val.Variables[0]) != "" && len(val.Strings) == 2 && val.Strings[0] == "" {
+		if varLiteralName(val.Variables[0]) == "PRODUCT_OUT" && val.Strings[1] == "/system/priv-app" {
+			return makeBlueprintBoolAssignment(ctx, "", "privileged", true)
+		}
 		fixed = val.Strings[1]
 		varname = val.Variables[0].Name.Strings[0]
+		// TARGET_OUT_OPTIONAL_EXECUTABLES puts the artifact in xbin, which is
+		// deprecated. TARGET_OUT_DATA_APPS install location will be handled
+		// automatically by Soong
+		if varname == "TARGET_OUT_OPTIONAL_EXECUTABLES" || varname == "TARGET_OUT_DATA_APPS" {
+			return nil
+		}
 	} else if len(val.Variables) == 2 && varLiteralName(val.Variables[0]) == "PRODUCT_OUT" && varLiteralName(val.Variables[1]) == "TARGET_COPY_OUT_VENDOR" &&
 		len(val.Strings) == 3 && val.Strings[0] == "" && val.Strings[1] == "/" {
 		fixed = val.Strings[2]
diff --git a/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index b8316a3..aaafdc7 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -411,6 +411,24 @@
 	return exp, nil
 }
 
+// If local is set to true, then the variable will be added as a part of the
+// variable at file.bpPos. For example, if file.bpPos references a module,
+// then calling this method will set a property on that module if local is set
+// to true. Otherwise, the Variable will be created at the root of the file.
+//
+// prefix should be populated with the top level value to be assigned, and
+// name with a sub-value. If prefix is empty, then name is the top level value.
+// For example, if prefix is "foo" and name is "bar" with a value of "baz", then
+// the following variable will be generated:
+//
+// foo {
+//   bar: "baz"
+// }
+//
+// If prefix is the empty string and name is "foo" with a value of "bar", the
+// following variable will be generated (if it is a property):
+//
+// foo: "bar"
 func setVariable(file *bpFile, plusequals bool, prefix, name string, value bpparser.Expression, local bool) error {
 	if prefix != "" {
 		name = prefix + "." + name
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 439f45d..afde68b 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -699,7 +699,7 @@
 		expected: `
 			android_library {
 				srcs: ["test.java"],
-				resource_dirs: ["res"],
+
 				jacoco: {
 					include_filter: ["foo.*"],
 				},
@@ -794,7 +794,7 @@
 include $(BUILD_CTS_SUPPORT_PACKAGE)
 `,
 		expected: `
-android_test {
+android_test_helper_app {
     name: "FooTest",
     defaults: ["cts_support_defaults"],
     test_suites: ["cts"],
@@ -1121,6 +1121,25 @@
 `,
 	},
 	{
+		desc: "prebuilt_root_host",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(HOST_OUT)/subdir
+LOCAL_SRC_FILES := foo.txt
+include $(BUILD_PREBUILT)
+`,
+		expected: `
+prebuilt_root_host {
+	name: "foo",
+
+	src: "foo.txt",
+	relative_install_path: "subdir",
+}
+`,
+	},
+	{
 		desc: "prebuilt_font",
 		in: `
 include $(CLEAR_VARS)
@@ -1439,7 +1458,7 @@
 runtime_resource_overlay {
 	name: "foo",
 	product_specific: true,
-	resource_dirs: ["res"],
+
 	sdk_version: "current",
 	theme: "FooTheme",
 
@@ -1463,6 +1482,229 @@
 }
 `,
 	},
+	{
+		desc: "LOCAL_CERTIFICATE_LINEAGE",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE_LINEAGE := lineage
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_test {
+    name: "foo",
+    lineage: "lineage",
+}
+`,
+	},
+	{
+		desc: "LOCAL_USES_LIBRARIES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_USES_LIBRARIES := foo.test bar.test baz.test
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+    name: "foo",
+    uses_libs: [
+        "foo.test",
+        "bar.test",
+        "baz.test",
+    ],
+}
+`,
+	},
+	{
+		desc: "LOCAL_OPTIONAL_USES_LIBRARIES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_OPTIONAL_USES_LIBRARIES := foo.test bar.test baz.test
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+    name: "foo",
+    optional_uses_libs: [
+        "foo.test",
+        "bar.test",
+        "baz.test",
+    ],
+}
+`,
+	},
+	{
+		desc: "Obsolete LOCAL_MODULE_PATH",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_CTS_TEST_PACKAGE := bar
+LOCAL_USE_AAPT2 := blah
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+  name: "foo",
+
+}
+`,
+	},
+	{
+		desc: "LOCAL_LICENSE_KINDS, LOCAL_LICENSE_CONDITIONS, LOCAL_NOTICE_FILE",
+		// When "android_license_files" is valid, the test requires an Android.mk file
+		// outside the current (and an Android.bp file is required as well if the license
+		// files locates directory), thus a mock file system is needed. The integration
+		// test cases for these scenarios have been added in
+		// $(ANDROID_BUILD_TOP)/build/soong/tests/androidmk_test.sh.
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := license_notice
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+	"Android-Apache-2.0",
+    ],
+}
+
+android_app {
+    name: "foo",
+    // ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
+    // LOCAL_NOTICE_FILE := license_notice
+
+}
+`,
+	},
+	{
+		desc: "LOCAL_CHECK_ELF_FILES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_SRC_FILES := test.c
+LOCAL_MODULE_CLASS := SHARED_LIBRARIES
+LOCAL_CHECK_ELF_FILES := false
+include $(BUILD_PREBUILT)
+		`,
+		expected: `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["test.c"],
+
+	check_elf_files: false,
+}
+`,
+	},
+	{
+		desc: "Drop default resource and asset dirs from bp",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_app {
+		name: "foo",
+
+}
+`,
+	},
+	{
+		desc: "LOCAL_GENERATED_SOURCES",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_SRC_FILES := src1, src2, src3
+LOCAL_GENERATED_SOURCES := gen_src1, gen_src2, gen_src3
+include $(BUILD_PACKAGE)
+		`,
+		expected: `
+android_app {
+	name: "foo",
+	srcs: [
+		"src1,",
+		"src2,",
+		"src3",
+	],
+	generated_sources: [
+		"gen_src1,",
+		"gen_src2,",
+		"gen_src3",
+	],
+}
+`,
+	},
+	{
+		desc: "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG is true",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true
+include $(BUILD_PACKAGE)
+		`,
+		expected: `
+android_app {
+	name: "foo",
+	auto_gen_config: false,
+}
+`,
+	},
+	{
+		desc: "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG is false",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := false
+include $(BUILD_PACKAGE)
+		`,
+		expected: `
+android_app {
+	name: "foo",
+	auto_gen_config: true,
+}
+`,
+	},
+	{
+		desc: "privileged app",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/priv-app
+include $(BUILD_PACKAGE)
+		`,
+		expected: `
+android_app {
+	name: "foo",
+	privileged: true
+}
+`,
+	},
+	{
+		desc: "convert android_app to android_test when having test_suites",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_COMPATIBILITY_SUITE := bar
+include $(BUILD_PACKAGE)
+		`,
+		expected: `
+android_test {
+	name: "foo",
+	test_suites: ["bar"],
+}
+`,
+	},
 }
 
 func TestEndToEnd(t *testing.T) {
diff --git a/androidmk/parser/make_strings.go b/androidmk/parser/make_strings.go
index 3c4815e..8030326 100644
--- a/androidmk/parser/make_strings.go
+++ b/androidmk/parser/make_strings.go
@@ -24,14 +24,24 @@
 // A MakeString is a string that may contain variable substitutions in it.
 // It can be considered as an alternating list of raw Strings and variable
 // substitutions, where the first and last entries in the list must be raw
-// Strings (possibly empty).  A MakeString that starts with a variable
-// will have an empty first raw string, and a MakeString that ends with a
-// variable will have an empty last raw string.  Two sequential Variables
-// will have an empty raw string between them.
+// Strings (possibly empty). The entirety of the text before the first variable,
+// between two variables, and after the last variable will be considered a
+// single String value. A MakeString that starts with a variable will have an
+// empty first raw string, and a MakeString that ends with a  variable will have
+// an empty last raw string.  Two sequential Variables will have an empty raw
+// string between them.
 //
 // The MakeString is stored as two lists, a list of raw Strings and a list
 // of Variables.  The raw string list is always one longer than the variable
 // list.
+//
+// For example, "$(FOO)/bar/baz" will be represented as the
+// following lists:
+//
+// {
+//   Strings: ["", "/bar/baz"],
+//   Variables: ["FOO"]
+// }
 type MakeString struct {
 	StringPos Pos
 	Strings   []string
@@ -278,6 +288,15 @@
 	}
 }
 
+// If MakeString is $(var) after trimming, returns var
+func (ms *MakeString) SingleVariable() (*MakeString, bool) {
+	if len(ms.Strings) != 2 || strings.TrimSpace(ms.Strings[0]) != "" ||
+		strings.TrimSpace(ms.Strings[1]) != "" {
+		return nil, false
+	}
+	return ms.Variables[0].Name, true
+}
+
 func splitAnyN(s, sep string, n int) []string {
 	ret := []string{}
 	for n == -1 || n > 1 {
diff --git a/androidmk/parser/parser.go b/androidmk/parser/parser.go
index 5afef65..fb6be38 100644
--- a/androidmk/parser/parser.go
+++ b/androidmk/parser/parser.go
@@ -216,13 +216,14 @@
 		// Nothing
 	case "else":
 		p.ignoreSpaces()
-		if p.tok != '\n' {
+		if p.tok != '\n' && p.tok != '#' {
 			d = p.scanner.TokenText()
 			p.accept(scanner.Ident)
 			if d == "ifdef" || d == "ifndef" || d == "ifeq" || d == "ifneq" {
 				d = "el" + d
 				p.ignoreSpaces()
-				expression = p.parseExpression()
+				expression = p.parseExpression('#')
+				expression.TrimRightSpaces()
 			} else {
 				p.errorf("expected ifdef/ifndef/ifeq/ifneq, found %s", d)
 			}
@@ -231,7 +232,7 @@
 		expression, endPos = p.parseDefine()
 	default:
 		p.ignoreSpaces()
-		expression = p.parseExpression()
+		expression = p.parseExpression('#')
 	}
 
 	p.nodes = append(p.nodes, &Directive{
@@ -337,9 +338,6 @@
 				value.appendString(`\` + string(p.tok))
 			}
 			p.accept(p.tok)
-		case '#':
-			p.parseComment()
-			break loop
 		case '$':
 			var variable Variable
 			variable = p.parseVariable()
@@ -521,7 +519,7 @@
 	// non-whitespace character after the = until the end of the logical line,
 	// which may included escaped newlines
 	p.accept('=')
-	value := p.parseExpression()
+	value := p.parseExpression('#')
 	value.TrimLeftSpaces()
 	if ident.EndsWith('+') && t == "=" {
 		ident.TrimRightOne()
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
index f562c29..9efebf8 100644
--- a/androidmk/parser/parser_test.go
+++ b/androidmk/parser/parser_test.go
@@ -34,6 +34,56 @@
 			},
 		},
 	},
+	{
+		name: "Simple warning",
+		in:   `$(warning A warning)`,
+		out: []Node{
+			&Variable{
+				Name: SimpleMakeString("warning A warning", NoPos),
+			},
+		},
+	},
+	{
+		name: "Warning with #",
+		in:   `$(warning # A warning)`,
+		out: []Node{
+			&Variable{
+				Name: SimpleMakeString("warning # A warning", NoPos),
+			},
+		},
+	},
+	{
+		name: "Findstring with #",
+		in:   `$(findstring x,x a #)`,
+		out: []Node{
+			&Variable{
+				Name: SimpleMakeString("findstring x,x a #", NoPos),
+			},
+		},
+	},
+	{
+		name: "If statement",
+		in: `ifeq (a,b) # comment
+endif`,
+		out: []Node{
+			&Directive{
+				NamePos: NoPos,
+				Name:    "ifeq",
+				Args:    SimpleMakeString("(a,b) ", NoPos),
+				EndPos:  NoPos,
+			},
+			&Comment{
+				CommentPos: NoPos,
+				Comment:    " comment",
+			},
+			&Directive{
+				NamePos: NoPos,
+				Name:    "endif",
+				Args:    SimpleMakeString("", NoPos),
+				EndPos:  NoPos,
+			},
+		},
+	},
 }
 
 func TestParse(t *testing.T) {
diff --git a/apex/Android.bp b/apex/Android.bp
index 6269757..41224ec 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,10 +9,12 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-bpf",
         "soong-cc",
         "soong-filesystem",
         "soong-java",
+        "soong-provenance",
         "soong-python",
         "soong-rust",
         "soong-sh",
diff --git a/apex/androidmk.go b/apex/androidmk.go
index ebf0833..e094a12 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -103,16 +103,9 @@
 		return moduleNames
 	}
 
-	var postInstallCommands []string
-	for _, fi := range a.filesInfo {
-		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
-			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
-			linkTarget := filepath.Join("/system", fi.path())
-			linkPath := filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.path())
-			mkdirCmd := "mkdir -p " + filepath.Dir(linkPath)
-			linkCmd := "ln -sfn " + linkTarget + " " + linkPath
-			postInstallCommands = append(postInstallCommands, mkdirCmd, linkCmd)
-		}
+	// Avoid creating duplicate build rules for multi-installed APEXes.
+	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
+		return moduleNames
 	}
 
 	seenDataOutPaths := make(map[string]bool)
@@ -156,7 +149,7 @@
 		var modulePath string
 		if apexType == flattenedApex {
 			// /system/apex/<name>/{lib|framework|...}
-			modulePath = filepath.Join(a.installDir.ToMakePath().String(), apexBundleName, fi.installDir)
+			modulePath = filepath.Join(a.installDir.String(), apexBundleName, fi.installDir)
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
 			if a.primaryApexType && !symbolFilesNotNeeded {
 				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
@@ -188,6 +181,8 @@
 			// we will have duplicated notice entries.
 			fmt.Fprintln(w, "LOCAL_NO_NOTICE_FILE := true")
 		}
+		fmt.Fprintln(w, "LOCAL_SOONG_INSTALLED_MODULE :=", filepath.Join(modulePath, fi.stem()))
+		fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS :=", fi.builtFile.String()+":"+filepath.Join(modulePath, fi.stem()))
 		fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", fi.builtFile.String())
 		fmt.Fprintln(w, "LOCAL_MODULE_CLASS :=", fi.class.nameInMake())
 		if fi.module != nil {
@@ -212,7 +207,7 @@
 			}
 			if host {
 				makeOs := fi.module.Target().Os.String()
-				if fi.module.Target().Os == android.Linux || fi.module.Target().Os == android.LinuxBionic {
+				if fi.module.Target().Os == android.Linux || fi.module.Target().Os == android.LinuxBionic || fi.module.Target().Os == android.LinuxMusl {
 					makeOs = "linux"
 				}
 				fmt.Fprintln(w, "LOCAL_MODULE_HOST_OS :=", makeOs)
@@ -258,7 +253,7 @@
 			if !ok {
 				panic(fmt.Sprintf("Expected %s to be AndroidAppSet", fi.module))
 			}
-			fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.InstallFile())
+			fmt.Fprintln(w, "LOCAL_APK_SET_INSTALL_FILE :=", as.PackedAdditionalOutputs().String())
 			fmt.Fprintln(w, "LOCAL_APKCERTS_FILE :=", as.APKCertsFile().String())
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_android_app_set.mk")
 		case nativeSharedLib, nativeExecutable, nativeTest:
@@ -272,7 +267,7 @@
 					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", ccMod.CoverageOutputFile().String())
 				}
 			}
-			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_prebuilt.mk")
+			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk")
 		default:
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", fi.stem())
 			if fi.builtFile == a.manifestPbOut && apexType == flattenedApex {
@@ -297,18 +292,10 @@
 					if len(patterns) > 0 {
 						fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(patterns, " "))
 					}
-					if len(a.compatSymlinks) > 0 {
-						// For flattened apexes, compat symlinks are attached to apex_manifest.json which is guaranteed for every apex
-						postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
-					}
 				}
 
 				// File_contexts of flattened APEXes should be merged into file_contexts.bin
 				fmt.Fprintln(w, "LOCAL_FILE_CONTEXTS :=", a.fileContexts)
-
-				if len(postInstallCommands) > 0 {
-					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(postInstallCommands, " && "))
-				}
 			}
 			fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 		}
@@ -322,16 +309,24 @@
 	return moduleNames
 }
 
-func (a *apexBundle) writeRequiredModules(w io.Writer, apexBundleName string) {
+func (a *apexBundle) writeRequiredModules(w io.Writer, moduleNames []string) {
+	if len(moduleNames) > 0 {
+		fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(moduleNames, " "))
+	}
+	if len(a.requiredDeps) > 0 {
+		fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(a.requiredDeps, " "))
+	}
+
 	var required []string
 	var targetRequired []string
 	var hostRequired []string
-	installMapSet := make(map[string]bool) // set of dependency module:location mappings
+	required = append(required, a.RequiredModuleNames()...)
+	targetRequired = append(targetRequired, a.TargetRequiredModuleNames()...)
+	hostRequired = append(hostRequired, a.HostRequiredModuleNames()...)
 	for _, fi := range a.filesInfo {
 		required = append(required, fi.requiredModuleNames...)
 		targetRequired = append(targetRequired, fi.targetRequiredModuleNames...)
 		hostRequired = append(hostRequired, fi.hostRequiredModuleNames...)
-		installMapSet[a.fullModuleName(apexBundleName, &fi)+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
 	}
 
 	if len(required) > 0 {
@@ -343,11 +338,6 @@
 	if len(hostRequired) > 0 {
 		fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES +=", strings.Join(hostRequired, " "))
 	}
-	if len(installMapSet) > 0 {
-		var installs []string
-		installs = append(installs, android.SortedStringKeys(installMapSet)...)
-		fmt.Fprintln(w, "LOCAL_LICENSE_INSTALL_MAP +=", strings.Join(installs, " "))
-	}
 }
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
@@ -366,10 +356,7 @@
 				fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 				fmt.Fprintln(w, "LOCAL_MODULE :=", name+a.suffix)
 				data.Entries.WriteLicenseVariables(w)
-				if len(moduleNames) > 0 {
-					fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(moduleNames, " "))
-				}
-				a.writeRequiredModules(w, name)
+				a.writeRequiredModules(w, moduleNames)
 				fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
 
 			} else {
@@ -379,13 +366,17 @@
 				data.Entries.WriteLicenseVariables(w)
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
-				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.ToMakePath().String())
+				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.String())
 				stemSuffix := apexType.suffix()
 				if a.isCompressed {
-					stemSuffix = ".capex"
+					stemSuffix = imageCapexSuffix
 				}
 				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", name+stemSuffix)
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE :=", !a.installable())
+				if a.installable() {
+					fmt.Fprintln(w, "LOCAL_SOONG_INSTALLED_MODULE :=", a.installedFile.String())
+					fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS :=", a.outputFile.String()+":"+a.installedFile.String())
+				}
 
 				// Because apex writes .mk with Custom(), we need to write manually some common properties
 				// which are available via data.Entries
@@ -403,27 +394,7 @@
 				if len(a.overridableProperties.Overrides) > 0 {
 					fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(a.overridableProperties.Overrides, " "))
 				}
-				if len(moduleNames) > 0 {
-					fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(moduleNames, " "))
-				}
-				if len(a.requiredDeps) > 0 {
-					fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(a.requiredDeps, " "))
-				}
-				a.writeRequiredModules(w, name)
-				var postInstallCommands []string
-				if a.prebuiltFileToDelete != "" {
-					postInstallCommands = append(postInstallCommands, "rm -rf "+
-						filepath.Join(a.installDir.ToMakePath().String(), a.prebuiltFileToDelete))
-				}
-				// For unflattened apexes, compat symlinks are attached to apex package itself as LOCAL_POST_INSTALL_CMD
-				postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
-				if len(postInstallCommands) > 0 {
-					fmt.Fprintln(w, "LOCAL_POST_INSTALL_CMD :=", strings.Join(postInstallCommands, " && "))
-				}
-
-				if a.mergedNotices.Merged.Valid() {
-					fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", a.mergedNotices.Merged.Path().String())
-				}
+				a.writeRequiredModules(w, moduleNames)
 
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 
@@ -446,23 +417,18 @@
 					fmt.Fprintf(w, dist)
 				}
 
-				if a.apisUsedByModuleFile.String() != "" {
-					goal := "apps_only"
-					distFile := a.apisUsedByModuleFile.String()
-					fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
-						" $(call dist-for-goals,%s,%s:ndk_apis_usedby_apex/$(notdir %s))\n"+
-						"endif\n",
-						goal, distFile, distFile)
-				}
-
-				if a.apisBackedByModuleFile.String() != "" {
-					goal := "apps_only"
-					distFile := a.apisBackedByModuleFile.String()
-					fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
-						" $(call dist-for-goals,%s,%s:ndk_apis_backedby_apex/$(notdir %s))\n"+
-						"endif\n",
-						goal, distFile, distFile)
-				}
+				distCoverageFiles(w, "ndk_apis_usedby_apex", a.nativeApisUsedByModuleFile.String())
+				distCoverageFiles(w, "ndk_apis_backedby_apex", a.nativeApisBackedByModuleFile.String())
+				distCoverageFiles(w, "java_apis_used_by_apex", a.javaApisUsedByModuleFile.String())
 			}
 		}}
 }
+
+func distCoverageFiles(w io.Writer, dir string, distfile string) {
+	if distfile != "" {
+		goal := "apps_only"
+		fmt.Fprintf(w, "ifneq (,$(filter $(my_register_name),$(TARGET_BUILD_APPS)))\n"+
+			" $(call dist-for-goals,%s,%s:%s/$(notdir %s))\n"+
+			"endif\n", goal, distfile, dir, distfile)
+	}
+}
diff --git a/apex/apex.go b/apex/apex.go
index 7785407..6e61743 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -19,6 +19,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strings"
 
@@ -27,6 +28,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/bpf"
 	"android/soong/cc"
 	prebuilt_etc "android/soong/etc"
@@ -75,6 +77,8 @@
 	ctx.BottomUp("apex", apexMutator).Parallel()
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
+	// Register after apex_info mutator so that it can use ApexVariationName
+	ctx.TopDown("apex_strict_updatability_lint", apexStrictUpdatibilityLintMutator).Parallel()
 }
 
 type apexBundleProperties struct {
@@ -95,28 +99,24 @@
 	// /system/sepolicy/apex/<module_name>_file_contexts.
 	File_contexts *string `android:"path"`
 
+	// Path to the canned fs config file for customizing file's uid/gid/mod/capabilities. The
+	// format is /<path_or_glob> <uid> <gid> <mode> [capabilities=0x<cap>], where path_or_glob is a
+	// path or glob pattern for a file or set of files, uid/gid are numerial values of user ID
+	// and group ID, mode is octal value for the file mode, and cap is hexadecimal value for the
+	// capability. If this property is not set, or a file is missing in the file, default config
+	// is used.
+	Canned_fs_config *string `android:"path"`
+
 	ApexNativeDependencies
 
 	Multilib apexMultilibProperties
 
-	// List of bootclasspath fragments that are embedded inside this APEX bundle.
-	Bootclasspath_fragments []string
-
-	// List of systemserverclasspath fragments that are embedded inside this APEX bundle.
-	Systemserverclasspath_fragments []string
-
-	// List of java libraries that are embedded inside this APEX bundle.
-	Java_libs []string
-
-	// List of prebuilt files that are embedded inside this APEX bundle.
-	Prebuilts []string
+	// List of sh binaries that are embedded inside this APEX bundle.
+	Sh_binaries []string
 
 	// List of platform_compat_config files that are embedded inside this APEX bundle.
 	Compat_configs []string
 
-	// List of BPF programs inside this APEX bundle.
-	Bpfs []string
-
 	// List of filesystem images that are embedded inside this APEX bundle.
 	Filesystems []string
 
@@ -130,25 +130,34 @@
 	// symlinking to the system libs. Default is true.
 	Updatable *bool
 
+	// Marks that this APEX is designed to be updatable in the future, although it's not
+	// updatable yet. This is used to mimic some of the build behaviors that are applied only to
+	// updatable APEXes. Currently, this disables the size optimization, so that the size of
+	// APEX will not increase when the APEX is actually marked as truly updatable. Default is
+	// false.
+	Future_updatable *bool
+
+	// Whether this APEX can use platform APIs or not. Can be set to true only when `updatable:
+	// false`. Default is false.
+	Platform_apis *bool
+
 	// Whether this APEX is installable to one of the partitions like system, vendor, etc.
 	// Default: true.
 	Installable *bool
 
-	// Whether this APEX can be compressed or not. Setting this property to false means this
-	// APEX will never be compressed. When set to true, APEX will be compressed if other
-	// conditions, e.g, target device needs to support APEX compression, are also fulfilled.
-	// Default: true.
-	Compressible *bool
-
 	// If set true, VNDK libs are considered as stable libs and are not included in this APEX.
 	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
 	Use_vndk_as_stable *bool
 
-	// 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
+	// Whether this is multi-installed APEX should skip installing symbol files.
+	// Multi-installed APEXes share the same apex_name and are installed at the same time.
+	// Default is false.
+	//
+	// Should be set to true for all multi-installed APEXes except the singular
+	// default version within the multi-installed group.
+	// Only the default version can install symbol files in $(PRODUCT_OUT}/apex,
+	// or else conflicting build rules may be created.
+	Multi_install_skip_symbol_files *bool
 
 	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
 	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
@@ -157,8 +166,8 @@
 	// is 'image'.
 	Payload_type *string
 
-	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4' or 'f2fs'.
-	// Default 'ext4'.
+	// The type of filesystem to use when the payload_type is 'image'. Either 'ext4', 'f2fs'
+	// or 'erofs'. Default 'ext4'.
 	Payload_fs_type *string
 
 	// For telling the APEX to ignore special handling for system libraries such as bionic.
@@ -177,6 +186,10 @@
 	// used in tests.
 	Test_only_force_compression *bool
 
+	// Put extra tags (signer=<value>) to apexkeys.txt, so that release tools can sign this apex
+	// with the tool to sign payload contents.
+	Custom_sign_tool *string
+
 	// Canonical name of this APEX bundle. Used to determine the path to the
 	// activated APEX on device (i.e. /apex/<apexVariationName>), and used for the
 	// apex mutator variations. For override_apex modules, this is the name of the
@@ -282,9 +295,24 @@
 	// List of APKs that are embedded inside this APEX.
 	Apps []string
 
+	// List of prebuilt files that are embedded inside this APEX bundle.
+	Prebuilts []string
+
 	// List of runtime resource overlays (RROs) that are embedded inside this APEX.
 	Rros []string
 
+	// List of BPF programs inside this APEX bundle.
+	Bpfs []string
+
+	// List of bootclasspath fragments that are embedded inside this APEX bundle.
+	Bootclasspath_fragments []string
+
+	// List of systemserverclasspath fragments that are embedded inside this APEX bundle.
+	Systemserverclasspath_fragments []string
+
+	// List of java libraries that are embedded inside this APEX bundle.
+	Java_libs []string
+
 	// Names of modules to be overridden. Listed modules can only be other binaries (in Make or
 	// Soong). This does not completely prevent installation of the overridden binaries, but if
 	// both binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will
@@ -310,6 +338,12 @@
 	// certificate and the private key are provided from the android_app_certificate module
 	// named "module".
 	Certificate *string
+
+	// Whether this APEX can be compressed or not. Setting this property to false means this
+	// APEX will never be compressed. When set to true, APEX will be compressed if other
+	// conditions, e.g., target device needs to support APEX compression, are also fulfilled.
+	// Default: false.
+	Compressible *bool
 }
 
 type apexBundle struct {
@@ -318,6 +352,7 @@
 	android.DefaultableModuleBase
 	android.OverridableModuleBase
 	android.SdkBase
+	android.BazelModuleBase
 
 	// Properties
 	properties            apexBundleProperties
@@ -340,7 +375,6 @@
 	// Flags for special variants of APEX
 	testApex bool
 	vndkApex bool
-	artApex  bool
 
 	// Tells whether this variant of the APEX bundle is the primary one or not. Only the primary
 	// one gets installed to the device.
@@ -375,12 +409,16 @@
 	// Processed file_contexts files
 	fileContexts android.WritablePath
 
-	// Struct holding the merged notice file paths in different formats
-	mergedNotices android.NoticeOutputs
+	// Path to notice file in html.gz format.
+	htmlGzNotice android.WritablePath
 
 	// The built APEX file. This is the main product.
+	// Could be .apex or .capex
 	outputFile android.WritablePath
 
+	// The built uncompressed .apex file.
+	outputApexFile android.WritablePath
+
 	// The built APEX file in app bundle format. This file is not directly installed to the
 	// device. For an APEX, multiple app bundles are created each of which is for a specific ABI
 	// like arm, arm64, x86, etc. Then they are processed again (outside of the Android build
@@ -388,14 +426,14 @@
 	// vendor/google/build/build_unbundled_mainline_module.sh for more detail.
 	bundleModuleFile android.WritablePath
 
-	// Target path to install this APEX. Usually out/target/product/<device>/<partition>/apex.
+	// Target directory to install this APEX. Usually out/target/product/<device>/<partition>/apex.
 	installDir android.InstallPath
 
-	// List of commands to create symlinks for backward compatibility. These commands will be
-	// attached as LOCAL_POST_INSTALL_CMD to apex package itself (for unflattened build) or
-	// apex_manifest (for flattened build) so that compat symlinks are always installed
-	// regardless of TARGET_FLATTEN_APEX setting.
-	compatSymlinks []string
+	// Path where this APEX was installed.
+	installedFile android.InstallPath
+
+	// Installed locations of symlinks for backward compatibility.
+	compatSymlinks android.InstallPaths
 
 	// Text file having the list of individual files that are included in this APEX. Used for
 	// debugging purpose.
@@ -413,8 +451,12 @@
 	isCompressed bool
 
 	// Path of API coverage generate file
-	apisUsedByModuleFile   android.ModuleOutPath
-	apisBackedByModuleFile android.ModuleOutPath
+	nativeApisUsedByModuleFile   android.ModuleOutPath
+	nativeApisBackedByModuleFile android.ModuleOutPath
+	javaApisUsedByModuleFile     android.ModuleOutPath
+
+	// Collect the module directory for IDE info in java/jdeps.go.
+	modulePaths []string
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -440,11 +482,10 @@
 // for each of the files in case when the APEX is flattened.
 type apexFile struct {
 	// buildFile is put in the installDir inside the APEX.
-	builtFile   android.Path
-	noticeFiles android.Paths
-	installDir  string
-	customStem  string
-	symlinks    []string // additional symlinks
+	builtFile  android.Path
+	installDir string
+	customStem string
+	symlinks   []string // additional symlinks
 
 	// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
 	// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
@@ -481,7 +522,6 @@
 		module:              module,
 	}
 	if module != nil {
-		ret.noticeFiles = module.NoticeFiles()
 		ret.moduleDir = ctx.OtherModuleDir(module)
 		ret.requiredModuleNames = module.RequiredModuleNames()
 		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
@@ -589,6 +629,7 @@
 	sharedLibTag    = dependencyTag{name: "sharedLib", payload: true}
 	testForTag      = dependencyTag{name: "test for"}
 	testTag         = dependencyTag{name: "test", payload: true}
+	shBinaryTag     = dependencyTag{name: "shBinary", payload: true}
 )
 
 // TODO(jiyong): shorten this function signature
@@ -662,7 +703,6 @@
 	// each target os/architectures, appropriate dependencies are selected by their
 	// target.<os>.multilib.<type> groups and are added as (direct) dependencies.
 	targets := ctx.MultiTargets()
-	config := ctx.DeviceConfig()
 	imageVariation := a.getImageVariation(ctx)
 
 	a.combineProperties(ctx)
@@ -734,51 +774,16 @@
 		for _, d := range depsList {
 			addDependenciesForNativeModules(ctx, d, target, imageVariation)
 		}
+		ctx.AddFarVariationDependencies([]blueprint.Variation{
+			{Mutator: "os", Variation: target.OsVariation()},
+			{Mutator: "arch", Variation: target.ArchVariation()},
+		}, shBinaryTag, a.properties.Sh_binaries...)
 	}
 
-	// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
-	// regardless of the TARGET_PREFER_* setting. See b/144532908
-	archForPrebuiltEtc := config.Arches()[0]
-	for _, arch := range config.Arches() {
-		// Prefer 64-bit arch if there is any
-		if arch.ArchType.Multilib == "lib64" {
-			archForPrebuiltEtc = arch
-			break
-		}
-	}
-	ctx.AddFarVariationDependencies([]blueprint.Variation{
-		{Mutator: "os", Variation: ctx.Os().String()},
-		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
-	}, prebuiltTag, a.properties.Prebuilts...)
-
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
-	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments...)
-	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.Systemserverclasspath_fragments...)
-	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
-	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
-
-	if a.artApex {
-		// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
-		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-			ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
-		}
-	}
-
-	// Marks that this APEX (in fact all the modules in it) has to be built with the given SDKs.
-	// This field currently isn't used.
-	// TODO(jiyong): consider dropping this feature
-	// 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.
@@ -789,7 +794,30 @@
 
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, androidAppTag, a.overridableProperties.Apps...)
+	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.overridableProperties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.overridableProperties.Rros...)
+	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.overridableProperties.Bootclasspath_fragments...)
+	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.overridableProperties.Systemserverclasspath_fragments...)
+	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.overridableProperties.Java_libs...)
+	if prebuilts := a.overridableProperties.Prebuilts; len(prebuilts) > 0 {
+		// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
+		// regardless of the TARGET_PREFER_* setting. See b/144532908
+		arches := ctx.DeviceConfig().Arches()
+		if len(arches) != 0 {
+			archForPrebuiltEtc := arches[0]
+			for _, arch := range arches {
+				// Prefer 64-bit arch if there is any
+				if arch.ArchType.Multilib == "lib64" {
+					archForPrebuiltEtc = arch
+					break
+				}
+			}
+			ctx.AddFarVariationDependencies([]blueprint.Variation{
+				{Mutator: "os", Variation: ctx.Os().String()},
+				{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
+			}, prebuiltTag, prebuilts...)
+		}
+	}
 
 	// Dependencies for signing
 	if String(a.overridableProperties.Key) == "" {
@@ -846,9 +874,18 @@
 	// APEX, but shared across APEXes via the VNDK APEX.
 	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
 	excludeVndkLibs := useVndk && proptools.Bool(a.properties.Use_vndk_as_stable)
-	if !useVndk && proptools.Bool(a.properties.Use_vndk_as_stable) {
-		mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
-		return
+	if proptools.Bool(a.properties.Use_vndk_as_stable) {
+		if !useVndk {
+			mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
+		}
+		mctx.VisitDirectDepsWithTag(sharedLibTag, func(dep android.Module) {
+			if c, ok := dep.(*cc.Module); ok && c.IsVndk() {
+				mctx.PropertyErrorf("use_vndk_as_stable", "Trying to include a VNDK library(%s) while use_vndk_as_stable is true.", dep.Name())
+			}
+		})
+		if mctx.Failed() {
+			return
+		}
 	}
 
 	continueApexDepsWalk := func(child, parent android.Module) bool {
@@ -911,8 +948,8 @@
 	apexInfo := android.ApexInfo{
 		ApexVariationName: apexVariationName,
 		MinSdkVersion:     minSdkVersion,
-		RequiredSdks:      a.RequiredSdks(),
 		Updatable:         a.Updatable(),
+		UsePlatformApis:   a.UsePlatformApis(),
 		InApexVariants:    []string{apexVariationName},
 		InApexModules:     []string{a.Name()}, // could be com.mycompany.android.foo
 		ApexContents:      []*android.ApexContents{apexContents},
@@ -938,6 +975,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
@@ -945,8 +983,84 @@
 
 	if a, ok := mctx.Module().(ApexInfoMutator); ok {
 		a.ApexInfoMutator(mctx)
+	}
+	enforceAppUpdatability(mctx)
+}
+
+// apexStrictUpdatibilityLintMutator propagates strict_updatability_linting to transitive deps of a mainline module
+// This check is enforced for updatable modules
+func apexStrictUpdatibilityLintMutator(mctx android.TopDownMutatorContext) {
+	if !mctx.Module().Enabled() {
 		return
 	}
+	if apex, ok := mctx.Module().(*apexBundle); ok && apex.checkStrictUpdatabilityLinting() {
+		mctx.WalkDeps(func(child, parent android.Module) bool {
+			// b/208656169 Do not propagate strict updatability linting to libcore/
+			// These libs are available on the classpath during compilation
+			// These libs are transitive deps of the sdk. See java/sdk.go:decodeSdkDep
+			// Only skip libraries defined in libcore root, not subdirectories
+			if mctx.OtherModuleDir(child) == "libcore" {
+				// Do not traverse transitive deps of libcore/ libs
+				return false
+			}
+			if android.InList(child.Name(), skipLintJavalibAllowlist) {
+				return false
+			}
+			if lintable, ok := child.(java.LintDepSetsIntf); ok {
+				lintable.SetStrictUpdatabilityLinting(true)
+			}
+			// visit transitive deps
+			return true
+		})
+	}
+}
+
+// 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
+var (
+	skipStrictUpdatabilityLintAllowlist = []string{
+		"com.android.art",
+		"com.android.art.debug",
+		"com.android.conscrypt",
+		"com.android.media",
+		// test apexes
+		"test_com.android.art",
+		"test_com.android.conscrypt",
+		"test_com.android.media",
+		"test_jitzygote_com.android.art",
+	}
+
+	// TODO: b/215736885 Remove this list
+	skipLintJavalibAllowlist = []string{
+		"conscrypt.module.platform.api.stubs",
+		"conscrypt.module.public.api.stubs",
+		"conscrypt.module.public.api.stubs.system",
+		"conscrypt.module.public.api.stubs.module_lib",
+		"framework-media.stubs",
+		"framework-media.stubs.system",
+		"framework-media.stubs.module_lib",
+	}
+)
+
+func (a *apexBundle) checkStrictUpdatabilityLinting() bool {
+	return a.Updatable() && !android.InList(a.ApexVariationName(), skipStrictUpdatabilityLintAllowlist)
 }
 
 // apexUniqueVariationsMutator checks if any dependencies use unique apex variations. If so, use
@@ -1075,13 +1189,6 @@
 			mctx.ModuleErrorf("base property is not set")
 			return
 		}
-		// Workaround the issue reported in b/191269918 by using the unprefixed module name of this
-		// module as the default variation to use if dependencies of this module do not have the correct
-		// apex variant name. This name matches the name used to create the variations of modules for
-		// which apexModuleTypeRequiresVariant return true.
-		// TODO(b/191269918): Remove this workaround.
-		unprefixedModuleName := android.RemoveOptionalPrebuiltPrefix(mctx.ModuleName())
-		mctx.SetDefaultDependencyVariation(&unprefixedModuleName)
 		mctx.CreateVariations(apexBundleName)
 		if strings.HasPrefix(apexBundleName, "com.android.art") {
 			// TODO(b/183882457): See note for CreateAliasVariation above.
@@ -1133,17 +1240,19 @@
 
 const (
 	// File extensions of an APEX for different packaging methods
-	imageApexSuffix = ".apex"
-	zipApexSuffix   = ".zipapex"
-	flattenedSuffix = ".flattened"
+	imageApexSuffix  = ".apex"
+	imageCapexSuffix = ".capex"
+	zipApexSuffix    = ".zipapex"
+	flattenedSuffix  = ".flattened"
 
 	// variant names each of which is for a packaging method
 	imageApexType     = "image"
 	zipApexType       = "zip"
 	flattenedApexType = "flattened"
 
-	ext4FsType = "ext4"
-	f2fsFsType = "f2fs"
+	ext4FsType  = "ext4"
+	f2fsFsType  = "f2fs"
+	erofsFsType = "erofs"
 )
 
 // The suffix for the output "file", not the module
@@ -1237,6 +1346,12 @@
 	case "", android.DefaultDistTag:
 		// This is the default dist path.
 		return android.Paths{a.outputFile}, nil
+	case imageApexSuffix:
+		// uncompressed one
+		if a.outputApexFile != nil {
+			return android.Paths{a.outputApexFile}, nil
+		}
+		fallthrough
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 	}
@@ -1272,11 +1387,19 @@
 
 var _ android.ApexBundleDepsInfoIntf = (*apexBundle)(nil)
 
-// Implements android.ApexBudleDepsInfoIntf
+// Implements android.ApexBundleDepsInfoIntf
 func (a *apexBundle) Updatable() bool {
 	return proptools.BoolDefault(a.properties.Updatable, true)
 }
 
+func (a *apexBundle) FutureUpdatable() bool {
+	return proptools.BoolDefault(a.properties.Future_updatable, false)
+}
+
+func (a *apexBundle) UsePlatformApis() bool {
+	return proptools.BoolDefault(a.properties.Platform_apis, false)
+}
+
 // getCertString returns the name of the cert that should be used to sign this APEX. This is
 // basically from the "certificate" property, but could be overridden by the device config.
 func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
@@ -1350,7 +1473,7 @@
 		for _, target := range ctx.MultiTargets() {
 			if target.Arch.ArchType.Multilib == "lib64" {
 				addDependenciesForNativeModules(ctx, ApexNativeDependencies{
-					Native_shared_libs: []string{"libclang_rt.hwasan-aarch64-android"},
+					Native_shared_libs: []string{"libclang_rt.hwasan"},
 					Tests:              nil,
 					Jni_libs:           nil,
 					Binaries:           nil,
@@ -1448,12 +1571,7 @@
 
 func apexFileForGoBinary(ctx android.BaseModuleContext, depName string, gb bootstrap.GoBinaryTool) apexFile {
 	dirInApex := "bin"
-	s, err := filepath.Rel(android.PathForOutput(ctx).String(), gb.InstallPath())
-	if err != nil {
-		ctx.ModuleErrorf("Unable to use compiled binary at %s", gb.InstallPath())
-		return apexFile{}
-	}
-	fileToCopy := android.PathForOutput(ctx, s)
+	fileToCopy := android.PathForGoBinary(ctx, gb)
 	// NB: Since go binaries are static we don't need the module for anything here, which is
 	// good since the go tool is a blueprint.Module not an android.Module like we would
 	// normally use.
@@ -1462,6 +1580,9 @@
 
 func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
 	dirInApex := filepath.Join("bin", sh.SubDir())
+	if sh.Target().NativeBridge == android.NativeBridgeEnabled {
+		dirInApex = filepath.Join(dirInApex, sh.Target().NativeBridgeRelativePath)
+	}
 	fileToCopy := sh.OutputFile()
 	af := newApexFile(ctx, fileToCopy, sh.BaseModuleName(), dirInApex, shBinary, sh)
 	af.symlinks = sh.Symlinks()
@@ -1485,7 +1606,7 @@
 type javaModule interface {
 	android.Module
 	BaseModuleName() string
-	DexJarBuildPath() android.Path
+	DexJarBuildPath() java.OptionalDexJarPath
 	JacocoReportClassesFile() android.Path
 	LintDepSets() java.LintDepSets
 	Stem() string
@@ -1499,7 +1620,7 @@
 
 // apexFileForJavaModule creates an apexFile for a java module's dex implementation jar.
 func apexFileForJavaModule(ctx android.BaseModuleContext, module javaModule) apexFile {
-	return apexFileForJavaModuleWithFile(ctx, module, module.DexJarBuildPath())
+	return apexFileForJavaModuleWithFile(ctx, module, module.DexJarBuildPath().PathOrNil())
 }
 
 // apexFileForJavaModuleWithFile creates an apexFile for a java module with the supplied file.
@@ -1509,6 +1630,11 @@
 	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
 	af.lintDepSets = module.LintDepSets()
 	af.customStem = module.Stem() + ".jar"
+	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+		for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() {
+			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+		}
+	}
 	return af
 }
 
@@ -1528,13 +1654,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()
@@ -1563,8 +1709,8 @@
 	return af
 }
 
-func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile {
-	dirInApex := filepath.Join("etc", "bpf")
+func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, apex_sub_dir string, bpfProgram bpf.BpfModule) apexFile {
+	dirInApex := filepath.Join("etc", "bpf", apex_sub_dir)
 	return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram)
 }
 
@@ -1607,6 +1753,7 @@
 const (
 	ext4 fsType = iota
 	f2fs
+	erofs
 )
 
 func (f fsType) string() string {
@@ -1615,6 +1762,8 @@
 		return ext4FsType
 	case f2fs:
 		return f2fsFsType
+	case erofs:
+		return erofsFsType
 	default:
 		panic(fmt.Errorf("unknown APEX payload type %d", f))
 	}
@@ -1631,8 +1780,9 @@
 	// 1) do some validity checks such as apex_available, min_sdk_version, etc.
 	a.checkApexAvailability(ctx)
 	a.checkUpdatable(ctx)
-	a.checkMinSdkVersion(ctx)
+	a.CheckMinSdkVersion(ctx)
 	a.checkStaticLinkingToStubLibraries(ctx)
+	a.checkStaticExecutables(ctx)
 	if len(a.properties.Tests) > 0 && !a.testApex {
 		ctx.PropertyErrorf("tests", "property allowed only in apex_test module type")
 		return
@@ -1650,6 +1800,9 @@
 
 	handleSpecialLibs := !android.Bool(a.properties.Ignore_system_library_special_case)
 
+	// Collect the module directory for IDE info in java/jdeps.go.
+	a.modulePaths = append(a.modulePaths, ctx.ModuleDir())
+
 	// TODO(jiyong): do this using WalkPayloadDeps
 	// TODO(jiyong): make this clean!!!
 	ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
@@ -1657,6 +1810,9 @@
 		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
 			return false
 		}
+		if mod, ok := child.(android.Module); ok && !mod.Enabled() {
+			return false
+		}
 		depName := ctx.OtherModuleName(child)
 		if _, isDirectDep := parent.(*apexBundle); isDirectDep {
 			switch depTag {
@@ -1675,7 +1831,9 @@
 					return true // track transitive dependencies
 				} else if r, ok := child.(*rust.Module); ok {
 					fi := apexFileForRustLibrary(ctx, r)
+					fi.isJniLib = isJniLib
 					filesInfo = append(filesInfo, fi)
+					return true // track transitive dependencies
 				} else {
 					propertyName := "native_shared_libs"
 					if isJniLib {
@@ -1687,8 +1845,6 @@
 				if cc, ok := child.(*cc.Module); ok {
 					filesInfo = append(filesInfo, apexFileForExecutable(ctx, cc))
 					return true // track transitive dependencies
-				} else if sh, ok := child.(*sh.ShBinary); ok {
-					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
 				} else if py, ok := child.(*python.Module); ok && py.HostToolPath().Valid() {
 					filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
 				} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
@@ -1697,17 +1853,27 @@
 					filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
 					return true // track transitive dependencies
 				} else {
-					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, nor (host) bootstrap_go_binary", depName)
+				}
+			case shBinaryTag:
+				if sh, ok := child.(*sh.ShBinary); ok {
+					filesInfo = append(filesInfo, apexFileForShBinary(ctx, sh))
+				} else {
+					ctx.PropertyErrorf("sh_binaries", "%q is not a sh_binary module", depName)
 				}
 			case bcpfTag:
 				{
-					if _, ok := child.(*java.BootclasspathFragmentModule); !ok {
+					bcpfModule, ok := child.(*java.BootclasspathFragmentModule)
+					if !ok {
 						ctx.PropertyErrorf("bootclasspath_fragments", "%q is not a bootclasspath_fragment module", depName)
 						return false
 					}
 
 					filesToAdd := apexBootclasspathFragmentFiles(ctx, child)
 					filesInfo = append(filesInfo, filesToAdd...)
+					for _, makeModuleName := range bcpfModule.BootImageDeviceInstallMakeModules() {
+						a.requiredDeps = append(a.requiredDeps, makeModuleName)
+					}
 					return true
 				}
 			case sscpfTag:
@@ -1716,7 +1882,9 @@
 						ctx.PropertyErrorf("systemserverclasspath_fragments", "%q is not a systemserverclasspath_fragment module", depName)
 						return false
 					}
-					filesInfo = append(filesInfo, apexClasspathFragmentProtoFile(ctx, child))
+					if af := apexClasspathFragmentProtoFile(ctx, child); af != nil {
+						filesInfo = append(filesInfo, *af)
+					}
 					return true
 				}
 			case javaLibTag:
@@ -1745,8 +1913,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 {
@@ -1761,8 +1933,9 @@
 			case bpfTag:
 				if bpfProgram, ok := child.(bpf.BpfModule); ok {
 					filesToCopy, _ := bpfProgram.OutputFiles("")
+					apex_sub_dir := bpfProgram.SubDir()
 					for _, bpfFile := range filesToCopy {
-						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram))
+						filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, apex_sub_dir, bpfProgram))
 					}
 				} else {
 					ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName)
@@ -1951,6 +2124,8 @@
 					}
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
 					// nothing
+				} else if depTag == android.DarwinUniversalVariantTag {
+					// nothing
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
 					ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
 				}
@@ -1998,7 +2173,7 @@
 	a.filesInfo = filesInfo
 
 	// Set suffix and primaryApexType depending on the ApexType
-	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
+	buildFlattenedAsDefault := ctx.Config().FlattenApex()
 	switch a.properties.ApexType {
 	case imageApex:
 		if buildFlattenedAsDefault {
@@ -2032,8 +2207,10 @@
 		a.payloadFsType = ext4
 	case f2fsFsType:
 		a.payloadFsType = f2fs
+	case erofsFsType:
+		a.payloadFsType = erofs
 	default:
-		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs]", *a.properties.Payload_fs_type)
+		ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs, erofs]", *a.properties.Payload_fs_type)
 	}
 
 	// Optimization. If we are building bundled APEX, for the files that are gathered due to the
@@ -2050,10 +2227,11 @@
 	}
 
 	forced := ctx.Config().ForceApexSymlinkOptimization()
+	updatable := a.Updatable() || a.FutureUpdatable()
 
 	// We don't need the optimization for updatable APEXes, as it might give false signal
 	// to the system health when the APEXes are still bundled (b/149805758).
-	if !forced && a.Updatable() && a.properties.ApexType == imageApex {
+	if !forced && updatable && a.properties.ApexType == imageApex {
 		a.linkToSystemLib = false
 	}
 
@@ -2062,7 +2240,9 @@
 		a.linkToSystemLib = false
 	}
 
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+	if a.properties.ApexType != zipApex {
+		a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, a.primaryApexType)
+	}
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 4) generate the build rules to create the APEX. This is done in builder.go.
@@ -2102,28 +2282,70 @@
 	var filesToAdd []apexFile
 
 	// Add the boot image files, e.g. .art, .oat and .vdex files.
-	for arch, files := range bootclasspathFragmentInfo.AndroidBootImageFilesByArchType() {
-		dirInApex := filepath.Join("javalib", arch.String())
-		for _, f := range files {
-			androidMkModuleName := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-			// TODO(b/177892522) - consider passing in the bootclasspath fragment module here instead of nil
-			af := newApexFile(ctx, f, androidMkModuleName, dirInApex, etc, nil)
-			filesToAdd = append(filesToAdd, af)
+	if bootclasspathFragmentInfo.ShouldInstallBootImageInApex() {
+		for arch, files := range bootclasspathFragmentInfo.AndroidBootImageFilesByArchType() {
+			dirInApex := filepath.Join("javalib", arch.String())
+			for _, f := range files {
+				androidMkModuleName := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
+				// TODO(b/177892522) - consider passing in the bootclasspath fragment module here instead of nil
+				af := newApexFile(ctx, f, androidMkModuleName, dirInApex, etc, nil)
+				filesToAdd = append(filesToAdd, af)
+			}
 		}
 	}
 
 	// Add classpaths.proto config.
-	filesToAdd = append(filesToAdd, apexClasspathFragmentProtoFile(ctx, module))
+	if af := apexClasspathFragmentProtoFile(ctx, module); af != nil {
+		filesToAdd = append(filesToAdd, *af)
+	}
+
+	if pathInApex := bootclasspathFragmentInfo.ProfileInstallPathInApex(); pathInApex != "" {
+		pathOnHost := bootclasspathFragmentInfo.ProfilePathOnHost()
+		tempPath := android.PathForModuleOut(ctx, "boot_image_profile", pathInApex)
+
+		if pathOnHost != nil {
+			// We need to copy the profile to a temporary path with the right filename because the apexer
+			// will take the filename as is.
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  pathOnHost,
+				Output: tempPath,
+			})
+		} else {
+			// At this point, the boot image profile cannot be generated. It is probably because the boot
+			// image profile source file does not exist on the branch, or it is not available for the
+			// current build target.
+			// However, we cannot enforce the boot image profile to be generated because some build
+			// targets (such as module SDK) do not need it. It is only needed when the APEX is being
+			// built. Therefore, we create an error rule so that an error will occur at the ninja phase
+			// only if the APEX is being built.
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.ErrorRule,
+				Output: tempPath,
+				Args: map[string]string{
+					"error": "Boot image profile cannot be generated",
+				},
+			})
+		}
+
+		androidMkModuleName := filepath.Base(pathInApex)
+		af := newApexFile(ctx, tempPath, androidMkModuleName, filepath.Dir(pathInApex), etc, nil)
+		filesToAdd = append(filesToAdd, af)
+	}
 
 	return filesToAdd
 }
 
-// apexClasspathFragmentProtoFile returns apexFile structure defining the classpath.proto config that
-// the module contributes to the apex.
-func apexClasspathFragmentProtoFile(ctx android.ModuleContext, module blueprint.Module) apexFile {
-	fragmentInfo := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo)
-	classpathProtoOutput := fragmentInfo.ClasspathFragmentProtoOutput
-	return newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), fragmentInfo.ClasspathFragmentProtoInstallDir.Rel(), etc, nil)
+// apexClasspathFragmentProtoFile returns *apexFile structure defining the classpath.proto config that
+// the module contributes to the apex; or nil if the proto config was not generated.
+func apexClasspathFragmentProtoFile(ctx android.ModuleContext, module blueprint.Module) *apexFile {
+	info := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo)
+	if !info.ClasspathFragmentProtoGenerated {
+		return nil
+	}
+	classpathProtoOutput := info.ClasspathFragmentProtoOutput
+	af := newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), info.ClasspathFragmentProtoInstallDir.Rel(), etc, nil)
+	return &af
 }
 
 // apexFileForBootclasspathFragmentContentModule creates an apexFile for a bootclasspath_fragment
@@ -2160,13 +2382,13 @@
 	android.InitDefaultableModule(module)
 	android.InitSdkAwareModule(module)
 	android.InitOverridableModule(module, &module.overridableProperties.Overrides)
+	android.InitBazelModule(module)
 	return module
 }
 
-func ApexBundleFactory(testApex bool, artApex bool) android.Module {
+func ApexBundleFactory(testApex bool) android.Module {
 	bundle := newApexBundle()
 	bundle.testApex = testApex
-	bundle.artApex = artApex
 	return bundle
 }
 
@@ -2237,23 +2459,56 @@
 //
 // TODO(jiyong): move these checks to a separate go file.
 
+var _ android.ModuleWithMinSdkVersionCheck = (*apexBundle)(nil)
+
 // Entures that min_sdk_version of the included modules are equal or less than the min_sdk_version
 // of this apexBundle.
-func (a *apexBundle) checkMinSdkVersion(ctx android.ModuleContext) {
+func (a *apexBundle) CheckMinSdkVersion(ctx android.ModuleContext) {
 	if a.testApex || a.vndkApex {
 		return
 	}
 	// apexBundle::minSdkVersion reports its own errors.
 	minSdkVersion := a.minSdkVersion(ctx)
-	android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+	android.CheckMinSdkVersion(ctx, minSdkVersion, a.WalkPayloadDeps)
 }
 
-func (a *apexBundle) minSdkVersion(ctx android.BaseModuleContext) android.ApiLevel {
-	ver := proptools.String(a.properties.Min_sdk_version)
-	if ver == "" {
+// Returns apex's min_sdk_version string value, honoring overrides
+func (a *apexBundle) minSdkVersionValue(ctx android.EarlyModuleContext) string {
+	// Only override the minSdkVersion value on Apexes which already specify
+	// a min_sdk_version (it's optional for non-updatable apexes), and that its
+	// min_sdk_version value is lower than the one to override with.
+	overrideMinSdkValue := ctx.DeviceConfig().ApexGlobalMinSdkVersionOverride()
+	overrideApiLevel := minSdkVersionFromValue(ctx, overrideMinSdkValue)
+	originalMinApiLevel := minSdkVersionFromValue(ctx, proptools.String(a.properties.Min_sdk_version))
+	isMinSdkSet := a.properties.Min_sdk_version != nil
+	isOverrideValueHigher := overrideApiLevel.CompareTo(originalMinApiLevel) > 0
+	if overrideMinSdkValue != "" && isMinSdkSet && isOverrideValueHigher {
+		return overrideMinSdkValue
+	}
+
+	return proptools.String(a.properties.Min_sdk_version)
+}
+
+// Returns apex's min_sdk_version SdkSpec, honoring overrides
+func (a *apexBundle) MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	return android.SdkSpec{
+		Kind:     android.SdkNone,
+		ApiLevel: a.minSdkVersion(ctx),
+		Raw:      a.minSdkVersionValue(ctx),
+	}
+}
+
+// Returns apex's min_sdk_version ApiLevel, honoring overrides
+func (a *apexBundle) minSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
+	return minSdkVersionFromValue(ctx, a.minSdkVersionValue(ctx))
+}
+
+// Construct ApiLevel object from min_sdk_version string value
+func minSdkVersionFromValue(ctx android.EarlyModuleContext, value string) android.ApiLevel {
+	if value == "" {
 		return android.NoneApiLevel
 	}
-	apiLevel, err := android.ApiLevelFromUser(ctx, ver)
+	apiLevel, err := android.ApiLevelFromUser(ctx, value)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		return android.NoneApiLevel
@@ -2305,16 +2560,39 @@
 	})
 }
 
-// Enforce that Java deps of the apex are using stable SDKs to compile
+// checkUpdatable enforces APEX and its transitive dep properties to have desired values for updatable APEXes.
 func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) {
 	if a.Updatable() {
-		if String(a.properties.Min_sdk_version) == "" {
+		if a.minSdkVersionValue(ctx) == "" {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
 		}
+		if a.UsePlatformApis() {
+			ctx.PropertyErrorf("updatable", "updatable APEXes can't use platform APIs")
+		}
+		if a.SocSpecific() || a.DeviceSpecific() {
+			ctx.PropertyErrorf("updatable", "vendor APEXes are not updatable")
+		}
+		if a.FutureUpdatable() {
+			ctx.PropertyErrorf("future_updatable", "Already updatable. Remove `future_updatable: true:`")
+		}
 		a.checkJavaStableSdkVersion(ctx)
+		a.checkClasspathFragments(ctx)
 	}
 }
 
+// checkClasspathFragments enforces that all classpath fragments in deps generate classpaths.proto config.
+func (a *apexBundle) checkClasspathFragments(ctx android.ModuleContext) {
+	ctx.VisitDirectDeps(func(module android.Module) {
+		if tag := ctx.OtherModuleDependencyTag(module); tag == bcpfTag || tag == sscpfTag {
+			info := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo)
+			if !info.ClasspathFragmentProtoGenerated {
+				ctx.OtherModuleErrorf(module, "is included in updatable apex %v, it must not set generate_classpaths_proto to false", ctx.ModuleName())
+			}
+		}
+	})
+}
+
+// checkJavaStableSdkVersion enforces that all Java deps are using stable SDKs to compile.
 func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
 	// Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs,
 	// java's checkLinkType guarantees correct usage for transitive deps
@@ -2333,7 +2611,7 @@
 	})
 }
 
-// Ensures that the all the dependencies are marked as available for this APEX
+// checkApexAvailability ensures that the all the dependencies are marked as available for this APEX.
 func (a *apexBundle) checkApexAvailability(ctx android.ModuleContext) {
 	// Let's be practical. Availability for test, host, and the VNDK apex isn't important
 	if ctx.Host() || a.testApex || a.vndkApex {
@@ -2385,6 +2663,49 @@
 	})
 }
 
+// checkStaticExecutable ensures that executables in an APEX are not static.
+func (a *apexBundle) checkStaticExecutables(ctx android.ModuleContext) {
+	// No need to run this for host APEXes
+	if ctx.Host() {
+		return
+	}
+
+	ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
+		if ctx.OtherModuleDependencyTag(module) != executableTag {
+			return
+		}
+
+		if l, ok := module.(cc.LinkableInterface); ok && l.StaticExecutable() {
+			apex := a.ApexVariationName()
+			exec := ctx.OtherModuleName(module)
+			if isStaticExecutableAllowed(apex, exec) {
+				return
+			}
+			ctx.ModuleErrorf("executable %s is static", ctx.OtherModuleName(module))
+		}
+	})
+}
+
+// A small list of exceptions where static executables are allowed in APEXes.
+func isStaticExecutableAllowed(apex string, exec string) bool {
+	m := map[string][]string{
+		"com.android.runtime": []string{
+			"linker",
+			"linkerconfig",
+		},
+	}
+	execNames, ok := m[apex]
+	return ok && android.InList(exec, execNames)
+}
+
+// Collect information for opening IDE project files in java/jdeps.go.
+func (a *apexBundle) IDEInfo(dpInfo *android.IdeInfo) {
+	dpInfo.Deps = append(dpInfo.Deps, a.overridableProperties.Java_libs...)
+	dpInfo.Deps = append(dpInfo.Deps, a.overridableProperties.Bootclasspath_fragments...)
+	dpInfo.Deps = append(dpInfo.Deps, a.overridableProperties.Systemserverclasspath_fragments...)
+	dpInfo.Paths = append(dpInfo.Paths, a.modulePaths...)
+}
+
 var (
 	apexAvailBaseline        = makeApexAvailableBaseline()
 	inverseApexAvailBaseline = invertApexBaseline(apexAvailBaseline)
@@ -2454,34 +2775,9 @@
 	//
 	// Module separator
 	//
-	m["com.android.bluetooth.updatable"] = []string{
-		"android.hardware.audio.common@5.0",
-		"android.hardware.bluetooth.a2dp@1.0",
-		"android.hardware.bluetooth.audio@2.0",
-		"android.hardware.bluetooth@1.0",
-		"android.hardware.bluetooth@1.1",
-		"android.hardware.graphics.bufferqueue@1.0",
-		"android.hardware.graphics.bufferqueue@2.0",
-		"android.hardware.graphics.common@1.0",
-		"android.hardware.graphics.common@1.1",
-		"android.hardware.graphics.common@1.2",
-		"android.hardware.media@1.0",
-		"android.hidl.safe_union@1.0",
-		"android.hidl.token@1.0",
-		"android.hidl.token@1.0-utils",
-		"avrcp-target-service",
-		"avrcp_headers",
+	m["com.android.bluetooth"] = []string{
 		"bluetooth-protos-lite",
-		"bluetooth.mapsapi",
-		"com.android.vcard",
-		"dnsresolver_aidl_interface-V2-java",
-		"ipmemorystore-aidl-interfaces-V5-java",
-		"ipmemorystore-aidl-interfaces-java",
 		"internal_include_headers",
-		"lib-bt-packets",
-		"lib-bt-packets-avrcp",
-		"lib-bt-packets-base",
-		"libFraunhoferAAC",
 		"libaudio-a2dp-hw-utils",
 		"libaudio-hearing-aid-hw-utils",
 		"libbluetooth",
@@ -2505,28 +2801,6 @@
 		"libbte",
 		"libbtif",
 		"libchrome",
-		"libevent",
-		"libfmq",
-		"libg722codec",
-		"libgui_headers",
-		"libmedia_headers",
-		"libmodpb64",
-		"libosi",
-		"libstagefright_foundation_headers",
-		"libstagefright_headers",
-		"libstatslog",
-		"libstatssocket",
-		"libtinyxml2",
-		"libudrv-uipc",
-		"libz",
-		"media_plugin_headers",
-		"net-utils-services-common",
-		"netd_aidl_interface-unstable-java",
-		"netd_event_listener_interface-java",
-		"netlink-client",
-		"networkstack-client",
-		"sap-api-java-static",
-		"services.net",
 	}
 	//
 	// Module separator
@@ -2561,266 +2835,18 @@
 		"libbuildversion",
 		"libmath",
 		"libprocpartition",
-		"libsync",
 	}
 	//
 	// Module separator
 	//
 	m["com.android.media"] = []string{
-		"android.frameworks.bufferhub@1.0",
-		"android.hardware.cas.native@1.0",
-		"android.hardware.cas@1.0",
-		"android.hardware.configstore-utils",
-		"android.hardware.configstore@1.0",
-		"android.hardware.configstore@1.1",
-		"android.hardware.graphics.allocator@2.0",
-		"android.hardware.graphics.allocator@3.0",
-		"android.hardware.graphics.bufferqueue@1.0",
-		"android.hardware.graphics.bufferqueue@2.0",
-		"android.hardware.graphics.common@1.0",
-		"android.hardware.graphics.common@1.1",
-		"android.hardware.graphics.common@1.2",
-		"android.hardware.graphics.mapper@2.0",
-		"android.hardware.graphics.mapper@2.1",
-		"android.hardware.graphics.mapper@3.0",
-		"android.hardware.media.omx@1.0",
-		"android.hardware.media@1.0",
-		"android.hidl.allocator@1.0",
-		"android.hidl.memory.token@1.0",
-		"android.hidl.memory@1.0",
-		"android.hidl.token@1.0",
-		"android.hidl.token@1.0-utils",
-		"bionic_libc_platform_headers",
-		"exoplayer2-extractor",
-		"exoplayer2-extractor-annotation-stubs",
-		"gl_headers",
-		"jsr305",
-		"libEGL",
-		"libEGL_blobCache",
-		"libEGL_getProcAddress",
-		"libFLAC",
-		"libFLAC-config",
-		"libFLAC-headers",
-		"libGLESv2",
-		"libaacextractor",
-		"libamrextractor",
-		"libarect",
-		"libaudio_system_headers",
-		"libaudioclient",
-		"libaudioclient_headers",
-		"libaudiofoundation",
-		"libaudiofoundation_headers",
-		"libaudiomanager",
-		"libaudiopolicy",
-		"libaudioutils",
-		"libaudioutils_fixedfft",
-		"libbluetooth-types-header",
-		"libbufferhub",
-		"libbufferhub_headers",
-		"libbufferhubqueue",
-		"libc_malloc_debug_backtrace",
-		"libcamera_client",
-		"libcamera_metadata",
-		"libdvr_headers",
-		"libexpat",
-		"libfifo",
-		"libflacextractor",
-		"libgrallocusage",
-		"libgraphicsenv",
-		"libgui",
-		"libgui_headers",
-		"libhardware_headers",
-		"libinput",
-		"liblzma",
-		"libmath",
-		"libmedia",
-		"libmedia_codeclist",
-		"libmedia_headers",
-		"libmedia_helper",
-		"libmedia_helper_headers",
-		"libmedia_midiiowrapper",
-		"libmedia_omx",
-		"libmediautils",
-		"libmidiextractor",
-		"libmkvextractor",
-		"libmp3extractor",
-		"libmp4extractor",
-		"libmpeg2extractor",
-		"libnativebase_headers",
-		"libnativewindow_headers",
-		"libnblog",
-		"liboggextractor",
-		"libpackagelistparser",
-		"libpdx",
-		"libpdx_default_transport",
-		"libpdx_headers",
-		"libpdx_uds",
-		"libprocinfo",
-		"libspeexresampler",
-		"libspeexresampler",
-		"libstagefright_esds",
-		"libstagefright_flacdec",
-		"libstagefright_flacdec",
-		"libstagefright_foundation",
-		"libstagefright_foundation_headers",
-		"libstagefright_foundation_without_imemory",
-		"libstagefright_headers",
-		"libstagefright_id3",
-		"libstagefright_metadatautils",
-		"libstagefright_mpeg2extractor",
-		"libstagefright_mpeg2support",
-		"libsync",
-		"libui",
-		"libui_headers",
-		"libunwindstack",
-		"libvibrator",
-		"libvorbisidec",
-		"libwavextractor",
-		"libwebm",
-		"media_ndk_headers",
-		"media_plugin_headers",
-		"updatable-media",
+		// empty
 	}
 	//
 	// Module separator
 	//
 	m["com.android.media.swcodec"] = []string{
-		"android.frameworks.bufferhub@1.0",
-		"android.hardware.common-ndk_platform",
-		"android.hardware.configstore-utils",
-		"android.hardware.configstore@1.0",
-		"android.hardware.configstore@1.1",
-		"android.hardware.graphics.allocator@2.0",
-		"android.hardware.graphics.allocator@3.0",
-		"android.hardware.graphics.allocator@4.0",
-		"android.hardware.graphics.bufferqueue@1.0",
-		"android.hardware.graphics.bufferqueue@2.0",
-		"android.hardware.graphics.common-ndk_platform",
-		"android.hardware.graphics.common@1.0",
-		"android.hardware.graphics.common@1.1",
-		"android.hardware.graphics.common@1.2",
-		"android.hardware.graphics.mapper@2.0",
-		"android.hardware.graphics.mapper@2.1",
-		"android.hardware.graphics.mapper@3.0",
-		"android.hardware.graphics.mapper@4.0",
-		"android.hardware.media.bufferpool@2.0",
-		"android.hardware.media.c2@1.0",
-		"android.hardware.media.c2@1.1",
-		"android.hardware.media.omx@1.0",
-		"android.hardware.media@1.0",
-		"android.hardware.media@1.0",
-		"android.hidl.memory.token@1.0",
-		"android.hidl.memory@1.0",
-		"android.hidl.safe_union@1.0",
-		"android.hidl.token@1.0",
-		"android.hidl.token@1.0-utils",
-		"libEGL",
-		"libFLAC",
-		"libFLAC-config",
-		"libFLAC-headers",
-		"libFraunhoferAAC",
-		"libLibGuiProperties",
-		"libarect",
-		"libaudio_system_headers",
-		"libaudioutils",
-		"libaudioutils",
-		"libaudioutils_fixedfft",
-		"libavcdec",
-		"libavcenc",
-		"libavservices_minijail",
-		"libavservices_minijail",
-		"libbinderthreadstateutils",
-		"libbluetooth-types-header",
-		"libbufferhub_headers",
-		"libcodec2",
-		"libcodec2_headers",
-		"libcodec2_hidl@1.0",
-		"libcodec2_hidl@1.1",
-		"libcodec2_internal",
-		"libcodec2_soft_aacdec",
-		"libcodec2_soft_aacenc",
-		"libcodec2_soft_amrnbdec",
-		"libcodec2_soft_amrnbenc",
-		"libcodec2_soft_amrwbdec",
-		"libcodec2_soft_amrwbenc",
-		"libcodec2_soft_av1dec_gav1",
-		"libcodec2_soft_avcdec",
-		"libcodec2_soft_avcenc",
-		"libcodec2_soft_common",
-		"libcodec2_soft_flacdec",
-		"libcodec2_soft_flacenc",
-		"libcodec2_soft_g711alawdec",
-		"libcodec2_soft_g711mlawdec",
-		"libcodec2_soft_gsmdec",
-		"libcodec2_soft_h263dec",
-		"libcodec2_soft_h263enc",
-		"libcodec2_soft_hevcdec",
-		"libcodec2_soft_hevcenc",
-		"libcodec2_soft_mp3dec",
-		"libcodec2_soft_mpeg2dec",
-		"libcodec2_soft_mpeg4dec",
-		"libcodec2_soft_mpeg4enc",
-		"libcodec2_soft_opusdec",
-		"libcodec2_soft_opusenc",
-		"libcodec2_soft_rawdec",
-		"libcodec2_soft_vorbisdec",
-		"libcodec2_soft_vp8dec",
-		"libcodec2_soft_vp8enc",
-		"libcodec2_soft_vp9dec",
-		"libcodec2_soft_vp9enc",
-		"libcodec2_vndk",
-		"libdvr_headers",
-		"libfmq",
-		"libfmq",
-		"libgav1",
-		"libgralloctypes",
-		"libgrallocusage",
-		"libgraphicsenv",
-		"libgsm",
-		"libgui_bufferqueue_static",
-		"libgui_headers",
-		"libhardware",
-		"libhardware_headers",
-		"libhevcdec",
-		"libhevcenc",
-		"libion",
-		"libjpeg",
-		"liblzma",
-		"libmath",
-		"libmedia_codecserviceregistrant",
-		"libmedia_headers",
-		"libmpeg2dec",
-		"libnativebase_headers",
-		"libnativewindow_headers",
-		"libpdx_headers",
-		"libscudo_wrapper",
-		"libsfplugin_ccodec_utils",
-		"libspeexresampler",
-		"libstagefright_amrnb_common",
-		"libstagefright_amrnbdec",
-		"libstagefright_amrnbenc",
-		"libstagefright_amrwbdec",
-		"libstagefright_amrwbenc",
-		"libstagefright_bufferpool@2.0.1",
-		"libstagefright_enc_common",
-		"libstagefright_flacdec",
-		"libstagefright_foundation",
-		"libstagefright_foundation_headers",
-		"libstagefright_headers",
-		"libstagefright_m4vh263dec",
-		"libstagefright_m4vh263enc",
-		"libstagefright_mp3dec",
-		"libsync",
-		"libui",
-		"libui_headers",
-		"libunwindstack",
-		"libvorbisidec",
-		"libvpx",
-		"libyuv",
-		"libyuv_static",
-		"media_ndk_headers",
-		"media_plugin_headers",
-		"mediaswcodec",
+		// empty
 	}
 	//
 	// Module separator
@@ -3005,20 +3031,27 @@
 }
 
 func init() {
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(qModulesPackages())...)
-	android.AddNeverAllowRules(createApexPermittedPackagesRules(rModulesPackages())...)
+	android.AddNeverAllowRules(createBcpPermittedPackagesRules(qBcpPackages())...)
+	android.AddNeverAllowRules(createBcpPermittedPackagesRules(rBcpPackages())...)
 }
 
-func createApexPermittedPackagesRules(modules_packages map[string][]string) []android.Rule {
-	rules := make([]android.Rule, 0, len(modules_packages))
-	for module_name, module_packages := range modules_packages {
+func createBcpPermittedPackagesRules(bcpPermittedPackages map[string][]string) []android.Rule {
+	rules := make([]android.Rule, 0, len(bcpPermittedPackages))
+	for jar, permittedPackages := range bcpPermittedPackages {
 		permittedPackagesRule := android.NeverAllow().
-			BootclasspathJar().
-			With("apex_available", module_name).
-			WithMatcher("permitted_packages", android.NotInList(module_packages)).
-			Because("jars that are part of the " + module_name +
-				" module may only allow these packages: " + strings.Join(module_packages, ",") +
-				". Please jarjar or move code around.")
+			With("name", jar).
+			WithMatcher("permitted_packages", android.NotInList(permittedPackages)).
+			Because(jar +
+				" bootjar may only use these package prefixes: " + strings.Join(permittedPackages, ",") +
+				". Please consider the following alternatives:\n" +
+				"    1. If the offending code is from a statically linked library, consider " +
+				"removing that dependency and using an alternative already in the " +
+				"bootclasspath, or perhaps a shared library." +
+				"    2. Move the offending code into an allowed package.\n" +
+				"    3. Jarjar the offending code. Please be mindful of the potential system " +
+				"health implications of bundling that code, particularly if the offending jar " +
+				"is part of the bootclasspath.")
+
 		rules = append(rules, permittedPackagesRule)
 	}
 	return rules
@@ -3026,13 +3059,13 @@
 
 // DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
-func qModulesPackages() map[string][]string {
+func qBcpPackages() map[string][]string {
 	return map[string][]string{
-		"com.android.conscrypt": []string{
+		"conscrypt": []string{
 			"android.net.ssl",
 			"com.android.org.conscrypt",
 		},
-		"com.android.media": []string{
+		"updatable-media": []string{
 			"android.media",
 		},
 	}
@@ -3040,35 +3073,263 @@
 
 // DO NOT EDIT! These are the package prefixes that are exempted from being AOT'ed by ART.
 // Adding code to the bootclasspath in new packages will cause issues on module update.
-func rModulesPackages() map[string][]string {
+func rBcpPackages() map[string][]string {
 	return map[string][]string{
-		"com.android.mediaprovider": []string{
+		"framework-mediaprovider": []string{
 			"android.provider",
 		},
-		"com.android.permission": []string{
+		"framework-permission": []string{
 			"android.permission",
 			"android.app.role",
 			"com.android.permission",
 			"com.android.role",
 		},
-		"com.android.sdkext": []string{
+		"framework-sdkextensions": []string{
 			"android.os.ext",
 		},
-		"com.android.os.statsd": []string{
+		"framework-statsd": []string{
 			"android.app",
 			"android.os",
 			"android.util",
 			"com.android.internal.statsd",
 			"com.android.server.stats",
 		},
-		"com.android.wifi": []string{
+		"framework-wifi": []string{
 			"com.android.server.wifi",
 			"com.android.wifi.x",
 			"android.hardware.wifi",
 			"android.net.wifi",
 		},
-		"com.android.tethering": []string{
+		"framework-tethering": []string{
 			"android.net",
 		},
 	}
 }
+
+// For Bazel / bp2build
+
+type bazelApexBundleAttributes struct {
+	Manifest              bazel.LabelAttribute
+	Android_manifest      bazel.LabelAttribute
+	File_contexts         bazel.LabelAttribute
+	Key                   bazel.LabelAttribute
+	Certificate           bazel.LabelAttribute
+	Min_sdk_version       *string
+	Updatable             bazel.BoolAttribute
+	Installable           bazel.BoolAttribute
+	Binaries              bazel.LabelListAttribute
+	Prebuilts             bazel.LabelListAttribute
+	Native_shared_libs_32 bazel.LabelListAttribute
+	Native_shared_libs_64 bazel.LabelListAttribute
+	Compressible          bazel.BoolAttribute
+}
+
+type convertedNativeSharedLibs struct {
+	Native_shared_libs_32 bazel.LabelListAttribute
+	Native_shared_libs_64 bazel.LabelListAttribute
+}
+
+// ConvertWithBp2build performs bp2build conversion of an apex
+func (a *apexBundle) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	// We do not convert apex_test modules at this time
+	if ctx.ModuleType() != "apex" {
+		return
+	}
+
+	var manifestLabelAttribute bazel.LabelAttribute
+	if a.properties.Manifest != nil {
+		manifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.Manifest))
+	}
+
+	var androidManifestLabelAttribute bazel.LabelAttribute
+	if a.properties.AndroidManifest != nil {
+		androidManifestLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *a.properties.AndroidManifest))
+	}
+
+	var fileContextsLabelAttribute bazel.LabelAttribute
+	if a.properties.File_contexts != nil {
+		fileContextsLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.properties.File_contexts))
+	}
+
+	// TODO(b/219503907) this would need to be set to a.MinSdkVersionValue(ctx) but
+	// given it's coming via config, we probably don't want to put it in here.
+	var minSdkVersion *string
+	if a.properties.Min_sdk_version != nil {
+		minSdkVersion = a.properties.Min_sdk_version
+	}
+
+	var keyLabelAttribute bazel.LabelAttribute
+	if a.overridableProperties.Key != nil {
+		keyLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Key))
+	}
+
+	var certificateLabelAttribute bazel.LabelAttribute
+	if a.overridableProperties.Certificate != nil {
+		certificateLabelAttribute.SetValue(android.BazelLabelForModuleDepSingle(ctx, *a.overridableProperties.Certificate))
+	}
+
+	nativeSharedLibs := &convertedNativeSharedLibs{
+		Native_shared_libs_32: bazel.LabelListAttribute{},
+		Native_shared_libs_64: bazel.LabelListAttribute{},
+	}
+	compileMultilib := "both"
+	if a.CompileMultilib() != nil {
+		compileMultilib = *a.CompileMultilib()
+	}
+
+	// properties.Native_shared_libs is treated as "both"
+	convertBothLibs(ctx, compileMultilib, a.properties.Native_shared_libs, nativeSharedLibs)
+	convertBothLibs(ctx, compileMultilib, a.properties.Multilib.Both.Native_shared_libs, nativeSharedLibs)
+	convert32Libs(ctx, compileMultilib, a.properties.Multilib.Lib32.Native_shared_libs, nativeSharedLibs)
+	convert64Libs(ctx, compileMultilib, a.properties.Multilib.Lib64.Native_shared_libs, nativeSharedLibs)
+	convertFirstLibs(ctx, compileMultilib, a.properties.Multilib.First.Native_shared_libs, nativeSharedLibs)
+
+	prebuilts := a.overridableProperties.Prebuilts
+	prebuiltsLabelList := android.BazelLabelForModuleDeps(ctx, prebuilts)
+	prebuiltsLabelListAttribute := bazel.MakeLabelListAttribute(prebuiltsLabelList)
+
+	binaries := android.BazelLabelForModuleDeps(ctx, a.properties.ApexNativeDependencies.Binaries)
+	binariesLabelListAttribute := bazel.MakeLabelListAttribute(binaries)
+
+	var updatableAttribute bazel.BoolAttribute
+	if a.properties.Updatable != nil {
+		updatableAttribute.Value = a.properties.Updatable
+	}
+
+	var installableAttribute bazel.BoolAttribute
+	if a.properties.Installable != nil {
+		installableAttribute.Value = a.properties.Installable
+	}
+
+	var compressibleAttribute bazel.BoolAttribute
+	if a.overridableProperties.Compressible != nil {
+		compressibleAttribute.Value = a.overridableProperties.Compressible
+	}
+
+	attrs := &bazelApexBundleAttributes{
+		Manifest:              manifestLabelAttribute,
+		Android_manifest:      androidManifestLabelAttribute,
+		File_contexts:         fileContextsLabelAttribute,
+		Min_sdk_version:       minSdkVersion,
+		Key:                   keyLabelAttribute,
+		Certificate:           certificateLabelAttribute,
+		Updatable:             updatableAttribute,
+		Installable:           installableAttribute,
+		Native_shared_libs_32: nativeSharedLibs.Native_shared_libs_32,
+		Native_shared_libs_64: nativeSharedLibs.Native_shared_libs_64,
+		Binaries:              binariesLabelListAttribute,
+		Prebuilts:             prebuiltsLabelListAttribute,
+		Compressible:          compressibleAttribute,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "apex",
+		Bzl_load_location: "//build/bazel/rules:apex.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+}
+
+// The following conversions are based on this table where the rows are the compile_multilib
+// values and the columns are the properties.Multilib.*.Native_shared_libs. Each cell
+// represents how the libs should be compiled for a 64-bit/32-bit device: 32 means it
+// should be compiled as 32-bit, 64 means it should be compiled as 64-bit, none means it
+// should not be compiled.
+// multib/compile_multilib, 32,        64,        both,     first
+// 32,                      32/32,     none/none, 32/32,    none/32
+// 64,                      none/none, 64/none,   64/none,  64/none
+// both,                    32/32,     64/none,   32&64/32, 64/32
+// first,                   32/32,     64/none,   64/32,    64/32
+
+func convert32Libs(ctx android.TopDownMutatorContext, compileMultilb string,
+	libs []string, nativeSharedLibs *convertedNativeSharedLibs) {
+	libsLabelList := android.BazelLabelForModuleDeps(ctx, libs)
+	switch compileMultilb {
+	case "both", "32":
+		makeNoConfig32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "first":
+		make32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "64":
+		// Incompatible, ignore
+	default:
+		invalidCompileMultilib(ctx, compileMultilb)
+	}
+}
+
+func convert64Libs(ctx android.TopDownMutatorContext, compileMultilb string,
+	libs []string, nativeSharedLibs *convertedNativeSharedLibs) {
+	libsLabelList := android.BazelLabelForModuleDeps(ctx, libs)
+	switch compileMultilb {
+	case "both", "64", "first":
+		make64SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "32":
+		// Incompatible, ignore
+	default:
+		invalidCompileMultilib(ctx, compileMultilb)
+	}
+}
+
+func convertBothLibs(ctx android.TopDownMutatorContext, compileMultilb string,
+	libs []string, nativeSharedLibs *convertedNativeSharedLibs) {
+	libsLabelList := android.BazelLabelForModuleDeps(ctx, libs)
+	switch compileMultilb {
+	case "both":
+		makeNoConfig32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+		make64SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "first":
+		makeFirstSharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "32":
+		makeNoConfig32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "64":
+		make64SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	default:
+		invalidCompileMultilib(ctx, compileMultilb)
+	}
+}
+
+func convertFirstLibs(ctx android.TopDownMutatorContext, compileMultilb string,
+	libs []string, nativeSharedLibs *convertedNativeSharedLibs) {
+	libsLabelList := android.BazelLabelForModuleDeps(ctx, libs)
+	switch compileMultilb {
+	case "both", "first":
+		makeFirstSharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "32":
+		make32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	case "64":
+		make64SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	default:
+		invalidCompileMultilib(ctx, compileMultilb)
+	}
+}
+
+func makeFirstSharedLibsAttributes(libsLabelList bazel.LabelList, nativeSharedLibs *convertedNativeSharedLibs) {
+	make32SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+	make64SharedLibsAttributes(libsLabelList, nativeSharedLibs)
+}
+
+func makeNoConfig32SharedLibsAttributes(libsLabelList bazel.LabelList, nativeSharedLibs *convertedNativeSharedLibs) {
+	list := bazel.LabelListAttribute{}
+	list.SetSelectValue(bazel.NoConfigAxis, "", libsLabelList)
+	nativeSharedLibs.Native_shared_libs_32.Append(list)
+}
+
+func make32SharedLibsAttributes(libsLabelList bazel.LabelList, nativeSharedLibs *convertedNativeSharedLibs) {
+	makeSharedLibsAttributes("x86", libsLabelList, &nativeSharedLibs.Native_shared_libs_32)
+	makeSharedLibsAttributes("arm", libsLabelList, &nativeSharedLibs.Native_shared_libs_32)
+}
+
+func make64SharedLibsAttributes(libsLabelList bazel.LabelList, nativeSharedLibs *convertedNativeSharedLibs) {
+	makeSharedLibsAttributes("x86_64", libsLabelList, &nativeSharedLibs.Native_shared_libs_64)
+	makeSharedLibsAttributes("arm64", libsLabelList, &nativeSharedLibs.Native_shared_libs_64)
+}
+
+func makeSharedLibsAttributes(config string, libsLabelList bazel.LabelList,
+	labelListAttr *bazel.LabelListAttribute) {
+	list := bazel.LabelListAttribute{}
+	list.SetSelectValue(bazel.ArchConfigurationAxis, config, libsLabelList)
+	labelListAttr.Append(list)
+}
+
+func invalidCompileMultilib(ctx android.TopDownMutatorContext, value string) {
+	ctx.PropertyErrorf("compile_multilib", "Invalid value: %s", value)
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 7b01b94..b3036b1 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -113,6 +113,12 @@
 	})
 }
 
+func withApexGlobalMinSdkVersionOverride(minSdkOverride *string) android.FixturePreparer {
+	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.ApexGlobalMinSdkVersionOverride = minSdkOverride
+	})
+}
+
 var withBinder32bit = android.FixtureModifyProductVariables(
 	func(variables android.FixtureProductVariables) {
 		variables.Binder32bit = proptools.BoolPtr(true)
@@ -168,44 +174,42 @@
 		"system/sepolicy/apex/otherapex-file_contexts":                nil,
 		"system/sepolicy/apex/com.android.vndk-file_contexts":         nil,
 		"system/sepolicy/apex/com.android.vndk.current-file_contexts": nil,
-		"mylib.cpp":                                  nil,
-		"mytest.cpp":                                 nil,
-		"mytest1.cpp":                                nil,
-		"mytest2.cpp":                                nil,
-		"mytest3.cpp":                                nil,
-		"myprebuilt":                                 nil,
-		"my_include":                                 nil,
-		"foo/bar/MyClass.java":                       nil,
-		"prebuilt.jar":                               nil,
-		"prebuilt.so":                                nil,
-		"vendor/foo/devkeys/test.x509.pem":           nil,
-		"vendor/foo/devkeys/test.pk8":                nil,
-		"testkey.x509.pem":                           nil,
-		"testkey.pk8":                                nil,
-		"testkey.override.x509.pem":                  nil,
-		"testkey.override.pk8":                       nil,
-		"vendor/foo/devkeys/testkey.avbpubkey":       nil,
-		"vendor/foo/devkeys/testkey.pem":             nil,
-		"NOTICE":                                     nil,
-		"custom_notice":                              nil,
-		"custom_notice_for_static_lib":               nil,
-		"testkey2.avbpubkey":                         nil,
-		"testkey2.pem":                               nil,
-		"myapex-arm64.apex":                          nil,
-		"myapex-arm.apex":                            nil,
-		"myapex.apks":                                nil,
-		"frameworks/base/api/current.txt":            nil,
-		"framework/aidl/a.aidl":                      nil,
-		"build/make/core/proguard.flags":             nil,
-		"build/make/core/proguard_basic_keeps.flags": nil,
-		"dummy.txt":                                  nil,
-		"baz":                                        nil,
-		"bar/baz":                                    nil,
-		"testdata/baz":                               nil,
-		"AppSet.apks":                                nil,
-		"foo.rs":                                     nil,
-		"libfoo.jar":                                 nil,
-		"libbar.jar":                                 nil,
+		"mylib.cpp":                            nil,
+		"mytest.cpp":                           nil,
+		"mytest1.cpp":                          nil,
+		"mytest2.cpp":                          nil,
+		"mytest3.cpp":                          nil,
+		"myprebuilt":                           nil,
+		"my_include":                           nil,
+		"foo/bar/MyClass.java":                 nil,
+		"prebuilt.jar":                         nil,
+		"prebuilt.so":                          nil,
+		"vendor/foo/devkeys/test.x509.pem":     nil,
+		"vendor/foo/devkeys/test.pk8":          nil,
+		"testkey.x509.pem":                     nil,
+		"testkey.pk8":                          nil,
+		"testkey.override.x509.pem":            nil,
+		"testkey.override.pk8":                 nil,
+		"vendor/foo/devkeys/testkey.avbpubkey": nil,
+		"vendor/foo/devkeys/testkey.pem":       nil,
+		"NOTICE":                               nil,
+		"custom_notice":                        nil,
+		"custom_notice_for_static_lib":         nil,
+		"testkey2.avbpubkey":                   nil,
+		"testkey2.pem":                         nil,
+		"myapex-arm64.apex":                    nil,
+		"myapex-arm.apex":                      nil,
+		"myapex.apks":                          nil,
+		"frameworks/base/api/current.txt":      nil,
+		"framework/aidl/a.aidl":                nil,
+		"dummy.txt":                            nil,
+		"baz":                                  nil,
+		"bar/baz":                              nil,
+		"testdata/baz":                         nil,
+		"AppSet.apks":                          nil,
+		"foo.rs":                               nil,
+		"libfoo.jar":                           nil,
+		"libbar.jar":                           nil,
 	},
 	),
 
@@ -215,8 +219,11 @@
 		variables.CertificateOverrides = []string{"myapex_keytest:myapex.certificate.override"}
 		variables.Platform_sdk_codename = proptools.StringPtr("Q")
 		variables.Platform_sdk_final = proptools.BoolPtr(false)
-		variables.Platform_version_active_codenames = []string{"Q"}
+		// "Tiramisu" needs to be in the next line for compatibility with soong code,
+		// not because of these tests specifically (it's not used by the tests)
+		variables.Platform_version_active_codenames = []string{"Q", "Tiramisu"}
 		variables.Platform_vndk_version = proptools.StringPtr("29")
+		variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
 	}),
 )
 
@@ -374,7 +381,6 @@
 			symlinks: ["foo_link_"],
 			symlink_preferred_arch: true,
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex", "com.android.gki.*" ],
 		}
@@ -590,15 +596,6 @@
 		t.Errorf("Could not find all expected symlinks! foo: %t, foo_link_64: %t. Command was %s", found_foo, found_foo_link_64, copyCmds)
 	}
 
-	mergeNoticesRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("mergeNoticesRule")
-	noticeInputs := mergeNoticesRule.Inputs.Strings()
-	if len(noticeInputs) != 3 {
-		t.Errorf("number of input notice files: expected = 3, actual = %q", len(noticeInputs))
-	}
-	ensureListContains(t, noticeInputs, "NOTICE")
-	ensureListContains(t, noticeInputs, "custom_notice")
-	ensureListContains(t, noticeInputs, "custom_notice_for_static_lib")
-
 	fullDepsInfo := strings.Split(ctx.ModuleForTests("myapex", "android_common_myapex_image").Output("depsinfo/fulllist.txt").Args["content"], "\\n")
 	ensureListContains(t, fullDepsInfo, "  myjar(minSdkVersion:(no version)) <- myapex")
 	ensureListContains(t, fullDepsInfo, "  mylib2(minSdkVersion:(no version)) <- mylib")
@@ -622,7 +619,7 @@
 			java_libs: ["myjar"],
 			apps: ["AppFoo"],
 			rros: ["rro"],
-			bpfs: ["bpf"],
+			bpfs: ["bpf", "netd_test"],
 			updatable: false,
 		}
 
@@ -675,15 +672,22 @@
 			srcs: ["bpf.c", "bpf2.c"],
 		}
 
+		bpf {
+			name: "netd_test",
+			srcs: ["netd_test.c"],
+			sub_dir: "netd",
+		}
+
 	`)
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"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",
+		"etc/bpf/netd/netd_test.o",
 	})
 }
 
@@ -709,6 +713,79 @@
 	}
 }
 
+func TestApexManifestMinSdkVersion(t *testing.T) {
+	ctx := testApex(t, `
+		apex_defaults {
+			name: "my_defaults",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: ":my-file-contexts",
+			updatable: false,
+		}
+		apex {
+			name: "myapex_30",
+			min_sdk_version: "30",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_current",
+			min_sdk_version: "current",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_none",
+			defaults: ["my_defaults"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		filegroup {
+			name: "my-file-contexts",
+			srcs: ["product_specific_file_contexts"],
+		}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}), android.FixtureModifyProductVariables(
+		func(variables android.FixtureProductVariables) {
+			variables.Unbundled_build = proptools.BoolPtr(true)
+			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(false)
+		}), android.FixtureMergeEnv(map[string]string{
+		"UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT": "true",
+	}))
+
+	testCases := []struct {
+		module        string
+		minSdkVersion string
+	}{
+		{
+			module:        "myapex_30",
+			minSdkVersion: "30",
+		},
+		{
+			module:        "myapex_current",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+		{
+			module:        "myapex_none",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+	}
+	for _, tc := range testCases {
+		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module+"_image")
+		args := module.Rule("apexRule").Args
+		optFlags := args["opt_flags"]
+		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
+			t.Errorf("%s: Expected min_sdk_version=%s, got: %s", tc.module, tc.minSdkVersion, optFlags)
+		}
+	}
+}
+
 func TestBasicZipApex(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -766,6 +843,7 @@
 			name: "myapex",
 			key: "myapex.key",
 			native_shared_libs: ["mylib", "mylib3"],
+			binaries: ["foo.rust"],
 			updatable: false,
 		}
 
@@ -814,6 +892,25 @@
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
+
+		rust_binary {
+			name: "foo.rust",
+			srcs: ["foo.rs"],
+			shared_libs: ["libfoo.shared_from_rust"],
+			prefer_rlib: true,
+			apex_available: ["myapex"],
+		}
+
+		cc_library_shared {
+			name: "libfoo.shared_from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
@@ -840,9 +937,17 @@
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12/mylib3.so")
 
+	// Comment out this test. Now it fails after the optimization of sharing "cflags" in cc/cc.go
+	// is replaced by sharing of "cFlags" in cc/builder.go.
+	// The "cflags" contains "-include mylib.h", but cFlags contained only a reference to the
+	// module variable representing "cflags". So it was not detected by ensureNotContains.
+	// Now "cFlags" is a reference to a module variable like $flags1, which includes all previous
+	// content of "cflags". ModuleForTests...Args["cFlags"] returns the full string of $flags1,
+	// including the original cflags's "-include mylib.h".
+	//
 	// Ensure that stubs libs are built without -include flags
-	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
-	ensureNotContains(t, mylib2Cflags, "-include ")
+	// mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	// ensureNotContains(t, mylib2Cflags, "-include ")
 
 	// Ensure that genstub is invoked with --apex
 	ensureContains(t, "--apex", ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"])
@@ -851,7 +956,93 @@
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
 		"lib64/mylib4.so",
+		"bin/foo.rust",
+		"lib64/libc++.so", // by the implicit dependency from foo.rust
+		"lib64/liblog.so", // by the implicit dependency from foo.rust
 	})
+
+	// Ensure that stub dependency from a rust module is not included
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
+	// The rust module is linked to the stub cc library
+	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
+	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
+
+	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.shared_from_rust.so")
+}
+
+func TestApexCanUsePrivateApis(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			binaries: ["foo.rust"],
+			updatable: false,
+			platform_apis: true,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			cflags: ["-include mylib.h"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
+		rust_binary {
+			name: "foo.rust",
+			srcs: ["foo.rs"],
+			shared_libs: ["libfoo.shared_from_rust"],
+			prefer_rlib: true,
+			apex_available: ["myapex"],
+		}
+
+		cc_library_shared {
+			name: "libfoo.shared_from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+	`)
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that indirect stubs dep is not included
+	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
+
+	// Ensure that we are using non-stub variants of mylib2 and libfoo.shared_from_rust (because
+	// of the platform_apis: true)
+	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
+	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
+	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
 }
 
 func TestApexWithStubsWithMinSdkVersion(t *testing.T) {
@@ -1220,13 +1411,14 @@
 		}
 
 		cc_prebuilt_library_shared {
-			name: "libclang_rt.hwasan-aarch64-android",
+			name: "libclang_rt.hwasan",
 			no_libcrt: true,
 			nocrt: true,
 			stl: "none",
 			system_shared_libs: [],
 			srcs: [""],
 			stubs: { versions: ["1"] },
+			stem: "libclang_rt.hwasan-aarch64-android",
 
 			sanitize: {
 				never: true,
@@ -1239,7 +1431,7 @@
 		"lib64/bionic/libclang_rt.hwasan-aarch64-android.so",
 	})
 
-	hwasan := ctx.ModuleForTests("libclang_rt.hwasan-aarch64-android", "android_arm64_armv8-a_shared")
+	hwasan := ctx.ModuleForTests("libclang_rt.hwasan", "android_arm64_armv8-a_shared")
 
 	installed := hwasan.Description("install libclang_rt.hwasan")
 	ensureContains(t, installed.Output.String(), "/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so")
@@ -1267,13 +1459,14 @@
 		}
 
 		cc_prebuilt_library_shared {
-			name: "libclang_rt.hwasan-aarch64-android",
+			name: "libclang_rt.hwasan",
 			no_libcrt: true,
 			nocrt: true,
 			stl: "none",
 			system_shared_libs: [],
 			srcs: [""],
 			stubs: { versions: ["1"] },
+			stem: "libclang_rt.hwasan-aarch64-android",
 
 			sanitize: {
 				never: true,
@@ -1287,7 +1480,7 @@
 		"lib64/bionic/libclang_rt.hwasan-aarch64-android.so",
 	})
 
-	hwasan := ctx.ModuleForTests("libclang_rt.hwasan-aarch64-android", "android_arm64_armv8-a_shared")
+	hwasan := ctx.ModuleForTests("libclang_rt.hwasan", "android_arm64_armv8-a_shared")
 
 	installed := hwasan.Description("install libclang_rt.hwasan")
 	ensureContains(t, installed.Output.String(), "/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so")
@@ -1620,6 +1813,36 @@
 	expectNoLink("libx", "shared_apex10000", "libz", "shared")
 }
 
+func TestApexMinSdkVersion_SupportsCodeNames_JavaLibs(t *testing.T) {
+	testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["libx"],
+			min_sdk_version: "S",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "libx",
+			srcs: ["a.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "S", // should be okay
+		}
+	`,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"S"}
+			variables.Platform_sdk_codename = proptools.StringPtr("S")
+		}),
+	)
+}
+
 func TestApexMinSdkVersion_DefaultsToLatest(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -1667,6 +1890,45 @@
 	expectNoLink("libx", "shared_apex10000", "libz", "shared")
 }
 
+func TestApexMinSdkVersion_crtobjectInVendorApex(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			updatable: false,
+			vendor: true,
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			vendor_available: true,
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
+		}
+	`)
+
+	vendorVariant := "android_vendor.29_arm64_armv8-a"
+
+	// First check that the correct variant of crtbegin_so is used.
+	ldRule := ctx.ModuleForTests("mylib", vendorVariant+"_shared_apex29").Rule("ld")
+	crtBegin := names(ldRule.Args["crtBegin"])
+	ensureListContains(t, crtBegin, "out/soong/.intermediates/"+cc.DefaultCcCommonTestModulesDir+"crtbegin_so/"+vendorVariant+"_apex29/crtbegin_so.o")
+
+	// Ensure that the crtbegin_so used by the APEX is targeting 29
+	cflags := ctx.ModuleForTests("crtbegin_so", vendorVariant+"_apex29").Rule("cc").Args["cFlags"]
+	android.AssertStringDoesContain(t, "cflags", cflags, "-target aarch64-linux-android29")
+}
+
 func TestPlatformUsesLatestStubsFromApexes(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -1933,6 +2195,7 @@
 		name          string
 		expectedError string
 		bp            string
+		preparer      android.FixturePreparer
 	}{
 		{
 			name: "Non-updatable apex with non-stable dep",
@@ -2004,6 +2267,30 @@
 			`,
 		},
 		{
+			name:          "Updatable apex with non-stable legacy core platform dep",
+			expectedError: `\Qcannot depend on "myjar-uses-legacy": non stable SDK core_platform_current - uses legacy core platform\E`,
+			bp: `
+				apex {
+					name: "myapex",
+					java_libs: ["myjar-uses-legacy"],
+					key: "myapex.key",
+					updatable: true,
+				}
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+				java_library {
+					name: "myjar-uses-legacy",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "core_platform",
+					apex_available: ["myapex"],
+				}
+			`,
+			preparer: java.FixtureUseLegacyCorePlatformApi("myjar-uses-legacy"),
+		},
+		{
 			name: "Updatable apex with non-stable transitive dep",
 			// This is not actually detecting that the transitive dependency is unstable, rather it is
 			// detecting that the transitive dependency is building against a wider API surface than the
@@ -2039,12 +2326,22 @@
 	}
 
 	for _, test := range testCases {
+		if test.name != "Updatable apex with non-stable legacy core platform dep" {
+			continue
+		}
 		t.Run(test.name, func(t *testing.T) {
-			if test.expectedError == "" {
-				testApex(t, test.bp)
-			} else {
-				testApexError(t, test.expectedError, test.bp)
+			errorHandler := android.FixtureExpectsNoErrors
+			if test.expectedError != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
 			}
+			android.GroupFixturePreparers(
+				java.PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				android.OptionalFixturePreparer(test.preparer),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, test.bp)
 		})
 	}
 }
@@ -2097,6 +2394,7 @@
 			key: "myapex.key",
 			apps: ["AppFoo"],
 			min_sdk_version: "29",
+			updatable: false,
 		}
 
 		apex_key {
@@ -2286,29 +2584,27 @@
 			srcs: ["mylib.cpp"],
 			relative_install_path: "foo/bar",
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
 	`)
 
 	generateFsRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("generateFsConfig")
-	dirs := strings.Split(generateFsRule.Args["exec_paths"], " ")
+	cmd := generateFsRule.RuleParams.Command
 
 	// Ensure that the subdirectories are all listed
-	ensureListContains(t, dirs, "etc")
-	ensureListContains(t, dirs, "etc/foo")
-	ensureListContains(t, dirs, "etc/foo/bar")
-	ensureListContains(t, dirs, "lib64")
-	ensureListContains(t, dirs, "lib64/foo")
-	ensureListContains(t, dirs, "lib64/foo/bar")
-	ensureListContains(t, dirs, "lib")
-	ensureListContains(t, dirs, "lib/foo")
-	ensureListContains(t, dirs, "lib/foo/bar")
-
-	ensureListContains(t, dirs, "bin")
-	ensureListContains(t, dirs, "bin/foo")
-	ensureListContains(t, dirs, "bin/foo/bar")
+	ensureContains(t, cmd, "/etc ")
+	ensureContains(t, cmd, "/etc/foo ")
+	ensureContains(t, cmd, "/etc/foo/bar ")
+	ensureContains(t, cmd, "/lib64 ")
+	ensureContains(t, cmd, "/lib64/foo ")
+	ensureContains(t, cmd, "/lib64/foo/bar ")
+	ensureContains(t, cmd, "/lib ")
+	ensureContains(t, cmd, "/lib/foo ")
+	ensureContains(t, cmd, "/lib/foo/bar ")
+	ensureContains(t, cmd, "/bin ")
+	ensureContains(t, cmd, "/bin/foo ")
+	ensureContains(t, cmd, "/bin/foo/bar ")
 }
 
 func TestFilesInSubDirWhenNativeBridgeEnabled(t *testing.T) {
@@ -2346,7 +2642,6 @@
 			name: "mybin",
 			relative_install_path: "foo/bar",
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			apex_available: [ "myapex" ],
 			native_bridge_supported: true,
@@ -2371,7 +2666,10 @@
 }
 
 func TestVendorApex(t *testing.T) {
-	ctx := testApex(t, `
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
@@ -2395,29 +2693,55 @@
 		}
 	`)
 
-	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"bin/mybin",
 		"lib64/libfoo.so",
 		// TODO(b/159195575): Add an option to use VNDK libs from VNDK APEX
 		"lib64/libc++.so",
 	})
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	apexBundle := result.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, result.TestContext, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
-	androidMk := android.StringRelativeToTop(ctx.Config(), builder.String())
+	androidMk := android.StringRelativeToTop(result.Config, builder.String())
 	installPath := "out/target/product/test_device/vendor/apex"
 	ensureContains(t, androidMk, "LOCAL_MODULE_PATH := "+installPath)
 
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
+	apexManifestRule := result.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
 	requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListNotContains(t, requireNativeLibs, ":vndk")
 }
 
+func TestVendorApex_use_vndk_as_stable_TryingToIncludeVNDKLib(t *testing.T) {
+	testApexError(t, `Trying to include a VNDK library`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["libc++"], // libc++ is a VNDK lib
+			vendor: true,
+			use_vndk_as_stable: true,
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}`)
+}
+
 func TestVendorApex_use_vndk_as_stable(t *testing.T) {
+	//   myapex                  myapex2
+	//    |                       |
+	//  mybin ------.           mybin2
+	//   \           \          /  |
+	// (stable)   .---\--------`   |
+	//     \     /     \           |
+	//      \   /       \         /
+	//      libvndk       libvendor
+	//      (vndk)
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2448,28 +2772,95 @@
 		cc_library {
 			name: "libvendor",
 			vendor: true,
+			stl: "none",
+		}
+		apex {
+			name: "myapex2",
+			key: "myapex.key",
+			binaries: ["mybin2"],
+			vendor: true,
+			use_vndk_as_stable: false,
+			updatable: false,
+		}
+		cc_binary {
+			name: "mybin2",
+			vendor: true,
+			shared_libs: ["libvndk", "libvendor"],
 		}
 	`)
 
 	vendorVariant := "android_vendor.29_arm64_armv8-a"
 
-	ldRule := ctx.ModuleForTests("mybin", vendorVariant+"_apex10000").Rule("ld")
-	libs := names(ldRule.Args["libFlags"])
-	// VNDK libs(libvndk/libc++) as they are
-	ensureListContains(t, libs, "out/soong/.intermediates/libvndk/"+vendorVariant+"_shared/libvndk.so")
-	ensureListContains(t, libs, "out/soong/.intermediates/"+cc.DefaultCcCommonTestModulesDir+"libc++/"+vendorVariant+"_shared/libc++.so")
-	// non-stable Vendor libs as APEX variants
-	ensureListContains(t, libs, "out/soong/.intermediates/libvendor/"+vendorVariant+"_shared_apex10000/libvendor.so")
+	for _, tc := range []struct {
+		name                 string
+		apexName             string
+		moduleName           string
+		moduleVariant        string
+		libs                 []string
+		contents             []string
+		requireVndkNamespace bool
+	}{
+		{
+			name:          "use_vndk_as_stable",
+			apexName:      "myapex",
+			moduleName:    "mybin",
+			moduleVariant: vendorVariant + "_apex10000",
+			libs: []string{
+				// should link with vendor variants of VNDK libs(libvndk/libc++)
+				"out/soong/.intermediates/libvndk/" + vendorVariant + "_shared/libvndk.so",
+				"out/soong/.intermediates/" + cc.DefaultCcCommonTestModulesDir + "libc++/" + vendorVariant + "_shared/libc++.so",
+				// unstable Vendor libs as APEX variant
+				"out/soong/.intermediates/libvendor/" + vendorVariant + "_shared_apex10000/libvendor.so",
+			},
+			contents: []string{
+				"bin/mybin",
+				"lib64/libvendor.so",
+				// VNDK libs (libvndk/libc++) are not included
+			},
+			requireVndkNamespace: true,
+		},
+		{
+			name:          "!use_vndk_as_stable",
+			apexName:      "myapex2",
+			moduleName:    "mybin2",
+			moduleVariant: vendorVariant + "_myapex2",
+			libs: []string{
+				// should link with "unique" APEX(myapex2) variant of VNDK libs(libvndk/libc++)
+				"out/soong/.intermediates/libvndk/" + vendorVariant + "_shared_myapex2/libvndk.so",
+				"out/soong/.intermediates/" + cc.DefaultCcCommonTestModulesDir + "libc++/" + vendorVariant + "_shared_myapex2/libc++.so",
+				// unstable vendor libs have "merged" APEX variants
+				"out/soong/.intermediates/libvendor/" + vendorVariant + "_shared_apex10000/libvendor.so",
+			},
+			contents: []string{
+				"bin/mybin2",
+				"lib64/libvendor.so",
+				// VNDK libs are included as well
+				"lib64/libvndk.so",
+				"lib64/libc++.so",
+			},
+			requireVndkNamespace: false,
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			// Check linked libs
+			ldRule := ctx.ModuleForTests(tc.moduleName, tc.moduleVariant).Rule("ld")
+			libs := names(ldRule.Args["libFlags"])
+			for _, lib := range tc.libs {
+				ensureListContains(t, libs, lib)
+			}
+			// Check apex contents
+			ensureExactContents(t, ctx, tc.apexName, "android_common_"+tc.apexName+"_image", tc.contents)
 
-	// VNDK libs are not included when use_vndk_as_stable: true
-	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
-		"bin/mybin",
-		"lib64/libvendor.so",
-	})
-
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
-	requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
-	ensureListContains(t, requireNativeLibs, ":vndk")
+			// Check "requireNativeLibs"
+			apexManifestRule := ctx.ModuleForTests(tc.apexName, "android_common_"+tc.apexName+"_image").Rule("apexManifestRule")
+			requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
+			if tc.requireVndkNamespace {
+				ensureListContains(t, requireNativeLibs, ":vndk")
+			} else {
+				ensureListNotContains(t, requireNativeLibs, ":vndk")
+			}
+		})
+	}
 }
 
 func TestProductVariant(t *testing.T) {
@@ -2500,7 +2891,7 @@
 	)
 
 	cflags := strings.Fields(
-		ctx.ModuleForTests("foo", "android_product.29_arm64_armv8-a_apex10000").Rule("cc").Args["cFlags"])
+		ctx.ModuleForTests("foo", "android_product.29_arm64_armv8-a_myapex").Rule("cc").Args["cFlags"])
 	ensureListContains(t, cflags, "-D__ANDROID_VNDK__")
 	ensureListContains(t, cflags, "-D__ANDROID_APEX__")
 	ensureListContains(t, cflags, "-D__ANDROID_PRODUCT__")
@@ -2898,7 +3289,6 @@
 				"myapex",
 				"otherapex",
 			],
-			use_apex_name_macro: true,
 			recovery_available: true,
 			min_sdk_version: "29",
 		}
@@ -2913,13 +3303,11 @@
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=10000")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex29").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=29")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
 
 	// When a cc_library sets use_apex_name_macro: true each apex gets a unique variant and
 	// each variant defines additional macros to distinguish which apex variant it is built for
@@ -2928,42 +3316,15 @@
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static_otherapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
 	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 
-	// When a dependency of a cc_library sets use_apex_name_macro: true each apex gets a unique
-	// variant.
-
 	// non-APEX variant does not have __ANDROID_APEX__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
-	// APEX variant has __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static_otherapex").Rule("cc").Args["cFlags"]
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MYAPEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_OTHERAPEX__")
-
 	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
@@ -3115,60 +3476,109 @@
 }
 
 func TestVndkApexCurrent(t *testing.T) {
-	ctx := testApex(t, `
-		apex_vndk {
-			name: "com.android.vndk.current",
-			key: "com.android.vndk.current.key",
-			updatable: false,
-		}
-
-		apex_key {
-			name: "com.android.vndk.current.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "libvndk",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-
-		cc_library {
-			name: "libvndksp",
-			srcs: ["mylib.cpp"],
-			vendor_available: true,
-			product_available: true,
-			vndk: {
-				enabled: true,
-				support_system_process: true,
-			},
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: [ "com.android.vndk.current" ],
-		}
-	`+vndkLibrariesTxtFiles("current"))
-
-	ensureExactContents(t, ctx, "com.android.vndk.current", "android_common_image", []string{
-		"lib/libvndk.so",
-		"lib/libvndksp.so",
+	commonFiles := []string{
 		"lib/libc++.so",
-		"lib64/libvndk.so",
-		"lib64/libvndksp.so",
 		"lib64/libc++.so",
 		"etc/llndk.libraries.29.txt",
 		"etc/vndkcore.libraries.29.txt",
 		"etc/vndksp.libraries.29.txt",
 		"etc/vndkprivate.libraries.29.txt",
 		"etc/vndkproduct.libraries.29.txt",
-	})
+	}
+	testCases := []struct {
+		vndkVersion   string
+		expectedFiles []string
+	}{
+		{
+			vndkVersion: "current",
+			expectedFiles: append(commonFiles,
+				"lib/libvndk.so",
+				"lib/libvndksp.so",
+				"lib64/libvndk.so",
+				"lib64/libvndksp.so"),
+		},
+		{
+			vndkVersion: "",
+			expectedFiles: append(commonFiles,
+				// Legacy VNDK APEX contains only VNDK-SP files (of core variant)
+				"lib/libvndksp.so",
+				"lib64/libvndksp.so"),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run("VNDK.current with DeviceVndkVersion="+tc.vndkVersion, func(t *testing.T) {
+			ctx := testApex(t, `
+			apex_vndk {
+				name: "com.android.vndk.current",
+				key: "com.android.vndk.current.key",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "com.android.vndk.current.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			cc_library {
+				name: "libvndk",
+				srcs: ["mylib.cpp"],
+				vendor_available: true,
+				product_available: true,
+				vndk: {
+					enabled: true,
+				},
+				system_shared_libs: [],
+				stl: "none",
+				apex_available: [ "com.android.vndk.current" ],
+			}
+
+			cc_library {
+				name: "libvndksp",
+				srcs: ["mylib.cpp"],
+				vendor_available: true,
+				product_available: true,
+				vndk: {
+					enabled: true,
+					support_system_process: true,
+				},
+				system_shared_libs: [],
+				stl: "none",
+				apex_available: [ "com.android.vndk.current" ],
+			}
+
+			// VNDK-Ext should not cause any problems
+
+			cc_library {
+				name: "libvndk.ext",
+				srcs: ["mylib2.cpp"],
+				vendor: true,
+				vndk: {
+					enabled: true,
+					extends: "libvndk",
+				},
+				system_shared_libs: [],
+				stl: "none",
+			}
+
+			cc_library {
+				name: "libvndksp.ext",
+				srcs: ["mylib2.cpp"],
+				vendor: true,
+				vndk: {
+					enabled: true,
+					support_system_process: true,
+					extends: "libvndksp",
+				},
+				system_shared_libs: [],
+				stl: "none",
+			}
+		`+vndkLibrariesTxtFiles("current"), android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = proptools.StringPtr(tc.vndkVersion)
+			}))
+			ensureExactContents(t, ctx, "com.android.vndk.current", "android_common_image", tc.expectedFiles)
+		})
+	}
 }
 
 func TestVndkApexWithPrebuilt(t *testing.T) {
@@ -3485,7 +3895,7 @@
 		}),
 		withBinder32bit,
 		withTargets(map[android.OsType][]android.Target{
-			android.Android: []android.Target{
+			android.Android: {
 				{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
 					NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
 			},
@@ -3944,7 +4354,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			binaries: ["myscript"],
+			sh_binaries: ["myscript"],
 			updatable: false,
 		}
 
@@ -4166,12 +4576,20 @@
 		}
 	`)
 
-	prebuilt := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+	testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+	prebuilt := testingModule.Module().(*Prebuilt)
 
 	expectedInput := "myapex-arm64.apex"
 	if prebuilt.inputApex.String() != expectedInput {
 		t.Errorf("inputApex invalid. expected: %q, actual: %q", expectedInput, prebuilt.inputApex.String())
 	}
+	android.AssertStringDoesContain(t, "Invalid provenance metadata file",
+		prebuilt.ProvenanceMetaDataFile().String(), "soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto")
+	rule := testingModule.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "myapex-arm64.apex", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.apex", rule.Args["install_path"])
 }
 
 func TestPrebuiltMissingSrc(t *testing.T) {
@@ -4191,12 +4609,47 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+	testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+	p := testingModule.Module().(*Prebuilt)
 
 	expected := "notmyapex.apex"
 	if p.installFilename != expected {
 		t.Errorf("installFilename invalid. expected: %q, actual: %q", expected, p.installFilename)
 	}
+	rule := testingModule.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "myapex", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/apex/notmyapex.apex", rule.Args["install_path"])
+}
+
+func TestApexSetFilenameOverride(t *testing.T) {
+	testApex(t, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "com.company.android.myapex.apex"
+		}
+	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+
+	testApex(t, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "com.company.android.myapex.capex"
+		}
+	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+
+	testApexError(t, `filename should end in .apex or .capex for apex_set`, `
+		apex_set {
+ 			name: "com.company.android.myapex",
+			apex_name: "com.android.myapex",
+			set: "company-myapex.apks",
+      filename: "some-random-suffix"
+		}
+	`)
 }
 
 func TestPrebuiltOverrides(t *testing.T) {
@@ -4210,13 +4663,19 @@
 		}
 	`)
 
-	p := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt").Module().(*Prebuilt)
+	testingModule := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt")
+	p := testingModule.Module().(*Prebuilt)
 
 	expected := []string{"myapex"}
 	actual := android.AndroidMkEntriesForTest(t, ctx, p)[0].EntryMap["LOCAL_OVERRIDES_MODULES"]
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("Incorrect LOCAL_OVERRIDES_MODULES value '%s', expected '%s'", actual, expected)
 	}
+	rule := testingModule.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "myapex-arm.apex", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/myapex.prebuilt/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "myapex.prebuilt", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/apex/myapex.prebuilt.apex", rule.Args["install_path"])
 }
 
 func TestPrebuiltApexName(t *testing.T) {
@@ -4261,7 +4720,15 @@
 
 			prebuilt_bootclasspath_fragment {
 				name: "art-bootclasspath-fragment",
+				image_name: "art",
 				contents: ["core-oj"],
+				hidden_api: {
+					annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+					metadata: "my-bootclasspath-fragment/metadata.csv",
+					index: "my-bootclasspath-fragment/index.csv",
+					stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
+					all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				},
 			}
 
 			java_import {
@@ -4278,20 +4745,33 @@
 	transform := android.NullFixturePreparer
 
 	checkDexJarBuildPath := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
 		// Make sure the import has been given the correct path to the dex jar.
 		p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
-		dexJarBuildPath := p.DexJarBuildPath()
+		dexJarBuildPath := p.DexJarBuildPath().PathOrNil()
 		stem := android.RemoveOptionalPrebuiltPrefix(name)
-		if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/"+stem+".jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected {
-			t.Errorf("Incorrect DexJarBuildPath value '%s', expected '%s'", actual, expected)
-		}
+		android.AssertStringEquals(t, "DexJarBuildPath should be apex-related path.",
+			".intermediates/myapex.deapexer/android_common/deapexer/javalib/"+stem+".jar",
+			android.NormalizePathForTesting(dexJarBuildPath))
+	}
+
+	checkDexJarInstallPath := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
+		// Make sure the import has been given the correct path to the dex jar.
+		p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.UsesLibraryDependency)
+		dexJarBuildPath := p.DexJarInstallPath()
+		stem := android.RemoveOptionalPrebuiltPrefix(name)
+		android.AssertStringEquals(t, "DexJarInstallPath should be apex-related path.",
+			"target/product/test_device/apex/myapex/javalib/"+stem+".jar",
+			android.NormalizePathForTesting(dexJarBuildPath))
 	}
 
 	ensureNoSourceVariant := func(t *testing.T, ctx *android.TestContext, name string) {
+		t.Helper()
 		// Make sure that an apex variant is not created for the source module.
-		if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests(name); !reflect.DeepEqual(expected, actual) {
-			t.Errorf("invalid set of variants for %q: expected %q, found %q", "libfoo", expected, actual)
-		}
+		android.AssertArrayString(t, "Check if there is no source variant",
+			[]string{"android_common"},
+			ctx.ModuleVariantsForTests(name))
 	}
 
 	t.Run("prebuilt only", func(t *testing.T) {
@@ -4325,8 +4805,11 @@
 		// Make sure that dexpreopt can access dex implementation files from the prebuilt.
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
+		deapexerName := deapexerModuleName("myapex")
+		android.AssertStringEquals(t, "APEX module name from deapexer name", "myapex", apexModuleName(deapexerName))
+
 		// Make sure that the deapexer has the correct input APEX.
-		deapexer := ctx.ModuleForTests("myapex.deapexer", "android_common")
+		deapexer := ctx.ModuleForTests(deapexerName, "android_common")
 		rule := deapexer.Rule("deapexer")
 		if expected, actual := []string{"myapex-arm64.apex"}, android.NormalizePathsForTesting(rule.Implicits); !reflect.DeepEqual(expected, actual) {
 			t.Errorf("expected: %q, found: %q", expected, actual)
@@ -4340,8 +4823,10 @@
 		}
 
 		checkDexJarBuildPath(t, ctx, "libfoo")
+		checkDexJarInstallPath(t, ctx, "libfoo")
 
 		checkDexJarBuildPath(t, ctx, "libbar")
+		checkDexJarInstallPath(t, ctx, "libbar")
 	})
 
 	t.Run("prebuilt with source preferred", func(t *testing.T) {
@@ -4387,9 +4872,11 @@
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
+		checkDexJarInstallPath(t, ctx, "prebuilt_libfoo")
 		ensureNoSourceVariant(t, ctx, "libfoo")
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+		checkDexJarInstallPath(t, ctx, "prebuilt_libbar")
 		ensureNoSourceVariant(t, ctx, "libbar")
 	})
 
@@ -4437,16 +4924,18 @@
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libfoo")
+		checkDexJarInstallPath(t, ctx, "prebuilt_libfoo")
 		ensureNoSourceVariant(t, ctx, "libfoo")
 
 		checkDexJarBuildPath(t, ctx, "prebuilt_libbar")
+		checkDexJarInstallPath(t, ctx, "prebuilt_libbar")
 		ensureNoSourceVariant(t, ctx, "libbar")
 	})
 }
 
 func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
 	preparer := android.GroupFixturePreparers(
-		java.FixtureConfigureBootJars("myapex:libfoo", "myapex:libbar"),
+		java.FixtureConfigureApexBootJars("myapex:libfoo", "myapex:libbar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
@@ -4465,11 +4954,11 @@
 			}
 		}
 		if !foundLibfooJar {
-			t.Errorf("Rule for libfoo.jar missing in dex_bootjars singleton outputs %q", android.StringPathsRelativeToTop(ctx.Config().BuildDir(), s.AllOutputs()))
+			t.Errorf("Rule for libfoo.jar missing in dex_bootjars singleton outputs %q", android.StringPathsRelativeToTop(ctx.Config().SoongOutDir(), s.AllOutputs()))
 		}
 	}
 
-	checkHiddenAPIIndexInputs := func(t *testing.T, ctx *android.TestContext, expectedIntermediateInputs string) {
+	checkHiddenAPIIndexFromClassesInputs := func(t *testing.T, ctx *android.TestContext, expectedIntermediateInputs string) {
 		t.Helper()
 		platformBootclasspath := ctx.ModuleForTests("platform-bootclasspath", "android_common")
 		var rule android.TestingBuildParams
@@ -4478,6 +4967,15 @@
 		java.CheckHiddenAPIRuleInputs(t, "intermediate index", expectedIntermediateInputs, rule)
 	}
 
+	checkHiddenAPIIndexFromFlagsInputs := func(t *testing.T, ctx *android.TestContext, expectedIntermediateInputs string) {
+		t.Helper()
+		platformBootclasspath := ctx.ModuleForTests("platform-bootclasspath", "android_common")
+		var rule android.TestingBuildParams
+
+		rule = platformBootclasspath.Output("hiddenapi-index.csv")
+		java.CheckHiddenAPIRuleInputs(t, "monolithic index", expectedIntermediateInputs, rule)
+	}
+
 	fragment := java.ApexVariantReference{
 		Apex:   proptools.StringPtr("myapex"),
 		Module: proptools.StringPtr("my-bootclasspath-fragment"),
@@ -4502,12 +5000,21 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+			},
 		}
 
 		java_import {
 			name: "libfoo",
 			jars: ["libfoo.jar"],
 			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
 		}
 
 		java_sdk_library_import {
@@ -4517,6 +5024,7 @@
 			},
 			apex_available: ["myapex"],
 			shared_library: false,
+			permitted_packages: ["bar"],
 		}
 	`
 
@@ -4525,9 +5033,10 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexInputs(t, ctx, `
-			out/soong/.intermediates/libbar.stubs/android_common/combined/libbar.stubs.jar
-			out/soong/.intermediates/libfoo/android_common_myapex/combined/libfoo.jar
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
+			my-bootclasspath-fragment/index.csv
+			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 		`)
 	})
 
@@ -4543,12 +5052,21 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+			},
 		}
 
 		java_import {
 			name: "libfoo",
 			jars: ["libfoo.jar"],
 			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
 		}
 
 		java_sdk_library_import {
@@ -4558,6 +5076,7 @@
 			},
 			apex_available: ["myapex"],
 			shared_library: false,
+			permitted_packages: ["bar"],
 		}
 	`
 
@@ -4566,9 +5085,10 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexInputs(t, ctx, `
-			out/soong/.intermediates/libbar.stubs/android_common/combined/libbar.stubs.jar
-			out/soong/.intermediates/libfoo/android_common_myapex/combined/libfoo.jar
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
+			my-bootclasspath-fragment/index.csv
+			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 		`)
 	})
 
@@ -4591,6 +5111,13 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
+				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+			},
 		}
 
 		java_import {
@@ -4628,6 +5155,12 @@
 		// find the dex boot jar in it. We either need to disable the source libfoo
 		// or make the prebuilt libfoo preferred.
 		testDexpreoptWithApexes(t, bp, "module libfoo does not provide a dex boot jar", preparer, fragment)
+		// dexbootjar check is skipped if AllowMissingDependencies is true
+		preparerAllowMissingDeps := android.GroupFixturePreparers(
+			preparer,
+			android.PrepareForTestWithAllowMissingDependencies,
+		)
+		testDexpreoptWithApexes(t, bp, "", preparerAllowMissingDeps, fragment)
 	})
 
 	t.Run("prebuilt library preferred with source", func(t *testing.T) {
@@ -4649,6 +5182,14 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+			},
 		}
 
 		java_import {
@@ -4656,6 +5197,7 @@
 			prefer: true,
 			jars: ["libfoo.jar"],
 			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
 		}
 
 		java_library {
@@ -4672,6 +5214,7 @@
 			},
 			apex_available: ["myapex"],
 			shared_library: false,
+			permitted_packages: ["bar"],
 		}
 
 		java_sdk_library {
@@ -4687,9 +5230,10 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexInputs(t, ctx, `
-			out/soong/.intermediates/prebuilt_libbar.stubs/android_common/combined/libbar.stubs.jar
-			out/soong/.intermediates/prebuilt_libfoo/android_common_myapex/combined/libfoo.jar
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
+			my-bootclasspath-fragment/index.csv
+			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 		`)
 	})
 
@@ -4725,6 +5269,14 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+			},
 		}
 
 		java_import {
@@ -4737,6 +5289,7 @@
 			name: "libfoo",
 			srcs: ["foo/bar/MyClass.java"],
 			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
 		}
 
 		java_sdk_library_import {
@@ -4753,6 +5306,7 @@
 			srcs: ["foo/bar/MyClass.java"],
 			unsafe_ignore_missing_latest_api: true,
 			apex_available: ["myapex"],
+			permitted_packages: ["bar"],
 		}
 	`
 
@@ -4761,9 +5315,10 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexInputs(t, ctx, `
-			out/soong/.intermediates/libbar/android_common_myapex/javac/libbar.jar
-			out/soong/.intermediates/libfoo/android_common_apex10000/javac/libfoo.jar
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
+			my-bootclasspath-fragment/index.csv
+			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 		`)
 	})
 
@@ -4799,6 +5354,14 @@
 			name: "my-bootclasspath-fragment",
 			contents: ["libfoo", "libbar"],
 			apex_available: ["myapex"],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+			},
 		}
 
 		java_import {
@@ -4806,6 +5369,7 @@
 			prefer: true,
 			jars: ["libfoo.jar"],
 			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
 		}
 
 		java_library {
@@ -4822,6 +5386,7 @@
 			},
 			apex_available: ["myapex"],
 			shared_library: false,
+			permitted_packages: ["bar"],
 		}
 
 		java_sdk_library {
@@ -4837,9 +5402,10 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexInputs(t, ctx, `
-			out/soong/.intermediates/prebuilt_libbar.stubs/android_common/combined/libbar.stubs.jar
-			out/soong/.intermediates/prebuilt_libfoo/android_common_myapex/combined/libfoo.jar
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
+			my-bootclasspath-fragment/index.csv
+			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 		`)
 	})
 }
@@ -5118,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
@@ -5136,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 {
@@ -5181,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) {
@@ -5223,7 +5819,7 @@
 	}))
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
-		"app/AppFoo/AppFooPrebuilt.apk",
+		"app/AppFoo@TEST.BUILD_ID/AppFooPrebuilt.apk",
 	})
 }
 
@@ -5256,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) {
@@ -5542,6 +6138,11 @@
 			name: "myapex",
 			key: "myapex.key",
 			apps: ["app"],
+			bpfs: ["bpf"],
+			prebuilts: ["myetc"],
+			bootclasspath_fragments: ["mybootclasspath_fragment"],
+			systemserverclasspath_fragments: ["mysystemserverclasspath_fragment"],
+			java_libs: ["myjava_library"],
 			overrides: ["oldapex"],
 			updatable: false,
 		}
@@ -5550,6 +6151,11 @@
 			name: "override_myapex",
 			base: "myapex",
 			apps: ["override_app"],
+			bpfs: ["override_bpf"],
+			prebuilts: ["override_myetc"],
+			bootclasspath_fragments: ["override_bootclasspath_fragment"],
+			systemserverclasspath_fragments: ["override_systemserverclasspath_fragment"],
+			java_libs: ["override_java_library"],
 			overrides: ["unknownapex"],
 			logging_parent: "com.foo.bar",
 			package_name: "test.overridden.package",
@@ -5588,6 +6194,92 @@
 			base: "app",
 			package_name: "bar",
 		}
+
+		bpf {
+			name: "bpf",
+			srcs: ["bpf.c"],
+		}
+
+		bpf {
+			name: "override_bpf",
+			srcs: ["override_bpf.c"],
+		}
+
+		prebuilt_etc {
+			name: "myetc",
+			src: "myprebuilt",
+		}
+
+		prebuilt_etc {
+			name: "override_myetc",
+			src: "override_myprebuilt",
+		}
+
+		java_library {
+			name: "bcplib",
+			srcs: ["a.java"],
+			compile_dex: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["bcp.lib"],
+		}
+
+		bootclasspath_fragment {
+			name: "mybootclasspath_fragment",
+			contents: ["bcplib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "override_bcplib",
+			srcs: ["a.java"],
+			compile_dex: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["override.bcp.lib"],
+		}
+
+		bootclasspath_fragment {
+			name: "override_bootclasspath_fragment",
+			contents: ["override_bcplib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "systemserverlib",
+			srcs: ["a.java"],
+			apex_available: ["myapex"],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspath_fragment",
+			standalone_contents: ["systemserverlib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "override_systemserverlib",
+			srcs: ["a.java"],
+			apex_available: ["myapex"],
+		}
+
+		systemserverclasspath_fragment {
+			name: "override_systemserverclasspath_fragment",
+			standalone_contents: ["override_systemserverlib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "myjava_library",
+			srcs: ["a.java"],
+			compile_dex: true,
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "override_java_library",
+			srcs: ["a.java"],
+			compile_dex: true,
+			apex_available: ["myapex"],
+		}
 	`, withManifestPackageNameOverrides([]string{"myapex:com.android.myapex"}))
 
 	originalVariant := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(android.OverridableModule)
@@ -5603,8 +6295,14 @@
 	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")
+
+	ensureNotContains(t, copyCmds, "image.apex/etc/myetc")
+	ensureContains(t, copyCmds, "image.apex/etc/override_myetc")
 
 	apexBundle := module.Module().(*apexBundle)
 	name := apexBundle.Name()
@@ -5616,6 +6314,13 @@
 		t.Errorf("override_myapex should have logging parent (com.foo.bar), but was %q.", apexBundle.overridableProperties.Logging_parent)
 	}
 
+	android.AssertArrayString(t, "Bootclasspath_fragments does not match",
+		[]string{"override_bootclasspath_fragment"}, apexBundle.overridableProperties.Bootclasspath_fragments)
+	android.AssertArrayString(t, "Systemserverclasspath_fragments does not match",
+		[]string{"override_systemserverclasspath_fragment"}, apexBundle.overridableProperties.Systemserverclasspath_fragments)
+	android.AssertArrayString(t, "Java_libs does not match",
+		[]string{"override_java_library"}, apexBundle.overridableProperties.Java_libs)
+
 	optFlags := apexRule.Args["opt_flags"]
 	ensureContains(t, optFlags, "--override_apk_package_name test.overridden.package")
 	ensureContains(t, optFlags, "--pubkey testkey2.avbpubkey")
@@ -5628,15 +6333,141 @@
 	data.Custom(&builder, name, "TARGET_", "", data)
 	androidMk := builder.String()
 	ensureContains(t, androidMk, "LOCAL_MODULE := override_app.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_bpf.o.override_myapex")
 	ensureContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_bcplib.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_systemserverlib.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_java_library.override_myapex")
 	ensureContains(t, androidMk, "LOCAL_MODULE_STEM := override_myapex.apex")
 	ensureContains(t, androidMk, "LOCAL_OVERRIDES_MODULES := unknownapex myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := app.myapex")
+	ensureNotContains(t, androidMk, "LOCAL_MODULE := bpf.myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := override_app.myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.myapex")
+	ensureNotContains(t, androidMk, "LOCAL_MODULE := override_bcplib.myapex")
+	ensureNotContains(t, androidMk, "LOCAL_MODULE := override_systemserverlib.myapex")
+	ensureNotContains(t, androidMk, "LOCAL_MODULE := override_java_library.pb.myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.apex")
 }
 
+func TestMinSdkVersionOverride(t *testing.T) {
+	// Override from 29 to 31
+	minSdkOverride31 := "31"
+	ctx := testApex(t, `
+			apex {
+					name: "myapex",
+					key: "myapex.key",
+					native_shared_libs: ["mylib"],
+					updatable: true,
+					min_sdk_version: "29"
+			}
+
+			override_apex {
+					name: "override_myapex",
+					base: "myapex",
+					logging_parent: "com.foo.bar",
+					package_name: "test.overridden.package"
+			}
+
+			apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+			}
+
+			cc_library {
+					name: "mylib",
+					srcs: ["mylib.cpp"],
+					runtime_libs: ["libbar"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+			cc_library {
+					name: "libbar",
+					srcs: ["mylib.cpp"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride31))
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+
+	// Ensure that runtime_libs dep in included
+	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
+
+	// Ensure libraries target overridden min_sdk_version value
+	ensureListContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_shared_apex31")
+}
+
+func TestMinSdkVersionOverrideToLowerVersionNoOp(t *testing.T) {
+	// Attempt to override from 31 to 29, should be a NOOP
+	minSdkOverride29 := "29"
+	ctx := testApex(t, `
+			apex {
+					name: "myapex",
+					key: "myapex.key",
+					native_shared_libs: ["mylib"],
+					updatable: true,
+					min_sdk_version: "31"
+			}
+
+			override_apex {
+					name: "override_myapex",
+					base: "myapex",
+					logging_parent: "com.foo.bar",
+					package_name: "test.overridden.package"
+			}
+
+			apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+			}
+
+			cc_library {
+					name: "mylib",
+					srcs: ["mylib.cpp"],
+					runtime_libs: ["libbar"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+			cc_library {
+					name: "libbar",
+					srcs: ["mylib.cpp"],
+					system_shared_libs: [],
+					stl: "none",
+					apex_available: [ "myapex" ],
+					min_sdk_version: "apex_inherit"
+			}
+
+	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride29))
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+
+	// Ensure that runtime_libs dep in included
+	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
+
+	// Ensure libraries target the original min_sdk_version value rather than the overridden
+	ensureListContains(t, ctx.ModuleVariantsForTests("libbar"), "android_arm64_armv8-a_shared_apex31")
+}
+
 func TestLegacyAndroid10Support(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -5732,7 +6563,7 @@
 	})
 	// Permission XML should point to the activated path of impl jar of java_sdk_library
 	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_myapex").Rule("java_sdk_xml")
-	ensureContains(t, sdkLibrary.RuleParams.Command, `<library name=\"foo\" file=\"/apex/myapex/javalib/foo.jar\"`)
+	ensureMatches(t, sdkLibrary.RuleParams.Command, `<library\\n\s+name=\\\"foo\\\"\\n\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"`)
 }
 
 func TestJavaSDKLibrary_WithinApex(t *testing.T) {
@@ -6254,7 +7085,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			jni_libs: ["mylib"],
+			jni_libs: ["mylib", "libfoo.rust"],
 			updatable: false,
 		}
 
@@ -6280,15 +7111,41 @@
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
+
+		rust_ffi_shared {
+			name: "libfoo.rust",
+			crate_name: "foo",
+			srcs: ["foo.rs"],
+			shared_libs: ["libfoo.shared_from_rust"],
+			prefer_rlib: true,
+			apex_available: ["myapex"],
+		}
+
+		cc_library_shared {
+			name: "libfoo.shared_from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+
 	`)
 
 	rule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
 	// Notice mylib2.so (transitive dep) is not added as a jni_lib
-	ensureEquals(t, rule.Args["opt"], "-a jniLibs mylib.so")
+	ensureEquals(t, rule.Args["opt"], "-a jniLibs libfoo.rust.so mylib.so")
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
 		"lib64/mylib.so",
 		"lib64/mylib2.so",
+		"lib64/libfoo.rust.so",
+		"lib64/libc++.so", // auto-added to libfoo.rust by Soong
+		"lib64/liblog.so", // auto-added to libfoo.rust by Soong
 	})
+
+	// b/220397949
+	ensureListContains(t, names(rule.Args["requireNativeLibs"]), "libfoo.shared_from_rust.so")
 }
 
 func TestApexMutatorsDontRunIfDisabled(t *testing.T) {
@@ -6343,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) {
@@ -6374,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) {
@@ -6418,6 +7275,7 @@
 			apex_available: [
 				"some-updatable-apex",
 			],
+			permitted_packages: ["some.updatable.apex.lib"],
 		}
 
 		java_library {
@@ -6427,6 +7285,7 @@
 				"some-non-updatable-apex",
 			],
 			compile_dex: true,
+			permitted_packages: ["some.non.updatable.apex.lib"],
 		}
 
 		bootclasspath_fragment {
@@ -6561,6 +7420,7 @@
 				`, insert))
 			}
 		}),
+		dexpreopt.FixtureSetBootImageProfiles("art/build/boot/boot-image-profile.txt"),
 	).
 		ExtendWithErrorHandler(errorHandler).
 		RunTestWithBp(t, bp)
@@ -6568,17 +7428,18 @@
 	return result.TestContext
 }
 
-func TestDuplicateDeapexeresFromPrebuiltApexes(t *testing.T) {
+func TestDuplicateDeapexersFromPrebuiltApexes(t *testing.T) {
 	preparers := android.GroupFixturePreparers(
 		java.PrepareForTestWithJavaDefaultModules,
 		PrepareForTestWithApexBuildComponents,
 	).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`Ambiguous duplicate deapexer module dependencies "com.android.myapex.deapexer" and "com.mycompany.android.myapex.deapexer"`))
+			"Multiple installable prebuilt APEXes provide ambiguous deapexers: com.android.myapex and com.mycompany.android.myapex"))
 
 	bpBase := `
 		apex_set {
 			name: "com.android.myapex",
+			installable: true,
 			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
 			set: "myapex.apks",
 		}
@@ -6586,6 +7447,7 @@
 		apex_set {
 			name: "com.mycompany.android.myapex",
 			apex_name: "com.android.myapex",
+			installable: true,
 			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
 			set: "company-myapex.apks",
 		}
@@ -6635,6 +7497,107 @@
 	})
 }
 
+func TestDuplicateButEquivalentDeapexersFromPrebuiltApexes(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithApexBuildComponents,
+	)
+
+	bpBase := `
+		apex_set {
+			name: "com.android.myapex",
+			installable: true,
+			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
+			set: "myapex.apks",
+		}
+
+		apex_set {
+			name: "com.android.myapex_compressed",
+			apex_name: "com.android.myapex",
+			installable: true,
+			exported_bootclasspath_fragments: ["my-bootclasspath-fragment"],
+			set: "myapex_compressed.apks",
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "my-bootclasspath-fragment",
+			apex_available: [
+				"com.android.myapex",
+				"com.android.myapex_compressed",
+			],
+			hidden_api: {
+				annotation_flags: "annotation-flags.csv",
+				metadata: "metadata.csv",
+				index: "index.csv",
+				signature_patterns: "signature_patterns.csv",
+			},
+			%s
+		}
+	`
+
+	t.Run("java_import", func(t *testing.T) {
+		result := preparers.RunTestWithBp(t,
+			fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+`
+			java_import {
+				name: "libfoo",
+				jars: ["libfoo.jar"],
+				apex_available: [
+					"com.android.myapex",
+					"com.android.myapex_compressed",
+				],
+			}
+		`)
+
+		module := result.Module("libfoo", "android_common_com.android.myapex")
+		usesLibraryDep := module.(java.UsesLibraryDependency)
+		android.AssertPathRelativeToTopEquals(t, "dex jar path",
+			"out/soong/.intermediates/com.android.myapex.deapexer/android_common/deapexer/javalib/libfoo.jar",
+			usesLibraryDep.DexJarBuildPath().Path())
+	})
+
+	t.Run("java_sdk_library_import", func(t *testing.T) {
+		result := preparers.RunTestWithBp(t,
+			fmt.Sprintf(bpBase, `contents: ["libfoo"]`)+`
+			java_sdk_library_import {
+				name: "libfoo",
+				public: {
+					jars: ["libbar.jar"],
+				},
+				apex_available: [
+					"com.android.myapex",
+					"com.android.myapex_compressed",
+				],
+				compile_dex: true,
+			}
+		`)
+
+		module := result.Module("libfoo", "android_common_com.android.myapex")
+		usesLibraryDep := module.(java.UsesLibraryDependency)
+		android.AssertPathRelativeToTopEquals(t, "dex jar path",
+			"out/soong/.intermediates/com.android.myapex.deapexer/android_common/deapexer/javalib/libfoo.jar",
+			usesLibraryDep.DexJarBuildPath().Path())
+	})
+
+	t.Run("prebuilt_bootclasspath_fragment", func(t *testing.T) {
+		_ = preparers.RunTestWithBp(t, fmt.Sprintf(bpBase, `
+			image_name: "art",
+			contents: ["libfoo"],
+		`)+`
+			java_sdk_library_import {
+				name: "libfoo",
+				public: {
+					jars: ["libbar.jar"],
+				},
+				apex_available: [
+					"com.android.myapex",
+					"com.android.myapex_compressed",
+				],
+				compile_dex: true,
+			}
+		`)
+	})
+}
+
 func TestUpdatable_should_set_min_sdk_version(t *testing.T) {
 	testApexError(t, `"myapex" .*: updatable: updatable APEXes should set min_sdk_version`, `
 		apex {
@@ -6666,6 +7629,66 @@
 	`)
 }
 
+func TestUpdatable_cannot_be_vendor_apex(t *testing.T) {
+	testApexError(t, `"myapex" .*: updatable: vendor APEXes are not updatable`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: true,
+			soc_specific: true,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+}
+
+func TestUpdatable_should_not_set_generate_classpaths_proto(t *testing.T) {
+	testApexError(t, `"mysystemserverclasspathfragment" .* it must not set generate_classpaths_proto to false`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			min_sdk_version: "29",
+			updatable: true,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			min_sdk_version: "29",
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			generate_classpaths_proto: false,
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+}
+
 func TestNoUpdatableJarsInBootImage(t *testing.T) {
 	// Set the BootJars in dexpreopt.GlobalConfig and productVariables to the same value. This can
 	// result in an invalid configuration as it does not set the ArtApexJars and allows art apex
@@ -6693,18 +7716,30 @@
 	}
 
 	t.Run("updatable jar from ART apex in the ART boot image => ok", func(t *testing.T) {
-		preparer := java.FixtureConfigureBootJars("com.android.art.debug:some-art-lib")
-		fragment := java.ApexVariantReference{
-			Apex:   proptools.StringPtr("com.android.art.debug"),
-			Module: proptools.StringPtr("art-bootclasspath-fragment"),
+		preparer := android.GroupFixturePreparers(
+			java.FixtureConfigureBootJars("com.android.art.debug:some-art-lib"),
+			java.FixtureConfigureApexBootJars("some-non-updatable-apex:some-non-updatable-apex-lib"),
+		)
+		fragments := []java.ApexVariantReference{
+			{
+				Apex:   proptools.StringPtr("com.android.art.debug"),
+				Module: proptools.StringPtr("art-bootclasspath-fragment"),
+			},
+			{
+				Apex:   proptools.StringPtr("some-non-updatable-apex"),
+				Module: proptools.StringPtr("some-non-updatable-fragment"),
+			},
 		}
-		testNoUpdatableJarsInBootImage(t, "", preparer, fragment)
+		testNoUpdatableJarsInBootImage(t, "", preparer, fragments...)
 	})
 
 	t.Run("updatable jar from ART apex in the framework boot image => error", func(t *testing.T) {
 		err := `module "some-art-lib" from updatable apexes \["com.android.art.debug"\] is not allowed in the framework boot image`
 		// Update the dexpreopt BootJars directly.
-		preparer := prepareSetBootJars("com.android.art.debug:some-art-lib")
+		preparer := android.GroupFixturePreparers(
+			prepareSetBootJars("com.android.art.debug:some-art-lib"),
+			java.FixtureConfigureApexBootJars("some-non-updatable-apex:some-non-updatable-apex-lib"),
+		)
 		testNoUpdatableJarsInBootImage(t, err, preparer)
 	})
 
@@ -6724,12 +7759,15 @@
 
 	t.Run("updatable jar from some other apex in the framework boot image => error", func(t *testing.T) {
 		err := `module "some-updatable-apex-lib" from updatable apexes \["some-updatable-apex"\] is not allowed in the framework boot image`
-		preparer := java.FixtureConfigureBootJars("some-updatable-apex:some-updatable-apex-lib")
+		preparer := android.GroupFixturePreparers(
+			java.FixtureConfigureBootJars("some-updatable-apex:some-updatable-apex-lib"),
+			java.FixtureConfigureApexBootJars("some-non-updatable-apex:some-non-updatable-apex-lib"),
+		)
 		testNoUpdatableJarsInBootImage(t, err, preparer)
 	})
 
 	t.Run("non-updatable jar from some other apex in the framework boot image => ok", func(t *testing.T) {
-		preparer := java.FixtureConfigureBootJars("some-non-updatable-apex:some-non-updatable-apex-lib")
+		preparer := java.FixtureConfigureApexBootJars("some-non-updatable-apex:some-non-updatable-apex-lib")
 		fragment := java.ApexVariantReference{
 			Apex:   proptools.StringPtr("some-non-updatable-apex"),
 			Module: proptools.StringPtr("some-non-updatable-fragment"),
@@ -6757,13 +7795,22 @@
 	})
 
 	t.Run("platform jar in the framework boot image => ok", func(t *testing.T) {
-		preparer := java.FixtureConfigureBootJars("platform:some-platform-lib")
-		testNoUpdatableJarsInBootImage(t, "", preparer)
+		preparer := android.GroupFixturePreparers(
+			java.FixtureConfigureBootJars("platform:some-platform-lib"),
+			java.FixtureConfigureApexBootJars("some-non-updatable-apex:some-non-updatable-apex-lib"),
+		)
+		fragments := []java.ApexVariantReference{
+			{
+				Apex:   proptools.StringPtr("some-non-updatable-apex"),
+				Module: proptools.StringPtr("some-non-updatable-fragment"),
+			},
+		}
+		testNoUpdatableJarsInBootImage(t, "", preparer, fragments...)
 	})
 }
 
 func TestDexpreoptAccessDexFilesFromPrebuiltApex(t *testing.T) {
-	preparer := java.FixtureConfigureBootJars("myapex:libfoo")
+	preparer := java.FixtureConfigureApexBootJars("myapex:libfoo")
 	t.Run("prebuilt no source", func(t *testing.T) {
 		fragment := java.ApexVariantReference{
 			Apex:   proptools.StringPtr("myapex"),
@@ -6788,18 +7835,27 @@
 				name: "my-bootclasspath-fragment",
 				contents: ["libfoo"],
 				apex_available: ["myapex"],
+				hidden_api: {
+					annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+					metadata: "my-bootclasspath-fragment/metadata.csv",
+					index: "my-bootclasspath-fragment/index.csv",
+					signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+					filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+					filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
+				},
 			}
 
 			java_import {
 				name: "libfoo",
 				jars: ["libfoo.jar"],
 				apex_available: ["myapex"],
+				permitted_packages: ["libfoo"],
 			}
 		`, "", preparer, fragment)
 	})
 }
 
-func testApexPermittedPackagesRules(t *testing.T, errmsg, bp string, apexBootJars []string, rules []android.Rule) {
+func testBootJarPermittedPackagesRules(t *testing.T, errmsg, bp string, bootJars []string, rules []android.Rule) {
 	t.Helper()
 	bp += `
 	apex_key {
@@ -6824,11 +7880,11 @@
 		PrepareForTestWithApexBuildComponents,
 		android.PrepareForTestWithNeverallowRules(rules),
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			updatableBootJars := make([]string, 0, len(apexBootJars))
-			for _, apexBootJar := range apexBootJars {
-				updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
+			apexBootJars := make([]string, 0, len(bootJars))
+			for _, apexBootJar := range bootJars {
+				apexBootJars = append(apexBootJars, "myapex:"+apexBootJar)
 			}
-			variables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
+			variables.ApexBootJars = android.CreateTestConfiguredJarList(apexBootJars)
 		}),
 		fs.AddToFixture(),
 	).
@@ -6838,11 +7894,11 @@
 
 func TestApexPermittedPackagesRules(t *testing.T) {
 	testcases := []struct {
-		name            string
-		expectedError   string
-		bp              string
-		bootJars        []string
-		modulesPackages map[string][]string
+		name                 string
+		expectedError        string
+		bp                   string
+		bootJars             []string
+		bcpPermittedPackages map[string][]string
 	}{
 
 		{
@@ -6872,15 +7928,15 @@
 					updatable: false,
 				}`,
 			bootJars: []string{"bcp_lib1"},
-			modulesPackages: map[string][]string{
-				"myapex": []string{
+			bcpPermittedPackages: map[string][]string{
+				"bcp_lib1": []string{
 					"foo.bar",
 				},
 			},
 		},
 		{
 			name:          "Bootclasspath apex jar not satisfying allowed module packages.",
-			expectedError: `module "bcp_lib2" .* which is restricted because jars that are part of the myapex module may only allow these packages: foo.bar. Please jarjar or move code around.`,
+			expectedError: `(?s)module "bcp_lib2" .* which is restricted because bcp_lib2 bootjar may only use these package prefixes: foo.bar. Please consider the following alternatives:\n    1. If the offending code is from a statically linked library, consider removing that dependency and using an alternative already in the bootclasspath, or perhaps a shared library.    2. Move the offending code into an allowed package.\n    3. Jarjar the offending code. Please be mindful of the potential system health implications of bundling that code, particularly if the offending jar is part of the bootclasspath.`,
 			bp: `
 				java_library {
 					name: "bcp_lib1",
@@ -6906,17 +7962,58 @@
 				}
 			`,
 			bootJars: []string{"bcp_lib1", "bcp_lib2"},
-			modulesPackages: map[string][]string{
-				"myapex": []string{
+			bcpPermittedPackages: map[string][]string{
+				"bcp_lib1": []string{
 					"foo.bar",
 				},
+				"bcp_lib2": []string{
+					"foo.bar",
+				},
+			},
+		},
+		{
+			name:          "Updateable Bootclasspath apex jar not satisfying allowed module packages.",
+			expectedError: "",
+			bp: `
+				java_library {
+					name: "bcp_lib_restricted",
+					srcs: ["lib1/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar"],
+					sdk_version: "none",
+					min_sdk_version: "29",
+					system_modules: "none",
+				}
+				java_library {
+					name: "bcp_lib_unrestricted",
+					srcs: ["lib2/src/*.java"],
+					apex_available: ["myapex"],
+					permitted_packages: ["foo.bar", "bar.baz"],
+					sdk_version: "none",
+					min_sdk_version: "29",
+					system_modules: "none",
+				}
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					java_libs: ["bcp_lib_restricted", "bcp_lib_unrestricted"],
+					updatable: true,
+					min_sdk_version: "29",
+				}
+			`,
+			bootJars: []string{"bcp_lib1", "bcp_lib2"},
+			bcpPermittedPackages: map[string][]string{
+				"bcp_lib1_non_updateable": []string{
+					"foo.bar",
+				},
+				// bcp_lib2_updateable has no entry here since updateable bcp can contain new packages - tracking via an allowlist is not necessary
 			},
 		},
 	}
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
-			rules := createApexPermittedPackagesRules(tc.modulesPackages)
-			testApexPermittedPackagesRules(t, tc.expectedError, tc.bp, tc.bootJars, rules)
+			rules := createBcpPermittedPackagesRules(tc.bcpPermittedPackages)
+			testBootJarPermittedPackagesRules(t, tc.expectedError, tc.bp, tc.bootJars, rules)
 		})
 	}
 }
@@ -7200,6 +8297,28 @@
 			name: "myapex",
 			key: "myapex.key",
 			updatable: false,
+			custom_sign_tool: "sign_myapex",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	apexKeysText := ctx.SingletonForTests("apex_keys_text")
+	content := apexKeysText.MaybeDescription("apexkeys.txt").BuildParams.Args["content"]
+	ensureContains(t, content, `name="myapex.apex" public_key="vendor/foo/devkeys/testkey.avbpubkey" private_key="vendor/foo/devkeys/testkey.pem" container_certificate="vendor/foo/devkeys/test.x509.pem" container_private_key="vendor/foo/devkeys/test.pk8" partition="system_ext" sign_tool="sign_myapex"`)
+}
+
+func TestApexKeysTxtOverrides(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			custom_sign_tool: "sign_myapex",
 		}
 
 		apex_key {
@@ -7627,6 +8746,28 @@
 	}
 }
 
+func TestHostApexInHostOnlyBuild(t *testing.T) {
+	testApex(t, `
+		apex {
+			name: "myapex",
+			host_supported: true,
+			key: "myapex.key",
+			updatable: false,
+			payload_type: "zip",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`,
+		android.FixtureModifyConfig(func(config android.Config) {
+			// We may not have device targets in all builds, e.g. in
+			// prebuilts/build-tools/build-prebuilts.sh
+			config.Targets[android.Android] = []android.Target{}
+		}))
+}
+
 func TestApexJavaCoverage(t *testing.T) {
 	bp := `
 		apex {
@@ -7684,6 +8825,8 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestWithAndroidBuildComponents,
 		android.FixtureWithRootAndroidBp(bp),
+		dexpreopt.FixtureSetApexBootJars("myapex:mybootclasspathlib"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:mysystemserverclasspathlib"),
 		android.FixtureMergeEnv(map[string]string{
 			"EMMA_INSTRUMENT": "true",
 		}),
@@ -7701,6 +8844,682 @@
 	}
 }
 
+func TestProhibitStaticExecutable(t *testing.T) {
+	testApexError(t, `executable mybin is static`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["mybin"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_binary {
+			name: "mybin",
+			srcs: ["mylib.cpp"],
+			relative_install_path: "foo/bar",
+			static_executable: true,
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+			min_sdk_version: "29",
+		}
+	`)
+
+	testApexError(t, `executable mybin.rust is static`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["mybin.rust"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		rust_binary {
+			name: "mybin.rust",
+			srcs: ["foo.rs"],
+			static_executable: true,
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
+	`)
+}
+
+func TestAndroidMk_DexpreoptBuiltInstalledForApex(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["foo.java"],
+			apex_available: ["myapex"],
+			installable: true,
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	var builder strings.Builder
+	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex")
+}
+
+func TestAndroidMk_DexpreoptBuiltInstalledForApex_Prebuilt(t *testing.T) {
+	ctx := testApex(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_java_libs: ["foo"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: ["myapex"],
+		}
+	`,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	)
+
+	prebuilt := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*Prebuilt)
+	entriesList := android.AndroidMkEntriesForTest(t, ctx, prebuilt)
+	mainModuleEntries := entriesList[0]
+	android.AssertArrayString(t,
+		"LOCAL_REQUIRED_MODULES",
+		mainModuleEntries.EntryMap["LOCAL_REQUIRED_MODULES"],
+		[]string{
+			"foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex",
+			"foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex",
+		})
+}
+
+func TestAndroidMk_RequiredModules(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+			required: ["otherapex"],
+		}
+
+		apex {
+			name: "otherapex",
+			key: "myapex.key",
+			updatable: false,
+			java_libs: ["foo"],
+			required: ["otherapex"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["foo.java"],
+			apex_available: ["myapex", "otherapex"],
+			installable: true,
+		}
+	`)
+
+	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
+	var builder strings.Builder
+	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += otherapex")
+}
+
+func TestAndroidMk_RequiredDeps(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	bundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
+	bundle.requiredDeps = append(bundle.requiredDeps, "foo")
+	data := android.AndroidMkDataForTest(t, ctx, bundle)
+	var builder strings.Builder
+	data.Custom(&builder, bundle.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += foo")
+
+	flattenedBundle := ctx.ModuleForTests("myapex", "android_common_myapex_flattened").Module().(*apexBundle)
+	flattenedBundle.requiredDeps = append(flattenedBundle.requiredDeps, "foo")
+	flattenedData := android.AndroidMkDataForTest(t, ctx, flattenedBundle)
+	var flattenedBuilder strings.Builder
+	flattenedData.Custom(&flattenedBuilder, flattenedBundle.BaseModuleName(), "TARGET_", "", flattenedData)
+	flattenedAndroidMk := flattenedBuilder.String()
+	ensureContains(t, flattenedAndroidMk, "LOCAL_REQUIRED_MODULES += foo")
+}
+
+func TestApexOutputFileProducer(t *testing.T) {
+	for _, tc := range []struct {
+		name          string
+		ref           string
+		expected_data []string
+	}{
+		{
+			name:          "test_using_output",
+			ref:           ":myapex",
+			expected_data: []string{"out/soong/.intermediates/myapex/android_common_myapex_image/myapex.capex:myapex.capex"},
+		},
+		{
+			name:          "test_using_apex",
+			ref:           ":myapex{.apex}",
+			expected_data: []string{"out/soong/.intermediates/myapex/android_common_myapex_image/myapex.apex:myapex.apex"},
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := testApex(t, `
+					apex {
+						name: "myapex",
+						key: "myapex.key",
+						compressible: true,
+						updatable: false,
+					}
+
+					apex_key {
+						name: "myapex.key",
+						public_key: "testkey.avbpubkey",
+						private_key: "testkey.pem",
+					}
+
+					java_test {
+						name: "`+tc.name+`",
+						srcs: ["a.java"],
+						data: ["`+tc.ref+`"],
+					}
+				`,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.CompressedApex = proptools.BoolPtr(true)
+				}))
+			javaTest := ctx.ModuleForTests(tc.name, "android_common").Module().(*java.Test)
+			data := android.AndroidMkEntriesForTest(t, ctx, javaTest)[0].EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
+			android.AssertStringPathsRelativeToTopEquals(t, "data", ctx.Config(), tc.expected_data, data)
+		})
+	}
+}
+
+func TestSdkLibraryCanHaveHigherMinSdkVersion(t *testing.T) {
+	preparer := android.GroupFixturePreparers(
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestWithAndroidBuildComponents,
+		dexpreopt.FixtureSetApexBootJars("myapex:mybootclasspathlib"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:mysystemserverclasspathlib"),
+	)
+
+	// Test java_sdk_library in bootclasspath_fragment may define higher min_sdk_version than the apex
+	t.Run("bootclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		preparer.RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+				min_sdk_version: "30",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: ["mybootclasspathlib"],
+				apex_available: ["myapex"],
+			}
+
+			java_sdk_library {
+				name: "mybootclasspathlib",
+				srcs: ["mybootclasspathlib.java"],
+				apex_available: ["myapex"],
+				compile_dex: true,
+				unsafe_ignore_missing_latest_api: true,
+				min_sdk_version: "31",
+				static_libs: ["util"],
+			}
+
+			java_library {
+				name: "util",
+                srcs: ["a.java"],
+				apex_available: ["myapex"],
+				min_sdk_version: "31",
+				static_libs: ["another_util"],
+			}
+
+			java_library {
+				name: "another_util",
+                srcs: ["a.java"],
+				min_sdk_version: "31",
+				apex_available: ["myapex"],
+			}
+		`)
+	})
+
+	// Test java_sdk_library in systemserverclasspath_fragment may define higher min_sdk_version than the apex
+	t.Run("systemserverclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		preparer.RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+				min_sdk_version: "30",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			systemserverclasspath_fragment {
+				name: "mysystemserverclasspathfragment",
+				contents: ["mysystemserverclasspathlib"],
+				apex_available: ["myapex"],
+			}
+
+			java_sdk_library {
+				name: "mysystemserverclasspathlib",
+				srcs: ["mysystemserverclasspathlib.java"],
+				apex_available: ["myapex"],
+				compile_dex: true,
+				min_sdk_version: "32",
+				unsafe_ignore_missing_latest_api: true,
+				static_libs: ["util"],
+			}
+
+			java_library {
+				name: "util",
+                srcs: ["a.java"],
+				apex_available: ["myapex"],
+				min_sdk_version: "31",
+				static_libs: ["another_util"],
+			}
+
+			java_library {
+				name: "another_util",
+                srcs: ["a.java"],
+				min_sdk_version: "31",
+				apex_available: ["myapex"],
+			}
+		`)
+	})
+
+	t.Run("bootclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "mybootclasspathlib".*must set min_sdk_version`)).
+			RunTestWithBp(t, `
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					bootclasspath_fragments: ["mybootclasspathfragment"],
+					min_sdk_version: "30",
+					updatable: false,
+				}
+
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+
+				bootclasspath_fragment {
+					name: "mybootclasspathfragment",
+					contents: ["mybootclasspathlib"],
+					apex_available: ["myapex"],
+				}
+
+				java_sdk_library {
+					name: "mybootclasspathlib",
+					srcs: ["mybootclasspathlib.java"],
+					apex_available: ["myapex"],
+					compile_dex: true,
+					unsafe_ignore_missing_latest_api: true,
+				}
+		`)
+	})
+
+	t.Run("systemserverclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "mysystemserverclasspathlib".*must set min_sdk_version`)).
+			RunTestWithBp(t, `
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+					min_sdk_version: "30",
+					updatable: false,
+				}
+
+				apex_key {
+					name: "myapex.key",
+					public_key: "testkey.avbpubkey",
+					private_key: "testkey.pem",
+				}
+
+				systemserverclasspath_fragment {
+					name: "mysystemserverclasspathfragment",
+					contents: ["mysystemserverclasspathlib"],
+					apex_available: ["myapex"],
+				}
+
+				java_sdk_library {
+					name: "mysystemserverclasspathlib",
+					srcs: ["mysystemserverclasspathlib.java"],
+					apex_available: ["myapex"],
+					compile_dex: true,
+					unsafe_ignore_missing_latest_api: true,
+				}
+		`)
+	})
+}
+
+// Verifies that the APEX depends on all the Make modules in the list.
+func ensureContainsRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
+	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
+	for _, dep := range deps {
+		android.AssertStringListContains(t, "", a.requiredDeps, dep)
+	}
+}
+
+// Verifies that the APEX does not depend on any of the Make modules in the list.
+func ensureDoesNotContainRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
+	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
+	for _, dep := range deps {
+		android.AssertStringListDoesNotContain(t, "", a.requiredDeps, dep)
+	}
+}
+
+func TestApexStrictUpdtabilityLint(t *testing.T) {
+	bpTemplate := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["myjavalib"],
+			updatable: %v,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			lint: {
+				strict_updatability_linting: %v,
+			},
+			sdk_version: "current",
+			min_sdk_version: "29",
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	testCases := []struct {
+		testCaseName              string
+		apexUpdatable             bool
+		javaStrictUpdtabilityLint bool
+		lintFileExists            bool
+		disallowedFlagExpected    bool
+	}{
+		{
+			testCaseName:              "lint-baseline.xml does not exist, no disallowed flag necessary in lint cmd",
+			apexUpdatable:             true,
+			javaStrictUpdtabilityLint: true,
+			lintFileExists:            false,
+			disallowedFlagExpected:    false,
+		},
+		{
+			testCaseName:              "non-updatable apex respects strict_updatability of javalib",
+			apexUpdatable:             false,
+			javaStrictUpdtabilityLint: false,
+			lintFileExists:            true,
+			disallowedFlagExpected:    false,
+		},
+		{
+			testCaseName:              "non-updatable apex respects strict updatability of javalib",
+			apexUpdatable:             false,
+			javaStrictUpdtabilityLint: true,
+			lintFileExists:            true,
+			disallowedFlagExpected:    true,
+		},
+		{
+			testCaseName:              "updatable apex sets strict updatability of javalib to true",
+			apexUpdatable:             true,
+			javaStrictUpdtabilityLint: false, // will be set to true by mutator
+			lintFileExists:            true,
+			disallowedFlagExpected:    true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		bp := fmt.Sprintf(bpTemplate, testCase.apexUpdatable, testCase.javaStrictUpdtabilityLint)
+		fixtures := []android.FixturePreparer{}
+		if testCase.lintFileExists {
+			fixtures = append(fixtures, fs.AddToFixture())
+		}
+
+		result := testApex(t, bp, fixtures...)
+		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+		sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
+		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi")
+
+		if disallowedFlagActual != testCase.disallowedFlagExpected {
+			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
+		}
+	}
+}
+
+func TestUpdatabilityLintSkipLibcore(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["myjavalib"],
+			updatable: true,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+		}
+		`
+
+	testCases := []struct {
+		testCaseName           string
+		moduleDirectory        string
+		disallowedFlagExpected bool
+	}{
+		{
+			testCaseName:           "lintable module defined outside libcore",
+			moduleDirectory:        "",
+			disallowedFlagExpected: true,
+		},
+		{
+			testCaseName:           "lintable module defined in libcore root directory",
+			moduleDirectory:        "libcore/",
+			disallowedFlagExpected: false,
+		},
+		{
+			testCaseName:           "lintable module defined in libcore child directory",
+			moduleDirectory:        "libcore/childdir/",
+			disallowedFlagExpected: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		lintFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"lint-baseline.xml", "")
+		bpFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"Android.bp", bp)
+		result := testApex(t, "", lintFileCreator, bpFileCreator)
+		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+		sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
+		cmdFlags := fmt.Sprintf("--baseline %vlint-baseline.xml --disallowed_issues NewApi", testCase.moduleDirectory)
+		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, cmdFlags)
+
+		if disallowedFlagActual != testCase.disallowedFlagExpected {
+			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
+		}
+	}
+}
+
+// checks transtive deps of an apex coming from bootclasspath_fragment
+func TestApexStrictUpdtabilityLintBcpFragmentDeps(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			updatable: true,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["myjavalib"],
+			apex_available: ["myapex"],
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			compile_dex: true,
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := testApex(t, bp, dexpreopt.FixtureSetApexBootJars("myapex:myjavalib"), fs.AddToFixture())
+	myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi") {
+		t.Errorf("Strict updabality lint missing in myjavalib coming from bootclasspath_fragment mybootclasspath-fragment\nActual lint cmd: %v", *sboxProto.Commands[0].Command)
+	}
+}
+
+// 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/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 4b1600e..ce6b7f7 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -16,12 +16,15 @@
 
 import (
 	"fmt"
+	"path"
 	"sort"
 	"strings"
 	"testing"
 
 	"android/soong/android"
+	"android/soong/dexpreopt"
 	"android/soong/java"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -34,11 +37,14 @@
 )
 
 // Some additional files needed for the art apex.
-var prepareForTestWithArtApex = android.FixtureMergeMockFs(android.MockFS{
-	"com.android.art.avbpubkey":                          nil,
-	"com.android.art.pem":                                nil,
-	"system/sepolicy/apex/com.android.art-file_contexts": nil,
-})
+var prepareForTestWithArtApex = android.GroupFixturePreparers(
+	android.FixtureMergeMockFs(android.MockFS{
+		"com.android.art.avbpubkey":                          nil,
+		"com.android.art.pem":                                nil,
+		"system/sepolicy/apex/com.android.art-file_contexts": nil,
+	}),
+	dexpreopt.FixtureSetBootImageProfiles("art/build/boot/boot-image-profile.txt"),
+)
 
 func TestBootclasspathFragments(t *testing.T) {
 	result := android.GroupFixturePreparers(
@@ -130,7 +136,8 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		// Configure some libraries in the art bootclasspath_fragment and platform_bootclasspath.
-		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "platform:foo", "platform:bar"),
+		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz"),
+		java.FixtureConfigureApexBootJars("someapex:foo", "someapex:bar"),
 		prepareForTestWithArtApex,
 
 		java.PrepareForTestWithJavaSdkLibraryFiles,
@@ -383,6 +390,13 @@
 				apex_available: [
 					"com.android.art",
 				],
+				hidden_api: {
+					annotation_flags: "mybootclasspathfragment/annotation-flags.csv",
+					metadata: "mybootclasspathfragment/metadata.csv",
+					index: "mybootclasspathfragment/index.csv",
+					stub_flags: "mybootclasspathfragment/stub-flags.csv",
+					all_flags: "mybootclasspathfragment/all-flags.csv",
+				},
 			}
 		`, contentsInsert(contents), prefer)
 		return android.FixtureAddTextFile("prebuilts/module_sdk/art/Android.bp", text)
@@ -396,9 +410,11 @@
 			// bootclasspath_fragment's contents property.
 			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
 			addSource("foo", "bar"),
+			java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
 		).RunTest(t)
 
 		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
 			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
@@ -422,12 +438,80 @@
 			`mybootclasspathfragment`,
 		})
 
+		// The boot images are installed in the APEX by Soong, so there shouldn't be any dexpreopt-related Make modules.
+		ensureDoesNotContainRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+
 		// Make sure that the source bootclasspath_fragment copies its dex files to the predefined
 		// locations for the art image.
 		module := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image files from source no boot image in apex", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+			java.FixtureSetBootImageInstallDirOnDevice("art", "system/framework"),
+		).RunTest(t)
+
+		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
+			"etc/classpaths/bootclasspath.pb",
+			"javalib/bar.jar",
+			"javalib/foo.jar",
+		})
+
+		ensureContainsRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+	})
+
+	t.Run("boot image disable generate profile", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+			dexpreopt.FixtureDisableGenerateProfile(true),
+		).RunTest(t)
+
+		files := getFiles(t, result.TestContext, "com.android.art", "android_common_com.android.art_image")
+		for _, file := range files {
+			matched, _ := path.Match("etc/boot-image.prof", file.path)
+			android.AssertBoolEquals(t, "\"etc/boot-image.prof\" should not be in the APEX", matched, false)
+		}
+	})
+
 	t.Run("boot image files with preferred prebuilt", func(t *testing.T) {
 		result := android.GroupFixturePreparers(
 			commonPreparer,
@@ -439,9 +523,12 @@
 
 			// Make sure that a preferred prebuilt with consistent contents doesn't affect the apex.
 			addPrebuilt(true, "foo", "bar"),
+
+			java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
 		).RunTest(t)
 
 		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
 			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
@@ -466,12 +553,66 @@
 			`prebuilt_com.android.art`,
 		})
 
+		// The boot images are installed in the APEX by Soong, so there shouldn't be any dexpreopt-related Make modules.
+		ensureDoesNotContainRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+
 		// Make sure that the prebuilt bootclasspath_fragment copies its dex files to the predefined
 		// locations for the art image.
 		module := result.ModuleForTests("prebuilt_mybootclasspathfragment", "android_common_com.android.art")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image files from preferred prebuilt no boot image in apex", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+
+			// Make sure that a preferred prebuilt with consistent contents doesn't affect the apex.
+			addPrebuilt(true, "foo", "bar"),
+
+			java.FixtureSetBootImageInstallDirOnDevice("art", "system/framework"),
+		).RunTest(t)
+
+		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
+			"etc/classpaths/bootclasspath.pb",
+			"javalib/bar.jar",
+			"javalib/foo.jar",
+		})
+
+		ensureContainsRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+	})
+
 	t.Run("source with inconsistency between config and contents", func(t *testing.T) {
 		android.GroupFixturePreparers(
 			commonPreparer,
@@ -533,7 +674,7 @@
 }
 
 func TestBootclasspathFragmentInPrebuiltArtApex(t *testing.T) {
-	result := android.GroupFixturePreparers(
+	preparers := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
 
@@ -544,7 +685,10 @@
 
 		// Configure some libraries in the art bootclasspath_fragment.
 		java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
-	).RunTestWithBp(t, `
+		java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
+	)
+
+	bp := `
 		prebuilt_apex {
 			name: "com.android.art",
 			arch: {
@@ -582,23 +726,53 @@
 			apex_available: [
 				"com.android.art",
 			],
+			hidden_api: {
+				annotation_flags: "mybootclasspathfragment/annotation-flags.csv",
+				metadata: "mybootclasspathfragment/metadata.csv",
+				index: "mybootclasspathfragment/index.csv",
+				stub_flags: "mybootclasspathfragment/stub-flags.csv",
+				all_flags: "mybootclasspathfragment/all-flags.csv",
+			},
 		}
-	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
-		`com.android.art.apex.selector`,
-		`prebuilt_mybootclasspathfragment`,
+		// A prebuilt apex with the same apex_name that shouldn't interfere when it isn't enabled.
+		prebuilt_apex {
+			name: "com.mycompany.android.art",
+			apex_name: "com.android.art",
+			%s
+			src: "com.mycompany.android.art.apex",
+			exported_bootclasspath_fragments: ["mybootclasspathfragment"],
+		}
+	`
+
+	t.Run("disabled alternative APEX", func(t *testing.T) {
+		result := preparers.RunTestWithBp(t, fmt.Sprintf(bp, "enabled: false,"))
+
+		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+			`com.android.art.apex.selector`,
+			`prebuilt_mybootclasspathfragment`,
+		})
+
+		java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_com.android.art", []string{
+			`com.android.art.deapexer`,
+			`dex2oatd`,
+			`prebuilt_bar`,
+			`prebuilt_foo`,
+		})
+
+		module := result.ModuleForTests("mybootclasspathfragment", "android_common_com.android.art")
+		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
+
+		// Check that the right deapexer module was chosen for a boot image.
+		param := module.Output("out/soong/test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot.art")
+		android.AssertStringDoesContain(t, "didn't find the expected deapexer in the input path", param.Input.String(), "/com.android.art.deapexer")
 	})
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_com.android.art", []string{
-		`com.android.art.deapexer`,
-		`dex2oatd`,
-		`prebuilt_bar`,
-		`prebuilt_foo`,
+	t.Run("enabled alternative APEX", func(t *testing.T) {
+		preparers.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			"Multiple installable prebuilt APEXes provide ambiguous deapexers: com.android.art and com.mycompany.android.art")).
+			RunTestWithBp(t, fmt.Sprintf(bp, ""))
 	})
-
-	module := result.ModuleForTests("mybootclasspathfragment", "android_common_com.android.art")
-	checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 }
 
 // checkCopiesToPredefinedLocationForArt checks that the supplied modules are copied to the
@@ -628,7 +802,7 @@
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithMyapex,
 		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
-		java.FixtureConfigureBootJars("myapex:foo", "myapex:bar"),
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
@@ -722,7 +896,7 @@
 
 func getDexJarPath(result *android.TestResult, name string) string {
 	module := result.Module(name, "android_common")
-	return module.(java.UsesLibraryDependency).DexJarBuildPath().RelativeToTop().String()
+	return module.(java.UsesLibraryDependency).DexJarBuildPath().Path().RelativeToTop().String()
 }
 
 // TestBootclasspathFragment_HiddenAPIList checks to make sure that the correct parameters are
@@ -734,7 +908,7 @@
 		prepareForTestWithMyapex,
 		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
 		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz"),
-		java.FixtureConfigureUpdatableBootJars("myapex:foo", "myapex:bar"),
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
@@ -879,7 +1053,8 @@
 		prepareForTestWithArtApex,
 		prepareForTestWithMyapex,
 		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
-		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "myapex:foo", "myapex:bar"),
+		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz"),
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
@@ -1048,7 +1223,8 @@
 		prepareForTestWithArtApex,
 		prepareForTestWithMyapex,
 		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
-		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "myapex:foo", "myapex:bar"),
+		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz"),
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
diff --git a/apex/builder.go b/apex/builder.go
index d2e6ad8..a21fcb8 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -17,7 +17,6 @@
 import (
 	"encoding/json"
 	"fmt"
-	"path"
 	"path/filepath"
 	"runtime"
 	"sort"
@@ -66,24 +65,13 @@
 	pctx.HostBinToolVariable("extract_apks", "extract_apks")
 	pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
 	pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
+	pctx.HostBinToolVariable("make_erofs", "make_erofs")
 	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
+	pctx.HostBinToolVariable("dexdeps", "dexdeps")
 	pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
 }
 
 var (
-	// Create a canned fs config file where all files and directories are
-	// by default set to (uid/gid/mode) = (1000/1000/0644)
-	// TODO(b/113082813) make this configurable using config.fs syntax
-	generateFsConfig = pctx.StaticRule("generateFsConfig", blueprint.RuleParams{
-		Command: `( echo '/ 1000 1000 0755' ` +
-			`&& for i in ${ro_paths}; do echo "/$$i 1000 1000 0644"; done ` +
-			`&& for i in  ${exec_paths}; do echo "/$$i 0 2000 0755"; done ` +
-			`&& ( tr ' ' '\n' <${out}.apklist | for i in ${apk_paths}; do read apk; echo "/$$i 0 2000 0755"; zipinfo -1 $$apk | sed "s:\(.*\):/$$i/\1 1000 1000 0644:"; done ) ) > ${out}`,
-		Description:    "fs_config ${out}",
-		Rspfile:        "$out.apklist",
-		RspfileContent: "$in",
-	}, "ro_paths", "exec_paths", "apk_paths")
-
 	apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{
 		Command: `rm -f $out && ${jsonmodify} $in ` +
 			`-a provideNativeLibs ${provideNativeLibs} ` +
@@ -106,7 +94,7 @@
 		Description: "convert ${in}=>${out}",
 	})
 
-	// TODO(b/113233103): make sure that file_contexts is sane, i.e., validate
+	// TODO(b/113233103): make sure that file_contexts is as expected, i.e., validate
 	// against the binary policy using sefcontext_compiler -p <policy>.
 
 	// TODO(b/114327326): automate the generation of file_contexts
@@ -121,7 +109,7 @@
 			`--payload_type image ` +
 			`--key ${key} ${opt_flags} ${image_dir} ${out} `,
 		CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
-			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}",
+			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}",
 			"${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"},
 		Rspfile:        "${out}.copy_commands",
 		RspfileContent: "${copy_commands}",
@@ -256,14 +244,24 @@
 // labeled as system_file.
 func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
 	var fileContexts android.Path
+	var fileContextsDir string
 	if a.properties.File_contexts == nil {
 		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
 	} else {
+		if m, t := android.SrcIsModuleWithTag(*a.properties.File_contexts); m != "" {
+			otherModule := android.GetModuleFromPathDep(ctx, m, t)
+			fileContextsDir = ctx.OtherModuleDir(otherModule)
+		}
 		fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
 	}
+	if fileContextsDir == "" {
+		fileContextsDir = filepath.Dir(fileContexts.String())
+	}
+	fileContextsDir += string(filepath.Separator)
+
 	if a.Platform() {
-		if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
-			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
+		if !strings.HasPrefix(fileContextsDir, "system/sepolicy/") {
+			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but found in  %q", fileContextsDir)
 		}
 	}
 	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
@@ -307,32 +305,6 @@
 	return output.OutputPath
 }
 
-// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
-// contributes to this APEX. The notice files are merged into a big notice file.
-func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
-	var noticeFiles android.Paths
-
-	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if externalDep {
-			// As soon as the dependency graph crosses the APEX boundary, don't go further.
-			return false
-		}
-		noticeFiles = append(noticeFiles, to.NoticeFiles()...)
-		return true
-	})
-
-	// TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
-	for _, fi := range a.filesInfo {
-		noticeFiles = append(noticeFiles, fi.noticeFiles...)
-	}
-
-	if len(noticeFiles) == 0 {
-		return android.NoticeOutputs{}
-	}
-
-	return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
-}
-
 // buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
 // files included in this APEX is shown. The text file is dist'ed so that people can see what's
 // included in the APEX without actually downloading and extracting it.
@@ -399,22 +371,47 @@
 	return output.OutputPath
 }
 
+func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path {
+	return java.ManifestFixer(ctx, androidManifestFile, java.ManifestFixerParams{
+		TestOnly: true,
+	})
+}
+
 // buildUnflattendApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
+	apexName := proptools.StringDefault(a.properties.Apex_name, a.BaseModuleName())
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1: copy built files to appropriate directories under the image directory
 
 	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 
+	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
+
+	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
+	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
+	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
+	// for the overriding VNDK APEXes.
+	if a.vndkApex && len(a.overridableProperties.Overrides) > 0 {
+		installSymbolFiles = false
+	}
+
+	// Avoid creating duplicate build rules for multi-installed APEXes.
+	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
+		installSymbolFiles = false
+
+	}
+	// set of dependency module:location mappings
+	installMapSet := make(map[string]bool)
+
 	// TODO(jiyong): use the RuleBuilder
 	var copyCommands []string
 	var implicitInputs []android.Path
+	pathWhenActivated := android.PathForModuleInPartitionInstall(ctx, "apex", apexName)
 	for _, fi := range a.filesInfo {
 		destPath := imageDir.Join(ctx, fi.path()).String()
-
 		// Prepare the destination path
 		destPathDir := filepath.Dir(destPath)
 		if fi.class == appSet {
@@ -422,6 +419,8 @@
 		}
 		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
 
+		installMapPath := fi.builtFile
+
 		// Copy the built file to the directory. But if the symlink optimization is turned
 		// on, place a symlink to the corresponding file in /system partition instead.
 		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
@@ -429,20 +428,38 @@
 			pathOnDevice := filepath.Join("/system", fi.path())
 			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
 		} else {
+			var installedPath android.InstallPath
 			if fi.class == appSet {
 				copyCommands = append(copyCommands,
-					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, fi.builtFile.String()))
+					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir,
+						fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String()))
+				if installSymbolFiles {
+					installedPath = ctx.InstallFileWithExtraFilesZip(pathWhenActivated.Join(ctx, fi.installDir),
+						fi.stem(), fi.builtFile, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs())
+				}
 			} else {
 				copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath)
+				if installSymbolFiles {
+					installedPath = ctx.InstallFile(pathWhenActivated.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
+				}
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
-		}
+			if installSymbolFiles {
+				implicitInputs = append(implicitInputs, installedPath)
+			}
 
-		// Create additional symlinks pointing the file inside the APEX (if any). Note that
-		// this is independent from the symlink optimization.
-		for _, symlinkPath := range fi.symlinkPaths() {
-			symlinkDest := imageDir.Join(ctx, symlinkPath).String()
-			copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
+			// Create additional symlinks pointing the file inside the APEX (if any). Note that
+			// this is independent from the symlink optimization.
+			for _, symlinkPath := range fi.symlinkPaths() {
+				symlinkDest := imageDir.Join(ctx, symlinkPath).String()
+				copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
+				if installSymbolFiles {
+					installedSymlink := ctx.InstallSymlink(pathWhenActivated.Join(ctx, filepath.Dir(symlinkPath)), filepath.Base(symlinkPath), installedPath)
+					implicitInputs = append(implicitInputs, installedSymlink)
+				}
+			}
+
+			installMapPath = installedPath
 		}
 
 		// Copy the test files (if any)
@@ -459,8 +476,21 @@
 			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
 			implicitInputs = append(implicitInputs, d.SrcPath)
 		}
+
+		installMapSet[installMapPath.String()+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
 	}
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
+	if installSymbolFiles {
+		installedManifest := ctx.InstallFile(pathWhenActivated, "apex_manifest.pb", a.manifestPbOut)
+		installedKey := ctx.InstallFile(pathWhenActivated, "apex_pubkey", a.publicKeyFile)
+		implicitInputs = append(implicitInputs, installedManifest, installedKey)
+	}
+
+	if len(installMapSet) > 0 {
+		var installs []string
+		installs = append(installs, android.SortedStringKeys(installMapSet)...)
+		a.SetLicenseInstallMap(installs)
+	}
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
@@ -514,61 +544,17 @@
 	}
 
 	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
-	outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
+	outHostBinDir := ctx.Config().HostToolPath(ctx, "").String()
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
-	// Figure out if need to compress apex.
-	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps()
+	// Figure out if we need to compress the apex.
+	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.overridableProperties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps()
 	if apexType == imageApex {
+
 		////////////////////////////////////////////////////////////////////////////////////
 		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
 		// in this APEX. The file will be used by apexer in later steps.
-		// TODO(jiyong): make this as a function
-		// TODO(jiyong): use the RuleBuilder
-		var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
-		var executablePaths []string // this also includes dirs
-		var extractedAppSetPaths android.Paths
-		var extractedAppSetDirs []string
-		for _, f := range a.filesInfo {
-			pathInApex := f.path()
-			if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
-				executablePaths = append(executablePaths, pathInApex)
-				for _, d := range f.dataPaths {
-					readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel()))
-				}
-				for _, s := range f.symlinks {
-					executablePaths = append(executablePaths, filepath.Join(f.installDir, s))
-				}
-			} else if f.class == appSet {
-				extractedAppSetPaths = append(extractedAppSetPaths, f.builtFile)
-				extractedAppSetDirs = append(extractedAppSetDirs, f.installDir)
-			} else {
-				readOnlyPaths = append(readOnlyPaths, pathInApex)
-			}
-			dir := f.installDir
-			for !android.InList(dir, executablePaths) && dir != "" {
-				executablePaths = append(executablePaths, dir)
-				dir, _ = filepath.Split(dir) // move up to the parent
-				if len(dir) > 0 {
-					// remove trailing slash
-					dir = dir[:len(dir)-1]
-				}
-			}
-		}
-		sort.Strings(readOnlyPaths)
-		sort.Strings(executablePaths)
-		cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        generateFsConfig,
-			Output:      cannedFsConfig,
-			Description: "generate fs config",
-			Inputs:      extractedAppSetPaths,
-			Args: map[string]string{
-				"ro_paths":   strings.Join(readOnlyPaths, " "),
-				"exec_paths": strings.Join(executablePaths, " "),
-				"apk_paths":  strings.Join(extractedAppSetDirs, " "),
-			},
-		})
+		cannedFsConfig := a.buildCannedFsConfig(ctx)
 		implicitInputs = append(implicitInputs, cannedFsConfig)
 
 		////////////////////////////////////////////////////////////////////////////////////
@@ -589,8 +575,15 @@
 
 		if a.properties.AndroidManifest != nil {
 			androidManifestFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.AndroidManifest))
+
+			if a.testApex {
+				androidManifestFile = markManifestTestOnly(ctx, androidManifestFile)
+			}
+
 			implicitInputs = append(implicitInputs, androidManifestFile)
 			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
+		} else if a.testApex {
+			optFlags = append(optFlags, "--test_only")
 		}
 
 		// Determine target/min sdk version from the context
@@ -602,6 +595,11 @@
 		// codename
 		if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
+
+			if java.UseApiFingerprint(ctx) {
+				minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
+				implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+			}
 		}
 		// apex module doesn't have a concept of target_sdk_version, hence for the time
 		// being targetSdkVersion == default targetSdkVersion of the branch.
@@ -611,10 +609,6 @@
 			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
 			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
 		}
-		if java.UseApiFingerprint(ctx) {
-			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
-			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
-		}
 		optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
 		optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
 
@@ -622,12 +616,22 @@
 			optFlags = append(optFlags, "--logging_parent ", a.overridableProperties.Logging_parent)
 		}
 
-		a.mergedNotices = a.buildNoticeFiles(ctx, a.Name()+suffix)
-		if a.mergedNotices.HtmlGzOutput.Valid() {
-			// If there's a NOTICE file, embed it as an asset file in the APEX.
-			implicitInputs = append(implicitInputs, a.mergedNotices.HtmlGzOutput.Path())
-			optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.mergedNotices.HtmlGzOutput.String()))
-		}
+		// Create a NOTICE file, and embed it as an asset file in the APEX.
+		a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE.html.gz")
+		android.BuildNoticeHtmlOutputFromLicenseMetadata(
+			ctx, a.htmlGzNotice, "", "",
+			[]string{
+				android.PathForModuleInstall(ctx).String() + "/",
+				android.PathForModuleInPartitionInstall(ctx, "apex").String() + "/",
+			})
+		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+		builder := android.NewRuleBuilder(pctx, ctx)
+		builder.Command().Text("cp").
+			Input(a.htmlGzNotice).
+			Output(noticeAssetPath)
+		builder.Build("notice_dir", "Building notice dir")
+		implicitInputs = append(implicitInputs, noticeAssetPath)
+		optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String()))
 
 		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
 			// Apexes which are supposed to be installed in builtin dirs(/system, etc)
@@ -696,24 +700,38 @@
 				"readelf":   "${config.ClangBin}/llvm-readelf",
 			},
 		})
-		a.apisUsedByModuleFile = apisUsedbyOutputFile
+		a.nativeApisUsedByModuleFile = apisUsedbyOutputFile
 
-		var libNames []string
+		var nativeLibNames []string
 		for _, f := range a.filesInfo {
 			if f.class == nativeSharedLib {
-				libNames = append(libNames, f.stem())
+				nativeLibNames = append(nativeLibNames, f.stem())
 			}
 		}
 		apisBackedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_backing.txt")
-		ndkLibraryList := android.PathForSource(ctx, "system/core/rootdir/etc/public.libraries.android.txt")
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")).
 			Output(apisBackedbyOutputFile).
-			Input(ndkLibraryList).
-			Flags(libNames)
+			Flags(nativeLibNames)
 		rule.Build("ndk_backedby_list", "Generate API libraries backed by Apex")
-		a.apisBackedByModuleFile = apisBackedbyOutputFile
+		a.nativeApisBackedByModuleFile = apisBackedbyOutputFile
+
+		var javaLibOrApkPath []android.Path
+		for _, f := range a.filesInfo {
+			if f.class == javaSharedLib || f.class == app {
+				javaLibOrApkPath = append(javaLibOrApkPath, f.builtFile)
+			}
+		}
+		javaApiUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.xml")
+		javaUsedByRule := android.NewRuleBuilder(pctx, ctx)
+		javaUsedByRule.Command().
+			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_java_usedby_apex.sh")).
+			BuiltTool("dexdeps").
+			Output(javaApiUsedbyOutputFile).
+			Inputs(javaLibOrApkPath)
+		javaUsedByRule.Build("java_usedby_list", "Generate Java APIs used by Apex")
+		a.javaApisUsedByModuleFile = javaApiUsedbyOutputFile
 
 		bundleConfig := a.buildBundleConfig(ctx)
 
@@ -760,7 +778,7 @@
 	rule := java.Signapk
 	args := map[string]string{
 		"certificates": pem.String() + " " + key.String(),
-		"flags":        "-a 4096", //alignment
+		"flags":        "-a 4096 --align-file-size", //alignment
 	}
 	implicits := android.Paths{pem, key}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
@@ -776,6 +794,9 @@
 		Implicits:   implicits,
 		Args:        args,
 	})
+	if suffix == imageApexSuffix {
+		a.outputApexFile = signedOutputFile
+	}
 	a.outputFile = signedOutputFile
 
 	if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() {
@@ -785,7 +806,7 @@
 
 	if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) {
 		a.isCompressed = true
-		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned")
+		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix+".unsigned")
 
 		compressRule := android.NewRuleBuilder(pctx, ctx)
 		compressRule.Command().
@@ -799,7 +820,7 @@
 			FlagWithOutput("--output ", unsignedCompressedOutputFile)
 		compressRule.Build("compressRule", "Generate unsigned compressed APEX file")
 
-		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex")
+		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix)
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
 			args["outCommaList"] = signedCompressedOutputFile.String()
 		}
@@ -814,49 +835,59 @@
 		a.outputFile = signedCompressedOutputFile
 	}
 
-	// Install to $OUT/soong/{target,host}/.../apex
-	if a.installable() {
-		ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
+	installSuffix := suffix
+	if a.isCompressed {
+		installSuffix = imageCapexSuffix
 	}
 
+	if !a.installable() {
+		a.SkipInstall()
+	}
+
+	// Install to $OUT/soong/{target,host}/.../apex.
+	a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile,
+		a.compatSymlinks.Paths()...)
+
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
 }
 
-// Context "decorator", overriding the InstallBypassMake method to always reply `true`.
-type flattenedApexContext struct {
-	android.ModuleContext
-}
-
-func (c *flattenedApexContext) InstallBypassMake() bool {
-	return true
-}
-
 // buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
 // single output file. It is a phony target for all the files under /system/apex/<name> directory.
 // This function creates the installation rules for the files.
 func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
 	bundleName := a.Name()
+	installedSymlinks := append(android.InstallPaths(nil), a.compatSymlinks...)
 	if a.installable() {
 		for _, fi := range a.filesInfo {
 			dir := filepath.Join("apex", bundleName, fi.installDir)
-			target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
-			for _, sym := range fi.symlinks {
-				ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
+			installDir := android.PathForModuleInstall(ctx, dir)
+			if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
+				// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
+				pathOnDevice := filepath.Join("/system", fi.path())
+				installedSymlinks = append(installedSymlinks,
+					ctx.InstallAbsoluteSymlink(installDir, fi.stem(), pathOnDevice))
+			} else {
+				target := ctx.InstallFile(installDir, fi.stem(), fi.builtFile)
+				for _, sym := range fi.symlinks {
+					installedSymlinks = append(installedSymlinks,
+						ctx.InstallSymlink(installDir, sym, target))
+				}
 			}
 		}
+
+		// Create install rules for the files added in GenerateAndroidBuildActions after
+		// buildFlattenedApex is called.  Add the links to system libs (if any) as dependencies
+		// of the apex_manifest.pb file since it is always present.
+		dir := filepath.Join("apex", bundleName)
+		installDir := android.PathForModuleInstall(ctx, dir)
+		ctx.InstallFile(installDir, "apex_manifest.pb", a.manifestPbOut, installedSymlinks.Paths()...)
+		ctx.InstallFile(installDir, "apex_pubkey", a.publicKeyFile)
 	}
 
 	a.fileContexts = a.buildFileContexts(ctx)
 
-	// Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
-	// to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
-	// `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
-	// correct path to the flattened APEX (as its contents is installed by Make, not Soong).
-	// TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
-	// it and it actually points to a path that can never be built. Remove this.
-	factx := flattenedApexContext{ctx}
-	a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+	a.outputFile = android.PathForModuleInstall(ctx, "apex", bundleName)
 }
 
 // getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
@@ -968,7 +999,7 @@
 		return !externalDep
 	})
 
-	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, proptools.String(a.properties.Min_sdk_version), depInfos)
+	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(ctx).Raw, depInfos)
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Phony,
@@ -988,3 +1019,65 @@
 
 	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
 }
+
+func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
+	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
+	var executablePaths []string // this also includes dirs
+	var appSetDirs []string
+	appSetFiles := make(map[string]android.Path)
+	for _, f := range a.filesInfo {
+		pathInApex := f.path()
+		if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
+			executablePaths = append(executablePaths, pathInApex)
+			for _, d := range f.dataPaths {
+				readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel()))
+			}
+			for _, s := range f.symlinks {
+				executablePaths = append(executablePaths, filepath.Join(f.installDir, s))
+			}
+		} else if f.class == appSet {
+			appSetDirs = append(appSetDirs, f.installDir)
+			appSetFiles[f.installDir] = f.builtFile
+		} else {
+			readOnlyPaths = append(readOnlyPaths, pathInApex)
+		}
+		dir := f.installDir
+		for !android.InList(dir, executablePaths) && dir != "" {
+			executablePaths = append(executablePaths, dir)
+			dir, _ = filepath.Split(dir) // move up to the parent
+			if len(dir) > 0 {
+				// remove trailing slash
+				dir = dir[:len(dir)-1]
+			}
+		}
+	}
+	sort.Strings(readOnlyPaths)
+	sort.Strings(executablePaths)
+	sort.Strings(appSetDirs)
+
+	cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
+	builder := android.NewRuleBuilder(pctx, ctx)
+	cmd := builder.Command()
+	cmd.Text("(")
+	cmd.Text("echo '/ 1000 1000 0755';")
+	for _, p := range readOnlyPaths {
+		cmd.Textf("echo '/%s 1000 1000 0644';", p)
+	}
+	for _, p := range executablePaths {
+		cmd.Textf("echo '/%s 0 2000 0755';", p)
+	}
+	for _, dir := range appSetDirs {
+		cmd.Textf("echo '/%s 0 2000 0755';", dir)
+		file := appSetFiles[dir]
+		cmd.Text("zipinfo -1").Input(file).Textf(`| sed "s:\(.*\):/%s/\1 1000 1000 0644:";`, dir)
+	}
+	// Custom fs_config is "appended" to the last so that entries from the file are preferred
+	// over default ones set above.
+	if a.properties.Canned_fs_config != nil {
+		cmd.Text("cat").Input(android.PathForModuleSrc(ctx, *a.properties.Canned_fs_config))
+	}
+	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
+	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
+
+	return cannedFsConfig.OutputPath
+}
diff --git a/apex/classpath_element_test.go b/apex/classpath_element_test.go
index 0193127..60f18bd 100644
--- a/apex/classpath_element_test.go
+++ b/apex/classpath_element_test.go
@@ -58,6 +58,7 @@
 		}),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
+		java.FixtureConfigureApexBootJars("myapex:bar"),
 		android.FixtureWithRootAndroidBp(`
 		apex {
 			name: "com.android.art",
@@ -79,6 +80,7 @@
 
 		bootclasspath_fragment {
 			name: "art-bootclasspath-fragment",
+			image_name: "art",
 			apex_available: [
 				"com.android.art",
 			],
@@ -157,11 +159,6 @@
 			],
 		}
 
-		bootclasspath_fragment {
-			name: "non-apex-fragment",
-			contents: ["othersdklibrary"],
-		}
-
 		apex {
 			name: "otherapex",
 			key: "otherapex.key",
@@ -193,6 +190,10 @@
 					apex: "com.android.art",
 					module: "art-bootclasspath-fragment",
 				},
+				{
+					apex: "myapex",
+					module: "mybootclasspath-fragment",
+				},
 			],
 		}
 	`),
@@ -207,7 +208,6 @@
 	myFragment := result.Module("mybootclasspath-fragment", "android_common_apex10000")
 	myBar := result.Module("bar", "android_common_apex10000")
 
-	nonApexFragment := result.Module("non-apex-fragment", "android_common")
 	other := result.Module("othersdklibrary", "android_common_apex10000")
 
 	otherApexLibrary := result.Module("otherapexlibrary", "android_common_apex10000")
@@ -247,15 +247,6 @@
 		assertElementsEquals(t, "elements", expectedElements, elements)
 	})
 
-	// Verify that CreateClasspathElements detects when a fragment does not have an associated apex.
-	t.Run("non apex fragment", func(t *testing.T) {
-		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{nonApexFragment})
-		android.FailIfNoMatchingErrors(t, "fragment non-apex-fragment{.*} is not part of an apex", ctx.errs)
-		expectedElements := java.ClasspathElements{}
-		assertElementsEquals(t, "elements", expectedElements, elements)
-	})
-
 	// Verify that CreateClasspathElements detects when an apex has multiple fragments.
 	t.Run("multiple fragments for same apex", func(t *testing.T) {
 		ctx := newCtx()
diff --git a/apex/deapexer.go b/apex/deapexer.go
index c70da15..8c9030a 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -15,6 +15,8 @@
 package apex
 
 import (
+	"strings"
+
 	"android/soong/android"
 )
 
@@ -75,6 +77,17 @@
 	inputApex android.Path
 }
 
+// Returns the name of the deapexer module corresponding to an APEX module with the given name.
+func deapexerModuleName(apexModuleName string) string {
+	return apexModuleName + ".deapexer"
+}
+
+// Returns the name of the APEX module corresponding to an deapexer module with
+// the given name. This reverses deapexerModuleName.
+func apexModuleName(deapexerModuleName string) string {
+	return strings.TrimSuffix(deapexerModuleName, ".deapexer")
+}
+
 func privateDeapexerFactory() android.Module {
 	module := &Deapexer{}
 	module.AddProperties(&module.properties, &module.selectedApexProperties)
@@ -97,7 +110,7 @@
 	// Create and remember the directory into which the .apex file's contents will be unpacked.
 	deapexerOutput := android.PathForModuleOut(ctx, "deapexer")
 
-	exports := make(map[string]android.Path)
+	exports := make(map[string]android.WritablePath)
 
 	// Create mappings from apex relative path to the extracted file's path.
 	exportedPaths := make(android.Paths, 0, len(exports))
@@ -113,7 +126,8 @@
 	// apex relative path to extracted file path available for other modules.
 	if len(exports) > 0 {
 		// Make the information available for other modules.
-		ctx.SetProvider(android.DeapexerProvider, android.NewDeapexerInfo(exports))
+		di := android.NewDeapexerInfo(apexModuleName(ctx.ModuleName()), exports)
+		ctx.SetProvider(android.DeapexerProvider, di)
 
 		// Create a sorted list of the files that this exports.
 		exportedPaths = android.SortedUniquePaths(exportedPaths)
@@ -131,6 +145,6 @@
 		for _, p := range exportedPaths {
 			command.Output(p.(android.WritablePath))
 		}
-		builder.Build("deapexer", "deapex "+ctx.ModuleName())
+		builder.Build("deapexer", "deapex "+apexModuleName(ctx.ModuleName()))
 	}
 }
diff --git a/apex/key.go b/apex/key.go
index 8b33b59..829410e 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -20,6 +20,7 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/bazel"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -37,6 +38,7 @@
 
 type apexKey struct {
 	android.ModuleBase
+	android.BazelModuleBase
 
 	properties apexKeyProperties
 
@@ -61,6 +63,7 @@
 	module := &apexKey{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidArchModule(module, android.HostAndDeviceDefault, android.MultilibCommon)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -118,13 +121,18 @@
 		containerCertificate string
 		containerPrivateKey  string
 		partition            string
+		signTool             string
 	}
 	toString := func(e apexKeyEntry) string {
-		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q\n"
+		signTool := ""
+		if e.signTool != "" {
+			signTool = fmt.Sprintf(" sign_tool=%q", e.signTool)
+		}
+		format := "name=%q public_key=%q private_key=%q container_certificate=%q container_private_key=%q partition=%q%s\n"
 		if e.presigned {
-			return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition)
+			return fmt.Sprintf(format, e.name, "PRESIGNED", "PRESIGNED", "PRESIGNED", "PRESIGNED", e.partition, signTool)
 		} else {
-			return fmt.Sprintf(format, e.name, e.publicKey, e.privateKey, e.containerCertificate, e.containerPrivateKey, e.partition)
+			return fmt.Sprintf(format, e.name, e.publicKey, e.privateKey, e.containerCertificate, e.containerPrivateKey, e.partition, signTool)
 		}
 	}
 
@@ -140,6 +148,7 @@
 				containerCertificate: pem.String(),
 				containerPrivateKey:  key.String(),
 				partition:            m.PartitionTag(ctx.DeviceConfig()),
+				signTool:             proptools.String(m.properties.Custom_sign_tool),
 			}
 		}
 	})
@@ -190,3 +199,39 @@
 func (s *apexKeysText) MakeVars(ctx android.MakeVarsContext) {
 	ctx.Strict("SOONG_APEX_KEYS_FILE", s.output.String())
 }
+
+// For Bazel / bp2build
+
+type bazelApexKeyAttributes struct {
+	Public_key  bazel.LabelAttribute
+	Private_key bazel.LabelAttribute
+}
+
+// ConvertWithBp2build performs conversion apexKey for bp2build
+func (m *apexKey) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	apexKeyBp2BuildInternal(ctx, m)
+}
+
+func apexKeyBp2BuildInternal(ctx android.TopDownMutatorContext, module *apexKey) {
+	var privateKeyLabelAttribute bazel.LabelAttribute
+	if module.properties.Private_key != nil {
+		privateKeyLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.Private_key))
+	}
+
+	var publicKeyLabelAttribute bazel.LabelAttribute
+	if module.properties.Public_key != nil {
+		publicKeyLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.Public_key))
+	}
+
+	attrs := &bazelApexKeyAttributes{
+		Private_key: privateKeyLabelAttribute,
+		Public_key:  publicKeyLabelAttribute,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "apex_key",
+		Bzl_load_location: "//build/bazel/rules:apex_key.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
+}
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index bc35479..06c39ee 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/java"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -39,7 +40,7 @@
 		prepareForTestWithMyapex,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo"),
-		java.FixtureConfigureBootJars("myapex:bar"),
+		java.FixtureConfigureApexBootJars("myapex:bar"),
 		android.FixtureWithRootAndroidBp(`
 			platform_bootclasspath {
 				name: "platform-bootclasspath",
@@ -159,11 +160,82 @@
 		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
 	}
 
-	android.AssertPathsRelativeToTopEquals(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/stub-flags.csv"}, info.StubFlagsPaths)
 	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
 	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
 	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/index.csv"}, info.IndexPaths)
-	android.AssertPathsRelativeToTopEquals(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/all-flags.csv"}, info.AllFlagsPaths)
+
+	android.AssertArrayString(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-stub-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+}
+
+// TestPlatformBootclasspath_LegacyPrebuiltFragment verifies that the
+// prebuilt_bootclasspath_fragment falls back to using the complete stub-flags/all-flags if the
+// filtered files are not provided.
+//
+// TODO: Remove once all prebuilts use the filtered_... properties.
+func TestPlatformBootclasspath_LegacyPrebuiltFragment(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
+		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
+		// because AlwaysUsePrebuiltSdks() is true.
+		java_sdk_library_import {
+			name: "foo",
+			prefer: false,
+			shared_library: false,
+			permitted_packages: ["foo"],
+			public: {
+				jars: ["sdk_library/public/foo-stubs.jar"],
+				stub_srcs: ["sdk_library/public/foo_stub_sources"],
+				current_api: "sdk_library/public/foo.txt",
+				removed_api: "sdk_library/public/foo-removed.txt",
+				sdk_version: "current",
+			},
+			apex_available: ["myapex"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "prebuilt-stub-flags.csv",
+				annotation_flags: "prebuilt-annotation-flags.csv",
+				metadata: "prebuilt-metadata.csv",
+				index: "prebuilt-index.csv",
+				all_flags: "prebuilt-all-flags.csv",
+			},
+		}
+
+		platform_bootclasspath {
+			name: "myplatform-bootclasspath",
+			fragments: [
+				{
+					apex: "myapex",
+					module:"mybootclasspath-fragment",
+				},
+			],
+		}
+`,
+	)
+
+	pbcp := result.Module("myplatform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, java.MonolithicHiddenAPIInfoProvider).(java.MonolithicHiddenAPIInfo)
+
+	android.AssertArrayString(t, "stub flags", []string{"prebuilt-stub-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"prebuilt-all-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
 func TestPlatformBootclasspathDependencies(t *testing.T) {
@@ -173,7 +245,7 @@
 		prepareForTestWithMyapex,
 		// Configure some libraries in the art and framework boot images.
 		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "platform:foo"),
-		java.FixtureConfigureUpdatableBootJars("myapex:bar"),
+		java.FixtureConfigureApexBootJars("myapex:bar"),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
@@ -194,6 +266,7 @@
 
 		bootclasspath_fragment {
 			name: "art-bootclasspath-fragment",
+			image_name: "art",
 			apex_available: [
 				"com.android.art",
 			],
@@ -288,7 +361,7 @@
 		"com.android.art:quuz",
 		"platform:foo",
 
-		// The configured contents of UpdatableBootJars.
+		// The configured contents of ApexBootJars.
 		"myapex:bar",
 	})
 
@@ -313,7 +386,7 @@
 		`com.android.art:quuz`,
 		`platform:foo`,
 
-		// The configured contents of UpdatableBootJars.
+		// The configured contents of ApexBootJars.
 		`myapex:bar`,
 
 		// The fragments.
@@ -323,32 +396,16 @@
 }
 
 // TestPlatformBootclasspath_AlwaysUsePrebuiltSdks verifies that the build does not fail when
-// AlwaysUsePrebuiltSdk() returns true. The structure of the modules in this test matches what
-// currently exists in some places in the Android build but it is not the intended structure. It is
-// in fact an invalid structure that should cause build failures. However, fixing that structure
-// will take too long so in the meantime this tests the workarounds to avoid build breakages.
-//
-// The main issues with this structure are:
-// 1. There is no prebuilt_bootclasspath_fragment referencing the "foo" java_sdk_library_import.
-// 2. There is no prebuilt_apex/apex_set which makes the dex implementation jar available to the
-//    prebuilt_bootclasspath_fragment and the "foo" java_sdk_library_import.
-//
-// Together these cause the following symptoms:
-// 1. The "foo" java_sdk_library_import does not have a dex implementation jar.
-// 2. The "foo" java_sdk_library_import does not have a myapex variant.
-//
-// TODO(b/179354495): Fix the structure in this test once the main Android build has been fixed.
+// AlwaysUsePrebuiltSdk() returns true.
 func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
 		// Configure two libraries, the first is a java_sdk_library whose prebuilt will be used because
-		// of AlwaysUsePrebuiltsSdk() but does not have an appropriate apex variant and does not provide
-		// a boot dex jar. The second is a normal library that is unaffected. The order matters because
-		// if the dependency on myapex:foo is filtered out because of either of those conditions then
-		// the dependencies resolved by the platform_bootclasspath will not match the configured list
-		// and so will fail the test.
-		java.FixtureConfigureUpdatableBootJars("myapex:foo", "myapex:bar"),
+		// of AlwaysUsePrebuiltsSdk(). The second is a normal library that is unaffected. The order
+		// matters, so that the dependencies resolved by the platform_bootclasspath matches the
+		// configured list.
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
@@ -392,6 +449,12 @@
 			permitted_packages: ["foo"],
 		}
 
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
 		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
 		// because AlwaysUsePrebuiltSdks() is true.
 		java_sdk_library_import {
@@ -421,6 +484,23 @@
 			],
 		}
 
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "",
+				annotation_flags: "",
+				metadata: "",
+				index: "",
+				all_flags: "",
+			},
+		}
+
 		platform_bootclasspath {
 			name: "myplatform-bootclasspath",
 			fragments: [
@@ -435,7 +515,7 @@
 
 	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
 		// The configured contents of BootJars.
-		"platform:prebuilt_foo", // Note: This is the platform not myapex variant.
+		"myapex:prebuilt_foo",
 		"myapex:bar",
 	})
 
@@ -454,16 +534,15 @@
 
 		// The platform_bootclasspath intentionally adds dependencies on both source and prebuilt
 		// modules when available as it does not know which one will be preferred.
-		//
-		// The source module has an APEX variant but the prebuilt does not.
 		"myapex:foo",
-		"platform:prebuilt_foo",
+		"myapex:prebuilt_foo",
 
 		// Only a source module exists.
 		"myapex:bar",
 
 		// The fragments.
 		"myapex:mybootclasspath-fragment",
+		"myapex:prebuilt_mybootclasspath-fragment",
 	})
 }
 
@@ -482,3 +561,199 @@
 	pairs := java.ApexNamePairsFromModules(ctx, modules)
 	android.AssertDeepEquals(t, "module dependencies", expected, pairs)
 }
+
+// TestPlatformBootclasspath_IncludesRemainingApexJars verifies that any apex boot jar is present in
+// platform_bootclasspath's classpaths.proto config, if the apex does not generate its own config
+// by setting generate_classpaths_proto property to false.
+func TestPlatformBootclasspath_IncludesRemainingApexJars(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithMyapex,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "myapex",
+						module:"foo-fragment",
+					},
+				],
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: ["foo-fragment"],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "foo-fragment",
+				generate_classpaths_proto: false,
+				contents: ["foo"],
+				apex_available: ["myapex"],
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+				apex_available: ["myapex"],
+				permitted_packages: ["foo"],
+			}
+		`),
+	).RunTest(t)
+
+	java.CheckClasspathFragmentProtoContentInfoProvider(t, result,
+		true,         // proto should be generated
+		"myapex:foo", // apex doesn't generate its own config, so must be in platform_bootclasspath
+		"bootclasspath.pb",
+		"out/soong/target/product/test_device/system/etc/classpaths",
+	)
+}
+
+func TestBootJarNotInApex(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`dependency "foo" of "myplatform-bootclasspath" missing variant`)).
+		RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: [
+					"myapex",
+				],
+			}
+
+			bootclasspath_fragment {
+				name: "not-in-apex-fragment",
+				contents: [
+					"foo",
+				],
+			}
+
+			platform_bootclasspath {
+				name: "myplatform-bootclasspath",
+			}
+		`)
+}
+
+func TestBootFragmentNotInApex(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`library foo.*have no corresponding fragment.*`)).RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				java_libs: ["foo"],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: ["myapex"],
+				permitted_packages: ["foo"],
+			}
+
+			bootclasspath_fragment {
+				name: "not-in-apex-fragment",
+				contents: ["foo"],
+			}
+
+			platform_bootclasspath {
+				name: "myplatform-bootclasspath",
+			}
+		`)
+}
+
+func TestNonBootJarInFragment(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`in contents must also be declared in PRODUCT_APEX_BOOT_JARS`)).
+		RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: ["apex-fragment"],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: ["myapex"],
+				permitted_packages: ["foo"],
+			}
+
+			java_library {
+				name: "bar",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: ["myapex"],
+				permitted_packages: ["bar"],
+			}
+
+			bootclasspath_fragment {
+				name: "apex-fragment",
+				contents: ["foo", "bar"],
+				apex_available:[ "myapex" ],
+			}
+
+			platform_bootclasspath {
+				name: "myplatform-bootclasspath",
+				fragments: [{
+						apex: "myapex",
+						module:"apex-fragment",
+				}],
+			}
+		`)
+}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index c567fe0..187e0df 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -17,11 +17,13 @@
 import (
 	"fmt"
 	"io"
+	"path/filepath"
 	"strconv"
 	"strings"
 
 	"android/soong/android"
 	"android/soong/java"
+	"android/soong/provenance"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -53,18 +55,18 @@
 
 	installDir      android.InstallPath
 	installFilename string
+	installedFile   android.InstallPath
 	outputApex      android.WritablePath
 
 	// A list of apexFile objects created in prebuiltCommon.initApexFilesForAndroidMk which are used
 	// to create make modules in prebuiltCommon.AndroidMkEntries.
 	apexFilesForAndroidMk []apexFile
 
-	// list of commands to create symlinks for backward compatibility.
-	// these commands will be attached as LOCAL_POST_INSTALL_CMD
-	compatSymlinks []string
+	// Installed locations of symlinks for backward compatibility.
+	compatSymlinks android.InstallPaths
 
 	hostRequired        []string
-	postInstallCommands []string
+	requiredModuleNames []string
 }
 
 type sanitizedPrebuilt interface {
@@ -102,6 +104,10 @@
 	// List of bootclasspath fragments inside this prebuilt APEX bundle and for which this APEX
 	// bundle will create an APEX variant.
 	Exported_bootclasspath_fragments []string
+
+	// List of systemserverclasspath fragments inside this prebuilt APEX bundle and for which this
+	// APEX bundle will create an APEX variant.
+	Exported_systemserverclasspath_fragments []string
 }
 
 // initPrebuiltCommon initializes the prebuiltCommon structure and performs initialization of the
@@ -169,27 +175,56 @@
 		tag := ctx.OtherModuleDependencyTag(child)
 
 		name := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(child))
-		if java.IsBootclasspathFragmentContentDepTag(tag) || tag == exportedJavaLibTag {
+		if java.IsBootclasspathFragmentContentDepTag(tag) ||
+			java.IsSystemServerClasspathFragmentContentDepTag(tag) || tag == exportedJavaLibTag {
 			// If the exported java module provides a dex jar path then add it to the list of apexFiles.
-			path := child.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-			if path != nil {
-				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, apexFile{
+			path := child.(interface {
+				DexJarBuildPath() java.OptionalDexJarPath
+			}).DexJarBuildPath()
+			if path.IsSet() {
+				af := apexFile{
 					module:              child,
 					moduleDir:           ctx.OtherModuleDir(child),
 					androidMkModuleName: name,
-					builtFile:           path,
+					builtFile:           path.Path(),
 					class:               javaSharedLib,
-				})
+				}
+				if module, ok := child.(java.DexpreopterInterface); ok {
+					for _, install := range module.DexpreoptBuiltInstalledForApex() {
+						af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+					}
+				}
+				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, af)
 			}
 		} else if tag == exportedBootclasspathFragmentTag {
+			bcpfModule, ok := child.(*java.PrebuiltBootclasspathFragmentModule)
+			if !ok {
+				ctx.PropertyErrorf("exported_bootclasspath_fragments", "%q is not a prebuilt_bootclasspath_fragment module", name)
+				return false
+			}
+			for _, makeModuleName := range bcpfModule.BootImageDeviceInstallMakeModules() {
+				p.requiredModuleNames = append(p.requiredModuleNames, makeModuleName)
+			}
 			// Visit the children of the bootclasspath_fragment.
 			return true
+		} else if tag == exportedSystemserverclasspathFragmentTag {
+			// Visit the children of the systemserver_fragment.
+			return true
 		}
 
 		return false
 	})
 }
 
+func (p *prebuiltCommon) addRequiredModules(entries *android.AndroidMkEntries) {
+	for _, fi := range p.apexFilesForAndroidMk {
+		entries.AddStrings("LOCAL_REQUIRED_MODULES", fi.requiredModuleNames...)
+		entries.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", fi.targetRequiredModuleNames...)
+		entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", fi.hostRequiredModuleNames...)
+	}
+	entries.AddStrings("LOCAL_REQUIRED_MODULES", p.requiredModuleNames...)
+}
+
 func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := []android.AndroidMkEntries{
 		{
@@ -199,15 +234,13 @@
 			Host_required: p.hostRequired,
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+					entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
 					entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
+					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", p.installedFile)
+					entries.SetString("LOCAL_SOONG_INSTALL_PAIRS", p.outputApex.String()+":"+p.installedFile.String())
 					entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
 					entries.AddStrings("LOCAL_OVERRIDES_MODULES", p.prebuiltCommonProperties.Overrides...)
-					postInstallCommands := append([]string{}, p.postInstallCommands...)
-					postInstallCommands = append(postInstallCommands, p.compatSymlinks...)
-					if len(postInstallCommands) > 0 {
-						entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(postInstallCommands, " && "))
-					}
+					p.addRequiredModules(entries)
 				},
 			},
 		},
@@ -235,23 +268,15 @@
 		Include:      "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
+				entries.SetString("LOCAL_SOONG_INSTALLED_MODULE", filepath.Join(p.installDir.String(), fi.stem()))
+				entries.SetString("LOCAL_SOONG_INSTALL_PAIRS",
+					fi.builtFile.String()+":"+filepath.Join(p.installDir.String(), fi.stem()))
 
 				// soong_java_prebuilt.mk sets LOCAL_MODULE_SUFFIX := .jar  Therefore
 				// we need to remove the suffix from LOCAL_MODULE_STEM, otherwise
 				// we will have foo.jar.jar
 				entries.SetString("LOCAL_MODULE_STEM", strings.TrimSuffix(fi.stem(), ".jar"))
-				var classesJar android.Path
-				var headerJar android.Path
-				if javaModule, ok := fi.module.(java.ApexDependency); ok {
-					classesJar = javaModule.ImplementationAndResourcesJars()[0]
-					headerJar = javaModule.HeaderJars()[0]
-				} else {
-					classesJar = fi.builtFile
-					headerJar = fi.builtFile
-				}
-				entries.SetString("LOCAL_SOONG_CLASSES_JAR", classesJar.String())
-				entries.SetString("LOCAL_SOONG_HEADER_JAR", headerJar.String())
 				entries.SetString("LOCAL_SOONG_DEX_JAR", fi.builtFile.String())
 				entries.SetString("LOCAL_DEX_PREOPT", "false")
 			},
@@ -289,21 +314,31 @@
 	}
 }
 
+func (p *prebuiltCommon) getExportedDependencies() map[string]exportedDependencyTag {
+	dependencies := make(map[string]exportedDependencyTag)
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_java_libs {
+		dependencies[dep] = exportedJavaLibTag
+	}
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_bootclasspath_fragments {
+		dependencies[dep] = exportedBootclasspathFragmentTag
+	}
+
+	for _, dep := range p.prebuiltCommonProperties.Exported_systemserverclasspath_fragments {
+		dependencies[dep] = exportedSystemserverclasspathFragmentTag
+	}
+
+	return dependencies
+}
+
 // prebuiltApexContentsDeps adds dependencies onto the prebuilt apex module's contents.
 func (p *prebuiltCommon) prebuiltApexContentsDeps(ctx android.BottomUpMutatorContext) {
 	module := ctx.Module()
-	// Add dependencies onto the java modules that represent the java libraries that are provided by
-	// and exported from this prebuilt apex.
-	for _, exported := range p.prebuiltCommonProperties.Exported_java_libs {
-		dep := android.PrebuiltNameFromSource(exported)
-		ctx.AddDependency(module, exportedJavaLibTag, dep)
-	}
 
-	// Add dependencies onto the bootclasspath fragment modules that are exported from this prebuilt
-	// apex.
-	for _, exported := range p.prebuiltCommonProperties.Exported_bootclasspath_fragments {
-		dep := android.PrebuiltNameFromSource(exported)
-		ctx.AddDependency(module, exportedBootclasspathFragmentTag, dep)
+	for dep, tag := range p.getExportedDependencies() {
+		prebuiltDep := android.PrebuiltNameFromSource(dep)
+		ctx.AddDependency(module, tag, prebuiltDep)
 	}
 }
 
@@ -447,6 +482,8 @@
 	properties PrebuiltProperties
 
 	inputApex android.Path
+
+	provenanceMetaDataFile android.OutputPath
 }
 
 type ApexFileProperties struct {
@@ -554,9 +591,9 @@
 // A deapexer module is only needed when the prebuilt apex specifies one or more modules in either
 // the `exported_java_libs` or `exported_bootclasspath_fragments` properties as that indicates that
 // the listed modules need access to files from within the prebuilt .apex file.
-func createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string, properties *PrebuiltCommonProperties) {
+func (p *prebuiltCommon) createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string) {
 	// Only create the deapexer module if it is needed.
-	if len(properties.Exported_java_libs)+len(properties.Exported_bootclasspath_fragments) == 0 {
+	if len(p.getExportedDependencies()) == 0 {
 		return
 	}
 
@@ -609,10 +646,6 @@
 	)
 }
 
-func deapexerModuleName(baseModuleName string) string {
-	return baseModuleName + ".deapexer"
-}
-
 func apexSelectorModuleName(baseModuleName string) string {
 	return baseModuleName + ".apex.selector"
 }
@@ -656,8 +689,9 @@
 var _ android.RequiresFilesFromPrebuiltApexTag = exportedDependencyTag{}
 
 var (
-	exportedJavaLibTag               = exportedDependencyTag{name: "exported_java_libs"}
-	exportedBootclasspathFragmentTag = exportedDependencyTag{name: "exported_bootclasspath_fragments"}
+	exportedJavaLibTag                       = exportedDependencyTag{name: "exported_java_libs"}
+	exportedBootclasspathFragmentTag         = exportedDependencyTag{name: "exported_bootclasspath_fragments"}
+	exportedSystemserverclasspathFragmentTag = exportedDependencyTag{name: "exported_systemserverclasspath_fragments"}
 )
 
 var _ prebuiltApexModuleCreator = (*Prebuilt)(nil)
@@ -698,7 +732,7 @@
 	createApexSelectorModule(ctx, apexSelectorModuleName, &p.properties.ApexFileProperties)
 
 	apexFileSource := ":" + apexSelectorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, p.prebuiltCommonProperties)
+	p.createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource)
 
 	// Add a source reference to retrieve the selected apex from the selector module.
 	p.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -737,16 +771,21 @@
 	// Save the files that need to be made available to Make.
 	p.initApexFilesForAndroidMk(ctx)
 
-	if p.installable() {
-		ctx.InstallFile(p.installDir, p.installFilename, p.inputApex)
-	}
-
 	// in case that prebuilt_apex replaces source apex (using prefer: prop)
-	p.compatSymlinks = makeCompatSymlinks(p.BaseModuleName(), ctx)
+	p.compatSymlinks = makeCompatSymlinks(p.BaseModuleName(), ctx, true)
 	// or that prebuilt_apex overrides other apexes (using overrides: prop)
 	for _, overridden := range p.prebuiltCommonProperties.Overrides {
-		p.compatSymlinks = append(p.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
+		p.compatSymlinks = append(p.compatSymlinks, makeCompatSymlinks(overridden, ctx, true)...)
 	}
+
+	if p.installable() {
+		p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, p.compatSymlinks.Paths()...)
+		p.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, p.inputApex, p.installedFile)
+	}
+}
+
+func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath {
+	return p.provenanceMetaDataFile
 }
 
 // prebuiltApexExtractorModule is a private module type that is only created by the prebuilt_apex
@@ -901,7 +940,7 @@
 	createApexExtractorModule(ctx, apexExtractorModuleName, &a.properties.ApexExtractorProperties)
 
 	apexFileSource := ":" + apexExtractorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, a.prebuiltCommonProperties)
+	a.createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource)
 
 	// After passing the arch specific src properties to the creating the apex selector module
 	a.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -919,8 +958,8 @@
 
 func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.installFilename = a.InstallFilename()
-	if !strings.HasSuffix(a.installFilename, imageApexSuffix) {
-		ctx.ModuleErrorf("filename should end in %s for apex_set", imageApexSuffix)
+	if !strings.HasSuffix(a.installFilename, imageApexSuffix) && !strings.HasSuffix(a.installFilename, imageCapexSuffix) {
+		ctx.ModuleErrorf("filename should end in %s or %s for apex_set", imageApexSuffix, imageCapexSuffix)
 	}
 
 	inputApex := android.OptionalPathForModuleSrc(ctx, a.prebuiltCommonProperties.Selected_apex).Path()
@@ -941,14 +980,14 @@
 
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	if a.installable() {
-		ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
+		a.installedFile = ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
 	}
 
 	// in case that apex_set replaces source apex (using prefer: prop)
-	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
+	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, true)
 	// or that apex_set overrides other apexes (using overrides: prop)
 	for _, overridden := range a.prebuiltCommonProperties.Overrides {
-		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
+		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx, true)...)
 	}
 }
 
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index 95b6e23..d037664 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -15,6 +15,7 @@
 package apex
 
 import (
+	"android/soong/dexpreopt"
 	"testing"
 
 	"android/soong/android"
@@ -30,6 +31,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -76,3 +78,248 @@
 		`mysystemserverclasspathfragment`,
 	})
 }
+
+func TestSystemserverclasspathFragmentNoGeneratedProto(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			generate_classpaths_proto: false,
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		"javalib/foo.jar",
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		`myapex.key`,
+		`mysystemserverclasspathfragment`,
+	})
+}
+
+func TestSystemServerClasspathFragmentWithContentNotInMake(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS`)).
+		RunTestWithBp(t, `
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				systemserverclasspath_fragments: [
+					"mysystemserverclasspathfragment",
+				],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			java_library {
+				name: "foo",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: ["myapex"],
+			}
+
+			java_library {
+				name: "bar",
+				srcs: ["b.java"],
+				installable: true,
+				apex_available: ["myapex"],
+			}
+
+			systemserverclasspath_fragment {
+				name: "mysystemserverclasspathfragment",
+				contents: [
+					"foo",
+					"bar",
+				],
+				apex_available: [
+					"myapex",
+				],
+			}
+		`)
+}
+
+func TestPrebuiltSystemserverclasspathFragmentContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		prebuilt_systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			prefer: true,
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex", []string{
+		`myapex.apex.selector`,
+		`prebuilt_mysystemserverclasspathfragment`,
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`myapex.deapexer`,
+		`prebuilt_foo`,
+	})
+}
+
+func TestSystemserverclasspathFragmentStandaloneContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			standalone_contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		"etc/classpaths/systemserverclasspath.pb",
+		"javalib/foo.jar",
+	})
+}
+
+func TestPrebuiltStandaloneSystemserverclasspathFragmentContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			arch: {
+				arm64: {
+					src: "myapex-arm64.apex",
+				},
+				arm: {
+					src: "myapex-arm.apex",
+				},
+			},
+			exported_systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		prebuilt_systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			prefer: true,
+			standalone_contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`myapex.deapexer`,
+		`prebuilt_foo`,
+	})
+}
diff --git a/apex/vndk.go b/apex/vndk.go
index 75c0fb0..ef3e5e1 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -15,7 +15,6 @@
 package apex
 
 import (
-	"path/filepath"
 	"strings"
 
 	"android/soong/android"
@@ -96,11 +95,14 @@
 }
 
 // name is module.BaseModuleName() which is used as LOCAL_MODULE_NAME and also LOCAL_OVERRIDES_*
-func makeCompatSymlinks(name string, ctx android.ModuleContext) (symlinks []string) {
+func makeCompatSymlinks(name string, ctx android.ModuleContext, primaryApex bool) (symlinks android.InstallPaths) {
 	// small helper to add symlink commands
-	addSymlink := func(target, dir, linkName string) {
-		link := filepath.Join(dir, linkName)
-		symlinks = append(symlinks, "mkdir -p "+dir+" && rm -rf "+link+" && ln -sf "+target+" "+link)
+	addSymlink := func(target string, dir android.InstallPath, linkName string) {
+		if primaryApex {
+			symlinks = append(symlinks, ctx.InstallAbsoluteSymlink(dir, linkName, target))
+		} else {
+			symlinks = append(symlinks, dir.Join(ctx, linkName))
+		}
 	}
 
 	// TODO(b/142911355): [VNDK APEX] Fix hard-coded references to /system/lib/vndk
@@ -118,14 +120,15 @@
 		// the name of vndk apex is formatted "com.android.vndk.v" + version
 		apexName := vndkApexNamePrefix + vndkVersion
 		if ctx.Config().Android64() {
-			addSymlink("/apex/"+apexName+"/lib64", "$(TARGET_OUT)/lib64", "vndk-sp-"+vndkVersion)
-			addSymlink("/apex/"+apexName+"/lib64", "$(TARGET_OUT)/lib64", "vndk-"+vndkVersion)
+			dir := android.PathForModuleInPartitionInstall(ctx, "system", "lib64")
+			addSymlink("/apex/"+apexName+"/lib64", dir, "vndk-sp-"+vndkVersion)
+			addSymlink("/apex/"+apexName+"/lib64", dir, "vndk-"+vndkVersion)
 		}
 		if !ctx.Config().Android64() || ctx.DeviceConfig().DeviceSecondaryArch() != "" {
-			addSymlink("/apex/"+apexName+"/lib", "$(TARGET_OUT)/lib", "vndk-sp-"+vndkVersion)
-			addSymlink("/apex/"+apexName+"/lib", "$(TARGET_OUT)/lib", "vndk-"+vndkVersion)
+			dir := android.PathForModuleInPartitionInstall(ctx, "system", "lib")
+			addSymlink("/apex/"+apexName+"/lib", dir, "vndk-sp-"+vndkVersion)
+			addSymlink("/apex/"+apexName+"/lib", dir, "vndk-"+vndkVersion)
 		}
-		return
 	}
 
 	// http://b/121248172 - create a link from /system/usr/icu to
@@ -133,19 +136,9 @@
 	// A symlink can't overwrite a directory and the /system/usr/icu directory once
 	// existed so the required structure must be created whatever we find.
 	if name == "com.android.i18n" {
-		addSymlink("/apex/com.android.i18n/etc/icu", "$(TARGET_OUT)/usr", "icu")
-		return
+		dir := android.PathForModuleInPartitionInstall(ctx, "system", "usr")
+		addSymlink("/apex/com.android.i18n/etc/icu", dir, "icu")
 	}
 
-	// TODO(b/124106384): Clean up compat symlinks for ART binaries.
-	if name == "com.android.art" || strings.HasPrefix(name, "com.android.art.") {
-		addSymlink("/apex/com.android.art/bin/dalvikvm", "$(TARGET_OUT)/bin", "dalvikvm")
-		dex2oat := "dex2oat32"
-		if ctx.Config().Android64() {
-			dex2oat = "dex2oat64"
-		}
-		addSymlink("/apex/com.android.art/bin/"+dex2oat, "$(TARGET_OUT)/bin", "dex2oat")
-		return
-	}
-	return
+	return symlinks
 }
diff --git a/bazel/Android.bp b/bazel/Android.bp
index b7c185a..9e7edc7 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -7,8 +7,10 @@
     pkgPath: "android/soong/bazel",
     srcs: [
         "aquery.go",
+        "configurability.go",
         "constants.go",
         "properties.go",
+        "testing.go",
     ],
     testSrcs: [
         "aquery_test.go",
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 555f1dc..fd8cf67 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"fmt"
 	"path/filepath"
+	"regexp"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -59,6 +60,8 @@
 	InputDepSetIds       []int
 	Mnemonic             string
 	OutputIds            []int
+	TemplateContent      string
+	Substitutions        []KeyValuePair
 }
 
 // actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
@@ -73,31 +76,53 @@
 // BuildStatement contains information to register a build statement corresponding (one to one)
 // with a Bazel action from Bazel's action graph.
 type BuildStatement struct {
-	Command     string
-	Depfile     *string
-	OutputPaths []string
-	InputPaths  []string
-	Env         []KeyValuePair
-	Mnemonic    string
+	Command      string
+	Depfile      *string
+	OutputPaths  []string
+	InputPaths   []string
+	SymlinkPaths []string
+	Env          []KeyValuePair
+	Mnemonic     string
 }
 
-// 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) {
-	buildStatements := []BuildStatement{}
+// 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
+	// may be an expensive operation.
+	depsetIdToArtifactIdsCache map[int][]int
+	// Maps artifact Id to fully expanded path.
+	artifactIdToPath map[int]string
+}
 
-	var aqueryResult actionGraphContainer
-	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
+// The tokens should be substituted with the value specified here, instead of the
+// one returned in 'substitutions' of TemplateExpand action.
+var TemplateActionOverriddenTokens = map[string]string{
+	// Uses "python3" for %python_binary% instead of the value returned by aquery
+	// which is "py3wrapper.sh". See removePy3wrapperScript.
+	"%python_binary%": "python3",
+}
 
-	if err != nil {
-		return nil, err
-	}
+// This pattern matches the MANIFEST file created for a py_binary target.
+var manifestFilePattern = regexp.MustCompile(".*/.+\\.runfiles/MANIFEST$")
 
+// The file name of py3wrapper.sh, which is used by py_binary targets.
+var py3wrapperFileName = "/py3wrapper.sh"
+
+func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
 	pathFragments := map[int]pathFragment{}
 	for _, pathFragment := range aqueryResult.PathFragments {
 		pathFragments[pathFragment.Id] = pathFragment
 	}
+
 	artifactIdToPath := map[int]string{}
 	for _, artifact := range aqueryResult.Artifacts {
 		artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
@@ -112,22 +137,111 @@
 		depsetIdToDepset[depset.Id] = depset
 	}
 
-	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
-	// may be an expensive operation.
-	depsetIdToArtifactIdsCache := map[int][]int{}
-
 	// Do a pass through all actions to identify which artifacts are middleman artifacts.
-	// These will be omitted from the inputs of other actions.
-	// TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
-	// headers may cause build failures.
-	middlemanArtifactIds := map[int]bool{}
+	middlemanIdToDepsetIds := map[int][]int{}
 	for _, actionEntry := range aqueryResult.Actions {
 		if actionEntry.Mnemonic == "Middleman" {
 			for _, outputId := range actionEntry.OutputIds {
-				middlemanArtifactIds[outputId] = true
+				middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
 			}
 		}
 	}
+	return &aqueryArtifactHandler{
+		middlemanIdToDepsetIds:     middlemanIdToDepsetIds,
+		depsetIdToDepset:           depsetIdToDepset,
+		depsetIdToArtifactIdsCache: map[int][]int{},
+		artifactIdToPath:           artifactIdToPath,
+	}, nil
+}
+
+func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
+	inputPaths := []string{}
+
+	for _, inputDepSetId := range depsetIds {
+		inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
+		if err != nil {
+			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)
+			}
+		}
+	}
+
+	// TODO(b/197135294): Clean up this custom runfiles handling logic when
+	// SourceSymlinkManifest and SymlinkTree actions are supported.
+	filteredInputPaths := filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths)
+
+	return filteredInputPaths, nil
+}
+
+// See go/python-binary-host-mixed-build for more details.
+// 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
+// Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
+// 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+// but it doesn't contain sufficient information so no Ninja build statements are generated
+// for creating it.
+// So in mixed build mode, when these two are used as input of some Ninja build statement,
+// since there is no build statement to create them, they should be removed from input paths.
+func filterOutPy3wrapperAndManifestFileFromInputPaths(inputPaths []string) []string {
+	filteredInputPaths := []string{}
+	for _, path := range inputPaths {
+		if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
+			continue
+		}
+		filteredInputPaths = append(filteredInputPaths, path)
+	}
+	return filteredInputPaths
+}
+
+func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
+	if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
+		return result, nil
+	}
+	if depset, exists := a.depsetIdToDepset[depsetId]; exists {
+		result := depset.DirectArtifactIds
+		for _, childId := range depset.TransitiveDepSetIds {
+			childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
+			if err != nil {
+				return nil, err
+			}
+			result = append(result, childArtifactIds...)
+		}
+		a.depsetIdToArtifactIdsCache[depsetId] = result
+		return result, nil
+	} else {
+		return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+	}
+}
+
+// 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) {
+	buildStatements := []BuildStatement{}
+
+	var aqueryResult actionGraphContainer
+	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
+	if err != nil {
+		return nil, err
+	}
+	aqueryHandler, err := newAqueryHandler(aqueryResult)
+	if err != nil {
+		return nil, err
+	}
 
 	for _, actionEntry := range aqueryResult.Actions {
 		if shouldSkipAction(actionEntry) {
@@ -136,7 +250,7 @@
 		outputPaths := []string{}
 		var depfile *string
 		for _, outputId := range actionEntry.OutputIds {
-			outputPath, exists := artifactIdToPath[outputId]
+			outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
 			if !exists {
 				return nil, fmt.Errorf("undefined outputId %d", outputId)
 			}
@@ -151,35 +265,72 @@
 				outputPaths = append(outputPaths, outputPath)
 			}
 		}
-		inputPaths := []string{}
-		for _, inputDepSetId := range actionEntry.InputDepSetIds {
-			inputArtifacts, err :=
-				artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, inputDepSetId)
-			if err != nil {
-				return nil, err
-			}
-			for _, inputId := range inputArtifacts {
-				if _, isMiddlemanArtifact := middlemanArtifactIds[inputId]; isMiddlemanArtifact {
-					// Omit middleman artifacts.
-					continue
-				}
-				inputPath, exists := artifactIdToPath[inputId]
-				if !exists {
-					return nil, fmt.Errorf("undefined input artifactId %d", inputId)
-				}
-				inputPaths = append(inputPaths, inputPath)
-			}
+		inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+		if err != nil {
+			return nil, err
 		}
+
 		buildStatement := BuildStatement{
-			Command:     strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
+			Command:     strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " "),
 			Depfile:     depfile,
 			OutputPaths: outputPaths,
 			InputPaths:  inputPaths,
 			Env:         actionEntry.EnvironmentVariables,
-			Mnemonic:    actionEntry.Mnemonic}
-		if len(actionEntry.Arguments) < 1 {
+			Mnemonic:    actionEntry.Mnemonic,
+		}
+
+		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[:]
+		} else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
+			if len(outputPaths) != 1 {
+				return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
+			}
+			expandedTemplateContent := expandTemplateContent(actionEntry)
+			// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
+			// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
+			// change \n to space and mess up the format of Python programs.
+			// sed is used to convert \\n back to \n before saving to output file.
+			// See go/python-binary-host-mixed-build for more details.
+			command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
+				escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
+			buildStatement.Command = command
+		} else if isPythonZipperAction(actionEntry) {
+			if len(inputPaths) < 1 || len(outputPaths) != 1 {
+				return nil, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
+			}
+			buildStatement.InputPaths, buildStatement.Command = removePy3wrapperScript(buildStatement)
+			buildStatement.Command = addCommandForPyBinaryRunfilesDir(buildStatement, inputPaths[0], outputPaths[0])
+			// Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
+			// In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
+			// which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
+			//
+			// The following logic relies on that Bazel aquery output returns actions in the order that
+			// PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
+			// in that order, the following logic might not find the build statement generated for Python binary
+			// stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
+			// See go/python-binary-host-mixed-build for more details.
+			pythonZipFilePath := outputPaths[0]
+			pyBinaryFound := false
+			for i, _ := range buildStatements {
+				if len(buildStatements[i].OutputPaths) == 1 && buildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
+					buildStatements[i].InputPaths = append(buildStatements[i].InputPaths, pythonZipFilePath)
+					pyBinaryFound = true
+				}
+			}
+			if !pyBinaryFound {
+				return nil, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
+			}
+		} else if len(actionEntry.Arguments) < 1 {
 			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
-			continue
 		}
 		buildStatements = append(buildStatements, buildStatement)
 	}
@@ -187,13 +338,93 @@
 	return buildStatements, nil
 }
 
+// expandTemplateContent substitutes the tokens in a template.
+func expandTemplateContent(actionEntry action) string {
+	replacerString := []string{}
+	for _, pair := range actionEntry.Substitutions {
+		value := pair.Value
+		if val, ok := TemplateActionOverriddenTokens[pair.Key]; ok {
+			value = val
+		}
+		replacerString = append(replacerString, pair.Key, value)
+	}
+	replacer := strings.NewReplacer(replacerString...)
+	return replacer.Replace(actionEntry.TemplateContent)
+}
+
+func escapeCommandlineArgument(str string) string {
+	// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
+	replacer := strings.NewReplacer(
+		`\`, `\\`,
+		`$`, `\$`,
+		"`", "\\`",
+		`"`, `\"`,
+		"\n", "\\n",
+		`'`, `'"'"'`,
+	)
+	return replacer.Replace(str)
+}
+
+// removePy3wrapperScript removes py3wrapper.sh from the input paths and command of the action of
+// creating python zip file in mixed build mode. py3wrapper.sh is returned as input by aquery but
+// there is no action returned by aquery for creating it. So in mixed build "python3" is used
+// as the PYTHON_BINARY in python binary stub script, and py3wrapper.sh is not needed and should be
+// removed from input paths and command of creating python zip file.
+// See go/python-binary-host-mixed-build for more details.
+// TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
+func removePy3wrapperScript(bs BuildStatement) (newInputPaths []string, newCommand string) {
+	// Remove from inputs
+	filteredInputPaths := []string{}
+	for _, path := range bs.InputPaths {
+		if !strings.HasSuffix(path, py3wrapperFileName) {
+			filteredInputPaths = append(filteredInputPaths, path)
+		}
+	}
+	newInputPaths = filteredInputPaths
+
+	// Remove from command line
+	var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
+	newCommand = re.ReplaceAllString(bs.Command, "")
+	return
+}
+
+// addCommandForPyBinaryRunfilesDir adds commands creating python binary runfiles directory.
+// runfiles directory is created by using MANIFEST file and MANIFEST file is the output of
+// SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
+// but since SourceSymlinkManifest doesn't contain sufficient information
+// so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
+// See go/python-binary-host-mixed-build for more details.
+// TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
+func addCommandForPyBinaryRunfilesDir(bs BuildStatement, zipperCommandPath, zipFilePath string) string {
+	// Unzip the zip file, zipFilePath looks like <python_binary>.zip
+	runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
+	command := fmt.Sprintf("%s x %s -d %s", zipperCommandPath, zipFilePath, runfilesDirName)
+	// Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
+	// when running the python binary stub script.
+	command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
+	return bs.Command + " && " + command
+}
+
+func isSymlinkAction(a action) bool {
+	return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
+}
+
+func isTemplateExpandAction(a action) bool {
+	return a.Mnemonic == "TemplateExpand"
+}
+
+func isPythonZipperAction(a action) bool {
+	return a.Mnemonic == "PythonZipper"
+}
+
 func shouldSkipAction(a action) bool {
-	// TODO(b/180945121): Handle symlink actions.
-	if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" {
+	// TODO(b/180945121): Handle complex symlink actions.
+	if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
 		return true
 	}
-	// TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
-	// headers may cause build failures.
+	// Middleman actions are not handled like other actions; they are handled separately as a
+	// preparatory step so that their inputs may be relayed to actions depending on middleman
+	// artifacts.
 	if a.Mnemonic == "Middleman" {
 		return true
 	}
@@ -209,28 +440,6 @@
 	return false
 }
 
-func artifactIdsFromDepsetId(depsetIdToDepset map[int]depSetOfFiles,
-	depsetIdToArtifactIdsCache map[int][]int, depsetId int) ([]int, error) {
-	if result, exists := depsetIdToArtifactIdsCache[depsetId]; exists {
-		return result, nil
-	}
-	if depset, exists := depsetIdToDepset[depsetId]; exists {
-		result := depset.DirectArtifactIds
-		for _, childId := range depset.TransitiveDepSetIds {
-			childArtifactIds, err :=
-				artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, childId)
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, childArtifactIds...)
-		}
-		depsetIdToArtifactIdsCache[depsetId] = result
-		return result, nil
-	} else {
-		return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
-	}
-}
-
 func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
 	labels := []string{}
 	currId := id
@@ -241,6 +450,9 @@
 			return "", fmt.Errorf("undefined path fragment id %d", currId)
 		}
 		labels = append([]string{currFragment.Label}, labels...)
+		if currId == currFragment.ParentId {
+			return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
+		}
 		currId = currFragment.ParentId
 	}
 	return filepath.Join(labels...), nil
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index fa8810f..68e50c2 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -709,7 +709,7 @@
 	}
 	expectedBuildStatements := []BuildStatement{
 		BuildStatement{
-			Command:     "/bin/bash -c touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out",
+			Command:     "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
 			OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
 			InputPaths:  inputPaths,
 			Mnemonic:    "Action",
@@ -718,19 +718,667 @@
 	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
 }
 
+func TestMiddlemenAction(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }, {
+    "id": 4,
+    "pathFragmentId": 4
+  }, {
+    "id": 5,
+    "pathFragmentId": 5
+  }, {
+    "id": 6,
+    "pathFragmentId": 6
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "middleinput_one"
+  }, {
+    "id": 2,
+    "label": "middleinput_two"
+  }, {
+    "id": 3,
+    "label": "middleman_artifact"
+  }, {
+    "id": 4,
+    "label": "maininput_one"
+  }, {
+    "id": 5,
+    "label": "maininput_two"
+  }, {
+    "id": 6,
+    "label": "output"
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1, 2]
+  }, {
+    "id": 2,
+    "directArtifactIds": [3, 4, 5]
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Middleman",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [1],
+    "outputIds": [3],
+    "primaryOutputId": 3
+  }, {
+    "targetId": 2,
+    "actionKey": "y",
+    "mnemonic": "Main action",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [2],
+    "outputIds": [6],
+    "primaryOutputId": 6
+  }]
+}`
+
+	actual, 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))
+	}
+
+	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)
+	}
+
+	expectedOutputs := []string{"output"}
+	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
+		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
+	}
+}
+
+func TestSimpleSymlink(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 3
+  }, {
+    "id": 2,
+    "pathFragmentId": 5
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "file_subdir",
+    "parentId": 1
+  }, {
+    "id": 3,
+    "label": "file",
+    "parentId": 2
+  }, {
+    "id": 4,
+    "label": "symlink_subdir",
+    "parentId": 1
+  }, {
+    "id": 5,
+    "label": "symlink",
+    "parentId": 4
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "mkdir -p one/symlink_subdir && " +
+				"rm -f one/symlink_subdir/symlink && " +
+				"ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink",
+			InputPaths:   []string{"one/file_subdir/file"},
+			OutputPaths:  []string{"one/symlink_subdir/symlink"},
+			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
+			Mnemonic:     "Symlink",
+		},
+	}
+	assertBuildStatements(t, actual, expectedBuildStatements)
+}
+
+func TestSymlinkQuotesPaths(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 3
+  }, {
+    "id": 2,
+    "pathFragmentId": 5
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "SolibSymlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "file subdir",
+    "parentId": 1
+  }, {
+    "id": 3,
+    "label": "file",
+    "parentId": 2
+  }, {
+    "id": 4,
+    "label": "symlink subdir",
+    "parentId": 1
+  }, {
+    "id": 5,
+    "label": "symlink",
+    "parentId": 4
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "mkdir -p 'one/symlink subdir' && " +
+				"rm -f 'one/symlink subdir/symlink' && " +
+				"ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'",
+			InputPaths:   []string{"one/file subdir/file"},
+			OutputPaths:  []string{"one/symlink subdir/symlink"},
+			SymlinkPaths: []string{"one/symlink subdir/symlink"},
+			Mnemonic:     "SolibSymlink",
+		},
+	}
+	assertBuildStatements(t, expectedBuildStatements, actual)
+}
+
+func TestSymlinkMultipleInputs(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [3],
+    "primaryOutputId": 3
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1,2]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "file"
+  }, {
+    "id": 2,
+    "label": "other_file"
+  }, {
+    "id": 3,
+    "label": "symlink"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`)
+}
+
+func TestSymlinkMultipleOutputs(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2,3],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "file"
+  }, {
+    "id": 2,
+    "label": "symlink"
+  }, {
+    "id": 3,
+    "label": "other_symlink"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
+}
+
+func TestTemplateExpandActionSubstitutions(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "outputIds": [1],
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "template_file"
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " +
+				"chmod a+x template_file'",
+			OutputPaths: []string{"template_file"},
+			Mnemonic:    "TemplateExpand",
+		},
+	}
+	assertBuildStatements(t, expectedBuildStatements, actual)
+}
+
+func TestTemplateExpandActionNoOutput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "template_file"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 output to template expand action, got: output []`)
+}
+
+func TestPythonZipperActionSuccess(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  },{
+    "id": 3,
+    "pathFragmentId": 3
+  },{
+    "id": 4,
+    "pathFragmentId": 4
+  },{
+    "id": 5,
+    "pathFragmentId": 10
+  },{
+    "id": 10,
+    "pathFragmentId": 20
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "TemplateExpand",
+    "configurationId": 1,
+    "outputIds": [1],
+    "primaryOutputId": 1,
+    "executionPlatform": "//build/bazel/platforms:linux_x86_64",
+    "templateContent": "Test template substitutions: %token1%, %python_binary%",
+    "substitutions": [{
+      "key": "%token1%",
+      "value": "abcd"
+    },{
+      "key": "%python_binary%",
+      "value": "python3"
+    }]
+  },{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "outputIds": [2],
+    "inputDepSetIds": [1],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [4, 3, 5]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  },{
+    "id": 3,
+    "label": "python_binary.py"
+  },{
+    "id": 9,
+    "label": ".."
+  }, {
+    "id": 8,
+    "label": "bazel_tools",
+    "parentId": 9
+  }, {
+    "id": 7,
+    "label": "tools",
+    "parentId": 8
+  }, {
+    "id": 6,
+    "label": "zip",
+    "parentId": 7
+  }, {
+    "id": 5,
+    "label": "zipper",
+    "parentId": 6
+  }, {
+    "id": 4,
+    "label": "zipper",
+    "parentId": 5
+  },{
+    "id": 16,
+    "label": "bazel-out"
+  },{
+    "id": 15,
+    "label": "bazel_tools",
+    "parentId": 16
+  }, {
+    "id": 14,
+    "label": "k8-fastbuild",
+    "parentId": 15
+  }, {
+    "id": 13,
+    "label": "bin",
+    "parentId": 14
+  }, {
+    "id": 12,
+    "label": "tools",
+    "parentId": 13
+  }, {
+    "id": 11,
+    "label": "python",
+    "parentId": 12
+  }, {
+    "id": 10,
+    "label": "py3wrapper.sh",
+    "parentId": 11
+  },{
+    "id": 20,
+    "label": "python_binary"
+  }]
+}`
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > python_binary && " +
+				"chmod a+x python_binary'",
+			InputPaths:  []string{"python_binary.zip"},
+			OutputPaths: []string{"python_binary"},
+			Mnemonic:    "TemplateExpand",
+		},
+		BuildStatement{
+			Command: "../bazel_tools/tools/zip/zipper/zipper cC python_binary.zip __main__.py=bazel-out/k8-fastbuild/bin/python_binary.temp " +
+				"__init__.py= runfiles/__main__/__init__.py= runfiles/__main__/python_binary.py=python_binary.py  && " +
+				"../bazel_tools/tools/zip/zipper/zipper x python_binary.zip -d python_binary.runfiles && ln -sf runfiles/__main__ python_binary.runfiles",
+			InputPaths:  []string{"../bazel_tools/tools/zip/zipper/zipper", "python_binary.py"},
+			OutputPaths: []string{"python_binary.zip"},
+			Mnemonic:    "PythonZipper",
+		},
+	}
+	assertBuildStatements(t, expectedBuildStatements, actual)
+}
+
+func TestPythonZipperActionNoInput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  }]
+}`
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input [], output ["python_binary.zip"]`)
+}
+
+func TestPythonZipperActionNoOutput(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  },{
+    "id": 2,
+    "pathFragmentId": 2
+  },{
+    "id": 3,
+    "pathFragmentId": 3
+  },{
+    "id": 4,
+    "pathFragmentId": 4
+  },{
+    "id": 5,
+    "pathFragmentId": 10
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "PythonZipper",
+    "configurationId": 1,
+    "arguments": ["../bazel_tools/tools/zip/zipper/zipper", "cC", "python_binary.zip", "__main__.py\u003dbazel-out/k8-fastbuild/bin/python_binary.temp", "__init__.py\u003d", "runfiles/__main__/__init__.py\u003d", "runfiles/__main__/python_binary.py\u003dpython_binary.py", "runfiles/bazel_tools/tools/python/py3wrapper.sh\u003dbazel-out/bazel_tools/k8-fastbuild/bin/tools/python/py3wrapper.sh"],
+    "inputDepSetIds": [1]
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [4, 3, 5]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "python_binary"
+  },{
+    "id": 2,
+    "label": "python_binary.zip"
+  },{
+    "id": 3,
+    "label": "python_binary.py"
+  },{
+    "id": 9,
+    "label": ".."
+  }, {
+    "id": 8,
+    "label": "bazel_tools",
+    "parentId": 9
+  }, {
+    "id": 7,
+    "label": "tools",
+    "parentId": 8
+  }, {
+    "id": 6,
+    "label": "zip",
+    "parentId": 7
+  }, {
+    "id": 5,
+    "label": "zipper",
+    "parentId": 6
+  }, {
+    "id": 4,
+    "label": "zipper",
+    "parentId": 5
+  },{
+    "id": 16,
+    "label": "bazel-out"
+  },{
+    "id": 15,
+    "label": "bazel_tools",
+    "parentId": 16
+  }, {
+    "id": 14,
+    "label": "k8-fastbuild",
+    "parentId": 15
+  }, {
+    "id": 13,
+    "label": "bin",
+    "parentId": 14
+  }, {
+    "id": 12,
+    "label": "tools",
+    "parentId": 13
+  }, {
+    "id": 11,
+    "label": "python",
+    "parentId": 12
+  }, {
+    "id": 10,
+    "label": "py3wrapper.sh",
+    "parentId": 11
+  }]
+}`
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1+ input and 1 output to python zipper action, got: input ["../bazel_tools/tools/zip/zipper/zipper" "python_binary.py"], output []`)
+}
+
 func assertError(t *testing.T, err error, expected string) {
+	t.Helper()
 	if err == nil {
 		t.Errorf("expected error '%s', but got no error", expected)
 	} else if err.Error() != expected {
-		t.Errorf("expected error '%s', but got: %s", expected, err.Error())
+		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
 	}
 }
 
 // Asserts that the given actual build statements match the given expected build statements.
 // Build statement equivalence is determined using buildStatementEquals.
 func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
+	t.Helper()
 	if len(expected) != len(actual) {
-		t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
+		t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v",
 			len(expected), len(actual), expected, actual)
 		return
 	}
@@ -741,7 +1389,7 @@
 				continue ACTUAL_LOOP
 			}
 		}
-		t.Errorf("unexpected build statement %v.\n expected: %v",
+		t.Errorf("unexpected build statement %#v.\n expected: %#v",
 			actualStatement, expected)
 		return
 	}
@@ -765,6 +1413,12 @@
 	if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) {
 		return false
 	}
+	if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) {
+		return false
+	}
+	if first.Depfile != second.Depfile {
+		return false
+	}
 	return true
 }
 
diff --git a/bazel/configurability.go b/bazel/configurability.go
new file mode 100644
index 0000000..7355ac7
--- /dev/null
+++ b/bazel/configurability.go
@@ -0,0 +1,235 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bazel
+
+import (
+	"fmt"
+	"strings"
+)
+
+const (
+	// ArchType names in arch.go
+	archArm    = "arm"
+	archArm64  = "arm64"
+	archX86    = "x86"
+	archX86_64 = "x86_64"
+
+	// OsType names in arch.go
+	osAndroid     = "android"
+	osDarwin      = "darwin"
+	osLinux       = "linux_glibc"
+	osLinuxMusl   = "linux_musl"
+	osLinuxBionic = "linux_bionic"
+	osWindows     = "windows"
+
+	// Targets in arch.go
+	osArchAndroidArm        = "android_arm"
+	osArchAndroidArm64      = "android_arm64"
+	osArchAndroidX86        = "android_x86"
+	osArchAndroidX86_64     = "android_x86_64"
+	osArchDarwinArm64       = "darwin_arm64"
+	osArchDarwinX86_64      = "darwin_x86_64"
+	osArchLinuxX86          = "linux_glibc_x86"
+	osArchLinuxX86_64       = "linux_glibc_x86_64"
+	osArchLinuxMuslX86      = "linux_musl_x86"
+	osArchLinuxMuslX86_64   = "linux_musl_x86_64"
+	osArchLinuxBionicArm64  = "linux_bionic_arm64"
+	osArchLinuxBionicX86_64 = "linux_bionic_x86_64"
+	osArchWindowsX86        = "windows_x86"
+	osArchWindowsX86_64     = "windows_x86_64"
+
+	// This is the string representation of the default condition wherever a
+	// configurable attribute is used in a select statement, i.e.
+	// //conditions:default for Bazel.
+	//
+	// This is consistently named "conditions_default" to mirror the Soong
+	// config variable default key in an Android.bp file, although there's no
+	// integration with Soong config variables (yet).
+	ConditionsDefaultConfigKey = "conditions_default"
+
+	ConditionsDefaultSelectKey = "//conditions:default"
+
+	productVariableBazelPackage = "//build/bazel/product_variables"
+)
+
+var (
+	// These are the list of OSes and architectures with a Bazel config_setting
+	// and constraint value equivalent. These exist in arch.go, but the android
+	// package depends on the bazel package, so a cyclic dependency prevents
+	// using those variables here.
+
+	// A map of architectures to the Bazel label of the constraint_value
+	// for the @platforms//cpu:cpu constraint_setting
+	platformArchMap = map[string]string{
+		archArm:                    "//build/bazel/platforms/arch:arm",
+		archArm64:                  "//build/bazel/platforms/arch:arm64",
+		archX86:                    "//build/bazel/platforms/arch:x86",
+		archX86_64:                 "//build/bazel/platforms/arch:x86_64",
+		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of as arch select map.
+	}
+
+	// A map of target operating systems to the Bazel label of the
+	// constraint_value for the @platforms//os:os constraint_setting
+	platformOsMap = map[string]string{
+		osAndroid:                  "//build/bazel/platforms/os:android",
+		osDarwin:                   "//build/bazel/platforms/os:darwin",
+		osLinux:                    "//build/bazel/platforms/os:linux",
+		osLinuxMusl:                "//build/bazel/platforms/os:linux_musl",
+		osLinuxBionic:              "//build/bazel/platforms/os:linux_bionic",
+		osWindows:                  "//build/bazel/platforms/os:windows",
+		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
+	}
+
+	platformOsArchMap = map[string]string{
+		osArchAndroidArm:           "//build/bazel/platforms/os_arch:android_arm",
+		osArchAndroidArm64:         "//build/bazel/platforms/os_arch:android_arm64",
+		osArchAndroidX86:           "//build/bazel/platforms/os_arch:android_x86",
+		osArchAndroidX86_64:        "//build/bazel/platforms/os_arch:android_x86_64",
+		osArchDarwinArm64:          "//build/bazel/platforms/os_arch:darwin_arm64",
+		osArchDarwinX86_64:         "//build/bazel/platforms/os_arch:darwin_x86_64",
+		osArchLinuxX86:             "//build/bazel/platforms/os_arch:linux_glibc_x86",
+		osArchLinuxX86_64:          "//build/bazel/platforms/os_arch:linux_glibc_x86_64",
+		osArchLinuxMuslX86:         "//build/bazel/platforms/os_arch:linux_musl_x86",
+		osArchLinuxMuslX86_64:      "//build/bazel/platforms/os_arch:linux_musl_x86_64",
+		osArchLinuxBionicArm64:     "//build/bazel/platforms/os_arch:linux_bionic_arm64",
+		osArchLinuxBionicX86_64:    "//build/bazel/platforms/os_arch:linux_bionic_x86_64",
+		osArchWindowsX86:           "//build/bazel/platforms/os_arch:windows_x86",
+		osArchWindowsX86_64:        "//build/bazel/platforms/os_arch:windows_x86_64",
+		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
+	}
+
+	// Map where keys are OsType names, and values are slices containing the archs
+	// that that OS supports.
+	// These definitions copied from arch.go.
+	// TODO(cparsons): Source from arch.go; this task is nontrivial, as it currently results
+	// in a cyclic dependency.
+	osToArchMap = map[string][]string{
+		osAndroid:     {archArm, archArm64, archX86, archX86_64},
+		osLinux:       {archX86, archX86_64},
+		osLinuxMusl:   {archX86, archX86_64},
+		osDarwin:      {archArm64, archX86_64},
+		osLinuxBionic: {archArm64, archX86_64},
+		// TODO(cparsons): According to arch.go, this should contain archArm, archArm64, as well.
+		osWindows: {archX86, archX86_64},
+	}
+)
+
+// basic configuration types
+type configurationType int
+
+const (
+	noConfig configurationType = iota
+	arch
+	os
+	osArch
+	productVariables
+)
+
+func osArchString(os string, arch string) string {
+	return fmt.Sprintf("%s_%s", os, arch)
+}
+
+func (ct configurationType) String() string {
+	return map[configurationType]string{
+		noConfig:         "no_config",
+		arch:             "arch",
+		os:               "os",
+		osArch:           "arch_os",
+		productVariables: "product_variables",
+	}[ct]
+}
+
+func (ct configurationType) validateConfig(config string) {
+	switch ct {
+	case noConfig:
+		if config != "" {
+			panic(fmt.Errorf("Cannot specify config with %s, but got %s", ct, config))
+		}
+	case arch:
+		if _, ok := platformArchMap[config]; !ok {
+			panic(fmt.Errorf("Unknown arch: %s", config))
+		}
+	case os:
+		if _, ok := platformOsMap[config]; !ok {
+			panic(fmt.Errorf("Unknown os: %s", config))
+		}
+	case osArch:
+		if _, ok := platformOsArchMap[config]; !ok {
+			panic(fmt.Errorf("Unknown os+arch: %s", config))
+		}
+	case productVariables:
+		// do nothing
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
+	}
+}
+
+// SelectKey returns the Bazel select key for a given configurationType and config string.
+func (ca ConfigurationAxis) SelectKey(config string) string {
+	ca.validateConfig(config)
+	switch ca.configurationType {
+	case noConfig:
+		panic(fmt.Errorf("SelectKey is unnecessary for noConfig ConfigurationType "))
+	case arch:
+		return platformArchMap[config]
+	case os:
+		return platformOsMap[config]
+	case osArch:
+		return platformOsArchMap[config]
+	case productVariables:
+		if strings.HasSuffix(config, ConditionsDefaultConfigKey) {
+			// e.g. "acme__feature1__conditions_default" or "android__board__conditions_default"
+			return ConditionsDefaultSelectKey
+		}
+		return fmt.Sprintf("%s:%s", productVariableBazelPackage, config)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ca.configurationType))
+	}
+}
+
+var (
+	// Indicating there is no configuration axis
+	NoConfigAxis = ConfigurationAxis{configurationType: noConfig}
+	// An axis for architecture-specific configurations
+	ArchConfigurationAxis = ConfigurationAxis{configurationType: arch}
+	// An axis for os-specific configurations
+	OsConfigurationAxis = ConfigurationAxis{configurationType: os}
+	// An axis for arch+os-specific configurations
+	OsArchConfigurationAxis = ConfigurationAxis{configurationType: osArch}
+)
+
+// ProductVariableConfigurationAxis returns an axis for the given product variable
+func ProductVariableConfigurationAxis(variable string) ConfigurationAxis {
+	return ConfigurationAxis{
+		configurationType: productVariables,
+		subType:           variable,
+	}
+}
+
+// ConfigurationAxis is an independent axis for configuration, there should be no overlap between
+// elements within an axis.
+type ConfigurationAxis struct {
+	configurationType
+	// some configuration types (e.g. productVariables) have multiple independent axes, subType helps
+	// distinguish between them without needing to list all 17 product variables.
+	subType string
+}
+
+func (ca *ConfigurationAxis) less(other ConfigurationAxis) bool {
+	if ca.configurationType < other.configurationType {
+		return true
+	}
+	return ca.subType < other.subType
+}
diff --git a/bazel/constants.go b/bazel/constants.go
index 15c75cf..b10f256 100644
--- a/bazel/constants.go
+++ b/bazel/constants.go
@@ -18,6 +18,10 @@
 
 	// Run bazel as a ninja executer
 	BazelNinjaExecRunName = RunName("bazel-ninja-exec")
+
+	SoongInjectionDirName = "soong_injection"
+
+	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT."
 )
 
 // String returns the name of the run.
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index c30abeb..5d00b0b 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -6,20 +6,34 @@
 )
 
 var (
-	GetOutputFiles = &getOutputFilesRequestType{}
-	GetCcInfo      = &getCcInfoType{}
+	GetOutputFiles  = &getOutputFilesRequestType{}
+	GetPythonBinary = &getPythonBinaryRequestType{}
+	GetCcInfo       = &getCcInfoType{}
 )
 
 type CcInfo struct {
 	OutputFiles          []string
 	CcObjectFiles        []string
+	CcSharedLibraryFiles []string
 	CcStaticLibraryFiles []string
 	Includes             []string
 	SystemIncludes       []string
+	Headers              []string
+	// Archives owned by the current target (not by its dependencies). These will
+	// be a subset of OutputFiles. (or static libraries, this will be equal to OutputFiles,
+	// but general cc_library will also have dynamic libraries in output files).
+	RootStaticArchives []string
+	// Dynamic libraries (.so files) created by the current target. These will
+	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
+	// but general cc_library will also have dynamic libraries in output files).
+	RootDynamicLibraries []string
+	TocFile              string
 }
 
 type getOutputFilesRequestType struct{}
 
+type getPythonBinaryRequestType struct{}
+
 // Name returns a string name for this request type. Such request type names must be unique,
 // and must only consist of alphanumeric characters.
 func (g getOutputFilesRequestType) Name() string {
@@ -45,6 +59,31 @@
 	return splitOrEmpty(rawString, ", ")
 }
 
+// Name returns a string name for this request type. Such request type names must be unique,
+// and must only consist of alphanumeric characters.
+func (g getPythonBinaryRequestType) Name() string {
+	return "getPythonBinary"
+}
+
+// StarlarkFunctionBody returns a starlark function body to process this request type.
+// The returned string is the body of a Starlark function which obtains
+// all request-relevant information about a target and returns a string containing
+// this information.
+// The function should have the following properties:
+//   - `target` is the only parameter to this function (a configured target).
+//   - The return value must be a string.
+//   - The function body should not be indented outside of its own scope.
+func (g getPythonBinaryRequestType) StarlarkFunctionBody() string {
+	return "return providers(target)['FilesToRunProvider'].executable.path"
+}
+
+// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
+// The given rawString must correspond to the string output which was created by evaluating the
+// Starlark given in StarlarkFunctionBody.
+func (g getPythonBinaryRequestType) ParseResult(rawString string) string {
+	return rawString
+}
+
 type getCcInfoType struct{}
 
 // Name returns a string name for this request type. Such request type names must be unique,
@@ -64,27 +103,70 @@
 func (g getCcInfoType) StarlarkFunctionBody() string {
 	return `
 outputFiles = [f.path for f in target.files.to_list()]
+cc_info = providers(target)["CcInfo"]
 
-includes = providers(target)["CcInfo"].compilation_context.includes.to_list()
-system_includes = providers(target)["CcInfo"].compilation_context.system_includes.to_list()
+includes = cc_info.compilation_context.includes.to_list()
+system_includes = cc_info.compilation_context.system_includes.to_list()
+headers = [f.path for f in cc_info.compilation_context.headers.to_list()]
 
 ccObjectFiles = []
 staticLibraries = []
-linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+rootStaticArchives = []
+linker_inputs = cc_info.linking_context.linker_inputs.to_list()
 
-for linker_input in linker_inputs:
-  for library in linker_input.libraries:
-    for object in library.objects:
-      ccObjectFiles += [object.path]
-    if library.static_library:
-      staticLibraries.append(library.static_library.path)
+static_info_tag = "//build/bazel/rules/cc:cc_library_static.bzl%CcStaticLibraryInfo"
+if static_info_tag in providers(target):
+  static_info = providers(target)[static_info_tag]
+  ccObjectFiles = [f.path for f in static_info.objects]
+  rootStaticArchives = [static_info.root_static_archive.path]
+else:
+  for linker_input in linker_inputs:
+    for library in linker_input.libraries:
+      for object in library.objects:
+        ccObjectFiles += [object.path]
+      if library.static_library:
+        staticLibraries.append(library.static_library.path)
+        if linker_input.owner == target.label:
+          rootStaticArchives.append(library.static_library.path)
+
+sharedLibraries = []
+rootSharedLibraries = []
+
+shared_info_tag = "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"
+if shared_info_tag in providers(target):
+  shared_info = providers(target)[shared_info_tag]
+  for lib in shared_info.linker_input.libraries:
+    path = lib.dynamic_library.path
+    rootSharedLibraries += [path]
+    sharedLibraries.append(path)
+else:
+  for linker_input in linker_inputs:
+    for library in linker_input.libraries:
+      if library.dynamic_library:
+        path = library.dynamic_library.path
+        sharedLibraries.append(path)
+        if linker_input.owner == target.label:
+          rootSharedLibraries.append(path)
+
+toc_file = ""
+toc_file_tag = "//build/bazel/rules/cc:generate_toc.bzl%CcTocInfo"
+if toc_file_tag in providers(target):
+  toc_file = providers(target)[toc_file_tag].toc.path
+else:
+  # NOTE: It's OK if there's no ToC, as Soong just uses it for optimization
+  pass
 
 returns = [
   outputFiles,
-  staticLibraries,
   ccObjectFiles,
+  sharedLibraries,
+  staticLibraries,
   includes,
   system_includes,
+  headers,
+  rootStaticArchives,
+  rootSharedLibraries,
+  [toc_file]
 ]
 
 return "|".join([", ".join(r) for r in returns])`
@@ -94,27 +176,42 @@
 // The given rawString must correspond to the string output which was created by evaluating the
 // Starlark given in StarlarkFunctionBody.
 func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) {
-	var outputFiles []string
-	var ccObjects []string
-
+	const expectedLen = 10
 	splitString := strings.Split(rawString, "|")
-	if expectedLen := 5; len(splitString) != expectedLen {
+	if len(splitString) != expectedLen {
 		return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString)
 	}
 	outputFilesString := splitString[0]
-	ccStaticLibrariesString := splitString[1]
-	ccObjectsString := splitString[2]
-	outputFiles = splitOrEmpty(outputFilesString, ", ")
+	ccObjectsString := splitString[1]
+	ccSharedLibrariesString := splitString[2]
+	ccStaticLibrariesString := splitString[3]
+	includesString := splitString[4]
+	systemIncludesString := splitString[5]
+	headersString := splitString[6]
+	rootStaticArchivesString := splitString[7]
+	rootDynamicLibrariesString := splitString[8]
+	tocFile := splitString[9] // NOTE: Will be the empty string if there wasn't
+
+	outputFiles := splitOrEmpty(outputFilesString, ", ")
+	ccObjects := splitOrEmpty(ccObjectsString, ", ")
+	ccSharedLibraries := splitOrEmpty(ccSharedLibrariesString, ", ")
 	ccStaticLibraries := splitOrEmpty(ccStaticLibrariesString, ", ")
-	ccObjects = splitOrEmpty(ccObjectsString, ", ")
-	includes := splitOrEmpty(splitString[3], ", ")
-	systemIncludes := splitOrEmpty(splitString[4], ", ")
+	includes := splitOrEmpty(includesString, ", ")
+	systemIncludes := splitOrEmpty(systemIncludesString, ", ")
+	headers := splitOrEmpty(headersString, ", ")
+	rootStaticArchives := splitOrEmpty(rootStaticArchivesString, ", ")
+	rootDynamicLibraries := splitOrEmpty(rootDynamicLibrariesString, ", ")
 	return CcInfo{
 		OutputFiles:          outputFiles,
 		CcObjectFiles:        ccObjects,
+		CcSharedLibraryFiles: ccSharedLibraries,
 		CcStaticLibraryFiles: ccStaticLibraries,
 		Includes:             includes,
 		SystemIncludes:       systemIncludes,
+		Headers:              headers,
+		RootStaticArchives:   rootStaticArchives,
+		RootDynamicLibraries: rootDynamicLibraries,
+		TocFile:              tocFile,
 	}, nil
 }
 
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 6369999..606e285 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -37,7 +37,34 @@
 	}
 }
 
+func TestGetPythonBinaryParseResults(t *testing.T) {
+	testCases := []struct {
+		description    string
+		input          string
+		expectedOutput string
+	}{
+		{
+			description:    "no result",
+			input:          "",
+			expectedOutput: "",
+		},
+		{
+			description:    "one result",
+			input:          "test",
+			expectedOutput: "test",
+		},
+	}
+	for _, tc := range testCases {
+		actualOutput := GetPythonBinary.ParseResult(tc.input)
+		if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
+			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
+		}
+	}
+}
+
 func TestGetCcInfoParseResults(t *testing.T) {
+	const expectedSplits = 10
+	noResult := strings.Repeat("|", expectedSplits-1)
 	testCases := []struct {
 		description          string
 		input                string
@@ -46,57 +73,97 @@
 	}{
 		{
 			description: "no result",
-			input:       "||||",
+			input:       noResult,
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{},
 				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				Headers:              []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
+				TocFile:              "",
 			},
 		},
 		{
 			description: "only output",
-			input:       "test||||",
+			input:       "test" + noResult,
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"test"},
 				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				Headers:              []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
+				TocFile:              "",
+			},
+		},
+		{
+			description: "only ToC",
+			input:       noResult + "test",
+			expectedOutput: CcInfo{
+				OutputFiles:          []string{},
+				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
+				CcStaticLibraryFiles: []string{},
+				Includes:             []string{},
+				SystemIncludes:       []string{},
+				Headers:              []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
+				TocFile:              "test",
 			},
 		},
 		{
 			description: "all items set",
-			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir",
+			input: "out1, out2" +
+				"|object1, object2" +
+				"|shared_lib1, shared_lib2" +
+				"|static_lib1, static_lib2" +
+				"|., dir/subdir" +
+				"|system/dir, system/other/dir" +
+				"|dir/subdir/hdr.h" +
+				"|rootstaticarchive1" +
+				"|rootdynamiclibrary1" +
+				"|lib.so.toc",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
 				CcObjectFiles:        []string{"object1", "object2"},
+				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
 				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
 				Includes:             []string{".", "dir/subdir"},
 				SystemIncludes:       []string{"system/dir", "system/other/dir"},
+				Headers:              []string{"dir/subdir/hdr.h"},
+				RootStaticArchives:   []string{"rootstaticarchive1"},
+				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
+				TocFile:              "lib.so.toc",
 			},
 		},
 		{
 			description:          "too few result splits",
 			input:                "|",
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 5, []string{"", ""}),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, []string{"", ""}),
 		},
 		{
 			description:          "too many result splits",
-			input:                strings.Repeat("|", 8),
+			input:                strings.Repeat("|", expectedSplits+1), // 2 too many
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 5, make([]string, 9)),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, make([]string, expectedSplits+2)),
 		},
 	}
 	for _, tc := range testCases {
 		actualOutput, err := GetCcInfo.ParseResult(tc.input)
 		if (err == nil && tc.expectedErrorMessage != "") ||
 			(err != nil && err.Error() != tc.expectedErrorMessage) {
-			t.Errorf("%q: expected Error %s, got %s", tc.description, tc.expectedErrorMessage, err)
+			t.Errorf("%q:\nexpected Error %s\n, got %s", tc.description, tc.expectedErrorMessage, err)
 		} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
+			t.Errorf("%q:\n expected %#v\n!= actual %#v", tc.description, tc.expectedOutput, actualOutput)
 		}
 	}
 }
diff --git a/bazel/properties.go b/bazel/properties.go
index a71b12b..f956031 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -19,6 +19,9 @@
 	"path/filepath"
 	"regexp"
 	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
 )
 
 // BazelTargetModuleProperties contain properties and metadata used for
@@ -31,8 +34,6 @@
 	Bzl_load_location string `blueprint:"mutated"`
 }
 
-const BazelTargetModuleNamePrefix = "__bp2build__"
-
 var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
 
 // Label is used to represent a Bazel compatible Label. Also stores the original
@@ -64,6 +65,42 @@
 	Excludes []Label
 }
 
+// MakeLabelList creates a LabelList from a list Label
+func MakeLabelList(labels []Label) LabelList {
+	return LabelList{
+		Includes: labels,
+		Excludes: nil,
+	}
+}
+
+func (ll *LabelList) Equals(other LabelList) bool {
+	if len(ll.Includes) != len(other.Includes) || len(ll.Excludes) != len(other.Excludes) {
+		return false
+	}
+	for i, _ := range ll.Includes {
+		if ll.Includes[i] != other.Includes[i] {
+			return false
+		}
+	}
+	for i, _ := range ll.Excludes {
+		if ll.Excludes[i] != other.Excludes[i] {
+			return false
+		}
+	}
+	return true
+}
+
+func (ll *LabelList) IsNil() bool {
+	return ll.Includes == nil && ll.Excludes == nil
+}
+
+func (ll *LabelList) deepCopy() LabelList {
+	return LabelList{
+		Includes: ll.Includes[:],
+		Excludes: ll.Excludes[:],
+	}
+}
+
 // uniqueParentDirectories returns a list of the unique parent directories for
 // all files in ll.Includes.
 func (ll *LabelList) uniqueParentDirectories() []string {
@@ -78,6 +115,14 @@
 	return dirs
 }
 
+// Add inserts the label Label at the end of the LabelList.
+func (ll *LabelList) Add(label *Label) {
+	if label == nil {
+		return
+	}
+	ll.Includes = append(ll.Includes, *label)
+}
+
 // Append appends the fields of other labelList to the corresponding fields of ll.
 func (ll *LabelList) Append(other LabelList) {
 	if len(ll.Includes) > 0 || len(other.Includes) > 0 {
@@ -105,7 +150,27 @@
 	return uniqueLabels
 }
 
-func UniqueBazelLabelList(originalLabelList LabelList) LabelList {
+func FirstUniqueBazelLabels(originalLabels []Label) []Label {
+	var labels []Label
+	found := make(map[Label]bool, len(originalLabels))
+	for _, l := range originalLabels {
+		if _, ok := found[l]; ok {
+			continue
+		}
+		labels = append(labels, l)
+		found[l] = true
+	}
+	return labels
+}
+
+func FirstUniqueBazelLabelList(originalLabelList LabelList) LabelList {
+	var uniqueLabelList LabelList
+	uniqueLabelList.Includes = FirstUniqueBazelLabels(originalLabelList.Includes)
+	uniqueLabelList.Excludes = FirstUniqueBazelLabels(originalLabelList.Excludes)
+	return uniqueLabelList
+}
+
+func UniqueSortedBazelLabelList(originalLabelList LabelList) LabelList {
 	var uniqueLabelList LabelList
 	uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
 	uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
@@ -115,51 +180,47 @@
 // Subtract needle from haystack
 func SubtractStrings(haystack []string, needle []string) []string {
 	// This is really a set
-	remainder := make(map[string]bool)
-
-	for _, s := range haystack {
-		remainder[s] = true
-	}
+	needleMap := make(map[string]bool)
 	for _, s := range needle {
-		delete(remainder, s)
+		needleMap[s] = true
 	}
 
 	var strings []string
-	for s, _ := range remainder {
-		strings = append(strings, s)
+	for _, s := range haystack {
+		if exclude := needleMap[s]; !exclude {
+			strings = append(strings, s)
+		}
 	}
 
-	sort.SliceStable(strings, func(i, j int) bool {
-		return strings[i] < strings[j]
-	})
-
 	return strings
 }
 
 // Subtract needle from haystack
 func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
 	// This is really a set
-	remainder := make(map[Label]bool)
-
-	for _, label := range haystack {
-		remainder[label] = true
-	}
-	for _, label := range needle {
-		delete(remainder, label)
+	needleMap := make(map[Label]bool)
+	for _, s := range needle {
+		needleMap[s] = true
 	}
 
 	var labels []Label
-	for label, _ := range remainder {
-		labels = append(labels, label)
+	for _, label := range haystack {
+		if exclude := needleMap[label]; !exclude {
+			labels = append(labels, label)
+		}
 	}
 
-	sort.SliceStable(labels, func(i, j int) bool {
-		return labels[i].Label < labels[j].Label
-	})
-
 	return labels
 }
 
+// Appends two LabelLists, returning the combined list.
+func AppendBazelLabelLists(a LabelList, b LabelList) LabelList {
+	var result LabelList
+	result.Includes = append(a.Includes, b.Includes...)
+	result.Excludes = append(a.Excludes, b.Excludes...)
+	return result
+}
+
 // Subtract needle from haystack
 func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
 	var result LabelList
@@ -169,247 +230,720 @@
 	return result
 }
 
-const (
-	// ArchType names in arch.go
-	ARCH_ARM    = "arm"
-	ARCH_ARM64  = "arm64"
-	ARCH_X86    = "x86"
-	ARCH_X86_64 = "x86_64"
-
-	// OsType names in arch.go
-	OS_ANDROID      = "android"
-	OS_DARWIN       = "darwin"
-	OS_FUCHSIA      = "fuchsia"
-	OS_LINUX        = "linux_glibc"
-	OS_LINUX_BIONIC = "linux_bionic"
-	OS_WINDOWS      = "windows"
-
-	// This is the string representation of the default condition wherever a
-	// configurable attribute is used in a select statement, i.e.
-	// //conditions:default for Bazel.
-	//
-	// This is consistently named "conditions_default" to mirror the Soong
-	// config variable default key in an Android.bp file, although there's no
-	// integration with Soong config variables (yet).
-	CONDITIONS_DEFAULT = "conditions_default"
-)
-
-var (
-	// These are the list of OSes and architectures with a Bazel config_setting
-	// and constraint value equivalent. These exist in arch.go, but the android
-	// package depends on the bazel package, so a cyclic dependency prevents
-	// using those variables here.
-
-	// A map of architectures to the Bazel label of the constraint_value
-	// for the @platforms//cpu:cpu constraint_setting
-	PlatformArchMap = map[string]string{
-		ARCH_ARM:           "//build/bazel/platforms/arch:arm",
-		ARCH_ARM64:         "//build/bazel/platforms/arch:arm64",
-		ARCH_X86:           "//build/bazel/platforms/arch:x86",
-		ARCH_X86_64:        "//build/bazel/platforms/arch:x86_64",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of as arch select map.
-	}
-
-	// A map of target operating systems to the Bazel label of the
-	// constraint_value for the @platforms//os:os constraint_setting
-	PlatformOsMap = map[string]string{
-		OS_ANDROID:         "//build/bazel/platforms/os:android",
-		OS_DARWIN:          "//build/bazel/platforms/os:darwin",
-		OS_FUCHSIA:         "//build/bazel/platforms/os:fuchsia",
-		OS_LINUX:           "//build/bazel/platforms/os:linux",
-		OS_LINUX_BIONIC:    "//build/bazel/platforms/os:linux_bionic",
-		OS_WINDOWS:         "//build/bazel/platforms/os:windows",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of an os select map.
-	}
-)
-
 type Attribute interface {
 	HasConfigurableValues() bool
 }
 
+type labelSelectValues map[string]*Label
+
+type configurableLabels map[ConfigurationAxis]labelSelectValues
+
+func (cl configurableLabels) setValueForAxis(axis ConfigurationAxis, config string, value *Label) {
+	if cl[axis] == nil {
+		cl[axis] = make(labelSelectValues)
+	}
+	cl[axis][config] = value
+}
+
 // Represents an attribute whose value is a single label
 type LabelAttribute struct {
-	Value  Label
-	X86    Label
-	X86_64 Label
-	Arm    Label
-	Arm64  Label
+	Value *Label
+
+	ConfigurableValues configurableLabels
 }
 
-func (attr *LabelAttribute) GetValueForArch(arch string) Label {
-	switch arch {
-	case ARCH_ARM:
-		return attr.Arm
-	case ARCH_ARM64:
-		return attr.Arm64
-	case ARCH_X86:
-		return attr.X86
-	case ARCH_X86_64:
-		return attr.X86_64
-	case CONDITIONS_DEFAULT:
-		return attr.Value
-	default:
-		panic("Invalid arch type")
-	}
-}
-
-func (attr *LabelAttribute) SetValueForArch(arch string, value Label) {
-	switch arch {
-	case ARCH_ARM:
-		attr.Arm = value
-	case ARCH_ARM64:
-		attr.Arm64 = value
-	case ARCH_X86:
-		attr.X86 = value
-	case ARCH_X86_64:
-		attr.X86_64 = value
-	default:
-		panic("Invalid arch type")
-	}
-}
-
-func (attr LabelAttribute) HasConfigurableValues() bool {
-	return attr.Arm.Label != "" || attr.Arm64.Label != "" || attr.X86.Label != "" || attr.X86_64.Label != ""
-}
-
-// Arch-specific label_list typed Bazel attribute values. This should correspond
-// to the types of architectures supported for compilation in arch.go.
-type labelListArchValues struct {
-	X86    LabelList
-	X86_64 LabelList
-	Arm    LabelList
-	Arm64  LabelList
-	Common LabelList
-
-	ConditionsDefault LabelList
-}
-
-type labelListOsValues struct {
-	Android     LabelList
-	Darwin      LabelList
-	Fuchsia     LabelList
-	Linux       LabelList
-	LinuxBionic LabelList
-	Windows     LabelList
-
-	ConditionsDefault LabelList
-}
-
-// LabelListAttribute is used to represent a list of Bazel labels as an
-// attribute.
-type LabelListAttribute struct {
-	// The non-arch specific attribute label list Value. Required.
-	Value LabelList
-
-	// The arch-specific attribute label list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-arch specific
-	// label list Value.
-	ArchValues labelListArchValues
-
-	// The os-specific attribute label list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-os specific
-	// label list Value.
-	OsValues labelListOsValues
-}
-
-// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
-func MakeLabelListAttribute(value LabelList) LabelListAttribute {
-	return LabelListAttribute{Value: UniqueBazelLabelList(value)}
-}
-
-// Append all values, including os and arch specific ones, from another
-// LabelListAttribute to this LabelListAttribute.
-func (attrs *LabelListAttribute) Append(other LabelListAttribute) {
-	for arch := range PlatformArchMap {
-		this := attrs.GetValueForArch(arch)
-		that := other.GetValueForArch(arch)
-		this.Append(that)
-		attrs.SetValueForArch(arch, this)
-	}
-
-	for os := range PlatformOsMap {
-		this := attrs.GetValueForOS(os)
-		that := other.GetValueForOS(os)
-		this.Append(that)
-		attrs.SetValueForOS(os, this)
-	}
-
-	attrs.Value.Append(other.Value)
-}
-
-// HasArchSpecificValues returns true if the attribute contains
-// architecture-specific label_list values.
-func (attrs LabelListAttribute) HasConfigurableValues() bool {
-	for arch := range PlatformArchMap {
-		if len(attrs.GetValueForArch(arch).Includes) > 0 {
-			return true
+func (la *LabelAttribute) axisTypes() map[configurationType]bool {
+	types := map[configurationType]bool{}
+	for k := range la.ConfigurableValues {
+		if len(la.ConfigurableValues[k]) > 0 {
+			types[k.configurationType] = true
 		}
 	}
+	return types
+}
 
-	for os := range PlatformOsMap {
-		if len(attrs.GetValueForOS(os).Includes) > 0 {
+// Collapse reduces the configurable axes of the label attribute to a single axis.
+// This is necessary for final writing to bp2build, as a configurable label
+// attribute can only be comprised by a single select.
+func (la *LabelAttribute) Collapse() error {
+	axisTypes := la.axisTypes()
+	_, containsOs := axisTypes[os]
+	_, containsArch := axisTypes[arch]
+	_, containsOsArch := axisTypes[osArch]
+	_, containsProductVariables := axisTypes[productVariables]
+	if containsProductVariables {
+		if containsOs || containsArch || containsOsArch {
+			return fmt.Errorf("label attribute could not be collapsed as it has two or more unrelated axes")
+		}
+	}
+	if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
+		// If a bool attribute has both os and arch configuration axes, the only
+		// way to successfully union their values is to increase the granularity
+		// of the configuration criteria to os_arch.
+		for osType, supportedArchs := range osToArchMap {
+			for _, supportedArch := range supportedArchs {
+				osArch := osArchString(osType, supportedArch)
+				if archOsVal := la.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
+					// Do nothing, as the arch_os is explicitly defined already.
+				} else {
+					archVal := la.SelectValue(ArchConfigurationAxis, supportedArch)
+					osVal := la.SelectValue(OsConfigurationAxis, osType)
+					if osVal != nil && archVal != nil {
+						// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
+						// runs after os mutator.
+						la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
+					} else if osVal != nil && archVal == nil {
+						la.SetSelectValue(OsArchConfigurationAxis, osArch, *osVal)
+					} else if osVal == nil && archVal != nil {
+						la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
+					}
+				}
+			}
+		}
+		// All os_arch values are now set. Clear os and arch axes.
+		delete(la.ConfigurableValues, ArchConfigurationAxis)
+		delete(la.ConfigurableValues, OsConfigurationAxis)
+	}
+	return nil
+}
+
+// HasConfigurableValues returns whether there are configurable values set for this label.
+func (la LabelAttribute) HasConfigurableValues() bool {
+	for _, selectValues := range la.ConfigurableValues {
+		if len(selectValues) > 0 {
 			return true
 		}
 	}
 	return false
 }
 
-func (attrs *LabelListAttribute) archValuePtrs() map[string]*LabelList {
-	return map[string]*LabelList{
-		ARCH_X86:           &attrs.ArchValues.X86,
-		ARCH_X86_64:        &attrs.ArchValues.X86_64,
-		ARCH_ARM:           &attrs.ArchValues.Arm,
-		ARCH_ARM64:         &attrs.ArchValues.Arm64,
-		CONDITIONS_DEFAULT: &attrs.ArchValues.ConditionsDefault,
+// SetValue sets the base, non-configured value for the Label
+func (la *LabelAttribute) SetValue(value Label) {
+	la.SetSelectValue(NoConfigAxis, "", value)
+}
+
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (la *LabelAttribute) SetSelectValue(axis ConfigurationAxis, config string, value Label) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		la.Value = &value
+	case arch, os, osArch, productVariables:
+		if la.ConfigurableValues == nil {
+			la.ConfigurableValues = make(configurableLabels)
+		}
+		la.ConfigurableValues.setValueForAxis(axis, config, &value)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
 	}
 }
 
-// GetValueForArch returns the label_list attribute value for an architecture.
-func (attrs *LabelListAttribute) GetValueForArch(arch string) LabelList {
-	var v *LabelList
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	return *v
-}
-
-// SetValueForArch sets the label_list attribute value for an architecture.
-func (attrs *LabelListAttribute) SetValueForArch(arch string, value LabelList) {
-	var v *LabelList
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	*v = value
-}
-
-func (attrs *LabelListAttribute) osValuePtrs() map[string]*LabelList {
-	return map[string]*LabelList{
-		OS_ANDROID:         &attrs.OsValues.Android,
-		OS_DARWIN:          &attrs.OsValues.Darwin,
-		OS_FUCHSIA:         &attrs.OsValues.Fuchsia,
-		OS_LINUX:           &attrs.OsValues.Linux,
-		OS_LINUX_BIONIC:    &attrs.OsValues.LinuxBionic,
-		OS_WINDOWS:         &attrs.OsValues.Windows,
-		CONDITIONS_DEFAULT: &attrs.OsValues.ConditionsDefault,
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (la *LabelAttribute) SelectValue(axis ConfigurationAxis, config string) *Label {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return la.Value
+	case arch, os, osArch, productVariables:
+		return la.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
 	}
 }
 
-// GetValueForOS returns the label_list attribute value for an OS target.
-func (attrs *LabelListAttribute) GetValueForOS(os string) LabelList {
-	var v *LabelList
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(la.ConfigurableValues))
+	for k := range la.ConfigurableValues {
+		keys = append(keys, k)
 	}
-	return *v
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
 }
 
-// SetValueForArch sets the label_list attribute value for an OS target.
-func (attrs *LabelListAttribute) SetValueForOS(os string, value LabelList) {
-	var v *LabelList
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
+// MakeLabelAttribute turns a string into a LabelAttribute
+func MakeLabelAttribute(label string) *LabelAttribute {
+	return &LabelAttribute{
+		Value: &Label{
+			Label: label,
+		},
 	}
-	*v = value
+}
+
+type configToBools map[string]bool
+
+func (ctb configToBools) setValue(config string, value *bool) {
+	if value == nil {
+		if _, ok := ctb[config]; ok {
+			delete(ctb, config)
+		}
+		return
+	}
+	ctb[config] = *value
+}
+
+type configurableBools map[ConfigurationAxis]configToBools
+
+func (cb configurableBools) setValueForAxis(axis ConfigurationAxis, config string, value *bool) {
+	if cb[axis] == nil {
+		cb[axis] = make(configToBools)
+	}
+	cb[axis].setValue(config, value)
+}
+
+// BoolAttribute represents an attribute whose value is a single bool but may be configurable..
+type BoolAttribute struct {
+	Value *bool
+
+	ConfigurableValues configurableBools
+}
+
+// HasConfigurableValues returns whether there are configurable values for this attribute.
+func (ba BoolAttribute) HasConfigurableValues() bool {
+	for _, cfgToBools := range ba.ConfigurableValues {
+		if len(cfgToBools) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// SetSelectValue sets value for the given axis/config.
+func (ba *BoolAttribute) SetSelectValue(axis ConfigurationAxis, config string, value *bool) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		ba.Value = value
+	case arch, os, osArch, productVariables:
+		if ba.ConfigurableValues == nil {
+			ba.ConfigurableValues = make(configurableBools)
+		}
+		ba.ConfigurableValues.setValueForAxis(axis, config, value)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// ToLabelListAttribute creates and returns a LabelListAttribute from this
+// bool attribute, where each bool in this attribute corresponds to a
+// label list value in the resultant attribute.
+func (ba *BoolAttribute) ToLabelListAttribute(falseVal LabelList, trueVal LabelList) (LabelListAttribute, error) {
+	getLabelList := func(boolPtr *bool) LabelList {
+		if boolPtr == nil {
+			return LabelList{nil, nil}
+		} else if *boolPtr {
+			return trueVal
+		} else {
+			return falseVal
+		}
+	}
+
+	mainVal := getLabelList(ba.Value)
+	if !ba.HasConfigurableValues() {
+		return MakeLabelListAttribute(mainVal), nil
+	}
+
+	result := LabelListAttribute{}
+	if err := ba.Collapse(); err != nil {
+		return result, err
+	}
+
+	for axis, configToBools := range ba.ConfigurableValues {
+		if len(configToBools) < 1 {
+			continue
+		}
+		for config, boolPtr := range configToBools {
+			val := getLabelList(&boolPtr)
+			if !val.Equals(mainVal) {
+				result.SetSelectValue(axis, config, val)
+			}
+		}
+		result.SetSelectValue(axis, ConditionsDefaultConfigKey, mainVal)
+	}
+
+	return result, nil
+}
+
+// Collapse reduces the configurable axes of the boolean attribute to a single axis.
+// This is necessary for final writing to bp2build, as a configurable boolean
+// attribute can only be comprised by a single select.
+func (ba *BoolAttribute) Collapse() error {
+	axisTypes := ba.axisTypes()
+	_, containsOs := axisTypes[os]
+	_, containsArch := axisTypes[arch]
+	_, containsOsArch := axisTypes[osArch]
+	_, containsProductVariables := axisTypes[productVariables]
+	if containsProductVariables {
+		if containsOs || containsArch || containsOsArch {
+			return fmt.Errorf("boolean attribute could not be collapsed as it has two or more unrelated axes")
+		}
+	}
+	if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
+		// If a bool attribute has both os and arch configuration axes, the only
+		// way to successfully union their values is to increase the granularity
+		// of the configuration criteria to os_arch.
+		for osType, supportedArchs := range osToArchMap {
+			for _, supportedArch := range supportedArchs {
+				osArch := osArchString(osType, supportedArch)
+				if archOsVal := ba.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
+					// Do nothing, as the arch_os is explicitly defined already.
+				} else {
+					archVal := ba.SelectValue(ArchConfigurationAxis, supportedArch)
+					osVal := ba.SelectValue(OsConfigurationAxis, osType)
+					if osVal != nil && archVal != nil {
+						// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
+						// runs after os mutator.
+						ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
+					} else if osVal != nil && archVal == nil {
+						ba.SetSelectValue(OsArchConfigurationAxis, osArch, osVal)
+					} else if osVal == nil && archVal != nil {
+						ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
+					}
+				}
+			}
+		}
+		// All os_arch values are now set. Clear os and arch axes.
+		delete(ba.ConfigurableValues, ArchConfigurationAxis)
+		delete(ba.ConfigurableValues, OsConfigurationAxis)
+		// Verify post-condition; this should never fail, provided no additional
+		// axes are introduced.
+		if len(ba.ConfigurableValues) > 1 {
+			panic(fmt.Errorf("error in collapsing attribute: %#v", ba))
+		}
+	}
+	return nil
+}
+
+func (ba *BoolAttribute) axisTypes() map[configurationType]bool {
+	types := map[configurationType]bool{}
+	for k := range ba.ConfigurableValues {
+		if len(ba.ConfigurableValues[k]) > 0 {
+			types[k.configurationType] = true
+		}
+	}
+	return types
+}
+
+// SelectValue gets the value for the given axis/config.
+func (ba BoolAttribute) SelectValue(axis ConfigurationAxis, config string) *bool {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return ba.Value
+	case arch, os, osArch, productVariables:
+		if v, ok := ba.ConfigurableValues[axis][config]; ok {
+			return &v
+		} else {
+			return nil
+		}
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (ba *BoolAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(ba.ConfigurableValues))
+	for k := range ba.ConfigurableValues {
+		keys = append(keys, k)
+	}
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
+// labelListSelectValues supports config-specific label_list typed Bazel attribute values.
+type labelListSelectValues map[string]LabelList
+
+func (ll labelListSelectValues) addSelects(label labelSelectValues) {
+	for k, v := range label {
+		if label == nil {
+			continue
+		}
+		l := ll[k]
+		(&l).Add(v)
+		ll[k] = l
+	}
+}
+
+func (ll labelListSelectValues) appendSelects(other labelListSelectValues, forceSpecifyEmptyList bool) {
+	for k, v := range other {
+		l := ll[k]
+		if forceSpecifyEmptyList && l.IsNil() && !v.IsNil() {
+			l.Includes = []Label{}
+		}
+		(&l).Append(v)
+		ll[k] = l
+	}
+}
+
+// HasConfigurableValues returns whether there are configurable values within this set of selects.
+func (ll labelListSelectValues) HasConfigurableValues() bool {
+	for _, v := range ll {
+		if v.Includes != nil {
+			return true
+		}
+	}
+	return false
+}
+
+// LabelListAttribute is used to represent a list of Bazel labels as an
+// attribute.
+type LabelListAttribute struct {
+	// The non-configured attribute label list Value. Required.
+	Value LabelList
+
+	// The configured attribute label list Values. Optional
+	// a map of independent configurability axes
+	ConfigurableValues configurableLabelLists
+
+	// If true, differentiate between "nil" and "empty" list. nil means that
+	// this attribute should not be specified at all, and "empty" means that
+	// the attribute should be explicitly specified as an empty list.
+	// This mode facilitates use of attribute defaults: an empty list should
+	// override the default.
+	ForceSpecifyEmptyList bool
+
+	// If true, signal the intent to the code generator to emit all select keys,
+	// even if the Includes list for that key is empty. This mode facilitates
+	// specific select statements where an empty list for a non-default select
+	// key has a meaning.
+	EmitEmptyList bool
+}
+
+type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
+
+func (cll configurableLabelLists) setValueForAxis(axis ConfigurationAxis, config string, list LabelList) {
+	if list.IsNil() {
+		if _, ok := cll[axis][config]; ok {
+			delete(cll[axis], config)
+		}
+		return
+	}
+	if cll[axis] == nil {
+		cll[axis] = make(labelListSelectValues)
+	}
+
+	cll[axis][config] = list
+}
+
+func (cll configurableLabelLists) Append(other configurableLabelLists, forceSpecifyEmptyList bool) {
+	for axis, otherSelects := range other {
+		selects := cll[axis]
+		if selects == nil {
+			selects = make(labelListSelectValues, len(otherSelects))
+		}
+		selects.appendSelects(otherSelects, forceSpecifyEmptyList)
+		cll[axis] = selects
+	}
+}
+
+func (lla *LabelListAttribute) Clone() *LabelListAttribute {
+	result := &LabelListAttribute{ForceSpecifyEmptyList: lla.ForceSpecifyEmptyList}
+	return result.Append(*lla)
+}
+
+// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
+func MakeLabelListAttribute(value LabelList) LabelListAttribute {
+	return LabelListAttribute{
+		Value:              value,
+		ConfigurableValues: make(configurableLabelLists),
+	}
+}
+
+func (lla *LabelListAttribute) SetValue(list LabelList) {
+	lla.SetSelectValue(NoConfigAxis, "", list)
+}
+
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (lla *LabelListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list LabelList) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		lla.Value = list
+	case arch, os, osArch, productVariables:
+		if lla.ConfigurableValues == nil {
+			lla.ConfigurableValues = make(configurableLabelLists)
+		}
+		lla.ConfigurableValues.setValueForAxis(axis, config, list)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (lla *LabelListAttribute) SelectValue(axis ConfigurationAxis, config string) LabelList {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return lla.Value
+	case arch, os, osArch, productVariables:
+		return lla.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(lla.ConfigurableValues))
+	for k := range lla.ConfigurableValues {
+		keys = append(keys, k)
+	}
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
+// Append all values, including os and arch specific ones, from another
+// LabelListAttribute to this LabelListAttribute. Returns this LabelListAttribute.
+func (lla *LabelListAttribute) Append(other LabelListAttribute) *LabelListAttribute {
+	forceSpecifyEmptyList := lla.ForceSpecifyEmptyList || other.ForceSpecifyEmptyList
+	if forceSpecifyEmptyList && lla.Value.IsNil() && !other.Value.IsNil() {
+		lla.Value.Includes = []Label{}
+	}
+	lla.Value.Append(other.Value)
+	if lla.ConfigurableValues == nil {
+		lla.ConfigurableValues = make(configurableLabelLists)
+	}
+	lla.ConfigurableValues.Append(other.ConfigurableValues, forceSpecifyEmptyList)
+	return lla
+}
+
+// Add inserts the labels for each axis of LabelAttribute at the end of corresponding axis's
+// LabelList within the LabelListAttribute
+func (lla *LabelListAttribute) Add(label *LabelAttribute) {
+	if label == nil {
+		return
+	}
+
+	lla.Value.Add(label.Value)
+	if lla.ConfigurableValues == nil && label.ConfigurableValues != nil {
+		lla.ConfigurableValues = make(configurableLabelLists)
+	}
+	for axis, _ := range label.ConfigurableValues {
+		if _, exists := lla.ConfigurableValues[axis]; !exists {
+			lla.ConfigurableValues[axis] = make(labelListSelectValues)
+		}
+		lla.ConfigurableValues[axis].addSelects(label.ConfigurableValues[axis])
+	}
+}
+
+// HasConfigurableValues returns true if the attribute contains axis-specific label list values.
+func (lla LabelListAttribute) HasConfigurableValues() bool {
+	for _, selectValues := range lla.ConfigurableValues {
+		if len(selectValues) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// IsEmpty returns true if the attribute has no values under any configuration.
+func (lla LabelListAttribute) IsEmpty() bool {
+	if len(lla.Value.Includes) > 0 {
+		return false
+	}
+	for axis, _ := range lla.ConfigurableValues {
+		if lla.ConfigurableValues[axis].HasConfigurableValues() {
+			return false
+		}
+	}
+	return true
+}
+
+// IsNil returns true if the attribute has not been set for any configuration.
+func (lla LabelListAttribute) IsNil() bool {
+	if lla.Value.Includes != nil {
+		return false
+	}
+	return !lla.HasConfigurableValues()
+}
+
+// Exclude for the given axis, config, removes Includes in labelList from Includes and appends them
+// to Excludes. This is to special case any excludes that are not specified in a bp file but need to
+// be removed, e.g. if they could cause duplicate element failures.
+func (lla *LabelListAttribute) Exclude(axis ConfigurationAxis, config string, labelList LabelList) {
+	val := lla.SelectValue(axis, config)
+	newList := SubtractBazelLabelList(val, labelList)
+	newList.Excludes = append(newList.Excludes, labelList.Includes...)
+	lla.SetSelectValue(axis, config, newList)
+}
+
+// ResolveExcludes handles excludes across the various axes, ensuring that items are removed from
+// the base value and included in default values as appropriate.
+func (lla *LabelListAttribute) ResolveExcludes() {
+	for axis, configToLabels := range lla.ConfigurableValues {
+		baseLabels := lla.Value.deepCopy()
+		for config, val := range configToLabels {
+			// Exclude config-specific excludes from base value
+			lla.Value = SubtractBazelLabelList(lla.Value, LabelList{Includes: val.Excludes})
+
+			// add base values to config specific to add labels excluded by others in this axis
+			// then remove all config-specific excludes
+			allLabels := baseLabels.deepCopy()
+			allLabels.Append(val)
+			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: val.Excludes})
+		}
+
+		// After going through all configs, delete the duplicates in the config
+		// values that are already in the base Value.
+		for config, val := range configToLabels {
+			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(val, lla.Value)
+		}
+
+		// Now that the Value list is finalized for this axis, compare it with
+		// the original list, and union the difference with the default
+		// condition for the axis.
+		difference := SubtractBazelLabelList(baseLabels, lla.Value)
+		existingDefaults := lla.ConfigurableValues[axis][ConditionsDefaultConfigKey]
+		existingDefaults.Append(difference)
+		lla.ConfigurableValues[axis][ConditionsDefaultConfigKey] = FirstUniqueBazelLabelList(existingDefaults)
+
+		// if everything ends up without includes, just delete the axis
+		if !lla.ConfigurableValues[axis].HasConfigurableValues() {
+			delete(lla.ConfigurableValues, axis)
+		}
+	}
+}
+
+// OtherModuleContext is a limited context that has methods with information about other modules.
+type OtherModuleContext interface {
+	ModuleFromName(name string) (blueprint.Module, bool)
+	OtherModuleType(m blueprint.Module) string
+	OtherModuleName(m blueprint.Module) string
+	OtherModuleDir(m blueprint.Module) string
+	ModuleErrorf(fmt string, args ...interface{})
+}
+
+// LabelMapper is a function that takes a OtherModuleContext and returns a (potentially changed)
+// label and whether it was changed.
+type LabelMapper func(OtherModuleContext, Label) (string, bool)
+
+// LabelPartition contains descriptions of a partition for labels
+type LabelPartition struct {
+	// Extensions to include in this partition
+	Extensions []string
+	// LabelMapper is a function that can map a label to a new label, and indicate whether to include
+	// the mapped label in the partition
+	LabelMapper LabelMapper
+	// Whether to store files not included in any other partition in a group of LabelPartitions
+	// Only one partition in a group of LabelPartitions can enabled Keep_remainder
+	Keep_remainder bool
+}
+
+// LabelPartitions is a map of partition name to a LabelPartition describing the elements of the
+// partition
+type LabelPartitions map[string]LabelPartition
+
+// filter returns a pointer to a label if the label should be included in the partition or nil if
+// not.
+func (lf LabelPartition) filter(ctx OtherModuleContext, label Label) *Label {
+	if lf.LabelMapper != nil {
+		if newLabel, changed := lf.LabelMapper(ctx, label); changed {
+			return &Label{newLabel, label.OriginalModuleName}
+		}
+	}
+	for _, ext := range lf.Extensions {
+		if strings.HasSuffix(label.Label, ext) {
+			return &label
+		}
+	}
+
+	return nil
+}
+
+// PartitionToLabelListAttribute is map of partition name to a LabelListAttribute
+type PartitionToLabelListAttribute map[string]LabelListAttribute
+
+type partitionToLabelList map[string]*LabelList
+
+func (p partitionToLabelList) appendIncludes(partition string, label Label) {
+	if _, ok := p[partition]; !ok {
+		p[partition] = &LabelList{}
+	}
+	p[partition].Includes = append(p[partition].Includes, label)
+}
+
+func (p partitionToLabelList) excludes(partition string, excludes []Label) {
+	if _, ok := p[partition]; !ok {
+		p[partition] = &LabelList{}
+	}
+	p[partition].Excludes = excludes
+}
+
+// PartitionLabelListAttribute partitions a LabelListAttribute into the requested partitions
+func PartitionLabelListAttribute(ctx OtherModuleContext, lla *LabelListAttribute, partitions LabelPartitions) PartitionToLabelListAttribute {
+	ret := PartitionToLabelListAttribute{}
+	var partitionNames []string
+	// Stored as a pointer to distinguish nil (no remainder partition) from empty string partition
+	var remainderPartition *string
+	for p, f := range partitions {
+		partitionNames = append(partitionNames, p)
+		if f.Keep_remainder {
+			if remainderPartition != nil {
+				panic("only one partition can store the remainder")
+			}
+			// If we take the address of p in a loop, we'll end up with the last value of p in
+			// remainderPartition, we want the requested partition
+			capturePartition := p
+			remainderPartition = &capturePartition
+		}
+	}
+
+	partitionLabelList := func(axis ConfigurationAxis, config string) {
+		value := lla.SelectValue(axis, config)
+		partitionToLabels := partitionToLabelList{}
+		for _, item := range value.Includes {
+			wasFiltered := false
+			var inPartition *string
+			for partition, f := range partitions {
+				filtered := f.filter(ctx, item)
+				if filtered == nil {
+					// did not match this filter, keep looking
+					continue
+				}
+				wasFiltered = true
+				partitionToLabels.appendIncludes(partition, *filtered)
+				// don't need to check other partitions if this filter used the item,
+				// continue checking if mapped to another name
+				if *filtered == item {
+					if inPartition != nil {
+						ctx.ModuleErrorf("%q was found in multiple partitions: %q, %q", item.Label, *inPartition, partition)
+					}
+					capturePartition := partition
+					inPartition = &capturePartition
+				}
+			}
+
+			// if not specified in a partition, add to remainder partition if one exists
+			if !wasFiltered && remainderPartition != nil {
+				partitionToLabels.appendIncludes(*remainderPartition, item)
+			}
+		}
+
+		// ensure empty lists are maintained
+		if value.Excludes != nil {
+			for _, partition := range partitionNames {
+				partitionToLabels.excludes(partition, value.Excludes)
+			}
+		}
+
+		for partition, list := range partitionToLabels {
+			val := ret[partition]
+			(&val).SetSelectValue(axis, config, *list)
+			ret[partition] = val
+		}
+	}
+
+	partitionLabelList(NoConfigAxis, "")
+	for axis, configToList := range lla.ConfigurableValues {
+		for config, _ := range configToList {
+			partitionLabelList(axis, config)
+		}
+	}
+	return ret
 }
 
 // StringListAttribute corresponds to the string_list Bazel attribute type with
@@ -418,139 +952,146 @@
 	// The base value of the string list attribute.
 	Value []string
 
-	// The arch-specific attribute string list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-arch specific
-	// label list Value.
-	ArchValues stringListArchValues
-
-	// The os-specific attribute string list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-os specific
-	// label list Value.
-	OsValues stringListOsValues
+	// The configured attribute label list Values. Optional
+	// a map of independent configurability axes
+	ConfigurableValues configurableStringLists
 }
 
-// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
-func MakeStringListAttribute(value []string) StringListAttribute {
-	// NOTE: These strings are not necessarily unique or sorted.
-	return StringListAttribute{Value: value}
-}
+type configurableStringLists map[ConfigurationAxis]stringListSelectValues
 
-// Arch-specific string_list typed Bazel attribute values. This should correspond
-// to the types of architectures supported for compilation in arch.go.
-type stringListArchValues struct {
-	X86    []string
-	X86_64 []string
-	Arm    []string
-	Arm64  []string
-	Common []string
-
-	ConditionsDefault []string
-}
-
-type stringListOsValues struct {
-	Android     []string
-	Darwin      []string
-	Fuchsia     []string
-	Linux       []string
-	LinuxBionic []string
-	Windows     []string
-
-	ConditionsDefault []string
-}
-
-// HasConfigurableValues returns true if the attribute contains
-// architecture-specific string_list values.
-func (attrs StringListAttribute) HasConfigurableValues() bool {
-	for arch := range PlatformArchMap {
-		if len(attrs.GetValueForArch(arch)) > 0 {
-			return true
+func (csl configurableStringLists) Append(other configurableStringLists) {
+	for axis, otherSelects := range other {
+		selects := csl[axis]
+		if selects == nil {
+			selects = make(stringListSelectValues, len(otherSelects))
 		}
+		selects.appendSelects(otherSelects)
+		csl[axis] = selects
 	}
+}
 
-	for os := range PlatformOsMap {
-		if len(attrs.GetValueForOS(os)) > 0 {
+func (csl configurableStringLists) setValueForAxis(axis ConfigurationAxis, config string, list []string) {
+	if csl[axis] == nil {
+		csl[axis] = make(stringListSelectValues)
+	}
+	csl[axis][config] = list
+}
+
+type stringListSelectValues map[string][]string
+
+func (sl stringListSelectValues) appendSelects(other stringListSelectValues) {
+	for k, v := range other {
+		sl[k] = append(sl[k], v...)
+	}
+}
+
+func (sl stringListSelectValues) hasConfigurableValues(other stringListSelectValues) bool {
+	for _, val := range sl {
+		if len(val) > 0 {
 			return true
 		}
 	}
 	return false
 }
 
-func (attrs *StringListAttribute) archValuePtrs() map[string]*[]string {
-	return map[string]*[]string{
-		ARCH_X86:           &attrs.ArchValues.X86,
-		ARCH_X86_64:        &attrs.ArchValues.X86_64,
-		ARCH_ARM:           &attrs.ArchValues.Arm,
-		ARCH_ARM64:         &attrs.ArchValues.Arm64,
-		CONDITIONS_DEFAULT: &attrs.ArchValues.ConditionsDefault,
+// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
+func MakeStringListAttribute(value []string) StringListAttribute {
+	// NOTE: These strings are not necessarily unique or sorted.
+	return StringListAttribute{
+		Value:              value,
+		ConfigurableValues: make(configurableStringLists),
 	}
 }
 
-// GetValueForArch returns the string_list attribute value for an architecture.
-func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
-	var v *[]string
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
+// HasConfigurableValues returns true if the attribute contains axis-specific string_list values.
+func (sla StringListAttribute) HasConfigurableValues() bool {
+	for _, selectValues := range sla.ConfigurableValues {
+		if len(selectValues) > 0 {
+			return true
+		}
 	}
-	return *v
-}
-
-// SetValueForArch sets the string_list attribute value for an architecture.
-func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
-	var v *[]string
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	*v = value
-}
-
-func (attrs *StringListAttribute) osValuePtrs() map[string]*[]string {
-	return map[string]*[]string{
-		OS_ANDROID:         &attrs.OsValues.Android,
-		OS_DARWIN:          &attrs.OsValues.Darwin,
-		OS_FUCHSIA:         &attrs.OsValues.Fuchsia,
-		OS_LINUX:           &attrs.OsValues.Linux,
-		OS_LINUX_BIONIC:    &attrs.OsValues.LinuxBionic,
-		OS_WINDOWS:         &attrs.OsValues.Windows,
-		CONDITIONS_DEFAULT: &attrs.OsValues.ConditionsDefault,
-	}
-}
-
-// GetValueForOS returns the string_list attribute value for an OS target.
-func (attrs *StringListAttribute) GetValueForOS(os string) []string {
-	var v *[]string
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
-	}
-	return *v
-}
-
-// SetValueForArch sets the string_list attribute value for an OS target.
-func (attrs *StringListAttribute) SetValueForOS(os string, value []string) {
-	var v *[]string
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
-	}
-	*v = value
+	return false
 }
 
 // Append appends all values, including os and arch specific ones, from another
 // StringListAttribute to this StringListAttribute
-func (attrs *StringListAttribute) Append(other StringListAttribute) {
-	for arch := range PlatformArchMap {
-		this := attrs.GetValueForArch(arch)
-		that := other.GetValueForArch(arch)
-		this = append(this, that...)
-		attrs.SetValueForArch(arch, this)
+func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
+	sla.Value = append(sla.Value, other.Value...)
+	if sla.ConfigurableValues == nil {
+		sla.ConfigurableValues = make(configurableStringLists)
+	}
+	sla.ConfigurableValues.Append(other.ConfigurableValues)
+	return sla
+}
+
+func (sla *StringListAttribute) Clone() *StringListAttribute {
+	result := &StringListAttribute{}
+	return result.Append(*sla)
+}
+
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (sla *StringListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list []string) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		sla.Value = list
+	case arch, os, osArch, productVariables:
+		if sla.ConfigurableValues == nil {
+			sla.ConfigurableValues = make(configurableStringLists)
+		}
+		sla.ConfigurableValues.setValueForAxis(axis, config, list)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (sla *StringListAttribute) SelectValue(axis ConfigurationAxis, config string) []string {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return sla.Value
+	case arch, os, osArch, productVariables:
+		return sla.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(sla.ConfigurableValues))
+	for k := range sla.ConfigurableValues {
+		keys = append(keys, k)
 	}
 
-	for os := range PlatformOsMap {
-		this := attrs.GetValueForOS(os)
-		that := other.GetValueForOS(os)
-		this = append(this, that...)
-		attrs.SetValueForOS(os, this)
-	}
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
 
-	attrs.Value = append(attrs.Value, other.Value...)
+// DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
+// configuration-specific values. For example, if we would convert this StringListAttribute as:
+// ["a", "b", "c"] + select({
+//    "//condition:one": ["a", "d"],
+//    "//conditions:default": [],
+// })
+// after this function, we would convert this StringListAttribute as:
+// ["a", "b", "c"] + select({
+//    "//condition:one": ["d"],
+//    "//conditions:default": [],
+// })
+func (sla *StringListAttribute) DeduplicateAxesFromBase() {
+	base := sla.Value
+	for axis, configToList := range sla.ConfigurableValues {
+		for config, list := range configToList {
+			remaining := SubtractStrings(list, base)
+			if len(remaining) == 0 {
+				delete(sla.ConfigurableValues[axis], config)
+			} else {
+				sla.ConfigurableValues[axis][config] = remaining
+			}
+		}
+	}
 }
 
 // TryVariableSubstitution, replace string substitution formatting within each string in slice with
@@ -569,6 +1110,6 @@
 // TryVariableSubstitution, replace string substitution formatting within s with Starlark
 // string.format compatible tag for productVariable.
 func TryVariableSubstitution(s string, productVariable string) (string, bool) {
-	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "{"+productVariable+"}")
+	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
 	return sub, s != sub
 }
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 229a4aa..7b76b74 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -16,7 +16,10 @@
 
 import (
 	"reflect"
+	"strings"
 	"testing"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func TestUniqueBazelLabels(t *testing.T) {
@@ -122,7 +125,7 @@
 		}
 	}
 }
-func TestUniqueBazelLabelList(t *testing.T) {
+func TestFirstUniqueBazelLabelList(t *testing.T) {
 	testCases := []struct {
 		originalLabelList       LabelList
 		expectedUniqueLabelList LabelList
@@ -157,9 +160,439 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualUniqueLabelList := UniqueBazelLabelList(tc.originalLabelList)
+		actualUniqueLabelList := FirstUniqueBazelLabelList(tc.originalLabelList)
 		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
 			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
 		}
 	}
 }
+
+func TestUniqueSortedBazelLabelList(t *testing.T) {
+	testCases := []struct {
+		originalLabelList       LabelList
+		expectedUniqueLabelList LabelList
+	}{
+		{
+			originalLabelList: LabelList{
+				Includes: []Label{
+					{Label: "c"},
+					{Label: "a"},
+					{Label: "a"},
+					{Label: "b"},
+				},
+				Excludes: []Label{
+					{Label: "y"},
+					{Label: "z"},
+					{Label: "x"},
+					{Label: "x"},
+				},
+			},
+			expectedUniqueLabelList: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualUniqueLabelList := UniqueSortedBazelLabelList(tc.originalLabelList)
+		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
+			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
+		}
+	}
+}
+
+func makeLabels(labels ...string) []Label {
+	var ret []Label
+	for _, l := range labels {
+		ret = append(ret, Label{Label: l})
+	}
+	return ret
+}
+
+func makeLabelList(includes, excludes []string) LabelList {
+	return LabelList{
+		Includes: makeLabels(includes...),
+		Excludes: makeLabels(excludes...),
+	}
+}
+
+func TestResolveExcludes(t *testing.T) {
+	attr := LabelListAttribute{
+		Value: makeLabelList(
+			[]string{
+				"all_include",
+				"arm_exclude",
+				"android_exclude",
+			},
+			[]string{"all_exclude"},
+		),
+		ConfigurableValues: configurableLabelLists{
+			ArchConfigurationAxis: labelListSelectValues{
+				"arm":                      makeLabelList([]string{}, []string{"arm_exclude"}),
+				"x86":                      makeLabelList([]string{"x86_include"}, []string{}),
+				ConditionsDefaultConfigKey: makeLabelList([]string{"default_include"}, []string{}),
+			},
+			OsConfigurationAxis: labelListSelectValues{
+				"android": makeLabelList([]string{}, []string{"android_exclude"}),
+				"linux":   makeLabelList([]string{"linux_include"}, []string{}),
+			},
+			OsArchConfigurationAxis: labelListSelectValues{
+				"linux_x86": makeLabelList([]string{"linux_x86_include"}, []string{}),
+			},
+			ProductVariableConfigurationAxis("product_with_defaults"): labelListSelectValues{
+				"a":                        makeLabelList([]string{}, []string{"not_in_value"}),
+				"b":                        makeLabelList([]string{"b_val"}, []string{}),
+				"c":                        makeLabelList([]string{"c_val"}, []string{}),
+				ConditionsDefaultConfigKey: makeLabelList([]string{"c_val", "default", "default2"}, []string{}),
+			},
+			ProductVariableConfigurationAxis("product_only_with_excludes"): labelListSelectValues{
+				"a": makeLabelList([]string{}, []string{"not_in_value"}),
+			},
+		},
+	}
+
+	attr.ResolveExcludes()
+
+	expectedBaseIncludes := []Label{{Label: "all_include"}}
+	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value.Includes) {
+		t.Errorf("Expected Value includes %q, got %q", attr.Value.Includes, expectedBaseIncludes)
+	}
+	var nilLabels []Label
+	expectedConfiguredIncludes := map[ConfigurationAxis]map[string][]Label{
+		ArchConfigurationAxis: {
+			"arm":                      nilLabels,
+			"x86":                      makeLabels("arm_exclude", "x86_include"),
+			ConditionsDefaultConfigKey: makeLabels("arm_exclude", "default_include"),
+		},
+		OsConfigurationAxis: {
+			"android":                  nilLabels,
+			"linux":                    makeLabels("android_exclude", "linux_include"),
+			ConditionsDefaultConfigKey: makeLabels("android_exclude"),
+		},
+		OsArchConfigurationAxis: {
+			"linux_x86":                makeLabels("linux_x86_include"),
+			ConditionsDefaultConfigKey: nilLabels,
+		},
+		ProductVariableConfigurationAxis("product_with_defaults"): {
+			"a":                        nilLabels,
+			"b":                        makeLabels("b_val"),
+			"c":                        makeLabels("c_val"),
+			ConditionsDefaultConfigKey: makeLabels("c_val", "default", "default2"),
+		},
+	}
+	for _, axis := range attr.SortedConfigurationAxes() {
+		if _, ok := expectedConfiguredIncludes[axis]; !ok {
+			t.Errorf("Found unexpected axis %s", axis)
+			continue
+		}
+		expectedForAxis := expectedConfiguredIncludes[axis]
+		gotForAxis := attr.ConfigurableValues[axis]
+		if len(expectedForAxis) != len(gotForAxis) {
+			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
+		}
+		for config, value := range gotForAxis {
+			if expected, ok := expectedForAxis[config]; ok {
+				if !reflect.DeepEqual(expected, value.Includes) {
+					t.Errorf("For %s,\nexpected: %#v\ngot %#v", axis, expected, value.Includes)
+				}
+			} else {
+				t.Errorf("Got unexpected config %q for %s", config, axis)
+			}
+		}
+	}
+}
+
+// labelAddSuffixForTypeMapper returns a LabelMapper that adds suffix to label name for modules of
+// typ
+func labelAddSuffixForTypeMapper(suffix, typ string) LabelMapper {
+	return func(omc OtherModuleContext, label Label) (string, bool) {
+		m, ok := omc.ModuleFromName(label.Label)
+		if !ok {
+			return label.Label, false
+		}
+		mTyp := omc.OtherModuleType(m)
+		if typ == mTyp {
+			return label.Label + suffix, true
+		}
+		return label.Label, false
+	}
+}
+
+func TestPartitionLabelListAttribute(t *testing.T) {
+	testCases := []struct {
+		name           string
+		ctx            *OtherModuleTestContext
+		labelList      LabelListAttribute
+		filters        LabelPartitions
+		expected       PartitionToLabelListAttribute
+		expectedErrMsg *string
+	}{
+		{
+			name: "no configurable values",
+			ctx:  &OtherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, remainder partition",
+			ctx:  &OtherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}, Keep_remainder: true},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "d.d", "e.e"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, empty partition",
+			ctx:  &OtherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "c.c"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "no configurable values, has map",
+			ctx: &OtherModuleTestContext{
+				Modules: []TestModuleInfo{{ModuleName: "srcs", Typ: "fg", Dir: "dir"}},
+			},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "srcs", "b.b", "c.c"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}, LabelMapper: labelAddSuffixForTypeMapper("_a", "fg")},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{Value: makeLabelList([]string{"a.a", "srcs_a"}, []string{})},
+				"B": LabelListAttribute{Value: makeLabelList([]string{"b.b"}, []string{})},
+				"C": LabelListAttribute{Value: makeLabelList([]string{"c.c"}, []string{})},
+			},
+		},
+		{
+			name: "configurable values, keeps empty if excludes",
+			ctx:  &OtherModuleTestContext{},
+			labelList: LabelListAttribute{
+				ConfigurableValues: configurableLabelLists{
+					ArchConfigurationAxis: labelListSelectValues{
+						"x86":    makeLabelList([]string{"a.a", "c.c"}, []string{}),
+						"arm":    makeLabelList([]string{"b.b"}, []string{}),
+						"x86_64": makeLabelList([]string{"b.b"}, []string{"d.d"}),
+					},
+				},
+			},
+			filters: LabelPartitions{
+				"A": LabelPartition{Extensions: []string{".a"}},
+				"B": LabelPartition{Extensions: []string{".b"}},
+				"C": LabelPartition{Extensions: []string{".c"}},
+			},
+			expected: PartitionToLabelListAttribute{
+				"A": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"x86":    makeLabelList([]string{"a.a"}, []string{}),
+							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
+						},
+					},
+				},
+				"B": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"arm":    makeLabelList([]string{"b.b"}, []string{}),
+							"x86_64": makeLabelList([]string{"b.b"}, []string{"c.c"}),
+						},
+					},
+				},
+				"C": LabelListAttribute{
+					ConfigurableValues: configurableLabelLists{
+						ArchConfigurationAxis: labelListSelectValues{
+							"x86":    makeLabelList([]string{"c.c"}, []string{}),
+							"x86_64": makeLabelList([]string{}, []string{"c.c"}),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "error for multiple partitions same value",
+			ctx:  &OtherModuleTestContext{},
+			labelList: LabelListAttribute{
+				Value: makeLabelList([]string{"a.a", "b.b", "c.c", "d.d", "e.e"}, []string{}),
+			},
+			filters: LabelPartitions{
+				"A":       LabelPartition{Extensions: []string{".a"}},
+				"other A": LabelPartition{Extensions: []string{".a"}},
+			},
+			expected:       PartitionToLabelListAttribute{},
+			expectedErrMsg: proptools.StringPtr(`"a.a" was found in multiple partitions:`),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := PartitionLabelListAttribute(tc.ctx, &tc.labelList, tc.filters)
+
+			if hasErrors, expectsErr := len(tc.ctx.errors) > 0, tc.expectedErrMsg != nil; hasErrors != expectsErr {
+				t.Errorf("Unexpected error(s): %q, expected: %q", tc.ctx.errors, *tc.expectedErrMsg)
+			} else if tc.expectedErrMsg != nil {
+				found := false
+				for _, err := range tc.ctx.errors {
+					if strings.Contains(err, *tc.expectedErrMsg) {
+						found = true
+						break
+					}
+				}
+
+				if !found {
+					t.Errorf("Expected error message: %q, got %q", *tc.expectedErrMsg, tc.ctx.errors)
+				}
+				return
+			}
+
+			if len(tc.expected) != len(got) {
+				t.Errorf("Expected %d partitions, got %d partitions", len(tc.expected), len(got))
+			}
+			for partition, expectedLla := range tc.expected {
+				gotLla, ok := got[partition]
+				if !ok {
+					t.Errorf("Expected partition %q, but it was not found %v", partition, got)
+					continue
+				}
+				expectedLabelList := expectedLla.Value
+				gotLabelList := gotLla.Value
+				if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
+					t.Errorf("Expected no config includes %v, got %v", expectedLabelList.Includes, gotLabelList.Includes)
+				}
+				expectedAxes := expectedLla.SortedConfigurationAxes()
+				gotAxes := gotLla.SortedConfigurationAxes()
+				if !reflect.DeepEqual(expectedAxes, gotAxes) {
+					t.Errorf("Expected axes %v, got %v (%#v)", expectedAxes, gotAxes, gotLla)
+				}
+				for _, axis := range expectedLla.SortedConfigurationAxes() {
+					if _, exists := gotLla.ConfigurableValues[axis]; !exists {
+						t.Errorf("Expected %s to be a supported axis, but it was not found", axis)
+					}
+					if expected, got := expectedLla.ConfigurableValues[axis], gotLla.ConfigurableValues[axis]; len(expected) != len(got) {
+						t.Errorf("For axis %q: expected configs %v, got %v", axis, expected, got)
+					}
+					for config, expectedLabelList := range expectedLla.ConfigurableValues[axis] {
+						gotLabelList, exists := gotLla.ConfigurableValues[axis][config]
+						if !exists {
+							t.Errorf("Expected %s to be a supported config, but config was not found", config)
+							continue
+						}
+						if !reflect.DeepEqual(expectedLabelList.Includes, gotLabelList.Includes) {
+							t.Errorf("Expected %s %s includes %v, got %v", axis, config, expectedLabelList.Includes, gotLabelList.Includes)
+						}
+					}
+				}
+			}
+		})
+	}
+}
+
+func TestDeduplicateAxesFromBase(t *testing.T) {
+	attr := StringListAttribute{
+		Value: []string{
+			"all_include",
+			"arm_include",
+			"android_include",
+			"linux_x86_include",
+		},
+		ConfigurableValues: configurableStringLists{
+			ArchConfigurationAxis: stringListSelectValues{
+				"arm": []string{"arm_include"},
+				"x86": []string{"x86_include"},
+			},
+			OsConfigurationAxis: stringListSelectValues{
+				"android": []string{"android_include"},
+				"linux":   []string{"linux_include"},
+			},
+			OsArchConfigurationAxis: stringListSelectValues{
+				"linux_x86": {"linux_x86_include"},
+			},
+			ProductVariableConfigurationAxis("a"): stringListSelectValues{
+				"a": []string{"not_in_value"},
+			},
+		},
+	}
+
+	attr.DeduplicateAxesFromBase()
+
+	expectedBaseIncludes := []string{
+		"all_include",
+		"arm_include",
+		"android_include",
+		"linux_x86_include",
+	}
+	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value) {
+		t.Errorf("Expected Value includes %q, got %q", attr.Value, expectedBaseIncludes)
+	}
+	expectedConfiguredIncludes := configurableStringLists{
+		ArchConfigurationAxis: stringListSelectValues{
+			"x86": []string{"x86_include"},
+		},
+		OsConfigurationAxis: stringListSelectValues{
+			"linux": []string{"linux_include"},
+		},
+		OsArchConfigurationAxis: stringListSelectValues{},
+		ProductVariableConfigurationAxis("a"): stringListSelectValues{
+			"a": []string{"not_in_value"},
+		},
+	}
+	for _, axis := range attr.SortedConfigurationAxes() {
+		if _, ok := expectedConfiguredIncludes[axis]; !ok {
+			t.Errorf("Found unexpected axis %s", axis)
+			continue
+		}
+		expectedForAxis := expectedConfiguredIncludes[axis]
+		gotForAxis := attr.ConfigurableValues[axis]
+		if len(expectedForAxis) != len(gotForAxis) {
+			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
+		}
+		for config, value := range gotForAxis {
+			if expected, ok := expectedForAxis[config]; ok {
+				if !reflect.DeepEqual(expected, value) {
+					t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value)
+				}
+			} else {
+				t.Errorf("Got unexpected config %q for %s", config, axis)
+			}
+		}
+	}
+}
diff --git a/bazel/testing.go b/bazel/testing.go
new file mode 100644
index 0000000..9a43b61
--- /dev/null
+++ b/bazel/testing.go
@@ -0,0 +1,105 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bazel
+
+import (
+	"fmt"
+
+	"github.com/google/blueprint"
+)
+
+// TestModuleInfo implements blueprint.Module interface with sufficient information to mock a subset of
+// a blueprint ModuleContext
+type TestModuleInfo struct {
+	ModuleName string
+	Typ        string
+	Dir        string
+}
+
+// Name returns name for testModuleInfo -- required to implement blueprint.Module
+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) 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{}
+
+// 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.ModuleName == name {
+			return m, true
+		}
+	}
+	return TestModuleInfo{}, false
+}
+
+// testModuleInfo returns the testModuleInfo corresponding to a blueprint.Module if it exists in omc
+func (omc *OtherModuleTestContext) testModuleInfo(m blueprint.Module) (TestModuleInfo, bool) {
+	mi, ok := m.(TestModuleInfo)
+	if !ok {
+		return TestModuleInfo{}, false
+	}
+	for _, other := range omc.Modules {
+		if other.equals(mi) {
+			return mi, true
+		}
+	}
+	return TestModuleInfo{}, false
+}
+
+// OtherModuleType returns type of m if it exists in omc
+func (omc *OtherModuleTestContext) OtherModuleType(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.Typ
+	}
+	return ""
+}
+
+// OtherModuleName returns name of m if it exists in omc
+func (omc *OtherModuleTestContext) OtherModuleName(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.ModuleName
+	}
+	return ""
+}
+
+// OtherModuleDir returns dir of m if it exists in omc
+func (omc *OtherModuleTestContext) OtherModuleDir(m blueprint.Module) string {
+	if mi, ok := omc.testModuleInfo(m); ok {
+		return mi.Dir
+	}
+	return ""
+}
+
+func (omc *OtherModuleTestContext) ModuleErrorf(format string, args ...interface{}) {
+	omc.errors = append(omc.errors, fmt.Sprintf(format, args...))
+}
+
+// Ensure otherModuleTestContext implements OtherModuleContext
+var _ OtherModuleContext = &OtherModuleTestContext{}
diff --git a/bloaty/bloaty_merger.py b/bloaty/bloaty_merger.py
index 1034462..46ce57f 100644
--- a/bloaty/bloaty_merger.py
+++ b/bloaty/bloaty_merger.py
@@ -24,58 +24,63 @@
 import csv
 import gzip
 
+# pylint: disable=import-error
 import ninja_rsp
 
 import file_sections_pb2
 
 BLOATY_EXTENSION = ".bloaty.csv"
 
+
 def parse_csv(path):
-  """Parses a Bloaty-generated CSV file into a protobuf.
+    """Parses a Bloaty-generated CSV file into a protobuf.
 
-  Args:
-    path: The filepath to the CSV file, relative to $ANDROID_TOP.
+    Args:
+      path: The filepath to the CSV file, relative to $ANDROID_TOP.
 
-  Returns:
-    A file_sections_pb2.File if the file was found; None otherwise.
-  """
-  file_proto = None
-  with open(path, newline='') as csv_file:
-    file_proto = file_sections_pb2.File()
-    if path.endswith(BLOATY_EXTENSION):
-      file_proto.path = path[:-len(BLOATY_EXTENSION)]
-    section_reader = csv.DictReader(csv_file)
-    for row in section_reader:
-      section = file_proto.sections.add()
-      section.name = row["sections"]
-      section.vm_size = int(row["vmsize"])
-      section.file_size = int(row["filesize"])
-  return file_proto
+    Returns:
+      A file_sections_pb2.File if the file was found; None otherwise.
+    """
+    file_proto = None
+    with open(path, newline='') as csv_file:
+        file_proto = file_sections_pb2.File()
+        if path.endswith(BLOATY_EXTENSION):
+            file_proto.path = path[: -len(BLOATY_EXTENSION)]
+        section_reader = csv.DictReader(csv_file)
+        for row in section_reader:
+            section = file_proto.sections.add()
+            section.name = row["sections"]
+            section.vm_size = int(row["vmsize"])
+            section.file_size = int(row["filesize"])
+    return file_proto
+
 
 def create_file_size_metrics(input_list, output_proto):
-  """Creates a FileSizeMetrics proto from a list of CSV files.
+    """Creates a FileSizeMetrics proto from a list of CSV files.
 
-  Args:
-    input_list: The path to the file which contains the list of CSV files. Each
-        filepath is separated by a space.
-    output_proto: The path for the output protobuf. It will be compressed using
-        gzip.
-  """
-  metrics = file_sections_pb2.FileSizeMetrics()
-  reader = ninja_rsp.NinjaRspFileReader(input_list)
-  for csv_path in reader:
-    file_proto = parse_csv(csv_path)
-    if file_proto:
-      metrics.files.append(file_proto)
-  with gzip.open(output_proto, "wb") as output:
-    output.write(metrics.SerializeToString())
+    Args:
+      input_list: The path to the file which contains the list of CSV files.
+          Each filepath is separated by a space.
+      output_proto: The path for the output protobuf. It will be compressed
+          using gzip.
+    """
+    metrics = file_sections_pb2.FileSizeMetrics()
+    reader = ninja_rsp.NinjaRspFileReader(input_list)
+    for csv_path in reader:
+        file_proto = parse_csv(csv_path)
+        if file_proto:
+            metrics.files.append(file_proto)
+    with gzip.open(output_proto, "wb") as output:
+        output.write(metrics.SerializeToString())
+
 
 def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument("input_list_file", help="List of bloaty csv files.")
-  parser.add_argument("output_proto", help="Output proto.")
-  args = parser.parse_args()
-  create_file_size_metrics(args.input_list_file, args.output_proto)
+    parser = argparse.ArgumentParser()
+    parser.add_argument("input_list_file", help="List of bloaty csv files.")
+    parser.add_argument("output_proto", help="Output proto.")
+    args = parser.parse_args()
+    create_file_size_metrics(args.input_list_file, args.output_proto)
+
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/bloaty/bloaty_merger_test.py b/bloaty/bloaty_merger_test.py
index 9de049a..83680b9 100644
--- a/bloaty/bloaty_merger_test.py
+++ b/bloaty/bloaty_merger_test.py
@@ -14,6 +14,7 @@
 import gzip
 import unittest
 
+# pylint: disable=import-error
 from pyfakefs import fake_filesystem_unittest
 
 import bloaty_merger
@@ -21,46 +22,46 @@
 
 
 class BloatyMergerTestCase(fake_filesystem_unittest.TestCase):
-  def setUp(self):
-    self.setUpPyfakefs()
+    def setUp(self):
+        self.setUpPyfakefs()
 
-  def test_parse_csv(self):
-    csv_content = "sections,vmsize,filesize\nsection1,2,3\n"
-    self.fs.create_file("file1.bloaty.csv", contents=csv_content)
-    pb = bloaty_merger.parse_csv("file1.bloaty.csv")
-    self.assertEqual(pb.path, "file1")
-    self.assertEqual(len(pb.sections), 1)
-    s = pb.sections[0]
-    self.assertEqual(s.name, "section1")
-    self.assertEqual(s.vm_size, 2)
-    self.assertEqual(s.file_size, 3)
+    def test_parse_csv(self):
+        csv_content = "sections,vmsize,filesize\nsection1,2,3\n"
+        self.fs.create_file("file1.bloaty.csv", contents=csv_content)
+        pb = bloaty_merger.parse_csv("file1.bloaty.csv")
+        self.assertEqual(pb.path, "file1")
+        self.assertEqual(len(pb.sections), 1)
+        s = pb.sections[0]
+        self.assertEqual(s.name, "section1")
+        self.assertEqual(s.vm_size, 2)
+        self.assertEqual(s.file_size, 3)
 
-  def test_missing_file(self):
-    with self.assertRaises(FileNotFoundError):
-      bloaty_merger.parse_csv("missing.bloaty.csv")
+    def test_missing_file(self):
+        with self.assertRaises(FileNotFoundError):
+            bloaty_merger.parse_csv("missing.bloaty.csv")
 
-  def test_malformed_csv(self):
-    csv_content = "header1,heaVder2,header3\n4,5,6\n"
-    self.fs.create_file("file1.bloaty.csv", contents=csv_content)
-    with self.assertRaises(KeyError):
-      bloaty_merger.parse_csv("file1.bloaty.csv")
+    def test_malformed_csv(self):
+        csv_content = "header1,heaVder2,header3\n4,5,6\n"
+        self.fs.create_file("file1.bloaty.csv", contents=csv_content)
+        with self.assertRaises(KeyError):
+            bloaty_merger.parse_csv("file1.bloaty.csv")
 
-  def test_create_file_metrics(self):
-    file_list = "file1.bloaty.csv file2.bloaty.csv"
-    file1_content = "sections,vmsize,filesize\nsection1,2,3\nsection2,7,8"
-    file2_content = "sections,vmsize,filesize\nsection1,4,5\n"
+    def test_create_file_metrics(self):
+        file_list = "file1.bloaty.csv file2.bloaty.csv"
+        file1_content = "sections,vmsize,filesize\nsection1,2,3\nsection2,7,8"
+        file2_content = "sections,vmsize,filesize\nsection1,4,5\n"
 
-    self.fs.create_file("files.lst", contents=file_list)
-    self.fs.create_file("file1.bloaty.csv", contents=file1_content)
-    self.fs.create_file("file2.bloaty.csv", contents=file2_content)
+        self.fs.create_file("files.lst", contents=file_list)
+        self.fs.create_file("file1.bloaty.csv", contents=file1_content)
+        self.fs.create_file("file2.bloaty.csv", contents=file2_content)
 
-    bloaty_merger.create_file_size_metrics("files.lst", "output.pb.gz")
+        bloaty_merger.create_file_size_metrics("files.lst", "output.pb.gz")
 
-    metrics = file_sections_pb2.FileSizeMetrics()
-    with gzip.open("output.pb.gz", "rb") as output:
-      metrics.ParseFromString(output.read())
+        metrics = file_sections_pb2.FileSizeMetrics()
+        with gzip.open("output.pb.gz", "rb") as output:
+            metrics.ParseFromString(output.read())
 
 
 if __name__ == '__main__':
-  suite = unittest.TestLoader().loadTestsFromTestCase(BloatyMergerTestCase)
-  unittest.TextTestRunner(verbosity=2).run(suite)
+    suite = unittest.TestLoader().loadTestsFromTestCase(BloatyMergerTestCase)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/bootstrap.bash b/bootstrap.bash
index 4db8539..726692a 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -15,9 +15,9 @@
 # limitations under the License.
 
 echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
-echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Use `m --soong-only` with a standalone OUT_DIR instead.' >&2
 echo 'Without envsetup.sh, use:' >&2
-echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '  build/soong/soong_ui.bash --make-mode --soong-only' >&2
 echo '======================================================' >&2
 exit 1
 
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 3abbc4c..e0ce194 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -18,23 +18,51 @@
     ],
     deps: [
         "soong-android",
+        "soong-android-allowlists",
+        "soong-android-soongconfig",
+        "soong-shared",
+        "soong-apex",
         "soong-bazel",
         "soong-cc",
         "soong-cc-config",
+        "soong-etc",
         "soong-genrule",
         "soong-python",
         "soong-sh",
+        "soong-starlark-format",
+        "soong-ui-metrics",
     ],
     testSrcs: [
+        "android_app_certificate_conversion_test.go",
+        "android_app_conversion_test.go",
+        "apex_conversion_test.go",
+        "apex_key_conversion_test.go",
         "build_conversion_test.go",
         "bzl_conversion_test.go",
+        "cc_binary_conversion_test.go",
         "cc_library_conversion_test.go",
         "cc_library_headers_conversion_test.go",
+        "cc_library_shared_conversion_test.go",
         "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
+        "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",
+        "java_binary_host_conversion_test.go",
+        "java_import_conversion_test.go",
+        "java_library_conversion_test.go",
+        "java_library_host_conversion_test.go",
+        "java_plugin_conversion_test.go",
+        "java_proto_conversion_test.go",
+        "performance_test.go",
+        "prebuilt_etc_conversion_test.go",
         "python_binary_conversion_test.go",
+        "python_library_conversion_test.go",
         "sh_conversion_test.go",
+        "soong_config_module_type_conversion_test.go",
         "testing.go",
     ],
     pluginFor: [
diff --git a/bp2build/android_app_certificate_conversion_test.go b/bp2build/android_app_certificate_conversion_test.go
new file mode 100644
index 0000000..035a352
--- /dev/null
+++ b/bp2build/android_app_certificate_conversion_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+
+	"testing"
+)
+
+func runAndroidAppCertificateTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerAndroidAppCertificateModuleTypes, tc)
+}
+
+func registerAndroidAppCertificateModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestAndroidAppCertificateSimple(t *testing.T) {
+	runAndroidAppCertificateTestCase(t, bp2buildTestCase{
+		description:                "Android app certificate - simple example",
+		moduleTypeUnderTest:        "android_app_certificate",
+		moduleTypeUnderTestFactory: java.AndroidAppCertificateFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+android_app_certificate {
+        name: "com.android.apogee.cert",
+        certificate: "chamber_of_secrets_dir",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_app_certificate", "com.android.apogee.cert", attrNameToString{
+				"certificate": `"chamber_of_secrets_dir"`,
+			}),
+		}})
+}
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
new file mode 100644
index 0000000..3824586
--- /dev/null
+++ b/bp2build/android_app_conversion_test.go
@@ -0,0 +1,130 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+
+	"testing"
+)
+
+func runAndroidAppTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerAndroidAppModuleTypes, tc)
+}
+
+func registerAndroidAppModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestMinimalAndroidApp(t *testing.T) {
+	runAndroidAppTestCase(t, bp2buildTestCase{
+		description:                "Android app - simple example",
+		moduleTypeUnderTest:        "android_app",
+		moduleTypeUnderTestFactory: java.AndroidAppFactory,
+		filesystem: map[string]string{
+			"app.java":            "",
+			"res/res.png":         "",
+			"AndroidManifest.xml": "",
+		},
+		blueprint: `
+android_app {
+        name: "TestApp",
+        srcs: ["app.java"],
+        sdk_version: "current",
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_binary", "TestApp", attrNameToString{
+				"srcs":           `["app.java"]`,
+				"manifest":       `"AndroidManifest.xml"`,
+				"resource_files": `["res/res.png"]`,
+			}),
+		}})
+}
+
+func TestAndroidAppAllSupportedFields(t *testing.T) {
+	runAndroidAppTestCase(t, bp2buildTestCase{
+		description:                "Android app - all supported fields",
+		moduleTypeUnderTest:        "android_app",
+		moduleTypeUnderTestFactory: java.AndroidAppFactory,
+		filesystem: map[string]string{
+			"app.java":                     "",
+			"resa/res.png":                 "",
+			"resb/res.png":                 "",
+			"manifest/AndroidManifest.xml": "",
+		},
+		blueprint: simpleModuleDoNotConvertBp2build("android_app", "static_lib_dep") + `
+android_app {
+        name: "TestApp",
+        srcs: ["app.java"],
+        sdk_version: "current",
+        package_name: "com.google",
+        resource_dirs: ["resa", "resb"],
+        manifest: "manifest/AndroidManifest.xml",
+        static_libs: ["static_lib_dep"]
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_binary", "TestApp", attrNameToString{
+				"srcs":     `["app.java"]`,
+				"manifest": `"manifest/AndroidManifest.xml"`,
+				"resource_files": `[
+        "resa/res.png",
+        "resb/res.png",
+    ]`,
+				"custom_package": `"com.google"`,
+				"deps":           `[":static_lib_dep"]`,
+			}),
+		}})
+}
+
+func TestAndroidAppArchVariantSrcs(t *testing.T) {
+	runAndroidAppTestCase(t, bp2buildTestCase{
+		description:                "Android app - arch variant srcs",
+		moduleTypeUnderTest:        "android_app",
+		moduleTypeUnderTestFactory: java.AndroidAppFactory,
+		filesystem: map[string]string{
+			"arm.java":            "",
+			"x86.java":            "",
+			"res/res.png":         "",
+			"AndroidManifest.xml": "",
+		},
+		blueprint: `
+android_app {
+        name: "TestApp",
+        sdk_version: "current",
+        arch: {
+			arm: {
+				srcs: ["arm.java"],
+			},
+			x86: {
+				srcs: ["x86.java"],
+			}
+		}
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("android_binary", "TestApp", attrNameToString{
+				"srcs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.java"],
+        "//build/bazel/platforms/arch:x86": ["x86.java"],
+        "//conditions:default": [],
+    })`,
+				"manifest":       `"AndroidManifest.xml"`,
+				"resource_files": `["res/res.png"]`,
+			}),
+		}})
+}
diff --git a/bp2build/apex_conversion_test.go b/bp2build/apex_conversion_test.go
new file mode 100644
index 0000000..9057189
--- /dev/null
+++ b/bp2build/apex_conversion_test.go
@@ -0,0 +1,365 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/apex"
+	"android/soong/cc"
+	"android/soong/java"
+	"android/soong/sh"
+
+	"testing"
+)
+
+func runApexTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerApexModuleTypes, tc)
+}
+
+func registerApexModuleTypes(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)
+}
+
+func TestApexBundleSimple(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - example with all props",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex_key {
+	name: "com.android.apogee.key",
+	public_key: "com.android.apogee.avbpubkey",
+	private_key: "com.android.apogee.pem",
+	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 },
+}
+
+// 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 },
+}
+
+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",
+	],
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"android_manifest": `"ApogeeAndroidManifest.xml"`,
+				"binaries": `[
+        ":cc_binary_1",
+        ":sh_binary_2",
+    ]`,
+				"certificate":     `":com.android.apogee.certificate"`,
+				"file_contexts":   `":com.android.apogee-file_contexts"`,
+				"installable":     "False",
+				"key":             `":com.android.apogee.key"`,
+				"manifest":        `"apogee_manifest.json"`,
+				"min_sdk_version": `"29"`,
+				"native_shared_libs_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": `[
+        ":pretend_prebuilt_1",
+        ":pretend_prebuilt_2",
+    ]`,
+				"updatable":    "False",
+				"compressible": "False",
+			}),
+		}})
+}
+
+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"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"native_shared_libs_32": `[
+        ":native_shared_lib_1",
+        ":native_shared_lib_3",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [":native_shared_lib_2"],
+        "//build/bazel/platforms/arch:x86": [":native_shared_lib_2"],
+        "//conditions:default": [],
+    })`,
+				"native_shared_libs_64": `select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
+
+func TestApexBundleCompileMultilibFirst(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - example with compile_multilib=first",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint:                  createMultilibBlueprint("first"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"native_shared_libs_32": `select({
+        "//build/bazel/platforms/arch:arm": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_3",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_3",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+				"native_shared_libs_64": `select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
+
+func TestApexBundleCompileMultilib32(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - example with compile_multilib=32",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint:                  createMultilibBlueprint("32"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"native_shared_libs_32": `[
+        ":native_shared_lib_1",
+        ":native_shared_lib_3",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [":native_shared_lib_2"],
+        "//build/bazel/platforms/arch:x86": [":native_shared_lib_2"],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
+
+func TestApexBundleCompileMultilib64(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - example with compile_multilib=64",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint:                  createMultilibBlueprint("64"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+				"native_shared_libs_64": `select({
+        "//build/bazel/platforms/arch:arm64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            ":native_shared_lib_1",
+            ":native_shared_lib_4",
+            ":native_shared_lib_2",
+        ],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
+
+func TestApexBundleDefaultPropertyValues(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - default property values",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex {
+	name: "com.android.apogee",
+	manifest: "apogee_manifest.json",
+}
+`,
+		expectedBazelTargets: []string{makeBazelTarget("apex", "com.android.apogee", attrNameToString{
+			"manifest": `"apogee_manifest.json"`,
+		}),
+		}})
+}
+
+func TestApexBundleHasBazelModuleProps(t *testing.T) {
+	runApexTestCase(t, bp2buildTestCase{
+		description:                "apex - has bazel module props",
+		moduleTypeUnderTest:        "apex",
+		moduleTypeUnderTestFactory: apex.BundleFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex {
+	name: "apogee",
+	manifest: "manifest.json",
+	bazel_module: { bp2build_available: true },
+}
+`,
+		expectedBazelTargets: []string{makeBazelTarget("apex", "apogee", attrNameToString{
+			"manifest": `"manifest.json"`,
+		}),
+		}})
+}
+
+func createMultilibBlueprint(compile_multilib string) string {
+	return `
+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: "native_shared_lib_3",
+	bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+	name: "native_shared_lib_4",
+	bazel_module: { bp2build_available: false },
+}
+
+apex {
+	name: "com.android.apogee",
+	compile_multilib: "` + compile_multilib + `",
+	multilib: {
+		both: {
+			native_shared_libs: [
+				"native_shared_lib_1",
+			],
+		},
+		first: {
+			native_shared_libs: [
+				"native_shared_lib_2",
+			],
+		},
+		lib32: {
+			native_shared_libs: [
+				"native_shared_lib_3",
+			],
+		},
+		lib64: {
+			native_shared_libs: [
+				"native_shared_lib_4",
+			],
+		},
+	},
+}`
+}
diff --git a/bp2build/apex_key_conversion_test.go b/bp2build/apex_key_conversion_test.go
new file mode 100644
index 0000000..1d949901
--- /dev/null
+++ b/bp2build/apex_key_conversion_test.go
@@ -0,0 +1,50 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/apex"
+
+	"testing"
+)
+
+func runApexKeyTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerApexKeyModuleTypes, tc)
+}
+
+func registerApexKeyModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestApexKeySimple(t *testing.T) {
+	runApexKeyTestCase(t, bp2buildTestCase{
+		description:                "apex key - simple example",
+		moduleTypeUnderTest:        "apex_key",
+		moduleTypeUnderTestFactory: apex.ApexKeyFactory,
+		filesystem:                 map[string]string{},
+		blueprint: `
+apex_key {
+        name: "com.android.apogee.key",
+        public_key: "com.android.apogee.avbpubkey",
+        private_key: "com.android.apogee.pem",
+}
+`,
+		expectedBazelTargets: []string{makeBazelTarget("apex_key", "com.android.apogee.key", attrNameToString{
+			"private_key": `"com.android.apogee.pem"`,
+			"public_key":  `"com.android.apogee.avbpubkey"`,
+		}),
+		}})
+}
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 59c5acd..b0c3899 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -15,9 +15,12 @@
 package bp2build
 
 import (
-	"android/soong/android"
 	"fmt"
 	"os"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/bazel"
 )
 
 // Codegen is the backend of bp2build. The code generator is responsible for
@@ -28,14 +31,22 @@
 	bp2buildDir := android.PathForOutput(ctx, "bp2build")
 	android.RemoveAllOutputDir(bp2buildDir)
 
-	buildToTargets, metrics := GenerateBazelTargets(ctx, true)
-	bp2buildFiles := CreateBazelFiles(nil, buildToTargets, ctx.mode)
+	res, errs := GenerateBazelTargets(ctx, true)
+	if len(errs) > 0 {
+		errMsgs := make([]string, len(errs))
+		for i, err := range errs {
+			errMsgs[i] = fmt.Sprintf("%q", err)
+		}
+		fmt.Printf("ERROR: Encountered %d error(s): \nERROR: %s", len(errs), strings.Join(errMsgs, "\n"))
+		os.Exit(1)
+	}
+	bp2buildFiles := CreateBazelFiles(nil, res.buildFileToTargets, ctx.mode)
 	writeFiles(ctx, bp2buildDir, bp2buildFiles)
 
-	soongInjectionDir := android.PathForOutput(ctx, "soong_injection")
-	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles())
+	soongInjectionDir := android.PathForOutput(ctx, bazel.SoongInjectionDirName)
+	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles(ctx.Config(), res.metrics))
 
-	return metrics
+	return res.metrics
 }
 
 // Get the output directory and create it if it doesn't exist.
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index bddc524..a96a3fc 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -14,13 +14,21 @@
 
 package bp2build
 
+/*
+For shareable/common functionality for conversion from soong-module to build files
+for queryview/bp2build
+*/
+
 import (
-	"android/soong/android"
-	"android/soong/bazel"
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 
+	"android/soong/android"
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -31,9 +39,11 @@
 
 type BazelTarget struct {
 	name            string
+	packageName     string
 	content         string
 	ruleClass       string
 	bzlLoadLocation string
+	handcrafted     bool
 }
 
 // IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
@@ -42,15 +52,60 @@
 	return t.bzlLoadLocation != ""
 }
 
+// Label is the fully qualified Bazel label constructed from the BazelTarget's
+// package name and target name.
+func (t BazelTarget) Label() string {
+	if t.packageName == "." {
+		return "//:" + t.name
+	} else {
+		return "//" + t.packageName + ":" + t.name
+	}
+}
+
 // BazelTargets is a typedef for a slice of BazelTarget objects.
 type BazelTargets []BazelTarget
 
+// HasHandcraftedTargetsreturns true if a set of bazel targets contain
+// handcrafted ones.
+func (targets BazelTargets) hasHandcraftedTargets() bool {
+	for _, target := range targets {
+		if target.handcrafted {
+			return true
+		}
+	}
+	return false
+}
+
+// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
+func (targets BazelTargets) sort() {
+	sort.Slice(targets, func(i, j int) bool {
+		if targets[i].handcrafted != targets[j].handcrafted {
+			// Handcrafted targets will be generated after the bp2build generated targets.
+			return targets[j].handcrafted
+		}
+		// This will cover all bp2build generated targets.
+		return targets[i].name < targets[j].name
+	})
+}
+
 // String returns the string representation of BazelTargets, without load
 // statements (use LoadStatements for that), since the targets are usually not
 // adjacent to the load statements at the top of the BUILD file.
 func (targets BazelTargets) String() string {
 	var res string
 	for i, target := range targets {
+		// There is only at most 1 handcrafted "target", because its contents
+		// represent the entire BUILD file content from the tree. See
+		// build_conversion.go#getHandcraftedBuildContent for more information.
+		//
+		// Add a header to make it easy to debug where the handcrafted targets
+		// are in a generated BUILD file.
+		if target.handcrafted {
+			res += "# -----------------------------\n"
+			res += "# Section: Handcrafted targets. \n"
+			res += "# -----------------------------\n\n"
+		}
+
 		res += target.content
 		if i != len(targets)-1 {
 			res += "\n\n"
@@ -99,10 +154,11 @@
 }
 
 type CodegenContext struct {
-	config         android.Config
-	context        android.Context
-	mode           CodegenMode
-	additionalDeps []string
+	config             android.Config
+	context            android.Context
+	mode               CodegenMode
+	additionalDeps     []string
+	unconvertedDepMode unconvertedDepsMode
 }
 
 func (c *CodegenContext) Mode() CodegenMode {
@@ -127,6 +183,16 @@
 	QueryView
 )
 
+type unconvertedDepsMode int
+
+const (
+	// Include a warning in conversion metrics about converted modules with unconverted direct deps
+	warnUnconvertedDeps unconvertedDepsMode = iota
+	// Error and fail conversion if encountering a module with unconverted direct deps
+	// Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
+	errorModulesUnconvertedDeps
+)
+
 func (mode CodegenMode) String() string {
 	switch mode {
 	case Bp2Build:
@@ -157,10 +223,15 @@
 // NewCodegenContext creates a wrapper context that conforms to PathContext for
 // writing BUILD files in the output directory.
 func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) *CodegenContext {
+	var unconvertedDeps unconvertedDepsMode
+	if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
+		unconvertedDeps = errorModulesUnconvertedDeps
+	}
 	return &CodegenContext{
-		context: context,
-		config:  config,
-		mode:    mode,
+		context:            context,
+		config:             config,
+		mode:               mode,
+		unconvertedDepMode: unconvertedDeps,
 	}
 }
 
@@ -169,55 +240,111 @@
 func propsToAttributes(props map[string]string) string {
 	var attributes string
 	for _, propName := range android.SortedStringKeys(props) {
-		if shouldGenerateAttribute(propName) {
-			attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
-		}
+		attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
 	}
 	return attributes
 }
 
-func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (map[string]BazelTargets, CodegenMetrics) {
+type conversionResults struct {
+	buildFileToTargets map[string]BazelTargets
+	metrics            CodegenMetrics
+}
+
+func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
+	return r.buildFileToTargets
+}
+
+func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
 	buildFileToTargets := make(map[string]BazelTargets)
 	buildFileToAppend := make(map[string]bool)
 
 	// Simple metrics tracking for bp2build
 	metrics := CodegenMetrics{
-		RuleClassCount: make(map[string]int),
+		ruleClassCount:           make(map[string]uint64),
+		convertedModuleTypeCount: make(map[string]uint64),
+		totalModuleTypeCount:     make(map[string]uint64),
 	}
 
 	dirs := make(map[string]bool)
 
+	var errs []error
+
 	bpCtx := ctx.Context()
 	bpCtx.VisitAllModules(func(m blueprint.Module) {
 		dir := bpCtx.ModuleDir(m)
+		moduleType := bpCtx.ModuleType(m)
 		dirs[dir] = true
 
-		var t BazelTarget
+		var targets []BazelTarget
 
 		switch ctx.Mode() {
 		case Bp2Build:
+			// There are two main ways of converting a Soong module to Bazel:
+			// 1) Manually handcrafting a Bazel target and associating the module with its label
+			// 2) Automatically generating with bp2build converters
+			//
+			// bp2build converters are used for the majority of modules.
 			if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
-				metrics.handCraftedTargetCount += 1
-				metrics.TotalModuleCount += 1
+				// Handle modules converted to handcrafted targets.
+				//
+				// Since these modules are associated with some handcrafted
+				// target in a BUILD file, we simply append the entire contents
+				// of that BUILD file to the generated BUILD file.
+				//
+				// The append operation is only done once, even if there are
+				// multiple modules from the same directory associated to
+				// targets in the same BUILD file (or package).
+
+				// Log the module.
+				metrics.AddConvertedModule(m, moduleType, Handcrafted)
+
 				pathToBuildFile := getBazelPackagePath(b)
-				// We are using the entire contents of handcrafted build file, so if multiple targets within
-				// a package have handcrafted targets, we only want to include the contents one time.
 				if _, exists := buildFileToAppend[pathToBuildFile]; exists {
+					// Append the BUILD file content once per package, at most.
 					return
 				}
-				var err error
-				t, err = getHandcraftedBuildContent(ctx, b, pathToBuildFile)
+				t, err := getHandcraftedBuildContent(ctx, b, pathToBuildFile)
 				if err != nil {
-					panic(fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
+					errs = append(errs, fmt.Errorf("Error converting %s: %s", bpCtx.ModuleName(m), err))
+					return
 				}
+				targets = append(targets, t)
 				// TODO(b/181575318): currently we append the whole BUILD file, let's change that to do
 				// something more targeted based on the rule type and target
 				buildFileToAppend[pathToBuildFile] = true
-			} else if btm, ok := m.(android.BazelTargetModule); ok {
-				t = generateBazelTarget(bpCtx, m, btm)
-				metrics.RuleClassCount[t.ruleClass] += 1
+			} else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
+				// Handle modules converted to generated targets.
+
+				// Log the module.
+				metrics.AddConvertedModule(aModule, moduleType, Generated)
+
+				// Handle modules with unconverted deps. By default, emit a warning.
+				if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
+					msg := fmt.Sprintf("%q depends on unconverted modules: %s", m.Name(), strings.Join(unconvertedDeps, ", "))
+					if ctx.unconvertedDepMode == warnUnconvertedDeps {
+						metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
+					} else if ctx.unconvertedDepMode == errorModulesUnconvertedDeps {
+						errs = append(errs, fmt.Errorf(msg))
+						return
+					}
+				}
+				if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 {
+					msg := fmt.Sprintf("%q depends on missing modules: %s", m.Name(), strings.Join(unconvertedDeps, ", "))
+					if ctx.unconvertedDepMode == warnUnconvertedDeps {
+						metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg)
+					} else if ctx.unconvertedDepMode == errorModulesUnconvertedDeps {
+						errs = append(errs, fmt.Errorf(msg))
+						return
+					}
+				}
+				targets = generateBazelTargets(bpCtx, aModule)
+				for _, t := range targets {
+					// A module can potentially generate more than 1 Bazel
+					// target, each of a different rule class.
+					metrics.IncrementRuleClassCount(t.ruleClass)
+				}
 			} else {
-				metrics.TotalModuleCount += 1
+				metrics.AddUnconvertedModule(moduleType)
 				return
 			}
 		case QueryView:
@@ -227,13 +354,20 @@
 				// be mapped cleanly to a bazel label.
 				return
 			}
-			t = generateSoongModuleTarget(bpCtx, m)
+			t := generateSoongModuleTarget(bpCtx, m)
+			targets = append(targets, t)
 		default:
-			panic(fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
+			errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
+			return
 		}
 
-		buildFileToTargets[dir] = append(buildFileToTargets[dir], t)
+		buildFileToTargets[dir] = append(buildFileToTargets[dir], targets...)
 	})
+
+	if len(errs) > 0 {
+		return conversionResults{}, errs
+	}
+
 	if generateFilegroups {
 		// Add a filegroup target that exposes all sources in the subtree of this package
 		// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
@@ -246,7 +380,10 @@
 		}
 	}
 
-	return buildFileToTargets, metrics
+	return conversionResults{
+		buildFileToTargets: buildFileToTargets,
+		metrics:            metrics,
+	}, errs
 }
 
 func getBazelPackagePath(b android.Bazelable) string {
@@ -267,25 +404,45 @@
 	}
 	// TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
 	return BazelTarget{
-		content: c,
+		content:     c,
+		handcrafted: true,
 	}, nil
 }
 
-func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module, btm android.BazelTargetModule) BazelTarget {
-	ruleClass := btm.RuleClass()
-	bzlLoadLocation := btm.BzlLoadLocation()
+func generateBazelTargets(ctx bpToBuildContext, m android.Module) []BazelTarget {
+	var targets []BazelTarget
+	for _, m := range m.Bp2buildTargets() {
+		targets = append(targets, generateBazelTarget(ctx, m))
+	}
+	return targets
+}
+
+type bp2buildModule interface {
+	TargetName() string
+	TargetPackage() string
+	BazelRuleClass() string
+	BazelRuleLoadLocation() string
+	BazelAttributes() []interface{}
+}
+
+func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) BazelTarget {
+	ruleClass := m.BazelRuleClass()
+	bzlLoadLocation := m.BazelRuleLoadLocation()
 
 	// extract the bazel attributes from the module.
-	props := getBuildProperties(ctx, m)
+	attrs := m.BazelAttributes()
+	props := extractModuleProperties(attrs, true)
 
-	delete(props.Attrs, "bp2build_available")
+	// name is handled in a special manner
+	delete(props.Attrs, "name")
 
 	// Return the Bazel target with rule class and attributes, ready to be
 	// code-generated.
 	attributes := propsToAttributes(props.Attrs)
-	targetName := targetNameForBp2Build(ctx, m)
+	targetName := m.TargetName()
 	return BazelTarget{
 		name:            targetName,
+		packageName:     m.TargetPackage(),
 		ruleClass:       ruleClass,
 		bzlLoadLocation: bzlLoadLocation,
 		content: fmt.Sprintf(
@@ -294,6 +451,7 @@
 			targetName,
 			attributes,
 		),
+		handcrafted: false,
 	}
 }
 
@@ -311,6 +469,10 @@
 			depLabels[qualifiedTargetLabel(ctx, depModule)] = true
 		})
 	}
+
+	for p, _ := range ignoredPropNames {
+		delete(props.Attrs, p)
+	}
 	attributes := propsToAttributes(props.Attrs)
 
 	depLabelList := "[\n"
@@ -334,24 +496,21 @@
 }
 
 func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
-	var allProps map[string]string
 	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
 	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
 	if aModule, ok := m.(android.Module); ok {
-		allProps = ExtractModuleProperties(aModule)
+		return extractModuleProperties(aModule.GetProperties(), false)
 	}
 
-	return BazelAttributes{
-		Attrs: allProps,
-	}
+	return BazelAttributes{}
 }
 
 // Generically extract module properties and types into a map, keyed by the module property name.
-func ExtractModuleProperties(aModule android.Module) map[string]string {
+func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) BazelAttributes {
 	ret := map[string]string{}
 
 	// Iterate over this android.Module's property structs.
-	for _, properties := range aModule.GetProperties() {
+	for _, properties := range props {
 		propertiesValue := reflect.ValueOf(properties)
 		// Check that propertiesValue is a pointer to the Properties struct, like
 		// *cc.BaseLinkerProperties or *java.CompilerProperties.
@@ -361,6 +520,11 @@
 		if isStructPtr(propertiesValue.Type()) {
 			structValue := propertiesValue.Elem()
 			for k, v := range extractStructProperties(structValue, 0) {
+				if existing, exists := ret[k]; checkForDuplicateProperties && exists {
+					panic(fmt.Errorf(
+						"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
+						k, existing))
+				}
 				ret[k] = v
 			}
 		} else {
@@ -370,7 +534,9 @@
 		}
 	}
 
-	return ret
+	return BazelAttributes{
+		Attrs: ret,
+	}
 }
 
 func isStructPtr(t reflect.Type) bool {
@@ -379,8 +545,8 @@
 
 // prettyPrint a property value into the equivalent Starlark representation
 // recursively.
-func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
-	if isZero(propertyValue) {
+func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
+	if !emitZeroValues && isZero(propertyValue) {
 		// A property value being set or unset actually matters -- Soong does set default
 		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
 		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
@@ -394,48 +560,29 @@
 		return "", nil
 	}
 
-	var ret string
 	switch propertyValue.Kind() {
 	case reflect.String:
-		ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
+		return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
 	case reflect.Bool:
-		ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
+		return starlark_fmt.PrintBool(propertyValue.Bool()), nil
 	case reflect.Int, reflect.Uint, reflect.Int64:
-		ret = fmt.Sprintf("%v", propertyValue.Interface())
+		return fmt.Sprintf("%v", propertyValue.Interface()), nil
 	case reflect.Ptr:
-		return prettyPrint(propertyValue.Elem(), indent)
+		return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
 	case reflect.Slice:
-		if propertyValue.Len() == 0 {
-			return "", nil
-		}
-
-		if propertyValue.Len() == 1 {
-			// Single-line list for list with only 1 element
-			ret += "["
-			indexedValue, err := prettyPrint(propertyValue.Index(0), indent)
+		elements := make([]string, 0, propertyValue.Len())
+		for i := 0; i < propertyValue.Len(); i++ {
+			val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
 			if err != nil {
 				return "", err
 			}
-			ret += indexedValue
-			ret += "]"
-		} else {
-			// otherwise, use a multiline list.
-			ret += "[\n"
-			for i := 0; i < propertyValue.Len(); i++ {
-				indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
-				if err != nil {
-					return "", err
-				}
-
-				if indexedValue != "" {
-					ret += makeIndent(indent + 1)
-					ret += indexedValue
-					ret += ",\n"
-				}
+			if val != "" {
+				elements = append(elements, val)
 			}
-			ret += makeIndent(indent)
-			ret += "]"
 		}
+		return starlark_fmt.PrintList(elements, indent, func(s string) string {
+			return "%s"
+		}), nil
 
 	case reflect.Struct:
 		// Special cases where the bp2build sends additional information to the codegenerator
@@ -446,15 +593,12 @@
 			return fmt.Sprintf("%q", label.Label), nil
 		}
 
-		ret = "{\n"
 		// Sort and print the struct props by the key.
 		structProps := extractStructProperties(propertyValue, indent)
-		for _, k := range android.SortedStringKeys(structProps) {
-			ret += makeIndent(indent + 1)
-			ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
+		if len(structProps) == 0 {
+			return "", nil
 		}
-		ret += makeIndent(indent)
-		ret += "}"
+		return starlark_fmt.PrintDict(structProps, indent), nil
 	case reflect.Interface:
 		// TODO(b/164227191): implement pretty print for interfaces.
 		// Interfaces are used for for arch, multilib and target properties.
@@ -463,7 +607,6 @@
 		return "", fmt.Errorf(
 			"unexpected kind for property struct field: %s", propertyValue.Kind())
 	}
-	return ret, nil
 }
 
 // Converts a reflected property struct value into a map of property names and property values,
@@ -489,8 +632,22 @@
 			continue
 		}
 
+		// if the struct is embedded (anonymous), flatten the properties into the containing struct
+		if field.Anonymous {
+			if field.Type.Kind() == reflect.Ptr {
+				fieldValue = fieldValue.Elem()
+			}
+			if fieldValue.Type().Kind() == reflect.Struct {
+				propsToMerge := extractStructProperties(fieldValue, indent)
+				for prop, value := range propsToMerge {
+					ret[prop] = value
+				}
+				continue
+			}
+		}
+
 		propertyName := proptools.PropertyNameForField(field.Name)
-		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
+		prettyPrintedValue, err := prettyPrint(fieldValue, indent+1, false)
 		if err != nil {
 			panic(
 				fmt.Errorf(
@@ -528,7 +685,14 @@
 		} else {
 			return true
 		}
+	// Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
+	// pointer instead
+	case reflect.Bool, reflect.String:
+		return false
 	default:
+		if !value.IsValid() {
+			return true
+		}
 		zeroValue := reflect.Zero(value.Type())
 		result := value.Interface() == zeroValue.Interface()
 		return result
@@ -547,17 +711,6 @@
 	return strings.ReplaceAll(s, "\"", "\\\"")
 }
 
-func makeIndent(indent int) string {
-	if indent < 0 {
-		panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
-	}
-	return strings.Repeat("    ", indent)
-}
-
-func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
-	return strings.Replace(c.ModuleName(logicModule), bazel.BazelTargetModuleNamePrefix, "", 1)
-}
-
 func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
 	name := ""
 	if c.ModuleSubDir(logicModule) != "" {
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 63a6c2e..0f3ca79 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -15,20 +15,25 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/genrule"
+	"fmt"
 	"strings"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/android/allowlists"
+	"android/soong/python"
 )
 
 func TestGenerateSoongModuleTargets(t *testing.T) {
 	testCases := []struct {
+		description         string
 		bp                  string
 		expectedBazelTarget string
 	}{
 		{
+			description: "only name",
 			bp: `custom { name: "foo" }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -36,14 +41,17 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
+    string_prop = "",
 )`,
 		},
 		{
+			description: "handles bool",
 			bp: `custom {
-	name: "foo",
-	ramdisk: true,
+  name: "foo",
+  bool_prop: true,
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -51,15 +59,17 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
-    ramdisk = True,
+    bool_prop = True,
+    string_prop = "",
 )`,
 		},
 		{
+			description: "string escaping",
 			bp: `custom {
-	name: "foo",
-	owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
+  name: "foo",
+  owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -67,15 +77,18 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
+    string_prop = "",
 )`,
 		},
 		{
+			description: "single item string list",
 			bp: `custom {
-	name: "foo",
-	required: ["bar"],
+  name: "foo",
+  required: ["bar"],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -83,15 +96,18 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     required = ["bar"],
+    string_prop = "",
 )`,
 		},
 		{
+			description: "list of strings",
 			bp: `custom {
-	name: "foo",
-	target_required: ["qux", "bazqux"],
+  name: "foo",
+  target_required: ["qux", "bazqux"],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -99,6 +115,8 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
+    string_prop = "",
     target_required = [
         "qux",
         "bazqux",
@@ -106,18 +124,19 @@
 )`,
 		},
 		{
+			description: "dist/dists",
 			bp: `custom {
-	name: "foo",
-	dist: {
-		targets: ["goal_foo"],
-		tag: ".foo",
-	},
-	dists: [{
-		targets: ["goal_bar"],
-		tag: ".bar",
-	}],
+  name: "foo",
+  dist: {
+    targets: ["goal_foo"],
+    tag: ".foo",
+  },
+  dists: [{
+    targets: ["goal_bar"],
+    tag: ".bar",
+  }],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -125,6 +144,7 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     dist = {
         "tag": ".foo",
         "targets": ["goal_foo"],
@@ -133,23 +153,25 @@
         "tag": ".bar",
         "targets": ["goal_bar"],
     }],
+    string_prop = "",
 )`,
 		},
 		{
+			description: "put it together",
 			bp: `custom {
-	name: "foo",
-	required: ["bar"],
-	target_required: ["qux", "bazqux"],
-	ramdisk: true,
-	owner: "custom_owner",
-	dists: [
-		{
-			tag: ".tag",
-			targets: ["my_goal"],
-		},
-	],
+  name: "foo",
+  required: ["bar"],
+  target_required: ["qux", "bazqux"],
+  bool_prop: true,
+  owner: "custom_owner",
+  dists: [
+    {
+      tag: ".tag",
+      targets: ["my_goal"],
+    },
+  ],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -157,13 +179,14 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = True,
     dists = [{
         "tag": ".tag",
         "targets": ["my_goal"],
     }],
     owner = "custom_owner",
-    ramdisk = True,
     required = ["bar"],
+    string_prop = "",
     target_required = [
         "qux",
         "bazqux",
@@ -174,76 +197,91 @@
 
 	dir := "."
 	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
+		t.Run(testCase.description, func(t *testing.T) {
+			config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+			ctx := android.NewTestContext(config)
 
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register()
+			ctx.RegisterModuleType("custom", customModuleFactory)
+			ctx.Register()
 
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		android.FailIfErrored(t, errs)
+			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+			android.FailIfErrored(t, errs)
+			_, errs = ctx.PrepareBuildActions(config)
+			android.FailIfErrored(t, errs)
 
-		codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
-			t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
-		}
+			codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
+			if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
+				t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
+			}
 
-		actualBazelTarget := bazelTargets[0]
-		if actualBazelTarget.content != testCase.expectedBazelTarget {
-			t.Errorf(
-				"Expected generated Bazel target to be '%s', got '%s'",
-				testCase.expectedBazelTarget,
-				actualBazelTarget.content,
-			)
-		}
+			actualBazelTarget := bazelTargets[0]
+			if actualBazelTarget.content != testCase.expectedBazelTarget {
+				t.Errorf(
+					"Expected generated Bazel target to be '%s', got '%s'",
+					testCase.expectedBazelTarget,
+					actualBazelTarget.content,
+				)
+			}
+		})
 	}
 }
 
 func TestGenerateBazelTargetModules(t *testing.T) {
-	testCases := []struct {
-		name                 string
-		bp                   string
-		expectedBazelTargets []string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			bp: `custom {
+			description: "string ptr props",
+			blueprint: `custom {
 	name: "foo",
-    string_list_prop: ["a", "b"],
-    string_prop: "a",
+    string_ptr_prop: "",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "foo",
-    string_list_prop = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_ptr_prop": `""`,
+				}),
+			},
+		},
+		{
+			description: "string props",
+			blueprint: `custom {
+  name: "foo",
+    string_list_prop: ["a", "b"],
+    string_ptr_prop: "a",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_list_prop": `[
         "a",
         "b",
-    ],
-    string_prop = "a",
-)`,
+    ]`,
+					"string_ptr_prop": `"a"`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
-	name: "control_characters",
+			description: "control characters",
+			blueprint: `custom {
+    name: "foo",
     string_list_prop: ["\t", "\n"],
-    string_prop: "a\t\n\r",
+    string_ptr_prop: "a\t\n\r",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "control_characters",
-    string_list_prop = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "foo", attrNameToString{
+					"string_list_prop": `[
         "\t",
         "\n",
-    ],
-    string_prop = "a\t\n\r",
-)`,
+    ]`,
+					"string_ptr_prop": `"a\t\n\r"`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "handles dep",
+			blueprint: `custom {
   name: "has_dep",
   arch_paths: [":dep"],
   bazel_module: { bp2build_available: true },
@@ -254,37 +292,122 @@
   arch_paths: ["abc"],
   bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "dep",
-    arch_paths = ["abc"],
-)`,
-				`custom(
-    name = "has_dep",
-    arch_paths = [":dep"],
-)`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "dep", attrNameToString{
+					"arch_paths": `["abc"]`,
+				}),
+				makeBazelTarget("custom", "has_dep", attrNameToString{
+					"arch_paths": `[":dep"]`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "non-existent dep",
+			blueprint: `custom {
+  name: "has_dep",
+  arch_paths: [":dep"],
+  bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "has_dep", attrNameToString{
+					"arch_paths": `[":dep__BP2BUILD__MISSING__DEP"]`,
+				}),
+			},
+		},
+		{
+			description: "arch-variant srcs",
+			blueprint: `custom {
     name: "arch_paths",
     arch: {
-      x86: {
-        arch_paths: ["abc"],
-      },
+      x86: { arch_paths: ["x86.txt"] },
+      x86_64:  { arch_paths: ["x86_64.txt"] },
+      arm:  { arch_paths: ["arm.txt"] },
+      arm64:  { arch_paths: ["arm64.txt"] },
+    },
+    target: {
+      linux: { arch_paths: ["linux.txt"] },
+      bionic: { arch_paths: ["bionic.txt"] },
+      host: { arch_paths: ["host.txt"] },
+      not_windows: { arch_paths: ["not_windows.txt"] },
+      android: { arch_paths: ["android.txt"] },
+      linux_musl: { arch_paths: ["linux_musl.txt"] },
+      musl: { arch_paths: ["musl.txt"] },
+      linux_glibc: { arch_paths: ["linux_glibc.txt"] },
+      glibc: { arch_paths: ["glibc.txt"] },
+      linux_bionic: { arch_paths: ["linux_bionic.txt"] },
+      darwin: { arch_paths: ["darwin.txt"] },
+      windows: { arch_paths: ["windows.txt"] },
+    },
+    multilib: {
+        lib32: { arch_paths: ["lib32.txt"] },
+        lib64: { arch_paths: ["lib64.txt"] },
     },
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "arch_paths",
-    arch_paths = select({
-        "//build/bazel/platforms/arch:x86": ["abc"],
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "arch_paths", attrNameToString{
+					"arch_paths": `select({
+        "//build/bazel/platforms/arch:arm": [
+            "arm.txt",
+            "lib32.txt",
+        ],
+        "//build/bazel/platforms/arch:arm64": [
+            "arm64.txt",
+            "lib64.txt",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            "x86.txt",
+            "lib32.txt",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "x86_64.txt",
+            "lib64.txt",
+        ],
         "//conditions:default": [],
-    }),
-)`,
+    }) + select({
+        "//build/bazel/platforms/os:android": [
+            "linux.txt",
+            "bionic.txt",
+            "android.txt",
+        ],
+        "//build/bazel/platforms/os:darwin": [
+            "host.txt",
+            "darwin.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux": [
+            "host.txt",
+            "linux.txt",
+            "glibc.txt",
+            "linux_glibc.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            "host.txt",
+            "linux.txt",
+            "bionic.txt",
+            "linux_bionic.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:linux_musl": [
+            "host.txt",
+            "linux.txt",
+            "musl.txt",
+            "linux_musl.txt",
+            "not_windows.txt",
+        ],
+        "//build/bazel/platforms/os:windows": [
+            "host.txt",
+            "windows.txt",
+        ],
+        "//conditions:default": [],
+    })`,
+				}),
 			},
 		},
 		{
-			bp: `custom {
+			description: "arch-variant deps",
+			blueprint: `custom {
   name: "has_dep",
   arch: {
     x86: {
@@ -299,56 +422,82 @@
     arch_paths: ["abc"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`custom(
-    name = "dep",
-    arch_paths = ["abc"],
-)`,
-				`custom(
-    name = "has_dep",
-    arch_paths = select({
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "dep", attrNameToString{
+					"arch_paths": `["abc"]`,
+				}),
+				makeBazelTarget("custom", "has_dep", attrNameToString{
+					"arch_paths": `select({
         "//build/bazel/platforms/arch:x86": [":dep"],
         "//conditions:default": [],
-    }),
-)`,
+    })`,
+				}),
+			},
+		},
+		{
+			description: "embedded props",
+			blueprint: `custom {
+    name: "embedded_props",
+    embedded_prop: "abc",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "embedded_props", attrNameToString{
+					"embedded_attr": `"abc"`,
+				}),
+			},
+		},
+		{
+			description: "ptr to embedded props",
+			blueprint: `custom {
+    name: "ptr_to_embedded_props",
+    other_embedded_prop: "abc",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("custom", "ptr_to_embedded_props", attrNameToString{
+					"other_embedded_attr": `"abc"`,
+				}),
 			},
 		},
 	}
 
 	dir := "."
 	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
+		t.Run(testCase.description, func(t *testing.T) {
+			config := android.TestConfig(buildDir, nil, testCase.blueprint, nil)
+			ctx := android.NewTestContext(config)
 
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+			registerCustomModuleForBp2buildConversion(ctx)
 
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		if Errored(t, "", errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, "", errs) {
-			continue
-		}
+			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+			if errored(t, testCase, errs) {
+				return
+			}
+			_, errs = ctx.ResolveDependencies(config)
+			if errored(t, testCase, errs) {
+				return
+			}
 
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("Expected %d bazel target, got %d", expectedCount, actualCount)
-		} else {
-			for i, expectedBazelTarget := range testCase.expectedBazelTargets {
-				actualBazelTarget := bazelTargets[i]
-				if actualBazelTarget.content != expectedBazelTarget {
-					t.Errorf(
-						"Expected generated Bazel target to be '%s', got '%s'",
-						expectedBazelTarget,
-						actualBazelTarget.content,
-					)
+			if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+				t.Errorf("Expected %d bazel target (%s),\ngot %d (%s)", expectedCount, testCase.expectedBazelTargets, actualCount, bazelTargets)
+			} else {
+				for i, expectedBazelTarget := range testCase.expectedBazelTargets {
+					actualBazelTarget := bazelTargets[i]
+					if actualBazelTarget.content != expectedBazelTarget {
+						t.Errorf(
+							"Expected generated Bazel target to be '%s', got '%s'",
+							expectedBazelTarget,
+							actualBazelTarget.content,
+						)
+					}
 				}
 			}
-		}
+		})
 	}
 }
 
@@ -461,18 +610,19 @@
 		{
 			bp: `custom {
     name: "bar",
+    one_to_many_prop: true,
     bazel_module: { bp2build_available: true  },
 }`,
 			expectedBazelTarget: `my_library(
     name = "bar",
 )
 
-my_proto_library(
-    name = "bar_my_proto_library_deps",
-)
-
 proto_library(
     name = "bar_proto_library_deps",
+)
+
+my_proto_library(
+    name = "bar_my_proto_library_deps",
 )`,
 			expectedBazelTargetCount: 3,
 			expectedLoadStatements: `load("//build/bazel/rules:proto.bzl", "my_proto_library", "proto_library")
@@ -485,7 +635,6 @@
 		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.RegisterBp2BuildMutator("custom", customBp2BuildMutatorFromStarlark)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
@@ -494,7 +643,8 @@
 		android.FailIfErrored(t, errs)
 
 		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+		bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+		android.FailIfErrored(t, err)
 		if actualCount := len(bazelTargets); actualCount != testCase.expectedBazelTargetCount {
 			t.Fatalf("Expected %d bazel target, got %d", testCase.expectedBazelTargetCount, actualCount)
 		}
@@ -520,120 +670,85 @@
 }
 
 func TestModuleTypeBp2Build(t *testing.T) {
-	otherGenruleBp := map[string]string{
-		"other/Android.bp": `genrule {
-    name: "foo.tool",
-    out: ["foo_tool.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-}
-genrule {
-    name: "other.tool",
-    out: ["other_tool.out"],
-    srcs: ["other_tool.in"],
-    cmd: "cp $(in) $(out)",
-}`,
-	}
-
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup with does not specify srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with does not specify srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`,
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
 			},
 		},
 		{
-			description:                        "filegroup with no srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with no srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: [],
     bazel_module: { bp2build_available: true },
 }`,
 			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`,
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
 			},
 		},
 		{
-			description:                        "filegroup with srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["a", "b"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
         "a",
         "b",
-    ],
-)`,
+    ]`,
+				}),
 			},
 		},
 		{
-			description:                        "filegroup with excludes srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup with excludes srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["a", "b"],
     exclude_srcs: ["a"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = ["b"],
-)`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `["b"]`,
+				}),
 			},
 		},
 		{
-			description:                        "filegroup with glob",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "foo",
+			description:                "filegroup with glob",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+    name: "fg_foo",
     srcs: ["**/*.txt"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
         "other/a.txt",
         "other/b.txt",
         "other/subdir/a.txt",
-    ],
-)`,
+    ]`,
+				}),
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/a.txt":        "",
 				"other/b.txt":        "",
 				"other/subdir/a.txt": "",
@@ -641,26 +756,11 @@
 			},
 		},
 		{
-			description:                        "filegroup with glob in subdir",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "foo",
-    srcs: ["a.txt"],
-    bazel_module: { bp2build_available: true },
-}`,
-			dir: "other",
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
-        "a.txt",
-        "b.txt",
-        "subdir/a.txt",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			description:                "filegroup with glob in subdir",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			dir:                        "other",
+			filesystem: map[string]string{
 				"other/Android.bp": `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
@@ -671,13 +771,50 @@
 				"other/subdir/a.txt": "",
 				"other/file":         "",
 			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a.txt",
+        "b.txt",
+        "subdir/a.txt",
+    ]`,
+				}),
+			},
 		},
 		{
-			description:                        "depends_on_other_dir_module",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "depends_on_other_dir_module",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+    name: "fg_foo",
+    srcs: [
+        ":foo",
+        "c",
+    ],
+    bazel_module: { bp2build_available: true },
+}`,
+			filesystem: map[string]string{
+				"other/Android.bp": `filegroup {
+    name: "foo",
+    srcs: ["a", "b"],
+    bazel_module: { bp2build_available: true },
+}`,
+			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "//other:foo",
+        "c",
+    ]`,
+				}),
+			},
+		},
+		{
+			description:                "depends_on_other_unconverted_module_error",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			unconvertedDepsMode:        errorModulesUnconvertedDeps,
+			blueprint: `filegroup {
     name: "foobar",
     srcs: [
         ":foo",
@@ -685,498 +822,37 @@
     ],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "foobar",
-    srcs = [
-        "//other:foo",
-        "c",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			expectedErr: fmt.Errorf(`"foobar" depends on unconverted modules: foo`),
+			filesystem: map[string]string{
 				"other/Android.bp": `filegroup {
     name: "foo",
     srcs: ["a", "b"],
 }`,
 			},
 		},
-		{
-			description:                        "genrule with command line variable replacements",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo.tool",
-    out: ["foo_tool.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-
-genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tool"],
-    cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{
-				`genrule(
-    name = "foo",
-    cmd = "$(location :foo.tool) --genDir=$(GENDIR) arg $(SRCS) $(OUTS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [":foo.tool"],
-)`,
-				`genrule(
-    name = "foo.tool",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["foo_tool.out"],
-    srcs = ["foo_tool.in"],
-)`,
-			},
-		},
-		{
-			description:                        "genrule using $(locations :label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo.tools",
-    out: ["foo_tool.out", "foo_tool2.out"],
-    srcs: ["foo_tool.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-
-genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tools"],
-    cmd: "$(locations :foo.tools) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations :foo.tools) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [":foo.tools"],
-)`,
-				`genrule(
-    name = "foo.tools",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = [
-        "foo_tool.out",
-        "foo_tool2.out",
-    ],
-    srcs = ["foo_tool.in"],
-)`,
-			},
-		},
-		{
-			description:                        "genrule using $(locations //absolute:label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tool_files: [":foo.tool"],
-    cmd: "$(locations :foo.tool) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = ["//other:foo.tool"],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule srcs using $(locations //absolute:label)",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: [":other.tool"],
-    tool_files: [":foo.tool"],
-    cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)",
-    outs = ["foo.out"],
-    srcs = ["//other:other.tool"],
-    tools = ["//other:foo.tool"],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule using $(location) label should substitute first tool label automatically",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tool_files: [":foo.tool", ":other.tool"],
-    cmd: "$(location) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(location //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [
-        "//other:foo.tool",
-        "//other:other.tool",
-    ],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule using $(locations) label should substitute first tool label automatically",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    tools: [":foo.tool", ":other.tool"],
-    cmd: "$(locations) -s $(out) $(in)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-    tools = [
-        "//other:foo.tool",
-        "//other:other.tool",
-    ],
-)`,
-			},
-			fs: otherGenruleBp,
-		},
-		{
-			description:                        "genrule without tools or tool_files can convert successfully",
-			moduleTypeUnderTest:                "genrule",
-			moduleTypeUnderTestFactory:         genrule.GenRuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: genrule.GenruleBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{genrule.RegisterGenruleBp2BuildDeps},
-			bp: `genrule {
-    name: "foo",
-    out: ["foo.out"],
-    srcs: ["foo.in"],
-    cmd: "cp $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}`,
-			expectedBazelTargets: []string{`genrule(
-    name = "foo",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["foo.out"],
-    srcs = ["foo.in"],
-)`,
-			},
-		},
 	}
 
-	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
+		t.Run(testCase.description, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, testCase)
+		})
 	}
 }
 
-func Errored(t *testing.T, desc string, errs []error) bool {
-	t.Helper()
-	if len(errs) > 0 {
-		for _, err := range errs {
-			t.Errorf("%s: %s", desc, err)
-		}
-		return true
-	}
-	return false
-}
-
 type bp2buildMutator = func(android.TopDownMutatorContext)
 
-func TestBp2BuildInlinesDefaults(t *testing.T) {
-	testCases := []struct {
-		moduleTypesUnderTest      map[string]android.ModuleFactory
-		bp2buildMutatorsUnderTest map[string]bp2buildMutator
-		bp                        string
-		expectedBazelTarget       string
-		description               string
-	}{
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults",
-    cmd: "do-something $(in) $(out)",
-}
-genrule {
-    name: "gen",
-    out: ["out"],
-    srcs: ["in1"],
-    defaults: ["gen_defaults"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "do-something $(SRCS) $(OUTS)",
-    outs = ["out"],
-    srcs = ["in1"],
-)`,
-			description: "genrule applies properties from a genrule_defaults dependency if not specified",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults",
-    out: ["out-from-defaults"],
-    srcs: ["in-from-defaults"],
-    cmd: "cmd-from-defaults",
-}
-genrule {
-    name: "gen",
-    out: ["out"],
-    srcs: ["in1"],
-    defaults: ["gen_defaults"],
-    cmd: "do-something $(in) $(out)",
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "do-something $(SRCS) $(OUTS)",
-    outs = [
-        "out-from-defaults",
-        "out",
-    ],
-    srcs = [
-        "in-from-defaults",
-        "in1",
-    ],
-)`,
-			description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults1",
-    cmd: "cp $(in) $(out)",
-}
-
-genrule_defaults {
-    name: "gen_defaults2",
-    srcs: ["in1"],
-}
-
-genrule {
-    name: "gen",
-    out: ["out"],
-    defaults: ["gen_defaults1", "gen_defaults2"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "cp $(SRCS) $(OUTS)",
-    outs = ["out"],
-    srcs = ["in1"],
-)`,
-			description: "genrule applies properties from list of genrule_defaults",
-		},
-		{
-			moduleTypesUnderTest: map[string]android.ModuleFactory{
-				"genrule":          genrule.GenRuleFactory,
-				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
-			},
-			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
-				"genrule": genrule.GenruleBp2Build,
-			},
-			bp: `genrule_defaults {
-    name: "gen_defaults1",
-    defaults: ["gen_defaults2"],
-    cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
-}
-
-genrule_defaults {
-    name: "gen_defaults2",
-    defaults: ["gen_defaults3"],
-    cmd: "cmd2 $(in) $(out)",
-    out: ["out-from-2"],
-    srcs: ["in1"],
-}
-
-genrule_defaults {
-    name: "gen_defaults3",
-    out: ["out-from-3"],
-    srcs: ["srcs-from-3"],
-}
-
-genrule {
-    name: "gen",
-    out: ["out"],
-    defaults: ["gen_defaults1"],
-    bazel_module: { bp2build_available: true },
-}
-`,
-			expectedBazelTarget: `genrule(
-    name = "gen",
-    cmd = "cmd1 $(SRCS) $(OUTS)",
-    outs = [
-        "out-from-3",
-        "out-from-2",
-        "out",
-    ],
-    srcs = [
-        "in1",
-        "srcs-from-3",
-    ],
-)`,
-			description: "genrule applies properties from genrule_defaults transitively",
-		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
-		for m, factory := range testCase.moduleTypesUnderTest {
-			ctx.RegisterModuleType(m, factory)
-		}
-		for mutator, f := range testCase.bp2buildMutatorsUnderTest {
-			ctx.RegisterBp2BuildMutator(mutator, f)
-		}
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.ResolveDependencies(config)
-		android.FailIfErrored(t, errs)
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount := len(bazelTargets); actualCount != 1 {
-			t.Fatalf("%s: Expected 1 bazel target, got %d", testCase.description, actualCount)
-		}
-
-		actualBazelTarget := bazelTargets[0]
-		if actualBazelTarget.content != testCase.expectedBazelTarget {
-			t.Errorf(
-				"%s: Expected generated Bazel target to be '%s', got '%s'",
-				testCase.description,
-				testCase.expectedBazelTarget,
-				actualBazelTarget.content,
-			)
-		}
-	}
-}
-
 func TestAllowlistingBp2buildTargetsExplicitly(t *testing.T) {
 	testCases := []struct {
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator bp2buildMutator
-		bp                                 string
-		expectedCount                      int
-		description                        string
+		moduleTypeUnderTest        string
+		moduleTypeUnderTestFactory android.ModuleFactory
+		bp                         string
+		expectedCount              int
+		description                string
 	}{
 		{
-			description:                        "explicitly unavailable",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "explicitly unavailable",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1185,10 +861,9 @@
 			expectedCount: 0,
 		},
 		{
-			description:                        "implicitly unavailable",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "implicitly unavailable",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1196,10 +871,9 @@
 			expectedCount: 0,
 		},
 		{
-			description:                        "explicitly available",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "explicitly available",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			bp: `filegroup {
     name: "foo",
     srcs: ["a", "b"],
@@ -1208,12 +882,12 @@
 			expectedCount: 1,
 		},
 		{
-			description:                        "generates more than 1 target if needed",
-			moduleTypeUnderTest:                "custom",
-			moduleTypeUnderTestFactory:         customModuleFactory,
-			moduleTypeUnderTestBp2BuildMutator: customBp2BuildMutatorFromStarlark,
+			description:                "generates more than 1 target if needed",
+			moduleTypeUnderTest:        "custom",
+			moduleTypeUnderTestFactory: customModuleFactory,
 			bp: `custom {
     name: "foo",
+    one_to_many_prop: true,
     bazel_module: { bp2build_available: true },
 }`,
 			expectedCount: 3,
@@ -1222,41 +896,41 @@
 
 	dir := "."
 	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+		t.Run(testCase.description, func(t *testing.T) {
+			config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+			ctx := android.NewTestContext(config)
+			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+			ctx.RegisterForBazelConversion()
 
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.ResolveDependencies(config)
-		android.FailIfErrored(t, errs)
+			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+			android.FailIfErrored(t, errs)
+			_, errs = ctx.ResolveDependencies(config)
+			android.FailIfErrored(t, errs)
 
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount := len(bazelTargets); actualCount != testCase.expectedCount {
-			t.Fatalf("%s: Expected %d bazel target, got %d", testCase.description, testCase.expectedCount, actualCount)
-		}
+			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
+			if actualCount := len(bazelTargets); actualCount != testCase.expectedCount {
+				t.Fatalf("%s: Expected %d bazel target, got %d", testCase.description, testCase.expectedCount, actualCount)
+			}
+		})
 	}
 }
 
 func TestAllowlistingBp2buildTargetsWithConfig(t *testing.T) {
 	testCases := []struct {
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator bp2buildMutator
-		expectedCount                      map[string]int
-		description                        string
-		bp2buildConfig                     android.Bp2BuildConfig
-		checkDir                           string
-		fs                                 map[string]string
+		moduleTypeUnderTest        string
+		moduleTypeUnderTestFactory android.ModuleFactory
+		expectedCount              map[string]int
+		description                string
+		bp2buildConfig             allowlists.Bp2BuildConfig
+		checkDir                   string
+		fs                         map[string]string
 	}{
 		{
-			description:                        "test bp2build config package and subpackages config",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "test bp2build config package and subpackages config",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			expectedCount: map[string]int{
 				"migrated":                           1,
 				"migrated/but_not_really":            0,
@@ -1264,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" }`,
@@ -1278,19 +952,18 @@
 			},
 		},
 		{
-			description:                        "test bp2build config opt-in and opt-out",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			description:                "test bp2build config opt-in and opt-out",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 			expectedCount: map[string]int{
 				"package-opt-in":             2,
 				"package-opt-in/subpackage":  0,
 				"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": `
@@ -1332,8 +1005,8 @@
 		config := android.TestConfig(buildDir, nil, "", fs)
 		ctx := android.NewTestContext(config)
 		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(testCase.bp2buildConfig)
+		allowlist := android.NewBp2BuildAllowlist().SetDefaultConfig(testCase.bp2buildConfig)
+		ctx.RegisterBp2BuildConfig(allowlist)
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
@@ -1345,7 +1018,8 @@
 
 		// For each directory, test that the expected number of generated targets is correct.
 		for dir, expectedCount := range testCase.expectedCount {
-			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, dir)
+			android.FailIfErrored(t, err)
 			if actualCount := len(bazelTargets); actualCount != expectedCount {
 				t.Fatalf(
 					"%s: Expected %d bazel target for %s package, got %d",
@@ -1360,192 +1034,167 @@
 }
 
 func TestCombineBuildFilesBp2buildTargets(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup bazel_module.label",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup bazel_module.label",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     bazel_module: { label: "//other:fg_foo" },
 }`,
 			expectedBazelTargets: []string{
 				`// BUILD file`,
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
 		},
 		{
-			description:                        "multiple bazel_module.label same BUILD",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "fg_foo",
-    bazel_module: { label: "//other:fg_foo" },
-}
+			description:                "multiple bazel_module.label same BUILD",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
+        name: "fg_foo",
+        bazel_module: { label: "//other:fg_foo" },
+    }
 
-filegroup {
-    name: "foo",
-    bazel_module: { label: "//other:foo" },
-}`,
+    filegroup {
+        name: "foo",
+        bazel_module: { label: "//other:foo" },
+    }`,
 			expectedBazelTargets: []string{
 				`// BUILD file`,
 			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
 		},
 		{
-			description:                        "filegroup bazel_module.label and bp2build",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "fg_foo",
-    bazel_module: {
-      label: "//other:fg_foo",
-      bp2build_available: true,
-    },
-}`,
-			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_foo",
-)`,
-				`// BUILD file`,
+			description:                "filegroup bazel_module.label and bp2build in subdir",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			dir:                        "other",
+			blueprint:                  ``,
+			filesystem: map[string]string{
+				"other/Android.bp": `filegroup {
+        name: "fg_foo",
+        bazel_module: {
+          bp2build_available: true,
+        },
+      }
+      filegroup {
+        name: "fg_bar",
+        bazel_module: {
+          label: "//other:fg_bar"
+        },
+      }`,
+				"other/BUILD.bazel": `// definition for fg_bar`,
 			},
-			fs: map[string]string{
-				"other/BUILD.bazel": `// BUILD file`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{}),
+				`// definition for fg_bar`,
 			},
 		},
 		{
-			description:                        "filegroup bazel_module.label and filegroup bp2build",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
-    name: "fg_foo",
-    bazel_module: {
-      label: "//other:fg_foo",
-    },
-}
+			description:                "filegroup bazel_module.label and filegroup bp2build",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
 
-filegroup {
-    name: "fg_bar",
-    bazel_module: {
-      bp2build_available: true,
-    },
-}`,
-			expectedBazelTargets: []string{
-				`filegroup(
-    name = "fg_bar",
-)`,
-				`// BUILD file`,
-			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"other/BUILD.bazel": `// BUILD file`,
 			},
+			blueprint: `filegroup {
+        name: "fg_foo",
+        bazel_module: {
+          label: "//other:fg_foo",
+        },
+    }
+
+    filegroup {
+        name: "fg_bar",
+        bazel_module: {
+          bp2build_available: true,
+        },
+    }`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_bar", map[string]string{}),
+				`// BUILD file`,
+			},
 		},
 	}
 
 	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
+		t.Run(testCase.description, func(t *testing.T) {
+			fs := make(map[string][]byte)
+			toParse := []string{
+				"Android.bp",
 			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+			for f, content := range testCase.filesystem {
+				if strings.HasSuffix(f, "Android.bp") {
+					toParse = append(toParse, f)
+				}
+				fs[f] = []byte(content)
+			}
+			config := android.TestConfig(buildDir, nil, testCase.blueprint, fs)
+			ctx := android.NewTestContext(config)
+			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+			ctx.RegisterForBazelConversion()
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
+			_, errs := ctx.ParseFileList(dir, toParse)
+			if errored(t, testCase, errs) {
+				return
+			}
+			_, errs = ctx.ResolveDependencies(config)
+			if errored(t, testCase, errs) {
+				return
+			}
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
-		} else {
+			checkDir := dir
+			if testCase.dir != "" {
+				checkDir = testCase.dir
+			}
+			codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+			bazelTargets, err := generateBazelTargetsForDir(codegenCtx, checkDir)
+			android.FailIfErrored(t, err)
+			bazelTargets.sort()
+			actualCount := len(bazelTargets)
+			expectedCount := len(testCase.expectedBazelTargets)
+			if actualCount != expectedCount {
+				t.Errorf("Expected %d bazel target, got %d\n%s", expectedCount, actualCount, bazelTargets)
+			}
+			if !strings.Contains(bazelTargets.String(), "Section: Handcrafted targets. ") {
+				t.Errorf("Expected string representation of bazelTargets to contain handcrafted section header.")
+			}
 			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+				actualContent := target.content
+				expectedContent := testCase.expectedBazelTargets[i]
+				if expectedContent != actualContent {
 					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
+						"Expected generated Bazel target to be '%s', got '%s'",
+						expectedContent,
+						actualContent,
 					)
 				}
 			}
-		}
+		})
 	}
 }
 
 func TestGlobExcludeSrcs(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		bp                                 string
-		expectedBazelTargets               []string
-		fs                                 map[string]string
-		dir                                string
-	}{
+	testCases := []bp2buildTestCase{
 		{
-			description:                        "filegroup top level exclude_srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp: `filegroup {
+			description:                "filegroup top level exclude_srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
     exclude_srcs: ["c.txt"],
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
-        "//dir:e.txt",
-        "//dir:f.txt",
-        "a.txt",
-        "b.txt",
-    ],
-)`,
-			},
-			fs: map[string]string{
+			filesystem: map[string]string{
 				"a.txt":          "",
 				"b.txt":          "",
 				"c.txt":          "",
@@ -1553,15 +1202,24 @@
 				"dir/e.txt":      "",
 				"dir/f.txt":      "",
 			},
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a.txt",
+        "b.txt",
+        "//dir:e.txt",
+        "//dir:f.txt",
+    ]`,
+				}),
+			},
 		},
 		{
-			description:                        "filegroup in subdir exclude_srcs",
-			moduleTypeUnderTest:                "filegroup",
-			moduleTypeUnderTestFactory:         android.FileGroupFactory,
-			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
-			bp:                                 "",
-			dir:                                "dir",
-			fs: map[string]string{
+			description:                "filegroup in subdir exclude_srcs",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint:                  "",
+			dir:                        "dir",
+			filesystem: map[string]string{
 				"dir/Android.bp": `filegroup {
     name: "fg_foo",
     srcs: ["**/*.txt"],
@@ -1575,63 +1233,118 @@
 				"dir/subdir/e.txt":      "",
 				"dir/subdir/f.txt":      "",
 			},
-			expectedBazelTargets: []string{`filegroup(
-    name = "fg_foo",
-    srcs = [
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a.txt",
         "//dir/subdir:e.txt",
         "//dir/subdir:f.txt",
-        "a.txt",
-    ],
-)`,
+    ]`,
+				}),
 			},
 		},
 	}
 
-	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+		t.Run(testCase.description, func(t *testing.T) {
+			runBp2BuildTestCaseSimple(t, testCase)
+		})
+	}
+}
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
+func TestCommonBp2BuildModuleAttrs(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description:                "Required into data test",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("filegroup", "reqd") + `
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"data": `[":reqd"]`,
+				}),
+			},
+		},
+		{
+			description:                "Required via arch into data test",
+			moduleTypeUnderTest:        "python_library",
+			moduleTypeUnderTestFactory: python.PythonLibraryFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("python_library", "reqdx86") +
+				simpleModuleDoNotConvertBp2build("python_library", "reqdarm") + `
+python_library {
+    name: "fg_foo",
+    arch: {
+       arm: {
+         required: ["reqdarm"],
+       },
+       x86: {
+         required: ["reqdx86"],
+       },
+    },
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "fg_foo", map[string]string{
+					"data": `select({
+        "//build/bazel/platforms/arch:arm": [":reqdarm"],
+        "//build/bazel/platforms/arch:x86": [":reqdx86"],
+        "//conditions:default": [],
+    })`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
+		},
+		{
+			description:                "Required appended to data test",
+			moduleTypeUnderTest:        "python_library",
+			moduleTypeUnderTestFactory: python.PythonLibraryFactory,
+			filesystem: map[string]string{
+				"data.bin": "",
+				"src.py":   "",
+			},
+			blueprint: simpleModuleDoNotConvertBp2build("python_library", "reqd") + `
+python_library {
+    name: "fg_foo",
+    data: ["data.bin"],
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("py_library", "fg_foo", map[string]string{
+					"data": `[
+        "data.bin",
+        ":reqd",
+    ]`,
+					"srcs_version": `"PY3"`,
+				}),
+			},
+		},
+		{
+			description:                "All props-to-attrs at once together test",
+			moduleTypeUnderTest:        "filegroup",
+			moduleTypeUnderTestFactory: android.FileGroupFactory,
+			blueprint: simpleModuleDoNotConvertBp2build("filegroup", "reqd") + `
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("filegroup", "fg_foo", map[string]string{
+					"data": `[":reqd"]`,
+				}),
+			},
+		},
+	}
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			runBp2BuildTestCaseSimple(t, tc)
+		})
 	}
 }
diff --git a/bp2build/bzl_conversion.go b/bp2build/bzl_conversion.go
index f2f6b01..992cc1c 100644
--- a/bp2build/bzl_conversion.go
+++ b/bp2build/bzl_conversion.go
@@ -160,8 +160,15 @@
 		if shouldSkipStructField(field) {
 			continue
 		}
-
-		properties = append(properties, extractPropertyDescriptions(field.Name, field.Type)...)
+		subProps := extractPropertyDescriptions(field.Name, field.Type)
+		// if the struct is embedded (anonymous), flatten the properties into the containing struct
+		if field.Anonymous {
+			for _, prop := range subProps {
+				properties = append(properties, prop.properties...)
+			}
+		} else {
+			properties = append(properties, subProps...)
+		}
 	}
 	return properties
 }
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 32b12e4..f3345a6 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -22,8 +22,6 @@
 	"testing"
 )
 
-var buildDir string
-
 func setUp() {
 	var err error
 	buildDir, err = ioutil.TempDir("", "bazel_queryview_test")
@@ -87,12 +85,14 @@
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
         "arch_paths": attr.string_list(),
+        "arch_paths_exclude": attr.string_list(),
         # bazel_module start
 #         "label": attr.string(),
 #         "bp2build_available": attr.bool(),
         # bazel_module end
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -100,6 +100,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
@@ -116,8 +118,10 @@
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
         "arch_paths": attr.string_list(),
+        "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -125,6 +129,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
@@ -141,8 +147,10 @@
         "soong_module_variant": attr.string(),
         "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
         "arch_paths": attr.string_list(),
+        "arch_paths_exclude": attr.string_list(),
         "bool_prop": attr.bool(),
         "bool_ptr_prop": attr.bool(),
+        "embedded_prop": attr.string(),
         "int64_ptr_prop": attr.int(),
         # nested_props start
 #         "nested_prop": attr.string(),
@@ -150,6 +158,8 @@
         # nested_props_ptr start
 #         "nested_prop": attr.string(),
         # nested_props_ptr end
+        "one_to_many_prop": attr.bool(),
+        "other_embedded_prop": attr.string(),
         "string_list_prop": attr.string_list(),
         "string_prop": attr.string(),
         "string_ptr_prop": attr.string(),
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
new file mode 100644
index 0000000..037564b
--- /dev/null
+++ b/bp2build/cc_binary_conversion_test.go
@@ -0,0 +1,507 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/genrule"
+)
+
+const (
+	ccBinaryTypePlaceHolder = "{rule_name}"
+)
+
+type testBazelTarget struct {
+	typ   string
+	name  string
+	attrs attrNameToString
+}
+
+func generateBazelTargetsForTest(targets []testBazelTarget) []string {
+	ret := make([]string, 0, len(targets))
+	for _, t := range targets {
+		ret = append(ret, makeBazelTarget(t.typ, t.name, t.attrs))
+	}
+	return ret
+}
+
+type ccBinaryBp2buildTestCase struct {
+	description string
+	blueprint   string
+	targets     []testBazelTarget
+}
+
+func registerCcBinaryModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+}
+
+var binaryReplacer = strings.NewReplacer(ccBinaryTypePlaceHolder, "cc_binary")
+var hostBinaryReplacer = strings.NewReplacer(ccBinaryTypePlaceHolder, "cc_binary_host")
+
+func runCcBinaryTests(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	runCcBinaryTestCase(t, tc)
+	runCcHostBinaryTestCase(t, tc)
+}
+
+func runCcBinaryTestCase(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	moduleTypeUnderTest := "cc_binary"
+	testCase := bp2buildTestCase{
+		expectedBazelTargets:       generateBazelTargetsForTest(tc.targets),
+		moduleTypeUnderTest:        moduleTypeUnderTest,
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		description:                fmt.Sprintf("%s %s", moduleTypeUnderTest, tc.description),
+		blueprint:                  binaryReplacer.Replace(tc.blueprint),
+	}
+	t.Run(testCase.description, func(t *testing.T) {
+		t.Helper()
+		runBp2BuildTestCase(t, registerCcBinaryModuleTypes, testCase)
+	})
+}
+
+func runCcHostBinaryTestCase(t *testing.T, tc ccBinaryBp2buildTestCase) {
+	t.Helper()
+	testCase := tc
+	for i, tar := range testCase.targets {
+		switch tar.typ {
+		case "cc_binary", "proto_library", "cc_lite_proto_library":
+			tar.attrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+		testCase.targets[i] = tar
+	}
+	moduleTypeUnderTest := "cc_binary_host"
+	t.Run(testCase.description, func(t *testing.T) {
+		runBp2BuildTestCase(t, registerCcBinaryModuleTypes, bp2buildTestCase{
+			expectedBazelTargets:       generateBazelTargetsForTest(testCase.targets),
+			moduleTypeUnderTest:        moduleTypeUnderTest,
+			moduleTypeUnderTestFactory: cc.BinaryHostFactory,
+			description:                fmt.Sprintf("%s %s", moduleTypeUnderTest, tc.description),
+			blueprint:                  hostBinaryReplacer.Replace(testCase.blueprint),
+		})
+	})
+}
+
+func TestBasicCcBinary(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: "basic -- properties -> attrs with little/no transformation",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    srcs: ["a.cc"],
+    local_include_dirs: ["dir"],
+    include_dirs: ["absolute_dir"],
+    cflags: ["-Dcopt"],
+    cppflags: ["-Dcppflag"],
+    conlyflags: ["-Dconlyflag"],
+    asflags: ["-Dasflag"],
+    ldflags: ["ld-flag"],
+    rtti: true,
+    strip: {
+        all: true,
+        keep_symbols: true,
+        keep_symbols_and_debug_frame: true,
+        keep_symbols_list: ["symbol"],
+        none: true,
+    },
+    sdk_version: "current",
+    min_sdk_version: "29",
+    use_version_lib: true,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"absolute_includes": `["absolute_dir"]`,
+				"asflags":           `["-Dasflag"]`,
+				"conlyflags":        `["-Dconlyflag"]`,
+				"copts":             `["-Dcopt"]`,
+				"cppflags":          `["-Dcppflag"]`,
+				"linkopts":          `["ld-flag"]`,
+				"local_includes": `[
+        "dir",
+        ".",
+    ]`,
+				"rtti": `True`,
+				"srcs": `["a.cc"]`,
+				"strip": `{
+        "all": True,
+        "keep_symbols": True,
+        "keep_symbols_and_debug_frame": True,
+        "keep_symbols_list": ["symbol"],
+        "none": True,
+    }`,
+				"sdk_version":     `"current"`,
+				"min_sdk_version": `"29"`,
+				"use_version_lib": `True`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryWithSharedLdflagDisableFeature(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: `ldflag "-shared" disables static_flag feature`,
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    ldflags: ["-shared"],
+    include_build_directory: false,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"features": `["-static_flag"]`,
+				"linkopts": `["-shared"]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryWithLinkStatic(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: "link static",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    static_executable: true,
+    include_build_directory: false,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"linkshared": `False`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryVersionScript(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		description: `version script`,
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    include_build_directory: false,
+    version_script: "vs",
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"additional_linker_inputs": `["vs"]`,
+				"linkopts":                 `["-Wl,--version-script,$(location vs)"]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinarySplitSrcsByLang(t *testing.T) {
+	runCcHostBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "split srcs by lang",
+		blueprint: `
+{rule_name} {
+    name: "foo",
+    srcs: [
+        "asonly.S",
+        "conly.c",
+        "cpponly.cpp",
+        ":fg_foo",
+    ],
+    include_build_directory: false,
+}
+` + simpleModuleDoNotConvertBp2build("filegroup", "fg_foo"),
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"srcs": `[
+        "cpponly.cpp",
+        ":fg_foo_cpp_srcs",
+    ]`,
+				"srcs_as": `[
+        "asonly.S",
+        ":fg_foo_as_srcs",
+    ]`,
+				"srcs_c": `[
+        "conly.c",
+        ":fg_foo_c_srcs",
+    ]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryDoNotDistinguishBetweenDepsAndImplementationDeps(t *testing.T) {
+	runCcBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "no implementation deps",
+		blueprint: `
+genrule {
+    name: "generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+genrule {
+    name: "export_generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
+
+{rule_name} {
+    name: "foo",
+    srcs: ["foo.cpp"],
+    shared_libs: ["implementation_shared_dep", "shared_dep"],
+    export_shared_lib_headers: ["shared_dep"],
+    static_libs: ["implementation_static_dep", "static_dep"],
+    export_static_lib_headers: ["static_dep", "whole_static_dep"],
+    whole_static_libs: ["not_explicitly_exported_whole_static_dep", "whole_static_dep"],
+    include_build_directory: false,
+    generated_headers: ["generated_hdr", "export_generated_hdr"],
+    export_generated_headers: ["export_generated_hdr"],
+}
+` +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep"),
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", attrNameToString{
+				"deps": `[
+        ":implementation_static_dep",
+        ":static_dep",
+    ]`,
+				"dynamic_deps": `[
+        ":implementation_shared_dep",
+        ":shared_dep",
+    ]`,
+				"srcs": `[
+        "foo.cpp",
+        ":generated_hdr",
+        ":export_generated_hdr",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep",
+        ":whole_static_dep",
+    ]`,
+				"local_includes": `["."]`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcBinaryNocrtTests(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "nocrt: true",
+			soongProperty: `nocrt: true,`,
+			bazelAttr:     attrNameToString{"link_crt": `False`},
+		},
+		{
+			description:   "nocrt: false",
+			soongProperty: `nocrt: false,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description: "nocrt: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinaryNo_libcrtTests(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "no_libcrt: true",
+			soongProperty: `no_libcrt: true,`,
+			bazelAttr:     attrNameToString{"use_libcrt": `False`},
+		},
+		{
+			description:   "no_libcrt: false",
+			soongProperty: `no_libcrt: false,`,
+			bazelAttr:     attrNameToString{"use_libcrt": `True`},
+		},
+		{
+			description: "no_libcrt: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinaryPropertiesToFeatures(t *testing.T) {
+	baseTestCases := []struct {
+		description   string
+		soongProperty string
+		bazelAttr     attrNameToString
+	}{
+		{
+			description:   "pack_relocation: true",
+			soongProperty: `pack_relocations: true,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description:   "pack_relocations: false",
+			soongProperty: `pack_relocations: false,`,
+			bazelAttr:     attrNameToString{"features": `["disable_pack_relocations"]`},
+		},
+		{
+			description: "pack_relocations: not set",
+			bazelAttr:   attrNameToString{},
+		},
+		{
+			description:   "pack_relocation: true",
+			soongProperty: `allow_undefined_symbols: true,`,
+			bazelAttr:     attrNameToString{"features": `["-no_undefined_symbols"]`},
+		},
+		{
+			description:   "allow_undefined_symbols: false",
+			soongProperty: `allow_undefined_symbols: false,`,
+			bazelAttr:     attrNameToString{},
+		},
+		{
+			description: "allow_undefined_symbols: not set",
+			bazelAttr:   attrNameToString{},
+		},
+	}
+
+	baseBlueprint := `{rule_name} {
+    name: "foo",%s
+    include_build_directory: false,
+}
+`
+	for _, btc := range baseTestCases {
+		prop := btc.soongProperty
+		if len(prop) > 0 {
+			prop = "\n" + prop
+		}
+		runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+			description: btc.description,
+			blueprint:   fmt.Sprintf(baseBlueprint, prop),
+			targets: []testBazelTarget{
+				{"cc_binary", "foo", btc.bazelAttr},
+			},
+		})
+	}
+}
+
+func TestCcBinarySharedProto(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		blueprint: soongCcProtoLibraries + `{rule_name} {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+	},
+	include_build_directory: false,
+}`,
+		targets: []testBazelTarget{
+			{"proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}}, {"cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}}, {"cc_binary", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}},
+		},
+	})
+}
+
+func TestCcBinaryStaticProto(t *testing.T) {
+	runCcBinaryTests(t, ccBinaryBp2buildTestCase{
+		blueprint: soongCcProtoLibraries + `{rule_name} {
+	name: "foo",
+	srcs: ["foo.proto"],
+	static_executable: true,
+	proto: {
+	},
+	include_build_directory: false,
+}`,
+		targets: []testBazelTarget{
+			{"proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}}, {"cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}}, {"cc_binary", "foo", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"linkshared":         `False`,
+			}},
+		},
+	})
+}
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index bf40a6b..2775a10 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -15,71 +15,74 @@
 package bp2build
 
 import (
+	"fmt"
+	"testing"
+
 	"android/soong/android"
 	"android/soong/cc"
-	"strings"
-	"testing"
 )
 
 const (
 	// See cc/testing.go for more context
 	soongCcLibraryPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
+    name: "linux_bionic_supported",
+}`
+
+	soongCcProtoLibraries = `
+cc_library {
+	name: "libprotobuf-cpp-lite",
+	bazel_module: { bp2build_available: false },
 }
 
-toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+cc_library {
+	name: "libprotobuf-cpp-full",
+	bazel_module: { bp2build_available: false },
 }`
+
+	soongCcProtoPreamble = soongCcLibraryPreamble + soongCcProtoLibraries
 )
 
-func TestCcLibraryBp2Build(t *testing.T) {
-	// b/191166471 disabled in sc-dev
-	t.Skip()
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-		depsMutators                       []android.RegisterMutatorFunc
-	}{
-		{
-			description:                        "cc_library - simple example",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			filesystem: map[string]string{
-				"android.cpp": "",
-				"darwin.cpp":  "",
-				// Refer to cc.headerExts for the supported header extensions in Soong.
-				"header.h":         "",
-				"header.hh":        "",
-				"header.hpp":       "",
-				"header.hxx":       "",
-				"header.h++":       "",
-				"header.inl":       "",
-				"header.inc":       "",
-				"header.ipp":       "",
-				"header.h.generic": "",
-				"impl.cpp":         "",
-				"linux.cpp":        "",
-				"x86.cpp":          "",
-				"x86_64.cpp":       "",
-				"foo-dir/a.h":      "",
-			},
-			bp: soongCcLibraryPreamble + `
-cc_library_headers { name: "some-headers" }
+func runCcLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibraryModuleTypes, tc)
+}
+
+func registerCcLibraryModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_prebuilt_library_static", cc.PrebuiltStaticLibraryFactory)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+}
+
+func TestCcLibrarySimple(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - simple example",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"android.cpp": "",
+			"bionic.cpp":  "",
+			"darwin.cpp":  "",
+			// Refer to cc.headerExts for the supported header extensions in Soong.
+			"header.h":         "",
+			"header.hh":        "",
+			"header.hpp":       "",
+			"header.hxx":       "",
+			"header.h++":       "",
+			"header.inl":       "",
+			"header.inc":       "",
+			"header.ipp":       "",
+			"header.h.generic": "",
+			"impl.cpp":         "",
+			"linux.cpp":        "",
+			"x86.cpp":          "",
+			"x86_64.cpp":       "",
+			"foo-dir/a.h":      "",
+		},
+		blueprint: soongCcLibraryPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library_headers", "some-headers") + `
 cc_library {
     name: "foo-lib",
     srcs: ["impl.cpp"],
@@ -107,48 +110,60 @@
         darwin: {
             srcs: ["darwin.cpp"],
         },
+        bionic: {
+          srcs: ["bionic.cpp"]
+        },
     },
+    include_build_directory: false,
+    sdk_version: "current",
+    min_sdk_version: "29",
+    use_version_lib: true,
 }
 `,
-			expectedBazelTargets: []string{`cc_library(
-    name = "foo-lib",
-    copts = [
-        "-Wall",
-        "-I.",
-    ],
-    deps = [":some-headers"],
-    includes = ["foo-dir"],
-    linkopts = ["-Wl,--exclude-libs=bar.a"] + select({
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"copts":               `["-Wall"]`,
+			"export_includes":     `["foo-dir"]`,
+			"implementation_deps": `[":some-headers"]`,
+			"linkopts": `["-Wl,--exclude-libs=bar.a"] + select({
         "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=baz.a"],
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=qux.a"],
         "//conditions:default": [],
-    }),
-    srcs = ["impl.cpp"] + select({
+    })`,
+			"srcs": `["impl.cpp"] + select({
         "//build/bazel/platforms/arch:x86": ["x86.cpp"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms/os:android": ["android.cpp"],
+        "//build/bazel/platforms/os:android": [
+            "bionic.cpp",
+            "android.cpp",
+        ],
         "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
         "//build/bazel/platforms/os:linux": ["linux.cpp"],
+        "//build/bazel/platforms/os:linux_bionic": ["bionic.cpp"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			"sdk_version":     `"current"`,
+			"min_sdk_version": `"29"`,
+			"use_version_lib": `True`,
+		}),
+	})
+}
+
+func TestCcLibraryTrimmedLdAndroid(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - trimmed example of //bionic/linker:ld-android",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"ld-android.cpp":           "",
+			"linked_list.h":            "",
+			"linker.h":                 "",
+			"linker_block_allocator.h": "",
+			"linker_cfi.h":             "",
 		},
-		{
-			description:                        "cc_library - trimmed example of //bionic/linker:ld-android",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			filesystem: map[string]string{
-				"ld-android.cpp":           "",
-				"linked_list.h":            "",
-				"linker.h":                 "",
-				"linker_block_allocator.h": "",
-				"linker_cfi.h":             "",
-			},
-			bp: soongCcLibraryPreamble + `
-cc_library_headers { name: "libc_headers" }
+		blueprint: soongCcLibraryPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library_headers", "libc_headers") + `
 cc_library {
     name: "fake-ld-android",
     srcs: ["ld_android.cpp"],
@@ -175,19 +190,19 @@
             ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
         },
     },
+    include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{`cc_library(
-    name = "fake-ld-android",
-    copts = [
+		expectedBazelTargets: makeCcLibraryTargets("fake-ld-android", attrNameToString{
+			"srcs": `["ld_android.cpp"]`,
+			"copts": `[
         "-Wall",
         "-Wextra",
         "-Wunused",
         "-Werror",
-        "-I.",
-    ],
-    deps = [":libc_headers"],
-    linkopts = [
+    ]`,
+			"implementation_deps": `[":libc_headers"]`,
+			"linkopts": `[
         "-Wl,--exclude-libs=libgcc.a",
         "-Wl,--exclude-libs=libgcc_stripped.a",
         "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
@@ -198,23 +213,24 @@
         "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=libgcc_eh.a"],
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--exclude-libs=libgcc_eh.a"],
         "//conditions:default": [],
-    }),
-    srcs = ["ld_android.cpp"],
-)`},
-		},
-		{
-			description:                        "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			dir:                                "external",
-			filesystem: map[string]string{
-				"external/math/cosf.c":      "",
-				"external/math/erf.c":       "",
-				"external/math/erf_data.c":  "",
-				"external/math/erff.c":      "",
-				"external/math/erff_data.c": "",
-				"external/Android.bp": `
+    })`,
+		}),
+	})
+}
+
+func TestCcLibraryExcludeSrcs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "external",
+		filesystem: map[string]string{
+			"external/math/cosf.c":      "",
+			"external/math/erf.c":       "",
+			"external/math/erf_data.c":  "",
+			"external/math/erff.c":      "",
+			"external/math/erff_data.c": "",
+			"external/Android.bp": `
 cc_library {
     name: "fake-libarm-optimized-routines-math",
     exclude_srcs: [
@@ -240,36 +256,37 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "fake-libarm-optimized-routines-math",
-    copts = ["-Iexternal"] + select({
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: makeCcLibraryTargets("fake-libarm-optimized-routines-math", attrNameToString{
+			"copts": `select({
         "//build/bazel/platforms/arch:arm64": ["-DHAVE_FAST_FMA=1"],
         "//conditions:default": [],
-    }),
-    srcs = ["math/cosf.c"],
-)`},
+    })`,
+			"local_includes": `["."]`,
+			"srcs_c":         `["math/cosf.c"]`,
+		}),
+	})
+}
+
+func TestCcLibrarySharedStaticProps(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared/static props",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"both.cpp":       "",
+			"sharedonly.cpp": "",
+			"staticonly.cpp": "",
 		},
-		{
-			description:                        "cc_library shared/static props",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/both.cpp":       "",
-				"foo/bar/sharedonly.cpp": "",
-				"foo/bar/staticonly.cpp": "",
-				"foo/bar/Android.bp": `
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     srcs: ["both.cpp"],
     cflags: ["bothflag"],
     shared_libs: ["shared_dep_for_both"],
-    static_libs: ["static_dep_for_both"],
-    whole_static_libs: ["whole_static_lib_for_both"],
+    static_libs: ["static_dep_for_both", "whole_and_static_lib_for_both"],
+    whole_static_libs: ["whole_static_lib_for_both", "whole_and_static_lib_for_both"],
     static: {
         srcs: ["staticonly.cpp"],
         cflags: ["staticflag"],
@@ -284,158 +301,699 @@
         static_libs: ["static_dep_for_shared"],
         whole_static_libs: ["whole_static_lib_for_shared"],
     },
+    include_build_directory: false,
+}
+
+cc_library_static {
+    name: "static_dep_for_shared",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "static_dep_for_static",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "static_dep_for_both",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_for_shared",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_for_static",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_for_both",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_and_static_lib_for_both",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "shared_dep_for_shared",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "shared_dep_for_static",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "shared_dep_for_both",
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "staticflag",
+    ]`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_static",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both",
+        ":whole_and_static_lib_for_both",
+        ":whole_static_lib_for_static",
+    ]`}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ]`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_shared",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both",
+        ":whole_and_static_lib_for_both",
+        ":whole_static_lib_for_shared",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryDeps(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared/static props",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"both.cpp":       "",
+			"sharedonly.cpp": "",
+			"staticonly.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "a",
+    srcs: ["both.cpp"],
+    cflags: ["bothflag"],
+    shared_libs: ["implementation_shared_dep_for_both", "shared_dep_for_both"],
+    export_shared_lib_headers: ["shared_dep_for_both"],
+    static_libs: ["implementation_static_dep_for_both", "static_dep_for_both"],
+    export_static_lib_headers: ["static_dep_for_both", "whole_static_dep_for_both"],
+    whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_both", "whole_static_dep_for_both"],
+    static: {
+        srcs: ["staticonly.cpp"],
+        cflags: ["staticflag"],
+        shared_libs: ["implementation_shared_dep_for_static", "shared_dep_for_static"],
+        export_shared_lib_headers: ["shared_dep_for_static"],
+        static_libs: ["implementation_static_dep_for_static", "static_dep_for_static"],
+        export_static_lib_headers: ["static_dep_for_static", "whole_static_dep_for_static"],
+        whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_static", "whole_static_dep_for_static"],
+    },
+    shared: {
+        srcs: ["sharedonly.cpp"],
+        cflags: ["sharedflag"],
+        shared_libs: ["implementation_shared_dep_for_shared", "shared_dep_for_shared"],
+        export_shared_lib_headers: ["shared_dep_for_shared"],
+        static_libs: ["implementation_static_dep_for_shared", "static_dep_for_shared"],
+        export_static_lib_headers: ["static_dep_for_shared", "whole_static_dep_for_shared"],
+        whole_static_libs: ["not_explicitly_exported_whole_static_dep_for_shared", "whole_static_dep_for_shared"],
+    },
+    include_build_directory: false,
+}
+` + simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "implementation_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "whole_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library_static", "not_explicitly_exported_whole_static_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_shared") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_static") +
+			simpleModuleDoNotConvertBp2build("cc_library", "shared_dep_for_both") +
+			simpleModuleDoNotConvertBp2build("cc_library", "implementation_shared_dep_for_both"),
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "staticflag",
+    ]`,
+				"deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ]`,
+				"dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_static",
+    ]`,
+				"implementation_deps": `[
+        ":implementation_static_dep_for_both",
+        ":implementation_static_dep_for_static",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":implementation_shared_dep_for_both",
+        ":implementation_shared_dep_for_static",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep_for_both",
+        ":whole_static_dep_for_both",
+        ":not_explicitly_exported_whole_static_dep_for_static",
+        ":whole_static_dep_for_static",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ]`,
+				"deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ]`,
+				"dynamic_deps": `[
+        ":shared_dep_for_both",
+        ":shared_dep_for_shared",
+    ]`,
+				"implementation_deps": `[
+        ":implementation_static_dep_for_both",
+        ":implementation_static_dep_for_shared",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":implementation_shared_dep_for_both",
+        ":implementation_shared_dep_for_shared",
+    ]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ]`,
+				"whole_archive_deps": `[
+        ":not_explicitly_exported_whole_static_dep_for_both",
+        ":whole_static_dep_for_both",
+        ":not_explicitly_exported_whole_static_dep_for_shared",
+        ":whole_static_dep_for_shared",
+    ]`,
+			})},
+	},
+	)
+}
+
+func TestCcLibraryWholeStaticLibsAlwaysLink(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    whole_static_libs: ["whole_static_lib_for_both"],
+    static: {
+        whole_static_libs: ["whole_static_lib_for_static"],
+    },
+    shared: {
+        whole_static_libs: ["whole_static_lib_for_shared"],
+    },
+    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
+}
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_shared" }
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_static" }
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_both" }
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both_alwayslink",
+        ":whole_static_lib_for_static_alwayslink",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"whole_archive_deps": `[
+        ":whole_static_lib_for_both_alwayslink",
+        ":whole_static_lib_for_shared_alwayslink",
+    ]`,
+			}),
+		},
+	},
+	)
+}
+
+func TestCcLibrarySharedStaticPropsInArch(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared/static props in arch",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/arm.cpp":        "",
+			"foo/bar/x86.cpp":        "",
+			"foo/bar/sharedonly.cpp": "",
+			"foo/bar/staticonly.cpp": "",
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    arch: {
+        arm: {
+            shared: {
+                srcs: ["arm_shared.cpp"],
+                cflags: ["-DARM_SHARED"],
+                static_libs: ["arm_static_dep_for_shared"],
+                whole_static_libs: ["arm_whole_static_dep_for_shared"],
+                shared_libs: ["arm_shared_dep_for_shared"],
+            },
+        },
+        x86: {
+            static: {
+                srcs: ["x86_static.cpp"],
+                cflags: ["-DX86_STATIC"],
+                static_libs: ["x86_dep_for_static"],
+            },
+        },
+    },
+    target: {
+        android: {
+            shared: {
+                srcs: ["android_shared.cpp"],
+                cflags: ["-DANDROID_SHARED"],
+                static_libs: ["android_dep_for_shared"],
+            },
+        },
+        android_arm: {
+            shared: {
+                cflags: ["-DANDROID_ARM_SHARED"],
+            },
+        },
+    },
+    srcs: ["both.cpp"],
+    cflags: ["bothflag"],
+    static_libs: ["static_dep_for_both"],
+    static: {
+        srcs: ["staticonly.cpp"],
+        cflags: ["staticflag"],
+        static_libs: ["static_dep_for_static"],
+    },
+    shared: {
+        srcs: ["sharedonly.cpp"],
+        cflags: ["sharedflag"],
+        static_libs: ["static_dep_for_shared"],
+    },
     bazel_module: { bp2build_available: true },
 }
 
 cc_library_static { name: "static_dep_for_shared" }
-
 cc_library_static { name: "static_dep_for_static" }
-
 cc_library_static { name: "static_dep_for_both" }
 
-cc_library_static { name: "whole_static_lib_for_shared" }
+cc_library_static { name: "arm_static_dep_for_shared" }
+cc_library_static { name: "arm_whole_static_dep_for_shared" }
+cc_library_static { name: "arm_shared_dep_for_shared" }
 
-cc_library_static { name: "whole_static_lib_for_static" }
+cc_library_static { name: "x86_dep_for_static" }
 
-cc_library_static { name: "whole_static_lib_for_both" }
-
-cc_library { name: "shared_dep_for_shared" }
-
-cc_library { name: "shared_dep_for_static" }
-
-cc_library { name: "shared_dep_for_both" }
+cc_library_static { name: "android_dep_for_shared" }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "bothflag",
-        "-Ifoo/bar",
-    ],
-    deps = [":static_dep_for_both"],
-    dynamic_deps = [":shared_dep_for_both"],
-    dynamic_deps_for_shared = [":shared_dep_for_shared"],
-    dynamic_deps_for_static = [":shared_dep_for_static"],
-    shared_copts = ["sharedflag"],
-    shared_srcs = ["sharedonly.cpp"],
-    srcs = ["both.cpp"],
-    static_copts = ["staticflag"],
-    static_deps_for_shared = [":static_dep_for_shared"],
-    static_deps_for_static = [":static_dep_for_static"],
-    static_srcs = ["staticonly.cpp"],
-    whole_archive_deps = [":whole_static_lib_for_both"],
-    whole_archive_deps_for_shared = [":whole_static_lib_for_shared"],
-    whole_archive_deps_for_static = [":whole_static_lib_for_static"],
-)`},
 		},
-		{
-			description:                        "cc_library non-configured version script",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "staticflag",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": ["-DX86_STATIC"],
+        "//conditions:default": [],
+    })`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_static",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [":x86_dep_for_static"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both.cpp",
+        "staticonly.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": ["x86_static.cpp"],
+        "//conditions:default": [],
+    })`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"copts": `[
+        "bothflag",
+        "sharedflag",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": ["-DARM_SHARED"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["-DANDROID_SHARED"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os_arch:android_arm": ["-DANDROID_ARM_SHARED"],
+        "//conditions:default": [],
+    })`,
+				"implementation_deps": `[
+        ":static_dep_for_both",
+        ":static_dep_for_shared",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [":arm_static_dep_for_shared"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":android_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_shared_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both.cpp",
+        "sharedonly.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": ["arm_shared.cpp"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["android_shared.cpp"],
+        "//conditions:default": [],
+    })`,
+				"whole_archive_deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_whole_static_dep_for_shared"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	},
+	)
+}
+
+func TestCcLibrarySharedStaticPropsWithMixedSources(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared/static props with c/cpp/s mixed sources",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/both_source.cpp":   "",
+			"foo/bar/both_source.cc":    "",
+			"foo/bar/both_source.c":     "",
+			"foo/bar/both_source.s":     "",
+			"foo/bar/both_source.S":     "",
+			"foo/bar/shared_source.cpp": "",
+			"foo/bar/shared_source.cc":  "",
+			"foo/bar/shared_source.c":   "",
+			"foo/bar/shared_source.s":   "",
+			"foo/bar/shared_source.S":   "",
+			"foo/bar/static_source.cpp": "",
+			"foo/bar/static_source.cc":  "",
+			"foo/bar/static_source.c":   "",
+			"foo/bar/static_source.s":   "",
+			"foo/bar/static_source.S":   "",
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    srcs: [
+    "both_source.cpp",
+    "both_source.cc",
+    "both_source.c",
+    "both_source.s",
+    "both_source.S",
+    ":both_filegroup",
+  ],
+    static: {
+        srcs: [
+          "static_source.cpp",
+          "static_source.cc",
+          "static_source.c",
+          "static_source.s",
+          "static_source.S",
+          ":static_filegroup",
+        ],
+    },
+    shared: {
+        srcs: [
+          "shared_source.cpp",
+          "shared_source.cc",
+          "shared_source.c",
+          "shared_source.s",
+          "shared_source.S",
+          ":shared_filegroup",
+        ],
+    },
+    bazel_module: { bp2build_available: true },
+}
+
+filegroup {
+    name: "both_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+
+filegroup {
+    name: "shared_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+
+filegroup {
+    name: "static_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both_source.cpp",
+        "both_source.cc",
+        ":both_filegroup_cpp_srcs",
+        "static_source.cpp",
+        "static_source.cc",
+        ":static_filegroup_cpp_srcs",
+    ]`,
+				"srcs_as": `[
+        "both_source.s",
+        "both_source.S",
+        ":both_filegroup_as_srcs",
+        "static_source.s",
+        "static_source.S",
+        ":static_filegroup_as_srcs",
+    ]`,
+				"srcs_c": `[
+        "both_source.c",
+        ":both_filegroup_c_srcs",
+        "static_source.c",
+        ":static_filegroup_c_srcs",
+    ]`,
+			}),
+			makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"local_includes": `["."]`,
+				"srcs": `[
+        "both_source.cpp",
+        "both_source.cc",
+        ":both_filegroup_cpp_srcs",
+        "shared_source.cpp",
+        "shared_source.cc",
+        ":shared_filegroup_cpp_srcs",
+    ]`,
+				"srcs_as": `[
+        "both_source.s",
+        "both_source.S",
+        ":both_filegroup_as_srcs",
+        "shared_source.s",
+        "shared_source.S",
+        ":shared_filegroup_as_srcs",
+    ]`,
+				"srcs_c": `[
+        "both_source.c",
+        ":both_filegroup_c_srcs",
+        "shared_source.c",
+        ":shared_filegroup_c_srcs",
+    ]`,
+			})}})
+}
+
+func TestCcLibraryNonConfiguredVersionScript(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library non-configured version script",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
 cc_library {
     name: "a",
     srcs: ["a.cpp"],
     version_script: "v.map",
     bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = ["-Ifoo/bar"],
-    srcs = ["a.cpp"],
-    version_script = "v.map",
-)`},
 		},
-		{
-			description:                        "cc_library configured version script",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
-		cc_library {
-		   name: "a",
-		   srcs: ["a.cpp"],
-		   arch: {
-		     arm: {
-		       version_script: "arm.map",
-		     },
-		     arm64: {
-		       version_script: "arm64.map",
-		     },
-		   },
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"additional_linker_inputs": `["v.map"]`,
+			"linkopts":                 `["-Wl,--version-script,$(location v.map)"]`,
+			"srcs":                     `["a.cpp"]`,
+		}),
+	},
+	)
+}
 
-		   bazel_module: { bp2build_available: true },
-		}
-		`,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = ["-Ifoo/bar"],
-    srcs = ["a.cpp"],
-    version_script = select({
-        "//build/bazel/platforms/arch:arm": "arm.map",
-        "//build/bazel/platforms/arch:arm64": "arm64.map",
-        "//conditions:default": None,
-    }),
-)`},
+func TestCcLibraryConfiguredVersionScript(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library configured version script",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+   name: "a",
+   srcs: ["a.cpp"],
+   arch: {
+     arm: {
+       version_script: "arm.map",
+     },
+     arm64: {
+       version_script: "arm64.map",
+     },
+   },
+
+   bazel_module: { bp2build_available: true },
+    include_build_directory: false,
+}
+    `,
 		},
-		{
-			description:                        "cc_library shared_libs",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"additional_linker_inputs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.map"],
+        "//build/bazel/platforms/arch:arm64": ["arm64.map"],
+        "//conditions:default": [],
+    })`,
+			"linkopts": `select({
+        "//build/bazel/platforms/arch:arm": ["-Wl,--version-script,$(location arm.map)"],
+        "//build/bazel/platforms/arch:arm64": ["-Wl,--version-script,$(location arm64.map)"],
+        "//conditions:default": [],
+    })`,
+			"srcs": `["a.cpp"]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibrarySharedLibs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library shared_libs",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "mylib",
-    bazel_module: { bp2build_available: true },
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library {
     name: "a",
     shared_libs: ["mylib",],
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = ["-Ifoo/bar"],
-    dynamic_deps = [":mylib"],
-)`, `cc_library(
-    name = "mylib",
-    copts = ["-Ifoo/bar"],
-)`},
-		},
-		{
-			description:                        "cc_library pack_relocations test",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"implementation_dynamic_deps": `[":mylib"]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryFeatures(t *testing.T) {
+	expected_targets := []string{}
+	expected_targets = append(expected_targets, makeCcLibraryTargets("a", attrNameToString{
+		"features": `[
+        "disable_pack_relocations",
+        "-no_undefined_symbols",
+    ]`,
+		"srcs": `["a.cpp"]`,
+	})...)
+	expected_targets = append(expected_targets, makeCcLibraryTargets("b", attrNameToString{
+		"features": `select({
+        "//build/bazel/platforms/arch:x86_64": [
+            "disable_pack_relocations",
+            "-no_undefined_symbols",
+        ],
+        "//conditions:default": [],
+    })`,
+		"srcs": `["b.cpp"]`,
+	})...)
+	expected_targets = append(expected_targets, makeCcLibraryTargets("c", attrNameToString{
+		"features": `select({
+        "//build/bazel/platforms/os:darwin": [
+            "disable_pack_relocations",
+            "-no_undefined_symbols",
+        ],
+        "//conditions:default": [],
+    })`,
+		"srcs": `["c.cpp"]`,
+	})...)
+
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library pack_relocations test",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     srcs: ["a.cpp"],
     pack_relocations: false,
-    bazel_module: { bp2build_available: true },
+    allow_undefined_symbols: true,
+    include_build_directory: false,
 }
 
 cc_library {
@@ -443,10 +1001,11 @@
     srcs: ["b.cpp"],
     arch: {
         x86_64: {
-		pack_relocations: false,
-	},
+            pack_relocations: false,
+            allow_undefined_symbols: true,
+        },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 
 cc_library {
@@ -454,169 +1013,1447 @@
     srcs: ["c.cpp"],
     target: {
         darwin: {
-		pack_relocations: false,
-	},
+            pack_relocations: false,
+            allow_undefined_symbols: true,
+        },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }`,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = ["-Ifoo/bar"],
-    linkopts = ["-Wl,--pack-dyn-relocs=none"],
-    srcs = ["a.cpp"],
-)`, `cc_library(
-    name = "b",
-    copts = ["-Ifoo/bar"],
-    linkopts = select({
-        "//build/bazel/platforms/arch:x86_64": ["-Wl,--pack-dyn-relocs=none"],
-        "//conditions:default": [],
-    }),
-    srcs = ["b.cpp"],
-)`, `cc_library(
-    name = "c",
-    copts = ["-Ifoo/bar"],
-    linkopts = select({
-        "//build/bazel/platforms/os:darwin": ["-Wl,--pack-dyn-relocs=none"],
-        "//conditions:default": [],
-    }),
-    srcs = ["c.cpp"],
-)`},
-		},
-		{
-			description:                        "cc_library spaces in copts",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		expectedBazelTargets: expected_targets,
+	})
+}
+
+func TestCcLibrarySpacesInCopts(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library spaces in copts",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
 cc_library {
     name: "a",
     cflags: ["-include header.h",],
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"copts": `[
         "-include",
         "header.h",
-        "-Ifoo/bar",
-    ],
-)`},
-		},
-		{
-			description:                        "cc_library cppflags goes into copts",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `cc_library {
+    ]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryCppFlagsGoesIntoCopts(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library cppflags usage",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `cc_library {
     name: "a",
     srcs: ["a.cpp"],
-    cflags: [
-		"-Wall",
-	],
+    cflags: ["-Wall"],
     cppflags: [
         "-fsigned-char",
         "-pedantic",
-	],
+    ],
     arch: {
         arm64: {
             cppflags: ["-DARM64=1"],
-		},
-	},
+        },
+    },
     target: {
         android: {
             cppflags: ["-DANDROID=1"],
-		},
-	},
-    bazel_module: { bp2build_available: true  },
+        },
+    },
+    include_build_directory: false,
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
-    name = "a",
-    copts = [
-        "-Wall",
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"copts": `["-Wall"]`,
+			"cppflags": `[
         "-fsigned-char",
         "-pedantic",
-        "-Ifoo/bar",
     ] + select({
         "//build/bazel/platforms/arch:arm64": ["-DARM64=1"],
         "//conditions:default": [],
     }) + select({
         "//build/bazel/platforms/os:android": ["-DANDROID=1"],
         "//conditions:default": [],
-    }),
-    srcs = ["a.cpp"],
-)`},
+    })`,
+			"srcs": `["a.cpp"]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryExcludeLibs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library {
+    name: "foo_static",
+    srcs: ["common.c"],
+    whole_static_libs: [
+        "arm_whole_static_lib_excludes",
+        "malloc_not_svelte_whole_static_lib_excludes"
+    ],
+    static_libs: [
+        "arm_static_lib_excludes",
+        "malloc_not_svelte_static_lib_excludes"
+    ],
+    shared_libs: [
+        "arm_shared_lib_excludes",
+    ],
+    arch: {
+        arm: {
+            exclude_shared_libs: [
+                 "arm_shared_lib_excludes",
+            ],
+            exclude_static_libs: [
+                "arm_static_lib_excludes",
+                "arm_whole_static_lib_excludes",
+            ],
+        },
+    },
+    product_variables: {
+        malloc_not_svelte: {
+            shared_libs: ["malloc_not_svelte_shared_lib"],
+            whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
+            exclude_static_libs: [
+                "malloc_not_svelte_static_lib_excludes",
+                "malloc_not_svelte_whole_static_lib_excludes",
+            ],
+        },
+    },
+    include_build_directory: false,
+}
+
+cc_library {
+    name: "arm_whole_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_whole_static_lib",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_whole_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "arm_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "arm_shared_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_shared_lib",
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo_static", attrNameToString{
+			"implementation_deps": `select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_static_lib_excludes_bp2build_cc_library_static"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [],
+        "//conditions:default": [":malloc_not_svelte_static_lib_excludes_bp2build_cc_library_static"],
+    })`,
+			"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_shared_lib_excludes"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_shared_lib"],
+        "//conditions:default": [],
+    })`,
+			"srcs_c": `["common.c"]`,
+			"whole_archive_deps": `select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_whole_static_lib_excludes_bp2build_cc_library_static"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib_bp2build_cc_library_static"],
+        "//conditions:default": [":malloc_not_svelte_whole_static_lib_excludes_bp2build_cc_library_static"],
+    })`,
+		}),
+	},
+	)
+}
+
+func TestCCLibraryNoCrtTrue(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - nocrt: true emits attribute",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
 		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    nocrt: true,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"link_crt": `False`,
+			"srcs":     `["impl.cpp"]`,
+		}),
+	},
+	)
+}
+
+func TestCCLibraryNoCrtFalse(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - nocrt: false - does not emit attribute",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    nocrt: false,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+		}),
+	})
+}
+
+func TestCCLibraryNoCrtArchVariant(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - nocrt in select",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            nocrt: true,
+        },
+        x86: {
+            nocrt: false,
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedErr: fmt.Errorf("module \"foo-lib\": nocrt is not supported for arch variants"),
+	})
+}
+
+func TestCCLibraryNoLibCrtTrue(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: true,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs":       `["impl.cpp"]`,
+			"use_libcrt": `False`,
+		}),
+	})
+}
+
+func makeCcLibraryTargets(name string, attrs attrNameToString) []string {
+	STATIC_ONLY_ATTRS := map[string]bool{}
+	SHARED_ONLY_ATTRS := map[string]bool{
+		"link_crt":                 true,
+		"additional_linker_inputs": true,
+		"linkopts":                 true,
+		"strip":                    true,
+		"stubs_symbol_file":        true,
+		"stubs_versions":           true,
+		"inject_bssl_hash":         true,
 	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
+	sharedAttrs := attrNameToString{}
+	staticAttrs := attrNameToString{}
+	for key, val := range attrs {
+		if _, staticOnly := STATIC_ONLY_ATTRS[key]; !staticOnly {
+			sharedAttrs[key] = val
 		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig) // TODO(jingwen): make this the default for all tests
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
+		if _, sharedOnly := SHARED_ONLY_ATTRS[key]; !sharedOnly {
+			staticAttrs[key] = val
 		}
 	}
+	sharedTarget := makeBazelTarget("cc_library_shared", name, sharedAttrs)
+	staticTarget := makeBazelTarget("cc_library_static", name+"_bp2build_cc_library_static", staticAttrs)
+
+	return []string{staticTarget, sharedTarget}
+}
+
+func TestCCLibraryNoLibCrtFalse(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: false,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs":       `["impl.cpp"]`,
+			"use_libcrt": `True`,
+		}),
+	})
+}
+
+func TestCCLibraryNoLibCrtArchVariant(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            no_libcrt: true,
+        },
+        x86: {
+            no_libcrt: true,
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+			"use_libcrt": `select({
+        "//build/bazel/platforms/arch:arm": False,
+        "//build/bazel/platforms/arch:x86": False,
+        "//conditions:default": None,
+    })`,
+		}),
+	})
+}
+
+func TestCCLibraryNoLibCrtArchAndTargetVariant(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            no_libcrt: true,
+        },
+        x86: {
+            no_libcrt: true,
+        },
+    },
+    target: {
+        darwin: {
+            no_libcrt: true,
+        }
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+			"use_libcrt": `select({
+        "//build/bazel/platforms/os_arch:android_arm": False,
+        "//build/bazel/platforms/os_arch:android_x86": False,
+        "//build/bazel/platforms/os_arch:darwin_arm64": False,
+        "//build/bazel/platforms/os_arch:darwin_x86_64": False,
+        "//build/bazel/platforms/os_arch:linux_glibc_x86": False,
+        "//build/bazel/platforms/os_arch:linux_musl_x86": False,
+        "//build/bazel/platforms/os_arch:windows_x86": False,
+        "//conditions:default": None,
+    })`,
+		}),
+	})
+}
+
+func TestCCLibraryNoLibCrtArchAndTargetVariantConflict(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            no_libcrt: true,
+        },
+        // This is expected to override the value for darwin_x86_64.
+        x86_64: {
+            no_libcrt: true,
+        },
+    },
+    target: {
+        darwin: {
+            no_libcrt: false,
+        }
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["impl.cpp"]`,
+			"use_libcrt": `select({
+        "//build/bazel/platforms/os_arch:android_arm": False,
+        "//build/bazel/platforms/os_arch:android_x86_64": False,
+        "//build/bazel/platforms/os_arch:darwin_arm64": True,
+        "//build/bazel/platforms/os_arch:darwin_x86_64": False,
+        "//build/bazel/platforms/os_arch:linux_bionic_x86_64": False,
+        "//build/bazel/platforms/os_arch:linux_glibc_x86_64": False,
+        "//build/bazel/platforms/os_arch:linux_musl_x86_64": False,
+        "//build/bazel/platforms/os_arch:windows_x86_64": False,
+        "//conditions:default": None,
+    })`,
+		}),
+	})
+}
+
+func TestCcLibraryStrip(t *testing.T) {
+	expectedTargets := []string{}
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("all", attrNameToString{
+		"strip": `{
+        "all": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols", attrNameToString{
+		"strip": `{
+        "keep_symbols": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols_and_debug_frame", attrNameToString{
+		"strip": `{
+        "keep_symbols_and_debug_frame": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("keep_symbols_list", attrNameToString{
+		"strip": `{
+        "keep_symbols_list": ["symbol"],
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("none", attrNameToString{
+		"strip": `{
+        "none": True,
+    }`,
+	})...)
+	expectedTargets = append(expectedTargets, makeCcLibraryTargets("nothing", attrNameToString{})...)
+
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library strip args",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "nothing",
+    include_build_directory: false,
+}
+cc_library {
+    name: "keep_symbols",
+    strip: {
+        keep_symbols: true,
+    },
+    include_build_directory: false,
+}
+cc_library {
+    name: "keep_symbols_and_debug_frame",
+    strip: {
+        keep_symbols_and_debug_frame: true,
+    },
+    include_build_directory: false,
+}
+cc_library {
+    name: "none",
+    strip: {
+        none: true,
+    },
+    include_build_directory: false,
+}
+cc_library {
+    name: "keep_symbols_list",
+    strip: {
+        keep_symbols_list: ["symbol"],
+    },
+    include_build_directory: false,
+}
+cc_library {
+    name: "all",
+    strip: {
+        all: true,
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: expectedTargets,
+	})
+}
+
+func TestCcLibraryStripWithArch(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library strip args",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "multi-arch",
+    target: {
+        darwin: {
+            strip: {
+                keep_symbols_list: ["foo", "bar"]
+            }
+        },
+    },
+    arch: {
+        arm: {
+            strip: {
+                keep_symbols_and_debug_frame: true,
+            },
+        },
+        arm64: {
+            strip: {
+                keep_symbols: true,
+            },
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("multi-arch", attrNameToString{
+			"strip": `{
+        "keep_symbols": select({
+            "//build/bazel/platforms/arch:arm64": True,
+            "//conditions:default": None,
+        }),
+        "keep_symbols_and_debug_frame": select({
+            "//build/bazel/platforms/arch:arm": True,
+            "//conditions:default": None,
+        }),
+        "keep_symbols_list": select({
+            "//build/bazel/platforms/os:darwin": [
+                "foo",
+                "bar",
+            ],
+            "//conditions:default": [],
+        }),
+    }`,
+		}),
+	},
+	)
+}
+
+func TestCcLibrary_SystemSharedLibsRootEmpty(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty at root",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "root_empty",
+    system_shared_libs: [],
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("root_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibrary_SystemSharedLibsStaticEmpty(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty for static variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "static_empty",
+    static: {
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "static_empty_bp2build_cc_library_static", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+			makeBazelTarget("cc_library_shared", "static_empty", attrNameToString{}),
+		},
+	})
+}
+
+func TestCcLibrary_SystemSharedLibsSharedEmpty(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty for shared variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "shared_empty",
+    shared: {
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "shared_empty_bp2build_cc_library_static", attrNameToString{}),
+			makeBazelTarget("cc_library_shared", "shared_empty", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+		},
+	})
+}
+
+func TestCcLibrary_SystemSharedLibsSharedBionicEmpty(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty for shared, bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "shared_empty",
+    target: {
+        bionic: {
+            shared: {
+                system_shared_libs: [],
+            }
+        }
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "shared_empty_bp2build_cc_library_static", attrNameToString{}),
+			makeBazelTarget("cc_library_shared", "shared_empty", attrNameToString{
+				"system_dynamic_deps": "[]",
+			}),
+		},
+	})
+}
+
+func TestCcLibrary_SystemSharedLibsLinuxBionicEmpty(t *testing.T) {
+	// Note that this behavior is technically incorrect (it's a simplification).
+	// The correct behavior would be if bp2build wrote `system_dynamic_deps = []`
+	// only for linux_bionic, but `android` had `["libc", "libdl", "libm"].
+	// b/195791252 tracks the fix.
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty for linux_bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "target_linux_bionic_empty",
+    target: {
+        linux_bionic: {
+            system_shared_libs: [],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("target_linux_bionic_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibrary_SystemSharedLibsBionicEmpty(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs empty for bionic variant",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "target_bionic_empty",
+    target: {
+        bionic: {
+            system_shared_libs: [],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("target_bionic_empty", attrNameToString{
+			"system_dynamic_deps": `[]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibrary_SystemSharedLibsSharedAndRoot(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library system_shared_libs set for shared and root",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "libc",
+    bazel_module: { bp2build_available: false },
+}
+cc_library {
+    name: "libm",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "foo",
+    system_shared_libs: ["libc"],
+    shared: {
+        system_shared_libs: ["libm"],
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"system_dynamic_deps": `[":libc"]`,
+			}),
+			makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"system_dynamic_deps": `[
+        ":libc",
+        ":libm",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryOsSelects(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - selects for all os targets",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "foo-lib",
+    srcs: ["base.cpp"],
+    target: {
+        android: {
+            srcs: ["android.cpp"],
+        },
+        linux: {
+            srcs: ["linux.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["linux_glibc.cpp"],
+        },
+        darwin: {
+            srcs: ["darwin.cpp"],
+        },
+        bionic: {
+            srcs: ["bionic.cpp"],
+        },
+        linux_musl: {
+            srcs: ["linux_musl.cpp"],
+        },
+        windows: {
+            srcs: ["windows.cpp"],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("foo-lib", attrNameToString{
+			"srcs": `["base.cpp"] + select({
+        "//build/bazel/platforms/os:android": [
+            "linux.cpp",
+            "bionic.cpp",
+            "android.cpp",
+        ],
+        "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
+        "//build/bazel/platforms/os:linux": [
+            "linux.cpp",
+            "linux_glibc.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            "linux.cpp",
+            "bionic.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_musl": [
+            "linux.cpp",
+            "linux_musl.cpp",
+        ],
+        "//build/bazel/platforms/os:windows": ["windows.cpp"],
+        "//conditions:default": [],
+    })`,
+		}),
+	},
+	)
+}
+
+func TestLibcryptoHashInjection(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library - libcrypto hash injection",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library {
+    name: "libcrypto",
+    target: {
+        android: {
+            inject_bssl_hash: true,
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: makeCcLibraryTargets("libcrypto", attrNameToString{
+			"inject_bssl_hash": `select({
+        "//build/bazel/platforms/os:android": True,
+        "//conditions:default": None,
+    })`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryCppStdWithGnuExtensions_ConvertsToFeatureAttr(t *testing.T) {
+	type testCase struct {
+		cpp_std        string
+		c_std          string
+		gnu_extensions string
+		bazel_cpp_std  string
+		bazel_c_std    string
+	}
+
+	testCases := []testCase{
+		// Existing usages of cpp_std in AOSP are:
+		// experimental, c++11, c++17, c++2a, c++98, gnu++11, gnu++17
+		//
+		// not set, only emit if gnu_extensions is disabled. the default (gnu+17
+		// is set in the toolchain.)
+		{cpp_std: "", gnu_extensions: "", bazel_cpp_std: ""},
+		{cpp_std: "", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "", gnu_extensions: "true", bazel_cpp_std: ""},
+		// experimental defaults to gnu++2a
+		{cpp_std: "experimental", gnu_extensions: "", bazel_cpp_std: "gnu++2a"},
+		{cpp_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++2a", bazel_c_std: "c99"},
+		{cpp_std: "experimental", gnu_extensions: "true", bazel_cpp_std: "gnu++2a"},
+		// Explicitly setting a c++ std does not use replace gnu++ std even if
+		// gnu_extensions is true.
+		// "c++11",
+		{cpp_std: "c++11", gnu_extensions: "", bazel_cpp_std: "c++11"},
+		{cpp_std: "c++11", gnu_extensions: "false", bazel_cpp_std: "c++11", bazel_c_std: "c99"},
+		{cpp_std: "c++11", gnu_extensions: "true", bazel_cpp_std: "c++11"},
+		// "c++17",
+		{cpp_std: "c++17", gnu_extensions: "", bazel_cpp_std: "c++17"},
+		{cpp_std: "c++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "c++17", gnu_extensions: "true", bazel_cpp_std: "c++17"},
+		// "c++2a",
+		{cpp_std: "c++2a", gnu_extensions: "", bazel_cpp_std: "c++2a"},
+		{cpp_std: "c++2a", gnu_extensions: "false", bazel_cpp_std: "c++2a", bazel_c_std: "c99"},
+		{cpp_std: "c++2a", gnu_extensions: "true", bazel_cpp_std: "c++2a"},
+		// "c++98",
+		{cpp_std: "c++98", gnu_extensions: "", bazel_cpp_std: "c++98"},
+		{cpp_std: "c++98", gnu_extensions: "false", bazel_cpp_std: "c++98", bazel_c_std: "c99"},
+		{cpp_std: "c++98", gnu_extensions: "true", bazel_cpp_std: "c++98"},
+		// gnu++ is replaced with c++ if gnu_extensions is explicitly false.
+		// "gnu++11",
+		{cpp_std: "gnu++11", gnu_extensions: "", bazel_cpp_std: "gnu++11"},
+		{cpp_std: "gnu++11", gnu_extensions: "false", bazel_cpp_std: "c++11", bazel_c_std: "c99"},
+		{cpp_std: "gnu++11", gnu_extensions: "true", bazel_cpp_std: "gnu++11"},
+		// "gnu++17",
+		{cpp_std: "gnu++17", gnu_extensions: "", bazel_cpp_std: "gnu++17"},
+		{cpp_std: "gnu++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c99"},
+		{cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17"},
+
+		// some c_std test cases
+		{c_std: "experimental", gnu_extensions: "", bazel_c_std: "gnu11"},
+		{c_std: "experimental", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
+		{c_std: "experimental", gnu_extensions: "true", bazel_c_std: "gnu11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "false", bazel_cpp_std: "c++17", bazel_c_std: "c11"},
+		{c_std: "gnu11", cpp_std: "gnu++17", gnu_extensions: "true", bazel_cpp_std: "gnu++17", bazel_c_std: "gnu11"},
+	}
+	for i, tc := range testCases {
+		name_prefix := fmt.Sprintf("a_%v", i)
+		cppStdProp := ""
+		if tc.cpp_std != "" {
+			cppStdProp = fmt.Sprintf("    cpp_std: \"%s\",", tc.cpp_std)
+		}
+		cStdProp := ""
+		if tc.c_std != "" {
+			cStdProp = fmt.Sprintf("    c_std: \"%s\",", tc.c_std)
+		}
+		gnuExtensionsProp := ""
+		if tc.gnu_extensions != "" {
+			gnuExtensionsProp = fmt.Sprintf("    gnu_extensions: %s,", tc.gnu_extensions)
+		}
+		attrs := attrNameToString{}
+		if tc.bazel_cpp_std != "" {
+			attrs["cpp_std"] = fmt.Sprintf(`"%s"`, tc.bazel_cpp_std)
+		}
+		if tc.bazel_c_std != "" {
+			attrs["c_std"] = fmt.Sprintf(`"%s"`, tc.bazel_c_std)
+		}
+
+		runCcLibraryTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library",
+			moduleTypeUnderTestFactory: cc.LibraryFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library {
+	name: "%s_full",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: makeCcLibraryTargets(name_prefix+"_full", attrs),
+		})
+
+		runCcLibraryStaticTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library_static with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library_static",
+			moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library_static {
+	name: "%s_static",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: []string{
+				makeBazelTarget("cc_library_static", name_prefix+"_static", attrs),
+			},
+		})
+
+		runCcLibrarySharedTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf(
+				"cc_library_shared with cpp_std: %s and gnu_extensions: %s", tc.cpp_std, tc.gnu_extensions),
+			moduleTypeUnderTest:        "cc_library_shared",
+			moduleTypeUnderTestFactory: cc.LibrarySharedFactory,
+			blueprint: soongCcLibraryPreamble + fmt.Sprintf(`
+cc_library_shared {
+	name: "%s_shared",
+%s // cpp_std: *string
+%s // c_std: *string
+%s // gnu_extensions: *bool
+	include_build_directory: false,
+}
+`, name_prefix, cppStdProp, cStdProp, gnuExtensionsProp),
+			expectedBazelTargets: []string{
+				makeBazelTarget("cc_library_shared", name_prefix+"_shared", attrs),
+			},
+		})
+	}
+}
+
+func TestCcLibraryProtoSimple(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoNoCanonicalPathFromRoot(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: { canonical_path_from_root: false},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs":                `["foo.proto"]`,
+				"strip_import_prefix": `""`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoExplicitCanonicalPathFromRoot(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: { canonical_path_from_root: true},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoFull(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		type: "full",
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_proto_library", "foo_cc_proto", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto"]`,
+				"deps":                              `[":libprotobuf-cpp-full"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-full"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoLite(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		type: "lite",
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+				"deps":                              `[":libprotobuf-cpp-lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps": `[":libprotobuf-cpp-lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoExportHeaders(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryProtoFilegroups(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble +
+			simpleModuleDoNotConvertBp2build("filegroup", "a_fg_proto") +
+			simpleModuleDoNotConvertBp2build("filegroup", "b_protos") +
+			simpleModuleDoNotConvertBp2build("filegroup", "c-proto-srcs") +
+			simpleModuleDoNotConvertBp2build("filegroup", "proto-srcs-d") + `
+cc_library {
+	name: "a",
+	srcs: [":a_fg_proto"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "b",
+	srcs: [":b_protos"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "c",
+	srcs: [":c-proto-srcs"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}
+
+cc_library {
+	name: "d",
+	srcs: [":proto-srcs-d"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "a_proto", attrNameToString{
+				"srcs": `[":a_fg_proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "a_cc_proto_lite", attrNameToString{
+				"deps": `[":a_proto"]`,
+			}), makeBazelTarget("cc_library_static", "a_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "a", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":a_cc_proto_lite"]`,
+				"srcs":               `[":a_fg_proto_cpp_srcs"]`,
+				"srcs_as":            `[":a_fg_proto_as_srcs"]`,
+				"srcs_c":             `[":a_fg_proto_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "b_proto", attrNameToString{
+				"srcs": `[":b_protos"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "b_cc_proto_lite", attrNameToString{
+				"deps": `[":b_proto"]`,
+			}), makeBazelTarget("cc_library_static", "b_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "b", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":b_cc_proto_lite"]`,
+				"srcs":               `[":b_protos_cpp_srcs"]`,
+				"srcs_as":            `[":b_protos_as_srcs"]`,
+				"srcs_c":             `[":b_protos_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "c_proto", attrNameToString{
+				"srcs": `[":c-proto-srcs"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "c_cc_proto_lite", attrNameToString{
+				"deps": `[":c_proto"]`,
+			}), makeBazelTarget("cc_library_static", "c_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "c", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":c_cc_proto_lite"]`,
+				"srcs":               `[":c-proto-srcs_cpp_srcs"]`,
+				"srcs_as":            `[":c-proto-srcs_as_srcs"]`,
+				"srcs_c":             `[":c-proto-srcs_c_srcs"]`,
+			}), makeBazelTarget("proto_library", "d_proto", attrNameToString{
+				"srcs": `[":proto-srcs-d"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "d_cc_proto_lite", attrNameToString{
+				"deps": `[":d_proto"]`,
+			}), makeBazelTarget("cc_library_static", "d_bp2build_cc_library_static", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}), makeBazelTarget("cc_library_shared", "d", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":d_cc_proto_lite"]`,
+				"srcs":               `[":proto-srcs-d_cpp_srcs"]`,
+				"srcs_as":            `[":proto-srcs-d_as_srcs"]`,
+				"srcs_c":             `[":proto-srcs-d_c_srcs"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryDisabledArchAndTarget(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.cpp"],
+	target: {
+		darwin: {
+			enabled: false,
+		},
+		windows: {
+			enabled: false,
+		},
+		linux_glibc_x86: {
+			enabled: false,
+		},
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: makeCcLibraryTargets("foo", attrNameToString{
+			"srcs": `["foo.cpp"]`,
+			"target_compatible_with": `select({
+        "//build/bazel/platforms/os_arch:darwin_arm64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:darwin_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:linux_glibc_x86": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:windows_x86": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:windows_x86_64": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+		}),
+	})
+}
+
+func TestCcLibraryDisabledArchAndTargetWithDefault(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.cpp"],
+  enabled: false,
+	target: {
+		darwin: {
+			enabled: true,
+		},
+		windows: {
+			enabled: false,
+		},
+		linux_glibc_x86: {
+			enabled: false,
+		},
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: makeCcLibraryTargets("foo", attrNameToString{
+			"srcs": `["foo.cpp"]`,
+			"target_compatible_with": `select({
+        "//build/bazel/platforms/os_arch:darwin_arm64": [],
+        "//build/bazel/platforms/os_arch:darwin_x86_64": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    })`,
+		}),
+	})
+}
+
+func TestCcLibrarySharedDisabled(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.cpp"],
+	enabled: false,
+	shared: {
+		enabled: true,
+	},
+	target: {
+		android: {
+			shared: {
+				enabled: false,
+			},
+		}
+  },
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+			"srcs":                   `["foo.cpp"]`,
+			"target_compatible_with": `["@platforms//:incompatible"]`,
+		}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+			"srcs": `["foo.cpp"]`,
+			"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+		}),
+		},
+	})
+}
+
+func TestCcLibraryStaticDisabledForSomeArch(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	srcs: ["foo.cpp"],
+	shared: {
+		enabled: false
+	},
+	target: {
+		darwin: {
+			enabled: true,
+		},
+		windows: {
+			enabled: false,
+		},
+		linux_glibc_x86: {
+			shared: {
+				enabled: true,
+			},
+		},
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{makeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", attrNameToString{
+			"srcs": `["foo.cpp"]`,
+			"target_compatible_with": `select({
+        "//build/bazel/platforms/os:windows": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+		}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+			"srcs": `["foo.cpp"]`,
+			"target_compatible_with": `select({
+        "//build/bazel/platforms/os_arch:darwin_arm64": [],
+        "//build/bazel/platforms/os_arch:darwin_x86_64": [],
+        "//build/bazel/platforms/os_arch:linux_glibc_x86": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    })`,
+		}),
+		}})
+}
+
+func TestCcLibraryStubs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                "cc_library stubs",
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    stubs: { symbol_file: "a.map.txt", versions: ["28", "29", "current"] },
+    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: makeCcLibraryTargets("a", attrNameToString{
+			"stubs_symbol_file": `"a.map.txt"`,
+			"stubs_versions": `[
+        "28",
+        "29",
+        "current",
+    ]`,
+		}),
+	},
+	)
+}
+
+func TestCcLibraryEscapeLdflags(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:        "cc_library",
+		moduleTypeUnderTestFactory: cc.LibraryFactory,
+		blueprint: soongCcProtoPreamble + `cc_library {
+	name: "foo",
+	ldflags: ["-Wl,--rpath,${ORIGIN}"],
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: makeCcLibraryTargets("foo", attrNameToString{
+			"linkopts": `["-Wl,--rpath,$${ORIGIN}"]`,
+		}),
+	})
 }
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index 0905aba..e5bb120 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -15,28 +15,17 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/cc"
-	"strings"
-	"testing"
 )
 
 const (
 	// See cc/testing.go for more context
 	soongCcLibraryHeadersPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
-}
-
-toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+    name: "linux_bionic_supported",
 }`
 )
 
@@ -64,49 +53,46 @@
 			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
 		}
 	}
-
 }
 
-func TestCcLibraryHeadersBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "cc_library_headers test",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			filesystem: map[string]string{
-				"lib-1/lib1a.h":                        "",
-				"lib-1/lib1b.h":                        "",
-				"lib-2/lib2a.h":                        "",
-				"lib-2/lib2b.h":                        "",
-				"dir-1/dir1a.h":                        "",
-				"dir-1/dir1b.h":                        "",
-				"dir-2/dir2a.h":                        "",
-				"dir-2/dir2b.h":                        "",
-				"arch_arm64_exported_include_dir/a.h":  "",
-				"arch_x86_exported_include_dir/b.h":    "",
-				"arch_x86_64_exported_include_dir/c.h": "",
-			},
-			bp: soongCcLibraryHeadersPreamble + `
+func registerCcLibraryHeadersModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+}
+
+func runCcLibraryHeadersTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibraryHeadersModuleTypes, tc)
+}
+
+func TestCcLibraryHeadersSimple(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                "cc_library_headers test",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem: map[string]string{
+			"lib-1/lib1a.h":                        "",
+			"lib-1/lib1b.h":                        "",
+			"lib-2/lib2a.h":                        "",
+			"lib-2/lib2b.h":                        "",
+			"dir-1/dir1a.h":                        "",
+			"dir-1/dir1b.h":                        "",
+			"dir-2/dir2a.h":                        "",
+			"dir-2/dir2b.h":                        "",
+			"arch_arm64_exported_include_dir/a.h":  "",
+			"arch_x86_exported_include_dir/b.h":    "",
+			"arch_x86_64_exported_include_dir/c.h": "",
+		},
+		blueprint: soongCcLibraryHeadersPreamble + `
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
     name: "lib-2",
     export_include_dirs: ["lib-2"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
@@ -116,7 +102,7 @@
 
     arch: {
         arm64: {
-	    // We expect dir-1 headers to be dropped, because dir-1 is already in export_include_dirs
+      // We expect dir-1 headers to be dropped, because dir-1 is already in export_include_dirs
             export_include_dirs: ["arch_arm64_exported_include_dir", "dir-1"],
         },
         x86: {
@@ -126,17 +112,14 @@
             export_include_dirs: ["arch_x86_64_exported_include_dir"],
         },
     },
+    sdk_version: "current",
+    min_sdk_version: "29",
 
     // TODO: Also support export_header_lib_headers
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
-    name = "foo_headers",
-    copts = ["-I."],
-    deps = [
-        ":lib-1",
-        ":lib-2",
-    ],
-    includes = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"export_includes": `[
         "dir-1",
         "dir-2",
     ] + select({
@@ -144,132 +127,133 @@
         "//build/bazel/platforms/arch:x86": ["arch_x86_exported_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["arch_x86_64_exported_include_dir"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_headers(
-    name = "lib-1",
-    copts = ["-I."],
-    includes = ["lib-1"],
-)`, `cc_library_headers(
-    name = "lib-2",
-    copts = ["-I."],
-    includes = ["lib-2"],
-)`},
+    })`,
+				"implementation_deps": `[
+        ":lib-1",
+        ":lib-2",
+    ]`,
+        "sdk_version": `"current"`,
+        "min_sdk_version": `"29"`,
+			}),
 		},
-		{
-			description:                        "cc_library_headers test with os-specific header_libs props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `
-cc_library_headers { name: "android-lib" }
-cc_library_headers { name: "base-lib" }
-cc_library_headers { name: "darwin-lib" }
-cc_library_headers { name: "fuchsia-lib" }
-cc_library_headers { name: "linux-lib" }
-cc_library_headers { name: "linux_bionic-lib" }
-cc_library_headers { name: "windows-lib" }
+	})
+}
+
+func TestCcLibraryHeadersOsSpecificHeader(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                "cc_library_headers test with os-specific header_libs props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers {
+    name: "android-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "base-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "darwin-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "linux-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "linux_bionic-lib",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_headers {
+    name: "windows-lib",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_headers {
     name: "foo_headers",
     header_libs: ["base-lib"],
     target: {
         android: { header_libs: ["android-lib"] },
         darwin: { header_libs: ["darwin-lib"] },
-        fuchsia: { header_libs: ["fuchsia-lib"] },
         linux_bionic: { header_libs: ["linux_bionic-lib"] },
         linux_glibc: { header_libs: ["linux-lib"] },
         windows: { header_libs: ["windows-lib"] },
     },
-    bazel_module: { bp2build_available: true },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
-    name = "android-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "base-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "darwin-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "foo_headers",
-    copts = ["-I."],
-    deps = [":base-lib"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"implementation_deps": `[":base-lib"] + select({
         "//build/bazel/platforms/os:android": [":android-lib"],
         "//build/bazel/platforms/os:darwin": [":darwin-lib"],
-        "//build/bazel/platforms/os:fuchsia": [":fuchsia-lib"],
         "//build/bazel/platforms/os:linux": [":linux-lib"],
         "//build/bazel/platforms/os:linux_bionic": [":linux_bionic-lib"],
         "//build/bazel/platforms/os:windows": [":windows-lib"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_headers(
-    name = "fuchsia-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "linux-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "linux_bionic-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "windows-lib",
-    copts = ["-I."],
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `
-cc_library_headers { name: "android-lib" }
-cc_library_headers { name: "exported-lib" }
+	})
+}
+
+func TestCcLibraryHeadersOsSpecficHeaderLibsExportHeaderLibHeaders(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers {
+    name: "android-lib",
+    bazel_module: { bp2build_available: false },
+  }
+cc_library_headers {
+    name: "exported-lib",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_headers {
     name: "foo_headers",
     target: {
-        android: { header_libs: ["android-lib"], export_header_lib_headers: ["exported-lib"] },
+        android: {
+            header_libs: ["android-lib", "exported-lib"],
+            export_header_lib_headers: ["exported-lib"]
+        },
     },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
-    name = "android-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "exported-lib",
-    copts = ["-I."],
-)`, `cc_library_headers(
-    name = "foo_headers",
-    copts = ["-I."],
-    deps = select({
-        "//build/bazel/platforms/os:android": [
-            ":android-lib",
-            ":exported-lib",
-        ],
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"deps": `select({
+        "//build/bazel/platforms/os:android": [":exported-lib"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+				"implementation_deps": `select({
+        "//build/bazel/platforms/os:android": [":android-lib"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `cc_library_headers {
+	})
+}
+
+func TestCcLibraryHeadersArchAndTargetExportSystemIncludes(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem:                 map[string]string{},
+		blueprint: soongCcLibraryPreamble + `cc_library_headers {
     name: "foo_headers",
     export_system_include_dirs: [
-	"shared_include_dir",
+        "shared_include_dir",
     ],
     target: {
-	android: {
-	    export_system_include_dirs: [
-		"android_include_dir",
+        android: {
+            export_system_include_dirs: [
+                "android_include_dir",
             ],
-	},
+        },
         linux_glibc: {
             export_system_include_dirs: [
                 "linux_include_dir",
@@ -283,21 +267,21 @@
     },
     arch: {
         arm: {
-	    export_system_include_dirs: [
-		"arm_include_dir",
+            export_system_include_dirs: [
+                "arm_include_dir",
             ],
-	},
+        },
         x86_64: {
             export_system_include_dirs: [
                 "x86_64_include_dir",
             ],
         },
     },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
-    name = "foo_headers",
-    copts = ["-I."],
-    includes = ["shared_include_dir"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "foo_headers", attrNameToString{
+				"export_system_includes": `["shared_include_dir"] + select({
         "//build/bazel/platforms/arch:arm": ["arm_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64_include_dir"],
         "//conditions:default": [],
@@ -306,67 +290,41 @@
         "//build/bazel/platforms/os:darwin": ["darwin_include_dir"],
         "//build/bazel/platforms/os:linux": ["linux_include_dir"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
 		},
-	}
+	})
+}
 
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-
-		// TODO(jingwen): make this default for all bp2build tests
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+func TestCcLibraryHeadersNoCrtIgnored(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                "cc_library_headers test",
+		moduleTypeUnderTest:        "cc_library_headers",
+		moduleTypeUnderTestFactory: cc.LibraryHeaderFactory,
+		filesystem: map[string]string{
+			"lib-1/lib1a.h":                        "",
+			"lib-1/lib1b.h":                        "",
+			"lib-2/lib2a.h":                        "",
+			"lib-2/lib2b.h":                        "",
+			"dir-1/dir1a.h":                        "",
+			"dir-1/dir1b.h":                        "",
+			"dir-2/dir2a.h":                        "",
+			"dir-2/dir2b.h":                        "",
+			"arch_arm64_exported_include_dir/a.h":  "",
+			"arch_x86_exported_include_dir/b.h":    "",
+			"arch_x86_64_exported_include_dir/c.h": "",
+		},
+		blueprint: soongCcLibraryHeadersPreamble + `
+cc_library_headers {
+    name: "lib-1",
+    export_include_dirs: ["lib-1"],
+    no_libcrt: true,
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_headers", "lib-1", attrNameToString{
+				"export_includes": `["lib-1"]`,
+			}),
+		},
+	})
 }
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
new file mode 100644
index 0000000..22c9dfe
--- /dev/null
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -0,0 +1,498 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+const (
+	// See cc/testing.go for more context
+	// TODO(alexmarquez): Split out the preamble into common code?
+	soongCcLibrarySharedPreamble = soongCcLibraryStaticPreamble
+)
+
+func registerCcLibrarySharedModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+}
+
+func runCcLibrarySharedTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "cc_library_shared"
+	(&tc).moduleTypeUnderTestFactory = cc.LibrarySharedFactory
+	runBp2BuildTestCase(t, registerCcLibrarySharedModuleTypes, tc)
+}
+
+func TestCcLibrarySharedSimple(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared simple overall test",
+		filesystem: map[string]string{
+			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+			"include_dir_1/include_dir_1_a.h": "",
+			"include_dir_1/include_dir_1_b.h": "",
+			"include_dir_2/include_dir_2_a.h": "",
+			"include_dir_2/include_dir_2_b.h": "",
+			// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+			"local_include_dir_1/local_include_dir_1_a.h": "",
+			"local_include_dir_1/local_include_dir_1_b.h": "",
+			"local_include_dir_2/local_include_dir_2_a.h": "",
+			"local_include_dir_2/local_include_dir_2_b.h": "",
+			// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+			"export_include_dir_1/export_include_dir_1_a.h": "",
+			"export_include_dir_1/export_include_dir_1_b.h": "",
+			"export_include_dir_2/export_include_dir_2_a.h": "",
+			"export_include_dir_2/export_include_dir_2_b.h": "",
+			// NOTE: Soong implicitly includes headers in the current directory
+			"implicit_include_1.h": "",
+			"implicit_include_2.h": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_headers {
+    name: "header_lib_1",
+    export_include_dirs: ["header_lib_1"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_headers {
+    name: "header_lib_2",
+    export_include_dirs: ["header_lib_2"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_1",
+    srcs: ["shared_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "shared_lib_2",
+    srcs: ["shared_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_1",
+    srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_static {
+    name: "whole_static_lib_2",
+    srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library_shared {
+    name: "foo_shared",
+    srcs: [
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ],
+    cflags: [
+        "-Dflag1",
+        "-Dflag2"
+    ],
+    shared_libs: [
+        "shared_lib_1",
+        "shared_lib_2"
+    ],
+    whole_static_libs: [
+        "whole_static_lib_1",
+        "whole_static_lib_2"
+    ],
+    include_dirs: [
+        "include_dir_1",
+        "include_dir_2",
+    ],
+    local_include_dirs: [
+        "local_include_dir_1",
+        "local_include_dir_2",
+    ],
+    export_include_dirs: [
+        "export_include_dir_1",
+        "export_include_dir_2"
+    ],
+    header_libs: [
+        "header_lib_1",
+        "header_lib_2"
+    ],
+    sdk_version: "current",
+    min_sdk_version: "29",
+
+    // TODO: Also support export_header_lib_headers
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"absolute_includes": `[
+        "include_dir_1",
+        "include_dir_2",
+    ]`,
+				"copts": `[
+        "-Dflag1",
+        "-Dflag2",
+    ]`,
+				"export_includes": `[
+        "export_include_dir_1",
+        "export_include_dir_2",
+    ]`,
+				"implementation_deps": `[
+        ":header_lib_1",
+        ":header_lib_2",
+    ]`,
+				"implementation_dynamic_deps": `[
+        ":shared_lib_1",
+        ":shared_lib_2",
+    ]`,
+				"local_includes": `[
+        "local_include_dir_1",
+        "local_include_dir_2",
+        ".",
+    ]`,
+				"srcs": `[
+        "foo_shared1.cc",
+        "foo_shared2.cc",
+    ]`,
+				"whole_archive_deps": `[
+        ":whole_static_lib_1",
+        ":whole_static_lib_2",
+    ]`,
+        "sdk_version": `"current"`,
+        "min_sdk_version": `"29"`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedArchSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared arch-specific shared_libs with whole_static_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    arch: { arm64: { shared_libs: ["shared_dep"], whole_static_libs: ["static_dep"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep"],
+        "//conditions:default": [],
+    })`,
+				"whole_archive_deps": `select({
+        "//build/bazel/platforms/arch:arm64": [":static_dep"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedOsSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared os-specific shared_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    target: { android: { shared_libs: ["shared_dep"], } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/os:android": [":shared_dep"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedBaseArchOsSpecificSharedLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared base, arch, and os-specific shared_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "shared_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep2",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "shared_dep3",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_shared {
+    name: "foo_shared",
+    shared_libs: ["shared_dep"],
+    target: { android: { shared_libs: ["shared_dep2"] } },
+    arch: { arm64: { shared_libs: ["shared_dep3"] } },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"implementation_dynamic_deps": `[":shared_dep"] + select({
+        "//build/bazel/platforms/arch:arm64": [":shared_dep3"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":shared_dep2"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedSimpleExcludeSrcs(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared simple exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":       "",
+			"foo-a.c":        "",
+			"foo-excluded.c": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["common.c", "foo-*.c"],
+    exclude_srcs: ["foo-excluded.c"],
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"srcs_c": `[
+        "common.c",
+        "foo-a.c",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedStrip(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared stripping",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    strip: {
+        keep_symbols: false,
+        keep_symbols_and_debug_frame: true,
+        keep_symbols_list: ["sym", "sym2"],
+        all: true,
+        none: false,
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"strip": `{
+        "all": True,
+        "keep_symbols": False,
+        "keep_symbols_and_debug_frame": True,
+        "keep_symbols_list": [
+            "sym",
+            "sym2",
+        ],
+        "none": False,
+    }`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedVersionScript(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared version script",
+		filesystem: map[string]string{
+			"version_script": "",
+		},
+		blueprint: soongCcLibrarySharedPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    version_script: "version_script",
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"additional_linker_inputs": `["version_script"]`,
+				"linkopts":                 `["-Wl,--version-script,$(location version_script)"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtTrue(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt: true emits attribute",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    nocrt: true,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"link_crt": `False`,
+				"srcs":     `["impl.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtFalse(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt: false doesn't emit attribute",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    nocrt: false,
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo_shared", attrNameToString{
+				"srcs": `["impl.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedNoCrtArchVariant(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description: "cc_library_shared - nocrt in select",
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_shared {
+    name: "foo_shared",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            nocrt: true,
+        },
+        x86: {
+            nocrt: false,
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedErr: fmt.Errorf("module \"foo_shared\": nocrt is not supported for arch variants"),
+	})
+}
+
+func TestCcLibrarySharedProto(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_shared {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"dynamic_deps":       `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedUseVersionLib(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_shared {
+        name: "foo",
+        use_version_lib: true,
+        include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_shared", "foo", attrNameToString{
+				"use_version_lib": "True",
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedStubs(t *testing.T) {
+	runCcLibrarySharedTestCase(t, bp2buildTestCase{
+		description:                "cc_library_shared stubs",
+		moduleTypeUnderTest:        "cc_library_shared",
+		moduleTypeUnderTestFactory: cc.LibrarySharedFactory,
+		dir:                        "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library_shared {
+	name: "a",
+	stubs: { symbol_file: "a.map.txt", versions: ["28", "29", "current"] },
+	bazel_module: { bp2build_available: true },
+	include_build_directory: false,
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{makeBazelTarget("cc_library_shared", "a", attrNameToString{
+			"stubs_symbol_file": `"a.map.txt"`,
+			"stubs_versions": `[
+        "28",
+        "29",
+        "current",
+    ]`,
+		}),
+		},
+	},
+	)
+}
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index d082db1..be10e86 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -17,7 +17,8 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
-	"strings"
+	"android/soong/genrule"
+
 	"testing"
 )
 
@@ -25,18 +26,7 @@
 	// See cc/testing.go for more context
 	soongCcLibraryStaticPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
-}
-
-toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+    name: "linux_bionic_supported",
 }`
 )
 
@@ -64,76 +54,82 @@
 			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
 		}
 	}
-
 }
 
-func TestCcLibraryStaticBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "cc_library_static test",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
-				"include_dir_1/include_dir_1_a.h": "",
-				"include_dir_1/include_dir_1_b.h": "",
-				"include_dir_2/include_dir_2_a.h": "",
-				"include_dir_2/include_dir_2_b.h": "",
-				// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
-				"local_include_dir_1/local_include_dir_1_a.h": "",
-				"local_include_dir_1/local_include_dir_1_b.h": "",
-				"local_include_dir_2/local_include_dir_2_a.h": "",
-				"local_include_dir_2/local_include_dir_2_b.h": "",
-				// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
-				"export_include_dir_1/export_include_dir_1_a.h": "",
-				"export_include_dir_1/export_include_dir_1_b.h": "",
-				"export_include_dir_2/export_include_dir_2_a.h": "",
-				"export_include_dir_2/export_include_dir_2_b.h": "",
-				// NOTE: Soong implicitly includes headers in the current directory
-				"implicit_include_1.h": "",
-				"implicit_include_2.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+func registerCcLibraryStaticModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+	// Required for system_shared_libs dependencies.
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+}
+
+func runCcLibraryStaticTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+
+	(&tc).moduleTypeUnderTest = "cc_library_static"
+	(&tc).moduleTypeUnderTestFactory = cc.LibraryStaticFactory
+	runBp2BuildTestCase(t, registerCcLibraryStaticModuleTypes, tc)
+}
+
+func TestCcLibraryStaticSimple(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static test",
+		filesystem: map[string]string{
+			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+			"include_dir_1/include_dir_1_a.h": "",
+			"include_dir_1/include_dir_1_b.h": "",
+			"include_dir_2/include_dir_2_a.h": "",
+			"include_dir_2/include_dir_2_b.h": "",
+			// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+			"local_include_dir_1/local_include_dir_1_a.h": "",
+			"local_include_dir_1/local_include_dir_1_b.h": "",
+			"local_include_dir_2/local_include_dir_2_a.h": "",
+			"local_include_dir_2/local_include_dir_2_b.h": "",
+			// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+			"export_include_dir_1/export_include_dir_1_a.h": "",
+			"export_include_dir_1/export_include_dir_1_b.h": "",
+			"export_include_dir_2/export_include_dir_2_a.h": "",
+			"export_include_dir_2/export_include_dir_2_b.h": "",
+			// NOTE: Soong implicitly includes headers in the current directory
+			"implicit_include_1.h": "",
+			"implicit_include_2.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_headers {
     name: "header_lib_1",
     export_include_dirs: ["header_lib_1"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_headers {
     name: "header_lib_2",
     export_include_dirs: ["header_lib_2"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "static_lib_1",
     srcs: ["static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "static_lib_2",
     srcs: ["static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "whole_static_lib_1",
     srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
     name: "whole_static_lib_2",
     srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: false },
 }
 
 cc_library_static {
@@ -163,160 +159,145 @@
         "local_include_dir_2",
     ],
     export_include_dirs: [
-    "export_include_dir_1",
-    "export_include_dir_2"
+        "export_include_dir_1",
+        "export_include_dir_2"
     ],
     header_libs: [
         "header_lib_1",
         "header_lib_2"
     ],
+    sdk_version: "current",
+    min_sdk_version: "29",
 
     // TODO: Also support export_header_lib_headers
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `[
+        "include_dir_1",
+        "include_dir_2",
+    ]`,
+				"copts": `[
         "-Dflag1",
         "-Dflag2",
-        "-Iinclude_dir_1",
-        "-Iinclude_dir_2",
-        "-Ilocal_include_dir_1",
-        "-Ilocal_include_dir_2",
-        "-I.",
-    ],
-    deps = [
+    ]`,
+				"export_includes": `[
+        "export_include_dir_1",
+        "export_include_dir_2",
+    ]`,
+				"implementation_deps": `[
         ":header_lib_1",
         ":header_lib_2",
         ":static_lib_1",
         ":static_lib_2",
-    ],
-    includes = [
-        "export_include_dir_1",
-        "export_include_dir_2",
-    ],
-    linkstatic = True,
-    srcs = [
+    ]`,
+				"local_includes": `[
+        "local_include_dir_1",
+        "local_include_dir_2",
+        ".",
+    ]`,
+				"srcs": `[
         "foo_static1.cc",
         "foo_static2.cc",
-    ],
-    whole_archive_deps = [
+    ]`,
+				"whole_archive_deps": `[
         ":whole_static_lib_1",
         ":whole_static_lib_2",
-    ],
-)`, `cc_library_static(
-    name = "static_lib_1",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["static_lib_1.cc"],
-)`, `cc_library_static(
-    name = "static_lib_2",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["static_lib_2.cc"],
-)`, `cc_library_static(
-    name = "whole_static_lib_1",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["whole_static_lib_1.cc"],
-)`, `cc_library_static(
-    name = "whole_static_lib_2",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["whole_static_lib_2.cc"],
-)`},
+    ]`,
+        "sdk_version": `"current"`,
+        "min_sdk_version": `"29"`,
+			}),
 		},
-		{
-			description:                        "cc_library_static subpackage test",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-				// subsubpackage with subdirectory
-				"subpackage/subsubpackage/Android.bp":                         "",
-				"subpackage/subsubpackage/subsubpackage_header.h":             "",
-				"subpackage/subsubpackage/subdirectory/subdirectory_header.h": "",
-				// subsubsubpackage with subdirectory
-				"subpackage/subsubpackage/subsubsubpackage/Android.bp":                         "",
-				"subpackage/subsubpackage/subsubsubpackage/subsubsubpackage_header.h":          "",
-				"subpackage/subsubpackage/subsubsubpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticSubpackage(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static subpackage test",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+			// subsubpackage with subdirectory
+			"subpackage/subsubpackage/Android.bp":                         "",
+			"subpackage/subsubpackage/subsubpackage_header.h":             "",
+			"subpackage/subsubpackage/subdirectory/subdirectory_header.h": "",
+			// subsubsubpackage with subdirectory
+			"subpackage/subsubpackage/subsubsubpackage/Android.bp":                         "",
+			"subpackage/subsubpackage/subsubsubpackage/subsubsubpackage_header.h":          "",
+			"subpackage/subsubpackage/subsubsubpackage/subdirectory/subdirectory_header.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
-    srcs: [
-    ],
+    srcs: [],
     include_dirs: [
-	"subpackage",
+        "subpackage",
     ],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-I.",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes":    `["."]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static export include dir",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticExportIncludeDir(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static export include dir",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     export_include_dirs: ["subpackage"],
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    includes = ["subpackage"],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_includes": `["subpackage"]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static export system include dir",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticExportSystemIncludeDir(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static export system include dir",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     export_system_include_dirs: ["subpackage"],
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    includes = ["subpackage"],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_system_includes": `["subpackage"]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			dir:                                "subpackage",
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp": `
+	})
+}
+
+func TestCcLibraryStaticManyIncludeDirs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
+		dir:         "subpackage",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp": `
 cc_library_static {
     name: "foo_static",
     // include_dirs are workspace/root relative
@@ -330,270 +311,255 @@
     include_build_directory: true,
     bazel_module: { bp2build_available: true },
 }`,
-				"subpackage/subsubpackage/header.h":          "",
-				"subpackage/subsubpackage2/header.h":         "",
-				"subpackage/exported_subsubpackage/header.h": "",
-				"subpackage2/header.h":                       "",
-				"subpackage3/subsubpackage/header.h":         "",
-			},
-			bp: soongCcLibraryStaticPreamble,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage/subsubpackage",
-        "-Isubpackage2",
-        "-Isubpackage3/subsubpackage",
-        "-Isubpackage/subsubpackage2",
-        "-Isubpackage",
-    ],
-    includes = ["./exported_subsubpackage"],
-    linkstatic = True,
-)`},
+			"subpackage/subsubpackage/header.h":          "",
+			"subpackage/subsubpackage2/header.h":         "",
+			"subpackage/exported_subsubpackage/header.h": "",
+			"subpackage2/header.h":                       "",
+			"subpackage3/subsubpackage/header.h":         "",
 		},
-		{
-			description:                        "cc_library_static include_build_directory disabled",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `[
+        "subpackage/subsubpackage",
+        "subpackage2",
+        "subpackage3/subsubpackage",
+    ]`,
+				"export_includes": `["./exported_subsubpackage"]`,
+				"local_includes": `[
+        "subsubpackage2",
+        ".",
+    ]`,
+			})},
+	})
+}
+
+func TestCcLibraryStaticIncludeBuildDirectoryDisabled(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static include_build_directory disabled",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
     local_include_dirs: ["subpackage2"],
     include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-Isubpackage2",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes":    `["subpackage2"]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static include_build_directory enabled",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage2/Android.bp":                        "",
-				"subpackage2/subpackage2_header.h":              "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticIncludeBuildDirectoryEnabled(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static include_build_directory enabled",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage2/Android.bp":                        "",
+			"subpackage2/subpackage2_header.h":              "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
     local_include_dirs: ["subpackage2"],
     include_build_directory: true,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = [
-        "-Isubpackage",
-        "-Isubpackage2",
-        "-I.",
-    ],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"absolute_includes": `["subpackage"]`,
+				"local_includes": `[
+        "subpackage2",
+        ".",
+    ]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static arch-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
+	})
+}
+
+func TestCcLibraryStaticArchSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch-specific static_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     arch: { arm64: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    deps = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `select({
         "//build/bazel/platforms/arch:arm64": [":static_dep"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = select({
+    })`,
+				"whole_archive_deps": `select({
         "//build/bazel/platforms/arch:arm64": [":static_dep2"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = ["-I."],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = ["-I."],
-    linkstatic = True,
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static os-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
+	})
+}
+
+func TestCcLibraryStaticOsSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static os-specific static_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     target: { android: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    deps = select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `select({
         "//build/bazel/platforms/os:android": [":static_dep"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = select({
+    })`,
+				"whole_archive_deps": `select({
         "//build/bazel/platforms/os:android": [":static_dep2"],
         "//conditions:default": [],
-    }),
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = ["-I."],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = ["-I."],
-    linkstatic = True,
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static base, arch and os-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
-cc_library_static { name: "static_dep2" }
-cc_library_static { name: "static_dep3" }
-cc_library_static { name: "static_dep4" }
+	})
+}
+
+func TestCcLibraryStaticBaseArchOsSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static base, arch and os-specific static_libs",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep2",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep3",
+    bazel_module: { bp2build_available: false },
+}
+cc_library_static {
+    name: "static_dep4",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     static_libs: ["static_dep"],
     whole_static_libs: ["static_dep2"],
     target: { android: { static_libs: ["static_dep3"] } },
     arch: { arm64: { static_libs: ["static_dep4"] } },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    deps = [":static_dep"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `[":static_dep"] + select({
         "//build/bazel/platforms/arch:arm64": [":static_dep4"],
         "//conditions:default": [],
     }) + select({
         "//build/bazel/platforms/os:android": [":static_dep3"],
         "//conditions:default": [],
-    }),
-    linkstatic = True,
-    whole_archive_deps = [":static_dep2"],
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = ["-I."],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep2",
-    copts = ["-I."],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep3",
-    copts = ["-I."],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep4",
-    copts = ["-I."],
-    linkstatic = True,
-)`},
+    })`,
+				"whole_archive_deps": `[":static_dep2"]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static simple exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":       "",
-				"foo-a.c":        "",
-				"foo-excluded.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticSimpleExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static simple exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":       "",
+			"foo-a.c":        "",
+			"foo-excluded.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "foo-*.c"],
     exclude_srcs: ["foo-excluded.c"],
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `[
         "common.c",
         "foo-a.c",
-    ],
-)`},
+    ]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static one arch specific srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":  "",
-				"foo-arm.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticOneArchSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static one arch specific srcs",
+		filesystem: map[string]string{
+			"common.c":  "",
+			"foo-arm.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c"],
-    arch: { arm: { srcs: ["foo-arm.c"] } }
+    arch: { arm: { srcs: ["foo-arm.c"] } },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["foo-arm.c"],
         "//conditions:default": [],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static one arch specific srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":           "",
-				"for-arm.c":          "",
-				"not-for-arm.c":      "",
-				"not-for-anything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticOneArchSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static one arch specific srcs and exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":           "",
+			"for-arm.c":          "",
+			"not-for-arm.c":      "",
+			"not-for-anything.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -601,31 +567,30 @@
     arch: {
         arm: { srcs: ["for-arm.c"], exclude_srcs: ["not-for-arm.c"] },
     },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-arm.c"],
         "//conditions:default": ["not-for-arm.c"],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static arch specific exclude_srcs for 2 architectures",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":      "",
-				"for-arm.c":     "",
-				"for-x86.c":     "",
-				"not-for-arm.c": "",
-				"not-for-x86.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticTwoArchExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch specific exclude_srcs for 2 architectures",
+		filesystem: map[string]string{
+			"common.c":      "",
+			"for-arm.c":     "",
+			"for-x86.c":     "",
+			"not-for-arm.c": "",
+			"not-for-x86.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -634,46 +599,45 @@
         arm: { srcs: ["for-arm.c"], exclude_srcs: ["not-for-arm.c"] },
         x86: { srcs: ["for-x86.c"], exclude_srcs: ["not-for-x86.c"] },
     },
+    include_build_directory: false,
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-arm.c",
             "not-for-x86.c",
+            "for-arm.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-x86.c",
             "not-for-arm.c",
+            "for-x86.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
             "not-for-x86.c",
         ],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static arch specific exclude_srcs for 4 architectures",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":             "",
-				"for-arm.c":            "",
-				"for-arm64.c":          "",
-				"for-x86.c":            "",
-				"for-x86_64.c":         "",
-				"not-for-arm.c":        "",
-				"not-for-arm64.c":      "",
-				"not-for-x86.c":        "",
-				"not-for-x86_64.c":     "",
-				"not-for-everything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticFourArchExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch specific exclude_srcs for 4 architectures",
+		filesystem: map[string]string{
+			"common.c":             "",
+			"for-arm.c":            "",
+			"for-arm64.c":          "",
+			"for-x86.c":            "",
+			"for-x86_64.c":         "",
+			"not-for-arm.c":        "",
+			"not-for-arm64.c":      "",
+			"not-for-x86.c":        "",
+			"not-for-x86_64.c":     "",
+			"not-for-everything.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -683,36 +647,35 @@
         arm64: { srcs: ["for-arm64.c"], exclude_srcs: ["not-for-arm64.c"] },
         x86: { srcs: ["for-x86.c"], exclude_srcs: ["not-for-x86.c"] },
         x86_64: { srcs: ["for-x86_64.c"], exclude_srcs: ["not-for-x86_64.c"] },
-	},
+  },
+    include_build_directory: false,
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-arm.c",
             "not-for-arm64.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-arm64.c",
             "not-for-arm.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-x86.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-x86_64.c",
+            "for-x86.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-x86_64.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-x86.c",
+            "for-x86_64.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
@@ -720,138 +683,198 @@
             "not-for-x86.c",
             "not-for-x86_64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static multiple dep same name panic",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
-cc_library_static { name: "static_dep" }
+	})
+}
+
+func TestCcLibraryStaticOneArchEmpty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static one arch empty",
+		filesystem: map[string]string{
+			"common.cc":       "",
+			"foo-no-arm.cc":   "",
+			"foo-excluded.cc": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.cc", "foo-*.cc"],
+    exclude_srcs: ["foo-excluded.cc"],
+    arch: {
+        arm: { exclude_srcs: ["foo-no-arm.cc"] },
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `["common.cc"] + select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": ["foo-no-arm.cc"],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticOneArchEmptyOtherSet(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static one arch empty other set",
+		filesystem: map[string]string{
+			"common.cc":       "",
+			"foo-no-arm.cc":   "",
+			"x86-only.cc":     "",
+			"foo-excluded.cc": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.cc", "foo-*.cc"],
+    exclude_srcs: ["foo-excluded.cc"],
+    arch: {
+        arm: { exclude_srcs: ["foo-no-arm.cc"] },
+        x86: { srcs: ["x86-only.cc"] },
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `["common.cc"] + select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//build/bazel/platforms/arch:x86": [
+            "foo-no-arm.cc",
+            "x86-only.cc",
+        ],
+        "//conditions:default": ["foo-no-arm.cc"],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticMultipleDepSameName(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static multiple dep same name panic",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "static_dep",
+    bazel_module: { bp2build_available: false },
+}
 cc_library_static {
     name: "foo_static",
     static_libs: ["static_dep", "static_dep"],
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    deps = [":static_dep"],
-    linkstatic = True,
-)`, `cc_library_static(
-    name = "static_dep",
-    copts = ["-I."],
-    linkstatic = True,
-)`},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"implementation_deps": `[":static_dep"]`,
+			}),
 		},
-		{
-			description:                        "cc_library_static 1 multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":        "",
-				"for-lib32.c":     "",
-				"not-for-lib32.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticOneMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static 1 multilib srcs and exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":        "",
+			"for-lib32.c":     "",
+			"not-for-lib32.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
     multilib: {
         lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
     },
+    include_build_directory: false,
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-lib32.c"],
         "//build/bazel/platforms/arch:x86": ["for-lib32.c"],
         "//conditions:default": ["not-for-lib32.c"],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static 2 multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":        "",
-				"for-lib32.c":     "",
-				"for-lib64.c":     "",
-				"not-for-lib32.c": "",
-				"not-for-lib64.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticTwoMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static 2 multilib srcs and exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":        "",
+			"for-lib32.c":     "",
+			"for-lib64.c":     "",
+			"not-for-lib32.c": "",
+			"not-for-lib64.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
-    name: "foo_static2",
+    name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
     multilib: {
         lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
         lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
     },
+    include_build_directory: false,
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static2",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
-            "for-lib32.c",
             "not-for-lib64.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-lib64.c",
             "not-for-lib32.c",
+            "for-lib64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-lib32.c",
             "not-for-lib64.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-lib64.c",
             "not-for-lib32.c",
+            "for-lib64.c",
         ],
         "//conditions:default": [
             "not-for-lib32.c",
             "not-for-lib64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
 		},
-		{
-			description:                        "cc_library_static arch and multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":             "",
-				"for-arm.c":            "",
-				"for-arm64.c":          "",
-				"for-x86.c":            "",
-				"for-x86_64.c":         "",
-				"for-lib32.c":          "",
-				"for-lib64.c":          "",
-				"not-for-arm.c":        "",
-				"not-for-arm64.c":      "",
-				"not-for-x86.c":        "",
-				"not-for-x86_64.c":     "",
-				"not-for-lib32.c":      "",
-				"not-for-lib64.c":      "",
-				"not-for-everything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibrarySTaticArchMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch and multilib srcs and exclude_srcs",
+		filesystem: map[string]string{
+			"common.c":             "",
+			"for-arm.c":            "",
+			"for-arm64.c":          "",
+			"for-x86.c":            "",
+			"for-x86_64.c":         "",
+			"for-lib32.c":          "",
+			"for-lib64.c":          "",
+			"not-for-arm.c":        "",
+			"not-for-arm64.c":      "",
+			"not-for-x86.c":        "",
+			"not-for-x86_64.c":     "",
+			"not-for-lib32.c":      "",
+			"not-for-lib64.c":      "",
+			"not-for-everything.c": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
-   name: "foo_static3",
+   name: "foo_static",
    srcs: ["common.c", "not-for-*.c"],
    exclude_srcs: ["not-for-everything.c"],
    arch: {
@@ -864,43 +887,42 @@
        lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
        lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
    },
+    include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
-    name = "foo_static3",
-    copts = ["-I."],
-    linkstatic = True,
-    srcs = ["common.c"] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
+            "not-for-arm64.c",
+            "not-for-lib64.c",
+            "not-for-x86.c",
+            "not-for-x86_64.c",
             "for-arm.c",
             "for-lib32.c",
-            "not-for-arm64.c",
-            "not-for-lib64.c",
-            "not-for-x86.c",
-            "not-for-x86_64.c",
         ],
         "//build/bazel/platforms/arch:arm64": [
-            "for-arm64.c",
-            "for-lib64.c",
             "not-for-arm.c",
             "not-for-lib32.c",
             "not-for-x86.c",
             "not-for-x86_64.c",
+            "for-arm64.c",
+            "for-lib64.c",
         ],
         "//build/bazel/platforms/arch:x86": [
-            "for-lib32.c",
-            "for-x86.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-lib64.c",
             "not-for-x86_64.c",
+            "for-x86.c",
+            "for-lib32.c",
         ],
         "//build/bazel/platforms/arch:x86_64": [
-            "for-lib64.c",
-            "for-x86_64.c",
             "not-for-arm.c",
             "not-for-arm64.c",
             "not-for-lib32.c",
             "not-for-x86.c",
+            "for-x86_64.c",
+            "for-lib64.c",
         ],
         "//conditions:default": [
             "not-for-arm.c",
@@ -910,66 +932,560 @@
             "not-for-x86.c",
             "not-for-x86_64.c",
         ],
-    }),
-)`},
+    })`,
+			}),
 		},
-	}
+	})
+}
 
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
+func TestCcLibraryStaticGeneratedHeadersAllPartitions(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcLibraryStaticPreamble + `
+genrule {
+    name: "generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
 
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+genrule {
+    name: "export_generated_hdr",
+    cmd: "nothing to see here",
+    bazel_module: { bp2build_available: false },
+}
 
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
+cc_library_static {
+    name: "foo_static",
+    srcs: ["cpp_src.cpp", "as_src.S", "c_src.c"],
+    generated_headers: ["generated_hdr", "export_generated_hdr"],
+    export_generated_headers: ["export_generated_hdr"],
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"export_includes": `["."]`,
+				"local_includes":  `["."]`,
+				"hdrs":            `[":export_generated_hdr"]`,
+				"srcs": `[
+        "cpp_src.cpp",
+        ":generated_hdr",
+    ]`,
+				"srcs_as": `[
+        "as_src.S",
+        ":generated_hdr",
+    ]`,
+				"srcs_c": `[
+        "c_src.c",
+        ":generated_hdr",
+    ]`,
+			}),
+		},
+	})
+}
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
+func TestCcLibraryStaticArchSrcsExcludeSrcsGeneratedFiles(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch srcs/exclude_srcs with generated files",
+		filesystem: map[string]string{
+			"common.cpp":             "",
+			"for-x86.cpp":            "",
+			"not-for-x86.cpp":        "",
+			"not-for-everything.cpp": "",
+			"dep/Android.bp": simpleModuleDoNotConvertBp2build("genrule", "generated_src_other_pkg") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_src_other_pkg_x86") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg_x86") +
+				simpleModuleDoNotConvertBp2build("genrule", "generated_hdr_other_pkg_android"),
+		},
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src_not_x86") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_src_android") +
+			simpleModuleDoNotConvertBp2build("genrule", "generated_hdr") + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.cpp", "not-for-*.cpp"],
+    exclude_srcs: ["not-for-everything.cpp"],
+    generated_sources: ["generated_src", "generated_src_other_pkg", "generated_src_not_x86"],
+    generated_headers: ["generated_hdr", "generated_hdr_other_pkg"],
+    export_generated_headers: ["generated_hdr_other_pkg"],
+    arch: {
+        x86: {
+          srcs: ["for-x86.cpp"],
+          exclude_srcs: ["not-for-x86.cpp"],
+          generated_headers: ["generated_hdr_other_pkg_x86"],
+          exclude_generated_sources: ["generated_src_not_x86"],
+    export_generated_headers: ["generated_hdr_other_pkg_x86"],
+        },
+    },
+    target: {
+        android: {
+            generated_sources: ["generated_src_android"],
+            generated_headers: ["generated_hdr_other_pkg_android"],
+    export_generated_headers: ["generated_hdr_other_pkg_android"],
+        },
+    },
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs": `[
+        "common.cpp",
+        ":generated_src",
+        "//dep:generated_src_other_pkg",
+        ":generated_hdr",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": ["for-x86.cpp"],
+        "//conditions:default": [
+            "not-for-x86.cpp",
+            ":generated_src_not_x86",
+        ],
+    }) + select({
+        "//build/bazel/platforms/os:android": [":generated_src_android"],
+        "//conditions:default": [],
+    })`,
+				"hdrs": `["//dep:generated_hdr_other_pkg"] + select({
+        "//build/bazel/platforms/arch:x86": ["//dep:generated_hdr_other_pkg_x86"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["//dep:generated_hdr_other_pkg_android"],
+        "//conditions:default": [],
+    })`,
+				"local_includes":           `["."]`,
+				"export_absolute_includes": `["dep"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticGetTargetProperties(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+
+		description: "cc_library_static complex GetTargetProperties",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    target: {
+        android: {
+            srcs: ["android_src.c"],
+        },
+        android_arm: {
+            srcs: ["android_arm_src.c"],
+        },
+        android_arm64: {
+            srcs: ["android_arm64_src.c"],
+        },
+        android_x86: {
+            srcs: ["android_x86_src.c"],
+        },
+        android_x86_64: {
+            srcs: ["android_x86_64_src.c"],
+        },
+        linux_bionic_arm64: {
+            srcs: ["linux_bionic_arm64_src.c"],
+        },
+        linux_bionic_x86_64: {
+            srcs: ["linux_bionic_x86_64_src.c"],
+        },
+    },
+    include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"srcs_c": `select({
+        "//build/bazel/platforms/os:android": ["android_src.c"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os_arch:android_arm": ["android_arm_src.c"],
+        "//build/bazel/platforms/os_arch:android_arm64": ["android_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86": ["android_x86_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86_64": ["android_x86_64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": ["linux_bionic_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_x86_64": ["linux_bionic_x86_64_src.c"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticProductVariableSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static product variable selects",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+      malloc_zero_contents: {
+        cflags: ["-Wmalloc_zero_contents"],
+      },
+      binder32bit: {
+        cflags: ["-Wbinder32bit"],
+      },
+    },
+    include_build_directory: false,
+} `,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"copts": `select({
+        "//build/bazel/product_variables:binder32bit": ["-Wbinder32bit"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
+        "//conditions:default": [],
+    })`,
+				"srcs_c": `["common.c"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticProductVariableArchSpecificSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static arch-specific product variable selects",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+    },
+    arch: {
+        arm64: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Warm64_malloc_not_svelte"],
+                },
+            },
+        },
+    },
+    multilib: {
+        lib32: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Wlib32_malloc_not_svelte"],
+                },
+            },
+        },
+    },
+    target: {
+        android: {
+            product_variables: {
+                malloc_not_svelte: {
+                    cflags: ["-Wandroid_malloc_not_svelte"],
+                },
+            },
+        }
+    },
+    include_build_directory: false,
+} `,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"copts": `select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-android": ["-Wandroid_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm64": ["-Warm64_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    })`,
+				"srcs_c": `["common.c"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticProductVariableStringReplacement(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static product variable string replacement",
+		filesystem:  map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.S"],
+    product_variables: {
+      platform_sdk_version: {
+          asflags: ["-DPLATFORM_SDK_VERSION=%d"],
+      },
+    },
+    include_build_directory: false,
+} `,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo_static", attrNameToString{
+				"asflags": `select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//conditions:default": [],
+    })`,
+				"srcs_as": `["common.S"]`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsRootEmpty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_lib empty root",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "root_empty",
+    system_shared_libs: [],
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "root_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsStaticEmpty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_lib empty static default",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_defaults {
+    name: "static_empty_defaults",
+    static: {
+        system_shared_libs: [],
+    },
+    include_build_directory: false,
+}
+cc_library_static {
+    name: "static_empty",
+    defaults: ["static_empty_defaults"],
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "static_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsBionicEmpty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_lib empty for bionic variant",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "target_bionic_empty",
+    target: {
+        bionic: {
+            system_shared_libs: [],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_bionic_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsLinuxBionicEmpty(t *testing.T) {
+	// Note that this behavior is technically incorrect (it's a simplification).
+	// The correct behavior would be if bp2build wrote `system_dynamic_deps = []`
+	// only for linux_bionic, but `android` had `["libc", "libdl", "libm"].
+	// b/195791252 tracks the fix.
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_lib empty for linux_bionic variant",
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "target_linux_bionic_empty",
+    target: {
+        linux_bionic: {
+            system_shared_libs: [],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_linux_bionic_empty", attrNameToString{
+				"system_dynamic_deps": `[]`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsBionic(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_libs set for bionic variant",
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library", "libc") + `
+cc_library_static {
+    name: "target_bionic",
+    target: {
+        bionic: {
+            system_shared_libs: ["libc"],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_bionic", attrNameToString{
+				"system_dynamic_deps": `select({
+        "//build/bazel/platforms/os:android": [":libc"],
+        "//build/bazel/platforms/os:linux_bionic": [":libc"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestStaticLibrary_SystemSharedLibsLinuxRootAndLinuxBionic(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_libs set for root and linux_bionic variant",
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library", "libc") +
+			simpleModuleDoNotConvertBp2build("cc_library", "libm") + `
+cc_library_static {
+    name: "target_linux_bionic",
+    system_shared_libs: ["libc"],
+    target: {
+        linux_bionic: {
+            system_shared_libs: ["libm"],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "target_linux_bionic", attrNameToString{
+				"system_dynamic_deps": `[":libc"] + select({
+        "//build/bazel/platforms/os:linux_bionic": [":libm"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarystatic_SystemSharedLibUsedAsDep(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description: "cc_library_static system_shared_lib empty for linux_bionic variant",
+		blueprint: soongCcLibraryStaticPreamble +
+			simpleModuleDoNotConvertBp2build("cc_library", "libc") + `
+cc_library_static {
+    name: "used_in_bionic_oses",
+    target: {
+        android: {
+            shared_libs: ["libc"],
+        },
+        linux_bionic: {
+            shared_libs: ["libc"],
+        },
+    },
+    include_build_directory: false,
+}
+
+cc_library_static {
+    name: "all",
+    shared_libs: ["libc"],
+    include_build_directory: false,
+}
+
+cc_library_static {
+    name: "keep_for_empty_system_shared_libs",
+    shared_libs: ["libc"],
+		system_shared_libs: [],
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "all", attrNameToString{
+				"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/os:android": [],
+        "//build/bazel/platforms/os:linux_bionic": [],
+        "//conditions:default": [":libc"],
+    })`,
+			}),
+			makeBazelTarget("cc_library_static", "keep_for_empty_system_shared_libs", attrNameToString{
+				"implementation_dynamic_deps": `[":libc"]`,
+				"system_dynamic_deps":         `[]`,
+			}),
+			makeBazelTarget("cc_library_static", "used_in_bionic_oses", attrNameToString{}),
+		},
+	})
+}
+
+func TestCcLibraryStaticProto(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_static {
+	name: "foo",
+	srcs: ["foo.proto"],
+	proto: {
+		export_proto_headers: true,
+	},
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "foo_proto", attrNameToString{
+				"srcs": `["foo.proto"]`,
+			}), makeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", attrNameToString{
+				"deps": `[":foo_proto"]`,
+			}), makeBazelTarget("cc_library_static", "foo", attrNameToString{
+				"deps":               `[":libprotobuf-cpp-lite"]`,
+				"whole_archive_deps": `[":foo_cc_proto_lite"]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticUseVersionLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_static {
+	name: "foo",
+	use_version_lib: true,
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo", attrNameToString{
+				"use_version_lib": "True",
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticStdInFlags(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		blueprint: soongCcProtoPreamble + `cc_library_static {
+	name: "foo",
+	cflags: ["-std=candcpp"],
+	conlyflags: ["-std=conly"],
+	cppflags: ["-std=cpp"],
+	include_build_directory: false,
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_library_static", "foo", attrNameToString{
+				"conlyflags": `["-std=conly"]`,
+				"cppflags":   `["-std=cpp"]`,
+			}),
+		},
+	})
 }
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 9efdb53..ea58086 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -15,37 +15,37 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/cc"
-	"fmt"
-	"strings"
-	"testing"
 )
 
-func TestCcObjectBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "simple cc_object generates cc_object with include header dep",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/foo.h":     "",
-				"a/b/bar.h":     "",
-				"a/b/exclude.c": "",
-				"a/b/c.c":       "",
-			},
-			blueprint: `cc_object {
+func registerCcObjectModuleTypes(ctx android.RegistrationContext) {
+	// Always register cc_defaults module factory
+	ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+}
+
+func runCcObjectTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "cc_object"
+	(&tc).moduleTypeUnderTestFactory = cc.ObjectFactory
+	runBp2BuildTestCase(t, registerCcObjectModuleTypes, tc)
+}
+
+func TestCcObjectSimple(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "simple cc_object generates cc_object with include header dep",
+		filesystem: map[string]string{
+			"a/b/foo.h":     "",
+			"a/b/bar.h":     "",
+			"a/b/exclude.c": "",
+			"a/b/c.c":       "",
+		},
+		blueprint: `cc_object {
     name: "foo",
     local_include_dirs: ["include"],
+    system_shared_libs: [],
     cflags: [
         "-Wno-gcc-compat",
         "-Wall",
@@ -55,30 +55,36 @@
         "a/b/*.c"
     ],
     exclude_srcs: ["a/b/exclude.c"],
+    sdk_version: "current",
+    min_sdk_version: "29",
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = [
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `[
         "-fno-addrsig",
         "-Wno-gcc-compat",
         "-Wall",
         "-Werror",
-        "-Iinclude",
-        "-I.",
-    ],
-    srcs = ["a/b/c.c"],
-)`,
-			},
+    ]`,
+				"local_includes": `[
+        "include",
+        ".",
+    ]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+        "sdk_version": `"current"`,
+        "min_sdk_version": `"29"`,
+			}),
 		},
-		{
-			description:                        "simple cc_object with defaults",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectDefaults(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		blueprint: `cc_object {
     name: "foo",
-    local_include_dirs: ["include"],
+    system_shared_libs: [],
     srcs: [
         "a/b/*.h",
         "a/b/c.c"
@@ -95,213 +101,153 @@
 cc_defaults {
     name: "foo_bar_defaults",
     cflags: [
-        "-Wno-gcc-compat",
-        "-Wall",
         "-Werror",
     ],
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = [
-        "-Wno-gcc-compat",
-        "-Wall",
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `[
         "-Werror",
         "-fno-addrsig",
-        "-Iinclude",
-        "-I.",
-    ],
-    srcs = ["a/b/c.c"],
-)`,
-			},
+    ]`,
+				"local_includes":      `["."]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
+		}})
+}
+
+func TestCcObjectCcObjetDepsInObjs(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object with cc_object deps in objs props",
+		filesystem: map[string]string{
+			"a/b/c.c": "",
+			"x/y/z.c": "",
 		},
-		{
-			description:                        "cc_object with cc_object deps in objs props",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/c.c": "",
-				"x/y/z.c": "",
-			},
-			blueprint: `cc_object {
+		blueprint: `cc_object {
     name: "foo",
+    system_shared_libs: [],
     srcs: ["a/b/c.c"],
     objs: ["bar"],
+    include_build_directory: false,
 }
 
 cc_object {
     name: "bar",
+    system_shared_libs: [],
     srcs: ["x/y/z.c"],
+    include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
-    name = "bar",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-    ],
-    srcs = ["x/y/z.c"],
-)`, `cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-    ],
-    deps = [":bar"],
-    srcs = ["a/b/c.c"],
-)`,
-			},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "bar", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"srcs":                `["x/y/z.c"]`,
+				"system_dynamic_deps": `[]`,
+			}), makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"deps":                `[":bar"]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
-		{
-			description:                        "cc_object with include_build_dir: false",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/c.c": "",
-				"x/y/z.c": "",
-			},
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectIncludeBuildDirFalse(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object with include_build_dir: false",
+		filesystem: map[string]string{
+			"a/b/c.c": "",
+			"x/y/z.c": "",
+		},
+		blueprint: `cc_object {
     name: "foo",
+    system_shared_libs: [],
     srcs: ["a/b/c.c"],
     include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    copts = ["-fno-addrsig"],
-    srcs = ["a/b/c.c"],
-)`,
-			},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":               `["-fno-addrsig"]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
-		{
-			description:                        "cc_object with product variable",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectProductVariable(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object with product variable",
+		blueprint: `cc_object {
     name: "foo",
+    system_shared_libs: [],
     include_build_directory: false,
     product_variables: {
         platform_sdk_version: {
             asflags: ["-DPLATFORM_SDK_VERSION=%d"],
         },
     },
+    srcs: ["src.S"],
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
-    name = "foo",
-    asflags = ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
-    copts = ["-fno-addrsig"],
-)`,
-			},
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"asflags": `select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//conditions:default": [],
+    })`,
+				"copts":               `["-fno-addrsig"]`,
+				"srcs_as":             `["src.S"]`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-		// Always register cc_defaults module factory
-		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
 
-func TestCcObjectConfigurableAttributesBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "cc_object setting cflags for one arch",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+func TestCcObjectCflagsOneArch(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting cflags for one arch",
+		blueprint: `cc_object {
     name: "foo",
+    system_shared_libs: [],
     srcs: ["a.cpp"],
     arch: {
         x86: {
             cflags: ["-fPIC"], // string list
         },
         arm: {
-            srcs: ["arch/arm/file.S"], // label list
+            srcs: ["arch/arm/file.cpp"], // label list
         },
     },
+    include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-    ] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"] + select({
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//conditions:default": [],
-    }),
-    srcs = ["a.cpp"] + select({
-        "//build/bazel/platforms/arch:arm": ["arch/arm/file.S"],
+    })`,
+				"srcs": `["a.cpp"] + select({
+        "//build/bazel/platforms/arch:arm": ["arch/arm/file.cpp"],
         "//conditions:default": [],
-    }),
-)`,
-			},
+    })`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
-		{
-			description:                        "cc_object setting cflags for 4 architectures",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectCflagsFourArch(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting cflags for 4 architectures",
+		blueprint: `cc_object {
     name: "foo",
+    system_shared_libs: [],
     srcs: ["base.cpp"],
     arch: {
         x86: {
@@ -321,111 +267,157 @@
             cflags: ["-Wall"],
         },
     },
+    include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-    ] + select({
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"] + select({
         "//build/bazel/platforms/arch:arm": ["-Wall"],
         "//build/bazel/platforms/arch:arm64": ["-Wall"],
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//build/bazel/platforms/arch:x86_64": ["-fPIC"],
         "//conditions:default": [],
-    }),
-    srcs = ["base.cpp"] + select({
+    })`,
+				"srcs": `["base.cpp"] + select({
         "//build/bazel/platforms/arch:arm": ["arm.cpp"],
         "//build/bazel/platforms/arch:arm64": ["arm64.cpp"],
         "//build/bazel/platforms/arch:x86": ["x86.cpp"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
-    }),
-)`,
-			},
+    })`,
+				"system_dynamic_deps": `[]`,
+			}),
 		},
-		{
-			description:                        "cc_object setting cflags for multiple OSes",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectLinkerScript(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting linker_script",
+		blueprint: `cc_object {
+    name: "foo",
+    srcs: ["base.cpp"],
+    linker_script: "bunny.lds",
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts":         `["-fno-addrsig"]`,
+				"linker_script": `"bunny.lds"`,
+				"srcs":          `["base.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcObjectDepsAndLinkerScriptSelects(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting deps and linker_script across archs",
+		blueprint: `cc_object {
+    name: "foo",
+    srcs: ["base.cpp"],
+    arch: {
+        x86: {
+            objs: ["x86_obj"],
+            linker_script: "x86.lds",
+        },
+        x86_64: {
+            objs: ["x86_64_obj"],
+            linker_script: "x86_64.lds",
+        },
+        arm: {
+            objs: ["arm_obj"],
+            linker_script: "arm.lds",
+        },
+    },
+    include_build_directory: false,
+}
+
+cc_object {
+    name: "x86_obj",
+    system_shared_libs: [],
+    srcs: ["x86.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+
+cc_object {
+    name: "x86_64_obj",
+    system_shared_libs: [],
+    srcs: ["x86_64.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+
+cc_object {
+    name: "arm_obj",
+    system_shared_libs: [],
+    srcs: ["arm.cpp"],
+    include_build_directory: false,
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"]`,
+				"deps": `select({
+        "//build/bazel/platforms/arch:arm": [":arm_obj"],
+        "//build/bazel/platforms/arch:x86": [":x86_obj"],
+        "//build/bazel/platforms/arch:x86_64": [":x86_64_obj"],
+        "//conditions:default": [],
+    })`,
+				"linker_script": `select({
+        "//build/bazel/platforms/arch:arm": "arm.lds",
+        "//build/bazel/platforms/arch:x86": "x86.lds",
+        "//build/bazel/platforms/arch:x86_64": "x86_64.lds",
+        "//conditions:default": None,
+    })`,
+				"srcs": `["base.cpp"]`,
+			}),
+		},
+	})
+}
+
+func TestCcObjectSelectOnLinuxAndBionicArchs(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description: "cc_object setting srcs based on linux and bionic archs",
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["base.cpp"],
     target: {
-        android: {
-            cflags: ["-fPIC"],
+        linux_arm64: {
+            srcs: ["linux_arm64.cpp",]
         },
-        windows: {
-            cflags: ["-fPIC"],
+        linux_x86: {
+            srcs: ["linux_x86.cpp",]
         },
-        darwin: {
-            cflags: ["-Wall"],
+        bionic_arm64: {
+            srcs: ["bionic_arm64.cpp",]
         },
     },
+    include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
-    name = "foo",
-    copts = [
-        "-fno-addrsig",
-        "-I.",
-    ] + select({
-        "//build/bazel/platforms/os:android": ["-fPIC"],
-        "//build/bazel/platforms/os:darwin": ["-Wall"],
-        "//build/bazel/platforms/os:windows": ["-fPIC"],
+		expectedBazelTargets: []string{
+			makeBazelTarget("cc_object", "foo", attrNameToString{
+				"copts": `["-fno-addrsig"]`,
+				"srcs": `["base.cpp"] + select({
+        "//build/bazel/platforms/os_arch:android_arm64": [
+            "linux_arm64.cpp",
+            "bionic_arm64.cpp",
+        ],
+        "//build/bazel/platforms/os_arch:android_x86": ["linux_x86.cpp"],
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": [
+            "linux_arm64.cpp",
+            "bionic_arm64.cpp",
+        ],
+        "//build/bazel/platforms/os_arch:linux_glibc_x86": ["linux_x86.cpp"],
+        "//build/bazel/platforms/os_arch:linux_musl_x86": ["linux_x86.cpp"],
         "//conditions:default": [],
-    }),
-    srcs = ["base.cpp"],
-)`,
-			},
+    })`,
+			}),
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-		// Always register cc_defaults module factory
-		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
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
new file mode 100644
index 0000000..57905e5
--- /dev/null
+++ b/bp2build/cc_prebuilt_library_shared_test.go
@@ -0,0 +1,85 @@
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/cc"
+)
+
+func TestSharedPrebuiltLibrary(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library shared simple",
+			moduleTypeUnderTest:        "cc_prebuilt_library_shared",
+			moduleTypeUnderTestFactory: cc.PrebuiltSharedLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_shared {
+	name: "libtest",
+	srcs: ["libf.so"],
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `"libf.so"`,
+				}),
+			},
+		})
+}
+
+func TestSharedPrebuiltLibraryWithArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t,
+		bp2buildTestCase{
+			description:                "prebuilt library shared with arch variance",
+			moduleTypeUnderTest:        "cc_prebuilt_library_shared",
+			moduleTypeUnderTestFactory: cc.PrebuiltSharedLibraryFactory,
+			filesystem: map[string]string{
+				"libf.so": "",
+				"libg.so": "",
+			},
+			blueprint: `
+cc_prebuilt_library_shared {
+	name: "libtest",
+	arch: {
+		arm64: { srcs: ["libf.so"], },
+		arm: { srcs: ["libg.so"], },
+	},
+	bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("prebuilt_library_shared", "libtest", attrNameToString{
+					"shared_library": `select({
+        "//build/bazel/platforms/arch:arm": "libg.so",
+        "//build/bazel/platforms/arch:arm64": "libf.so",
+        "//conditions:default": None,
+    })`,
+				}),
+			},
+		})
+}
+
+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/configurability.go b/bp2build/configurability.go
index 2b8f6cc..d37a523 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -1,93 +1,172 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/bazel"
 	"fmt"
 	"reflect"
+
+	"android/soong/android"
+	"android/soong/bazel"
+	"android/soong/starlark_fmt"
 )
 
 // Configurability support for bp2build.
 
 type selects map[string]reflect.Value
 
-func getStringListValues(list bazel.StringListAttribute) (reflect.Value, selects, selects) {
+func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value)
 	if !list.HasConfigurableValues() {
-		return value, nil, nil
+		return value, []selects{}
 	}
 
-	archSelects := map[string]reflect.Value{}
-	for arch, selectKey := range bazel.PlatformArchMap {
-		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch))
-	}
-
-	osSelects := map[string]reflect.Value{}
-	for os, selectKey := range bazel.PlatformOsMap {
-		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os))
-	}
-
-	return value, archSelects, osSelects
-}
-
-func getLabelValue(label bazel.LabelAttribute) (reflect.Value, selects, selects) {
-	var value reflect.Value
-	var archSelects selects
-
-	if label.HasConfigurableValues() {
-		archSelects = map[string]reflect.Value{}
-		for arch, selectKey := range bazel.PlatformArchMap {
-			archSelects[selectKey] = reflect.ValueOf(label.GetValueForArch(arch))
+	var ret []selects
+	for _, axis := range list.SortedConfigurationAxes() {
+		configToLists := list.ConfigurableValues[axis]
+		archSelects := map[string]reflect.Value{}
+		for config, labels := range configToLists {
+			selectKey := axis.SelectKey(config)
+			archSelects[selectKey] = reflect.ValueOf(labels)
 		}
-	} else {
-		value = reflect.ValueOf(label.Value)
+		if len(archSelects) > 0 {
+			ret = append(ret, archSelects)
+		}
 	}
 
-	return value, archSelects, nil
+	return value, ret
 }
 
-func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, selects, selects) {
+func getLabelValue(label bazel.LabelAttribute) (reflect.Value, []selects) {
+	value := reflect.ValueOf(label.Value)
+	if !label.HasConfigurableValues() {
+		return value, []selects{}
+	}
+
+	ret := selects{}
+	for _, axis := range label.SortedConfigurationAxes() {
+		configToLabels := label.ConfigurableValues[axis]
+		for config, labels := range configToLabels {
+			selectKey := axis.SelectKey(config)
+			ret[selectKey] = reflect.ValueOf(labels)
+		}
+	}
+
+	// if there is a select, use the base value as the conditions default value
+	if len(ret) > 0 {
+		ret[bazel.ConditionsDefaultSelectKey] = value
+		value = reflect.Zero(value.Type())
+	}
+
+	return value, []selects{ret}
+}
+
+func getBoolValue(boolAttr bazel.BoolAttribute) (reflect.Value, []selects) {
+	value := reflect.ValueOf(boolAttr.Value)
+	if !boolAttr.HasConfigurableValues() {
+		return value, []selects{}
+	}
+
+	ret := selects{}
+	for _, axis := range boolAttr.SortedConfigurationAxes() {
+		configToBools := boolAttr.ConfigurableValues[axis]
+		for config, bools := range configToBools {
+			selectKey := axis.SelectKey(config)
+			ret[selectKey] = reflect.ValueOf(bools)
+		}
+	}
+	// if there is a select, use the base value as the conditions default value
+	if len(ret) > 0 {
+		ret[bazel.ConditionsDefaultSelectKey] = value
+		value = reflect.Zero(value.Type())
+	}
+
+	return value, []selects{ret}
+}
+func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value.Includes)
-	if !list.HasConfigurableValues() {
-		return value, nil, nil
+	var ret []selects
+	for _, axis := range list.SortedConfigurationAxes() {
+		configToLabels := list.ConfigurableValues[axis]
+		if !configToLabels.HasConfigurableValues() {
+			continue
+		}
+		archSelects := map[string]reflect.Value{}
+		defaultVal := configToLabels[bazel.ConditionsDefaultConfigKey]
+		// Skip empty list values unless ether EmitEmptyList is true, or these values differ from the default.
+		emitEmptyList := list.EmitEmptyList || len(defaultVal.Includes) > 0
+		for config, labels := range configToLabels {
+			// Omit any entries in the map which match the default value, for brevity.
+			if config != bazel.ConditionsDefaultConfigKey && labels.Equals(defaultVal) {
+				continue
+			}
+			selectKey := axis.SelectKey(config)
+			if use, value := labelListSelectValue(selectKey, labels, emitEmptyList); use {
+				archSelects[selectKey] = value
+			}
+		}
+		if len(archSelects) > 0 {
+			ret = append(ret, archSelects)
+		}
 	}
 
-	archSelects := map[string]reflect.Value{}
-	for arch, selectKey := range bazel.PlatformArchMap {
-		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch).Includes)
-	}
-
-	osSelects := map[string]reflect.Value{}
-	for os, selectKey := range bazel.PlatformOsMap {
-		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os).Includes)
-	}
-
-	return value, archSelects, osSelects
+	return value, ret
 }
 
+func labelListSelectValue(selectKey string, list bazel.LabelList, emitEmptyList bool) (bool, reflect.Value) {
+	if selectKey == bazel.ConditionsDefaultSelectKey || emitEmptyList || len(list.Includes) > 0 {
+		return true, reflect.ValueOf(list.Includes)
+	} else if len(list.Excludes) > 0 {
+		// if there is still an excludes -- we need to have an empty list for this select & use the
+		// value in conditions default Includes
+		return true, reflect.ValueOf([]string{})
+	}
+	return false, reflect.Zero(reflect.TypeOf([]string{}))
+}
+
+var (
+	emptyBazelList = "[]"
+	bazelNone      = "None"
+)
+
 // prettyPrintAttribute converts an Attribute to its Bazel syntax. May contain
 // select statements.
 func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
 	var value reflect.Value
-	var archSelects, osSelects selects
-	var defaultSelectValue string
+	var configurableAttrs []selects
+	var defaultSelectValue *string
+	var emitZeroValues bool
+	// If true, print the default attribute value, even if the attribute is zero.
+	shouldPrintDefault := false
 	switch list := v.(type) {
 	case bazel.StringListAttribute:
-		value, archSelects, osSelects = getStringListValues(list)
-		defaultSelectValue = "[]"
+		value, configurableAttrs = getStringListValues(list)
+		defaultSelectValue = &emptyBazelList
 	case bazel.LabelListAttribute:
-		value, archSelects, osSelects = getLabelListValues(list)
-		defaultSelectValue = "[]"
+		value, configurableAttrs = getLabelListValues(list)
+		emitZeroValues = list.EmitEmptyList
+		defaultSelectValue = &emptyBazelList
+		if list.ForceSpecifyEmptyList && (!value.IsNil() || list.HasConfigurableValues()) {
+			shouldPrintDefault = true
+		}
 	case bazel.LabelAttribute:
-		value, archSelects, osSelects = getLabelValue(list)
-		defaultSelectValue = "None"
+		if err := list.Collapse(); err != nil {
+			return "", err
+		}
+		value, configurableAttrs = getLabelValue(list)
+		defaultSelectValue = &bazelNone
+	case bazel.BoolAttribute:
+		if err := list.Collapse(); err != nil {
+			return "", err
+		}
+		value, configurableAttrs = getBoolValue(list)
+		defaultSelectValue = &bazelNone
 	default:
 		return "", fmt.Errorf("Not a supported Bazel attribute type: %s", v)
 	}
 
+	var err error
 	ret := ""
 	if value.Kind() != reflect.Invalid {
-		s, err := prettyPrint(value, indent)
+		s, err := prettyPrint(value, indent, false) // never emit zero values for the base value
 		if err != nil {
 			return ret, err
 		}
@@ -95,8 +174,8 @@
 		ret += s
 	}
 	// Convenience function to append selects components to an attribute value.
-	appendSelects := func(selectsData selects, defaultValue, s string) (string, error) {
-		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent)
+	appendSelects := func(selectsData selects, defaultValue *string, s string) (string, error) {
+		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent, emitZeroValues)
 		if err != nil {
 			return "", err
 		}
@@ -108,37 +187,39 @@
 		return s, nil
 	}
 
-	ret, err := appendSelects(archSelects, defaultSelectValue, ret)
-	if err != nil {
-		return "", err
+	for _, configurableAttr := range configurableAttrs {
+		ret, err = appendSelects(configurableAttr, defaultSelectValue, ret)
+		if err != nil {
+			return "", err
+		}
 	}
 
-	ret, err = appendSelects(osSelects, defaultSelectValue, ret)
-	return ret, err
+	if ret == "" && shouldPrintDefault {
+		return *defaultSelectValue, nil
+	}
+	return ret, nil
 }
 
 // prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
 // to construct a select map for any kind of attribute type.
-func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue string, indent int) (string, error) {
+func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *string, indent int, emitZeroValues bool) (string, error) {
 	if selectMap == nil {
 		return "", nil
 	}
 
-	// addConditionsDefault := false
-	conditionsDefaultKey := bazel.PlatformArchMap[bazel.CONDITIONS_DEFAULT]
-
 	var selects string
 	for _, selectKey := range android.SortedStringKeys(selectMap) {
-		if selectKey == conditionsDefaultKey {
+		if selectKey == bazel.ConditionsDefaultSelectKey {
 			// Handle default condition later.
 			continue
 		}
 		value := selectMap[selectKey]
-		if isZero(value) {
-			// Ignore zero values to not generate empty lists.
+		if isZero(value) && !emitZeroValues && isZero(selectMap[bazel.ConditionsDefaultSelectKey]) {
+			// Ignore zero values to not generate empty lists. However, always note zero values if
+			// the default value is non-zero.
 			continue
 		}
-		s, err := prettyPrintSelectEntry(value, selectKey, indent)
+		s, err := prettyPrintSelectEntry(value, selectKey, indent, true)
 		if err != nil {
 			return "", err
 		}
@@ -159,21 +240,21 @@
 	ret += selects
 
 	// Handle the default condition
-	s, err := prettyPrintSelectEntry(selectMap[conditionsDefaultKey], conditionsDefaultKey, indent)
+	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent, emitZeroValues)
 	if err != nil {
 		return "", err
 	}
-	if s == "" {
-		// Print an explicit empty list (the default value) even if the value is
-		// empty, to avoid errors about not finding a configuration that matches.
-		ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)
-	} else {
+	if s != "" {
 		// Print the custom default value.
 		ret += s
 		ret += ",\n"
+	} else if defaultValue != nil {
+		// Print an explicit empty list (the default value) even if the value is
+		// empty, to avoid errors about not finding a configuration that matches.
+		ret += fmt.Sprintf("%s\"%s\": %s,\n", starlark_fmt.Indention(indent+1), bazel.ConditionsDefaultSelectKey, *defaultValue)
 	}
 
-	ret += makeIndent(indent)
+	ret += starlark_fmt.Indention(indent)
 	ret += "})"
 
 	return ret, nil
@@ -181,9 +262,9 @@
 
 // prettyPrintSelectEntry converts a reflect.Value into an entry in a select map
 // with a provided key.
-func prettyPrintSelectEntry(value reflect.Value, key string, indent int) (string, error) {
-	s := makeIndent(indent + 1)
-	v, err := prettyPrint(value, indent+1)
+func prettyPrintSelectEntry(value reflect.Value, key string, indent int, emitZeroValues bool) (string, error) {
+	s := starlark_fmt.Indention(indent + 1)
+	v, err := prettyPrint(value, indent+1, emitZeroValues)
 	if err != nil {
 		return "", err
 	}
diff --git a/bp2build/constants.go b/bp2build/constants.go
index 70f320e..4870dff 100644
--- a/bp2build/constants.go
+++ b/bp2build/constants.go
@@ -19,7 +19,10 @@
 	// be preferred for use within a Bazel build.
 
 	// The file name used for automatically generated files.
-	GeneratedBuildFileName = "BUILD"
+	GeneratedBuildFileName = "BUILD.bazel"
+
 	// The file name used for hand-crafted build targets.
+	// NOTE: It is okay that this matches GeneratedBuildFileName, since we generate BUILD files in a different directory to source files
+	// FIXME: Because there are hundreds of existing BUILD.bazel files in the AOSP tree, we should pick another name here, like BUILD.android
 	HandcraftedBuildFileName = "BUILD.bazel"
 )
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index eb83b38..1790dd7 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -1,13 +1,15 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/cc/config"
+	"encoding/json"
 	"fmt"
 	"reflect"
-	"sort"
 	"strings"
 
+	"android/soong/android"
+	cc_config "android/soong/cc/config"
+	java_config "android/soong/java/config"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -17,15 +19,36 @@
 	Contents string
 }
 
-func CreateSoongInjectionFiles() []BazelFile {
+func CreateSoongInjectionFiles(cfg android.Config, metrics CodegenMetrics) []BazelFile {
 	var files []BazelFile
 
-	files = append(files, newFile("cc_toolchain", "BUILD", "")) // Creates a //cc_toolchain package.
-	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars()))
+	files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
+	files = append(files, newFile("cc_toolchain", "constants.bzl", cc_config.BazelCcToolchainVars(cfg)))
+
+	files = append(files, newFile("java_toolchain", GeneratedBuildFileName, "")) // Creates a //java_toolchain package.
+	files = append(files, newFile("java_toolchain", "constants.bzl", java_config.BazelJavaToolchainVars(cfg)))
+
+	files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.convertedModules, "\n")))
+
+	files = append(files, newFile("product_config", "soong_config_variables.bzl", cfg.Bp2buildSoongConfigDefinitions.String()))
+
+	files = append(files, newFile("product_config", "arch_configuration.bzl", android.StarlarkArchConfigurations()))
+
+	apiLevelsContent, err := json.Marshal(android.GetApiLevelsMap(cfg))
+	if err != nil {
+		panic(err)
+	}
+	files = append(files, newFile("api_levels", GeneratedBuildFileName, `exports_files(["api_levels.json"])`))
+	files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
+	files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg)))
 
 	return files
 }
 
+func convertedModules(convertedModules []string) string {
+	return strings.Join(convertedModules, "\n")
+}
+
 func CreateBazelFiles(
 	ruleShims map[string]RuleShim,
 	buildToTargets map[string]BazelTargets,
@@ -59,27 +82,33 @@
 func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(buildToTargets))
 	for _, dir := range android.SortedStringKeys(buildToTargets) {
-		if mode == Bp2Build && !android.ShouldWriteBuildFileForDir(dir) {
+		if mode == Bp2Build && android.ShouldKeepExistingBuildFileForDir(dir) {
 			fmt.Printf("[bp2build] Not writing generated BUILD file for dir: '%s'\n", dir)
 			continue
 		}
 		targets := buildToTargets[dir]
-		sort.Slice(targets, func(i, j int) bool {
-			// this will cover all bp2build generated targets
-			if targets[i].name < targets[j].name {
-				return true
-			}
-			// give a strict ordering to content from hand-crafted targets
-			return targets[i].content < targets[j].content
-		})
-		content := soongModuleLoad
+		targets.sort()
+
+		var content string
 		if mode == Bp2Build {
-			content = `# This file was automatically generated by bp2build for the Bazel migration project.
-# Feel free to edit or test it, but do *not* check it into your version control system.`
-			content += "\n\n"
-			content += "package(default_visibility = [\"//visibility:public\"])"
-			content += "\n\n"
+			content = `# READ THIS FIRST:
+# This file was automatically generated by bp2build for the Bazel migration project.
+# Feel free to edit or test it, but do *not* check it into your version control system.
+`
+			if targets.hasHandcraftedTargets() {
+				// For BUILD files with both handcrafted and generated targets,
+				// don't hardcode actual content, like package() declarations.
+				// Leave that responsibility to the checked-in BUILD file
+				// instead.
+				content += `# This file contains generated targets and handcrafted targets that are manually managed in the source tree.`
+			} else {
+				// For fully-generated BUILD files, hardcode the default visibility.
+				content += "package(default_visibility = [\"//visibility:public\"])"
+			}
+			content += "\n"
 			content += targets.LoadStatements()
+		} else if mode == QueryView {
+			content = soongModuleLoad
 		}
 		if content != "" {
 			// If there are load statements, add a couple of newlines.
@@ -131,7 +160,7 @@
 }
 
 func shouldSkipStructField(field reflect.StructField) bool {
-	if field.PkgPath != "" {
+	if field.PkgPath != "" && !field.Anonymous {
 		// Skip unexported fields. Some properties are
 		// internal to Soong only, and these fields do not have PkgPath.
 		return true
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index a08c03d..e49d855 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -17,6 +17,8 @@
 import (
 	"sort"
 	"testing"
+
+	"android/soong/android"
 )
 
 type bazelFilepath struct {
@@ -29,7 +31,7 @@
 	expectedFilePaths := []bazelFilepath{
 		{
 			dir:      "",
-			basename: "BUILD",
+			basename: "BUILD.bazel",
 		},
 		{
 			dir:      "",
@@ -37,7 +39,7 @@
 		},
 		{
 			dir:      bazelRulesSubDir,
-			basename: "BUILD",
+			basename: "BUILD.bazel",
 		},
 		{
 			dir:      bazelRulesSubDir,
@@ -69,7 +71,7 @@
 
 		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
 			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
-		} else if actualFile.Basename == "BUILD" || actualFile.Basename == "WORKSPACE" {
+		} else if actualFile.Basename == "BUILD.bazel" || actualFile.Basename == "WORKSPACE" {
 			if actualFile.Contents != "" {
 				t.Errorf("Expected %s to have no content.", actualFile)
 			}
@@ -80,17 +82,50 @@
 }
 
 func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
-	files := CreateSoongInjectionFiles()
+	testConfig := android.TestConfig("", make(map[string]string), "", make(map[string][]byte))
+	files := CreateSoongInjectionFiles(testConfig, CodegenMetrics{})
 
 	expectedFilePaths := []bazelFilepath{
 		{
 			dir:      "cc_toolchain",
-			basename: "BUILD",
+			basename: GeneratedBuildFileName,
 		},
 		{
 			dir:      "cc_toolchain",
 			basename: "constants.bzl",
 		},
+		{
+			dir:      "java_toolchain",
+			basename: GeneratedBuildFileName,
+		},
+		{
+			dir:      "java_toolchain",
+			basename: "constants.bzl",
+		},
+		{
+			dir:      "metrics",
+			basename: "converted_modules.txt",
+		},
+		{
+			dir:      "product_config",
+			basename: "soong_config_variables.bzl",
+		},
+		{
+			dir:      "product_config",
+			basename: "arch_configuration.bzl",
+		},
+		{
+			dir:      "api_levels",
+			basename: GeneratedBuildFileName,
+		},
+		{
+			dir:      "api_levels",
+			basename: "api_levels.json",
+		},
+		{
+			dir:      "api_levels",
+			basename: "api_levels.bzl",
+		},
 	}
 
 	if len(files) != len(expectedFilePaths) {
@@ -103,9 +138,5 @@
 		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
 			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
 		}
-
-		if expectedFile.basename != "BUILD" && actualFile.Contents == "" {
-			t.Errorf("Contents of %s unexpected empty.", actualFile)
-		}
 	}
 }
diff --git a/bp2build/filegroup_conversion_test.go b/bp2build/filegroup_conversion_test.go
new file mode 100644
index 0000000..b43cf53
--- /dev/null
+++ b/bp2build/filegroup_conversion_test.go
@@ -0,0 +1,58 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"fmt"
+
+	"testing"
+)
+
+func runFilegroupTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "filegroup"
+	(&tc).moduleTypeUnderTestFactory = android.FileGroupFactory
+	runBp2BuildTestCase(t, registerFilegroupModuleTypes, tc)
+}
+
+func registerFilegroupModuleTypes(ctx android.RegistrationContext) {}
+
+func TestFilegroupSameNameAsFile_OneFile(t *testing.T) {
+	runFilegroupTestCase(t, bp2buildTestCase{
+		description: "filegroup - same name as file, with one file",
+		filesystem:  map[string]string{},
+		blueprint: `
+filegroup {
+    name: "foo",
+    srcs: ["foo"],
+}
+`,
+		expectedBazelTargets: []string{}})
+}
+
+func TestFilegroupSameNameAsFile_MultipleFiles(t *testing.T) {
+	runFilegroupTestCase(t, bp2buildTestCase{
+		description: "filegroup - same name as file, with multiple files",
+		filesystem:  map[string]string{},
+		blueprint: `
+filegroup {
+	name: "foo",
+	srcs: ["foo", "bar"],
+}
+`,
+		expectedErr: fmt.Errorf("filegroup 'foo' cannot contain a file with the same name"),
+	})
+}
diff --git a/bp2build/genrule_conversion_test.go b/bp2build/genrule_conversion_test.go
new file mode 100644
index 0000000..9244b99
--- /dev/null
+++ b/bp2build/genrule_conversion_test.go
@@ -0,0 +1,668 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/genrule"
+	"android/soong/java"
+	"fmt"
+	"testing"
+)
+
+func registerGenruleModuleTypes(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("genrule_defaults", func() android.Module { return genrule.DefaultsFactory() })
+}
+
+func runGenruleTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "genrule"
+	(&tc).moduleTypeUnderTestFactory = genrule.GenRuleFactory
+	runBp2BuildTestCase(t, registerGenruleModuleTypes, tc)
+}
+
+func otherGenruleBp(genruleTarget string) map[string]string {
+	return map[string]string{
+		"other/Android.bp": fmt.Sprintf(`%s {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+}
+%s {
+    name: "other.tool",
+    out: ["other_tool.out"],
+    srcs: ["other_tool.in"],
+    cmd: "cp $(in) $(out)",
+}`, genruleTarget, genruleTarget),
+	}
+}
+
+func TestGenruleCliVariableReplacement(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+		genDir     string
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+			genDir:     "$(GENDIR)",
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+			genDir:     "$(RULEDIR)",
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+			genDir:     "$(RULEDIR)",
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+			genDir:     "$(RULEDIR)",
+		},
+	}
+
+	bp := `%s {
+    name: "foo.tool",
+    out: ["foo_tool.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: false },
+}
+
+%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tool"],
+    cmd: "$(location :foo.tool) --genDir=$(genDir) arg $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":   fmt.Sprintf(`"$(location :foo.tool) --genDir=%s arg $(SRCS) $(OUTS)"`, tc.genDir),
+			"outs":  `["foo.out"]`,
+			"srcs":  `["foo.in"]`,
+			"tools": `[":foo.tool"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+				})
+		})
+	}
+}
+
+func TestGenruleLocationsLabel(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo.tools",
+    out: ["foo_tool.out", "foo_tool2.out"],
+    srcs: ["foo_tool.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+
+%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tools: [":foo.tools"],
+    cmd: "$(locations :foo.tools) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		fooAttrs := attrNameToString{
+			"cmd":   `"$(locations :foo.tools) -s $(OUTS) $(SRCS)"`,
+			"outs":  `["foo.out"]`,
+			"srcs":  `["foo.in"]`,
+			"tools": `[":foo.tools"]`,
+		}
+		fooToolsAttrs := attrNameToString{
+			"cmd": `"cp $(SRCS) $(OUTS)"`,
+			"outs": `[
+        "foo_tool.out",
+        "foo_tool2.out",
+    ]`,
+			"srcs": `["foo_tool.in"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			compatibilityAttrs := `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+			fooAttrs["target_compatible_with"] = compatibilityAttrs
+			fooToolsAttrs["target_compatible_with"] = compatibilityAttrs
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", fooAttrs),
+			makeBazelTarget("genrule", "foo.tools", fooToolsAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+				})
+		})
+	}
+}
+
+func TestGenruleLocationsAbsoluteLabel(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+			"outs":  `["foo.out"]`,
+			"srcs":  `["foo.in"]`,
+			"tools": `["//other:foo.tool"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+					filesystem:                 otherGenruleBp(tc.moduleType),
+				})
+		})
+	}
+}
+
+func TestGenruleSrcsLocationsAbsoluteLabel(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: [":other.tool"],
+    tool_files: [":foo.tool"],
+    cmd: "$(locations :foo.tool) -s $(out) $(location :other.tool)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)"`,
+			"outs":  `["foo.out"]`,
+			"srcs":  `["//other:other.tool"]`,
+			"tools": `["//other:foo.tool"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+					filesystem:                 otherGenruleBp(tc.moduleType),
+				})
+		})
+	}
+}
+
+func TestGenruleLocationLabelShouldSubstituteFirstToolLabel(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool", ":other.tool"],
+    cmd: "$(location) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":  `"$(location //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+			"outs": `["foo.out"]`,
+			"srcs": `["foo.in"]`,
+			"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+					filesystem:                 otherGenruleBp(tc.moduleType),
+				})
+		})
+	}
+}
+
+func TestGenruleLocationsLabelShouldSubstituteFirstToolLabel(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    tool_files: [":foo.tool", ":other.tool"],
+    cmd: "$(locations) -s $(out) $(in)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":  `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
+			"outs": `["foo.out"]`,
+			"srcs": `["foo.in"]`,
+			"tools": `[
+        "//other:foo.tool",
+        "//other:other.tool",
+    ]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+					filesystem:                 otherGenruleBp(tc.moduleType),
+				})
+		})
+	}
+}
+
+func TestGenruleWithoutToolsOrToolFiles(t *testing.T) {
+	testCases := []struct {
+		moduleType string
+		factory    android.ModuleFactory
+	}{
+		{
+			moduleType: "genrule",
+			factory:    genrule.GenRuleFactory,
+		},
+		{
+			moduleType: "cc_genrule",
+			factory:    cc.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule",
+			factory:    java.GenRuleFactory,
+		},
+		{
+			moduleType: "java_genrule_host",
+			factory:    java.GenRuleFactoryHost,
+		},
+	}
+
+	bp := `%s {
+    name: "foo",
+    out: ["foo.out"],
+    srcs: ["foo.in"],
+    cmd: "cp $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}`
+
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":  `"cp $(SRCS) $(OUTS)"`,
+			"outs": `["foo.out"]`,
+			"srcs": `["foo.in"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
+		t.Run(tc.moduleType, func(t *testing.T) {
+			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
+				bp2buildTestCase{
+					moduleTypeUnderTest:        tc.moduleType,
+					moduleTypeUnderTestFactory: tc.factory,
+					blueprint:                  fmt.Sprintf(bp, tc.moduleType),
+					expectedBazelTargets:       expectedBazelTargets,
+				})
+		})
+	}
+}
+
+func TestGenruleBp2BuildInlinesDefaults(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description: "genrule applies properties from a genrule_defaults dependency if not specified",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults",
+    cmd: "do-something $(in) $(out)",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd":  `"do-something $(SRCS) $(OUTS)"`,
+					"outs": `["out"]`,
+					"srcs": `["in1"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults",
+    out: ["out-from-defaults"],
+    srcs: ["in-from-defaults"],
+    cmd: "cmd-from-defaults",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+    cmd: "do-something $(in) $(out)",
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd": `"do-something $(SRCS) $(OUTS)"`,
+					"outs": `[
+        "out-from-defaults",
+        "out",
+    ]`,
+					"srcs": `[
+        "in-from-defaults",
+        "in1",
+    ]`,
+				}),
+			},
+		},
+		{
+			description: "genrule applies properties from list of genrule_defaults",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults1",
+    cmd: "cp $(in) $(out)",
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    srcs: ["in1"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1", "gen_defaults2"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd":  `"cp $(SRCS) $(OUTS)"`,
+					"outs": `["out"]`,
+					"srcs": `["in1"]`,
+				}),
+			},
+		},
+		{
+			description: "genrule applies properties from genrule_defaults transitively",
+			blueprint: `genrule_defaults {
+    name: "gen_defaults1",
+    defaults: ["gen_defaults2"],
+    cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    defaults: ["gen_defaults3"],
+    cmd: "cmd2 $(in) $(out)",
+    out: ["out-from-2"],
+    srcs: ["in1"],
+}
+
+genrule_defaults {
+    name: "gen_defaults3",
+    out: ["out-from-3"],
+    srcs: ["srcs-from-3"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				makeBazelTarget("genrule", "gen", attrNameToString{
+					"cmd": `"cmd1 $(SRCS) $(OUTS)"`,
+					"outs": `[
+        "out-from-3",
+        "out-from-2",
+        "out",
+    ]`,
+					"srcs": `[
+        "srcs-from-3",
+        "in1",
+    ]`,
+				}),
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			runGenruleTestCase(t, testCase)
+		})
+	}
+}
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
new file mode 100644
index 0000000..4fc07e0
--- /dev/null
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -0,0 +1,100 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/java"
+)
+
+func runJavaBinaryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_binary_host"
+	(&tc).moduleTypeUnderTestFactory = java.BinaryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("cc_library_host_shared", cc.LibraryHostSharedFactory)
+		ctx.RegisterModuleType("java_library", java.LibraryFactory)
+	}, tc)
+}
+
+var fs = map[string]string{
+	"test.mf": "Main-Class: com.android.test.MainClass",
+	"other/Android.bp": `cc_library_host_shared {
+    name: "jni-lib-1",
+    stl: "none",
+}`,
+}
+
+func TestJavaBinaryHost(t *testing.T) {
+	runJavaBinaryHostTestCase(t, bp2buildTestCase{
+		description: "java_binary_host with srcs, exclude_srcs, jni_libs, javacflags, and manifest.",
+		filesystem:  fs,
+		blueprint: `java_binary_host {
+    name: "java-binary-host-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    manifest: "test.mf",
+    jni_libs: ["jni-lib-1"],
+    javacflags: ["-Xdoclint:all/protected"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
+				"srcs":       `["a.java"]`,
+				"main_class": `"com.android.test.MainClass"`,
+				"deps":       `["//other:jni-lib-1"]`,
+				"jvm_flags":  `["-Djava.library.path=$${RUNPATH}other"]`,
+				"javacopts":  `["-Xdoclint:all/protected"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
+
+func TestJavaBinaryHostRuntimeDeps(t *testing.T) {
+	runJavaBinaryHostTestCase(t, bp2buildTestCase{
+		description: "java_binary_host with srcs, exclude_srcs, jni_libs, javacflags, and manifest.",
+		filesystem:  fs,
+		blueprint: `java_binary_host {
+    name: "java-binary-host-1",
+    static_libs: ["java-dep-1"],
+    manifest: "test.mf",
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-dep-1",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_binary", "java-binary-host-1", attrNameToString{
+				"main_class":   `"com.android.test.MainClass"`,
+				"runtime_deps": `[":java-dep-1"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_import_conversion_test.go b/bp2build/java_import_conversion_test.go
new file mode 100644
index 0000000..0b3191c
--- /dev/null
+++ b/bp2build/java_import_conversion_test.go
@@ -0,0 +1,85 @@
+// 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 (
+	"android/soong/android"
+	"android/soong/java"
+
+	"testing"
+)
+
+func runJavaImportTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerJavaImportModuleTypes, tc)
+}
+
+func registerJavaImportModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestJavaImportMinimal(t *testing.T) {
+	runJavaImportTestCase(t, bp2buildTestCase{
+		description:                "Java import - simple example",
+		moduleTypeUnderTest:        "java_import",
+		moduleTypeUnderTestFactory: java.ImportFactory,
+		filesystem: map[string]string{
+			"import.jar": "",
+		},
+		blueprint: `
+java_import {
+        name: "example_import",
+        jars: ["import.jar"],
+        bazel_module: { bp2build_available: true },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_import", "example_import", attrNameToString{
+				"jars": `["import.jar"]`,
+			}),
+		}})
+}
+
+func TestJavaImportArchVariant(t *testing.T) {
+	runJavaImportTestCase(t, bp2buildTestCase{
+		description:                "Java import - simple example",
+		moduleTypeUnderTest:        "java_import",
+		moduleTypeUnderTestFactory: java.ImportFactory,
+		filesystem: map[string]string{
+			"import.jar": "",
+		},
+		blueprint: `
+java_import {
+        name: "example_import",
+		target: {
+			android: {
+				jars: ["android.jar"],
+			},
+			linux_glibc: {
+				jars: ["linux.jar"],
+			},
+		},
+        bazel_module: { bp2build_available: true },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_import", "example_import", attrNameToString{
+				"jars": `select({
+        "//build/bazel/platforms/os:android": ["android.jar"],
+        "//build/bazel/platforms/os:linux": ["linux.jar"],
+        "//conditions:default": [],
+    })`,
+			}),
+		}})
+}
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
new file mode 100644
index 0000000..ccc52ef
--- /dev/null
+++ b/bp2build/java_library_conversion_test.go
@@ -0,0 +1,253 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaLibraryTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_library"
+	(&tc).moduleTypeUnderTestFactory = java.LibraryFactory
+	runBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runJavaLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runJavaLibraryTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
+}
+
+func TestJavaLibrary(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		description: "java_library with srcs, exclude_srcs and libs",
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[":java-lib-2"]`,
+			}),
+			makeBazelTarget("java_library", "java-lib-2", attrNameToString{
+				"srcs": `["b.java"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryConvertsStaticLibsToDepsAndExports(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    libs: ["java-lib-2"],
+    static_libs: ["java-lib-3"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-3",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[
+        ":java-lib-2",
+        ":java-lib-3",
+    ]`,
+				"exports": `[":java-lib-3"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryConvertsStaticLibsToExportsIfNoSrcs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"exports": `[":java-lib-2"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryFailsToConvertLibsWithNoSrcs(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		expectedErr: fmt.Errorf("Module has direct dependencies but no sources. Bazel will not allow this."),
+		blueprint: `java_library {
+    name: "java-lib-1",
+    libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{},
+	})
+}
+
+func TestJavaLibraryPlugins(t *testing.T) {
+	runJavaLibraryTestCaseWithRegistrationCtxFunc(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    plugins: ["java-plugin-1"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_plugin {
+    name: "java-plugin-1",
+    srcs: ["a.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"plugins": `[":java-plugin-1"]`,
+			}),
+		},
+	}, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_plugin", java.PluginFactory)
+	})
+}
+
+func TestJavaLibraryErrorproneJavacflagsEnabledManually(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    javacflags: ["-Xsuper-fast"],
+    errorprone: {
+        enabled: true,
+        javacflags: ["-Xep:SpeedLimit:OFF"],
+    },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"javacopts": `[
+        "-Xsuper-fast",
+        "-Xep:SpeedLimit:OFF",
+    ]`,
+				"srcs": `["a.java"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledByDefault(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    javacflags: ["-Xsuper-fast"],
+    errorprone: {
+        javacflags: ["-Xep:SpeedLimit:OFF"],
+    },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"javacopts": `["-Xsuper-fast"]`,
+				"srcs":      `["a.java"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryErrorproneJavacflagsErrorproneDisabledManually(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    javacflags: ["-Xsuper-fast"],
+    errorprone: {
+		enabled: false,
+        javacflags: ["-Xep:SpeedLimit:OFF"],
+    },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-1", attrNameToString{
+				"javacopts": `["-Xsuper-fast"]`,
+				"srcs":      `["a.java"]`,
+			}),
+		},
+	})
+}
+
+func TestJavaLibraryLogTags(t *testing.T) {
+	runJavaLibraryTestCase(t, bp2buildTestCase{
+		description:                "Java library - logtags creates separate dependency",
+		moduleTypeUnderTest:        "java_library",
+		moduleTypeUnderTestFactory: java.LibraryFactory,
+		blueprint: `java_library {
+        name: "example_lib",
+        srcs: [
+			"a.java",
+			"b.java",
+			"a.logtag",
+			"b.logtag",
+		],
+        bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("event_log_tags", "example_lib_logtags", attrNameToString{
+				"srcs": `[
+        "a.logtag",
+        "b.logtag",
+    ]`,
+			}),
+			makeBazelTarget("java_library", "example_lib", attrNameToString{
+				"srcs": `[
+        "a.java",
+        "b.java",
+        ":example_lib_logtags",
+    ]`,
+			}),
+		}})
+}
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
new file mode 100644
index 0000000..73abdd2
--- /dev/null
+++ b/bp2build/java_library_host_conversion_test.go
@@ -0,0 +1,65 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaLibraryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_library_host"
+	(&tc).moduleTypeUnderTestFactory = java.LibraryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestJavaLibraryHost(t *testing.T) {
+	runJavaLibraryHostTestCase(t, bp2buildTestCase{
+		description: "java_library_host with srcs, exclude_srcs and libs",
+		blueprint: `java_library_host {
+    name: "java-lib-host-1",
+    srcs: ["a.java", "b.java"],
+    exclude_srcs: ["b.java"],
+    libs: ["java-lib-host-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library_host {
+    name: "java-lib-host-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_library", "java-lib-host-1", attrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[":java-lib-host-2"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
+			makeBazelTarget("java_library", "java-lib-host-2", attrNameToString{
+				"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
new file mode 100644
index 0000000..c2a2182
--- /dev/null
+++ b/bp2build/java_plugin_conversion_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 bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaPluginTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_plugin"
+	(&tc).moduleTypeUnderTestFactory = java.PluginFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_library", java.LibraryFactory)
+	}, tc)
+}
+
+func TestJavaPlugin(t *testing.T) {
+	runJavaPluginTestCase(t, bp2buildTestCase{
+		description: "java_plugin with srcs, libs, static_libs",
+		blueprint: `java_plugin {
+    name: "java-plug-1",
+    srcs: ["a.java", "b.java"],
+    libs: ["java-lib-1"],
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-1",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_plugin", "java-plug-1", attrNameToString{
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+				"deps": `[
+        ":java-lib-1",
+        ":java-lib-2",
+    ]`,
+				"srcs": `[
+        "a.java",
+        "b.java",
+    ]`,
+			}),
+		},
+	})
+}
+
+func TestJavaPluginNoSrcs(t *testing.T) {
+	runJavaPluginTestCase(t, bp2buildTestCase{
+		description: "java_plugin without srcs converts (static) libs to deps",
+		blueprint: `java_plugin {
+    name: "java-plug-1",
+    libs: ["java-lib-1"],
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-1",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_plugin", "java-plug-1", attrNameToString{
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+				"deps": `[
+        ":java-lib-1",
+        ":java-lib-2",
+    ]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
new file mode 100644
index 0000000..67f8044
--- /dev/null
+++ b/bp2build/java_proto_conversion_test.go
@@ -0,0 +1,122 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaProtoTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_library_static"
+	(&tc).moduleTypeUnderTestFactory = java.LibraryFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestJavaProto(t *testing.T) {
+	testCases := []struct {
+		protoType                string
+		javaLibraryType          string
+		javaLibraryNameExtension string
+	}{
+		{
+			protoType:                "nano",
+			javaLibraryType:          "java_nano_proto_library",
+			javaLibraryNameExtension: "java_proto_nano",
+		},
+		{
+			protoType:                "micro",
+			javaLibraryType:          "java_micro_proto_library",
+			javaLibraryNameExtension: "java_proto_micro",
+		},
+		{
+			protoType:                "lite",
+			javaLibraryType:          "java_lite_proto_library",
+			javaLibraryNameExtension: "java_proto_lite",
+		},
+		{
+			protoType:                "stream",
+			javaLibraryType:          "java_stream_proto_library",
+			javaLibraryNameExtension: "java_proto_stream",
+		},
+		{
+			protoType:                "full",
+			javaLibraryType:          "java_proto_library",
+			javaLibraryNameExtension: "java_proto",
+		},
+	}
+
+	bp := `java_library_static {
+    name: "java-protos",
+    proto: {
+        type: "%s",
+    },
+    srcs: ["a.proto"],
+}`
+
+	protoLibrary := makeBazelTarget("proto_library", "java-protos_proto", attrNameToString{
+		"srcs": `["a.proto"]`,
+	})
+
+	for _, tc := range testCases {
+		javaLibraryName := fmt.Sprintf("java-protos_%s", tc.javaLibraryNameExtension)
+
+		runJavaProtoTestCase(t, bp2buildTestCase{
+			description: fmt.Sprintf("java_proto %s", tc.protoType),
+			blueprint:   fmt.Sprintf(bp, tc.protoType),
+			expectedBazelTargets: []string{
+				protoLibrary,
+				makeBazelTarget(
+					tc.javaLibraryType,
+					javaLibraryName,
+					attrNameToString{
+						"deps": `[":java-protos_proto"]`,
+					}),
+				makeBazelTarget("java_library", "java-protos", attrNameToString{
+					"exports": fmt.Sprintf(`[":%s"]`, javaLibraryName),
+				}),
+			},
+		})
+	}
+}
+
+func TestJavaProtoDefault(t *testing.T) {
+	runJavaProtoTestCase(t, bp2buildTestCase{
+		description: "java_library proto default",
+		blueprint: `java_library_static {
+    name: "java-protos",
+    srcs: ["a.proto"],
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("proto_library", "java-protos_proto", attrNameToString{
+				"srcs": `["a.proto"]`,
+			}),
+			makeBazelTarget(
+				"java_lite_proto_library",
+				"java-protos_java_proto_lite",
+				attrNameToString{
+					"deps": `[":java-protos_proto"]`,
+				}),
+			makeBazelTarget("java_library", "java-protos", attrNameToString{
+				"exports": `[":java-protos_java_proto_lite"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index 65b06c6..04fac44 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -1,34 +1,164 @@
 package bp2build
 
 import (
-	"android/soong/android"
 	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/shared"
+	"android/soong/ui/metrics/bp2build_metrics_proto"
+	"github.com/google/blueprint"
 )
 
 // Simple metrics struct to collect information about a Blueprint to BUILD
 // conversion process.
 type CodegenMetrics struct {
-	// Total number of Soong/Blueprint modules
-	TotalModuleCount int
+	// Total number of Soong modules converted to generated targets
+	generatedModuleCount uint64
+
+	// Total number of Soong modules converted to handcrafted targets
+	handCraftedModuleCount uint64
+
+	// Total number of unconverted Soong modules
+	unconvertedModuleCount uint64
 
 	// Counts of generated Bazel targets per Bazel rule class
-	RuleClassCount map[string]int
+	ruleClassCount map[string]uint64
 
-	// Total number of handcrafted targets
-	handCraftedTargetCount int
+	// List of modules with unconverted deps
+	// NOTE: NOT in the .proto
+	moduleWithUnconvertedDepsMsgs []string
+
+	// List of modules with missing deps
+	// NOTE: NOT in the .proto
+	moduleWithMissingDepsMsgs []string
+
+	// List of converted modules
+	convertedModules []string
+
+	// Counts of converted modules by module type.
+	convertedModuleTypeCount map[string]uint64
+
+	// Counts of total modules by module type.
+	totalModuleTypeCount map[string]uint64
+
+	Events []*bp2build_metrics_proto.Event
+}
+
+// Serialize returns the protoized version of CodegenMetrics: bp2build_metrics_proto.Bp2BuildMetrics
+func (metrics *CodegenMetrics) Serialize() bp2build_metrics_proto.Bp2BuildMetrics {
+	return bp2build_metrics_proto.Bp2BuildMetrics{
+		GeneratedModuleCount:     metrics.generatedModuleCount,
+		HandCraftedModuleCount:   metrics.handCraftedModuleCount,
+		UnconvertedModuleCount:   metrics.unconvertedModuleCount,
+		RuleClassCount:           metrics.ruleClassCount,
+		ConvertedModules:         metrics.convertedModules,
+		ConvertedModuleTypeCount: metrics.convertedModuleTypeCount,
+		TotalModuleTypeCount:     metrics.totalModuleTypeCount,
+		Events:                   metrics.Events,
+	}
 }
 
 // Print the codegen metrics to stdout.
-func (metrics CodegenMetrics) Print() {
-	generatedTargetCount := 0
-	for _, ruleClass := range android.SortedStringKeys(metrics.RuleClassCount) {
-		count := metrics.RuleClassCount[ruleClass]
+func (metrics *CodegenMetrics) Print() {
+	generatedTargetCount := uint64(0)
+	for _, ruleClass := range android.SortedStringKeys(metrics.ruleClassCount) {
+		count := metrics.ruleClassCount[ruleClass]
 		fmt.Printf("[bp2build] %s: %d targets\n", ruleClass, count)
 		generatedTargetCount += count
 	}
 	fmt.Printf(
-		"[bp2build] Generated %d total BUILD targets and included %d handcrafted BUILD targets from %d Android.bp modules.\n",
+		`[bp2build] Converted %d Android.bp modules to %d total generated BUILD targets. Included %d handcrafted BUILD targets. There are %d total Android.bp modules.
+%d converted modules have unconverted deps:
+	%s
+%d converted modules have missing deps:
+	%s
+`,
+		metrics.generatedModuleCount,
 		generatedTargetCount,
-		metrics.handCraftedTargetCount,
-		metrics.TotalModuleCount)
+		metrics.handCraftedModuleCount,
+		metrics.TotalModuleCount(),
+		len(metrics.moduleWithUnconvertedDepsMsgs),
+		strings.Join(metrics.moduleWithUnconvertedDepsMsgs, "\n\t"),
+		len(metrics.moduleWithMissingDepsMsgs),
+		strings.Join(metrics.moduleWithMissingDepsMsgs, "\n\t"),
+	)
+}
+
+const bp2buildMetricsFilename = "bp2build_metrics.pb"
+
+// fail prints $PWD to stderr, followed by the given printf string and args (vals),
+// then the given alert, and then exits with 1 for failure
+func fail(err error, alertFmt string, vals ...interface{}) {
+	cwd, wderr := os.Getwd()
+	if wderr != nil {
+		cwd = "FAILED TO GET $PWD: " + wderr.Error()
+	}
+	fmt.Fprintf(os.Stderr, "\nIn "+cwd+":\n"+alertFmt+"\n"+err.Error()+"\n", vals...)
+	os.Exit(1)
+}
+
+// Write the bp2build-protoized codegen metrics into the given directory
+func (metrics *CodegenMetrics) Write(dir string) {
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		// The metrics dir doesn't already exist, so create it (and parents)
+		if err := os.MkdirAll(dir, 0755); err != nil { // rx for all; w for user
+			fail(err, "Failed to `mkdir -p` %s", dir)
+		}
+	} else if err != nil {
+		fail(err, "Failed to `stat` %s", dir)
+	}
+	metricsFile := filepath.Join(dir, bp2buildMetricsFilename)
+	if err := metrics.dump(metricsFile); err != nil {
+		fail(err, "Error outputting %s", metricsFile)
+	}
+	if _, err := os.Stat(metricsFile); err != nil {
+		fail(err, "MISSING BP2BUILD METRICS OUTPUT: Failed to `stat` %s", metricsFile)
+	} else {
+		fmt.Printf("\nWrote bp2build metrics to: %s\n", metricsFile)
+	}
+}
+
+func (metrics *CodegenMetrics) IncrementRuleClassCount(ruleClass string) {
+	metrics.ruleClassCount[ruleClass] += 1
+}
+
+func (metrics *CodegenMetrics) AddUnconvertedModule(moduleType string) {
+	metrics.unconvertedModuleCount += 1
+	metrics.totalModuleTypeCount[moduleType] += 1
+}
+
+func (metrics *CodegenMetrics) TotalModuleCount() uint64 {
+	return metrics.handCraftedModuleCount +
+		metrics.generatedModuleCount +
+		metrics.unconvertedModuleCount
+}
+
+// Dump serializes the metrics to the given filename
+func (metrics *CodegenMetrics) dump(filename string) (err error) {
+	ser := metrics.Serialize()
+	return shared.Save(&ser, filename)
+}
+
+type ConversionType int
+
+const (
+	Generated ConversionType = iota
+	Handcrafted
+)
+
+func (metrics *CodegenMetrics) AddConvertedModule(m blueprint.Module, moduleType string, conversionType ConversionType) {
+	// Undo prebuilt_ module name prefix modifications
+	moduleName := android.RemoveOptionalPrebuiltPrefix(m.Name())
+	metrics.convertedModules = append(metrics.convertedModules, moduleName)
+	metrics.convertedModuleTypeCount[moduleType] += 1
+	metrics.totalModuleTypeCount[moduleType] += 1
+
+	if conversionType == Handcrafted {
+		metrics.handCraftedModuleCount += 1
+	} else if conversionType == Generated {
+		metrics.generatedModuleCount += 1
+	}
 }
diff --git a/bp2build/performance_test.go b/bp2build/performance_test.go
new file mode 100644
index 0000000..c4bbae2
--- /dev/null
+++ b/bp2build/performance_test.go
@@ -0,0 +1,218 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+// to run the benchmarks in this file, you must run go test with the -bench.
+// The benchmarked portion will run for the specified time (can be set via -benchtime)
+// This can mean if you are benchmarking a faster portion of a larger operation, it will take
+// longer.
+// If you are seeing a small number of iterations for a specific run, the data is less reliable, to
+// run for longer, set -benchtime to a larger value.
+
+import (
+	"android/soong/android"
+	"fmt"
+	"math"
+	"strings"
+	"testing"
+)
+
+const (
+	performance_test_dir = "."
+)
+
+func genCustomModule(i int, convert bool) string {
+	var conversionString string
+	if convert {
+		conversionString = `bazel_module: { bp2build_available: true },`
+	}
+	return fmt.Sprintf(`
+custom {
+    name: "arch_paths_%[1]d",
+    string_list_prop: ["\t", "\n"],
+    string_prop: "a\t\n\r",
+    arch_paths: ["outer", ":outer_dep_%[1]d"],
+    arch: {
+      x86: {
+        arch_paths: ["abc", ":x86_dep_%[1]d"],
+      },
+      x86_64: {
+        arch_paths: ["64bit"],
+        arch_paths_exclude: ["outer"],
+      },
+    },
+		%[2]s
+}
+
+custom {
+    name: "outer_dep_%[1]d",
+		%[2]s
+}
+
+custom {
+    name: "x86_dep_%[1]d",
+		%[2]s
+}
+`, i, conversionString)
+}
+
+func genCustomModuleBp(pctConverted float64) string {
+	modules := 100
+
+	bp := make([]string, 0, modules)
+	toConvert := int(math.Round(float64(modules) * pctConverted))
+
+	for i := 0; i < modules; i++ {
+		bp = append(bp, genCustomModule(i, i < toConvert))
+	}
+	return strings.Join(bp, "\n\n")
+}
+
+type testConfig struct {
+	config     android.Config
+	ctx        *android.TestContext
+	codegenCtx *CodegenContext
+}
+
+func (tc testConfig) parse() []error {
+	_, errs := tc.ctx.ParseFileList(performance_test_dir, []string{"Android.bp"})
+	return errs
+}
+
+func (tc testConfig) resolveDependencies() []error {
+	_, errs := tc.ctx.ResolveDependencies(tc.config)
+	return errs
+}
+
+func (tc testConfig) convert() {
+	generateBazelTargetsForDir(tc.codegenCtx, performance_test_dir)
+}
+
+func setup(builddir string, tcSize float64) testConfig {
+	config := android.TestConfig(buildDir, nil, genCustomModuleBp(tcSize), nil)
+	ctx := android.NewTestContext(config)
+
+	registerCustomModuleForBp2buildConversion(ctx)
+	codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+	return testConfig{
+		config,
+		ctx,
+		codegenCtx,
+	}
+}
+
+var pctToConvert = []float64{0.0, 0.01, 0.05, 0.10, 0.25, 0.5, 0.75, 1.0}
+
+// This is not intended to test performance, but to verify performance infra continues to work
+func TestConvertManyModulesFull(t *testing.T) {
+	for _, tcSize := range pctToConvert {
+
+		t.Run(fmt.Sprintf("pctConverted %f", tcSize), func(t *testing.T) {
+			testConfig := setup(buildDir, tcSize)
+
+			errs := testConfig.parse()
+			if len(errs) > 0 {
+				t.Fatalf("Unexpected errors: %s", errs)
+			}
+
+			errs = testConfig.resolveDependencies()
+			if len(errs) > 0 {
+				t.Fatalf("Unexpected errors: %s", errs)
+			}
+
+			testConfig.convert()
+		})
+	}
+}
+
+func BenchmarkManyModulesFull(b *testing.B) {
+	for _, tcSize := range pctToConvert {
+
+		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
+			for n := 0; n < b.N; n++ {
+				b.StopTimer()
+				testConfig := setup(buildDir, tcSize)
+
+				b.StartTimer()
+				errs := testConfig.parse()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				errs = testConfig.resolveDependencies()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				testConfig.convert()
+				b.StopTimer()
+			}
+		})
+	}
+}
+
+func BenchmarkManyModulesResolveDependencies(b *testing.B) {
+	for _, tcSize := range pctToConvert {
+
+		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
+			for n := 0; n < b.N; n++ {
+				b.StopTimer()
+				// setup we don't want to measure
+				testConfig := setup(buildDir, tcSize)
+
+				errs := testConfig.parse()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				b.StartTimer()
+				errs = testConfig.resolveDependencies()
+				b.StopTimer()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				testConfig.convert()
+			}
+		})
+	}
+}
+
+func BenchmarkManyModulesGenerateBazelTargetsForDir(b *testing.B) {
+	for _, tcSize := range pctToConvert {
+
+		b.Run(fmt.Sprintf("pctConverted %f", tcSize), func(b *testing.B) {
+			for n := 0; n < b.N; n++ {
+				b.StopTimer()
+				// setup we don't want to measure
+				testConfig := setup(buildDir, tcSize)
+
+				errs := testConfig.parse()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				errs = testConfig.resolveDependencies()
+				if len(errs) > 0 {
+					b.Fatalf("Unexpected errors: %s", errs)
+				}
+
+				b.StartTimer()
+				testConfig.convert()
+				b.StopTimer()
+			}
+		})
+	}
+}
diff --git a/bp2build/prebuilt_etc_conversion_test.go b/bp2build/prebuilt_etc_conversion_test.go
new file mode 100644
index 0000000..2e4b221
--- /dev/null
+++ b/bp2build/prebuilt_etc_conversion_test.go
@@ -0,0 +1,183 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/etc"
+
+	"testing"
+)
+
+func runPrebuiltEtcTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "prebuilt_etc"
+	(&tc).moduleTypeUnderTestFactory = etc.PrebuiltEtcFactory
+	runBp2BuildTestCase(t, registerPrebuiltEtcModuleTypes, tc)
+}
+
+func registerPrebuiltEtcModuleTypes(ctx android.RegistrationContext) {
+}
+
+func TestPrebuiltEtcSimple(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description: "prebuilt_etc - simple example",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    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":         `"etc/tz"`,
+			})}})
+}
+
+func TestPrebuiltEtcArchVariant(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description: "prebuilt_etc - arch variant",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    sub_dir: "tz",
+    installable: false,
+    arch: {
+      arm: {
+        src: "arm",
+      },
+      arm64: {
+        src: "arm64",
+      },
+    }
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src": `select({
+        "//build/bazel/platforms/arch:arm": "arm",
+        "//build/bazel/platforms/arch:arm64": "arm64",
+        "//conditions:default": "version/tz_version",
+    })`,
+				"dir": `"etc/tz"`,
+			})}})
+}
+
+func TestPrebuiltEtcArchAndTargetVariant(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description: "prebuilt_etc - arch variant",
+		filesystem:  map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    sub_dir: "tz",
+    installable: false,
+    arch: {
+      arm: {
+        src: "arm",
+      },
+      arm64: {
+        src: "darwin_or_arm64",
+      },
+    },
+    target: {
+      darwin: {
+        src: "darwin_or_arm64",
+      }
+    },
+}
+`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("prebuilt_file", "apex_tz_version", attrNameToString{
+				"filename":    `"tz_version"`,
+				"installable": `False`,
+				"src": `select({
+        "//build/bazel/platforms/os_arch:android_arm": "arm",
+        "//build/bazel/platforms/os_arch:android_arm64": "darwin_or_arm64",
+        "//build/bazel/platforms/os_arch:darwin_arm64": "darwin_or_arm64",
+        "//build/bazel/platforms/os_arch:darwin_x86_64": "darwin_or_arm64",
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": "darwin_or_arm64",
+        "//conditions:default": "version/tz_version",
+    })`,
+				"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/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 2054e06..dfa11d1 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -1,62 +1,71 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/python"
-	"fmt"
-	"strings"
-	"testing"
 )
 
-func TestPythonBinaryHost(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "simple python_binary_host converts to a native py_binary",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			filesystem: map[string]string{
-				"a.py":           "",
-				"b/c.py":         "",
-				"b/d.py":         "",
-				"b/e.py":         "",
-				"files/data.txt": "",
-			},
-			blueprint: `python_binary_host {
+func runBp2BuildTestCaseWithPythonLibraries(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("python_library", python.PythonLibraryFactory)
+		ctx.RegisterModuleType("python_library_host", python.PythonLibraryHostFactory)
+	}, tc)
+}
+
+func TestPythonBinaryHostSimple(t *testing.T) {
+	runBp2BuildTestCaseWithPythonLibraries(t, bp2buildTestCase{
+		description:                "simple python_binary_host converts to a native py_binary",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
+		filesystem: map[string]string{
+			"a.py":           "",
+			"b/c.py":         "",
+			"b/d.py":         "",
+			"b/e.py":         "",
+			"files/data.txt": "",
+		},
+		blueprint: `python_binary_host {
     name: "foo",
     main: "a.py",
     srcs: ["**/*.py"],
     exclude_srcs: ["b/e.py"],
     data: ["files/data.txt",],
+    libs: ["bar"],
     bazel_module: { bp2build_available: true },
 }
-`,
-			expectedBazelTargets: []string{`py_binary(
-    name = "foo",
-    data = ["files/data.txt"],
-    main = "a.py",
-    srcs = [
+    python_library_host {
+      name: "bar",
+      srcs: ["b/e.py"],
+      bazel_module: { bp2build_available: false },
+    }`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"data": `["files/data.txt"]`,
+				"deps": `[":bar"]`,
+				"main": `"a.py"`,
+				"srcs": `[
         "a.py",
         "b/c.py",
         "b/d.py",
-    ],
-)`,
-			},
+    ]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
-		{
-			description:                        "py2 python_binary_host",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			blueprint: `python_binary_host {
+	})
+}
+
+func TestPythonBinaryHostPy2(t *testing.T) {
+	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
+		description:                "py2 python_binary_host",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
+		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -71,19 +80,25 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			expectedBazelTargets: []string{`py_binary(
-    name = "foo",
-    python_version = "PY2",
-    srcs = ["a.py"],
-)`,
-			},
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"python_version": `"PY2"`,
+				"srcs":           `["a.py"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
-		{
-			description:                        "py3 python_binary_host",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			blueprint: `python_binary_host {
+	})
+}
+
+func TestPythonBinaryHostPy3(t *testing.T) {
+	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
+		description:                "py3 python_binary_host",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
+		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -98,60 +113,51 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			expectedBazelTargets: []string{
-				// python_version is PY3 by default.
-				`py_binary(
-    name = "foo",
-    srcs = ["a.py"],
-)`,
-			},
+		expectedBazelTargets: []string{
+			// python_version is PY3 by default.
+			makeBazelTarget("py_binary", "foo", attrNameToString{
+				"srcs": `["a.py"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
 		},
-	}
+	})
+}
 
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+func TestPythonBinaryHostArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
+		description:                "test arch variants",
+		moduleTypeUnderTest:        "python_binary_host",
+		moduleTypeUnderTestFactory: python.PythonBinaryHostFactory,
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: `python_binary_host {
+					 name: "foo-arm",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("py_binary", "foo-arm", attrNameToString{
+				"srcs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    })`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+			}),
+		},
+	})
 }
diff --git a/bp2build/python_library_conversion_test.go b/bp2build/python_library_conversion_test.go
new file mode 100644
index 0000000..356d52e
--- /dev/null
+++ b/bp2build/python_library_conversion_test.go
@@ -0,0 +1,239 @@
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/python"
+)
+
+// TODO(alexmarquez): Should be lifted into a generic Bp2Build file
+type PythonLibBp2Build func(ctx android.TopDownMutatorContext)
+
+type pythonLibBp2BuildTestCase struct {
+	description          string
+	filesystem           map[string]string
+	blueprint            string
+	expectedBazelTargets []testBazelTarget
+}
+
+func convertPythonLibTestCaseToBp2build_Host(tc pythonLibBp2BuildTestCase) bp2buildTestCase {
+	for i := range tc.expectedBazelTargets {
+		tc.expectedBazelTargets[i].attrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+	}
+
+	return convertPythonLibTestCaseToBp2build(tc)
+}
+
+func convertPythonLibTestCaseToBp2build(tc pythonLibBp2BuildTestCase) bp2buildTestCase {
+	var bp2BuildTargets []string
+	for _, t := range tc.expectedBazelTargets {
+		bp2BuildTargets = append(bp2BuildTargets, makeBazelTarget(t.typ, t.name, t.attrs))
+	}
+	return bp2buildTestCase{
+		description:          tc.description,
+		filesystem:           tc.filesystem,
+		blueprint:            tc.blueprint,
+		expectedBazelTargets: bp2BuildTargets,
+	}
+}
+
+func runPythonLibraryTestCase(t *testing.T, tc pythonLibBp2BuildTestCase) {
+	t.Helper()
+	testCase := convertPythonLibTestCaseToBp2build(tc)
+	testCase.description = fmt.Sprintf(testCase.description, "python_library")
+	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library")
+	testCase.moduleTypeUnderTest = "python_library"
+	testCase.moduleTypeUnderTestFactory = python.PythonLibraryFactory
+
+	runBp2BuildTestCaseSimple(t, testCase)
+}
+
+func runPythonLibraryHostTestCase(t *testing.T, tc pythonLibBp2BuildTestCase) {
+	t.Helper()
+	testCase := convertPythonLibTestCaseToBp2build_Host(tc)
+	testCase.description = fmt.Sprintf(testCase.description, "python_library_host")
+	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library_host")
+	testCase.moduleTypeUnderTest = "python_library_host"
+	testCase.moduleTypeUnderTestFactory = python.PythonLibraryHostFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("python_library", python.PythonLibraryFactory)
+	},
+		testCase)
+}
+
+func runPythonLibraryTestCases(t *testing.T, tc pythonLibBp2BuildTestCase) {
+	t.Helper()
+	runPythonLibraryTestCase(t, tc)
+	runPythonLibraryHostTestCase(t, tc)
+}
+
+func TestSimplePythonLib(t *testing.T) {
+	testCases := []pythonLibBp2BuildTestCase{
+		{
+			description: "simple %s converts to a native py_library",
+			filesystem: map[string]string{
+				"a.py":           "",
+				"b/c.py":         "",
+				"b/d.py":         "",
+				"b/e.py":         "",
+				"files/data.txt": "",
+			},
+			blueprint: `%s {
+    name: "foo",
+    srcs: ["**/*.py"],
+    exclude_srcs: ["b/e.py"],
+    data: ["files/data.txt",],
+    libs: ["bar"],
+    bazel_module: { bp2build_available: true },
+}
+    python_library {
+      name: "bar",
+      srcs: ["b/e.py"],
+      bazel_module: { bp2build_available: false },
+    }`,
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"data": `["files/data.txt"]`,
+						"deps": `[":bar"]`,
+						"srcs": `[
+        "a.py",
+        "b/c.py",
+        "b/d.py",
+    ]`,
+						"srcs_version": `"PY3"`,
+					},
+				},
+			},
+		},
+		{
+			description: "py2 %s converts to a native py_library",
+			blueprint: `%s {
+    name: "foo",
+    srcs: ["a.py"],
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: false,
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs":         `["a.py"]`,
+						"srcs_version": `"PY2"`,
+					},
+				},
+			},
+		},
+		{
+			description: "py3 %s converts to a native py_library",
+			blueprint: `%s {
+    name: "foo",
+    srcs: ["a.py"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs":         `["a.py"]`,
+						"srcs_version": `"PY3"`,
+					},
+				},
+			},
+		},
+		{
+			description: "py2&3 %s converts to a native py_library",
+			blueprint: `%s {
+    name: "foo",
+    srcs: ["a.py"],
+    version: {
+        py2: {
+            enabled: true,
+        },
+        py3: {
+            enabled: true,
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []testBazelTarget{
+				{
+					// srcs_version is PY2ANDPY3 by default.
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs": `["a.py"]`,
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.description, func(t *testing.T) {
+			runPythonLibraryTestCases(t, tc)
+		})
+	}
+}
+
+func TestPythonArchVariance(t *testing.T) {
+	runPythonLibraryTestCases(t, pythonLibBp2BuildTestCase{
+		description: "test %s arch variants",
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: `%s {
+					 name: "foo",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`,
+		expectedBazelTargets: []testBazelTarget{
+			{
+				typ:  "py_library",
+				name: "foo",
+				attrs: attrNameToString{
+					"srcs": `select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    })`,
+					"srcs_version": `"PY3"`,
+				},
+			},
+		},
+	})
+}
diff --git a/bp2build/sh_conversion_test.go b/bp2build/sh_conversion_test.go
index 37f542e..ac89087 100644
--- a/bp2build/sh_conversion_test.go
+++ b/bp2build/sh_conversion_test.go
@@ -15,10 +15,10 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/sh"
-	"strings"
-	"testing"
 )
 
 func TestShBinaryLoadStatement(t *testing.T) {
@@ -46,88 +46,47 @@
 			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
 		}
 	}
-
 }
 
-func TestShBinaryBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "sh_binary test",
-			moduleTypeUnderTest:                "sh_binary",
-			moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
-			moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
-			bp: `sh_binary {
+func runShBinaryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestShBinarySimple(t *testing.T) {
+	runShBinaryTestCase(t, bp2buildTestCase{
+		description:                "sh_binary test",
+		moduleTypeUnderTest:        "sh_binary",
+		moduleTypeUnderTestFactory: sh.ShBinaryFactory,
+		blueprint: `sh_binary {
+    name: "foo",
+    src: "foo.sh",
+    filename: "foo.exe",
+    sub_dir: "sub",
+    bazel_module: { bp2build_available: true },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("sh_binary", "foo", attrNameToString{
+				"srcs":     `["foo.sh"]`,
+				"filename": `"foo.exe"`,
+				"sub_dir":  `"sub"`,
+			})},
+	})
+}
+
+func TestShBinaryDefaults(t *testing.T) {
+	runShBinaryTestCase(t, bp2buildTestCase{
+		description:                "sh_binary test",
+		moduleTypeUnderTest:        "sh_binary",
+		moduleTypeUnderTestFactory: sh.ShBinaryFactory,
+		blueprint: `sh_binary {
     name: "foo",
     src: "foo.sh",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`sh_binary(
-    name = "foo",
-    srcs = ["foo.sh"],
-)`},
-		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+		expectedBazelTargets: []string{
+			makeBazelTarget("sh_binary", "foo", attrNameToString{
+				"srcs": `["foo.sh"]`,
+			})},
+	})
 }
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
new file mode 100644
index 0000000..b1e1fb2
--- /dev/null
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -0,0 +1,985 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"testing"
+)
+
+func runSoongConfigModuleTypeTest(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerSoongConfigModuleTypes, tc)
+}
+
+func registerSoongConfigModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+
+	ctx.RegisterModuleType("soong_config_module_type_import", android.SoongConfigModuleTypeImportFactory)
+	ctx.RegisterModuleType("soong_config_module_type", android.SoongConfigModuleTypeFactory)
+	ctx.RegisterModuleType("soong_config_string_variable", android.SoongConfigStringVariableDummyFactory)
+	ctx.RegisterModuleType("soong_config_bool_variable", android.SoongConfigBoolVariableDummyFactory)
+
+	ctx.RegisterModuleType("cc_library", cc.LibraryFactory)
+}
+
+func TestSoongConfigModuleType(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	bool_variables: ["feature1"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - soong_config_module_type is supported in bp2build",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleTypeImport(t *testing.T) {
+	configBp := `
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	bool_variables: ["feature1"],
+	properties: ["cflags"],
+}
+`
+	bp := `
+soong_config_module_type_import {
+	from: "foo/bar/SoongConfig.bp",
+	module_types: ["custom_cc_library_static"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - soong_config_module_type_import is supported in bp2build",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		filesystem: map[string]string{
+			"foo/bar/SoongConfig.bp": configBp,
+		},
+		blueprint: bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringVar(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["board"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"]
+			},
+		},
+	},
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for string vars",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringAndBoolVar(t *testing.T) {
+	bp := `
+soong_config_bool_variable {
+	name: "feature1",
+}
+
+soong_config_bool_variable {
+	name: "feature2",
+}
+
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["feature1", "feature2", "board"],
+	properties: ["cflags"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		feature1: {
+			conditions_default: {
+				cflags: ["-DDEFAULT1"],
+			},
+			cflags: ["-DFEATURE1"],
+		},
+		feature2: {
+			cflags: ["-DFEATURE2"],
+			conditions_default: {
+				cflags: ["-DDEFAULT2"],
+			},
+		},
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"]
+			},
+		},
+	},
+}`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for multiple variable types",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:acme__feature1": ["-DFEATURE1"],
+        "//conditions:default": ["-DDEFAULT1"],
+    }) + select({
+        "//build/bazel/product_variables:acme__feature2": ["-DFEATURE2"],
+        "//conditions:default": ["-DDEFAULT2"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_StringVar_LabelListDeps(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+	name: "board",
+	values: ["soc_a", "soc_b", "soc_c"],
+}
+
+soong_config_module_type {
+	name: "custom_cc_library_static",
+	module_type: "cc_library_static",
+	config_namespace: "acme",
+	variables: ["board"],
+	properties: ["cflags", "static_libs"],
+}
+
+custom_cc_library_static {
+	name: "foo",
+	bazel_module: { bp2build_available: true },
+	soong_config_variables: {
+		board: {
+			soc_a: {
+				cflags: ["-DSOC_A"],
+				static_libs: ["soc_a_dep"],
+			},
+			soc_b: {
+				cflags: ["-DSOC_B"],
+				static_libs: ["soc_b_dep"],
+			},
+			soc_c: {},
+			conditions_default: {
+				cflags: ["-DSOC_DEFAULT"],
+				static_libs: ["soc_default_static_dep"],
+			},
+		},
+	},
+}`
+
+	otherDeps := `
+cc_library_static { name: "soc_a_dep", bazel_module: { bp2build_available: false } }
+cc_library_static { name: "soc_b_dep", bazel_module: { bp2build_available: false } }
+cc_library_static { name: "soc_default_static_dep", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for label list attributes",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo",
+    copts = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["-DSOC_A"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["-DSOC_B"],
+        "//conditions:default": ["-DSOC_DEFAULT"],
+    }),
+    implementation_deps = select({
+        "//build/bazel/product_variables:acme__board__soc_a": ["//foo/bar:soc_a_dep"],
+        "//build/bazel/product_variables:acme__board__soc_b": ["//foo/bar:soc_b_dep"],
+        "//conditions:default": ["//foo/bar:soc_default_static_dep"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_SingleNamespace(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "vendor_foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_foo",
+	bool_variables: ["feature"],
+	properties: ["cflags", "cppflags"],
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults_1",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_feature_1"],
+			conditions_default: {
+				cflags: ["-cflag_default_1"],
+			},
+		},
+	},
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults_2",
+	defaults: ["foo_defaults_1"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_feature_2"],
+			conditions_default: {
+				cflags: ["-cflag_default_2"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults_2"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - defaults with a single namespace",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    copts = select({
+        "//build/bazel/product_variables:vendor_foo__feature": [
+            "-cflag_feature_2",
+            "-cflag_feature_1",
+        ],
+        "//conditions:default": [
+            "-cflag_default_2",
+            "-cflag_default_1",
+        ],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_MultipleDefaults_SingleNamespace(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "acme",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "bar_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "acme",
+	bool_variables: ["feature"],
+	properties: ["cflags", "asflags"],
+}
+
+foo_cc_defaults {
+	name: "foo_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_foo"],
+			conditions_default: {
+				cflags: ["-cflag_default_foo"],
+			},
+		},
+	},
+}
+
+bar_cc_defaults {
+	name: "bar_defaults",
+	srcs: ["file.S"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-cflag_bar"],
+			asflags: ["-asflag_bar"],
+			conditions_default: {
+				asflags: ["-asflag_default_bar"],
+				cflags: ["-cflag_default_bar"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults", "bar_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+	name: "lib2",
+	defaults: ["bar_defaults", "foo_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - multiple defaults with a single namespace",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    asflags = select({
+        "//build/bazel/product_variables:acme__feature": ["-asflag_bar"],
+        "//conditions:default": ["-asflag_default_bar"],
+    }),
+    copts = select({
+        "//build/bazel/product_variables:acme__feature": [
+            "-cflag_foo",
+            "-cflag_bar",
+        ],
+        "//conditions:default": [
+            "-cflag_default_foo",
+            "-cflag_default_bar",
+        ],
+    }),
+    local_includes = ["."],
+    srcs_as = ["file.S"],
+)`,
+			`cc_library_static(
+    name = "lib2",
+    asflags = select({
+        "//build/bazel/product_variables:acme__feature": ["-asflag_bar"],
+        "//conditions:default": ["-asflag_default_bar"],
+    }),
+    copts = select({
+        "//build/bazel/product_variables:acme__feature": [
+            "-cflag_bar",
+            "-cflag_foo",
+        ],
+        "//conditions:default": [
+            "-cflag_default_bar",
+            "-cflag_default_foo",
+        ],
+    }),
+    local_includes = ["."],
+    srcs_as = ["file.S"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_MultipleNamespaces(t *testing.T) {
+	bp := `
+soong_config_module_type {
+	name: "vendor_foo_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_foo",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "vendor_bar_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_bar",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+soong_config_module_type {
+	name: "vendor_qux_cc_defaults",
+	module_type: "cc_defaults",
+	config_namespace: "vendor_qux",
+	bool_variables: ["feature"],
+	properties: ["cflags"],
+}
+
+vendor_foo_cc_defaults {
+	name: "foo_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_FOO_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_FOO_DEFAULT"],
+			},
+		},
+	},
+}
+
+vendor_bar_cc_defaults {
+	name: "bar_defaults",
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_BAR_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_BAR_DEFAULT"],
+			},
+		},
+	},
+}
+
+vendor_qux_cc_defaults {
+	name: "qux_defaults",
+	defaults: ["bar_defaults"],
+	soong_config_variables: {
+		feature: {
+			cflags: ["-DVENDOR_QUX_FEATURE"],
+			conditions_default: {
+				cflags: ["-DVENDOR_QUX_DEFAULT"],
+			},
+		},
+	},
+}
+
+cc_library_static {
+	name: "lib",
+	defaults: ["foo_defaults", "qux_defaults"],
+	bazel_module: { bp2build_available: true },
+}
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - defaults with multiple namespaces",
+		moduleTypeUnderTest:        "cc_library_static",
+		moduleTypeUnderTestFactory: cc.LibraryStaticFactory,
+		blueprint:                  bp,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "lib",
+    copts = select({
+        "//build/bazel/product_variables:vendor_bar__feature": ["-DVENDOR_BAR_FEATURE"],
+        "//conditions:default": ["-DVENDOR_BAR_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:vendor_foo__feature": ["-DVENDOR_FOO_FEATURE"],
+        "//conditions:default": ["-DVENDOR_FOO_DEFAULT"],
+    }) + select({
+        "//build/bazel/product_variables:vendor_qux__feature": ["-DVENDOR_QUX_FEATURE"],
+        "//conditions:default": ["-DVENDOR_QUX_DEFAULT"],
+    }),
+    local_includes = ["."],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+    name: "library_linking_strategy",
+    values: [
+        "prefer_static",
+    ],
+}
+
+soong_config_module_type {
+    name: "library_linking_strategy_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    variables: ["library_linking_strategy"],
+    properties: [
+        "shared_libs",
+        "static_libs",
+    ],
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_lib_a_defaults",
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_a",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_a",
+                ],
+            },
+        },
+    },
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_merged_defaults",
+    defaults: ["library_linking_strategy_lib_a_defaults"],
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_b",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary",
+    srcs: ["library_linking_strategy.cc"],
+    defaults: ["library_linking_strategy_merged_defaults"],
+}`
+
+	otherDeps := `
+cc_library { name: "lib_a", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_b", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_default", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "library_linking_strategy_sample_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+            "//foo/bar:lib_b_bp2build_cc_library_static",
+            "//foo/bar:lib_a_bp2build_cc_library_static",
+        ],
+        "//conditions:default": [],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": [
+            "//foo/bar:lib_b",
+            "//foo/bar:lib_a",
+        ],
+    }),
+    local_includes = ["."],
+    srcs = ["library_linking_strategy.cc"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_Another(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+    name: "library_linking_strategy",
+    values: [
+        "prefer_static",
+    ],
+}
+
+soong_config_module_type {
+    name: "library_linking_strategy_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    variables: ["library_linking_strategy"],
+    properties: [
+        "shared_libs",
+        "static_libs",
+    ],
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_sample_defaults",
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {
+                static_libs: [
+                    "lib_a",
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                shared_libs: [
+                    "lib_a",
+                    "lib_b",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary",
+    srcs: ["library_linking_strategy.cc"],
+    defaults: ["library_linking_strategy_sample_defaults"],
+}`
+
+	otherDeps := `
+cc_library { name: "lib_a", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_b", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "library_linking_strategy_sample_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [
+            "//foo/bar:lib_a_bp2build_cc_library_static",
+            "//foo/bar:lib_b_bp2build_cc_library_static",
+        ],
+        "//conditions:default": [],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": [
+            "//foo/bar:lib_a",
+            "//foo/bar:lib_b",
+        ],
+    }),
+    local_includes = ["."],
+    srcs = ["library_linking_strategy.cc"],
+)`}})
+}
+
+func TestSoongConfigModuleType_Defaults_UnusedProps(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+    name: "alphabet",
+    values: [
+        "a",
+        "b",
+        "c", // unused
+    ],
+}
+
+soong_config_module_type {
+    name: "alphabet_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    variables: ["alphabet"],
+    properties: [
+        "cflags", // unused
+        "shared_libs",
+        "static_libs",
+    ],
+}
+
+alphabet_cc_defaults {
+    name: "alphabet_sample_cc_defaults",
+    soong_config_variables: {
+        alphabet: {
+            a: {
+                shared_libs: [
+                    "lib_a",
+                ],
+            },
+            b: {
+                shared_libs: [
+                    "lib_b",
+                ],
+            },
+            conditions_default: {
+                static_libs: [
+                    "lib_default",
+                ],
+            },
+        },
+    },
+}
+
+cc_binary {
+    name: "alphabet_binary",
+    srcs: ["main.cc"],
+    defaults: ["alphabet_sample_cc_defaults"],
+}`
+
+	otherDeps := `
+cc_library { name: "lib_a", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_b", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_default", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "alphabet_binary",
+    deps = select({
+        "//build/bazel/product_variables:android__alphabet__a": [],
+        "//build/bazel/product_variables:android__alphabet__b": [],
+        "//conditions:default": ["//foo/bar:lib_default_bp2build_cc_library_static"],
+    }),
+    dynamic_deps = select({
+        "//build/bazel/product_variables:android__alphabet__a": ["//foo/bar:lib_a"],
+        "//build/bazel/product_variables:android__alphabet__b": ["//foo/bar:lib_b"],
+        "//conditions:default": [],
+    }),
+    local_includes = ["."],
+    srcs = ["main.cc"],
+)`}})
+}
+
+func TestSoongConfigModuleType_ProductVariableConfigWithPlatformConfig(t *testing.T) {
+	bp := `
+soong_config_bool_variable {
+    name: "special_build",
+}
+
+soong_config_module_type {
+    name: "alphabet_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "alphabet_module",
+    bool_variables: ["special_build"],
+    properties: ["enabled"],
+}
+
+alphabet_cc_defaults {
+    name: "alphabet_sample_cc_defaults",
+    soong_config_variables: {
+        special_build: {
+            enabled: true,
+        },
+    },
+}
+
+cc_binary {
+    name: "alphabet_binary",
+    srcs: ["main.cc"],
+    defaults: ["alphabet_sample_cc_defaults"],
+    enabled: false,
+    arch: {
+        x86_64: {
+            enabled: false,
+        },
+    },
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+}`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem:                 map[string]string{},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "alphabet_binary",
+    local_includes = ["."],
+    srcs = ["main.cc"],
+    target_compatible_with = ["//build/bazel/product_variables:alphabet_module__special_build"] + select({
+        "//build/bazel/platforms/os_arch:android_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:darwin_arm64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:darwin_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:linux_bionic_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:linux_glibc_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:linux_musl_x86_64": ["@platforms//:incompatible"],
+        "//build/bazel/platforms/os_arch:windows_x86_64": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    }),
+)`}})
+}
+
+func TestSoongConfigModuleType_ProductVariableConfigOverridesEnable(t *testing.T) {
+	bp := `
+soong_config_bool_variable {
+    name: "special_build",
+}
+
+soong_config_module_type {
+    name: "alphabet_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "alphabet_module",
+    bool_variables: ["special_build"],
+    properties: ["enabled"],
+}
+
+alphabet_cc_defaults {
+    name: "alphabet_sample_cc_defaults",
+    soong_config_variables: {
+        special_build: {
+            enabled: true,
+        },
+    },
+}
+
+cc_binary {
+    name: "alphabet_binary",
+    srcs: ["main.cc"],
+    defaults: ["alphabet_sample_cc_defaults"],
+    enabled: false,
+}`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem:                 map[string]string{},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "alphabet_binary",
+    local_includes = ["."],
+    srcs = ["main.cc"],
+    target_compatible_with = ["//build/bazel/product_variables:alphabet_module__special_build"],
+)`}})
+}
+
+func TestSoongConfigModuleType_ProductVariableIgnoredIfEnabledByDefault(t *testing.T) {
+	bp := `
+soong_config_bool_variable {
+    name: "special_build",
+}
+
+soong_config_module_type {
+    name: "alphabet_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "alphabet_module",
+    bool_variables: ["special_build"],
+    properties: ["enabled"],
+}
+
+alphabet_cc_defaults {
+    name: "alphabet_sample_cc_defaults",
+    soong_config_variables: {
+        special_build: {
+            enabled: true,
+        },
+    },
+}
+
+cc_binary {
+    name: "alphabet_binary",
+    srcs: ["main.cc"],
+    defaults: ["alphabet_sample_cc_defaults"],
+}`
+
+	runSoongConfigModuleTypeTest(t, bp2buildTestCase{
+		description:                "soong config variables - generates selects for library_linking_strategy",
+		moduleTypeUnderTest:        "cc_binary",
+		moduleTypeUnderTestFactory: cc.BinaryFactory,
+		blueprint:                  bp,
+		filesystem:                 map[string]string{},
+		expectedBazelTargets: []string{`cc_binary(
+    name = "alphabet_binary",
+    local_includes = ["."],
+    srcs = ["main.cc"],
+)`}})
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index b925682..029ba49 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -1,22 +1,175 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package bp2build
 
+/*
+For shareable/common bp2build testing functionality and dumping ground for
+specific-but-shared functionality among tests in package
+*/
+
 import (
+	"fmt"
+	"strings"
+	"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
 )
 
+func checkError(t *testing.T, errs []error, expectedErr error) bool {
+	t.Helper()
+
+	if len(errs) != 1 {
+		return false
+	}
+	if strings.Contains(errs[0].Error(), expectedErr.Error()) {
+		return true
+	}
+
+	return false
+}
+
+func errored(t *testing.T, tc bp2buildTestCase, errs []error) bool {
+	t.Helper()
+	if tc.expectedErr != nil {
+		// Rely on checkErrors, as this test case is expected to have an error.
+		return false
+	}
+
+	if len(errs) > 0 {
+		for _, err := range errs {
+			t.Errorf("%s: %s", tc.description, err)
+		}
+		return true
+	}
+
+	// All good, continue execution.
+	return false
+}
+
+func runBp2BuildTestCaseSimple(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+type bp2buildTestCase struct {
+	description                string
+	moduleTypeUnderTest        string
+	moduleTypeUnderTestFactory android.ModuleFactory
+	blueprint                  string
+	expectedBazelTargets       []string
+	filesystem                 map[string]string
+	dir                        string
+	// 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) {
+	t.Helper()
+	dir := "."
+	filesystem := make(map[string][]byte)
+	toParse := []string{
+		"Android.bp",
+	}
+	for f, content := range tc.filesystem {
+		if strings.HasSuffix(f, "Android.bp") {
+			toParse = append(toParse, f)
+		}
+		filesystem[f] = []byte(content)
+	}
+	config := android.TestConfig(buildDir, nil, tc.blueprint, filesystem)
+	ctx := android.NewTestContext(config)
+
+	registerModuleTypes(ctx)
+	ctx.RegisterModuleType(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestFactory)
+	ctx.RegisterBp2BuildConfig(bp2buildConfig)
+	ctx.RegisterForBazelConversion()
+
+	_, parseErrs := ctx.ParseFileList(dir, toParse)
+	if errored(t, tc, parseErrs) {
+		return
+	}
+	_, resolveDepsErrs := ctx.ResolveDependencies(config)
+	if errored(t, tc, resolveDepsErrs) {
+		return
+	}
+
+	errs := append(parseErrs, resolveDepsErrs...)
+	if tc.expectedErr != nil && checkError(t, errs, tc.expectedErr) {
+		return
+	}
+
+	checkDir := dir
+	if tc.dir != "" {
+		checkDir = tc.dir
+	}
+	codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+	codegenCtx.unconvertedDepMode = tc.unconvertedDepsMode
+	bazelTargets, errs := generateBazelTargetsForDir(codegenCtx, checkDir)
+	if tc.expectedErr != nil {
+		if checkError(t, errs, tc.expectedErr) {
+			return
+		} else {
+			t.Errorf("Expected error: %q, got: %q", tc.expectedErr, errs)
+		}
+	} else {
+		android.FailIfErrored(t, errs)
+	}
+	if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount {
+		t.Errorf("%s: Expected %d bazel target (%s), got `%d`` (%s)",
+			tc.description, expectedCount, tc.expectedBazelTargets, actualCount, bazelTargets)
+	} else {
+		for i, target := range bazelTargets {
+			if w, g := tc.expectedBazelTargets[i], target.content; w != g {
+				t.Errorf(
+					"%s: Expected generated Bazel target to be `%s`, got `%s`",
+					tc.description, w, g)
+			}
+		}
+	}
+}
+
 type nestedProps struct {
-	Nested_prop string
+	Nested_prop *string
+}
+
+type EmbeddedProps struct {
+	Embedded_prop *string
+}
+
+type OtherEmbeddedProps struct {
+	Other_embedded_prop *string
 }
 
 type customProps struct {
+	EmbeddedProps
+	*OtherEmbeddedProps
+
 	Bool_prop     bool
 	Bool_ptr_prop *bool
 	// Ensure that properties tagged `blueprint:mutated` are omitted
@@ -29,7 +182,11 @@
 	Nested_props     nestedProps
 	Nested_props_ptr *nestedProps
 
-	Arch_paths []string `android:"path,arch_variant"`
+	Arch_paths         []string `android:"path,arch_variant"`
+	Arch_paths_exclude []string `android:"path,arch_variant"`
+
+	// Prop used to indicate this conversion should be 1 module -> multiple targets
+	One_to_many_prop *bool
 }
 
 type customModule struct {
@@ -113,89 +270,112 @@
 	return m
 }
 
+type EmbeddedAttr struct {
+	Embedded_attr *string
+}
+
+type OtherEmbeddedAttr struct {
+	Other_embedded_attr *string
+}
+
 type customBazelModuleAttributes struct {
-	String_prop      string
+	EmbeddedAttr
+	*OtherEmbeddedAttr
+	String_ptr_prop  *string
 	String_list_prop []string
 	Arch_paths       bazel.LabelListAttribute
 }
 
-type customBazelModule struct {
-	android.BazelTargetModuleBase
-	customBazelModuleAttributes
-}
+func (m *customModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	paths := bazel.LabelListAttribute{}
 
-func customBazelModuleFactory() android.Module {
-	module := &customBazelModule{}
-	module.AddProperties(&module.customBazelModuleAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
+	if p := m.props.One_to_many_prop; p != nil && *p {
+		customBp2buildOneToMany(ctx, m)
+		return
+	}
 
-func (m *customBazelModule) Name() string                                          { return m.BaseModuleName() }
-func (m *customBazelModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-func customBp2BuildMutator(ctx android.TopDownMutatorContext) {
-	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build(ctx) {
-			return
-		}
-
-		paths := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, m.props.Arch_paths))
-
-		for arch, props := range m.GetArchProperties(ctx, &customProps{}) {
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
+		for config, props := range configToProps {
 			if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil {
-				paths.SetValueForArch(arch.Name, android.BazelLabelForModuleSrc(ctx, archProps.Arch_paths))
+				paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrcExcludes(ctx, archProps.Arch_paths, archProps.Arch_paths_exclude))
 			}
 		}
-
-		attrs := &customBazelModuleAttributes{
-			String_prop:      m.props.String_prop,
-			String_list_prop: m.props.String_list_prop,
-			Arch_paths:       paths,
-		}
-
-		props := bazel.BazelTargetModuleProperties{
-			Rule_class: "custom",
-		}
-
-		ctx.CreateBazelTargetModule(customBazelModuleFactory, m.Name(), props, attrs)
 	}
+
+	paths.ResolveExcludes()
+
+	attrs := &customBazelModuleAttributes{
+		String_ptr_prop:  m.props.String_ptr_prop,
+		String_list_prop: m.props.String_list_prop,
+		Arch_paths:       paths,
+	}
+	attrs.Embedded_attr = m.props.Embedded_prop
+	if m.props.OtherEmbeddedProps != nil {
+		attrs.OtherEmbeddedAttr = &OtherEmbeddedAttr{Other_embedded_attr: m.props.OtherEmbeddedProps.Other_embedded_prop}
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "custom",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 // A bp2build mutator that uses load statements and creates a 1:M mapping from
 // module to target.
-func customBp2BuildMutatorFromStarlark(ctx android.TopDownMutatorContext) {
-	if m, ok := ctx.Module().(*customModule); ok {
-		if !m.ConvertWithBp2build(ctx) {
-			return
-		}
+func customBp2buildOneToMany(ctx android.TopDownMutatorContext, m *customModule) {
 
-		baseName := m.Name()
-		attrs := &customBazelModuleAttributes{}
+	baseName := m.Name()
+	attrs := &customBazelModuleAttributes{}
 
-		myLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "my_library",
-			Bzl_load_location: "//build/bazel/rules:rules.bzl",
-		}
-		ctx.CreateBazelTargetModule(customBazelModuleFactory, baseName, myLibraryProps, attrs)
-
-		protoLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "proto_library",
-			Bzl_load_location: "//build/bazel/rules:proto.bzl",
-		}
-		ctx.CreateBazelTargetModule(customBazelModuleFactory, baseName+"_proto_library_deps", protoLibraryProps, attrs)
-
-		myProtoLibraryProps := bazel.BazelTargetModuleProperties{
-			Rule_class:        "my_proto_library",
-			Bzl_load_location: "//build/bazel/rules:proto.bzl",
-		}
-		ctx.CreateBazelTargetModule(customBazelModuleFactory, baseName+"_my_proto_library_deps", myProtoLibraryProps, attrs)
+	myLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "my_library",
+		Bzl_load_location: "//build/bazel/rules:rules.bzl",
 	}
+	ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs)
+
+	protoLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "proto_library",
+		Bzl_load_location: "//build/bazel/rules:proto.bzl",
+	}
+	ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs)
+
+	myProtoLibraryProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "my_proto_library",
+		Bzl_load_location: "//build/bazel/rules:proto.bzl",
+	}
+	ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs)
 }
 
 // Helper method for tests to easily access the targets in a dir.
-func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) BazelTargets {
+func generateBazelTargetsForDir(codegenCtx *CodegenContext, dir string) (BazelTargets, []error) {
 	// TODO: Set generateFilegroups to true and/or remove the generateFilegroups argument completely
-	buildFileToTargets, _ := GenerateBazelTargets(codegenCtx, false)
-	return buildFileToTargets[dir]
+	res, err := GenerateBazelTargets(codegenCtx, false)
+	return res.buildFileToTargets[dir], err
+}
+
+func registerCustomModuleForBp2buildConversion(ctx *android.TestContext) {
+	ctx.RegisterModuleType("custom", customModuleFactory)
+	ctx.RegisterForBazelConversion()
+}
+
+func simpleModuleDoNotConvertBp2build(typ, name string) string {
+	return fmt.Sprintf(`
+%s {
+		name: "%s",
+		bazel_module: { bp2build_available: false },
+}`, typ, name)
+}
+
+type attrNameToString map[string]string
+
+func makeBazelTarget(typ, name string, attrs attrNameToString) string {
+	attrStrings := make([]string, 0, len(attrs)+1)
+	attrStrings = append(attrStrings, fmt.Sprintf(`    name = "%s",`, name))
+	for _, k := range android.SortedStringKeys(attrs) {
+		attrStrings = append(attrStrings, fmt.Sprintf("    %s = %s,", k, attrs[k]))
+	}
+	return fmt.Sprintf(`%s(
+%s
+)`, typ, strings.Join(attrStrings, "\n"))
 }
diff --git a/bpf/bpf.go b/bpf/bpf.go
index fa1a84d..14b2d84 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -20,9 +20,9 @@
 	"strings"
 
 	"android/soong/android"
-	_ "android/soong/cc/config"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -41,6 +41,14 @@
 			CommandDeps: []string{"$ccCmd"},
 		},
 		"ccCmd", "cFlags")
+
+	stripRule = pctx.AndroidStaticRule("stripRule",
+		blueprint.RuleParams{
+			Command: `$stripCmd --strip-unneeded --remove-section=.rel.BTF ` +
+				`--remove-section=.rel.BTF.ext --remove-section=.BTF.ext $in -o $out`,
+			CommandDeps: []string{"$stripCmd"},
+		},
+		"stripCmd")
 )
 
 func registerBpfBuildComponents(ctx android.RegistrationContext) {
@@ -54,12 +62,21 @@
 	android.Module
 
 	OutputFiles(tag string) (android.Paths, error)
+
+	// Returns the sub install directory if the bpf module is included by apex.
+	SubDir() string
 }
 
 type BpfProperties struct {
 	Srcs         []string `android:"path"`
 	Cflags       []string
 	Include_dirs []string
+	Sub_dir      string
+	// If set to true, generate BTF debug info for maps & programs
+	Btf    *bool
+	Vendor *bool
+
+	VendorInternal bool `blueprint:"mutated"`
 }
 
 type bpf struct {
@@ -70,6 +87,41 @@
 	objs android.Paths
 }
 
+var _ android.ImageInterface = (*bpf)(nil)
+
+func (bpf *bpf) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+
+func (bpf *bpf) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+	return !proptools.Bool(bpf.properties.Vendor)
+}
+
+func (bpf *bpf) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (bpf *bpf) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (bpf *bpf) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (bpf *bpf) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (bpf *bpf) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+	if proptools.Bool(bpf.properties.Vendor) {
+		return []string{"vendor"}
+	}
+	return nil
+}
+
+func (bpf *bpf) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+	bpf.properties.VendorInternal = variation == "vendor"
+}
+
 func (bpf *bpf) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	cflags := []string{
 		"-nostdlibinc",
@@ -83,9 +135,9 @@
 		// The architecture doesn't matter here, but asm/types.h is included by linux/types.h.
 		"-isystem bionic/libc/kernel/uapi/asm-arm64",
 		"-isystem bionic/libc/kernel/android/uapi",
+		"-I       frameworks/libs/net/common/native/bpf_headers/include/bpf",
 		// TODO(b/149785767): only give access to specific file with AID_* constants
 		"-I       system/core/libcutils/include",
-		"-I       system/bpf/progs/include",
 		"-I " + ctx.ModuleDir(),
 	}
 
@@ -95,10 +147,14 @@
 
 	cflags = append(cflags, bpf.properties.Cflags...)
 
+	if proptools.Bool(bpf.properties.Btf) {
+		cflags = append(cflags, "-g")
+	}
+
 	srcs := android.PathsForModuleSrc(ctx, bpf.properties.Srcs)
 
 	for _, src := range srcs {
-		obj := android.ObjPathWithExt(ctx, "", src, "o")
+		obj := android.ObjPathWithExt(ctx, "unstripped", src, "o")
 
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   ccRule,
@@ -110,7 +166,21 @@
 			},
 		})
 
-		bpf.objs = append(bpf.objs, obj.WithoutRel())
+		if proptools.Bool(bpf.properties.Btf) {
+			objStripped := android.ObjPathWithExt(ctx, "", src, "o")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   stripRule,
+				Input:  obj,
+				Output: objStripped,
+				Args: map[string]string{
+					"stripCmd": "${config.ClangBin}/llvm-strip",
+				},
+			})
+			bpf.objs = append(bpf.objs, objStripped.WithoutRel())
+		} else {
+			bpf.objs = append(bpf.objs, obj.WithoutRel())
+		}
+
 	}
 }
 
@@ -121,6 +191,15 @@
 			fmt.Fprintln(w)
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w)
+			var localModulePath string
+			if bpf.properties.VendorInternal {
+				localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_ETC)/bpf"
+			} else {
+				localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf"
+			}
+			if len(bpf.properties.Sub_dir) > 0 {
+				localModulePath += "/" + bpf.properties.Sub_dir
+			}
 			for _, obj := range bpf.objs {
 				objName := name + "_" + obj.Base()
 				names = append(names, objName)
@@ -130,7 +209,7 @@
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
 				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base())
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
-				fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf")
+				fmt.Fprintln(w, localModulePath)
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 				fmt.Fprintln(w)
 			}
@@ -154,6 +233,10 @@
 	}
 }
 
+func (bpf *bpf) SubDir() string {
+	return bpf.properties.Sub_dir
+}
+
 var _ android.OutputFileProducer = (*bpf)(nil)
 
 func BpfFactory() android.Module {
diff --git a/bpfix/Android.bp b/bpfix/Android.bp
index 345dbd0..a72d9b4 100644
--- a/bpfix/Android.bp
+++ b/bpfix/Android.bp
@@ -52,5 +52,6 @@
     ],
     deps: [
         "blueprint-parser",
+        "blueprint-pathtools",
     ],
 }
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index fae6101..94b28dc 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -19,12 +19,18 @@
 import (
 	"bytes"
 	"errors"
+	"flag"
 	"fmt"
 	"io"
+	"io/ioutil"
+	"os"
 	"path/filepath"
+	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
 )
 
 // Reformat takes a blueprint file as a string and returns a formatted version
@@ -130,7 +136,43 @@
 	},
 	{
 		Name: "removePdkProperty",
-		Fix:  runPatchListMod(removePdkProperty),
+		Fix:  runPatchListMod(removeObsoleteProperty("product_variables.pdk")),
+	},
+	{
+		Name: "removeScudoProperty",
+		Fix:  runPatchListMod(removeObsoleteProperty("sanitize.scudo")),
+	},
+	{
+		Name: "removeAndroidLicenseKinds",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_kinds")),
+	},
+	{
+		Name: "removeAndroidLicenseConditions",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_conditions")),
+	},
+	{
+		Name: "removeAndroidLicenseFiles",
+		Fix:  runPatchListMod(removeIncorrectProperties("android_license_files")),
+	},
+	{
+		Name: "formatFlagProperties",
+		Fix:  runPatchListMod(formatFlagProperties),
+	},
+	{
+		Name: "removeResourcesAndAssetsIfDefault",
+		Fix:  removeResourceAndAssetsIfDefault,
+	},
+}
+
+// for fix that only need to run once
+var fixStepsOnce = []FixStep{
+	{
+		Name: "haveSameLicense",
+		Fix:  haveSameLicense,
+	},
+	{
+		Name: "rewriteLicenseProperties",
+		Fix:  runPatchListMod(rewriteLicenseProperty(nil, "")),
 	},
 }
 
@@ -188,6 +230,16 @@
 		return nil, err
 	}
 
+	// run fix that is expected to run once first
+	configOnce := NewFixRequest()
+	configOnce.steps = append(configOnce.steps, fixStepsOnce...)
+	if len(configOnce.steps) > 0 {
+		err = f.fixTreeOnce(configOnce)
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	maxNumIterations := 20
 	i := 0
 	for {
@@ -319,7 +371,7 @@
 		var defStr string
 		switch mod.Type {
 		case "cts_support_package":
-			mod.Type = "android_test"
+			mod.Type = "android_test_helper_app"
 			defStr = "cts_support_defaults"
 		case "cts_package":
 			mod.Type = "android_test"
@@ -392,11 +444,12 @@
 			continue
 		}
 
-		if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") {
+		if !strings.HasPrefix(mod.Type, "java_") && !strings.HasPrefix(mod.Type, "android_") && mod.Type != "cc_binary" {
 			continue
 		}
 
 		hasInstrumentationFor := hasNonEmptyLiteralStringProperty(mod, "instrumentation_for")
+		hasTestSuites := hasNonEmptyLiteralListProperty(mod, "test_suites")
 		tags, _ := getLiteralListPropertyValue(mod, "tags")
 
 		var hasTestsTag bool
@@ -406,7 +459,7 @@
 			}
 		}
 
-		isTest := hasInstrumentationFor || hasTestsTag
+		isTest := hasInstrumentationFor || hasTestsTag || hasTestSuites
 
 		if isTest {
 			switch mod.Type {
@@ -418,6 +471,8 @@
 				mod.Type = "java_test"
 			case "java_library_host":
 				mod.Type = "java_test_host"
+			case "cc_binary":
+				mod.Type = "cc_test"
 			}
 		}
 	}
@@ -554,7 +609,11 @@
 }
 
 var localModuleUpdate = map[string][]etcPrebuiltModuleUpdate{
-	"HOST_OUT":    {{prefix: "/etc", modType: "prebuilt_etc_host"}, {prefix: "/usr/share", modType: "prebuilt_usr_share_host"}},
+	"HOST_OUT": {
+		{prefix: "/etc", modType: "prebuilt_etc_host"},
+		{prefix: "/usr/share", modType: "prebuilt_usr_share_host"},
+		{prefix: "", modType: "prebuilt_root_host"},
+	},
 	"PRODUCT_OUT": {{prefix: "/system/etc"}, {prefix: "/vendor/etc", flags: []string{"proprietary"}}},
 	"TARGET_OUT": {{prefix: "/usr/share", modType: "prebuilt_usr_share"}, {prefix: "/fonts", modType: "prebuilt_font"},
 		{prefix: "/etc/firmware", modType: "prebuilt_firmware"}, {prefix: "/vendor/firmware", modType: "prebuilt_firmware", flags: []string{"proprietary"}},
@@ -585,7 +644,7 @@
 		// 'srcs' --> 'src' conversion
 		convertToSingleSource(mod, "src")
 
-		renameProperty(mod, "sub_dir", "relative_install_dir")
+		renameProperty(mod, "sub_dir", "relative_install_path")
 
 		// The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute
 		// 'local_module_path'. Analyze its contents and create the correct sub_dir:,
@@ -622,12 +681,20 @@
 func rewriteAndroidTest(f *Fixer) error {
 	for _, def := range f.tree.Defs {
 		mod, ok := def.(*parser.Module)
-		if !(ok && mod.Type == "android_test") {
+		if !ok {
+			// The definition is not a module.
+			continue
+		}
+		if mod.Type != "android_test" && mod.Type != "android_test_helper_app" {
+			// The module is not an android_test or android_test_helper_app.
 			continue
 		}
 		// The rewriter converts LOCAL_MODULE_PATH attribute into a struct attribute
 		// 'local_module_path'. For the android_test module, it should be  $(TARGET_OUT_DATA_APPS),
 		// that is, `local_module_path: { var: "TARGET_OUT_DATA_APPS"}`
+		// 1. if the `key: val` pair matches, (key is `local_module_path`,
+		//    and val is `{ var: "TARGET_OUT_DATA_APPS"}`), this property is removed;
+		// 2. o/w, an error msg is thrown.
 		const local_module_path = "local_module_path"
 		if prop_local_module_path, ok := mod.GetProperty(local_module_path); ok {
 			removeProperty(mod, local_module_path)
@@ -637,7 +704,7 @@
 				continue
 			}
 			return indicateAttributeError(mod, "filename",
-				"Only LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) is allowed for the android_test")
+				"Only LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) is allowed for the %s", mod.Type)
 		}
 	}
 	return nil
@@ -818,6 +885,24 @@
 	return nil
 }
 
+func removeResourceAndAssetsIfDefault(f *Fixer) error {
+	for _, def := range f.tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+		resourceDirList, resourceDirFound := getLiteralListPropertyValue(mod, "resource_dirs")
+		if resourceDirFound && len(resourceDirList) == 1 && resourceDirList[0] == "res" {
+			removeProperty(mod, "resource_dirs")
+		}
+		assetDirList, assetDirFound := getLiteralListPropertyValue(mod, "asset_dirs")
+		if assetDirFound && len(assetDirList) == 1 && assetDirList[0] == "assets" {
+			removeProperty(mod, "asset_dirs")
+		}
+	}
+	return nil
+}
+
 // Converts the default source list property, 'srcs', to a single source property with a given name.
 // "LOCAL_MODULE" reference is also resolved during the conversion process.
 func convertToSingleSource(mod *parser.Module, srcPropertyName string) {
@@ -847,7 +932,9 @@
 	}
 }
 
-func runPatchListMod(modFunc func(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error) func(*Fixer) error {
+type patchListModFunction func(*parser.Module, []byte, *parser.PatchList) error
+
+func runPatchListMod(modFunc patchListModFunction) func(*Fixer) error {
 	return func(f *Fixer) error {
 		// Make sure all the offsets are accurate
 		buf, err := f.reparse()
@@ -1017,23 +1104,63 @@
 	return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, replaceStr)
 }
 
-func removePdkProperty(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
-	prop, ok := mod.GetProperty("product_variables")
-	if !ok {
-		return nil
+type propertyProvider interface {
+	GetProperty(string) (*parser.Property, bool)
+	RemoveProperty(string) bool
+}
+
+func removeNestedProperty(mod *parser.Module, patchList *parser.PatchList, propName string) error {
+	propNames := strings.Split(propName, ".")
+
+	var propProvider, toRemoveFrom propertyProvider
+	propProvider = mod
+
+	var propToRemove *parser.Property
+	for i, name := range propNames {
+		p, ok := propProvider.GetProperty(name)
+		if !ok {
+			return nil
+		}
+		// if this is the inner most element, it's time to delete
+		if i == len(propNames)-1 {
+			if propToRemove == nil {
+				// if we cannot remove the properties that the current property is nested in,
+				// remove only the current property
+				propToRemove = p
+				toRemoveFrom = propProvider
+			}
+
+			// remove the property from the list, in case we remove other properties in this list
+			toRemoveFrom.RemoveProperty(propToRemove.Name)
+			// only removing the property would leave blank line(s), remove with a patch
+			if err := patchList.Add(propToRemove.Pos().Offset, propToRemove.End().Offset+2, ""); err != nil {
+				return err
+			}
+		} else {
+			propMap, ok := p.Value.(*parser.Map)
+			if !ok {
+				return nil
+			}
+			if len(propMap.Properties) > 1 {
+				// if there are other properties in this struct, we need to keep this struct
+				toRemoveFrom = nil
+				propToRemove = nil
+			} else if propToRemove == nil {
+				// otherwise, we can remove the empty struct entirely
+				toRemoveFrom = propProvider
+				propToRemove = p
+			}
+			propProvider = propMap
+		}
 	}
-	propMap, ok := prop.Value.(*parser.Map)
-	if !ok {
-		return nil
+
+	return nil
+}
+
+func removeObsoleteProperty(propName string) patchListModFunction {
+	return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
+		return removeNestedProperty(mod, patchList, propName)
 	}
-	pdkProp, ok := propMap.GetProperty("pdk")
-	if !ok {
-		return nil
-	}
-	if len(propMap.Properties) > 1 {
-		return patchlist.Add(pdkProp.Pos().Offset, pdkProp.End().Offset+2, "")
-	}
-	return patchlist.Add(prop.Pos().Offset, prop.End().Offset+2, "")
 }
 
 func mergeMatchingModuleProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
@@ -1281,3 +1408,408 @@
 	}
 	return false
 }
+
+func formatFlagProperty(mod *parser.Module, field string, buf []byte, patchlist *parser.PatchList) error {
+	// the comment or empty lines in the value of the field are skipped
+	listValue, ok := getLiteralListProperty(mod, field)
+	if !ok {
+		// if do not find
+		return nil
+	}
+	for i := 0; i < len(listValue.Values); i++ {
+		curValue, ok := listValue.Values[i].(*parser.String)
+		if !ok {
+			return fmt.Errorf("Expecting string for %s.%s fields", mod.Type, field)
+		}
+		if !strings.HasPrefix(curValue.Value, "-") {
+			return fmt.Errorf("Expecting the string `%s` starting with '-'", curValue.Value)
+		}
+		if i+1 < len(listValue.Values) {
+			nextValue, ok := listValue.Values[i+1].(*parser.String)
+			if !ok {
+				return fmt.Errorf("Expecting string for %s.%s fields", mod.Type, field)
+			}
+			if !strings.HasPrefix(nextValue.Value, "-") {
+				// delete the line
+				err := patchlist.Add(curValue.Pos().Offset, curValue.End().Offset+2, "")
+				if err != nil {
+					return err
+				}
+				// replace the line
+				value := "\"" + curValue.Value + " " + nextValue.Value + "\","
+				err = patchlist.Add(nextValue.Pos().Offset, nextValue.End().Offset+1, value)
+				if err != nil {
+					return err
+				}
+				// combined two lines to one
+				i++
+			}
+		}
+	}
+	return nil
+}
+
+func formatFlagProperties(mod *parser.Module, buf []byte, patchlist *parser.PatchList) error {
+	relevantFields := []string{
+		// cc flags
+		"asflags",
+		"cflags",
+		"clang_asflags",
+		"clang_cflags",
+		"conlyflags",
+		"cppflags",
+		"ldflags",
+		"tidy_flags",
+		// java flags
+		"aaptflags",
+		"dxflags",
+		"javacflags",
+		"kotlincflags",
+	}
+	for _, field := range relevantFields {
+		err := formatFlagProperty(mod, field, buf, patchlist)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func rewriteLicenseProperty(fs pathtools.FileSystem, relativePath string) patchListModFunction {
+	return func(mod *parser.Module, buf []byte, patchList *parser.PatchList) error {
+		return rewriteLicenseProperties(mod, patchList, fs, relativePath)
+	}
+}
+
+// rewrite the "android_license_kinds" and "android_license_files" properties to a package module
+// (and a license module when needed).
+func rewriteLicenseProperties(mod *parser.Module, patchList *parser.PatchList, fs pathtools.FileSystem,
+	relativePath string) error {
+	// if a package module has been added, no more action is needed.
+	for _, patch := range *patchList {
+		if strings.Contains(patch.Replacement, "package {") {
+			return nil
+		}
+	}
+
+	// initial the fs
+	if fs == nil {
+		fs = pathtools.NewOsFs(os.Getenv("ANDROID_BUILD_TOP"))
+	}
+
+	// initial the relativePath
+	if len(relativePath) == 0 {
+		relativePath = getModuleRelativePath()
+	}
+	// validate the relativePath
+	ok := hasFile(relativePath+"/Android.mk", fs)
+	// some modules in the existing test cases in the androidmk_test.go do not have a valid path
+	if !ok && len(relativePath) > 0 {
+		return fmt.Errorf("Cannot find an Android.mk file at path %q", relativePath)
+	}
+
+	licenseKindsPropertyName := "android_license_kinds"
+	licenseFilesPropertyName := "android_license_files"
+
+	androidBpFileErr := "// Error: No Android.bp file is found at path\n" +
+		"// %s\n" +
+		"// Please add one there with the needed license module first.\n" +
+		"// Then reset the default_applicable_licenses property below with the license module name.\n"
+	licenseModuleErr := "// Error: Cannot get the name of the license module in the\n" +
+		"// %s file.\n" +
+		"// If no such license module exists, please add one there first.\n" +
+		"// Then reset the default_applicable_licenses property below with the license module name.\n"
+
+	defaultApplicableLicense := "Android-Apache-2.0"
+	var licenseModuleName, licensePatch string
+	var hasFileInParentDir bool
+
+	// when LOCAL_NOTICE_FILE is not empty
+	if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) {
+		hasFileInParentDir = hasValueStartWithTwoDotsLiteralList(mod, licenseFilesPropertyName)
+		// if have LOCAL_NOTICE_FILE outside the current directory, need to find and refer to the license
+		// module in the LOCAL_NOTICE_FILE location directly and no new license module needs to be created
+		if hasFileInParentDir {
+			bpPath, ok := getPathFromProperty(mod, licenseFilesPropertyName, fs, relativePath)
+			if !ok {
+				bpDir, err := getDirFromProperty(mod, licenseFilesPropertyName, fs, relativePath)
+				if err != nil {
+					return err
+				}
+				licensePatch += fmt.Sprintf(androidBpFileErr, bpDir)
+				defaultApplicableLicense = ""
+			} else {
+				licenseModuleName, _ = getModuleName(bpPath, "license", fs)
+				if len(licenseModuleName) == 0 {
+					licensePatch += fmt.Sprintf(licenseModuleErr, bpPath)
+				}
+				defaultApplicableLicense = licenseModuleName
+			}
+		} else {
+			// if have LOCAL_NOTICE_FILE in the current directory, need to create a new license module
+			if len(relativePath) == 0 {
+				return fmt.Errorf("Cannot obtain the relative path of the Android.mk file")
+			}
+			licenseModuleName = strings.Replace(relativePath, "/", "_", -1) + "_license"
+			defaultApplicableLicense = licenseModuleName
+		}
+	}
+
+	//add the package module
+	if hasNonEmptyLiteralListProperty(mod, licenseKindsPropertyName) {
+		licensePatch += "package {\n" +
+			"    // See: http://go/android-license-faq\n" +
+			"    default_applicable_licenses: [\n" +
+			"         \"" + defaultApplicableLicense + "\",\n" +
+			"    ],\n" +
+			"}\n" +
+			"\n"
+	}
+
+	// append the license module when necessary
+	// when LOCAL_NOTICE_FILE is not empty and in the current directory, create a new license module
+	// otherwise, use the above default license directly
+	if hasNonEmptyLiteralListProperty(mod, licenseFilesPropertyName) && !hasFileInParentDir {
+		licenseKinds, err := mergeLiteralListPropertyValue(mod, licenseKindsPropertyName)
+		if err != nil {
+			return err
+		}
+		licenseFiles, err := mergeLiteralListPropertyValue(mod, licenseFilesPropertyName)
+		if err != nil {
+			return err
+		}
+		licensePatch += "license {\n" +
+			"    name: \"" + licenseModuleName + "\",\n" +
+			"    visibility: [\":__subpackages__\"],\n" +
+			"    license_kinds: [\n" +
+			licenseKinds +
+			"    ],\n" +
+			"    license_text: [\n" +
+			licenseFiles +
+			"    ],\n" +
+			"}\n" +
+			"\n"
+	}
+
+	// add to the patchList
+	pos := mod.Pos().Offset
+	err := patchList.Add(pos, pos, licensePatch)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// merge the string vaules in a list property of a module into one string with expected format
+func mergeLiteralListPropertyValue(mod *parser.Module, property string) (s string, err error) {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if !ok {
+		// if do not find
+		return "", fmt.Errorf("Cannot retrieve the %s.%s field", mod.Type, property)
+	}
+	for i := 0; i < len(listValue); i++ {
+		s += "         \"" + listValue[i] + "\",\n"
+	}
+	return s, nil
+}
+
+// check whether a string list property has any value starting with `../`
+func hasValueStartWithTwoDotsLiteralList(mod *parser.Module, property string) bool {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if ok {
+		for i := 0; i < len(listValue); i++ {
+			if strings.HasPrefix(listValue[i], "../") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// get the relative path from ANDROID_BUILD_TOP to the Android.mk file to be converted
+func getModuleRelativePath() string {
+	// get the absolute path of the top of the tree
+	rootPath := os.Getenv("ANDROID_BUILD_TOP")
+	// get the absolute path of the `Android.mk` file to be converted
+	absPath := getModuleAbsolutePath()
+	// get the relative path of the `Android.mk` file to top of the tree
+	relModulePath, err := filepath.Rel(rootPath, absPath)
+	if err != nil {
+		return ""
+	}
+	return relModulePath
+}
+
+// get the absolute path of the Android.mk file to be converted
+func getModuleAbsolutePath() string {
+	// get the absolute path at where the `androidmk` commend is executed
+	curAbsPath, err := filepath.Abs(".")
+	if err != nil {
+		return ""
+	}
+	// the argument for the `androidmk` command could be
+	// 1. "./a/b/c/Android.mk"; 2. "a/b/c/Android.mk"; 3. "Android.mk"
+	argPath := flag.Arg(0)
+	if strings.HasPrefix(argPath, "./") {
+		argPath = strings.TrimPrefix(argPath, ".")
+	}
+	argPath = strings.TrimSuffix(argPath, "Android.mk")
+	if strings.HasSuffix(argPath, "/") {
+		argPath = strings.TrimSuffix(argPath, "/")
+	}
+	if len(argPath) > 0 && !strings.HasPrefix(argPath, "/") {
+		argPath = "/" + argPath
+	}
+	// get the absolute path of the `Android.mk` file to be converted
+	absPath := curAbsPath + argPath
+	return absPath
+}
+
+// check whether a file exists in a filesystem
+func hasFile(path string, fs pathtools.FileSystem) bool {
+	ok, _, _ := fs.Exists(path)
+	return ok
+}
+
+// get the directory where an `Android.bp` file and the property files are expected to locate
+func getDirFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, error) {
+	listValue, ok := getLiteralListPropertyValue(mod, property)
+	if !ok {
+		// if do not find
+		return "", fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property)
+	}
+	if len(listValue) == 0 {
+		// if empty
+		return "", fmt.Errorf("Cannot find the value of the %s.%s property", mod.Type, property)
+	}
+	if relativePath == "" {
+		relativePath = "."
+	}
+	_, isDir, _ := fs.Exists(relativePath)
+	if !isDir {
+		return "", fmt.Errorf("Cannot find the path %q", relativePath)
+	}
+	path := relativePath
+	for {
+		if !strings.HasPrefix(listValue[0], "../") {
+			break
+		}
+		path = filepath.Dir(path)
+		listValue[0] = strings.TrimPrefix(listValue[0], "../")
+	}
+	_, isDir, _ = fs.Exists(path)
+	if !isDir {
+		return "", fmt.Errorf("Cannot find the path %q", path)
+	}
+	return path, nil
+}
+
+// get the path of the `Android.bp` file at the expected location where the property files locate
+func getPathFromProperty(mod *parser.Module, property string, fs pathtools.FileSystem, relativePath string) (string, bool) {
+	dir, err := getDirFromProperty(mod, property, fs, relativePath)
+	if err != nil {
+		return "", false
+	}
+	ok := hasFile(dir+"/Android.bp", fs)
+	if !ok {
+		return "", false
+	}
+	return dir + "/Android.bp", true
+}
+
+// parse an Android.bp file to get the name of the first module with type of moduleType
+func getModuleName(path string, moduleType string, fs pathtools.FileSystem) (string, error) {
+	tree, err := parserPath(path, fs)
+	if err != nil {
+		return "", err
+	}
+	for _, def := range tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok || mod.Type != moduleType {
+			continue
+		}
+		prop, ok := mod.GetProperty("name")
+		if !ok {
+			return "", fmt.Errorf("Cannot get the %s."+"name property", mod.Type)
+		}
+		propVal, ok := prop.Value.(*parser.String)
+		if ok {
+			return propVal.Value, nil
+		}
+	}
+	return "", fmt.Errorf("Cannot find the value of the %s."+"name property", moduleType)
+}
+
+// parse an Android.bp file with the specific path
+func parserPath(path string, fs pathtools.FileSystem) (tree *parser.File, err error) {
+	f, err := fs.Open(path)
+	if err != nil {
+		return tree, err
+	}
+	defer f.Close()
+	fileContent, _ := ioutil.ReadAll(f)
+	tree, err = parse(path, bytes.NewBufferString(string(fileContent)))
+	if err != nil {
+		return tree, err
+	}
+	return tree, nil
+}
+
+// remove the incorrect property that Soong does not support
+func removeIncorrectProperties(propName string) patchListModFunction {
+	return removeObsoleteProperty(propName)
+}
+
+// the modules on the same Android.mk file are expected to have the same license
+func haveSameLicense(f *Fixer) error {
+	androidLicenseProperties := []string{
+		"android_license_kinds",
+		"android_license_conditions",
+		"android_license_files",
+	}
+
+	var prevModuleName string
+	var prevLicenseKindsVals, prevLicenseConditionsVals, prevLicenseFilesVals []string
+	prevLicenseVals := [][]string{
+		prevLicenseKindsVals,
+		prevLicenseConditionsVals,
+		prevLicenseFilesVals,
+	}
+
+	for _, def := range f.tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+		for idx, property := range androidLicenseProperties {
+			curModuleName, ok := getLiteralStringPropertyValue(mod, "name")
+			// some modules in the existing test cases in the androidmk_test.go do not have name property
+			hasNameProperty := hasProperty(mod, "name")
+			if hasNameProperty && (!ok || len(curModuleName) == 0) {
+				return fmt.Errorf("Cannot retrieve the name property of a module of %s type.", mod.Type)
+			}
+			curVals, ok := getLiteralListPropertyValue(mod, property)
+			// some modules in the existing test cases in the androidmk_test.go do not have license-related property
+			hasLicenseProperty := hasProperty(mod, property)
+			if hasLicenseProperty && (!ok || len(curVals) == 0) {
+				// if do not find the property, or no value is found for the property
+				return fmt.Errorf("Cannot retrieve the %s.%s property", mod.Type, property)
+			}
+			if len(prevLicenseVals[idx]) > 0 {
+				if !reflect.DeepEqual(prevLicenseVals[idx], curVals) {
+					return fmt.Errorf("Modules %s and %s are expected to have the same %s property.",
+						prevModuleName, curModuleName, property)
+				}
+			}
+			sort.Strings(curVals)
+			prevLicenseVals[idx] = curVals
+			prevModuleName = curModuleName
+		}
+	}
+	return nil
+}
+
+func hasProperty(mod *parser.Module, propName string) bool {
+	_, ok := mod.GetProperty(propName)
+	return ok
+}
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index 61dfe1a..672e852 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -19,12 +19,12 @@
 import (
 	"bytes"
 	"fmt"
+	"reflect"
 	"strings"
 	"testing"
 
-	"reflect"
-
 	"github.com/google/blueprint/parser"
+	"github.com/google/blueprint/pathtools"
 )
 
 // TODO(jeffrygaston) remove this when position is removed from ParseNode (in b/38325146) and we can directly do reflect.DeepEqual
@@ -125,34 +125,103 @@
 	implFilterListTest(t, []string{}, []string{}, []string{})
 }
 
-func runPass(t *testing.T, in, out string, innerTest func(*Fixer) error) {
-	expected, err := Reformat(out)
+func checkError(t *testing.T, in, expectedErr string, innerTest func(*Fixer) error) {
+	expected := preProcessOutErr(expectedErr)
+	runTestOnce(t, in, expected, innerTest)
+}
+
+func runTestOnce(t *testing.T, in, expected string, innerTest func(*Fixer) error) {
+	fixer, err := preProcessIn(in)
 	if err != nil {
 		t.Fatal(err)
 	}
 
+	out, err := runFixerOnce(fixer, innerTest)
+	if err != nil {
+		out = err.Error()
+	}
+
+	compareResult := compareOutExpected(in, out, expected)
+	if len(compareResult) > 0 {
+		t.Errorf(compareResult)
+	}
+}
+
+func preProcessOutErr(expectedErr string) string {
+	expected := strings.TrimSpace(expectedErr)
+	return expected
+}
+
+func preProcessOut(out string) (expected string, err error) {
+	expected, err = Reformat(out)
+	if err != nil {
+		return expected, err
+	}
+	return expected, nil
+}
+
+func preProcessIn(in string) (fixer *Fixer, err error) {
 	in, err = Reformat(in)
 	if err != nil {
-		t.Fatal(err)
+		return fixer, err
 	}
 
 	tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil))
 	if errs != nil {
-		t.Fatal(errs)
+		return fixer, err
 	}
 
-	fixer := NewFixer(tree)
+	fixer = NewFixer(tree)
+
+	return fixer, nil
+}
+
+func runFixerOnce(fixer *Fixer, innerTest func(*Fixer) error) (string, error) {
+	err := innerTest(fixer)
+	if err != nil {
+		return "", err
+	}
+
+	out, err := parser.Print(fixer.tree)
+	if err != nil {
+		return "", err
+	}
+	return string(out), nil
+}
+
+func compareOutExpected(in, out, expected string) string {
+	if out != expected {
+		return fmt.Sprintf("output didn't match:\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
+			in, expected, out)
+	}
+	return ""
+}
+
+func runPassOnce(t *testing.T, in, out string, innerTest func(*Fixer) error) {
+	expected, err := preProcessOut(out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	runTestOnce(t, in, expected, innerTest)
+}
+
+func runPass(t *testing.T, in, out string, innerTest func(*Fixer) error) {
+	expected, err := preProcessOut(out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fixer, err := preProcessIn(in)
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	got := ""
 	prev := "foo"
 	passes := 0
 	for got != prev && passes < 10 {
-		err := innerTest(fixer)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		out, err := parser.Print(fixer.tree)
+		out, err = runFixerOnce(fixer, innerTest)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -162,9 +231,9 @@
 		passes++
 	}
 
-	if got != expected {
-		t.Errorf("output didn't match:\ninput:\n%s\n\nexpected:\n%s\ngot:\n%s\n",
-			in, expected, got)
+	compareResult := compareOutExpected(in, out, expected)
+	if len(compareResult) > 0 {
+		t.Errorf(compareResult)
 	}
 }
 
@@ -636,7 +705,7 @@
 				}
 			`,
 			out: `
-				android_test {
+				android_test_helper_app {
 					name: "foo",
 					defaults: ["cts_support_defaults"],
 				}
@@ -754,7 +823,47 @@
 			out: `prebuilt_etc {
 			name: "foo",
 			src: "bar",
-			relative_install_dir: "baz",
+			relative_install_path: "baz",
+		}
+		`,
+		},
+		{
+			name: "prebuilt_etc sub_dir",
+			in: `
+			prebuilt_etc_host {
+			name: "foo",
+			src: "bar",
+			local_module_path: {
+				var: "HOST_OUT",
+				fixed: "/etc/baz",
+			},
+		}
+		`,
+			out: `prebuilt_etc_host {
+			name: "foo",
+			src: "bar",
+			relative_install_path: "baz",
+
+		}
+		`,
+		},
+		{
+			name: "prebuilt_etc sub_dir",
+			in: `
+			prebuilt_etc_host {
+			name: "foo",
+			src: "bar",
+			local_module_path: {
+				var: "HOST_OUT",
+				fixed: "/baz/sub",
+			},
+		}
+		`,
+			out: `prebuilt_root_host {
+			name: "foo",
+			src: "bar",
+			relative_install_path: "baz/sub",
+
 		}
 		`,
 		},
@@ -999,7 +1108,171 @@
 	}
 }
 
-func TestRemovePdkProperty(t *testing.T) {
+func TestRemoveNestedProperty(t *testing.T) {
+	tests := []struct {
+		name         string
+		in           string
+		out          string
+		propertyName string
+	}{
+		{
+			name: "remove no nesting",
+			in: `
+cc_library {
+	name: "foo",
+	foo: true,
+}`,
+			out: `
+cc_library {
+	name: "foo",
+}
+`,
+			propertyName: "foo",
+		},
+		{
+			name: "remove one nest",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: true,
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+}
+`,
+			propertyName: "foo.bar",
+		},
+		{
+			name: "remove one nest, multiple props",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: true,
+		baz: false,
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+	foo: {
+		baz: false,
+	},
+}
+`,
+			propertyName: "foo.bar",
+		},
+		{
+			name: "remove multiple nest",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			baz: {
+				a: true,
+			}
+		},
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+}
+`,
+			propertyName: "foo.bar.baz.a",
+		},
+		{
+			name: "remove multiple nest, outer non-empty",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			baz: {
+				a: true,
+			}
+		},
+		other: true,
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+	foo: {
+		other: true,
+	},
+}
+`,
+			propertyName: "foo.bar.baz.a",
+		},
+		{
+			name: "remove multiple nest, inner non-empty",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			baz: {
+				a: true,
+			},
+			other: true,
+		},
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			other: true,
+		},
+	},
+}
+`,
+			propertyName: "foo.bar.baz.a",
+		},
+		{
+			name: "remove multiple nest, inner-most non-empty",
+			in: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			baz: {
+				a: true,
+				other: true,
+			},
+		},
+	},
+}`,
+			out: `
+cc_library {
+	name: "foo",
+	foo: {
+		bar: {
+			baz: {
+				other: true,
+			},
+		},
+	},
+}
+`,
+			propertyName: "foo.bar.baz.a",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPass(t, test.in, test.out, runPatchListMod(removeObsoleteProperty(test.propertyName)))
+		})
+	}
+}
+
+func TestRemoveObsoleteProperties(t *testing.T) {
 	tests := []struct {
 		name string
 		in   string
@@ -1052,7 +1325,7 @@
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			runPass(t, test.in, test.out, runPatchListMod(removePdkProperty))
+			runPass(t, test.in, test.out, runPatchListMod(removeObsoleteProperty("product_variables.pdk")))
 		})
 	}
 }
@@ -1124,3 +1397,821 @@
 		})
 	}
 }
+
+func TestRewriteTestModuleTypes(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "cc_binary with test_suites",
+			in: `
+				cc_binary {
+					name: "foo",
+					srcs: ["srcs"],
+					test_suites: ["test_suite1"],
+				}
+			`,
+			out: `
+				cc_test {
+					name: "foo",
+					srcs: ["srcs"],
+					test_suites: ["test_suite1"],
+				}
+			`,
+		},
+		{
+			name: "cc_binary without test_suites",
+			in: `
+				cc_binary {
+					name: "foo",
+					srcs: ["srcs"],
+				}
+			`,
+			out: `
+				cc_binary {
+					name: "foo",
+					srcs: ["srcs"],
+				}
+			`,
+		},
+		{
+			name: "android_app with android_test",
+			in: `
+				android_app {
+					name: "foo",
+					srcs: ["srcs"],
+					test_suites: ["test_suite1"],
+				}
+			`,
+			out: `
+				android_test {
+					name: "foo",
+					srcs: ["srcs"],
+					test_suites: ["test_suite1"],
+				}
+			`,
+		},
+		{
+			name: "android_app without test_suites",
+			in: `
+				android_app {
+					name: "foo",
+					srcs: ["srcs"],
+				}
+			`,
+			out: `
+				android_app {
+					name: "foo",
+					srcs: ["srcs"],
+				}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPass(t, test.in, test.out, func(fixer *Fixer) error {
+				return rewriteTestModuleTypes(fixer)
+			})
+		})
+	}
+}
+
+func TestFormatFlagProperty(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "group options and values for apptflags, dxflags, javacflags, and kotlincflags",
+			in: `
+				android_test {
+					name: "foo",
+					aaptflags: [
+						// comment1_1
+						"--flag1",
+						// comment1_2
+						"1",
+						// comment2_1
+						// comment2_2
+						"--flag2",
+						// comment3_1
+						// comment3_2
+						// comment3_3
+						"--flag3",
+						// comment3_4
+						// comment3_5
+						// comment3_6
+						"3",
+						// other comment1_1
+						// other comment1_2
+					],
+					dxflags: [
+						"--flag1",
+						// comment1_1
+						"1",
+						// comment2_1
+						"--flag2",
+						// comment3_1
+						"--flag3",
+						// comment3_2
+						"3",
+					],
+					javacflags: [
+						"--flag1",
+
+						"1",
+						"--flag2",
+						"--flag3",
+						"3",
+					],
+					kotlincflags: [
+
+						"--flag1",
+						"1",
+
+						"--flag2",
+						"--flag3",
+						"3",
+
+					],
+				}
+			`,
+			out: `
+				android_test {
+					name: "foo",
+					aaptflags: [
+						// comment1_1
+						// comment1_2
+						"--flag1 1",
+						// comment2_1
+						// comment2_2
+						"--flag2",
+						// comment3_1
+						// comment3_2
+						// comment3_3
+						// comment3_4
+						// comment3_5
+						// comment3_6
+						"--flag3 3",
+						// other comment1_1
+						// other comment1_2
+					],
+					dxflags: [
+						// comment1_1
+						"--flag1 1",
+						// comment2_1
+						"--flag2",
+						// comment3_1
+						// comment3_2
+						"--flag3 3",
+					],
+					javacflags: [
+
+						"--flag1 1",
+						"--flag2",
+						"--flag3 3",
+					],
+					kotlincflags: [
+
+						"--flag1 1",
+
+						"--flag2",
+						"--flag3 3",
+
+					],
+				}
+			`,
+		},
+		{
+			name: "group options and values for asflags, cflags, clang_asflags, clang_cflags, conlyflags, cppflags, ldflags, and tidy_flags",
+			in: `
+				cc_test {
+					name: "foo",
+					asflags: [
+						// comment1_1
+						"--flag1",
+						"1",
+						// comment2_1
+						// comment2_2
+						"--flag2",
+						// comment2_3
+						"2",
+						// comment3_1
+						// comment3_2
+						"--flag3",
+						// comment3_3
+						// comment3_4
+						// comment3_4
+						"3",
+						// comment4_1
+						// comment4_2
+						// comment4_3
+						"--flag4",
+					],
+					cflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					clang_asflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					clang_cflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					conlyflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					cppflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					ldflags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+					tidy_flags: [
+						"--flag1",
+						"1",
+						"--flag2",
+						"2",
+						"--flag3",
+						"3",
+						"--flag4",
+					],
+				}
+			`,
+			out: `
+				cc_test {
+					name: "foo",
+					asflags: [
+						// comment1_1
+						"--flag1 1",
+						// comment2_1
+						// comment2_2
+						// comment2_3
+						"--flag2 2",
+						// comment3_1
+						// comment3_2
+						// comment3_3
+						// comment3_4
+						// comment3_4
+						"--flag3 3",
+						// comment4_1
+						// comment4_2
+						// comment4_3
+						"--flag4",
+					],
+					cflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					clang_asflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					clang_cflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					conlyflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					cppflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					ldflags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+					tidy_flags: [
+						"--flag1 1",
+						"--flag2 2",
+						"--flag3 3",
+						"--flag4",
+					],
+				}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPass(t, test.in, test.out, runPatchListMod(formatFlagProperties))
+		})
+	}
+}
+
+func TestRewriteLicenseProperty(t *testing.T) {
+	mockFs := pathtools.MockFs(map[string][]byte{
+		"a/b/c/d/Android.mk": []byte("this is not important."),
+		"a/b/LicenseFile1":   []byte("LicenseFile1"),
+		"a/b/LicenseFile2":   []byte("LicenseFile2"),
+		"a/b/Android.bp":     []byte("license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+	})
+	relativePath := "a/b/c/d"
+	relativePathErr := "a/b/c"
+	tests := []struct {
+		name string
+		in   string
+		fs   pathtools.FileSystem
+		path string
+		out  string
+	}{
+		{
+			name: "license rewriting with one module",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+			out: `
+				package {
+					// See: http://go/android-license-faq
+					default_applicable_licenses: [
+						"Android-Apache-2.0",
+					],
+				}
+
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+		},
+		{
+			name: "license rewriting with two modules",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+			out: `
+				package {
+					// See: http://go/android-license-faq
+					default_applicable_licenses: [
+						"Android-Apache-2.0",
+					],
+				}
+
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+		},
+		{
+			name: "license rewriting with license files in the current directory",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["LicenseFile1", "LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePath,
+			out: `
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"a_b_c_d_license",
+				],
+			}
+
+			license {
+				name: "a_b_c_d_license",
+				visibility: [":__subpackages__"],
+				license_kinds: [
+					"license_kind",
+				],
+				license_text: [
+					"LicenseFile1",
+					"LicenseFile2",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"LicenseFile1",
+					"LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with license files outside the current directory",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePath,
+			out: `
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"reuse_a_b_license",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with no Android.bp file in the expected location",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs: pathtools.MockFs(map[string][]byte{
+				"a/b/c/d/Android.mk": []byte("this is not important."),
+				"a/b/LicenseFile1":   []byte("LicenseFile1"),
+				"a/b/LicenseFile2":   []byte("LicenseFile2"),
+				"a/Android.bp":       []byte("license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+			}),
+			path: relativePath,
+			out: `
+			// Error: No Android.bp file is found at path
+			// a/b
+			// Please add one there with the needed license module first.
+			// Then reset the default_applicable_licenses property below with the license module name.
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+		{
+			name: "license rewriting with an Android.bp file without a license module",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs: pathtools.MockFs(map[string][]byte{
+				"a/b/c/d/Android.mk": []byte("this is not important."),
+				"a/b/LicenseFile1":   []byte("LicenseFile1"),
+				"a/b/LicenseFile2":   []byte("LicenseFile2"),
+				"a/b/Android.bp":     []byte("non_license {\n\tname: \"reuse_a_b_license\",\n}\n"),
+			}),
+			path: relativePath,
+			out: `
+			// Error: Cannot get the name of the license module in the
+			// a/b/Android.bp file.
+			// If no such license module exists, please add one there first.
+			// Then reset the default_applicable_licenses property below with the license module name.
+			package {
+				// See: http://go/android-license-faq
+				default_applicable_licenses: [
+					"",
+				],
+			}
+
+			android_test {
+				name: "foo",
+				android_license_kinds: ["license_kind"],
+				android_license_conditions: ["license_notice"],
+				android_license_files: [
+					"../../LicenseFile1",
+					"../../LicenseFile2",
+				],
+			}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPassOnce(t, test.in, test.out, runPatchListMod(rewriteLicenseProperty(test.fs, test.path)))
+		})
+	}
+
+	testErrs := []struct {
+		name        string
+		in          string
+		fs          pathtools.FileSystem
+		path        string
+		expectedErr string
+	}{
+		{
+			name: "license rewriting with a wrong path",
+			in: `
+				android_test {
+					name: "foo",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+					android_license_files: ["../../LicenseFile1", "../../LicenseFile2",],
+				}
+			`,
+			fs:   mockFs,
+			path: relativePathErr,
+			expectedErr: `
+				Cannot find an Android.mk file at path "a/b/c"
+			`,
+		},
+	}
+	for _, test := range testErrs {
+		t.Run(test.name, func(t *testing.T) {
+			checkError(t, test.in, test.expectedErr, runPatchListMod(rewriteLicenseProperty(test.fs, test.path)))
+		})
+	}
+}
+
+func TestHaveSameLicense(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "two modules with the same license",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+			out: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind"],
+					android_license_conditions: ["license_notice"],
+				}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPassOnce(t, test.in, test.out, func(fixer *Fixer) error {
+				return haveSameLicense(fixer)
+			})
+		})
+	}
+	testErrs := []struct {
+		name        string
+		in          string
+		expectedErr string
+	}{
+		{
+			name: "two modules will different licenses",
+			in: `
+				android_test {
+					name: "foo1",
+					android_license_kinds: ["license_kind1"],
+					android_license_conditions: ["license_notice1"],
+				}
+
+				android_test {
+					name: "foo2",
+					android_license_kinds: ["license_kind2"],
+					android_license_conditions: ["license_notice2"],
+				}
+			`,
+			expectedErr: `
+				Modules foo1 and foo2 are expected to have the same android_license_kinds property.
+			`,
+		},
+	}
+	for _, test := range testErrs {
+		t.Run(test.name, func(t *testing.T) {
+			checkError(t, test.in, test.expectedErr, func(fixer *Fixer) error {
+				return haveSameLicense(fixer)
+			})
+		})
+	}
+}
+
+func TestRemoveResourceAndAssetsIfDefault(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		out  string
+	}{
+		{
+			name: "resource_dirs default",
+			in: `
+			android_app {
+				name: "foo",
+				resource_dirs: ["res"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+
+			}
+			`,
+		},
+		{
+			name: "resource_dirs not default",
+			in: `
+			android_app {
+				name: "foo",
+				resource_dirs: ["reso"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+				resource_dirs: ["reso"],
+			}
+			`,
+		},
+		{
+			name: "resource_dirs includes not default",
+			in: `
+			android_app {
+				name: "foo",
+				resource_dirs: ["res", "reso"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+				resource_dirs: ["res", "reso"],
+			}
+			`,
+		}, {
+			name: "asset_dirs default",
+			in: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assets"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+
+			}
+			`,
+		},
+		{
+			name: "asset_dirs not default",
+			in: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assety"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assety"],
+			}
+			`,
+		},
+		{
+			name: "asset_dirs includes not default",
+			in: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assets", "assety"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assets", "assety"],
+			}
+			`,
+		},
+		{
+			name: "resource_dirs and asset_dirs both default",
+			in: `
+			android_app {
+				name: "foo",
+				asset_dirs: ["assets"],
+				resource_dirs: ["res"],
+			}
+			`,
+			out: `
+			android_app {
+				name: "foo",
+
+			}
+			`,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			runPassOnce(t, test.in, test.out, func(fixer *Fixer) error {
+				return removeResourceAndAssetsIfDefault(fixer)
+			})
+		})
+	}
+}
diff --git a/bpfix/cmd_lib/bpfix.go b/bpfix/cmd_lib/bpfix.go
index f90f65b..1106d4a 100644
--- a/bpfix/cmd_lib/bpfix.go
+++ b/bpfix/cmd_lib/bpfix.go
@@ -114,7 +114,7 @@
 
 func makeFileVisitor(fixRequest bpfix.FixRequest) func(string, os.FileInfo, error) error {
 	return func(path string, f os.FileInfo, err error) error {
-		if err == nil && (f.Name() == "Blueprints" || f.Name() == "Android.bp") {
+		if err == nil && f.Name() == "Android.bp" {
 			err = openAndProcess(path, os.Stdout, fixRequest)
 		}
 		if err != nil {
diff --git a/build_kzip.bash b/build_kzip.bash
index a09335e..aff2d6d 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -6,6 +6,8 @@
 # The following environment variables affect the result:
 #   BUILD_NUMBER          build number, used to generate unique ID (will use UUID if not set)
 #   SUPERPROJECT_SHA      superproject sha, used to generate unique id (will use BUILD_NUMBER if not set)
+#   SUPERPROJECT_REVISION superproject revision, used for unique id if defined as a sha
+#   KZIP_NAME             name of the output file (will use SUPERPROJECT_REVISION|SUPERPROJECT_SHA|BUILD_NUMBER|UUID if not set)
 #   DIST_DIR              where the resulting all.kzip will be placed
 #   KYTHE_KZIP_ENCODING   proto or json (proto is default)
 #   KYTHE_JAVA_SOURCE_BATCH_SIZE maximum number of the Java source files in a compilation unit
@@ -14,8 +16,16 @@
 #   TARGET_PRODUCT        target device name, e.g., 'aosp_blueline'
 #   XREF_CORPUS           source code repository URI, e.g., 'android.googlesource.com/platform/superproject'
 
-: ${BUILD_NUMBER:=$(uuidgen)}
-: ${SUPERPROJECT_SHA:=$BUILD_NUMBER}
+# If the SUPERPROJECT_REVISION is defined as a sha, use this as the default value if no
+# SUPERPROJECT_SHA is specified.
+if [[ ${SUPERPROJECT_REVISION:-} =~ [0-9a-f]{40} ]]; then
+  : ${KZIP_NAME:=${SUPERPROJECT_REVISION:-}}
+fi
+
+: ${KZIP_NAME:=${SUPERPROJECT_SHA:-}}
+: ${KZIP_NAME:=${BUILD_NUMBER:-}}
+: ${KZIP_NAME:=$(uuidgen)}
+
 : ${KYTHE_JAVA_SOURCE_BATCH_SIZE:=500}
 : ${KYTHE_KZIP_ENCODING:=proto}
 : ${XREF_CORPUS:?should be set}
@@ -50,6 +60,6 @@
 
 # Pack
 # TODO(asmundak): this should be done by soong.
-declare -r allkzip="$SUPERPROJECT_SHA.kzip"
-"$out/soong/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
+declare -r allkzip="$KZIP_NAME.kzip"
+"$out/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
 
diff --git a/build_test.bash b/build_test.bash
index 3230f2d..1dc6660 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -25,7 +25,9 @@
 
 # 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.
     mainline_sdk
+    ndk
 )
 
 # To track how long we took to startup. %N isn't supported on Darwin, but
@@ -49,8 +51,14 @@
 esac
 
 echo
+echo "Free disk space:"
+# Ignore df errors because it errors out on gvfsd file systems
+# but still displays most of the useful info we need
+df -h || true
+
+echo
 echo "Running Bazel smoke test..."
-"${TOP}/tools/bazel" --batch --max_idle_secs=1 info
+STANDALONE_BAZEL=true "${TOP}/tools/bazel" --batch --max_idle_secs=1 info
 
 echo
 echo "Running Soong test..."
diff --git a/cc/Android.bp b/cc/Android.bp
index 1fc8d9f..9103a48 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -13,14 +13,17 @@
         "soong-bazel",
         "soong-cc-config",
         "soong-etc",
+        "soong-fuzz",
         "soong-genrule",
+        "soong-snapshot",
         "soong-tradefed",
     ],
     srcs: [
+        "afdo.go",
         "androidmk.go",
         "api_level.go",
-        "builder.go",
         "bp2build.go",
+        "builder.go",
         "cc.go",
         "ccdeps.go",
         "check.go",
@@ -58,16 +61,18 @@
         "binary.go",
         "binary_sdk_member.go",
         "fuzz.go",
+        "image_sdk_traits.go",
         "library.go",
         "library_headers.go",
         "library_sdk_member.go",
+        "native_bridge_sdk_trait.go",
         "object.go",
         "test.go",
-        "toolchain_library.go",
 
-        "ndk_prebuilt.go",
+        "ndk_abi.go",
         "ndk_headers.go",
         "ndk_library.go",
+        "ndk_prebuilt.go",
         "ndk_sysroot.go",
 
         "llndk_library.go",
@@ -83,6 +88,7 @@
         "stub_library.go",
     ],
     testSrcs: [
+        "afdo_test.go",
         "cc_test.go",
         "compiler_test.go",
         "gen_test.go",
diff --git a/cc/OWNERS b/cc/OWNERS
index 6d7c30a..a438b15 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 lto.go,pgo.go = srhines@google.com, pirama@google.com, yikong@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/afdo.go b/cc/afdo.go
new file mode 100644
index 0000000..66e9732
--- /dev/null
+++ b/cc/afdo.go
@@ -0,0 +1,202 @@
+// 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"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+var (
+	globalAfdoProfileProjects = []string{
+		"vendor/google_data/pgo_profile/sampling/",
+		"toolchain/pgo-profiles/sampling/",
+	}
+)
+
+var afdoProfileProjectsConfigKey = android.NewOnceKey("AfdoProfileProjects")
+
+const afdoCFlagsFormat = "-funique-internal-linkage-names -fprofile-sample-accurate -fprofile-sample-use=%s"
+
+func getAfdoProfileProjects(config android.DeviceConfig) []string {
+	return config.OnceStringSlice(afdoProfileProjectsConfigKey, func() []string {
+		return append(globalAfdoProfileProjects, config.AfdoAdditionalProfileDirs()...)
+	})
+}
+
+func recordMissingAfdoProfileFile(ctx android.BaseModuleContext, missing string) {
+	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
+}
+
+type AfdoProperties struct {
+	// Afdo allows developers self-service enroll for
+	// automatic feedback-directed optimization using profile data.
+	Afdo bool
+
+	AfdoTarget *string  `blueprint:"mutated"`
+	AfdoDeps   []string `blueprint:"mutated"`
+}
+
+type afdo struct {
+	Properties AfdoProperties
+}
+
+func (afdo *afdo) props() []interface{} {
+	return []interface{}{&afdo.Properties}
+}
+
+func (afdo *afdo) AfdoEnabled() bool {
+	return afdo != nil && afdo.Properties.Afdo && afdo.Properties.AfdoTarget != nil
+}
+
+// Get list of profile file names, ordered by level of specialisation. For example:
+//   1. libfoo_arm64.afdo
+//   2. libfoo.afdo
+// Add more specialisation as needed.
+func getProfileFiles(ctx android.BaseModuleContext, moduleName string) []string {
+	var files []string
+	files = append(files, moduleName+"_"+ctx.Arch().ArchType.String()+".afdo")
+	files = append(files, moduleName+".afdo")
+	return files
+}
+
+func (props *AfdoProperties) GetAfdoProfileFile(ctx android.BaseModuleContext, module string) android.OptionalPath {
+	// Test if the profile_file is present in any of the Afdo profile projects
+	for _, profileFile := range getProfileFiles(ctx, module) {
+		for _, profileProject := range getAfdoProfileProjects(ctx.DeviceConfig()) {
+			path := android.ExistentPathForSource(ctx, profileProject, profileFile)
+			if path.Valid() {
+				return path
+			}
+		}
+	}
+
+	// Record that this module's profile file is absent
+	missing := ctx.ModuleDir() + ":" + module
+	recordMissingAfdoProfileFile(ctx, missing)
+
+	return android.OptionalPathForPath(nil)
+}
+
+func (afdo *afdo) begin(ctx BaseModuleContext) {
+	if ctx.Host() {
+		return
+	}
+	if ctx.static() && !ctx.staticBinary() {
+		return
+	}
+	if afdo.Properties.Afdo {
+		module := ctx.ModuleName()
+		if afdo.Properties.GetAfdoProfileFile(ctx, module).Valid() {
+			afdo.Properties.AfdoTarget = proptools.StringPtr(module)
+		}
+	}
+}
+
+func (afdo *afdo) flags(ctx ModuleContext, flags Flags) Flags {
+	if profile := afdo.Properties.AfdoTarget; profile != nil {
+		if profileFile := afdo.Properties.GetAfdoProfileFile(ctx, *profile); profileFile.Valid() {
+			profileFilePath := profileFile.Path()
+
+			profileUseFlag := fmt.Sprintf(afdoCFlagsFormat, profileFile)
+			flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlag)
+			flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlag)
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-no-warn-sample-unused=true")
+
+			// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
+			// if profileFile gets updated
+			flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath)
+			flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath)
+		}
+	}
+
+	return flags
+}
+
+// Propagate afdo requirements down from binaries
+func afdoDepsMutator(mctx android.TopDownMutatorContext) {
+	if m, ok := mctx.Module().(*Module); ok && m.afdo.AfdoEnabled() {
+		afdoTarget := *m.afdo.Properties.AfdoTarget
+		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
+			tag := mctx.OtherModuleDependencyTag(dep)
+			libTag, isLibTag := tag.(libraryDependencyTag)
+
+			// Do not recurse down non-static dependencies
+			if isLibTag {
+				if !libTag.static() {
+					return false
+				}
+			} else {
+				if tag != objDepTag && tag != reuseObjTag {
+					return false
+				}
+			}
+
+			if dep, ok := dep.(*Module); ok {
+				dep.afdo.Properties.AfdoDeps = append(dep.afdo.Properties.AfdoDeps, afdoTarget)
+			}
+
+			return true
+		})
+	}
+}
+
+// Create afdo variants for modules that need them
+func afdoMutator(mctx android.BottomUpMutatorContext) {
+	if m, ok := mctx.Module().(*Module); ok && m.afdo != nil {
+		if m.afdo.AfdoEnabled() && !m.static() {
+			afdoTarget := *m.afdo.Properties.AfdoTarget
+			mctx.SetDependencyVariation(encodeTarget(afdoTarget))
+		}
+
+		variationNames := []string{""}
+		afdoDeps := android.FirstUniqueStrings(m.afdo.Properties.AfdoDeps)
+		for _, dep := range afdoDeps {
+			variationNames = append(variationNames, encodeTarget(dep))
+		}
+		if len(variationNames) > 1 {
+			modules := mctx.CreateVariations(variationNames...)
+			for i, name := range variationNames {
+				if name == "" {
+					continue
+				}
+				variation := modules[i].(*Module)
+				variation.Properties.PreventInstall = true
+				variation.Properties.HideFromMake = true
+				variation.afdo.Properties.AfdoTarget = proptools.StringPtr(decodeTarget(name))
+			}
+		}
+	}
+}
+
+// Encode target name to variation name.
+func encodeTarget(target string) string {
+	if target == "" {
+		return ""
+	}
+	return "afdo-" + target
+}
+
+// Decode target name from variation name.
+func decodeTarget(variation string) string {
+	if variation == "" {
+		return ""
+	}
+	return strings.TrimPrefix(variation, "afdo-")
+}
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
new file mode 100644
index 0000000..5515464
--- /dev/null
+++ b/cc/afdo_test.go
@@ -0,0 +1,70 @@
+// 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 (
+	"testing"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+func TestAfdoDeps(t *testing.T) {
+	bp := `
+	cc_library {
+		name: "libTest",
+		srcs: ["foo.c"],
+		static_libs: ["libFoo"],
+		afdo: true,
+	}
+
+	cc_library {
+		name: "libFoo",
+		static_libs: ["libBar"],
+	}
+
+	cc_library {
+		name: "libBar",
+	}
+	`
+	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForAfdoTest,
+	).RunTestWithBp(t, bp)
+
+	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
+	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest").Module()
+	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest").Module()
+
+	hasDep := func(m android.Module, wantDep android.Module) bool {
+		var found bool
+		result.VisitDirectDeps(m, func(dep blueprint.Module) {
+			if dep == wantDep {
+				found = true
+			}
+		})
+		return found
+	}
+
+	if !hasDep(libTest, libFoo) {
+		t.Errorf("libTest missing dependency on afdo variant of libFoo")
+	}
+
+	if !hasDep(libFoo, libBar) {
+		t.Errorf("libTest missing dependency on afdo variant of libBar")
+	}
+}
diff --git a/cc/androidmk.go b/cc/androidmk.go
index e58d166..ff5ba45 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -15,6 +15,8 @@
 package cc
 
 import (
+	"github.com/google/blueprint/proptools"
+
 	"fmt"
 	"io"
 	"path/filepath"
@@ -24,12 +26,12 @@
 )
 
 var (
-	nativeBridgeSuffix  = ".native_bridge"
-	productSuffix       = ".product"
+	NativeBridgeSuffix  = ".native_bridge"
+	ProductSuffix       = ".product"
 	VendorSuffix        = ".vendor"
-	ramdiskSuffix       = ".ramdisk"
+	RamdiskSuffix       = ".ramdisk"
 	VendorRamdiskSuffix = ".vendor_ramdisk"
-	recoverySuffix      = ".recovery"
+	RecoverySuffix      = ".recovery"
 	sdkSuffix           = ".sdk"
 )
 
@@ -80,7 +82,7 @@
 		// to be installed. And this is breaking some older devices (like marlin)
 		// where system.img is small.
 		Required: c.Properties.AndroidMkRuntimeLibs,
-		Include:  "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:  "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
@@ -106,6 +108,9 @@
 				if len(c.Properties.AndroidMkHeaderLibs) > 0 {
 					entries.AddStrings("LOCAL_HEADER_LIBRARIES", c.Properties.AndroidMkHeaderLibs...)
 				}
+				if len(c.Properties.AndroidMkRuntimeLibs) > 0 {
+					entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...)
+				}
 				entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType)
 				if c.UseVndk() {
 					entries.SetBool("LOCAL_USE_VNDK", true)
@@ -182,7 +187,7 @@
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
 		var result []string
 		for _, override := range overrides {
-			result = append(result, override+nativeBridgeSuffix)
+			result = append(result, override+NativeBridgeSuffix)
 		}
 		return result
 	}
@@ -241,7 +246,7 @@
 		entries.Class = "SHARED_LIBRARIES"
 		entries.ExtraEntries = append(entries.ExtraEntries, func(_ android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 			entries.SetString("LOCAL_SOONG_TOC", library.toc().String())
-			if !library.buildStubs() {
+			if !library.buildStubs() && library.unstrippedOutputFile != nil {
 				entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", library.unstrippedOutputFile.String())
 			}
 			if len(library.Properties.Overrides) > 0 {
@@ -263,9 +268,10 @@
 		library.androidMkWriteExportedFlags(entries)
 		library.androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries)
 
-		_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
-
-		entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+		if entries.OutputFile.Valid() {
+			_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
+			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+		}
 
 		if library.coverageOutputFile.Valid() {
 			entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputFile.String())
@@ -325,6 +331,14 @@
 		})
 }
 
+func (test *testDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+		if len(test.InstallerProperties.Test_suites) > 0 {
+			entries.AddCompatibilityTestSuites(test.InstallerProperties.Test_suites...)
+		}
+	})
+}
+
 func (binary *binaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	ctx.subAndroidMk(entries, binary.baseInstaller)
 
@@ -373,14 +387,13 @@
 
 func (test *testBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	ctx.subAndroidMk(entries, test.binaryDecorator)
+	ctx.subAndroidMk(entries, test.testDecorator)
+
 	entries.Class = "NATIVE_TESTS"
 	if Bool(test.Properties.Test_per_src) {
 		entries.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
 	}
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		if len(test.Properties.Test_suites) > 0 {
-			entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
-		}
 		if test.testConfig != nil {
 			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
 		}
@@ -391,6 +404,11 @@
 		if Bool(test.Properties.Test_options.Unit_test) {
 			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
 		}
+
+		entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(test.Properties.Per_testcase_directory))
+		if len(test.Properties.Data_bins) > 0 {
+			entries.AddStrings("LOCAL_TEST_DATA_BINS", test.Properties.Data_bins...)
+		}
 	})
 
 	AndroidMkWriteTestData(test.data, entries)
@@ -401,24 +419,24 @@
 	ctx.subAndroidMk(entries, fuzz.binaryDecorator)
 
 	var fuzzFiles []string
-	for _, d := range fuzz.corpus {
+	for _, d := range fuzz.fuzzPackagedModule.Corpus {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.corpusIntermediateDir.String())+":corpus/"+d.Base())
+			filepath.Dir(fuzz.fuzzPackagedModule.CorpusIntermediateDir.String())+":corpus/"+d.Base())
 	}
 
-	for _, d := range fuzz.data {
+	for _, d := range fuzz.fuzzPackagedModule.Data {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.dataIntermediateDir.String())+":data/"+d.Rel())
+			filepath.Dir(fuzz.fuzzPackagedModule.DataIntermediateDir.String())+":data/"+d.Rel())
 	}
 
-	if fuzz.dictionary != nil {
+	if fuzz.fuzzPackagedModule.Dictionary != nil {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.dictionary.String())+":"+fuzz.dictionary.Base())
+			filepath.Dir(fuzz.fuzzPackagedModule.Dictionary.String())+":"+fuzz.fuzzPackagedModule.Dictionary.Base())
 	}
 
-	if fuzz.config != nil {
+	if fuzz.fuzzPackagedModule.Config != nil {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.config.String())+":config.json")
+			filepath.Dir(fuzz.fuzzPackagedModule.Config.String())+":config.json")
 	}
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
@@ -434,28 +452,16 @@
 
 func (test *testLibrary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	ctx.subAndroidMk(entries, test.libraryDecorator)
-}
-
-func (library *toolchainLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	entries.Class = "STATIC_LIBRARIES"
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		_, suffix, _ := android.SplitFileExt(entries.OutputFile.Path().Base())
-		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
-	})
+	ctx.subAndroidMk(entries, test.testDecorator)
 }
 
 func (installer *baseInstaller) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	if installer.path == (android.InstallPath{}) {
 		return
 	}
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if ctx.Target().Os.Class == android.Host {
-		entries.OutputFile = android.OptionalPathForPath(installer.path)
-	}
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		path, file := filepath.Split(installer.path.ToMakePath().String())
+		path, file := filepath.Split(installer.path.String())
 		stem, suffix, _ := android.SplitFileExt(file)
 		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 		entries.SetString("LOCAL_MODULE_PATH", path)
@@ -534,7 +540,13 @@
 		c.libraryDecorator.androidMkWriteExportedFlags(entries)
 
 		if c.shared() || c.static() {
-			path, file := filepath.Split(c.path.ToMakePath().String())
+			src := c.path.String()
+			// For static libraries which aren't installed, directly use Src to extract filename.
+			// This is safe: generated snapshot modules have a real path as Src, not a module
+			if c.static() {
+				src = proptools.String(c.properties.Src)
+			}
+			path, file := filepath.Split(src)
 			stem, suffix, ext := android.SplitFileExt(file)
 			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
@@ -556,10 +568,6 @@
 func (c *snapshotBinaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	entries.Class = "EXECUTABLES"
 	entries.SubName = c.baseProperties.Androidmk_suffix
-
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		entries.AddStrings("LOCAL_MODULE_SYMLINKS", c.Properties.Symlinks...)
-	})
 }
 
 func (c *snapshotObjectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
@@ -585,8 +593,8 @@
 		if p.properties.Check_elf_files != nil {
 			entries.SetBool("LOCAL_CHECK_ELF_FILES", *p.properties.Check_elf_files)
 		} else {
-			// soong_cc_prebuilt.mk does not include check_elf_file.mk by default
-			// because cc_library_shared and cc_binary use soong_cc_prebuilt.mk as well.
+			// soong_cc_rust_prebuilt.mk does not include check_elf_file.mk by default
+			// because cc_library_shared and cc_binary use soong_cc_rust_prebuilt.mk as well.
 			// In order to turn on prebuilt ABI checker, set `LOCAL_CHECK_ELF_FILES` to
 			// true if `p.properties.Check_elf_files` is not specified.
 			entries.SetBool("LOCAL_CHECK_ELF_FILES", true)
diff --git a/cc/binary.go b/cc/binary.go
index 999b82c..c5017c1 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -18,8 +18,10 @@
 	"path/filepath"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 )
 
 type BinaryLinkerProperties struct {
@@ -62,18 +64,19 @@
 
 func RegisterBinaryBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("cc_binary", BinaryFactory)
-	ctx.RegisterModuleType("cc_binary_host", binaryHostFactory)
+	ctx.RegisterModuleType("cc_binary_host", BinaryHostFactory)
 }
 
 // cc_binary produces a binary that is runnable on a device.
 func BinaryFactory() android.Module {
-	module, _ := NewBinary(android.HostAndDeviceSupported)
+	module, _ := newBinary(android.HostAndDeviceSupported, true)
+	module.bazelHandler = &ccBinaryBazelHandler{module: module}
 	return module.Init()
 }
 
 // cc_binary_host produces a binary that is runnable on a host.
-func binaryHostFactory() android.Module {
-	module, _ := NewBinary(android.HostSupported)
+func BinaryHostFactory() android.Module {
+	module, _ := newBinary(android.HostSupported, true)
 	return module.Init()
 }
 
@@ -146,16 +149,21 @@
 // modules common to most binaries, such as bionic libraries.
 func (binary *binaryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = binary.baseLinker.linkerDeps(ctx, deps)
-	if ctx.toolchain().Bionic() {
-		if !Bool(binary.baseLinker.Properties.Nocrt) {
-			if binary.static() {
-				deps.CrtBegin = "crtbegin_static"
-			} else {
-				deps.CrtBegin = "crtbegin_dynamic"
-			}
-			deps.CrtEnd = "crtend_android"
+	if !Bool(binary.baseLinker.Properties.Nocrt) {
+		if binary.static() {
+			deps.CrtBegin = ctx.toolchain().CrtBeginStaticBinary()
+			deps.CrtEnd = ctx.toolchain().CrtEndStaticBinary()
+		} else {
+			deps.CrtBegin = ctx.toolchain().CrtBeginSharedBinary()
+			deps.CrtEnd = ctx.toolchain().CrtEndSharedBinary()
 		}
+	}
 
+	if binary.static() {
+		deps.StaticLibs = append(deps.StaticLibs, deps.SystemSharedLibs...)
+	}
+
+	if ctx.toolchain().Bionic() {
 		if binary.static() {
 			if ctx.selectedStl() == "libc++_static" {
 				deps.StaticLibs = append(deps.StaticLibs, "libm", "libc")
@@ -169,16 +177,8 @@
 			deps.LateStaticLibs = append(groupLibs, deps.LateStaticLibs...)
 		}
 
-		// Embed the linker into host bionic binaries. This is needed to support host bionic,
-		// as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
-		// either an absolute path, or relative from CWD. To work around this, we extract
-		// the load sections from the runtime linker ELF binary and embed them into each host
-		// bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
-		// binary, and then we use a special entry point to fix up the arguments passed by
-		// the kernel before jumping to the embedded linker.
 		if ctx.Os() == android.LinuxBionic && !binary.static() {
 			deps.DynamicLinker = "linker"
-			deps.LinkerFlagsFile = "host_bionic_linker_flags"
 		}
 	}
 
@@ -190,15 +190,14 @@
 	return deps
 }
 
-func (binary *binaryDecorator) isDependencyRoot() bool {
-	// Binaries are always the dependency root.
-	return true
-}
-
 // NewBinary builds and returns a new Module corresponding to a C++ binary.
 // Individual module implementations which comprise a C++ binary should call this function,
 // set some fields on the result, and then call the Init function.
 func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
+	return newBinary(hod, true)
+}
+
+func newBinary(hod android.HostOrDeviceSupported, bazelable bool) (*Module, *binaryDecorator) {
 	module := newModule(hod, android.MultilibFirst)
 	binary := &binaryDecorator{
 		baseLinker:    NewBaseLinker(module.sanitize),
@@ -207,6 +206,7 @@
 	module.compiler = NewBaseCompiler()
 	module.linker = binary
 	module.installer = binary
+	module.bazelable = bazelable
 
 	// Allow module to be added as member of an sdk/module_exports.
 	module.sdkMemberTypes = []android.SdkMemberType{
@@ -220,18 +220,18 @@
 func (binary *binaryDecorator) linkerInit(ctx BaseModuleContext) {
 	binary.baseLinker.linkerInit(ctx)
 
-	if !ctx.toolchain().Bionic() {
-		if ctx.Os() == android.Linux {
-			// Unless explicitly specified otherwise, host static binaries are built with -static
-			// if HostStaticBinaries is true for the product configuration.
-			if binary.Properties.Static_executable == nil && ctx.Config().HostStaticBinaries() {
-				binary.Properties.Static_executable = BoolPtr(true)
-			}
-		} else if !ctx.Fuchsia() {
-			// Static executables are not supported on Darwin or Windows
-			binary.Properties.Static_executable = nil
+	if ctx.Os().Linux() && ctx.Host() {
+		// Unless explicitly specified otherwise, host static binaries are built with -static
+		// if HostStaticBinaries is true for the product configuration.
+		if binary.Properties.Static_executable == nil && ctx.Config().HostStaticBinaries() {
+			binary.Properties.Static_executable = BoolPtr(true)
 		}
 	}
+
+	if ctx.Darwin() || ctx.Windows() {
+		// Static executables are not supported on Darwin or Windows
+		binary.Properties.Static_executable = nil
+	}
 }
 
 func (binary *binaryDecorator) static() bool {
@@ -345,18 +345,18 @@
 
 	var linkerDeps android.Paths
 
-	// Add flags from linker flags file.
-	if deps.LinkerFlagsFile.Valid() {
-		flags.Local.LdFlags = append(flags.Local.LdFlags, "$$(cat "+deps.LinkerFlagsFile.String()+")")
-		linkerDeps = append(linkerDeps, deps.LinkerFlagsFile.Path())
-	}
-
 	if flags.DynamicLinker != "" {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-dynamic-linker,"+flags.DynamicLinker)
-	} else if ctx.toolchain().Bionic() && !binary.static() {
+	} else if (ctx.toolchain().Bionic() || ctx.toolchain().Musl()) && !binary.static() {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-dynamic-linker")
 	}
 
+	if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() {
+		fatOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName)
+		transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path())
+	}
+
 	builderFlags := flagsToBuilderFlags(flags)
 	stripFlags := flagsToStripFlags(flags)
 	if binary.stripper.NeedsStrip(ctx) {
@@ -401,16 +401,18 @@
 		}
 	}
 
+	var validations android.Paths
+
 	// Handle host bionic linker symbols.
 	if ctx.Os() == android.LinuxBionic && !binary.static() {
-		injectedOutputFile := outputFile
-		outputFile = android.PathForModuleOut(ctx, "prelinker", fileName)
+		verifyFile := android.PathForModuleOut(ctx, "host_bionic_verify.stamp")
 
 		if !deps.DynamicLinker.Valid() {
 			panic("Non-static host bionic modules must have a dynamic linker")
 		}
 
-		binary.injectHostBionicLinkerSymbols(ctx, outputFile, deps.DynamicLinker.Path(), injectedOutputFile)
+		binary.verifyHostBionicLinker(ctx, outputFile, deps.DynamicLinker.Path(), verifyFile)
+		validations = append(validations, verifyFile)
 	}
 
 	var sharedLibs android.Paths
@@ -422,15 +424,16 @@
 		linkerDeps = append(linkerDeps, deps.EarlySharedLibsDeps...)
 		linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 		linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
+		linkerDeps = append(linkerDeps, ndkSharedLibDeps(ctx)...)
 	}
 
-	linkerDeps = append(linkerDeps, objs.tidyFiles...)
+	validations = append(validations, objs.tidyDepFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
 	// Register link action.
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
-		builderFlags, outputFile, nil)
+		builderFlags, outputFile, nil, validations)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -438,6 +441,16 @@
 
 	// Need to determine symlinks early since some targets (ie APEX) need this
 	// information but will not call 'install'
+	binary.setSymlinkList(ctx)
+
+	return ret
+}
+
+func (binary *binaryDecorator) unstrippedOutputFilePath() android.Path {
+	return binary.unstrippedOutputFile
+}
+
+func (binary *binaryDecorator) setSymlinkList(ctx ModuleContext) {
 	for _, symlink := range binary.Properties.Symlinks {
 		binary.symlinks = append(binary.symlinks,
 			symlink+String(binary.Properties.Suffix)+ctx.toolchain().ExecutableSuffix())
@@ -454,12 +467,6 @@
 			binary.preferredArchSymlink = symlinkName
 		}
 	}
-
-	return ret
-}
-
-func (binary *binaryDecorator) unstrippedOutputFilePath() android.Path {
-	return binary.unstrippedOutputFile
 }
 
 func (binary *binaryDecorator) symlinkList() []string {
@@ -532,19 +539,19 @@
 }
 
 func init() {
-	pctx.HostBinToolVariable("hostBionicSymbolsInjectCmd", "host_bionic_inject")
+	pctx.HostBinToolVariable("verifyHostBionicCmd", "host_bionic_verify")
 }
 
-var injectHostBionicSymbols = pctx.AndroidStaticRule("injectHostBionicSymbols",
+var verifyHostBionic = pctx.AndroidStaticRule("verifyHostBionic",
 	blueprint.RuleParams{
-		Command:     "$hostBionicSymbolsInjectCmd -i $in -l $linker -o $out",
-		CommandDeps: []string{"$hostBionicSymbolsInjectCmd"},
+		Command:     "$verifyHostBionicCmd -i $in -l $linker && touch $out",
+		CommandDeps: []string{"$verifyHostBionicCmd"},
 	}, "linker")
 
-func (binary *binaryDecorator) injectHostBionicLinkerSymbols(ctx ModuleContext, in, linker android.Path, out android.WritablePath) {
+func (binary *binaryDecorator) verifyHostBionicLinker(ctx ModuleContext, in, linker android.Path, out android.WritablePath) {
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        injectHostBionicSymbols,
-		Description: "inject host bionic symbols",
+		Rule:        verifyHostBionic,
+		Description: "verify host bionic",
 		Input:       in,
 		Implicit:    linker,
 		Output:      out,
@@ -553,3 +560,123 @@
 		},
 	})
 }
+
+type ccBinaryBazelHandler struct {
+	android.BazelHandler
+
+	module *Module
+}
+
+func (handler *ccBinaryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+	if ok {
+		if len(filePaths) != 1 {
+			ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, filePaths)
+			return false
+		}
+		outputFilePath := android.PathForBazelOut(ctx, filePaths[0])
+		handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+		// TODO(b/220164721): We need to decide if we should return the stripped as the unstripped.
+		handler.module.linker.(*binaryDecorator).unstrippedOutputFile = outputFilePath
+	}
+	return ok
+}
+
+func binaryBp2build(ctx android.TopDownMutatorContext, m *Module, typ string) {
+	baseAttrs := bp2BuildParseBaseProps(ctx, m)
+	binaryLinkerAttrs := bp2buildBinaryLinkerProps(ctx, m)
+
+	if proptools.BoolDefault(binaryLinkerAttrs.Linkshared, true) {
+		baseAttrs.implementationDynamicDeps.Add(baseAttrs.protoDependency)
+	} else {
+		baseAttrs.implementationDeps.Add(baseAttrs.protoDependency)
+	}
+
+	attrs := &binaryAttributes{
+		binaryLinkerAttrs: binaryLinkerAttrs,
+
+		Srcs:    baseAttrs.srcs,
+		Srcs_c:  baseAttrs.cSrcs,
+		Srcs_as: baseAttrs.asSrcs,
+
+		Copts:      baseAttrs.copts,
+		Cppflags:   baseAttrs.cppFlags,
+		Conlyflags: baseAttrs.conlyFlags,
+		Asflags:    baseAttrs.asFlags,
+
+		Deps:               baseAttrs.implementationDeps,
+		Dynamic_deps:       baseAttrs.implementationDynamicDeps,
+		Whole_archive_deps: baseAttrs.wholeArchiveDeps,
+		System_deps:        baseAttrs.systemDynamicDeps,
+
+		Local_includes:    baseAttrs.localIncludes,
+		Absolute_includes: baseAttrs.absoluteIncludes,
+		Linkopts:          baseAttrs.linkopts,
+		Link_crt:          baseAttrs.linkCrt,
+		Use_libcrt:        baseAttrs.useLibcrt,
+		Use_version_lib:   baseAttrs.useVersionLib,
+		Rtti:              baseAttrs.rtti,
+		Stl:               baseAttrs.stl,
+		Cpp_std:           baseAttrs.cppStd,
+
+		Additional_linker_inputs: baseAttrs.additionalLinkerInputs,
+
+		Strip: stripAttributes{
+			Keep_symbols:                 baseAttrs.stripKeepSymbols,
+			Keep_symbols_and_debug_frame: baseAttrs.stripKeepSymbolsAndDebugFrame,
+			Keep_symbols_list:            baseAttrs.stripKeepSymbolsList,
+			All:                          baseAttrs.stripAll,
+			None:                         baseAttrs.stripNone,
+		},
+
+		Features: baseAttrs.features,
+
+		sdkAttributes: bp2BuildParseSdkAttributes(m),
+	}
+
+	ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_binary",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_binary.bzl",
+	},
+		android.CommonAttributes{Name: m.Name()},
+		attrs)
+}
+
+// binaryAttributes contains Bazel attributes corresponding to a cc binary
+type binaryAttributes struct {
+	binaryLinkerAttrs
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+
+	Copts      bazel.StringListAttribute
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Deps               bazel.LabelListAttribute
+	Dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps bazel.LabelListAttribute
+	System_deps        bazel.LabelListAttribute
+
+	Local_includes    bazel.StringListAttribute
+	Absolute_includes bazel.StringListAttribute
+
+	Linkopts                 bazel.StringListAttribute
+	Additional_linker_inputs bazel.LabelListAttribute
+
+	Link_crt        bazel.BoolAttribute
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+
+	Strip stripAttributes
+
+	Features bazel.StringListAttribute
+
+	sdkAttributes
+}
diff --git a/cc/binary_sdk_member.go b/cc/binary_sdk_member.go
index ebf89ea..71e0cd8 100644
--- a/cc/binary_sdk_member.go
+++ b/cc/binary_sdk_member.go
@@ -38,16 +38,16 @@
 	android.SdkMemberTypeBase
 }
 
-func (mt *binarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	targets := mctx.MultiTargets()
+func (mt *binarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	targets := ctx.MultiTargets()
 	for _, bin := range names {
 		for _, target := range targets {
 			variations := target.Variations()
-			if mctx.Device() {
+			if ctx.Device() {
 				variations = append(variations,
 					blueprint.Variation{Mutator: "image", Variation: android.CoreVariation})
 			}
-			mctx.AddFarVariationDependencies(variations, dependencyTag, bin)
+			ctx.AddFarVariationDependencies(variations, dependencyTag, bin)
 		}
 	}
 }
diff --git a/cc/binary_test.go b/cc/binary_test.go
new file mode 100644
index 0000000..8ec3871
--- /dev/null
+++ b/cc/binary_test.go
@@ -0,0 +1,51 @@
+// 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 (
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestCcBinaryWithBazel(t *testing.T) {
+	bp := `
+cc_binary {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToOutputFiles: map[string][]string{
+			"//foo/bar:bar": []string{"foo"},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	binMod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module()
+	producer := binMod.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_binary outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	unStrippedFilePath := binMod.(*Module).UnstrippedOutputFile()
+	expectedUnStrippedFile := "outputbase/execroot/__main__/foo"
+	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 9f9143b..cc378b3 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -14,374 +14,796 @@
 package cc
 
 import (
-	"android/soong/android"
-	"android/soong/bazel"
+	"fmt"
 	"path/filepath"
 	"strings"
+
+	"android/soong/android"
+	"android/soong/bazel"
+
+	"github.com/google/blueprint"
+
+	"github.com/google/blueprint/proptools"
 )
 
-// bp2build functions and helpers for converting cc_* modules to Bazel.
+const (
+	cSrcPartition     = "c"
+	asSrcPartition    = "as"
+	cppSrcPartition   = "cpp"
+	protoSrcPartition = "proto"
+)
 
-func init() {
-	android.DepsBp2BuildMutators(RegisterDepsBp2Build)
+// staticOrSharedAttributes are the Bazel-ified versions of StaticOrSharedProperties --
+// properties which apply to either the shared or static version of a cc_library module.
+type staticOrSharedAttributes struct {
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+	Hdrs    bazel.LabelListAttribute
+	Copts   bazel.StringListAttribute
+
+	Deps                              bazel.LabelListAttribute
+	Implementation_deps               bazel.LabelListAttribute
+	Dynamic_deps                      bazel.LabelListAttribute
+	Implementation_dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps                bazel.LabelListAttribute
+	Implementation_whole_archive_deps bazel.LabelListAttribute
+
+	System_dynamic_deps bazel.LabelListAttribute
+
+	Enabled bazel.BoolAttribute
+
+	sdkAttributes
 }
 
-func RegisterDepsBp2Build(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("cc_bp2build_deps", depsBp2BuildMutator)
+// groupSrcsByExtension partitions `srcs` into groups based on file extension.
+func groupSrcsByExtension(ctx android.BazelConversionPathContext, srcs bazel.LabelListAttribute) bazel.PartitionToLabelListAttribute {
+	// Convert filegroup dependencies into extension-specific filegroups filtered in the filegroup.bzl
+	// macro.
+	addSuffixForFilegroup := func(suffix string) bazel.LabelMapper {
+		return func(ctx bazel.OtherModuleContext, label bazel.Label) (string, bool) {
+			m, exists := ctx.ModuleFromName(label.OriginalModuleName)
+			labelStr := label.Label
+			if !exists || !android.IsFilegroup(ctx, m) {
+				return labelStr, false
+			}
+			return labelStr + suffix, true
+		}
+	}
+
+	// TODO(b/190006308): Handle language detection of sources in a Bazel rule.
+	labels := bazel.LabelPartitions{
+		protoSrcPartition: android.ProtoSrcLabelPartition,
+		cSrcPartition:     bazel.LabelPartition{Extensions: []string{".c"}, LabelMapper: addSuffixForFilegroup("_c_srcs")},
+		asSrcPartition:    bazel.LabelPartition{Extensions: []string{".s", ".S"}, LabelMapper: addSuffixForFilegroup("_as_srcs")},
+		// C++ is the "catch-all" group, and comprises generated sources because we don't
+		// know the language of these sources until the genrule is executed.
+		cppSrcPartition: bazel.LabelPartition{Extensions: []string{".cpp", ".cc", ".cxx", ".mm"}, LabelMapper: addSuffixForFilegroup("_cpp_srcs"), Keep_remainder: true},
+	}
+
+	return bazel.PartitionLabelListAttribute(ctx, &srcs, labels)
 }
 
-// A naive deps mutator to add deps on all modules across all combinations of
-// target props for cc modules. This is needed to make module -> bazel label
-// resolution work in the bp2build mutator later. This is probably
-// the wrong way to do it, but it works.
-//
-// TODO(jingwen): can we create a custom os mutator in depsBp2BuildMutator to do this?
-func depsBp2BuildMutator(ctx android.BottomUpMutatorContext) {
-	module, ok := ctx.Module().(*Module)
+// bp2BuildParseLibProps returns the attributes for a variant of a cc_library.
+func bp2BuildParseLibProps(ctx android.BazelConversionPathContext, module *Module, isStatic bool) staticOrSharedAttributes {
+	lib, ok := module.compiler.(*libraryDecorator)
 	if !ok {
-		// Not a cc module
-		return
+		return staticOrSharedAttributes{}
 	}
-
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	var allDeps []string
-
-	for _, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
-		// arch specific linker props
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
-			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
-		}
-	}
-
-	for _, p := range module.GetArchProperties(ctx, &BaseLinkerProperties{}) {
-		// arch specific linker props
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
-			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
-		}
-	}
-
-	// Deps in the static: { .. } and shared: { .. } props of a cc_library.
-	if lib, ok := module.compiler.(*libraryDecorator); ok {
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Static_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Whole_static_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Shared_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.System_shared_libs...)
-
-		allDeps = append(allDeps, lib.StaticProperties.Static.Static_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.Whole_static_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.Shared_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.System_shared_libs...)
-	}
-
-	ctx.AddDependency(module, nil, android.SortedUniqueStrings(allDeps)...)
-}
-
-type sharedAttributes struct {
-	copts            bazel.StringListAttribute
-	srcs             bazel.LabelListAttribute
-	staticDeps       bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
+	return bp2buildParseStaticOrSharedProps(ctx, module, lib, isStatic)
 }
 
 // bp2buildParseSharedProps returns the attributes for the shared variant of a cc_library.
-func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) sharedAttributes {
-	lib, ok := module.compiler.(*libraryDecorator)
-	if !ok {
-		return sharedAttributes{}
-	}
-
-	copts := bazel.StringListAttribute{Value: lib.SharedProperties.Shared.Cflags}
-
-	srcs := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleSrc(ctx, lib.SharedProperties.Shared.Srcs)}
-
-	staticDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Static_libs)}
-
-	dynamicDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Shared_libs)}
-
-	wholeArchiveDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Whole_static_libs)}
-
-	return sharedAttributes{
-		copts:            copts,
-		srcs:             srcs,
-		staticDeps:       staticDeps,
-		dynamicDeps:      dynamicDeps,
-		wholeArchiveDeps: wholeArchiveDeps,
-	}
-}
-
-type staticAttributes struct {
-	copts            bazel.StringListAttribute
-	srcs             bazel.LabelListAttribute
-	staticDeps       bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
+func bp2BuildParseSharedProps(ctx android.BazelConversionPathContext, module *Module) staticOrSharedAttributes {
+	return bp2BuildParseLibProps(ctx, module, false)
 }
 
 // bp2buildParseStaticProps returns the attributes for the static variant of a cc_library.
-func bp2BuildParseStaticProps(ctx android.TopDownMutatorContext, module *Module) staticAttributes {
-	lib, ok := module.compiler.(*libraryDecorator)
-	if !ok {
-		return staticAttributes{}
+func bp2BuildParseStaticProps(ctx android.BazelConversionPathContext, module *Module) staticOrSharedAttributes {
+	return bp2BuildParseLibProps(ctx, module, true)
+}
+
+type depsPartition struct {
+	export         bazel.LabelList
+	implementation bazel.LabelList
+}
+
+type bazelLabelForDepsFn func(android.BazelConversionPathContext, []string) bazel.LabelList
+
+func maybePartitionExportedAndImplementationsDeps(ctx android.BazelConversionPathContext, exportsDeps bool, allDeps, exportedDeps []string, fn bazelLabelForDepsFn) depsPartition {
+	if !exportsDeps {
+		return depsPartition{
+			implementation: fn(ctx, allDeps),
+		}
 	}
 
-	copts := bazel.StringListAttribute{Value: lib.StaticProperties.Static.Cflags}
+	implementation, export := android.FilterList(allDeps, exportedDeps)
 
-	srcs := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleSrc(ctx, lib.StaticProperties.Static.Srcs)}
-
-	staticDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Static_libs)}
-
-	dynamicDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Shared_libs)}
-
-	wholeArchiveDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Whole_static_libs)}
-
-	return staticAttributes{
-		copts:            copts,
-		srcs:             srcs,
-		staticDeps:       staticDeps,
-		dynamicDeps:      dynamicDeps,
-		wholeArchiveDeps: wholeArchiveDeps,
+	return depsPartition{
+		export:         fn(ctx, export),
+		implementation: fn(ctx, implementation),
 	}
 }
 
+type bazelLabelForDepsExcludesFn func(android.BazelConversionPathContext, []string, []string) bazel.LabelList
+
+func maybePartitionExportedAndImplementationsDepsExcludes(ctx android.BazelConversionPathContext, exportsDeps bool, allDeps, excludes, exportedDeps []string, fn bazelLabelForDepsExcludesFn) depsPartition {
+	if !exportsDeps {
+		return depsPartition{
+			implementation: fn(ctx, allDeps, excludes),
+		}
+	}
+	implementation, export := android.FilterList(allDeps, exportedDeps)
+
+	return depsPartition{
+		export:         fn(ctx, export, excludes),
+		implementation: fn(ctx, implementation, excludes),
+	}
+}
+
+// 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{}
+
+	setAttrs := func(axis bazel.ConfigurationAxis, config string, props StaticOrSharedProperties) {
+		attrs.Copts.SetSelectValue(axis, config, parseCommandLineFlags(props.Cflags, filterOutStdFlag))
+		attrs.Srcs.SetSelectValue(axis, config, android.BazelLabelForModuleSrc(ctx, props.Srcs))
+		attrs.System_dynamic_deps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, props.System_shared_libs))
+
+		staticDeps := maybePartitionExportedAndImplementationsDeps(ctx, true, props.Static_libs, props.Export_static_lib_headers, bazelLabelForStaticDeps)
+		attrs.Deps.SetSelectValue(axis, config, staticDeps.export)
+		attrs.Implementation_deps.SetSelectValue(axis, config, staticDeps.implementation)
+
+		sharedDeps := maybePartitionExportedAndImplementationsDeps(ctx, true, props.Shared_libs, props.Export_shared_lib_headers, bazelLabelForSharedDeps)
+		attrs.Dynamic_deps.SetSelectValue(axis, config, sharedDeps.export)
+		attrs.Implementation_dynamic_deps.SetSelectValue(axis, config, sharedDeps.implementation)
+
+		attrs.Whole_archive_deps.SetSelectValue(axis, config, bazelLabelForWholeDeps(ctx, props.Whole_static_libs))
+		attrs.Enabled.SetSelectValue(axis, config, props.Enabled)
+	}
+	// system_dynamic_deps distinguishes between nil/empty list behavior:
+	//    nil -> use default values
+	//    empty list -> no values specified
+	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)
+				}
+			}
+		}
+	} 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)
+				}
+			}
+		}
+	}
+
+	partitionedSrcs := groupSrcsByExtension(ctx, attrs.Srcs)
+	attrs.Srcs = partitionedSrcs[cppSrcPartition]
+	attrs.Srcs_c = partitionedSrcs[cSrcPartition]
+	attrs.Srcs_as = partitionedSrcs[asSrcPartition]
+
+	if !partitionedSrcs[protoSrcPartition].IsEmpty() {
+		// TODO(b/208815215): determine whether this is used and add support if necessary
+		ctx.ModuleErrorf("Migrating static/shared only proto srcs is not currently supported")
+	}
+
+	return attrs
+}
+
+// Convenience struct to hold all attributes parsed from prebuilt properties.
+type prebuiltAttributes struct {
+	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, 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
+
+	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,
+		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)
+		}
+	}
+}
+
+type baseAttributes struct {
+	compilerAttributes
+	linkerAttributes
+
+	protoDependency *bazel.LabelAttribute
+}
+
 // Convenience struct to hold all attributes parsed from compiler properties.
 type compilerAttributes struct {
-	copts    bazel.StringListAttribute
+	// Options for all languages
+	copts bazel.StringListAttribute
+	// Assembly options and sources
+	asFlags bazel.StringListAttribute
+	asSrcs  bazel.LabelListAttribute
+	// C options and sources
+	conlyFlags bazel.StringListAttribute
+	cSrcs      bazel.LabelListAttribute
+	// C++ options and sources
+	cppFlags bazel.StringListAttribute
 	srcs     bazel.LabelListAttribute
-	includes bazel.StringListAttribute
+
+	hdrs bazel.LabelListAttribute
+
+	rtti bazel.BoolAttribute
+
+	// Not affected by arch variants
+	stl    *string
+	cStd   *string
+	cppStd *string
+
+	localIncludes    bazel.StringListAttribute
+	absoluteIncludes bazel.StringListAttribute
+
+	includes BazelIncludes
+
+	protoSrcs bazel.LabelListAttribute
+
+	stubsSymbolFile *string
+	stubsVersions   bazel.StringListAttribute
 }
 
-// bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
-func bp2BuildParseCompilerProps(ctx android.TopDownMutatorContext, module *Module) compilerAttributes {
-	var srcs bazel.LabelListAttribute
-	var copts bazel.StringListAttribute
+type filterOutFn func(string) bool
 
-	// Creates the -I flag for a directory, while making the directory relative
-	// to the exec root for Bazel to work.
-	includeFlag := func(dir string) string {
-		// filepath.Join canonicalizes the path, i.e. it takes care of . or .. elements.
-		return "-I" + filepath.Join(ctx.ModuleDir(), dir)
-	}
+func filterOutStdFlag(flag string) bool {
+	return strings.HasPrefix(flag, "-std=")
+}
 
-	// Parse the list of module-relative include directories (-I).
-	parseLocalIncludeDirs := func(baseCompilerProps *BaseCompilerProperties) []string {
-		// include_dirs are root-relative, not module-relative.
-		includeDirs := bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
-		return append(includeDirs, baseCompilerProps.Local_include_dirs...)
-	}
-
-	// Parse the list of copts.
-	parseCopts := func(baseCompilerProps *BaseCompilerProperties) []string {
-		var copts []string
-		for _, flag := range append(baseCompilerProps.Cflags, baseCompilerProps.Cppflags...) {
-			// Soong's cflags can contain spaces, like `-include header.h`. For
-			// Bazel's copts, split them up to be compatible with the
-			// no_copts_tokenization feature.
-			copts = append(copts, strings.Split(flag, " ")...)
+func parseCommandLineFlags(soongFlags []string, filterOut filterOutFn) []string {
+	var result []string
+	for _, flag := range soongFlags {
+		if filterOut != nil && filterOut(flag) {
+			continue
 		}
-		for _, dir := range parseLocalIncludeDirs(baseCompilerProps) {
-			copts = append(copts, includeFlag(dir))
-		}
-		return copts
+		// Soong's cflags can contain spaces, like `-include header.h`. For
+		// Bazel's copts, split them up to be compatible with the
+		// no_copts_tokenization feature.
+		result = append(result, strings.Split(flag, " ")...)
+	}
+	return result
+}
+
+func (ca *compilerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversionPathContext, axis bazel.ConfigurationAxis, config string, props *BaseCompilerProperties) {
+	// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
+	// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
+	if srcsList, ok := parseSrcs(ctx, props); ok {
+		ca.srcs.SetSelectValue(axis, config, srcsList)
 	}
 
-	// baseSrcs contain the list of src files that are used for every configuration.
-	var baseSrcs []string
-	// baseExcludeSrcs contain the list of src files that are excluded for every configuration.
-	var baseExcludeSrcs []string
-	// baseSrcsLabelList is a clone of the base srcs LabelList, used for computing the
-	// arch or os specific srcs later.
-	var baseSrcsLabelList bazel.LabelList
-
-	// Parse srcs from an arch or OS's props value, taking the base srcs and
-	// exclude srcs into account.
-	parseSrcs := func(baseCompilerProps *BaseCompilerProperties) bazel.LabelList {
-		// Combine the base srcs and arch-specific srcs
-		allSrcs := append(baseSrcs, baseCompilerProps.Srcs...)
-		// Combine the base exclude_srcs and configuration-specific exclude_srcs
-		allExcludeSrcs := append(baseExcludeSrcs, baseCompilerProps.Exclude_srcs...)
-		return android.BazelLabelForModuleSrcExcludes(ctx, allSrcs, allExcludeSrcs)
-	}
-
-	for _, props := range module.compiler.compilerProps() {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			srcs.Value = parseSrcs(baseCompilerProps)
-			copts.Value = parseCopts(baseCompilerProps)
-
-			// Used for arch-specific srcs later.
-			baseSrcs = baseCompilerProps.Srcs
-			baseExcludeSrcs = baseCompilerProps.Exclude_srcs
-			baseSrcsLabelList = parseSrcs(baseCompilerProps)
-			break
+	localIncludeDirs := props.Local_include_dirs
+	if axis == bazel.NoConfigAxis {
+		ca.cStd, ca.cppStd = bp2buildResolveCppStdValue(props.C_std, props.Cpp_std, props.Gnu_extensions)
+		if includeBuildDirectory(props.Include_build_directory) {
+			localIncludeDirs = append(localIncludeDirs, ".")
 		}
 	}
 
-	// Handle include_build_directory prop. If the property is true, then the
-	// target has access to all headers recursively in the package, and has
-	// "-I<module-dir>" in its copts.
-	if c, ok := module.compiler.(*baseCompiler); ok && c.includeBuildDirectory() {
-		copts.Value = append(copts.Value, includeFlag("."))
-	} else if c, ok := module.compiler.(*libraryDecorator); ok && c.includeBuildDirectory() {
-		copts.Value = append(copts.Value, includeFlag("."))
+	ca.absoluteIncludes.SetSelectValue(axis, config, props.Include_dirs)
+	ca.localIncludes.SetSelectValue(axis, config, localIncludeDirs)
+
+	// In Soong, cflags occur on the command line before -std=<val> flag, resulting in the value being
+	// overridden. In Bazel we always allow overriding, via flags; however, this can cause
+	// incompatibilities, so we remove "-std=" flags from Cflag properties while leaving it in other
+	// cases.
+	ca.copts.SetSelectValue(axis, config, parseCommandLineFlags(props.Cflags, filterOutStdFlag))
+	ca.asFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Asflags, nil))
+	ca.conlyFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Conlyflags, nil))
+	ca.cppFlags.SetSelectValue(axis, config, parseCommandLineFlags(props.Cppflags, nil))
+	ca.rtti.SetSelectValue(axis, config, props.Rtti)
+}
+
+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)
+				}
+			}
+		}
+	}
+}
+
+func (ca *compilerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
+	productVarPropNameToAttribute := map[string]*bazel.StringListAttribute{
+		"Cflags":   &ca.copts,
+		"Asflags":  &ca.asFlags,
+		"CppFlags": &ca.cppFlags,
+	}
+	for propName, attr := range productVarPropNameToAttribute {
+		if productConfigProps, exists := productVariableProps[propName]; exists {
+			for productConfigProp, prop := range productConfigProps {
+				flags, ok := prop.([]string)
+				if !ok {
+					ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
+				}
+				newFlags, _ := bazel.TryVariableSubstitutions(flags, productConfigProp.Name)
+				attr.SetSelectValue(productConfigProp.ConfigurationAxis(), productConfigProp.SelectKey(), newFlags)
+			}
+		}
+	}
+}
+
+func (ca *compilerAttributes) finalize(ctx android.BazelConversionPathContext, implementationHdrs bazel.LabelListAttribute) {
+	ca.srcs.ResolveExcludes()
+	partitionedSrcs := groupSrcsByExtension(ctx, ca.srcs)
+
+	ca.protoSrcs = partitionedSrcs[protoSrcPartition]
+
+	for p, lla := range partitionedSrcs {
+		// if there are no sources, there is no need for headers
+		if lla.IsEmpty() {
+			continue
+		}
+		lla.Append(implementationHdrs)
+		partitionedSrcs[p] = lla
 	}
 
-	for arch, props := range module.GetArchProperties(ctx, &BaseCompilerProperties{}) {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
-			// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
-			if len(baseCompilerProps.Srcs) > 0 || len(baseCompilerProps.Exclude_srcs) > 0 {
-				srcsList := parseSrcs(baseCompilerProps)
-				srcs.SetValueForArch(arch.Name, srcsList)
-				// The base srcs value should not contain any arch-specific excludes.
-				srcs.Value = bazel.SubtractBazelLabelList(srcs.Value, bazel.LabelList{Includes: srcsList.Excludes})
+	ca.srcs = partitionedSrcs[cppSrcPartition]
+	ca.cSrcs = partitionedSrcs[cSrcPartition]
+	ca.asSrcs = partitionedSrcs[asSrcPartition]
+
+	ca.absoluteIncludes.DeduplicateAxesFromBase()
+	ca.localIncludes.DeduplicateAxesFromBase()
+}
+
+// Parse srcs from an arch or OS's props value.
+func parseSrcs(ctx android.BazelConversionPathContext, props *BaseCompilerProperties) (bazel.LabelList, bool) {
+	anySrcs := false
+	// Add srcs-like dependencies such as generated files.
+	// First create a LabelList containing these dependencies, then merge the values with srcs.
+	generatedSrcsLabelList := android.BazelLabelForModuleDepsExcludes(ctx, props.Generated_sources, props.Exclude_generated_sources)
+	if len(props.Generated_sources) > 0 || len(props.Exclude_generated_sources) > 0 {
+		anySrcs = true
+	}
+
+	allSrcsLabelList := android.BazelLabelForModuleSrcExcludes(ctx, props.Srcs, props.Exclude_srcs)
+	if len(props.Srcs) > 0 || len(props.Exclude_srcs) > 0 {
+		anySrcs = true
+	}
+	return bazel.AppendBazelLabelLists(allSrcsLabelList, generatedSrcsLabelList), anySrcs
+}
+
+func bp2buildResolveCppStdValue(c_std *string, cpp_std *string, gnu_extensions *bool) (*string, *string) {
+	var cStdVal, cppStdVal string
+	// If c{,pp}std properties are not specified, don't generate them in the BUILD file.
+	// Defaults are handled by the toolchain definition.
+	// However, if gnu_extensions is false, then the default gnu-to-c version must be specified.
+	if cpp_std != nil {
+		cppStdVal = parseCppStd(cpp_std)
+	} else if gnu_extensions != nil && !*gnu_extensions {
+		cppStdVal = "c++17"
+	}
+	if c_std != nil {
+		cStdVal = parseCStd(c_std)
+	} else if gnu_extensions != nil && !*gnu_extensions {
+		cStdVal = "c99"
+	}
+
+	cStdVal, cppStdVal = maybeReplaceGnuToC(gnu_extensions, cStdVal, cppStdVal)
+	var c_std_prop, cpp_std_prop *string
+	if cStdVal != "" {
+		c_std_prop = &cStdVal
+	}
+	if cppStdVal != "" {
+		cpp_std_prop = &cppStdVal
+	}
+
+	return c_std_prop, cpp_std_prop
+}
+
+// packageFromLabel extracts package from a fully-qualified or relative Label and whether the label
+// is fully-qualified.
+// e.g. fully-qualified "//a/b:foo" -> "a/b", true, relative: ":bar" -> ".", false
+func packageFromLabel(label string) (string, bool) {
+	split := strings.Split(label, ":")
+	if len(split) != 2 {
+		return "", false
+	}
+	if split[0] == "" {
+		return ".", false
+	}
+	// remove leading "//"
+	return split[0][2:], true
+}
+
+// includesFromLabelList extracts relative/absolute includes from a bazel.LabelList>
+func includesFromLabelList(labelList bazel.LabelList) (relative, absolute []string) {
+	for _, hdr := range labelList.Includes {
+		if pkg, hasPkg := packageFromLabel(hdr.Label); hasPkg {
+			absolute = append(absolute, pkg)
+		} else if pkg != "" {
+			relative = append(relative, pkg)
+		}
+	}
+	return relative, absolute
+}
+
+// bp2BuildParseBaseProps returns all compiler, linker, library attributes of a cc module..
+func bp2BuildParseBaseProps(ctx android.Bp2buildMutatorContext, module *Module) baseAttributes {
+	archVariantCompilerProps := module.GetArchVariantProperties(ctx, &BaseCompilerProperties{})
+	archVariantLinkerProps := module.GetArchVariantProperties(ctx, &BaseLinkerProperties{})
+	archVariantLibraryProperties := module.GetArchVariantProperties(ctx, &LibraryProperties{})
+
+	var implementationHdrs bazel.LabelListAttribute
+
+	axisToConfigs := map[bazel.ConfigurationAxis]map[string]bool{}
+	allAxesAndConfigs := func(cp android.ConfigurationAxisToArchVariantProperties) {
+		for axis, configMap := range cp {
+			if _, ok := axisToConfigs[axis]; !ok {
+				axisToConfigs[axis] = map[string]bool{}
+			}
+			for config, _ := range configMap {
+				axisToConfigs[axis][config] = true
+			}
+		}
+	}
+	allAxesAndConfigs(archVariantCompilerProps)
+	allAxesAndConfigs(archVariantLinkerProps)
+	allAxesAndConfigs(archVariantLibraryProperties)
+
+	compilerAttrs := compilerAttributes{}
+	linkerAttrs := linkerAttributes{}
+
+	for axis, configs := range axisToConfigs {
+		for config, _ := range configs {
+			var allHdrs []string
+			if baseCompilerProps, ok := archVariantCompilerProps[axis][config].(*BaseCompilerProperties); ok {
+				allHdrs = baseCompilerProps.Generated_headers
+
+				(&compilerAttrs).bp2buildForAxisAndConfig(ctx, axis, config, baseCompilerProps)
 			}
 
-			copts.SetValueForArch(arch.Name, parseCopts(baseCompilerProps))
+			var exportHdrs []string
+
+			if baseLinkerProps, ok := archVariantLinkerProps[axis][config].(*BaseLinkerProperties); ok {
+				exportHdrs = baseLinkerProps.Export_generated_headers
+
+				(&linkerAttrs).bp2buildForAxisAndConfig(ctx, module.Binary(), axis, config, baseLinkerProps)
+			}
+			headers := maybePartitionExportedAndImplementationsDeps(ctx, !module.Binary(), allHdrs, exportHdrs, android.BazelLabelForModuleDeps)
+			implementationHdrs.SetSelectValue(axis, config, headers.implementation)
+			compilerAttrs.hdrs.SetSelectValue(axis, config, headers.export)
+
+			exportIncludes, exportAbsoluteIncludes := includesFromLabelList(headers.export)
+			compilerAttrs.includes.Includes.SetSelectValue(axis, config, exportIncludes)
+			compilerAttrs.includes.AbsoluteIncludes.SetSelectValue(axis, config, exportAbsoluteIncludes)
+
+			includes, absoluteIncludes := includesFromLabelList(headers.implementation)
+			currAbsoluteIncludes := compilerAttrs.absoluteIncludes.SelectValue(axis, config)
+			currAbsoluteIncludes = android.FirstUniqueStrings(append(currAbsoluteIncludes, absoluteIncludes...))
+			compilerAttrs.absoluteIncludes.SetSelectValue(axis, config, currAbsoluteIncludes)
+			currIncludes := compilerAttrs.localIncludes.SelectValue(axis, config)
+			currIncludes = android.FirstUniqueStrings(append(currIncludes, includes...))
+			compilerAttrs.localIncludes.SetSelectValue(axis, config, currIncludes)
+
+			if libraryProps, ok := archVariantLibraryProperties[axis][config].(*LibraryProperties); ok {
+				if axis == bazel.NoConfigAxis {
+					compilerAttrs.stubsSymbolFile = libraryProps.Stubs.Symbol_file
+					compilerAttrs.stubsVersions.SetSelectValue(axis, config, libraryProps.Stubs.Versions)
+				}
+			}
 		}
 	}
 
-	// After going through all archs, delete the duplicate files in the arch
-	// values that are already in the base srcs.Value.
-	for arch, props := range module.GetArchProperties(ctx, &BaseCompilerProperties{}) {
-		if _, ok := props.(*BaseCompilerProperties); ok {
-			srcs.SetValueForArch(arch.Name, bazel.SubtractBazelLabelList(srcs.GetValueForArch(arch.Name), srcs.Value))
-		}
-	}
+	compilerAttrs.convertStlProps(ctx, module)
+	(&linkerAttrs).convertStripProps(ctx, module)
 
-	// Now that the srcs.Value list is finalized, compare it with the original
-	// list, and put the difference into the default condition for the arch
-	// select.
-	defaultsSrcs := bazel.SubtractBazelLabelList(baseSrcsLabelList, srcs.Value)
-	// TODO(b/186153868): handle the case with multiple variant types, e.g. when arch and os are both used.
-	srcs.SetValueForArch(bazel.CONDITIONS_DEFAULT, defaultsSrcs)
+	productVariableProps := android.ProductVariableProperties(ctx)
 
-	// Handle OS specific props.
-	for os, props := range module.GetTargetProperties(&BaseCompilerProperties{}) {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			srcsList := parseSrcs(baseCompilerProps)
-			// TODO(b/186153868): add support for os-specific srcs and exclude_srcs
-			srcs.SetValueForOS(os.Name, bazel.SubtractBazelLabelList(srcsList, baseSrcsLabelList))
-			copts.SetValueForOS(os.Name, parseCopts(baseCompilerProps))
-		}
-	}
+	(&compilerAttrs).convertProductVariables(ctx, productVariableProps)
+	(&linkerAttrs).convertProductVariables(ctx, productVariableProps)
 
-	return compilerAttributes{
-		srcs:  srcs,
-		copts: copts,
+	(&compilerAttrs).finalize(ctx, implementationHdrs)
+	(&linkerAttrs).finalize(ctx)
+
+	protoDep := bp2buildProto(ctx, module, compilerAttrs.protoSrcs)
+
+	// bp2buildProto will only set wholeStaticLib or implementationWholeStaticLib, but we don't know
+	// which. This will add the newly generated proto library to the appropriate attribute and nothing
+	// to the other
+	(&linkerAttrs).wholeArchiveDeps.Add(protoDep.wholeStaticLib)
+	(&linkerAttrs).implementationWholeArchiveDeps.Add(protoDep.implementationWholeStaticLib)
+
+	return baseAttributes{
+		compilerAttrs,
+		linkerAttrs,
+		protoDep.protoDep,
 	}
 }
 
+func bp2BuildParseSdkAttributes(module *Module) sdkAttributes {
+	return sdkAttributes{
+		Sdk_version:     module.Properties.Sdk_version,
+		Min_sdk_version: module.Properties.Min_sdk_version,
+	}
+}
+
+type sdkAttributes struct {
+	Sdk_version     *string
+	Min_sdk_version *string
+}
+
 // Convenience struct to hold all attributes parsed from linker properties.
 type linkerAttributes struct {
-	deps             bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
-	linkopts         bazel.StringListAttribute
-	versionScript    bazel.LabelAttribute
+	deps                             bazel.LabelListAttribute
+	implementationDeps               bazel.LabelListAttribute
+	dynamicDeps                      bazel.LabelListAttribute
+	implementationDynamicDeps        bazel.LabelListAttribute
+	wholeArchiveDeps                 bazel.LabelListAttribute
+	implementationWholeArchiveDeps   bazel.LabelListAttribute
+	systemDynamicDeps                bazel.LabelListAttribute
+	usedSystemDynamicDepAsDynamicDep map[string]bool
+
+	linkCrt                       bazel.BoolAttribute
+	useLibcrt                     bazel.BoolAttribute
+	useVersionLib                 bazel.BoolAttribute
+	linkopts                      bazel.StringListAttribute
+	additionalLinkerInputs        bazel.LabelListAttribute
+	stripKeepSymbols              bazel.BoolAttribute
+	stripKeepSymbolsAndDebugFrame bazel.BoolAttribute
+	stripKeepSymbolsList          bazel.StringListAttribute
+	stripAll                      bazel.BoolAttribute
+	stripNone                     bazel.BoolAttribute
+	features                      bazel.StringListAttribute
 }
 
-// FIXME(b/187655838): Use the existing linkerFlags() function instead of duplicating logic here
-func getBp2BuildLinkerFlags(linkerProperties *BaseLinkerProperties) []string {
-	flags := linkerProperties.Ldflags
-	if !BoolDefault(linkerProperties.Pack_relocations, true) {
-		flags = append(flags, "-Wl,--pack-dyn-relocs=none")
+var (
+	soongSystemSharedLibs = []string{"libc", "libm", "libdl"}
+)
+
+func (la *linkerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversionPathContext, isBinary bool, axis bazel.ConfigurationAxis, config string, props *BaseLinkerProperties) {
+	// Use a single variable to capture usage of nocrt in arch variants, so there's only 1 error message for this module
+	var axisFeatures []string
+
+	wholeStaticLibs := android.FirstUniqueStrings(props.Whole_static_libs)
+	la.wholeArchiveDeps.SetSelectValue(axis, config, bazelLabelForWholeDepsExcludes(ctx, wholeStaticLibs, props.Exclude_static_libs))
+	// Excludes to parallel Soong:
+	// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=247-249;drc=088b53577dde6e40085ffd737a1ae96ad82fc4b0
+	staticLibs := android.FirstUniqueStrings(android.RemoveListFromList(props.Static_libs, wholeStaticLibs))
+
+	staticDeps := maybePartitionExportedAndImplementationsDepsExcludes(ctx, !isBinary, staticLibs, props.Exclude_static_libs, props.Export_static_lib_headers, bazelLabelForStaticDepsExcludes)
+
+	headerLibs := android.FirstUniqueStrings(props.Header_libs)
+	hDeps := maybePartitionExportedAndImplementationsDeps(ctx, !isBinary, headerLibs, props.Export_header_lib_headers, bazelLabelForHeaderDeps)
+
+	(&hDeps.export).Append(staticDeps.export)
+	la.deps.SetSelectValue(axis, config, hDeps.export)
+
+	(&hDeps.implementation).Append(staticDeps.implementation)
+	la.implementationDeps.SetSelectValue(axis, config, hDeps.implementation)
+
+	systemSharedLibs := props.System_shared_libs
+	// systemSharedLibs distinguishes between nil/empty list behavior:
+	//    nil -> use default values
+	//    empty list -> no values specified
+	if len(systemSharedLibs) > 0 {
+		systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
 	}
-	return flags
+	la.systemDynamicDeps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, systemSharedLibs))
+
+	sharedLibs := android.FirstUniqueStrings(props.Shared_libs)
+	excludeSharedLibs := props.Exclude_shared_libs
+	usedSystem := android.FilterListPred(sharedLibs, func(s string) bool {
+		return android.InList(s, soongSystemSharedLibs) && !android.InList(s, excludeSharedLibs)
+	})
+	for _, el := range usedSystem {
+		if la.usedSystemDynamicDepAsDynamicDep == nil {
+			la.usedSystemDynamicDepAsDynamicDep = map[string]bool{}
+		}
+		la.usedSystemDynamicDepAsDynamicDep[el] = true
+	}
+
+	sharedDeps := maybePartitionExportedAndImplementationsDepsExcludes(ctx, !isBinary, sharedLibs, props.Exclude_shared_libs, props.Export_shared_lib_headers, bazelLabelForSharedDepsExcludes)
+	la.dynamicDeps.SetSelectValue(axis, config, sharedDeps.export)
+	la.implementationDynamicDeps.SetSelectValue(axis, config, sharedDeps.implementation)
+
+	if !BoolDefault(props.Pack_relocations, packRelocationsDefault) {
+		axisFeatures = append(axisFeatures, "disable_pack_relocations")
+	}
+
+	if Bool(props.Allow_undefined_symbols) {
+		axisFeatures = append(axisFeatures, "-no_undefined_symbols")
+	}
+
+	var linkerFlags []string
+	if len(props.Ldflags) > 0 {
+		linkerFlags = append(linkerFlags, proptools.NinjaEscapeList(props.Ldflags)...)
+		// binaries remove static flag if -shared is in the linker flags
+		if isBinary && android.InList("-shared", linkerFlags) {
+			axisFeatures = append(axisFeatures, "-static_flag")
+		}
+	}
+	if props.Version_script != nil {
+		label := android.BazelLabelForModuleSrcSingle(ctx, *props.Version_script)
+		la.additionalLinkerInputs.SetSelectValue(axis, config, bazel.LabelList{Includes: []bazel.Label{label}})
+		linkerFlags = append(linkerFlags, fmt.Sprintf("-Wl,--version-script,$(location %s)", label.Label))
+	}
+	la.linkopts.SetSelectValue(axis, config, linkerFlags)
+	la.useLibcrt.SetSelectValue(axis, config, props.libCrt())
+
+	if axis == bazel.NoConfigAxis {
+		la.useVersionLib.SetSelectValue(axis, config, props.Use_version_lib)
+	}
+
+	// it's very unlikely for nocrt to be arch variant, so bp2build doesn't support it.
+	if props.crt() != nil {
+		if axis == bazel.NoConfigAxis {
+			la.linkCrt.SetSelectValue(axis, config, props.crt())
+		} else if axis == bazel.ArchConfigurationAxis {
+			ctx.ModuleErrorf("nocrt is not supported for arch variants")
+		}
+	}
+
+	if axisFeatures != nil {
+		la.features.SetSelectValue(axis, config, axisFeatures)
+	}
 }
 
-// bp2BuildParseLinkerProps parses the linker properties of a module, including
-// configurable attribute values.
-func bp2BuildParseLinkerProps(ctx android.TopDownMutatorContext, module *Module) linkerAttributes {
-	var deps bazel.LabelListAttribute
-	var dynamicDeps bazel.LabelListAttribute
-	var wholeArchiveDeps bazel.LabelListAttribute
-	var linkopts bazel.StringListAttribute
-	var versionScript bazel.LabelAttribute
+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)
+			}
+		}
+	}
+}
 
-	for _, linkerProps := range module.linker.linkerProps() {
-		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, libs))
-			linkopts.Value = getBp2BuildLinkerFlags(baseLinkerProps)
-			wholeArchiveDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
+func (la *linkerAttributes) convertProductVariables(ctx android.BazelConversionPathContext, productVariableProps android.ProductConfigProperties) {
 
-			if baseLinkerProps.Version_script != nil {
-				versionScript.Value = android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script)
+	type productVarDep struct {
+		// the name of the corresponding excludes field, if one exists
+		excludesField string
+		// reference to the bazel attribute that should be set for the given product variable config
+		attribute *bazel.LabelListAttribute
+
+		depResolutionFunc func(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList
+	}
+
+	productVarToDepFields := map[string]productVarDep{
+		// product variables do not support exclude_shared_libs
+		"Shared_libs":       {attribute: &la.implementationDynamicDeps, depResolutionFunc: bazelLabelForSharedDepsExcludes},
+		"Static_libs":       {"Exclude_static_libs", &la.implementationDeps, bazelLabelForStaticDepsExcludes},
+		"Whole_static_libs": {"Exclude_static_libs", &la.wholeArchiveDeps, bazelLabelForWholeDepsExcludes},
+	}
+
+	for name, dep := range productVarToDepFields {
+		props, exists := productVariableProps[name]
+		excludeProps, excludesExists := productVariableProps[dep.excludesField]
+		// if neither an include or excludes property exists, then skip it
+		if !exists && !excludesExists {
+			continue
+		}
+		// Collect all the configurations that an include or exclude property exists for.
+		// We want to iterate all configurations rather than either the include or exclude because, for a
+		// particular configuration, we may have either only an include or an exclude to handle.
+		productConfigProps := make(map[android.ProductConfigProperty]bool, len(props)+len(excludeProps))
+		for p := range props {
+			productConfigProps[p] = true
+		}
+		for p := range excludeProps {
+			productConfigProps[p] = true
+		}
+
+		for productConfigProp := range productConfigProps {
+			prop, includesExists := props[productConfigProp]
+			excludesProp, excludesExists := excludeProps[productConfigProp]
+			var includes, excludes []string
+			var ok bool
+			// if there was no includes/excludes property, casting fails and that's expected
+			if includes, ok = prop.([]string); includesExists && !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", name)
+			}
+			if excludes, ok = excludesProp.([]string); excludesExists && !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", dep.excludesField)
 			}
 
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, sharedLibs))
-
-			break
+			dep.attribute.EmitEmptyList = productConfigProp.AlwaysEmit()
+			dep.attribute.SetSelectValue(
+				productConfigProp.ConfigurationAxis(),
+				productConfigProp.SelectKey(),
+				dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes),
+			)
 		}
 	}
+}
 
-	for arch, p := range module.GetArchProperties(ctx, &BaseLinkerProperties{}) {
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			deps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, libs))
-			linkopts.SetValueForArch(arch.Name, getBp2BuildLinkerFlags(baseLinkerProps))
-			wholeArchiveDeps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
-
-			if baseLinkerProps.Version_script != nil {
-				versionScript.SetValueForArch(arch.Name,
-					android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script))
-			}
-
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, sharedLibs))
-		}
+func (la *linkerAttributes) finalize(ctx android.BazelConversionPathContext) {
+	// if system dynamic deps have the default value, any use of a system dynamic library used will
+	// result in duplicate library errors for bionic OSes. Here, we explicitly exclude those libraries
+	// from bionic OSes.
+	if la.systemDynamicDeps.IsNil() && len(la.usedSystemDynamicDepAsDynamicDep) > 0 {
+		toRemove := bazelLabelForSharedDeps(ctx, android.SortedStringKeys(la.usedSystemDynamicDepAsDynamicDep))
+		la.dynamicDeps.Exclude(bazel.OsConfigurationAxis, "android", toRemove)
+		la.dynamicDeps.Exclude(bazel.OsConfigurationAxis, "linux_bionic", toRemove)
+		la.implementationDynamicDeps.Exclude(bazel.OsConfigurationAxis, "android", toRemove)
+		la.implementationDynamicDeps.Exclude(bazel.OsConfigurationAxis, "linux_bionic", toRemove)
 	}
 
-	for os, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			wholeArchiveDeps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
-			deps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
+	la.deps.ResolveExcludes()
+	la.implementationDeps.ResolveExcludes()
+	la.dynamicDeps.ResolveExcludes()
+	la.implementationDynamicDeps.ResolveExcludes()
+	la.wholeArchiveDeps.ResolveExcludes()
+	la.systemDynamicDeps.ForceSpecifyEmptyList = true
 
-			linkopts.SetValueForOS(os.Name, getBp2BuildLinkerFlags(baseLinkerProps))
-
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, sharedLibs))
-		}
-	}
-
-	return linkerAttributes{
-		deps:             deps,
-		dynamicDeps:      dynamicDeps,
-		wholeArchiveDeps: wholeArchiveDeps,
-		linkopts:         linkopts,
-		versionScript:    versionScript,
-	}
 }
 
 // Relativize a list of root-relative paths with respect to the module's
@@ -407,47 +829,131 @@
 	return relativePaths
 }
 
+// BazelIncludes contains information about -I and -isystem paths from a module converted to Bazel
+// attributes.
+type BazelIncludes struct {
+	AbsoluteIncludes bazel.StringListAttribute
+	Includes         bazel.StringListAttribute
+	SystemIncludes   bazel.StringListAttribute
+}
+
+func bp2BuildParseExportedIncludes(ctx android.BazelConversionPathContext, module *Module, existingIncludes BazelIncludes) BazelIncludes {
+	libraryDecorator := module.linker.(*libraryDecorator)
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator, &existingIncludes)
+}
+
+// Bp2buildParseExportedIncludesForPrebuiltLibrary returns a BazelIncludes with Bazel-ified values
+// to export includes from the underlying module's properties.
+func Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx android.BazelConversionPathContext, module *Module) BazelIncludes {
+	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
+	libraryDecorator := prebuiltLibraryLinker.libraryDecorator
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator, nil)
+}
+
 // bp2BuildParseExportedIncludes creates a string list attribute contains the
 // exported included directories of a module.
-func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
-	libraryDecorator := module.linker.(*libraryDecorator)
+func bp2BuildParseExportedIncludesHelper(ctx android.BazelConversionPathContext, module *Module, libraryDecorator *libraryDecorator, includes *BazelIncludes) BazelIncludes {
+	var exported BazelIncludes
+	if includes != nil {
+		exported = *includes
+	} else {
+		exported = BazelIncludes{}
+	}
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &FlagExporterProperties{}) {
+		for config, props := range configToProps {
+			if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
+				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()
 
-	// Export_system_include_dirs and export_include_dirs are already module dir
-	// relative, so they don't need to be relativized like include_dirs, which
-	// are root-relative.
-	includeDirs := libraryDecorator.flagExporter.Properties.Export_system_include_dirs
-	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
-	includeDirsAttribute := bazel.MakeStringListAttribute(includeDirs)
+	return exported
+}
 
-	for arch, props := range module.GetArchProperties(ctx, &FlagExporterProperties{}) {
-		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-			archIncludeDirs := flagExporterProperties.Export_system_include_dirs
-			archIncludeDirs = append(archIncludeDirs, flagExporterProperties.Export_include_dirs...)
+func bazelLabelForStaticModule(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	label := android.BazelModuleLabel(ctx, m)
+	if ccModule, ok := m.(*Module); ok && ccModule.typ() == fullLibrary && !android.GenerateCcLibraryStaticOnly(m.Name()) {
+		label += "_bp2build_cc_library_static"
+	}
+	return label
+}
 
-			// To avoid duplicate includes when base includes + arch includes are combined
-			// FIXME: This doesn't take conflicts between arch and os includes into account
-			archIncludeDirs = bazel.SubtractStrings(archIncludeDirs, includeDirs)
+func bazelLabelForSharedModule(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	// cc_library, at it's root name, propagates the shared library, which depends on the static
+	// library.
+	return android.BazelModuleLabel(ctx, m)
+}
 
-			if len(archIncludeDirs) > 0 {
-				includeDirsAttribute.SetValueForArch(arch.Name, archIncludeDirs)
+func bazelLabelForStaticWholeModuleDeps(ctx android.BazelConversionPathContext, m blueprint.Module) string {
+	label := bazelLabelForStaticModule(ctx, m)
+	if aModule, ok := m.(android.Module); ok {
+		if android.IsModulePrebuilt(aModule) {
+			label += "_alwayslink"
+		}
+	}
+	return label
+}
+
+func bazelLabelForWholeDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForStaticWholeModuleDeps)
+}
+
+func bazelLabelForWholeDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForStaticWholeModuleDeps)
+}
+
+func bazelLabelForStaticDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForStaticModule)
+}
+
+func bazelLabelForStaticDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForStaticModule)
+}
+
+func bazelLabelForSharedDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsWithFn(ctx, modules, bazelLabelForSharedModule)
+}
+
+func bazelLabelForHeaderDeps(ctx android.BazelConversionPathContext, modules []string) bazel.LabelList {
+	// This is not elegant, but bp2build's shared library targets only propagate
+	// their header information as part of the normal C++ provider.
+	return bazelLabelForSharedDeps(ctx, modules)
+}
+
+func bazelLabelForSharedDepsExcludes(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return android.BazelLabelForModuleDepsExcludesWithFn(ctx, modules, excludes, bazelLabelForSharedModule)
+}
+
+type binaryLinkerAttrs struct {
+	Linkshared *bool
+}
+
+func bp2buildBinaryLinkerProps(ctx android.BazelConversionPathContext, m *Module) binaryLinkerAttrs {
+	attrs := binaryLinkerAttrs{}
+	archVariantProps := m.GetArchVariantProperties(ctx, &BinaryLinkerProperties{})
+	for axis, configToProps := range archVariantProps {
+		for _, p := range configToProps {
+			props := p.(*BinaryLinkerProperties)
+			staticExecutable := props.Static_executable
+			if axis == bazel.NoConfigAxis {
+				if linkBinaryShared := !proptools.Bool(staticExecutable); !linkBinaryShared {
+					attrs.Linkshared = &linkBinaryShared
+				}
+			} else if staticExecutable != nil {
+				// TODO(b/202876379): Static_executable is arch-variant; however, linkshared is a
+				// nonconfigurable attribute. Only 4 AOSP modules use this feature, defer handling
+				ctx.ModuleErrorf("bp2build cannot migrate a module with arch/target-specific static_executable values")
 			}
 		}
 	}
 
-	for os, props := range module.GetTargetProperties(&FlagExporterProperties{}) {
-		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-			osIncludeDirs := flagExporterProperties.Export_system_include_dirs
-			osIncludeDirs = append(osIncludeDirs, flagExporterProperties.Export_include_dirs...)
-
-			// To avoid duplicate includes when base includes + os includes are combined
-			// FIXME: This doesn't take conflicts between arch and os includes into account
-			osIncludeDirs = bazel.SubtractStrings(osIncludeDirs, includeDirs)
-
-			if len(osIncludeDirs) > 0 {
-				includeDirsAttribute.SetValueForOS(os.Name, osIncludeDirs)
-			}
-		}
-	}
-
-	return includeDirsAttribute
+	return attrs
 }
diff --git a/cc/builder.go b/cc/builder.go
index 51c8a0b..ea06839 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -21,6 +21,7 @@
 import (
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -164,6 +165,12 @@
 		}
 	}()
 
+	darwinLipo = pctx.AndroidStaticRule("darwinLipo",
+		blueprint.RuleParams{
+			Command:     "${config.MacLipoPath} -create -output $out $in",
+			CommandDeps: []string{"${config.MacLipoPath}"},
+		})
+
 	_ = pctx.SourcePathVariable("archiveRepackPath", "build/soong/scripts/archive_repack.sh")
 
 	// Rule to repack an archive (.a) file with a subset of object files.
@@ -196,21 +203,43 @@
 		"clangBin", "format")
 
 	// Rule for invoking clang-tidy (a clang-based linter).
-	clangTidy, clangTidyRE = pctx.RemoteStaticRules("clangTidy",
+	clangTidyDep, clangTidyDepRE = pctx.RemoteStaticRules("clangTidyDep",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && $reTemplate${config.ClangBin}/clang-tidy $tidyFlags $in -- $cFlags && touch $out",
-			CommandDeps: []string{"${config.ClangBin}/clang-tidy"},
+			Depfile: "$out",
+			Deps:    blueprint.DepsGCC,
+			Command: "${config.CcWrapper}$ccCmd $cFlags -E -o /dev/null $in " +
+				"-MQ $tidyFile -MD -MF $out",
+			CommandDeps: []string{"$ccCmd"},
 		},
 		&remoteexec.REParams{
 			Labels:       map[string]string{"type": "lint", "tool": "clang-tidy", "lang": "cpp"},
 			ExecStrategy: "${config.REClangTidyExecStrategy}",
 			Inputs:       []string{"$in"},
-			// OutputFile here is $in for remote-execution since its possible that
-			// clang-tidy modifies the given input file itself and $out refers to the
-			// ".tidy" file generated for ninja-dependency reasons.
-			OutputFiles: []string{"$in"},
-			Platform:    map[string]string{remoteexec.PoolKey: "${config.REClangTidyPool}"},
-		}, []string{"cFlags", "tidyFlags"}, []string{})
+			Platform:     map[string]string{remoteexec.PoolKey: "${config.REClangTidyPool}"},
+		}, []string{"ccCmd", "cFlags", "tidyFile"}, []string{})
+
+	clangTidy, clangTidyRE = pctx.RemoteStaticRules("clangTidy",
+		blueprint.RuleParams{
+			Depfile: "${out}.d",
+			Deps:    blueprint.DepsGCC,
+			Command: "cp ${out}.dep ${out}.d && " +
+				"$tidyVars$reTemplate${config.ClangBin}/clang-tidy $tidyFlags $in -- $cFlags && " +
+				"touch $out",
+			CommandDeps: []string{"${config.ClangBin}/clang-tidy"},
+		},
+		&remoteexec.REParams{
+			Labels:               map[string]string{"type": "lint", "tool": "clang-tidy", "lang": "cpp"},
+			ExecStrategy:         "${config.REClangTidyExecStrategy}",
+			Inputs:               []string{"$in", "${out}.dep"},
+			EnvironmentVariables: []string{"TIDY_TIMEOUT"},
+			// Although clang-tidy has an option to "fix" source files, that feature is hardly useable
+			// under parallel compilation and RBE. So we assume no OutputFiles here.
+			// The clang-tidy fix option is best run locally in single thread.
+			// Copying source file back to local caused two problems:
+			// (1) New timestamps trigger clang and clang-tidy compilations again.
+			// (2) Changing source files caused concurrent clang or clang-tidy jobs to crash.
+			Platform: map[string]string{remoteexec.PoolKey: "${config.REClangTidyPool}"},
+		}, []string{"cFlags", "tidyFlags", "tidyVars"}, []string{})
 
 	_ = pctx.SourcePathVariable("yasmCmd", "prebuilts/misc/${config.HostPrebuiltTag}/yasm/yasm")
 
@@ -224,20 +253,12 @@
 		},
 		"asFlags")
 
-	// Rule to invoke windres, for interaction with Windows resources.
-	windres = pctx.AndroidStaticRule("windres",
-		blueprint.RuleParams{
-			Command:     "$windresCmd $flags -I$$(dirname $in) -i $in -o $out --preprocessor \"${config.ClangBin}/clang -E -xc-header -DRC_INVOKED\"",
-			CommandDeps: []string{"$windresCmd"},
-		},
-		"windresCmd", "flags")
-
 	_ = pctx.SourcePathVariable("sAbiDumper", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-dumper")
 
 	// -w has been added since header-abi-dumper does not need to produce any sort of diagnostic information.
 	sAbiDump, sAbiDumpRE = pctx.RemoteStaticRules("sAbiDump",
 		blueprint.RuleParams{
-			Command:     "rm -f $out && $reTemplate$sAbiDumper -o ${out} $in $exportDirs -- $cFlags -w -isystem prebuilts/clang-tools/${config.HostPrebuiltTag}/clang-headers",
+			Command:     "rm -f $out && $reTemplate$sAbiDumper --root-dir . --root-dir $$OUT_DIR:out -o ${out} $in $exportDirs -- $cFlags -w -isystem prebuilts/clang-tools/${config.HostPrebuiltTag}/clang-headers",
 			CommandDeps: []string{"$sAbiDumper"},
 		}, &remoteexec.REParams{
 			Labels:       map[string]string{"type": "abi-dump", "tool": "header-abi-dumper"},
@@ -255,7 +276,7 @@
 	// sAbi dump file.
 	sAbiLink, sAbiLinkRE = pctx.RemoteStaticRules("sAbiLink",
 		blueprint.RuleParams{
-			Command:        "$reTemplate$sAbiLinker -o ${out} $symbolFilter -arch $arch  $exportedHeaderFlags @${out}.rsp ",
+			Command:        "$reTemplate$sAbiLinker --root-dir . --root-dir $$OUT_DIR:out -o ${out} $symbolFilter -arch $arch $exportedHeaderFlags @${out}.rsp",
 			CommandDeps:    []string{"$sAbiLinker"},
 			Rspfile:        "${out}.rsp",
 			RspfileContent: "${in}",
@@ -375,18 +396,16 @@
 	toolchain     config.Toolchain
 
 	// True if these extra features are enabled.
-	tidy         bool
-	gcovCoverage bool
-	sAbiDump     bool
-	emitXrefs    bool
+	tidy          bool
+	needTidyFiles bool
+	gcovCoverage  bool
+	sAbiDump      bool
+	emitXrefs     bool
 
 	assemblerWithCpp bool // True if .s files should be processed with the c preprocessor.
 
 	systemIncludeFlags string
 
-	// True if static libraries should be grouped (using `-Wl,--start-group` and `-Wl,--end-group`).
-	groupStaticLibs bool
-
 	proto            android.ProtoFlags
 	protoC           bool // If true, compile protos as `.c` files. Otherwise, output as `.cc`.
 	protoOptionsFile bool // If true, output a proto options file.
@@ -411,6 +430,7 @@
 type Objects struct {
 	objFiles      android.Paths
 	tidyFiles     android.Paths
+	tidyDepFiles  android.Paths // link dependent .tidy files
 	coverageFiles android.Paths
 	sAbiDumpFiles android.Paths
 	kytheFiles    android.Paths
@@ -420,6 +440,7 @@
 	return Objects{
 		objFiles:      append(android.Paths{}, a.objFiles...),
 		tidyFiles:     append(android.Paths{}, a.tidyFiles...),
+		tidyDepFiles:  append(android.Paths{}, a.tidyDepFiles...),
 		coverageFiles: append(android.Paths{}, a.coverageFiles...),
 		sAbiDumpFiles: append(android.Paths{}, a.sAbiDumpFiles...),
 		kytheFiles:    append(android.Paths{}, a.kytheFiles...),
@@ -430,6 +451,7 @@
 	return Objects{
 		objFiles:      append(a.objFiles, b.objFiles...),
 		tidyFiles:     append(a.tidyFiles, b.tidyFiles...),
+		tidyDepFiles:  append(a.tidyDepFiles, b.tidyDepFiles...),
 		coverageFiles: append(a.coverageFiles, b.coverageFiles...),
 		sAbiDumpFiles: append(a.sAbiDumpFiles, b.sAbiDumpFiles...),
 		kytheFiles:    append(a.kytheFiles, b.kytheFiles...),
@@ -437,14 +459,26 @@
 }
 
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
-func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles android.Paths,
+func transformSourceToObj(ctx ModuleContext, subdir string, srcFiles, noTidySrcs, timeoutTidySrcs android.Paths,
 	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
-
 	// Source files are one-to-one with tidy, coverage, or kythe files, if enabled.
 	objFiles := make(android.Paths, len(srcFiles))
 	var tidyFiles android.Paths
+	noTidySrcsMap := make(map[string]bool)
+	var tidyVars string
 	if flags.tidy {
 		tidyFiles = make(android.Paths, 0, len(srcFiles))
+		for _, path := range noTidySrcs {
+			noTidySrcsMap[path.String()] = true
+		}
+		tidyTimeout := ctx.Config().Getenv("TIDY_TIMEOUT")
+		if len(tidyTimeout) > 0 {
+			tidyVars += "TIDY_TIMEOUT=" + tidyTimeout + " "
+			// add timeoutTidySrcs into noTidySrcsMap if TIDY_TIMEOUT is set
+			for _, path := range timeoutTidySrcs {
+				noTidySrcsMap[path.String()] = true
+			}
+		}
 	}
 	var coverageFiles android.Paths
 	if flags.gcovCoverage {
@@ -500,10 +534,43 @@
 		sAbiDumpFiles = make(android.Paths, 0, len(srcFiles))
 	}
 
-	cflags += " ${config.NoOverrideClangGlobalCflags}"
-	toolingCflags += " ${config.NoOverrideClangGlobalCflags}"
-	cppflags += " ${config.NoOverrideClangGlobalCflags}"
-	toolingCppflags += " ${config.NoOverrideClangGlobalCflags}"
+	cflags += " ${config.NoOverrideGlobalCflags}"
+	toolingCflags += " ${config.NoOverrideGlobalCflags}"
+	cppflags += " ${config.NoOverrideGlobalCflags}"
+	toolingCppflags += " ${config.NoOverrideGlobalCflags}"
+
+	modulePath := android.PathForModuleSrc(ctx).String()
+	if android.IsThirdPartyPath(modulePath) {
+		cflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCflags += " ${config.NoOverrideExternalGlobalCflags}"
+		cppflags += " ${config.NoOverrideExternalGlobalCflags}"
+		toolingCppflags += " ${config.NoOverrideExternalGlobalCflags}"
+	}
+
+	// Multiple source files have build rules usually share the same cFlags or tidyFlags.
+	// Define only one version in this module and share it in multiple build rules.
+	// To simplify the code, the shared variables are all named as $flags<nnn>.
+	shared := ctx.getSharedFlags()
+
+	// Share flags only when there are multiple files or tidy rules.
+	var hasMultipleRules = len(srcFiles) > 1 || flags.tidy
+
+	var shareFlags = func(kind string, flags string) string {
+		if !hasMultipleRules || len(flags) < 60 {
+			// Modules have long names and so do the module variables.
+			// It does not save space by replacing a short name with a long one.
+			return flags
+		}
+		mapKey := kind + flags
+		n, ok := shared.flagsMap[mapKey]
+		if !ok {
+			shared.numSharedFlags += 1
+			n = strconv.Itoa(shared.numSharedFlags)
+			shared.flagsMap[mapKey] = n
+			ctx.Variable(pctx, kind+n, flags)
+		}
+		return "$" + kind + n
+	}
 
 	for i, srcFile := range srcFiles {
 		objFile := android.ObjPathWithExt(ctx, subdir, srcFile, "o")
@@ -521,21 +588,7 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"asFlags": flags.globalYasmFlags + " " + flags.localYasmFlags,
-				},
-			})
-			continue
-		case ".rc":
-			ctx.Build(pctx, android.BuildParams{
-				Rule:        windres,
-				Description: "windres " + srcFile.Rel(),
-				Output:      objFile,
-				Input:       srcFile,
-				Implicits:   cFlagsDeps,
-				OrderOnly:   pathDeps,
-				Args: map[string]string{
-					"windresCmd": gccCmd(flags.toolchain, "windres"),
-					"flags":      flags.toolchain.WindresFlags(),
+					"asFlags": shareFlags("asFlags", flags.globalYasmFlags+" "+flags.localYasmFlags),
 				},
 			})
 			continue
@@ -603,8 +656,8 @@
 			Implicits:       cFlagsDeps,
 			OrderOnly:       pathDeps,
 			Args: map[string]string{
-				"cFlags": moduleFlags,
-				"ccCmd":  ccCmd,
+				"cFlags": shareFlags("cFlags", moduleFlags),
+				"ccCmd":  ccCmd, // short and not shared
 			},
 		})
 
@@ -619,34 +672,54 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags": moduleFlags,
+					"cFlags": shareFlags("cFlags", moduleFlags),
 				},
 			})
 			kytheFiles = append(kytheFiles, kytheFile)
 		}
 
-		if tidy {
+		//  Even with tidy, some src file could be skipped by noTidySrcsMap.
+		if tidy && !noTidySrcsMap[srcFile.String()] {
 			tidyFile := android.ObjPathWithExt(ctx, subdir, srcFile, "tidy")
+			tidyDepFile := android.ObjPathWithExt(ctx, subdir, srcFile, "tidy.dep")
 			tidyFiles = append(tidyFiles, tidyFile)
 
+			ruleDep := clangTidyDep
 			rule := clangTidy
 			if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CLANG_TIDY") {
+				ruleDep = clangTidyDepRE
 				rule = clangTidyRE
 			}
 
+			sharedCFlags := shareFlags("cFlags", moduleFlags)
+			srcRelPath := srcFile.Rel()
+
+			// Add the .tidy.d rule
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        ruleDep,
+				Description: "clang-tidy-dep " + srcRelPath,
+				Output:      tidyDepFile,
+				Input:       srcFile,
+				Implicits:   cFlagsDeps,
+				OrderOnly:   pathDeps,
+				Args: map[string]string{
+					"ccCmd":    ccCmd,
+					"cFlags":   sharedCFlags,
+					"tidyFile": tidyFile.String(),
+				},
+			})
+			// Add the .tidy rule with order only dependency on the .tidy.d file
 			ctx.Build(pctx, android.BuildParams{
 				Rule:        rule,
-				Description: "clang-tidy " + srcFile.Rel(),
+				Description: "clang-tidy " + srcRelPath,
 				Output:      tidyFile,
 				Input:       srcFile,
-				// We must depend on objFile, since clang-tidy doesn't
-				// support exporting dependencies.
-				Implicit:  objFile,
-				Implicits: cFlagsDeps,
-				OrderOnly: pathDeps,
+				Implicits:   cFlagsDeps,
+				OrderOnly:   append(android.Paths{}, tidyDepFile),
 				Args: map[string]string{
-					"cFlags":    moduleToolingFlags,
-					"tidyFlags": flags.tidyFlags,
+					"cFlags":    sharedCFlags,
+					"tidyFlags": shareFlags("tidyFlags", config.TidyFlagsForSrcFile(srcFile, flags.tidyFlags)),
+					"tidyVars":  tidyVars, // short and not shared
 				},
 			})
 		}
@@ -655,10 +728,8 @@
 			sAbiDumpFile := android.ObjPathWithExt(ctx, subdir, srcFile, "sdump")
 			sAbiDumpFiles = append(sAbiDumpFiles, sAbiDumpFile)
 
+			// TODO(b/226497964): dumpRule = sAbiDumpRE if USE_RBE and RBE_ABI_DUMPER are true.
 			dumpRule := sAbiDump
-			if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_ABI_DUMPER") {
-				dumpRule = sAbiDumpRE
-			}
 			ctx.Build(pctx, android.BuildParams{
 				Rule:        dumpRule,
 				Description: "header-abi-dumper " + srcFile.Rel(),
@@ -668,17 +739,22 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"cFlags":     moduleToolingFlags,
-					"exportDirs": flags.sAbiFlags,
+					"cFlags":     shareFlags("cFlags", moduleToolingFlags),
+					"exportDirs": shareFlags("exportDirs", flags.sAbiFlags),
 				},
 			})
 		}
 
 	}
 
+	var tidyDepFiles android.Paths
+	if flags.needTidyFiles {
+		tidyDepFiles = tidyFiles
+	}
 	return Objects{
 		objFiles:      objFiles,
 		tidyFiles:     tidyFiles,
+		tidyDepFiles:  tidyDepFiles,
 		coverageFiles: coverageFiles,
 		sAbiDumpFiles: sAbiDumpFiles,
 		kytheFiles:    kytheFiles,
@@ -688,12 +764,12 @@
 // Generate a rule for compiling multiple .o files to a static library (.a)
 func transformObjToStaticLib(ctx android.ModuleContext,
 	objFiles android.Paths, wholeStaticLibs android.Paths,
-	flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths) {
+	flags builderFlags, outputFile android.ModuleOutPath, deps android.Paths, validations android.Paths) {
 
 	arCmd := "${config.ClangBin}/llvm-ar"
 	arFlags := ""
 	if !ctx.Darwin() {
-		arFlags += " -format=gnu"
+		arFlags += " --format=gnu"
 	}
 
 	if len(wholeStaticLibs) == 0 {
@@ -703,6 +779,7 @@
 			Output:      outputFile,
 			Inputs:      objFiles,
 			Implicits:   deps,
+			Validations: validations,
 			Args: map[string]string{
 				"arFlags": "crsPD" + arFlags,
 				"arCmd":   arCmd,
@@ -730,8 +807,9 @@
 // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
 // and shared libraries, to a shared library (.so) or dynamic executable
 func transformObjToDynamicBinary(ctx android.ModuleContext,
-	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps android.Paths,
-	crtBegin, crtEnd android.OptionalPath, groupLate bool, flags builderFlags, outputFile android.WritablePath, implicitOutputs android.WritablePaths) {
+	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps, crtBegin, crtEnd android.Paths,
+	groupLate bool, flags builderFlags, outputFile android.WritablePath,
+	implicitOutputs android.WritablePaths, validations android.Paths) {
 
 	ldCmd := "${config.ClangBin}/clang++"
 
@@ -751,13 +829,7 @@
 		}
 	}
 
-	if flags.groupStaticLibs && !ctx.Darwin() && len(staticLibs) > 0 {
-		libFlagsList = append(libFlagsList, "-Wl,--start-group")
-	}
 	libFlagsList = append(libFlagsList, staticLibs.Strings()...)
-	if flags.groupStaticLibs && !ctx.Darwin() && len(staticLibs) > 0 {
-		libFlagsList = append(libFlagsList, "-Wl,--end-group")
-	}
 
 	if groupLate && !ctx.Darwin() && len(lateStaticLibs) > 0 {
 		libFlagsList = append(libFlagsList, "-Wl,--start-group")
@@ -778,18 +850,17 @@
 	deps = append(deps, staticLibs...)
 	deps = append(deps, lateStaticLibs...)
 	deps = append(deps, wholeStaticLibs...)
-	if crtBegin.Valid() {
-		deps = append(deps, crtBegin.Path(), crtEnd.Path())
-	}
+	deps = append(deps, crtBegin...)
+	deps = append(deps, crtEnd...)
 
 	rule := ld
 	args := map[string]string{
 		"ldCmd":         ldCmd,
-		"crtBegin":      crtBegin.String(),
+		"crtBegin":      strings.Join(crtBegin.Strings(), " "),
 		"libFlags":      strings.Join(libFlagsList, " "),
 		"extraLibFlags": flags.extraLibFlags,
 		"ldFlags":       flags.globalLdFlags + " " + flags.localLdFlags,
-		"crtEnd":        crtEnd.String(),
+		"crtEnd":        strings.Join(crtEnd.Strings(), " "),
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
 		rule = ldRE
@@ -805,6 +876,7 @@
 		Inputs:          objFiles,
 		Implicits:       deps,
 		OrderOnly:       sharedLibs,
+		Validations:     validations,
 		Args:            args,
 	})
 }
@@ -870,9 +942,10 @@
 	return outputFile
 }
 
-// sourceAbiDiff registers a build statement to compare linked sAbi dump files (.ldump).
+// sourceAbiDiff registers a build statement to compare linked sAbi dump files (.lsdump).
 func sourceAbiDiff(ctx android.ModuleContext, inputDump android.Path, referenceDump android.Path,
-	baseName, exportedHeaderFlags string, checkAllApis, isLlndk, isNdk, isVndkExt bool) android.OptionalPath {
+	baseName, exportedHeaderFlags string, diffFlags []string,
+	checkAllApis, isLlndk, isNdk, isVndkExt bool) android.OptionalPath {
 
 	outputFile := android.PathForModuleOut(ctx, baseName+".abidiff")
 	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
@@ -903,6 +976,8 @@
 	if isVndkExt {
 		extraFlags = append(extraFlags, "-allow-extensions")
 	}
+	// TODO(b/232891473): Simplify the above logic with diffFlags.
+	extraFlags = append(extraFlags, diffFlags...)
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        sAbiDiff,
@@ -922,8 +997,7 @@
 }
 
 // Generate a rule for extracting a table of contents from a shared library (.so)
-func transformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path,
-	outputFile android.WritablePath, flags builderFlags) {
+func TransformSharedObjectToToc(ctx android.ModuleContext, inputFile android.Path, outputFile android.WritablePath) {
 
 	var format string
 	if ctx.Darwin() {
@@ -1034,6 +1108,15 @@
 	})
 }
 
+func transformDarwinUniversalBinary(ctx android.ModuleContext, outputFile android.WritablePath, inputFiles ...android.Path) {
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        darwinLipo,
+		Description: "lipo " + outputFile.Base(),
+		Output:      outputFile,
+		Inputs:      inputFiles,
+	})
+}
+
 // Registers build statement to zip one or more coverage files.
 func transformCoverageFilesToZip(ctx android.ModuleContext,
 	inputs Objects, baseName string) android.OptionalPath {
@@ -1069,6 +1152,6 @@
 	})
 }
 
-func gccCmd(toolchain config.Toolchain, cmd string) string {
+func mingwCmd(toolchain config.Toolchain, cmd string) string {
 	return filepath.Join(toolchain.GccRoot(), "bin", toolchain.GccTriple()+"-"+cmd)
 }
diff --git a/cc/cc.go b/cc/cc.go
index c62fd6c..456b736 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -29,7 +29,9 @@
 
 	"android/soong/android"
 	"android/soong/cc/config"
+	"android/soong/fuzz"
 	"android/soong/genrule"
+	"android/soong/snapshot"
 )
 
 func init() {
@@ -53,31 +55,18 @@
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.TopDown("asan_deps", sanitizerDepsMutator(Asan))
-		ctx.BottomUp("asan", sanitizerMutator(Asan)).Parallel()
-
-		ctx.TopDown("hwasan_deps", sanitizerDepsMutator(Hwasan))
-		ctx.BottomUp("hwasan", sanitizerMutator(Hwasan)).Parallel()
-
-		ctx.TopDown("fuzzer_deps", sanitizerDepsMutator(Fuzzer))
-		ctx.BottomUp("fuzzer", sanitizerMutator(Fuzzer)).Parallel()
-
-		// cfi mutator shouldn't run before sanitizers that return true for
-		// incompatibleWithCfi()
-		ctx.TopDown("cfi_deps", sanitizerDepsMutator(cfi))
-		ctx.BottomUp("cfi", sanitizerMutator(cfi)).Parallel()
-
-		ctx.TopDown("scs_deps", sanitizerDepsMutator(scs))
-		ctx.BottomUp("scs", sanitizerMutator(scs)).Parallel()
-
-		ctx.TopDown("tsan_deps", sanitizerDepsMutator(tsan))
-		ctx.BottomUp("tsan", sanitizerMutator(tsan)).Parallel()
+		for _, san := range Sanitizers {
+			san.registerMutators(ctx)
+		}
 
 		ctx.TopDown("sanitize_runtime_deps", sanitizerRuntimeDepsMutator).Parallel()
 		ctx.BottomUp("sanitize_runtime", sanitizerRuntimeMutator).Parallel()
 
 		ctx.BottomUp("coverage", coverageMutator).Parallel()
 
+		ctx.TopDown("afdo_deps", afdoDepsMutator)
+		ctx.BottomUp("afdo", afdoMutator).Parallel()
+
 		ctx.TopDown("lto_deps", ltoDepsMutator)
 		ctx.BottomUp("lto", ltoMutator).Parallel()
 
@@ -109,6 +98,7 @@
 
 	// Used for data dependencies adjacent to tests
 	DataLibs []string
+	DataBins []string
 
 	// Used by DepsMutator to pass system_shared_libs information to check_elf_file.py.
 	SystemSharedLibs []string
@@ -126,11 +116,10 @@
 
 	ReexportGeneratedHeaders []string
 
-	CrtBegin, CrtEnd string
+	CrtBegin, CrtEnd []string
 
 	// Used for host bionic
-	LinkerFlagsFile string
-	DynamicLinker   string
+	DynamicLinker string
 
 	// List of libs that need to be excluded for APEX variant
 	ExcludeLibsForApex []string
@@ -177,13 +166,14 @@
 	ReexportedDeps             android.Paths
 
 	// Paths to crt*.o files
-	CrtBegin, CrtEnd android.OptionalPath
-
-	// Path to the file container flags to use with the linker
-	LinkerFlagsFile android.OptionalPath
+	CrtBegin, CrtEnd android.Paths
 
 	// Path to the dynamic linker binary
 	DynamicLinker android.OptionalPath
+
+	// For Darwin builds, the path to the second architecture's output that should
+	// be combined with this architectures's output into a FAT MachO file.
+	DarwinSecondArchOutput android.OptionalPath
 }
 
 // LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module
@@ -220,11 +210,12 @@
 	// These must be after any module include flags, which will be in CommonFlags.
 	SystemIncludeFlags []string
 
-	Toolchain    config.Toolchain
-	Tidy         bool // True if clang-tidy is enabled.
-	GcovCoverage bool // True if coverage files should be generated.
-	SAbiDump     bool // True if header abi dumps should be generated.
-	EmitXrefs    bool // If true, generate Ninja rules to generate emitXrefs input files for Kythe
+	Toolchain     config.Toolchain
+	Tidy          bool // True if ninja .tidy rules should be generated.
+	NeedTidyFiles bool // True if module link should depend on .tidy files
+	GcovCoverage  bool // True if coverage files should be generated.
+	SAbiDump      bool // True if header abi dumps should be generated.
+	EmitXrefs     bool // If true, generate Ninja rules to generate emitXrefs input files for Kythe
 
 	// The instruction set required for clang ("arm" or "thumb").
 	RequiredInstructionSet string
@@ -236,8 +227,6 @@
 
 	// True if .s files should be processed with the c preprocessor.
 	AssemblerWithCpp bool
-	// True if static libraries should be grouped (using `-Wl,--start-group` and `-Wl,--end-group`).
-	GroupStaticLibs bool
 
 	proto            android.ProtoFlags
 	protoC           bool // Whether to use C instead of C++
@@ -290,9 +279,15 @@
 	// Set by DepsMutator.
 	AndroidMkSystemSharedLibs []string `blueprint:"mutated"`
 
+	// The name of the image this module is built for, suffixed with a '.'
 	ImageVariationPrefix string `blueprint:"mutated"`
-	VndkVersion          string `blueprint:"mutated"`
-	SubName              string `blueprint:"mutated"`
+
+	// The VNDK version this module is built against. If empty, the module is not
+	// build against the VNDK.
+	VndkVersion string `blueprint:"mutated"`
+
+	// Suffix for the name of Android.mk entries generated by this module
+	SubName string `blueprint:"mutated"`
 
 	// *.logtags files, to combine together in order to generate the /system/etc/event-log-tags
 	// file
@@ -315,12 +310,15 @@
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
-	// Set by imageMutator
-	CoreVariantNeeded          bool     `blueprint:"mutated"`
-	RamdiskVariantNeeded       bool     `blueprint:"mutated"`
-	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
-	RecoveryVariantNeeded      bool     `blueprint:"mutated"`
-	ExtraVariants              []string `blueprint:"mutated"`
+	// Used by imageMutator, set by ImageMutatorBegin()
+	CoreVariantNeeded          bool `blueprint:"mutated"`
+	RamdiskVariantNeeded       bool `blueprint:"mutated"`
+	VendorRamdiskVariantNeeded bool `blueprint:"mutated"`
+	RecoveryVariantNeeded      bool `blueprint:"mutated"`
+
+	// A list of variations for the "image" mutator of the form
+	//<image name> '.' <version char>, for example, 'vendor.S'
+	ExtraVersionedImageVariations []string `blueprint:"mutated"`
 
 	// Allows this module to use non-APEX version of libraries. Useful
 	// for building binaries that are started before APEXes are activated.
@@ -332,9 +330,10 @@
 
 	// Used by vendor snapshot to record dependencies from snapshot modules.
 	SnapshotSharedLibs  []string `blueprint:"mutated"`
+	SnapshotStaticLibs  []string `blueprint:"mutated"`
 	SnapshotRuntimeLibs []string `blueprint:"mutated"`
 
-	Installable *bool
+	Installable *bool `android:"arch_variant"`
 
 	// Set by factories of module types that can only be referenced from variants compiled against
 	// the SDK.
@@ -364,6 +363,24 @@
 	// can depend on libraries that are not exported by the APEXes and use private symbols
 	// from the exported libraries.
 	Test_for []string `android:"arch_variant"`
+
+	Target struct {
+		Platform struct {
+			// List of modules required by the core variant.
+			Required []string `android:"arch_variant"`
+
+			// List of modules not required by the core variant.
+			Exclude_required []string `android:"arch_variant"`
+		} `android:"arch_variant"`
+
+		Recovery struct {
+			// List of modules required by the recovery variant.
+			Required []string `android:"arch_variant"`
+
+			// List of modules not required by the recovery variant.
+			Exclude_required []string `android:"arch_variant"`
+		} `android:"arch_variant"`
+	} `android:"arch_variant"`
 }
 
 type VendorProperties struct {
@@ -488,6 +505,7 @@
 	selectedStl() string
 	baseModuleName() string
 	getVndkExtendsModuleName() string
+	isAfdoCompile() bool
 	isPgoCompile() bool
 	isNDKStubLibrary() bool
 	useClangLld(actx ModuleContext) bool
@@ -500,6 +518,12 @@
 	directlyInAnyApex() bool
 	isPreventInstall() bool
 	isCfiAssemblySupportEnabled() bool
+	getSharedFlags() *SharedFlags
+}
+
+type SharedFlags struct {
+	numSharedFlags int
+	flagsMap       map[string]string
 }
 
 type ModuleContext interface {
@@ -520,15 +544,12 @@
 // feature represents additional (optional) steps to building cc-related modules, such as invocation
 // of clang-tidy.
 type feature interface {
-	begin(ctx BaseModuleContext)
-	deps(ctx DepsContext, deps Deps) Deps
 	flags(ctx ModuleContext, flags Flags) Flags
 	props() []interface{}
 }
 
 // compiler is the interface for a compiler helper object. Different module decorators may implement
-// this helper differently. For example, compiling a `cc_library` may use a different build
-// statement than building a `toolchain_library`.
+// this helper differently.
 type compiler interface {
 	compilerInit(ctx BaseModuleContext)
 	compilerDeps(ctx DepsContext, deps Deps) Deps
@@ -580,17 +601,7 @@
 	hostToolPath() android.OptionalPath
 	relativeInstallPath() string
 	makeUninstallable(mod *Module)
-}
-
-// bazelHandler is the interface for a helper object related to deferring to Bazel for
-// processing a module (during Bazel mixed builds). Individual module types should define
-// their own bazel handler if they support deferring to Bazel.
-type bazelHandler interface {
-	// Issue query to Bazel to retrieve information about Bazel's view of the current module.
-	// If Bazel returns this information, set module properties on the current module to reflect
-	// the returned information.
-	// Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
-	generateBazelBuildActions(ctx android.ModuleContext, label string) bool
+	installInRoot() bool
 }
 
 type xref interface {
@@ -685,6 +696,15 @@
 	return d.Kind == staticLibraryDependency
 }
 
+func (d libraryDependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.shared() {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	}
+	return nil
+}
+
+var _ android.LicenseAnnotationsDependencyTag = libraryDependencyTag{}
+
 // InstallDepNeeded returns true for shared libraries so that shared library dependencies of
 // binaries or other shared libraries are installed as dependencies.
 func (d libraryDependencyTag) InstallDepNeeded() bool {
@@ -717,16 +737,15 @@
 	genHeaderDepTag       = dependencyTag{name: "gen header"}
 	genHeaderExportDepTag = dependencyTag{name: "gen header export"}
 	objDepTag             = dependencyTag{name: "obj"}
-	linkerFlagsDepTag     = dependencyTag{name: "linker flags file"}
 	dynamicLinkerDepTag   = installDependencyTag{name: "dynamic linker"}
 	reuseObjTag           = dependencyTag{name: "reuse objects"}
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
 	dataLibDepTag         = dependencyTag{name: "data lib"}
+	dataBinDepTag         = dependencyTag{name: "data bin"}
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
-	llndkStubDepTag       = dependencyTag{name: "llndk stub"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
@@ -768,18 +787,18 @@
 // members of the cc.Module to this decorator. Thus, a cc_binary module has custom linker and
 // installer logic.
 type Module struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.ApexModuleBase
+	fuzz.FuzzModule
+
 	android.SdkBase
 	android.BazelModuleBase
 
-	Properties       BaseProperties
 	VendorProperties VendorProperties
+	Properties       BaseProperties
 
 	// initialize before calling Init
-	hod      android.HostOrDeviceSupported
-	multilib android.Multilib
+	hod       android.HostOrDeviceSupported
+	multilib  android.Multilib
+	bazelable bool
 
 	// Allowable SdkMemberTypes of this module type.
 	sdkMemberTypes []android.SdkMemberType
@@ -792,7 +811,7 @@
 	compiler     compiler
 	linker       linker
 	installer    installer
-	bazelHandler bazelHandler
+	bazelHandler android.BazelHandler
 
 	features []feature
 	stl      *stl
@@ -801,6 +820,7 @@
 	sabi     *sabi
 	vndkdep  *vndkdep
 	lto      *lto
+	afdo     *afdo
 	pgo      *pgo
 
 	library libraryInterface
@@ -814,12 +834,19 @@
 	// Flags used to compile this module
 	flags Flags
 
+	// Shared flags among build rules of this module
+	sharedFlags SharedFlags
+
 	// only non-nil when this is a shared library that reuses the objects of a static library
 	staticAnalogue *StaticLibraryInfo
 
 	makeLinkType string
 	// Kythe (source file indexer) paths for this compilation module
 	kytheFiles android.Paths
+	// Object .o file output paths for this compilation module
+	objFiles android.Paths
+	// Tidy .tidy file output paths for this compilation module
+	tidyFiles android.Paths
 
 	// For apex variants, this is set as apex.min_sdk_version
 	apexSdkVersion android.ApiLevel
@@ -827,6 +854,64 @@
 	hideApexVariantFromMake bool
 }
 
+func (c *Module) AddJSONData(d *map[string]interface{}) {
+	var hasAidl, hasLex, hasProto, hasRenderscript, hasSysprop, hasWinMsg, hasYacc bool
+	if b, ok := c.compiler.(*baseCompiler); ok {
+		hasAidl = b.hasSrcExt(".aidl")
+		hasLex = b.hasSrcExt(".l") || b.hasSrcExt(".ll")
+		hasProto = b.hasSrcExt(".proto")
+		hasRenderscript = b.hasSrcExt(".rscript") || b.hasSrcExt(".fs")
+		hasSysprop = b.hasSrcExt(".sysprop")
+		hasWinMsg = b.hasSrcExt(".mc")
+		hasYacc = b.hasSrcExt(".y") || b.hasSrcExt(".yy")
+	}
+	c.AndroidModuleBase().AddJSONData(d)
+	(*d)["Cc"] = map[string]interface{}{
+		"SdkVersion":             c.SdkVersion(),
+		"MinSdkVersion":          c.MinSdkVersion(),
+		"VndkVersion":            c.VndkVersion(),
+		"ProductSpecific":        c.ProductSpecific(),
+		"SocSpecific":            c.SocSpecific(),
+		"DeviceSpecific":         c.DeviceSpecific(),
+		"InProduct":              c.InProduct(),
+		"InVendor":               c.InVendor(),
+		"InRamdisk":              c.InRamdisk(),
+		"InVendorRamdisk":        c.InVendorRamdisk(),
+		"InRecovery":             c.InRecovery(),
+		"VendorAvailable":        c.VendorAvailable(),
+		"ProductAvailable":       c.ProductAvailable(),
+		"RamdiskAvailable":       c.RamdiskAvailable(),
+		"VendorRamdiskAvailable": c.VendorRamdiskAvailable(),
+		"RecoveryAvailable":      c.RecoveryAvailable(),
+		"OdmAvailable":           c.OdmAvailable(),
+		"InstallInData":          c.InstallInData(),
+		"InstallInRamdisk":       c.InstallInRamdisk(),
+		"InstallInSanitizerDir":  c.InstallInSanitizerDir(),
+		"InstallInVendorRamdisk": c.InstallInVendorRamdisk(),
+		"InstallInRecovery":      c.InstallInRecovery(),
+		"InstallInRoot":          c.InstallInRoot(),
+		"IsVndk":                 c.IsVndk(),
+		"IsVndkExt":              c.IsVndkExt(),
+		"IsVndkPrivate":          c.IsVndkPrivate(),
+		"IsVndkSp":               c.IsVndkSp(),
+		"IsLlndk":                c.IsLlndk(),
+		"IsLlndkPublic":          c.IsLlndkPublic(),
+		"IsSnapshotLibrary":      c.IsSnapshotLibrary(),
+		"IsSnapshotPrebuilt":     c.IsSnapshotPrebuilt(),
+		"IsVendorPublicLibrary":  c.IsVendorPublicLibrary(),
+		"ApexSdkVersion":         c.apexSdkVersion,
+		"TestFor":                c.TestFor(),
+		"AidlSrcs":               hasAidl,
+		"LexSrcs":                hasLex,
+		"ProtoSrcs":              hasProto,
+		"RenderscriptSrcs":       hasRenderscript,
+		"SyspropSrcs":            hasSysprop,
+		"WinMsgSrcs":             hasWinMsg,
+		"YaccSrsc":               hasYacc,
+		"OnlyCSrcs":              !(hasAidl || hasLex || hasProto || hasRenderscript || hasSysprop || hasWinMsg || hasYacc),
+	}
+}
+
 func (c *Module) SetPreventInstall() {
 	c.Properties.PreventInstall = true
 }
@@ -839,6 +924,18 @@
 	return c.Properties.HideFromMake
 }
 
+func (c *Module) RequiredModuleNames() []string {
+	required := android.CopyOf(c.ModuleBase.RequiredModuleNames())
+	if c.ImageVariation().Variation == android.CoreVariation {
+		required = append(required, c.Properties.Target.Platform.Required...)
+		required = removeListFromList(required, c.Properties.Target.Platform.Exclude_required)
+	} else if c.InRecovery() {
+		required = append(required, c.Properties.Target.Recovery.Required...)
+		required = removeListFromList(required, c.Properties.Target.Recovery.Exclude_required)
+	}
+	return android.FirstUniqueStrings(required)
+}
+
 func (c *Module) Toc() android.OptionalPath {
 	if c.linker != nil {
 		if library, ok := c.linker.(libraryInterface); ok {
@@ -882,13 +979,6 @@
 	return ""
 }
 
-func (c *Module) ToolchainLibrary() bool {
-	if _, ok := c.linker.(*toolchainLibraryDecorator); ok {
-		return true
-	}
-	return false
-}
-
 func (c *Module) NdkPrebuiltStl() bool {
 	if _, ok := c.linker.(*ndkPrebuiltStlLinker); ok {
 		return true
@@ -911,16 +1001,17 @@
 	return String(c.Properties.Min_sdk_version)
 }
 
-func (c *Module) SplitPerApiLevel() bool {
-	if !c.canUseSdk() {
-		return false
-	}
+func (c *Module) isCrt() bool {
 	if linker, ok := c.linker.(*objectLinker); ok {
 		return linker.isCrt()
 	}
 	return false
 }
 
+func (c *Module) SplitPerApiLevel() bool {
+	return c.canUseSdk() && c.isCrt()
+}
+
 func (c *Module) AlwaysSdk() bool {
 	return c.Properties.AlwaysSdk || Bool(c.Properties.Sdk_variant_only)
 }
@@ -1059,6 +1150,9 @@
 	if c.lto != nil {
 		c.AddProperties(c.lto.props()...)
 	}
+	if c.afdo != nil {
+		c.AddProperties(c.afdo.props()...)
+	}
 	if c.pgo != nil {
 		c.AddProperties(c.pgo.props()...)
 	}
@@ -1067,7 +1161,9 @@
 	}
 
 	android.InitAndroidArchModule(c, c.hod, c.multilib)
-	android.InitBazelModule(c)
+	if c.bazelable {
+		android.InitBazelModule(c)
+	}
 	android.InitApexModule(c)
 	android.InitSdkAwareModule(c)
 	android.InitDefaultableModule(c)
@@ -1075,17 +1171,6 @@
 	return c
 }
 
-// Returns true for dependency roots (binaries)
-// TODO(ccross): also handle dlopenable libraries
-func (c *Module) IsDependencyRoot() bool {
-	if root, ok := c.linker.(interface {
-		isDependencyRoot() bool
-	}); ok {
-		return root.isDependencyRoot()
-	}
-	return false
-}
-
 func (c *Module) UseVndk() bool {
 	return c.Properties.VndkVersion != ""
 }
@@ -1133,6 +1218,17 @@
 	return c.VendorProperties.IsVendorPublicLibrary
 }
 
+func (c *Module) IsVndkPrebuiltLibrary() bool {
+	if _, ok := c.linker.(*vndkPrebuiltLibraryDecorator); ok {
+		return true
+	}
+	return false
+}
+
+func (c *Module) SdkAndPlatformVariantVisibleToMake() bool {
+	return c.Properties.SdkAndPlatformVariantVisibleToMake
+}
+
 func (c *Module) HasLlndkStubs() bool {
 	lib := moduleLibraryInterface(c)
 	return lib != nil && lib.hasLLNDKStubs()
@@ -1175,6 +1271,13 @@
 	return false
 }
 
+func (c *Module) isAfdoCompile() bool {
+	if afdo := c.afdo; afdo != nil {
+		return afdo.Properties.AfdoTarget != nil
+	}
+	return false
+}
+
 func (c *Module) isPgoCompile() bool {
 	if pgo := c.pgo; pgo != nil {
 		return pgo.Properties.PgoCompile
@@ -1255,7 +1358,7 @@
 	return name
 }
 
-func (c *Module) bootstrap() bool {
+func (c *Module) Bootstrap() bool {
 	return Bool(c.Properties.Bootstrap)
 }
 
@@ -1268,8 +1371,8 @@
 }
 
 func (c *Module) IsSnapshotPrebuilt() bool {
-	if p, ok := c.linker.(snapshotInterface); ok {
-		return p.isSnapshotPrebuilt()
+	if p, ok := c.linker.(SnapshotInterface); ok {
+		return p.IsSnapshotPrebuilt()
 	}
 	return false
 }
@@ -1291,7 +1394,7 @@
 }
 
 func InstallToBootstrap(name string, config android.Config) bool {
-	if name == "libclang_rt.hwasan-aarch64-android" {
+	if name == "libclang_rt.hwasan" {
 		return true
 	}
 	return isBionic(name)
@@ -1306,6 +1409,10 @@
 		Bool(c.sanitize.Properties.Sanitize.Config.Cfi_assembly_support)
 }
 
+func (c *Module) InstallInRoot() bool {
+	return c.installer != nil && c.installer.installInRoot()
+}
+
 type baseModuleContext struct {
 	android.BaseModuleContext
 	moduleContextImpl
@@ -1389,12 +1496,20 @@
 	// create versioned variants for. For example, if min_sdk_version is 16, then sdk variant of
 	// the crt object has local variants of 16, 17, ..., up to the latest version. sdk_version
 	// and min_sdk_version properties of the variants are set to the corresponding version
-	// numbers. However, the platform (non-sdk) variant of the crt object is left untouched.
-	// min_sdk_version: 16 doesn't actually mean that the platform variant has to support such
-	// an old version. Since the variant is for the platform, it's preferred to target the
-	// latest version.
-	if ctx.mod.SplitPerApiLevel() && !ctx.isSdkVariant() {
-		ver = strconv.Itoa(android.FutureApiLevelInt)
+	// numbers. However, the non-sdk variant (for apex or platform) of the crt object is left
+	// untouched.  min_sdk_version: 16 doesn't actually mean that the non-sdk variant has to
+	// support such an old version. The version is set to the later version in case when the
+	// non-sdk variant is for the platform, or the min_sdk_version of the containing APEX if
+	// it's for an APEX.
+	if ctx.mod.isCrt() && !ctx.isSdkVariant() {
+		if ctx.isForPlatform() {
+			ver = strconv.Itoa(android.FutureApiLevelInt)
+		} else { // for apex
+			ver = ctx.apexSdkVersion().String()
+			if ver == "" { // in case when min_sdk_version was not set by the APEX
+				ver = ctx.sdkVersion()
+			}
+		}
 	}
 
 	// Also make sure that minSdkVersion is not greater than sdkVersion, if they are both numbers
@@ -1440,6 +1555,10 @@
 	return ctx.mod.IsVndk()
 }
 
+func (ctx *moduleContextImpl) isAfdoCompile() bool {
+	return ctx.mod.isAfdoCompile()
+}
+
 func (ctx *moduleContextImpl) isPgoCompile() bool {
 	return ctx.mod.isPgoCompile()
 }
@@ -1496,7 +1615,7 @@
 }
 
 func (ctx *moduleContextImpl) bootstrap() bool {
-	return ctx.mod.bootstrap()
+	return ctx.mod.Bootstrap()
 }
 
 func (ctx *moduleContextImpl) nativeCoverage() bool {
@@ -1511,6 +1630,15 @@
 	return ctx.mod.Properties.PreventInstall
 }
 
+func (ctx *moduleContextImpl) getSharedFlags() *SharedFlags {
+	shared := &ctx.mod.sharedFlags
+	if shared.flagsMap == nil {
+		shared.numSharedFlags = 0
+		shared.flagsMap = make(map[string]string)
+	}
+	return shared
+}
+
 func (ctx *moduleContextImpl) isCfiAssemblySupportEnabled() bool {
 	return ctx.mod.isCfiAssemblySupportEnabled()
 }
@@ -1533,6 +1661,7 @@
 	module.sabi = &sabi{}
 	module.vndkdep = &vndkdep{}
 	module.lto = &lto{}
+	module.afdo = &afdo{}
 	module.pgo = &pgo{}
 	return module
 }
@@ -1581,7 +1710,7 @@
 	return nil
 }
 
-func (c *Module) getNameSuffixWithVndkVersion(ctx android.ModuleContext) string {
+func getNameSuffixWithVndkVersion(ctx android.ModuleContext, c LinkableInterface) string {
 	// Returns the name suffix for product and vendor variants. If the VNDK version is not
 	// "current", it will append the VNDK version to the name suffix.
 	var vndkVersion string
@@ -1593,7 +1722,7 @@
 			return ""
 		}
 		vndkVersion = ctx.DeviceConfig().ProductVndkVersion()
-		nameSuffix = productSuffix
+		nameSuffix = ProductSuffix
 	} else {
 		vndkVersion = ctx.DeviceConfig().VndkVersion()
 		nameSuffix = VendorSuffix
@@ -1601,19 +1730,19 @@
 	if vndkVersion == "current" {
 		vndkVersion = ctx.DeviceConfig().PlatformVndkVersion()
 	}
-	if c.Properties.VndkVersion != vndkVersion && c.Properties.VndkVersion != "" {
+	if c.VndkVersion() != vndkVersion && c.VndkVersion() != "" {
 		// add version suffix only if the module is using different vndk version than the
 		// version in product or vendor partition.
-		nameSuffix += "." + c.Properties.VndkVersion
+		nameSuffix += "." + c.VndkVersion()
 	}
 	return nameSuffix
 }
 
-func (c *Module) setSubnameProperty(actx android.ModuleContext) {
-	c.Properties.SubName = ""
+func GetSubnameProperty(actx android.ModuleContext, c LinkableInterface) string {
+	var subName = ""
 
 	if c.Target().NativeBridge == android.NativeBridgeEnabled {
-		c.Properties.SubName += nativeBridgeSuffix
+		subName += NativeBridgeSuffix
 	}
 
 	llndk := c.IsLlndk()
@@ -1621,33 +1750,46 @@
 		// .vendor.{version} suffix is added for vendor variant or .product.{version} suffix is
 		// added for product variant only when we have vendor and product variants with core
 		// variant. The suffix is not added for vendor-only or product-only module.
-		c.Properties.SubName += c.getNameSuffixWithVndkVersion(actx)
+		subName += getNameSuffixWithVndkVersion(actx, c)
 	} else if c.IsVendorPublicLibrary() {
-		c.Properties.SubName += vendorPublicLibrarySuffix
-	} else if _, ok := c.linker.(*vndkPrebuiltLibraryDecorator); ok {
+		subName += vendorPublicLibrarySuffix
+	} else if c.IsVndkPrebuiltLibrary() {
 		// .vendor suffix is added for backward compatibility with VNDK snapshot whose names with
 		// such suffixes are already hard-coded in prebuilts/vndk/.../Android.bp.
-		c.Properties.SubName += VendorSuffix
+		subName += VendorSuffix
 	} else if c.InRamdisk() && !c.OnlyInRamdisk() {
-		c.Properties.SubName += ramdiskSuffix
+		subName += RamdiskSuffix
 	} else if c.InVendorRamdisk() && !c.OnlyInVendorRamdisk() {
-		c.Properties.SubName += VendorRamdiskSuffix
+		subName += VendorRamdiskSuffix
 	} else if c.InRecovery() && !c.OnlyInRecovery() {
-		c.Properties.SubName += recoverySuffix
-	} else if c.IsSdkVariant() && (c.Properties.SdkAndPlatformVariantVisibleToMake || c.SplitPerApiLevel()) {
-		c.Properties.SubName += sdkSuffix
+		subName += RecoverySuffix
+	} else if c.IsSdkVariant() && (c.SdkAndPlatformVariantVisibleToMake() || c.SplitPerApiLevel()) {
+		subName += sdkSuffix
 		if c.SplitPerApiLevel() {
-			c.Properties.SubName += "." + c.SdkVersion()
+			subName += "." + c.SdkVersion()
 		}
 	}
+
+	return subName
 }
 
 // Returns true if Bazel was successfully used for the analysis of this module.
 func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
-	bazelModuleLabel := c.GetBazelLabel(actx, c)
+	var bazelModuleLabel string
+	if c.typ() == fullLibrary && c.static() {
+		// cc_library is a special case in bp2build; two targets are generated -- one for each
+		// of the shared and static variants. The shared variant keeps the module name, but the
+		// static variant uses a different suffixed name.
+		bazelModuleLabel = bazelLabelForStaticModule(actx, c)
+	} else {
+		bazelModuleLabel = c.GetBazelLabel(actx, c)
+	}
+
 	bazelActionsUsed := false
+	// Mixed builds mode is disabled for modules outside of device OS.
+	// TODO(b/200841190): Support non-device OS in mixed builds.
 	if c.MixedBuildsEnabled(actx) && c.bazelHandler != nil {
-		bazelActionsUsed = c.bazelHandler.generateBazelBuildActions(actx, bazelModuleLabel)
+		bazelActionsUsed = c.bazelHandler.GenerateBazelBuildActions(actx, bazelModuleLabel)
 	}
 	return bazelActionsUsed
 }
@@ -1666,7 +1808,7 @@
 		return
 	}
 
-	c.setSubnameProperty(actx)
+	c.Properties.SubName = GetSubnameProperty(actx, c)
 	apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if !apexInfo.IsForPlatform() {
 		c.hideApexVariantFromMake = true
@@ -1674,10 +1816,6 @@
 
 	c.makeLinkType = GetMakeLinkType(actx, c)
 
-	if c.maybeGenerateBazelActions(actx) {
-		return
-	}
-
 	ctx := &moduleContext{
 		ModuleContext: actx,
 		moduleContextImpl: moduleContextImpl{
@@ -1686,6 +1824,11 @@
 	}
 	ctx.ctx = ctx
 
+	if c.maybeGenerateBazelActions(actx) {
+		c.maybeInstall(ctx, apexInfo)
+		return
+	}
+
 	deps := c.depsToPaths(ctx)
 	if ctx.Failed() {
 		return
@@ -1717,6 +1860,9 @@
 	if c.lto != nil {
 		flags = c.lto.flags(ctx, flags)
 	}
+	if c.afdo != nil {
+		flags = c.afdo.flags(ctx, flags)
+	}
 	if c.pgo != nil {
 		flags = c.pgo.flags(ctx, flags)
 	}
@@ -1748,15 +1894,6 @@
 
 	flags.AssemblerWithCpp = inList("-xassembler-with-cpp", flags.Local.AsFlags)
 
-	// Optimization to reduce size of build.ninja
-	// Replace the long list of flags for each file with a module-local variable
-	ctx.Variable(pctx, "cflags", strings.Join(flags.Local.CFlags, " "))
-	ctx.Variable(pctx, "cppflags", strings.Join(flags.Local.CppFlags, " "))
-	ctx.Variable(pctx, "asflags", strings.Join(flags.Local.AsFlags, " "))
-	flags.Local.CFlags = []string{"$cflags"}
-	flags.Local.CppFlags = []string{"$cppflags"}
-	flags.Local.AsFlags = []string{"$asflags"}
-
 	var objs Objects
 	if c.compiler != nil {
 		objs = c.compiler.compile(ctx, flags, deps)
@@ -1764,6 +1901,8 @@
 			return
 		}
 		c.kytheFiles = objs.kytheFiles
+		c.objFiles = objs.objFiles
+		c.tidyFiles = objs.tidyFiles
 	}
 
 	if c.linker != nil {
@@ -1773,19 +1912,7 @@
 		}
 		c.outputFile = android.OptionalPathForPath(outputFile)
 
-		// If a lib is directly included in any of the APEXes or is not available to the
-		// platform (which is often the case when the stub is provided as a prebuilt),
-		// unhide the stubs variant having the latest version gets visible to make. In
-		// addition, the non-stubs variant is renamed to <libname>.bootstrap. This is to
-		// force anything in the make world to link against the stubs library.  (unless it
-		// is explicitly referenced via .bootstrap suffix or the module is marked with
-		// 'bootstrap: true').
-		if c.HasStubsVariants() && c.NotInPlatform() && !c.InRamdisk() &&
-			!c.InRecovery() && !c.UseVndk() && !c.static() && !c.isCoverageVariant() &&
-			c.IsStubs() && !c.InVendorRamdisk() {
-			c.Properties.HideFromMake = false // unhide
-			// Note: this is still non-installable
-		}
+		c.maybeUnhideFromMake()
 
 		// glob exported headers for snapshot, if BOARD_VNDK_VERSION is current or
 		// RECOVERY_SNAPSHOT_VERSION is current.
@@ -1796,7 +1923,27 @@
 		}
 	}
 
-	if !proptools.BoolDefault(c.Properties.Installable, true) {
+	c.maybeInstall(ctx, apexInfo)
+}
+
+func (c *Module) maybeUnhideFromMake() {
+	// If a lib is directly included in any of the APEXes or is not available to the
+	// platform (which is often the case when the stub is provided as a prebuilt),
+	// unhide the stubs variant having the latest version gets visible to make. In
+	// addition, the non-stubs variant is renamed to <libname>.bootstrap. This is to
+	// force anything in the make world to link against the stubs library.  (unless it
+	// is explicitly referenced via .bootstrap suffix or the module is marked with
+	// 'bootstrap: true').
+	if c.HasStubsVariants() && c.NotInPlatform() && !c.InRamdisk() &&
+		!c.InRecovery() && !c.UseVndk() && !c.static() && !c.isCoverageVariant() &&
+		c.IsStubs() && !c.InVendorRamdisk() {
+		c.Properties.HideFromMake = false // unhide
+		// Note: this is still non-installable
+	}
+}
+
+func (c *Module) maybeInstall(ctx ModuleContext, apexInfo android.ApexInfo) {
+	if !proptools.BoolDefault(c.Installable(), true) {
 		// If the module has been specifically configure to not be installed then
 		// hide from make as otherwise it will break when running inside make
 		// as the output path to install will not be specified. Not all uninstallable
@@ -1840,21 +1987,15 @@
 	if c.coverage != nil {
 		c.coverage.begin(ctx)
 	}
-	if c.sabi != nil {
-		c.sabi.begin(ctx)
-	}
-	if c.vndkdep != nil {
-		c.vndkdep.begin(ctx)
-	}
 	if c.lto != nil {
 		c.lto.begin(ctx)
 	}
+	if c.afdo != nil {
+		c.afdo.begin(ctx)
+	}
 	if c.pgo != nil {
 		c.pgo.begin(ctx)
 	}
-	for _, feature := range c.features {
-		feature.begin(ctx)
-	}
 	if ctx.useSdk() && c.IsSdkVariant() {
 		version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion())
 		if err != nil {
@@ -1878,24 +2019,9 @@
 	if c.stl != nil {
 		deps = c.stl.deps(ctx, deps)
 	}
-	if c.sanitize != nil {
-		deps = c.sanitize.deps(ctx, deps)
-	}
 	if c.coverage != nil {
 		deps = c.coverage.deps(ctx, deps)
 	}
-	if c.sabi != nil {
-		deps = c.sabi.deps(ctx, deps)
-	}
-	if c.vndkdep != nil {
-		deps = c.vndkdep.deps(ctx, deps)
-	}
-	if c.lto != nil {
-		deps = c.lto.deps(ctx, deps)
-	}
-	for _, feature := range c.features {
-		deps = feature.deps(ctx, deps)
-	}
 
 	deps.WholeStaticLibs = android.LastUniqueStrings(deps.WholeStaticLibs)
 	deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs)
@@ -1985,15 +2111,19 @@
 	}
 }
 
-func (c *Module) addSharedLibDependenciesWithVersions(ctx android.BottomUpMutatorContext,
-	variations []blueprint.Variation, depTag libraryDependencyTag, name, version string, far bool) {
+func AddSharedLibDependenciesWithVersions(ctx android.BottomUpMutatorContext, mod LinkableInterface,
+	variations []blueprint.Variation, depTag blueprint.DependencyTag, name, version string, far bool) {
 
 	variations = append([]blueprint.Variation(nil), variations...)
 
-	if version != "" && CanBeOrLinkAgainstVersionVariants(c) {
+	if version != "" && CanBeOrLinkAgainstVersionVariants(mod) {
 		// Version is explicitly specified. i.e. libFoo#30
 		variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
-		depTag.explicitlyVersioned = true
+		if tag, ok := depTag.(libraryDependencyTag); ok {
+			tag.explicitlyVersioned = true
+		} else {
+			panic(fmt.Errorf("Unexpected dependency tag: %T", depTag))
+		}
 	}
 
 	if far {
@@ -2003,6 +2133,74 @@
 	}
 }
 
+func GetSnapshot(c LinkableInterface, snapshotInfo **SnapshotInfo, actx android.BottomUpMutatorContext) SnapshotInfo {
+	// Only device modules with BOARD_VNDK_VERSION uses snapshot.  Others use the zero value of
+	// SnapshotInfo, which provides no mappings.
+	if *snapshotInfo == nil && c.Device() {
+		// Only retrieve the snapshot on demand in order to avoid circular dependencies
+		// between the modules in the snapshot and the snapshot itself.
+		var snapshotModule []blueprint.Module
+		if c.InVendor() && c.VndkVersion() == actx.DeviceConfig().VndkVersion() {
+			snapshotModule = actx.AddVariationDependencies(nil, nil, "vendor_snapshot")
+		} else if recoverySnapshotVersion := actx.DeviceConfig().RecoverySnapshotVersion(); recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" && c.InRecovery() {
+			snapshotModule = actx.AddVariationDependencies(nil, nil, "recovery_snapshot")
+		}
+		if len(snapshotModule) > 0 && snapshotModule[0] != nil {
+			snapshot := actx.OtherModuleProvider(snapshotModule[0], SnapshotInfoProvider).(SnapshotInfo)
+			*snapshotInfo = &snapshot
+			// republish the snapshot for use in later mutators on this module
+			actx.SetProvider(SnapshotInfoProvider, snapshot)
+		}
+	}
+	if *snapshotInfo == nil {
+		*snapshotInfo = &SnapshotInfo{}
+	}
+	return **snapshotInfo
+}
+
+func RewriteSnapshotLib(lib string, snapshotMap map[string]string) string {
+	if snapshot, ok := snapshotMap[lib]; ok {
+		return snapshot
+	}
+
+	return lib
+}
+
+// RewriteLibs takes a list of names of shared libraries and scans it for three types
+// of names:
+//
+// 1. Name of an NDK library that refers to a prebuilt module.
+//    For each of these, it adds the name of the prebuilt module (which will be in
+//    prebuilts/ndk) to the list of nonvariant libs.
+// 2. Name of an NDK library that refers to an ndk_library module.
+//    For each of these, it adds the name of the ndk_library module to the list of
+//    variant libs.
+// 3. Anything else (so anything that isn't an NDK library).
+//    It adds these to the nonvariantLibs list.
+//
+// The caller can then know to add the variantLibs dependencies differently from the
+// nonvariantLibs
+func RewriteLibs(c LinkableInterface, snapshotInfo **SnapshotInfo, actx android.BottomUpMutatorContext, config android.Config, list []string) (nonvariantLibs []string, variantLibs []string) {
+	variantLibs = []string{}
+
+	nonvariantLibs = []string{}
+	for _, entry := range list {
+		// strip #version suffix out
+		name, _ := StubsLibNameAndVersion(entry)
+		if c.InRecovery() {
+			nonvariantLibs = append(nonvariantLibs, RewriteSnapshotLib(entry, GetSnapshot(c, snapshotInfo, actx).SharedLibs))
+		} else if c.UseSdk() && inList(name, *getNDKKnownLibs(config)) {
+			variantLibs = append(variantLibs, name+ndkLibrarySuffix)
+		} else if c.UseVndk() {
+			nonvariantLibs = append(nonvariantLibs, RewriteSnapshotLib(entry, GetSnapshot(c, snapshotInfo, actx).SharedLibs))
+		} else {
+			// put name#version back
+			nonvariantLibs = append(nonvariantLibs, entry)
+		}
+	}
+	return nonvariantLibs, variantLibs
+}
+
 func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
 	if !c.Enabled() {
 		return
@@ -2021,83 +2219,16 @@
 	c.Properties.AndroidMkSystemSharedLibs = deps.SystemSharedLibs
 
 	var snapshotInfo *SnapshotInfo
-	getSnapshot := func() SnapshotInfo {
-		// Only modules with BOARD_VNDK_VERSION uses snapshot.  Others use the zero value of
-		// SnapshotInfo, which provides no mappings.
-		if snapshotInfo == nil {
-			// Only retrieve the snapshot on demand in order to avoid circular dependencies
-			// between the modules in the snapshot and the snapshot itself.
-			var snapshotModule []blueprint.Module
-			if c.InVendor() && c.VndkVersion() == actx.DeviceConfig().VndkVersion() {
-				snapshotModule = ctx.AddVariationDependencies(nil, nil, "vendor_snapshot")
-			} else if recoverySnapshotVersion := actx.DeviceConfig().RecoverySnapshotVersion(); recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" && c.InRecovery() {
-				snapshotModule = ctx.AddVariationDependencies(nil, nil, "recovery_snapshot")
-			}
-			if len(snapshotModule) > 0 {
-				snapshot := ctx.OtherModuleProvider(snapshotModule[0], SnapshotInfoProvider).(SnapshotInfo)
-				snapshotInfo = &snapshot
-				// republish the snapshot for use in later mutators on this module
-				ctx.SetProvider(SnapshotInfoProvider, snapshot)
-			} else {
-				snapshotInfo = &SnapshotInfo{}
-			}
-		}
-
-		return *snapshotInfo
-	}
-
-	rewriteSnapshotLib := func(lib string, snapshotMap map[string]string) string {
-		if snapshot, ok := snapshotMap[lib]; ok {
-			return snapshot
-		}
-
-		return lib
-	}
 
 	variantNdkLibs := []string{}
 	variantLateNdkLibs := []string{}
 	if ctx.Os() == android.Android {
-		// rewriteLibs takes a list of names of shared libraries and scans it for three types
-		// of names:
-		//
-		// 1. Name of an NDK library that refers to a prebuilt module.
-		//    For each of these, it adds the name of the prebuilt module (which will be in
-		//    prebuilts/ndk) to the list of nonvariant libs.
-		// 2. Name of an NDK library that refers to an ndk_library module.
-		//    For each of these, it adds the name of the ndk_library module to the list of
-		//    variant libs.
-		// 3. Anything else (so anything that isn't an NDK library).
-		//    It adds these to the nonvariantLibs list.
-		//
-		// The caller can then know to add the variantLibs dependencies differently from the
-		// nonvariantLibs
-
-		rewriteLibs := func(list []string) (nonvariantLibs []string, variantLibs []string) {
-			variantLibs = []string{}
-			nonvariantLibs = []string{}
-			for _, entry := range list {
-				// strip #version suffix out
-				name, _ := StubsLibNameAndVersion(entry)
-				if c.InRecovery() {
-					nonvariantLibs = append(nonvariantLibs, rewriteSnapshotLib(entry, getSnapshot().SharedLibs))
-				} else if ctx.useSdk() && inList(name, *getNDKKnownLibs(ctx.Config())) {
-					variantLibs = append(variantLibs, name+ndkLibrarySuffix)
-				} else if ctx.useVndk() {
-					nonvariantLibs = append(nonvariantLibs, rewriteSnapshotLib(entry, getSnapshot().SharedLibs))
-				} else {
-					// put name#version back
-					nonvariantLibs = append(nonvariantLibs, entry)
-				}
-			}
-			return nonvariantLibs, variantLibs
-		}
-
-		deps.SharedLibs, variantNdkLibs = rewriteLibs(deps.SharedLibs)
-		deps.LateSharedLibs, variantLateNdkLibs = rewriteLibs(deps.LateSharedLibs)
-		deps.ReexportSharedLibHeaders, _ = rewriteLibs(deps.ReexportSharedLibHeaders)
+		deps.SharedLibs, variantNdkLibs = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.SharedLibs)
+		deps.LateSharedLibs, variantLateNdkLibs = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.LateSharedLibs)
+		deps.ReexportSharedLibHeaders, _ = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.ReexportSharedLibHeaders)
 
 		for idx, lib := range deps.RuntimeLibs {
-			deps.RuntimeLibs[idx] = rewriteSnapshotLib(lib, getSnapshot().SharedLibs)
+			deps.RuntimeLibs[idx] = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).SharedLibs)
 		}
 	}
 
@@ -2107,7 +2238,7 @@
 			depTag.reexportFlags = true
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().HeaderLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).HeaderLibs)
 
 		if c.IsStubs() {
 			actx.AddFarVariationDependencies(append(ctx.Target().Variations(), c.ImageVariation()),
@@ -2117,6 +2248,15 @@
 		}
 	}
 
+	if c.isNDKStubLibrary() {
+		// NDK stubs depend on their implementation because the ABI dumps are
+		// generated from the implementation library.
+		actx.AddFarVariationDependencies(append(ctx.Target().Variations(),
+			c.ImageVariation(),
+			blueprint.Variation{Mutator: "link", Variation: "shared"},
+		), stubImplementation, c.BaseModuleName())
+	}
+
 	// sysprop_library has to support both C++ and Java. So sysprop_library internally creates one
 	// C++ implementation library and one Java implementation library. When a module links against
 	// sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a
@@ -2130,7 +2270,7 @@
 			lib = impl
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().StaticLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs)
 
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
@@ -2150,7 +2290,7 @@
 			lib = impl
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().StaticLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs)
 
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
@@ -2164,14 +2304,7 @@
 		depTag := libraryDependencyTag{Kind: staticLibraryDependency, staticUnwinder: true}
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
-		}, depTag, rewriteSnapshotLib(staticUnwinder(actx), getSnapshot().StaticLibs))
-	}
-
-	for _, lib := range deps.LateStaticLibs {
-		depTag := libraryDependencyTag{Kind: staticLibraryDependency, Order: lateLibraryDependency}
-		actx.AddVariationDependencies([]blueprint.Variation{
-			{Mutator: "link", Variation: "static"},
-		}, depTag, rewriteSnapshotLib(lib, getSnapshot().StaticLibs))
+		}, depTag, RewriteSnapshotLib(staticUnwinder(actx), GetSnapshot(c, &snapshotInfo, actx).StaticLibs))
 	}
 
 	// shared lib names without the #version suffix
@@ -2196,7 +2329,14 @@
 		variations := []blueprint.Variation{
 			{Mutator: "link", Variation: "shared"},
 		}
-		c.addSharedLibDependenciesWithVersions(ctx, variations, depTag, name, version, false)
+		AddSharedLibDependenciesWithVersions(ctx, c, variations, depTag, name, version, false)
+	}
+
+	for _, lib := range deps.LateStaticLibs {
+		depTag := libraryDependencyTag{Kind: staticLibraryDependency, Order: lateLibraryDependency}
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs))
 	}
 
 	for _, lib := range deps.LateSharedLibs {
@@ -2210,13 +2350,15 @@
 		variations := []blueprint.Variation{
 			{Mutator: "link", Variation: "shared"},
 		}
-		c.addSharedLibDependenciesWithVersions(ctx, variations, depTag, lib, "", false)
+		AddSharedLibDependenciesWithVersions(ctx, c, variations, depTag, lib, "", false)
 	}
 
 	actx.AddVariationDependencies([]blueprint.Variation{
 		{Mutator: "link", Variation: "shared"},
 	}, dataLibDepTag, deps.DataLibs...)
 
+	actx.AddVariationDependencies(nil, dataBinDepTag, deps.DataBins...)
+
 	actx.AddVariationDependencies([]blueprint.Variation{
 		{Mutator: "link", Variation: "shared"},
 	}, runtimeDepTag, deps.RuntimeLibs...)
@@ -2233,16 +2375,13 @@
 
 	crtVariations := GetCrtVariations(ctx, c)
 	actx.AddVariationDependencies(crtVariations, objDepTag, deps.ObjFiles...)
-	if deps.CrtBegin != "" {
+	for _, crt := range deps.CrtBegin {
 		actx.AddVariationDependencies(crtVariations, CrtBeginDepTag,
-			rewriteSnapshotLib(deps.CrtBegin, getSnapshot().Objects))
+			RewriteSnapshotLib(crt, GetSnapshot(c, &snapshotInfo, actx).Objects))
 	}
-	if deps.CrtEnd != "" {
+	for _, crt := range deps.CrtEnd {
 		actx.AddVariationDependencies(crtVariations, CrtEndDepTag,
-			rewriteSnapshotLib(deps.CrtEnd, getSnapshot().Objects))
-	}
-	if deps.LinkerFlagsFile != "" {
-		actx.AddDependency(c, linkerFlagsDepTag, deps.LinkerFlagsFile)
+			RewriteSnapshotLib(crt, GetSnapshot(c, &snapshotInfo, actx).Objects))
 	}
 	if deps.DynamicLinker != "" {
 		actx.AddDependency(c, dynamicLinkerDepTag, deps.DynamicLinker)
@@ -2267,7 +2406,7 @@
 			actx.AddVariationDependencies([]blueprint.Variation{
 				c.ImageVariation(),
 				{Mutator: "link", Variation: "shared"},
-			}, vndkExtDepTag, vndkdep.getVndkExtendsModuleName())
+			}, vndkExtDepTag, RewriteSnapshotLib(vndkdep.getVndkExtendsModuleName(), GetSnapshot(c, &snapshotInfo, actx).SharedLibs))
 		}
 	}
 }
@@ -2329,10 +2468,6 @@
 		return
 	}
 	if c, ok := to.(*Module); ok {
-		if c.ToolchainLibrary() {
-			// These are always allowed
-			return
-		}
 		if c.NdkPrebuiltStl() {
 			// These are allowed, but they don't set sdk_version
 			return
@@ -2510,6 +2645,11 @@
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
 
+		if depTag == android.DarwinUniversalVariantTag {
+			depPaths.DarwinSecondArchOutput = dep.(*Module).OutputFile()
+			return
+		}
+
 		ccDep, ok := dep.(LinkableInterface)
 		if !ok {
 
@@ -2542,17 +2682,10 @@
 				} else {
 					ctx.ModuleErrorf("module %q is not a genrule", depName)
 				}
-			case linkerFlagsDepTag:
-				if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
-					files := genRule.GeneratedSourceFiles()
-					if len(files) == 1 {
-						depPaths.LinkerFlagsFile = android.OptionalPathForPath(files[0])
-					} else if len(files) > 1 {
-						ctx.ModuleErrorf("module %q can only generate a single file if used for a linker flag file", depName)
-					}
-				} else {
-					ctx.ModuleErrorf("module %q is not a genrule", depName)
-				}
+			case CrtBeginDepTag:
+				depPaths.CrtBegin = append(depPaths.CrtBegin, android.OutputFileForModule(ctx, dep, ""))
+			case CrtEndDepTag:
+				depPaths.CrtEnd = append(depPaths.CrtEnd, android.OutputFileForModule(ctx, dep, ""))
 			}
 			return
 		}
@@ -2625,64 +2758,8 @@
 					return
 				}
 
-				sharedLibraryInfo := ctx.OtherModuleProvider(dep, SharedLibraryInfoProvider).(SharedLibraryInfo)
-				sharedLibraryStubsInfo := ctx.OtherModuleProvider(dep, SharedLibraryStubsProvider).(SharedLibraryStubsInfo)
-
-				if !libDepTag.explicitlyVersioned && len(sharedLibraryStubsInfo.SharedStubLibraries) > 0 {
-					useStubs := false
-
-					if lib := moduleLibraryInterface(dep); lib.buildStubs() && c.UseVndk() { // LLNDK
-						if !apexInfo.IsForPlatform() {
-							// For platform libraries, use current version of LLNDK
-							useStubs = true
-						}
-					} else if apexInfo.IsForPlatform() {
-						// If not building for APEX, use stubs only when it is from
-						// an APEX (and not from platform)
-						// However, for host, ramdisk, vendor_ramdisk, recovery or bootstrap modules,
-						// always link to non-stub variant
-						useStubs = dep.(android.ApexModule).NotInPlatform() && !c.bootstrap()
-						if useStubs {
-							// Another exception: if this module is a test for an APEX, then
-							// it is linked with the non-stub variant of a module in the APEX
-							// as if this is part of the APEX.
-							testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
-							for _, apexContents := range testFor.ApexContents {
-								if apexContents.DirectlyInApex(depName) {
-									useStubs = false
-									break
-								}
-							}
-						}
-						if useStubs {
-							// Yet another exception: If this module and the dependency are
-							// available to the same APEXes then skip stubs between their
-							// platform variants. This complements the test_for case above,
-							// which avoids the stubs on a direct APEX library dependency, by
-							// avoiding stubs for indirect test dependencies as well.
-							//
-							// TODO(b/183882457): This doesn't work if the two libraries have
-							// only partially overlapping apex_available. For that test_for
-							// modules would need to be split into APEX variants and resolved
-							// separately for each APEX they have access to.
-							if android.AvailableToSameApexes(c, dep.(android.ApexModule)) {
-								useStubs = false
-							}
-						}
-					} else {
-						// If building for APEX, use stubs when the parent is in any APEX that
-						// the child is not in.
-						useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
-					}
-
-					// when to use (unspecified) stubs, use the latest one.
-					if useStubs {
-						stubs := sharedLibraryStubsInfo.SharedStubLibraries
-						toUse := stubs[len(stubs)-1]
-						sharedLibraryInfo = toUse.SharedLibraryInfo
-						depExporterInfo = toUse.FlagExporterInfo
-					}
-				}
+				sharedLibraryInfo, returnedDepExporterInfo := ChooseStubOrImpl(ctx, dep)
+				depExporterInfo = returnedDepExporterInfo
 
 				// Stubs lib doesn't link to the shared lib dependencies. Don't set
 				// linkFile, depFile, and ptr.
@@ -2743,6 +2820,8 @@
 						// dependency.
 						depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts, linkFile.Path())
 					}
+					depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts,
+						staticLibraryInfo.WholeStaticLibsFromPrebuilts...)
 				} else {
 					switch libDepTag.Order {
 					case earlyLibraryDependency:
@@ -2840,8 +2919,8 @@
 				// they merely serve as Make dependencies and do not affect this lib itself.
 				c.Properties.AndroidMkSharedLibs = append(
 					c.Properties.AndroidMkSharedLibs, makeLibName)
-				// Record baseLibName for snapshots.
-				c.Properties.SnapshotSharedLibs = append(c.Properties.SnapshotSharedLibs, baseLibName(depName))
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotSharedLibs = append(c.Properties.SnapshotSharedLibs, BaseLibName(depName))
 			case libDepTag.static():
 				if libDepTag.wholeStatic {
 					c.Properties.AndroidMkWholeStaticLibs = append(
@@ -2850,6 +2929,8 @@
 					c.Properties.AndroidMkStaticLibs = append(
 						c.Properties.AndroidMkStaticLibs, makeLibName)
 				}
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotStaticLibs = append(c.Properties.SnapshotStaticLibs, BaseLibName(depName))
 			}
 		} else if !c.IsStubs() {
 			// Stubs lib doesn't link to the runtime lib, object, crt, etc. dependencies.
@@ -2858,14 +2939,14 @@
 			case runtimeDepTag:
 				c.Properties.AndroidMkRuntimeLibs = append(
 					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ctx, c, ccDep, depName)+libDepTag.makeSuffix)
-				// Record baseLibName for snapshots.
-				c.Properties.SnapshotRuntimeLibs = append(c.Properties.SnapshotRuntimeLibs, baseLibName(depName))
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotRuntimeLibs = append(c.Properties.SnapshotRuntimeLibs, BaseLibName(depName))
 			case objDepTag:
 				depPaths.Objs.objFiles = append(depPaths.Objs.objFiles, linkFile.Path())
 			case CrtBeginDepTag:
-				depPaths.CrtBegin = linkFile
+				depPaths.CrtBegin = append(depPaths.CrtBegin, linkFile.Path())
 			case CrtEndDepTag:
-				depPaths.CrtEnd = linkFile
+				depPaths.CrtEnd = append(depPaths.CrtEnd, linkFile.Path())
 			case dynamicLinkerDepTag:
 				depPaths.DynamicLinker = linkFile
 			}
@@ -2895,6 +2976,100 @@
 	return depPaths
 }
 
+// ChooseStubOrImpl determines whether a given dependency should be redirected to the stub variant
+// of the dependency or not, and returns the SharedLibraryInfo and FlagExporterInfo for the right
+// dependency. The stub variant is selected when the dependency crosses a boundary where each side
+// has different level of updatability. For example, if a library foo in an APEX depends on a
+// library bar which provides stable interface and exists in the platform, foo uses the stub variant
+// of bar. If bar doesn't provide a stable interface (i.e. buildStubs() == false) or is in the
+// same APEX as foo, the non-stub variant of bar is used.
+func ChooseStubOrImpl(ctx android.ModuleContext, dep android.Module) (SharedLibraryInfo, FlagExporterInfo) {
+	depName := ctx.OtherModuleName(dep)
+	depTag := ctx.OtherModuleDependencyTag(dep)
+	libDepTag, ok := depTag.(libraryDependencyTag)
+	if !ok || !libDepTag.shared() {
+		panic(fmt.Errorf("Unexpected dependency tag: %T", depTag))
+	}
+
+	thisModule, ok := ctx.Module().(android.ApexModule)
+	if !ok {
+		panic(fmt.Errorf("Not an APEX module: %q", ctx.ModuleName()))
+	}
+
+	useVndk := false
+	bootstrap := false
+	if linkable, ok := ctx.Module().(LinkableInterface); !ok {
+		panic(fmt.Errorf("Not a Linkable module: %q", ctx.ModuleName()))
+	} else {
+		useVndk = linkable.UseVndk()
+		bootstrap = linkable.Bootstrap()
+	}
+
+	sharedLibraryInfo := ctx.OtherModuleProvider(dep, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	depExporterInfo := ctx.OtherModuleProvider(dep, FlagExporterInfoProvider).(FlagExporterInfo)
+	sharedLibraryStubsInfo := ctx.OtherModuleProvider(dep, SharedLibraryStubsProvider).(SharedLibraryStubsInfo)
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+
+	if !libDepTag.explicitlyVersioned && len(sharedLibraryStubsInfo.SharedStubLibraries) > 0 {
+		useStubs := false
+
+		if lib := moduleLibraryInterface(dep); lib.buildStubs() && useVndk { // LLNDK
+			if !apexInfo.IsForPlatform() {
+				// For platform libraries, use current version of LLNDK
+				// If this is for use_vendor apex we will apply the same rules
+				// of apex sdk enforcement below to choose right version.
+				useStubs = true
+			}
+		} else if apexInfo.IsForPlatform() || apexInfo.UsePlatformApis {
+			// If not building for APEX or the containing APEX allows the use of
+			// platform APIs, use stubs only when it is from an APEX (and not from
+			// platform) However, for host, ramdisk, vendor_ramdisk, recovery or
+			// bootstrap modules, always link to non-stub variant
+			useStubs = dep.(android.ApexModule).NotInPlatform() && !bootstrap
+			if useStubs {
+				// Another exception: if this module is a test for an APEX, then
+				// it is linked with the non-stub variant of a module in the APEX
+				// as if this is part of the APEX.
+				testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
+				for _, apexContents := range testFor.ApexContents {
+					if apexContents.DirectlyInApex(depName) {
+						useStubs = false
+						break
+					}
+				}
+			}
+			if useStubs {
+				// Yet another exception: If this module and the dependency are
+				// available to the same APEXes then skip stubs between their
+				// platform variants. This complements the test_for case above,
+				// which avoids the stubs on a direct APEX library dependency, by
+				// avoiding stubs for indirect test dependencies as well.
+				//
+				// TODO(b/183882457): This doesn't work if the two libraries have
+				// only partially overlapping apex_available. For that test_for
+				// modules would need to be split into APEX variants and resolved
+				// separately for each APEX they have access to.
+				if android.AvailableToSameApexes(thisModule, dep.(android.ApexModule)) {
+					useStubs = false
+				}
+			}
+		} else {
+			// If building for APEX, use stubs when the parent is in any APEX that
+			// the child is not in.
+			useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
+		}
+
+		// when to use (unspecified) stubs, use the latest one.
+		if useStubs {
+			stubs := sharedLibraryStubsInfo.SharedStubLibraries
+			toUse := stubs[len(stubs)-1]
+			sharedLibraryInfo = toUse.SharedLibraryInfo
+			depExporterInfo = toUse.FlagExporterInfo
+		}
+	}
+	return sharedLibraryInfo, depExporterInfo
+}
+
 // orderStaticModuleDeps rearranges the order of the static library dependencies of the module
 // to match the topological order of the dependency tree, including any static analogues of
 // direct shared libraries.  It returns the ordered static dependencies, and an android.DepSet
@@ -2907,8 +3082,8 @@
 		transitiveStaticLibsBuilder.Transitive(staticDep.TransitiveStaticLibrariesForOrdering)
 	}
 	for _, sharedDep := range sharedDeps {
-		if sharedDep.StaticAnalogue != nil {
-			transitiveStaticLibsBuilder.Transitive(sharedDep.StaticAnalogue.TransitiveStaticLibrariesForOrdering)
+		if sharedDep.TransitiveStaticLibrariesForOrdering != nil {
+			transitiveStaticLibsBuilder.Transitive(sharedDep.TransitiveStaticLibrariesForOrdering)
 		}
 	}
 	transitiveStaticLibs := transitiveStaticLibsBuilder.Build()
@@ -2927,8 +3102,8 @@
 	return orderedStaticPaths, transitiveStaticLibs
 }
 
-// baseLibName trims known prefixes and suffixes
-func baseLibName(depName string) string {
+// BaseLibName trims known prefixes and suffixes
+func BaseLibName(depName string) string {
 	libName := strings.TrimSuffix(depName, llndkLibrarySuffix)
 	libName = strings.TrimSuffix(libName, vendorPublicLibrarySuffix)
 	libName = android.RemoveOptionalPrebuiltPrefix(libName)
@@ -2936,7 +3111,7 @@
 }
 
 func MakeLibName(ctx android.ModuleContext, c LinkableInterface, ccDep LinkableInterface, depName string) string {
-	libName := baseLibName(depName)
+	libName := BaseLibName(depName)
 	ccDepModule, _ := ccDep.(*Module)
 	isLLndk := ccDepModule != nil && ccDepModule.IsLlndk()
 	nonSystemVariantsExist := ccDep.HasNonSystemVariants() || isLLndk
@@ -2944,10 +3119,10 @@
 	if ccDepModule != nil {
 		// TODO(ivanlozano) Support snapshots for Rust-produced C library variants.
 		// Use base module name for snapshots when exporting to Makefile.
-		if snapshotPrebuilt, ok := ccDepModule.linker.(snapshotInterface); ok {
+		if snapshotPrebuilt, ok := ccDepModule.linker.(SnapshotInterface); ok {
 			baseName := ccDepModule.BaseModuleName()
 
-			return baseName + snapshotPrebuilt.snapshotAndroidMkSuffix()
+			return baseName + snapshotPrebuilt.SnapshotAndroidMkSuffix()
 		}
 	}
 
@@ -2961,13 +3136,13 @@
 		// core module, so update the dependency name here accordingly.
 		return libName + ccDep.SubName()
 	} else if ccDep.InRamdisk() && !ccDep.OnlyInRamdisk() {
-		return libName + ramdiskSuffix
+		return libName + RamdiskSuffix
 	} else if ccDep.InVendorRamdisk() && !ccDep.OnlyInVendorRamdisk() {
 		return libName + VendorRamdiskSuffix
 	} else if ccDep.InRecovery() && !ccDep.OnlyInRecovery() {
-		return libName + recoverySuffix
+		return libName + RecoverySuffix
 	} else if ccDep.Target().NativeBridge == android.NativeBridgeEnabled {
-		return libName + nativeBridgeSuffix
+		return libName + NativeBridgeSuffix
 	} else {
 		return libName
 	}
@@ -3060,6 +3235,24 @@
 	return false
 }
 
+func (c *Module) benchmarkBinary() bool {
+	if b, ok := c.linker.(interface {
+		benchmarkBinary() bool
+	}); ok {
+		return b.benchmarkBinary()
+	}
+	return false
+}
+
+func (c *Module) fuzzBinary() bool {
+	if f, ok := c.linker.(interface {
+		fuzzBinary() bool
+	}); ok {
+		return f.fuzzBinary()
+	}
+	return false
+}
+
 // Header returns true if the module is a header-only variant. (See cc/library.go header()).
 func (c *Module) Header() bool {
 	if h, ok := c.linker.(interface {
@@ -3079,6 +3272,13 @@
 	return false
 }
 
+func (c *Module) StaticExecutable() bool {
+	if b, ok := c.linker.(*binaryDecorator); ok {
+		return b.static()
+	}
+	return false
+}
+
 func (c *Module) Object() bool {
 	if o, ok := c.linker.(interface {
 		object() bool
@@ -3151,16 +3351,6 @@
 	return c.Properties.Test_for
 }
 
-func (c *Module) UniqueApexVariations() bool {
-	if u, ok := c.compiler.(interface {
-		uniqueApexVariations() bool
-	}); ok {
-		return u.uniqueApexVariations()
-	} else {
-		return false
-	}
-}
-
 func (c *Module) EverInstallable() bool {
 	return c.installer != nil &&
 		// Check to see whether the module is actually ever installable.
@@ -3172,6 +3362,11 @@
 }
 
 func (c *Module) Installable() *bool {
+	if c.library != nil {
+		if i := c.library.installable(); i != nil {
+			return i
+		}
+	}
 	return c.Properties.Installable
 }
 
@@ -3240,8 +3435,8 @@
 			return false
 		}
 	}
-	if depTag == stubImplDepTag || depTag == llndkStubDepTag {
-		// We don't track beyond LLNDK or from an implementation library to its stubs.
+	if depTag == stubImplDepTag {
+		// We don't track from an implementation library to its stubs.
 		return false
 	}
 	if depTag == staticVariantTag {
@@ -3260,10 +3455,6 @@
 	if strings.HasPrefix(ctx.OtherModuleName(c), "libclang_rt") {
 		return nil
 	}
-	// b/154569636: set min_sdk_version correctly for toolchain_libraries
-	if c.ToolchainLibrary() {
-		return nil
-	}
 	// We don't check for prebuilt modules
 	if _, ok := c.linker.(prebuiltLinkerInterface); ok {
 		return nil
@@ -3301,16 +3492,96 @@
 	return c.IsStubs() || c.Target().NativeBridge == android.NativeBridgeEnabled
 }
 
+// Overrides android.ApexModuleBase.UniqueApexVariations
+func (c *Module) UniqueApexVariations() bool {
+	// When a vendor APEX needs a VNDK lib in it (use_vndk_as_stable: false), it should be a unique
+	// APEX variation. Otherwise, another vendor APEX with use_vndk_as_stable:true may use a wrong
+	// variation of the VNDK lib because APEX variations are merged/grouped.
+	return c.UseVndk() && c.IsVndk()
+}
+
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
+
+type moduleType int
+
+const (
+	unknownType moduleType = iota
+	binary
+	object
+	fullLibrary
+	staticLibrary
+	sharedLibrary
+	headerLibrary
+)
+
+func (c *Module) typ() moduleType {
+	if c.Binary() {
+		return binary
+	} else if c.Object() {
+		return object
+	} else if c.CcLibrary() {
+		static := false
+		shared := false
+		if library, ok := c.linker.(*libraryDecorator); ok {
+			static = library.MutatedProperties.BuildStatic
+			shared = library.MutatedProperties.BuildShared
+		} else if library, ok := c.linker.(*prebuiltLibraryLinker); ok {
+			static = library.MutatedProperties.BuildStatic
+			shared = library.MutatedProperties.BuildShared
+		}
+		if static && shared {
+			return fullLibrary
+		} else if !static && !shared {
+			return headerLibrary
+		} else if static {
+			return staticLibrary
+		}
+		return sharedLibrary
+	}
+	return unknownType
+}
+
+// ConvertWithBp2build converts Module to Bazel for bp2build.
+func (c *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	prebuilt := c.IsPrebuilt()
+	switch c.typ() {
+	case binary:
+		if !prebuilt {
+			binaryBp2build(ctx, c, ctx.ModuleType())
+		}
+	case object:
+		if !prebuilt {
+			objectBp2Build(ctx, c)
+		}
+	case fullLibrary:
+		if !prebuilt {
+			libraryBp2Build(ctx, c)
+		} else {
+			prebuiltLibraryBp2Build(ctx, c)
+		}
+	case headerLibrary:
+		libraryHeadersBp2Build(ctx, c)
+	case staticLibrary:
+		if prebuilt {
+			prebuiltLibraryStaticBp2Build(ctx, c, false)
+		} else {
+			sharedOrStaticLibraryBp2Build(ctx, c, true)
+		}
+	case sharedLibrary:
+		if prebuilt {
+			prebuiltLibrarySharedBp2Build(ctx, c)
+		} else {
+			sharedOrStaticLibraryBp2Build(ctx, c, false)
+		}
+	}
+}
+
 //
 // Defaults
 //
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
-	// target. This is primarily useful for modules that were architecture specific and instead are
-	// handled in Bazel as a select().
-	android.BazelModuleBase
 	android.ApexModuleBase
 }
 
@@ -3338,10 +3609,11 @@
 		&SharedProperties{},
 		&FlagExporterProperties{},
 		&BinaryLinkerProperties{},
-		&TestProperties{},
+		&TestLinkerProperties{},
+		&TestInstallerProperties{},
 		&TestBinaryProperties{},
 		&BenchmarkProperties{},
-		&FuzzProperties{},
+		&fuzz.FuzzProperties{},
 		&StlProperties{},
 		&SanitizeProperties{},
 		&StripProperties{},
@@ -3351,6 +3623,7 @@
 		&SAbiProperties{},
 		&VndkProperties{},
 		&LTOProperties{},
+		&AfdoProperties{},
 		&PgoProperties{},
 		&android.ProtoProperties{},
 		// RustBindgenProperties is included here so that cc_defaults can be used for rust_bindgen modules.
@@ -3358,15 +3631,13 @@
 		&prebuiltLinkerProperties{},
 	)
 
-	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
-	android.InitBazelModule(module)
 	android.InitDefaultsModule(module)
 
 	return module
 }
 
 func (c *Module) IsSdkVariant() bool {
-	return c.Properties.IsSdkVariant || c.AlwaysSdk()
+	return c.Properties.IsSdkVariant
 }
 
 func kytheExtractAllFactory() android.Singleton {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 5acafbe..09cc352 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -152,71 +152,6 @@
 	).RunTest(t)
 }
 
-func TestFuchsiaDeps(t *testing.T) {
-	t.Helper()
-
-	bp := `
-		cc_library {
-			name: "libTest",
-			srcs: ["foo.c"],
-			target: {
-				fuchsia: {
-					srcs: ["bar.c"],
-				},
-			},
-		}`
-
-	result := android.GroupFixturePreparers(
-		prepareForCcTest,
-		PrepareForTestOnFuchsia,
-	).RunTestWithBp(t, bp)
-
-	rt := false
-	fb := false
-
-	ld := result.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
-	implicits := ld.Implicits
-	for _, lib := range implicits {
-		if strings.Contains(lib.Rel(), "libcompiler_rt") {
-			rt = true
-		}
-
-		if strings.Contains(lib.Rel(), "libbioniccompat") {
-			fb = true
-		}
-	}
-
-	if !rt || !fb {
-		t.Errorf("fuchsia libs must link libcompiler_rt and libbioniccompat")
-	}
-}
-
-func TestFuchsiaTargetDecl(t *testing.T) {
-	t.Helper()
-
-	bp := `
-		cc_library {
-			name: "libTest",
-			srcs: ["foo.c"],
-			target: {
-				fuchsia: {
-					srcs: ["bar.c"],
-				},
-			},
-		}`
-
-	result := android.GroupFixturePreparers(
-		prepareForCcTest,
-		PrepareForTestOnFuchsia,
-	).RunTestWithBp(t, bp)
-	ld := result.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
-	var objs []string
-	for _, o := range ld.Inputs {
-		objs = append(objs, o.Base())
-	}
-	android.AssertArrayString(t, "libTest inputs", []string{"foo.o", "bar.o"}, objs)
-}
-
 func TestVendorSrc(t *testing.T) {
 	ctx := testCc(t, `
 		cc_library {
@@ -396,50 +331,6 @@
 	}
 }
 
-func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
-	t.Helper()
-	mod := ctx.ModuleForTests(moduleName, variant)
-	outputFiles := mod.OutputFiles(t, "")
-	if len(outputFiles) != 1 {
-		t.Errorf("%q must have single output\n", moduleName)
-		return
-	}
-	snapshotPath := filepath.Join(subDir, snapshotFilename)
-
-	if include {
-		out := singleton.Output(snapshotPath)
-		if fake {
-			if out.Rule == nil {
-				t.Errorf("Missing rule for module %q output file %q", moduleName, outputFiles[0])
-			}
-		} else {
-			if out.Input.String() != outputFiles[0].String() {
-				t.Errorf("The input of snapshot %q must be %q, but %q", moduleName, out.Input.String(), outputFiles[0])
-			}
-		}
-	} else {
-		out := singleton.MaybeOutput(snapshotPath)
-		if out.Rule != nil {
-			t.Errorf("There must be no rule for module %q output file %q", moduleName, outputFiles[0])
-		}
-	}
-}
-
-func checkSnapshot(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, false)
-}
-
-func checkSnapshotExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, false, false)
-}
-
-func checkSnapshotRule(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
-}
-
 func checkWriteFileOutput(t *testing.T, params android.TestingBuildParams, expected []string) {
 	t.Helper()
 	content := android.ContentFromFileRuleForTests(t, params)
@@ -455,7 +346,7 @@
 
 func checkVndkLibrariesOutput(t *testing.T, ctx *android.TestContext, module string, expected []string) {
 	t.Helper()
-	got := ctx.ModuleForTests(module, "").Module().(*vndkLibrariesTxt).fileNames
+	got := ctx.ModuleForTests(module, "android_common").Module().(*vndkLibrariesTxt).fileNames
 	assertArrayString(t, got, expected)
 }
 
@@ -631,21 +522,21 @@
 
 	snapshotSingleton := ctx.SingletonForTests("vndk-snapshot")
 
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLib2ndPath, variant2nd)
 
 	snapshotConfigsPath := filepath.Join(snapshotVariantPath, "configs")
-	checkSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "android_common")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "android_common")
 
 	checkVndkOutput(t, ctx, "vndk/vndk.libraries.txt", []string{
 		"LLNDK: libc.so",
@@ -723,7 +614,7 @@
 	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
-	module := ctx.ModuleForTests("llndk.libraries.txt", "")
+	module := ctx.ModuleForTests("llndk.libraries.txt", "android_common")
 	entries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
 	assertArrayString(t, entries.EntryMap["LOCAL_MODULE_STEM"], []string{"llndk.libraries.29.txt"})
 }
@@ -839,9 +730,16 @@
 			gtest: false,
 		}
 
+		cc_binary {
+			name: "test_bin",
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "both",
+		}
+
 		cc_test {
 			name: "main_test",
 			data_libs: ["test_lib"],
+			data_bins: ["test_bin"],
 			gtest: false,
 		}
  `
@@ -859,10 +757,10 @@
 		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
 	}
 	if len(outputFiles) != 1 {
-		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
-	if len(testBinary.dataPaths()) != 1 {
-		t.Errorf("expected exactly one test data file. test data files: [%s]", testBinary.dataPaths())
+	if len(testBinary.dataPaths()) != 2 {
+		t.Fatalf("expected exactly one test data file. test data files: [%s]", testBinary.dataPaths())
 	}
 
 	outputPath := outputFiles[0].String()
@@ -875,6 +773,72 @@
 		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
 	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":test_bin:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_bin:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
+	}
+}
+
+func TestTestBinaryTestSuites(t *testing.T) {
+	bp := `
+		cc_test {
+			name: "main_test",
+			srcs: ["main_test.cpp"],
+			test_suites: [
+				"suite_1",
+				"suite_2",
+			],
+			gtest: false,
+		}
+	`
+
+	ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+	if len(compatEntries) != 2 {
+		t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+	}
+	if compatEntries[0] != "suite_1" {
+		t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+			" but was '%s'", compatEntries[0])
+	}
+	if compatEntries[1] != "suite_2" {
+		t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+			" but was '%s'", compatEntries[1])
+	}
+}
+
+func TestTestLibraryTestSuites(t *testing.T) {
+	bp := `
+		cc_test_library {
+			name: "main_test_lib",
+			srcs: ["main_test_lib.cpp"],
+			test_suites: [
+				"suite_1",
+				"suite_2",
+			],
+			gtest: false,
+		}
+	`
+
+	ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
+	module := ctx.ModuleForTests("main_test_lib", "android_arm_armv7-a-neon_shared").Module()
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
+	if len(compatEntries) != 2 {
+		t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
+	}
+	if compatEntries[0] != "suite_1" {
+		t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_1`,"+
+			" but was '%s'", compatEntries[0])
+	}
+	if compatEntries[1] != "suite_2" {
+		t.Errorf("expected LOCAL_COMPATIBILITY_SUITE to be`suite_2`,"+
+			" but was '%s'", compatEntries[1])
+	}
 }
 
 func TestVndkWhenVndkVersionIsNotSet(t *testing.T) {
@@ -2643,15 +2607,6 @@
 	return modulesInOrder, allDeps
 }
 
-func getOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
-	for _, moduleName := range moduleNames {
-		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
-		output := module.outputFile.Path().RelativeToTop()
-		paths = append(paths, output)
-	}
-	return paths
-}
-
 func TestStaticLibDepReordering(t *testing.T) {
 	ctx := testCc(t, `
 	cc_library {
@@ -2679,7 +2634,7 @@
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
 	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
 		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
-	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
+	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -2714,7 +2669,7 @@
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
 	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
 		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
-	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b"})
+	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings did not account for shared libs"+
@@ -2799,12 +2754,8 @@
 		}
 	}
 	expected := []string{
-		"android_vendor.29_arm64_armv8-a_shared_1",
-		"android_vendor.29_arm64_armv8-a_shared_2",
 		"android_vendor.29_arm64_armv8-a_shared_current",
 		"android_vendor.29_arm64_armv8-a_shared",
-		"android_vendor.29_arm_armv7-a-neon_shared_1",
-		"android_vendor.29_arm_armv7-a-neon_shared_2",
 		"android_vendor.29_arm_armv7-a-neon_shared_current",
 		"android_vendor.29_arm_armv7-a-neon_shared",
 	}
@@ -2813,9 +2764,6 @@
 	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
 	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
 
-	params = result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared_1").Description("generate stub")
-	android.AssertSame(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
-
 	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
 		t.Helper()
 		m := result.ModuleForTests(module, variant).Module()
@@ -3058,13 +3006,13 @@
 	// Check the shared version of lib2.
 	variant := "android_arm64_armv8-a_shared"
 	module := ctx.ModuleForTests("lib2", variant).Module().(*Module)
-	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins-aarch64-android"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins"}, module)
 
 	// Check the static version of lib2.
 	variant = "android_arm64_armv8-a_static"
 	module = ctx.ModuleForTests("lib2", variant).Module().(*Module)
 	// libc++_static is linked additionally.
-	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins-aarch64-android"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins"}, module)
 }
 
 var compilerFlagsTestCases = []struct {
@@ -3364,7 +3312,7 @@
 
 	mybin := ctx.ModuleForTests("mybin", "android_arm64_armv8-a").Rule("ld")
 	actual := mybin.Implicits[:2]
-	expected := getOutputPaths(ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
+	expected := GetOutputPaths(ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -3710,6 +3658,58 @@
 	}
 }
 
+func TestAidlFlagsWithMinSdkVersion(t *testing.T) {
+	for _, tc := range []struct {
+		name       string
+		sdkVersion string
+		variant    string
+		expected   string
+	}{
+		{
+			name:       "default is current",
+			sdkVersion: "",
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "platform_apis",
+		},
+		{
+			name:       "use sdk_version",
+			sdkVersion: `sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "platform_apis",
+		},
+		{
+			name:       "use sdk_version(sdk variant)",
+			sdkVersion: `sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_sdk_static",
+			expected:   "29",
+		},
+		{
+			name:       "use min_sdk_version",
+			sdkVersion: `min_sdk_version: "29"`,
+			variant:    "android_arm64_armv8-a_static",
+			expected:   "29",
+		},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := testCc(t, `
+				cc_library {
+					name: "libfoo",
+					stl: "none",
+					srcs: ["a/Foo.aidl"],
+					`+tc.sdkVersion+`
+				}
+			`)
+			libfoo := ctx.ModuleForTests("libfoo", tc.variant)
+			manifest := android.RuleBuilderSboxProtoForTests(t, libfoo.Output("aidl.sbox.textproto"))
+			aidlCommand := manifest.Commands[0].GetCommand()
+			expectedAidlFlag := "--min_sdk_version=" + tc.expected
+			if !strings.Contains(aidlCommand, expectedAidlFlag) {
+				t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+			}
+		})
+	}
+}
+
 func TestMinSdkVersionInClangTriple(t *testing.T) {
 	ctx := testCc(t, `
 		cc_library_shared {
@@ -3722,305 +3722,6 @@
 	android.AssertStringDoesContain(t, "min sdk version", cFlags, "-target aarch64-linux-android29")
 }
 
-type MemtagNoteType int
-
-const (
-	None MemtagNoteType = iota + 1
-	Sync
-	Async
-)
-
-func (t MemtagNoteType) str() string {
-	switch t {
-	case None:
-		return "none"
-	case Sync:
-		return "sync"
-	case Async:
-		return "async"
-	default:
-		panic("invalid note type")
-	}
-}
-
-func checkHasMemtagNote(t *testing.T, m android.TestingModule, expected MemtagNoteType) {
-	note_async := "note_memtag_heap_async"
-	note_sync := "note_memtag_heap_sync"
-
-	found := None
-	implicits := m.Rule("ld").Implicits
-	for _, lib := range implicits {
-		if strings.Contains(lib.Rel(), note_async) {
-			found = Async
-			break
-		} else if strings.Contains(lib.Rel(), note_sync) {
-			found = Sync
-			break
-		}
-	}
-
-	if found != expected {
-		t.Errorf("Wrong Memtag note in target %q: found %q, expected %q", m.Module().(*Module).Name(), found.str(), expected.str())
-	}
-}
-
-var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
-	android.FixtureModifyMockFS(func(fs android.MockFS) {
-		templateBp := `
-		cc_test {
-			name: "%[1]s_test",
-			gtest: false,
-		}
-
-		cc_test {
-			name: "%[1]s_test_false",
-			gtest: false,
-			sanitize: { memtag_heap: false },
-		}
-
-		cc_test {
-			name: "%[1]s_test_true",
-			gtest: false,
-			sanitize: { memtag_heap: true },
-		}
-
-		cc_test {
-			name: "%[1]s_test_true_nodiag",
-			gtest: false,
-			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
-		}
-
-		cc_test {
-			name: "%[1]s_test_true_diag",
-			gtest: false,
-			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
-		}
-
-		cc_binary {
-			name: "%[1]s_binary",
-		}
-
-		cc_binary {
-			name: "%[1]s_binary_false",
-			sanitize: { memtag_heap: false },
-		}
-
-		cc_binary {
-			name: "%[1]s_binary_true",
-			sanitize: { memtag_heap: true },
-		}
-
-		cc_binary {
-			name: "%[1]s_binary_true_nodiag",
-			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
-		}
-
-		cc_binary {
-			name: "%[1]s_binary_true_diag",
-			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
-		}
-		`
-		subdirDefaultBp := fmt.Sprintf(templateBp, "default")
-		subdirExcludeBp := fmt.Sprintf(templateBp, "exclude")
-		subdirSyncBp := fmt.Sprintf(templateBp, "sync")
-		subdirAsyncBp := fmt.Sprintf(templateBp, "async")
-
-		fs.Merge(android.MockFS{
-			"subdir_default/Android.bp": []byte(subdirDefaultBp),
-			"subdir_exclude/Android.bp": []byte(subdirExcludeBp),
-			"subdir_sync/Android.bp":    []byte(subdirSyncBp),
-			"subdir_async/Android.bp":   []byte(subdirAsyncBp),
-		})
-	}),
-	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-		variables.MemtagHeapExcludePaths = []string{"subdir_exclude"}
-		// "subdir_exclude" is covered by both include and exclude paths. Exclude wins.
-		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync", "subdir_exclude"}
-		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async", "subdir_exclude"}
-	}),
-)
-
-func TestSanitizeMemtagHeap(t *testing.T) {
-	variant := "android_arm64_armv8-a"
-
-	result := android.GroupFixturePreparers(
-		prepareForCcTest,
-		prepareForTestWithMemtagHeap,
-	).RunTest(t)
-	ctx := result.TestContext
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_diag", variant), Sync)
-}
-
-func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
-	variant := "android_arm64_armv8-a"
-
-	result := android.GroupFixturePreparers(
-		prepareForCcTest,
-		prepareForTestWithMemtagHeap,
-		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.SanitizeDevice = []string{"memtag_heap"}
-		}),
-	).RunTest(t)
-	ctx := result.TestContext
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_diag", variant), Sync)
-}
-
-func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
-	variant := "android_arm64_armv8-a"
-
-	result := android.GroupFixturePreparers(
-		prepareForCcTest,
-		prepareForTestWithMemtagHeap,
-		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.SanitizeDevice = []string{"memtag_heap"}
-			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
-		}),
-	).RunTest(t)
-	ctx := result.TestContext
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("default_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("exclude_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("async_binary_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_test_true_diag", variant), Sync)
-
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_false", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_nodiag", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("sync_binary_true_diag", variant), Sync)
-}
-
 func TestIncludeDirsExporting(t *testing.T) {
 
 	// Trim spaces from the beginning, end and immediately after any newline characters. Leaves
@@ -4291,3 +3992,207 @@
 		)
 	})
 }
+
+func TestIncludeDirectoryOrdering(t *testing.T) {
+	baseExpectedFlags := []string{
+		"${config.ArmThumbCflags}",
+		"${config.ArmCflags}",
+		"${config.CommonGlobalCflags}",
+		"${config.DeviceGlobalCflags}",
+		"${config.ExternalCflags}",
+		"${config.ArmToolchainCflags}",
+		"${config.ArmArmv7ANeonCflags}",
+		"${config.ArmGenericCflags}",
+		"-target",
+		"armv7a-linux-androideabi20",
+	}
+
+	expectedIncludes := []string{
+		"external/foo/android_arm_export_include_dirs",
+		"external/foo/lib32_export_include_dirs",
+		"external/foo/arm_export_include_dirs",
+		"external/foo/android_export_include_dirs",
+		"external/foo/linux_export_include_dirs",
+		"external/foo/export_include_dirs",
+		"external/foo/android_arm_local_include_dirs",
+		"external/foo/lib32_local_include_dirs",
+		"external/foo/arm_local_include_dirs",
+		"external/foo/android_local_include_dirs",
+		"external/foo/linux_local_include_dirs",
+		"external/foo/local_include_dirs",
+		"external/foo",
+		"external/foo/libheader1",
+		"external/foo/libheader2",
+		"external/foo/libwhole1",
+		"external/foo/libwhole2",
+		"external/foo/libstatic1",
+		"external/foo/libstatic2",
+		"external/foo/libshared1",
+		"external/foo/libshared2",
+		"external/foo/liblinux",
+		"external/foo/libandroid",
+		"external/foo/libarm",
+		"external/foo/lib32",
+		"external/foo/libandroid_arm",
+		"defaults/cc/common/ndk_libc++_shared",
+		"defaults/cc/common/ndk_libandroid_support",
+	}
+
+	conly := []string{"-fPIC", "${config.CommonGlobalConlyflags}"}
+	cppOnly := []string{"-fPIC", "${config.CommonGlobalCppflags}", "${config.DeviceGlobalCppflags}", "${config.ArmCppflags}"}
+
+	cflags := []string{"-Wall", "-Werror", "-std=candcpp"}
+	cstd := []string{"-std=gnu99", "-std=conly"}
+	cppstd := []string{"-std=gnu++17", "-std=cpp", "-fno-rtti"}
+
+	lastIncludes := []string{
+		"out/soong/ndk/sysroot/usr/include",
+		"out/soong/ndk/sysroot/usr/include/arm-linux-androideabi",
+	}
+
+	combineSlices := func(slices ...[]string) []string {
+		var ret []string
+		for _, s := range slices {
+			ret = append(ret, s...)
+		}
+		return ret
+	}
+
+	testCases := []struct {
+		name     string
+		src      string
+		expected []string
+	}{
+		{
+			name:     "c",
+			src:      "foo.c",
+			expected: combineSlices(baseExpectedFlags, conly, expectedIncludes, cflags, cstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+		},
+		{
+			name:     "cc",
+			src:      "foo.cc",
+			expected: combineSlices(baseExpectedFlags, cppOnly, expectedIncludes, cflags, cppstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+		},
+		{
+			name:     "assemble",
+			src:      "foo.s",
+			expected: combineSlices(baseExpectedFlags, []string{"-D__ASSEMBLY__"}, expectedIncludes, lastIncludes),
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			bp := fmt.Sprintf(`
+		cc_library {
+			name: "libfoo",
+			srcs: ["%s"],
+			cflags: ["-std=candcpp"],
+			conlyflags: ["-std=conly"],
+			cppflags: ["-std=cpp"],
+			local_include_dirs: ["local_include_dirs"],
+			export_include_dirs: ["export_include_dirs"],
+			export_system_include_dirs: ["export_system_include_dirs"],
+			static_libs: ["libstatic1", "libstatic2"],
+			whole_static_libs: ["libwhole1", "libwhole2"],
+			shared_libs: ["libshared1", "libshared2"],
+			header_libs: ["libheader1", "libheader2"],
+			target: {
+				android: {
+					shared_libs: ["libandroid"],
+					local_include_dirs: ["android_local_include_dirs"],
+					export_include_dirs: ["android_export_include_dirs"],
+				},
+				android_arm: {
+					shared_libs: ["libandroid_arm"],
+					local_include_dirs: ["android_arm_local_include_dirs"],
+					export_include_dirs: ["android_arm_export_include_dirs"],
+				},
+				linux: {
+					shared_libs: ["liblinux"],
+					local_include_dirs: ["linux_local_include_dirs"],
+					export_include_dirs: ["linux_export_include_dirs"],
+				},
+			},
+			multilib: {
+				lib32: {
+					shared_libs: ["lib32"],
+					local_include_dirs: ["lib32_local_include_dirs"],
+					export_include_dirs: ["lib32_export_include_dirs"],
+				},
+			},
+			arch: {
+				arm: {
+					shared_libs: ["libarm"],
+					local_include_dirs: ["arm_local_include_dirs"],
+					export_include_dirs: ["arm_export_include_dirs"],
+				},
+			},
+			stl: "libc++",
+			sdk_version: "20",
+		}
+
+		cc_library_headers {
+			name: "libheader1",
+			export_include_dirs: ["libheader1"],
+			sdk_version: "20",
+			stl: "none",
+		}
+
+		cc_library_headers {
+			name: "libheader2",
+			export_include_dirs: ["libheader2"],
+			sdk_version: "20",
+			stl: "none",
+		}
+	`, tc.src)
+
+			libs := []string{
+				"libstatic1",
+				"libstatic2",
+				"libwhole1",
+				"libwhole2",
+				"libshared1",
+				"libshared2",
+				"libandroid",
+				"libandroid_arm",
+				"liblinux",
+				"lib32",
+				"libarm",
+			}
+
+			for _, lib := range libs {
+				bp += fmt.Sprintf(`
+			cc_library {
+				name: "%s",
+				export_include_dirs: ["%s"],
+				sdk_version: "20",
+				stl: "none",
+			}
+		`, lib, lib)
+			}
+
+			ctx := android.GroupFixturePreparers(
+				PrepareForIntegrationTestWithCc,
+				android.FixtureAddTextFile("external/foo/Android.bp", bp),
+			).RunTest(t)
+			// Use the arm variant instead of the arm64 variant so that it gets headers from
+			// ndk_libandroid_support to test LateStaticLibs.
+			cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
+
+			var includes []string
+			flags := strings.Split(cflags, " ")
+			for _, flag := range flags {
+				if strings.HasPrefix(flag, "-I") {
+					includes = append(includes, strings.TrimPrefix(flag, "-I"))
+				} else if flag == "-isystem" {
+					// skip isystem, include next
+				} else if len(flag) > 0 {
+					includes = append(includes, flag)
+				}
+			}
+
+			android.AssertArrayString(t, "includes", tc.expected, includes)
+		})
+	}
+
+}
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index b96d8b0..75e1faf 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -44,11 +44,9 @@
 var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil)
 
 const (
-	// Environment variables used to control the behavior of this singleton.
-	envVariableCollectCCDeps = "SOONG_COLLECT_CC_DEPS"
-	ccdepsJsonFileName       = "module_bp_cc_deps.json"
-	cClang                   = "clang"
-	cppClang                 = "clang++"
+	ccdepsJsonFileName = "module_bp_cc_deps.json"
+	cClang             = "clang"
+	cppClang           = "clang++"
 )
 
 type ccIdeInfo struct {
@@ -83,10 +81,7 @@
 }
 
 func (c *ccdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !ctx.Config().IsEnvTrue(envVariableCollectCCDeps) {
-		return
-	}
-
+	// (b/204397180) Generate module_bp_cc_deps.json by default.
 	moduleDeps := ccDeps{}
 	moduleInfos := map[string]ccIdeInfo{}
 
diff --git a/cc/cmakelists.go b/cc/cmakelists.go
index 04536fc..ad130ba 100644
--- a/cc/cmakelists.go
+++ b/cc/cmakelists.go
@@ -316,7 +316,7 @@
 	if strings.HasPrefix(parameter, "--sysroot") {
 		return systemRoot
 	}
-	if strings.HasPrefix(parameter, "-fsanitize-blacklist") {
+	if strings.HasPrefix(parameter, "-fsanitize-ignorelist") {
 		return relativeFilePathFlag
 	}
 	if strings.HasPrefix(parameter, "-fprofile-sample-use") {
diff --git a/cc/compiler.go b/cc/compiler.go
index 78a5a5d..eb5458f 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -39,6 +39,12 @@
 	// or filegroup using the syntax ":module".
 	Srcs []string `android:"path,arch_variant"`
 
+	// list of source files that should not be compiled with clang-tidy.
+	Tidy_disabled_srcs []string `android:"path,arch_variant"`
+
+	// list of source files that should not be compiled by clang-tidy when TIDY_TIMEOUT is set.
+	Tidy_timeout_srcs []string `android:"path,arch_variant"`
+
 	// list of source files that should not be used to build the C/C++ module.
 	// This is most useful in the arch/multilib variants to remove non-common files
 	Exclude_srcs []string `android:"path,arch_variant"`
@@ -92,7 +98,7 @@
 
 	// list of generated headers to add to the include path. These are the names
 	// of genrule modules.
-	Generated_headers []string `android:"arch_variant"`
+	Generated_headers []string `android:"arch_variant,variant_prepend"`
 
 	// pass -frtti instead of -fno-rtti
 	Rtti *bool
@@ -189,6 +195,11 @@
 			// variant of the C/C++ module.
 			Cflags []string
 		}
+		Platform struct {
+			// List of additional cflags that should be used to build the platform
+			// variant of the C/C++ module.
+			Cflags []string
+		}
 	}
 
 	Proto struct {
@@ -201,15 +212,6 @@
 
 	// Build and link with OpenMP
 	Openmp *bool `android:"arch_variant"`
-
-	// Deprecated.
-	// Adds __ANDROID_APEX_<APEX_MODULE_NAME>__ macro defined for apex variants in addition to __ANDROID_APEX__
-	Use_apex_name_macro *bool
-
-	// Adds two macros for apex variants in addition to __ANDROID_APEX__
-	// * __ANDROID_APEX_COM_ANDROID_FOO__
-	// * __ANDROID_APEX_NAME__="com.android.foo"
-	UseApexNameMacro bool `blueprint:"mutated"`
 }
 
 func NewBaseCompiler() *baseCompiler {
@@ -256,8 +258,12 @@
 	return []interface{}{&compiler.Properties, &compiler.Proto}
 }
 
+func includeBuildDirectory(prop *bool) bool {
+	return proptools.BoolDefault(prop, true)
+}
+
 func (compiler *baseCompiler) includeBuildDirectory() bool {
-	return proptools.BoolDefault(compiler.Properties.Include_build_directory, true)
+	return includeBuildDirectory(compiler.Properties.Include_build_directory)
 }
 
 func (compiler *baseCompiler) compilerInit(ctx BaseModuleContext) {}
@@ -279,10 +285,6 @@
 	return deps
 }
 
-func (compiler *baseCompiler) useApexNameMacro() bool {
-	return Bool(compiler.Properties.Use_apex_name_macro) || compiler.Properties.UseApexNameMacro
-}
-
 // Return true if the module is in the WarningAllowedProjects.
 func warningsAreAllowed(subdir string) bool {
 	subdir += "/"
@@ -293,6 +295,38 @@
 	getNamedMapForConfig(ctx.Config(), key).Store(module, true)
 }
 
+func maybeReplaceGnuToC(gnuExtensions *bool, cStd string, cppStd string) (string, string) {
+	if gnuExtensions != nil && *gnuExtensions == false {
+		cStd = gnuToCReplacer.Replace(cStd)
+		cppStd = gnuToCReplacer.Replace(cppStd)
+	}
+	return cStd, cppStd
+}
+
+func parseCppStd(cppStdPtr *string) string {
+	cppStd := String(cppStdPtr)
+	switch cppStd {
+	case "":
+		return config.CppStdVersion
+	case "experimental":
+		return config.ExperimentalCppStdVersion
+	default:
+		return cppStd
+	}
+}
+
+func parseCStd(cStdPtr *string) string {
+	cStd := String(cStdPtr)
+	switch cStd {
+	case "":
+		return config.CStdVersion
+	case "experimental":
+		return config.ExperimentalCStdVersion
+	default:
+		return cStd
+	}
+}
+
 // Create a Flags struct that collects the compile flags from global values,
 // per-target values, module type values, and per-module Blueprints properties
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
@@ -310,6 +344,7 @@
 	CheckBadCompilerFlags(ctx, "product.cflags", compiler.Properties.Target.Product.Cflags)
 	CheckBadCompilerFlags(ctx, "recovery.cflags", compiler.Properties.Target.Recovery.Cflags)
 	CheckBadCompilerFlags(ctx, "vendor_ramdisk.cflags", compiler.Properties.Target.Vendor_ramdisk.Cflags)
+	CheckBadCompilerFlags(ctx, "platform.cflags", compiler.Properties.Target.Platform.Cflags)
 
 	esc := proptools.NinjaAndShellEscapeList
 
@@ -371,12 +406,12 @@
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_RECOVERY__")
 	}
 
+	if ctx.inRecovery() || ctx.inRamdisk() || ctx.inVendorRamdisk() {
+		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_RAMDISK__")
+	}
+
 	if ctx.apexVariationName() != "" {
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX__")
-		if compiler.useApexNameMacro() {
-			flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX_"+makeDefineString(ctx.apexVariationName())+"__")
-			flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX_NAME__='\""+ctx.apexVariationName()+"\"'")
-		}
 		if ctx.Device() {
 			flags.Global.CommonFlags = append(flags.Global.CommonFlags,
 				fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
@@ -392,7 +427,7 @@
 	if flags.RequiredInstructionSet != "" {
 		instructionSet = flags.RequiredInstructionSet
 	}
-	instructionSetFlags, err := tc.ClangInstructionSetFlags(instructionSet)
+	instructionSetFlags, err := tc.InstructionSetFlags(instructionSet)
 	if err != nil {
 		ctx.ModuleErrorf("%s", err)
 	}
@@ -422,11 +457,9 @@
 		}
 	}
 
-	gccPrefix := "-B" + config.ToolPath(tc)
-
-	flags.Global.CFlags = append(flags.Global.CFlags, target, gccPrefix)
-	flags.Global.AsFlags = append(flags.Global.AsFlags, target, gccPrefix)
-	flags.Global.LdFlags = append(flags.Global.LdFlags, target, gccPrefix)
+	flags.Global.CFlags = append(flags.Global.CFlags, target)
+	flags.Global.AsFlags = append(flags.Global.AsFlags, target)
+	flags.Global.LdFlags = append(flags.Global.LdFlags, target)
 
 	hod := "Host"
 	if ctx.Os().Class == android.Device {
@@ -437,15 +470,15 @@
 	flags.Global.ConlyFlags = append([]string{"${config.CommonGlobalConlyflags}"}, flags.Global.ConlyFlags...)
 	flags.Global.CppFlags = append([]string{fmt.Sprintf("${config.%sGlobalCppflags}", hod)}, flags.Global.CppFlags...)
 
-	flags.Global.AsFlags = append(flags.Global.AsFlags, tc.ClangAsflags())
-	flags.Global.CppFlags = append([]string{"${config.CommonClangGlobalCppflags}"}, flags.Global.CppFlags...)
+	flags.Global.AsFlags = append(flags.Global.AsFlags, tc.Asflags())
+	flags.Global.CppFlags = append([]string{"${config.CommonGlobalCppflags}"}, flags.Global.CppFlags...)
 	flags.Global.CommonFlags = append(flags.Global.CommonFlags,
-		tc.ClangCflags(),
-		"${config.CommonClangGlobalCflags}",
-		fmt.Sprintf("${config.%sClangGlobalCflags}", hod))
+		tc.Cflags(),
+		"${config.CommonGlobalCflags}",
+		fmt.Sprintf("${config.%sGlobalCflags}", hod))
 
-	if isThirdParty(modulePath) {
-		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "${config.ClangExternalCflags}")
+	if android.IsThirdPartyPath(modulePath) {
+		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "${config.ExternalCflags}")
 	}
 
 	if tc.Bionic() {
@@ -458,31 +491,16 @@
 
 	flags.Global.AsFlags = append(flags.Global.AsFlags, "-D__ASSEMBLY__")
 
-	flags.Global.CppFlags = append(flags.Global.CppFlags, tc.ClangCppflags())
+	flags.Global.CppFlags = append(flags.Global.CppFlags, tc.Cppflags())
 
 	flags.Global.YasmFlags = append(flags.Global.YasmFlags, tc.YasmFlags())
 
-	flags.Global.CommonFlags = append(flags.Global.CommonFlags, tc.ToolchainClangCflags())
+	flags.Global.CommonFlags = append(flags.Global.CommonFlags, tc.ToolchainCflags())
 
-	cStd := config.CStdVersion
-	if String(compiler.Properties.C_std) == "experimental" {
-		cStd = config.ExperimentalCStdVersion
-	} else if String(compiler.Properties.C_std) != "" {
-		cStd = String(compiler.Properties.C_std)
-	}
+	cStd := parseCStd(compiler.Properties.C_std)
+	cppStd := parseCppStd(compiler.Properties.Cpp_std)
 
-	cppStd := String(compiler.Properties.Cpp_std)
-	switch String(compiler.Properties.Cpp_std) {
-	case "":
-		cppStd = config.CppStdVersion
-	case "experimental":
-		cppStd = config.ExperimentalCppStdVersion
-	}
-
-	if compiler.Properties.Gnu_extensions != nil && *compiler.Properties.Gnu_extensions == false {
-		cStd = gnuToCReplacer.Replace(cStd)
-		cppStd = gnuToCReplacer.Replace(cppStd)
-	}
+	cStd, cppStd = maybeReplaceGnuToC(compiler.Properties.Gnu_extensions, cStd, cppStd)
 
 	flags.Local.ConlyFlags = append([]string{"-std=" + cStd}, flags.Local.ConlyFlags...)
 	flags.Local.CppFlags = append([]string{"-std=" + cppStd}, flags.Local.CppFlags...)
@@ -502,6 +520,9 @@
 	if ctx.inVendorRamdisk() {
 		flags.Local.CFlags = append(flags.Local.CFlags, esc(compiler.Properties.Target.Vendor_ramdisk.Cflags)...)
 	}
+	if !ctx.useSdk() {
+		flags.Local.CFlags = append(flags.Local.CFlags, esc(compiler.Properties.Target.Platform.Cflags)...)
+	}
 
 	// We can enforce some rules more strictly in the code we own. strict
 	// indicates if this is code that we can be stricter with. If we have
@@ -528,11 +549,6 @@
 			"-I"+android.PathForModuleGen(ctx, "yacc", ctx.ModuleDir()).String())
 	}
 
-	if compiler.hasSrcExt(".mc") {
-		flags.Local.CommonFlags = append(flags.Local.CommonFlags,
-			"-I"+android.PathForModuleGen(ctx, "windmc", ctx.ModuleDir()).String())
-	}
-
 	if compiler.hasSrcExt(".aidl") {
 		flags.aidlFlags = append(flags.aidlFlags, compiler.Properties.Aidl.Flags...)
 		if len(compiler.Properties.Aidl.Local_include_dirs) > 0 {
@@ -548,6 +564,12 @@
 			flags.aidlFlags = append(flags.aidlFlags, "-t")
 		}
 
+		aidlMinSdkVersion := ctx.minSdkVersion()
+		if aidlMinSdkVersion == "" {
+			aidlMinSdkVersion = "platform_apis"
+		}
+		flags.aidlFlags = append(flags.aidlFlags, "--min_sdk_version="+aidlMinSdkVersion)
+
 		flags.Local.CommonFlags = append(flags.Local.CommonFlags,
 			"-I"+android.PathForModuleGen(ctx, "aidl").String())
 	}
@@ -608,10 +630,6 @@
 	return false
 }
 
-func (compiler *baseCompiler) uniqueApexVariations() bool {
-	return compiler.useApexNameMacro()
-}
-
 var invalidDefineCharRegex = regexp.MustCompile("[^a-zA-Z0-9_]")
 
 // makeDefineString transforms a name of an APEX module into a value to be used as value for C define
@@ -624,9 +642,9 @@
 
 func ndkPathDeps(ctx ModuleContext) android.Paths {
 	if ctx.Module().(*Module).IsSdkVariant() {
-		// The NDK sysroot timestamp file depends on all the NDK sysroot files
-		// (headers and libraries).
-		return android.Paths{getNdkBaseTimestampFile(ctx)}
+		// The NDK sysroot timestamp file depends on all the NDK sysroot header files
+		// for compiling src to obj files.
+		return android.Paths{getNdkHeadersTimestampFile(ctx)}
 	}
 	return nil
 }
@@ -650,7 +668,10 @@
 	compiler.srcs = srcs
 
 	// Compile files listed in c.Properties.Srcs into objects
-	objs := compileObjs(ctx, buildFlags, "", srcs, pathDeps, compiler.cFlagsDeps)
+	objs := compileObjs(ctx, buildFlags, "", srcs,
+		android.PathsForModuleSrc(ctx, compiler.Properties.Tidy_disabled_srcs),
+		android.PathsForModuleSrc(ctx, compiler.Properties.Tidy_timeout_srcs),
+		pathDeps, compiler.cFlagsDeps)
 
 	if ctx.Failed() {
 		return Objects{}
@@ -660,31 +681,10 @@
 }
 
 // Compile a list of source files into objects a specified subdirectory
-func compileObjs(ctx android.ModuleContext, flags builderFlags,
-	subdir string, srcFiles, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
+func compileObjs(ctx ModuleContext, flags builderFlags, subdir string,
+	srcFiles, noTidySrcs, timeoutTidySrcs, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
 
-	return transformSourceToObj(ctx, subdir, srcFiles, flags, pathDeps, cFlagsDeps)
-}
-
-var thirdPartyDirPrefixExceptions = []*regexp.Regexp{
-	regexp.MustCompile("^vendor/[^/]*google[^/]*/"),
-	regexp.MustCompile("^hardware/google/"),
-	regexp.MustCompile("^hardware/interfaces/"),
-	regexp.MustCompile("^hardware/libhardware[^/]*/"),
-	regexp.MustCompile("^hardware/ril/"),
-}
-
-func isThirdParty(path string) bool {
-	thirdPartyDirPrefixes := []string{"external/", "vendor/", "hardware/"}
-
-	if android.HasAnyPrefix(path, thirdPartyDirPrefixes) {
-		for _, prefix := range thirdPartyDirPrefixExceptions {
-			if prefix.MatchString(path) {
-				return false
-			}
-		}
-	}
-	return true
+	return transformSourceToObj(ctx, subdir, srcFiles, noTidySrcs, timeoutTidySrcs, flags, pathDeps, cFlagsDeps)
 }
 
 // Properties for rust_bindgen related to generating rust bindings.
diff --git a/cc/compiler_test.go b/cc/compiler_test.go
index c301388..9ae4d18 100644
--- a/cc/compiler_test.go
+++ b/cc/compiler_test.go
@@ -16,27 +16,30 @@
 
 import (
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestIsThirdParty(t *testing.T) {
-	shouldFail := []string{
+	thirdPartyPaths := []string{
 		"external/foo/",
 		"vendor/bar/",
 		"hardware/underwater_jaguar/",
 	}
-	shouldPass := []string{
+	nonThirdPartyPaths := []string{
 		"vendor/google/cts/",
 		"hardware/google/pixel",
 		"hardware/interfaces/camera",
 		"hardware/ril/supa_ril",
+		"bionic/libc",
 	}
-	for _, path := range shouldFail {
-		if !isThirdParty(path) {
+	for _, path := range thirdPartyPaths {
+		if !android.IsThirdPartyPath(path) {
 			t.Errorf("Expected %s to be considered third party", path)
 		}
 	}
-	for _, path := range shouldPass {
-		if isThirdParty(path) {
+	for _, path := range nonThirdPartyPaths {
+		if android.IsThirdPartyPath(path) {
 			t.Errorf("Expected %s to *not* be considered third party", path)
 		}
 	}
diff --git a/cc/config/Android.bp b/cc/config/Android.bp
index e4a8b62..1a21c13 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -8,23 +8,23 @@
     deps: [
         "soong-android",
         "soong-remoteexec",
+        "soong-starlark-format",
     ],
     srcs: [
-        "bp2build.go",
         "clang.go",
         "global.go",
         "tidy.go",
         "toolchain.go",
         "vndk.go",
 
+        "bionic.go",
+
         "arm_device.go",
         "arm64_device.go",
-        "arm64_fuchsia_device.go",
         "x86_device.go",
         "x86_64_device.go",
-        "x86_64_fuchsia_device.go",
 
-        "x86_darwin_host.go",
+        "darwin_host.go",
         "x86_linux_host.go",
         "x86_linux_bionic_host.go",
         "x86_windows_host.go",
@@ -32,7 +32,6 @@
         "arm64_linux_host.go",
     ],
     testSrcs: [
-        "bp2build_test.go",
         "tidy_test.go",
     ],
 }
diff --git a/cc/config/OWNERS b/cc/config/OWNERS
index 701db92..580f215 100644
--- a/cc/config/OWNERS
+++ b/cc/config/OWNERS
@@ -1,3 +1,3 @@
 per-file vndk.go = smoreland@google.com, victoryang@google.com
-per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
+per-file clang.go,global.go,tidy.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com
 
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index dbc9c38..66087e6 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -33,9 +33,7 @@
 		},
 		"armv8-a-branchprot": []string{
 			"-march=armv8-a",
-			// Disable BTI until drm vendors stop using OS libraries as sources
-			// of gadgets (https://issuetracker.google.com/216395195).
-			"-mbranch-protection=pac-ret",
+			"-mbranch-protection=standard",
 		},
 		"armv8-2a": []string{
 			"-march=armv8.2-a",
@@ -50,12 +48,12 @@
 		"-Wl,-z,separate-code",
 	}
 
-	arm64Lldflags = append(ClangFilterUnknownLldflags(arm64Ldflags),
+	arm64Lldflags = append(arm64Ldflags,
 		"-Wl,-z,max-page-size=4096")
 
 	arm64Cppflags = []string{}
 
-	arm64ClangCpuVariantCflags = map[string][]string{
+	arm64CpuVariantCflags = map[string][]string{
 		"cortex-a53": []string{
 			"-mcpu=cortex-a53",
 		},
@@ -98,64 +96,68 @@
 	pctx.SourcePathVariable("Arm64GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/aarch64/aarch64-linux-android-${arm64GccVersion}")
 
-	pctx.StaticVariable("Arm64Ldflags", strings.Join(arm64Ldflags, " "))
-	pctx.StaticVariable("Arm64Lldflags", strings.Join(arm64Lldflags, " "))
+	exportedVars.ExportStringListStaticVariable("Arm64Ldflags", arm64Ldflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Lldflags", arm64Lldflags)
 
-	pctx.StaticVariable("Arm64ClangCflags", strings.Join(ClangFilterUnknownCflags(arm64Cflags), " "))
-	pctx.StaticVariable("Arm64ClangLdflags", strings.Join(ClangFilterUnknownCflags(arm64Ldflags), " "))
-	pctx.StaticVariable("Arm64ClangLldflags", strings.Join(ClangFilterUnknownCflags(arm64Lldflags), " "))
-	pctx.StaticVariable("Arm64ClangCppflags", strings.Join(ClangFilterUnknownCflags(arm64Cppflags), " "))
+	exportedVars.ExportStringListStaticVariable("Arm64Cflags", arm64Cflags)
+	exportedVars.ExportStringListStaticVariable("Arm64Cppflags", arm64Cppflags)
 
-	pctx.StaticVariable("Arm64ClangArmv8ACflags", strings.Join(arm64ArchVariantCflags["armv8-a"], " "))
-	pctx.StaticVariable("Arm64ClangArmv8ABranchProtCflags", strings.Join(arm64ArchVariantCflags["armv8-a-branchprot"], " "))
-	pctx.StaticVariable("Arm64ClangArmv82ACflags", strings.Join(arm64ArchVariantCflags["armv8-2a"], " "))
-	pctx.StaticVariable("Arm64ClangArmv82ADotprodCflags", strings.Join(arm64ArchVariantCflags["armv8-2a-dotprod"], " "))
+	exportedVars.ExportVariableReferenceDict("Arm64ArchVariantCflags", arm64ArchVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("Arm64CpuVariantCflags", arm64CpuVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("Arm64CpuVariantLdflags", arm64CpuVariantLdflags)
 
-	pctx.StaticVariable("Arm64ClangCortexA53Cflags",
-		strings.Join(arm64ClangCpuVariantCflags["cortex-a53"], " "))
+	exportedVars.ExportStringListStaticVariable("Arm64Armv8ACflags", arm64ArchVariantCflags["armv8-a"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv8ABranchProtCflags", arm64ArchVariantCflags["armv8-a-branchprot"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv82ACflags", arm64ArchVariantCflags["armv8-2a"])
+	exportedVars.ExportStringListStaticVariable("Arm64Armv82ADotprodCflags", arm64ArchVariantCflags["armv8-2a-dotprod"])
 
-	pctx.StaticVariable("Arm64ClangCortexA55Cflags",
-		strings.Join(arm64ClangCpuVariantCflags["cortex-a55"], " "))
+	exportedVars.ExportStringListStaticVariable("Arm64CortexA53Cflags", arm64CpuVariantCflags["cortex-a53"])
+	exportedVars.ExportStringListStaticVariable("Arm64CortexA55Cflags", arm64CpuVariantCflags["cortex-a55"])
+	exportedVars.ExportStringListStaticVariable("Arm64KryoCflags", arm64CpuVariantCflags["kryo"])
+	exportedVars.ExportStringListStaticVariable("Arm64ExynosM1Cflags", arm64CpuVariantCflags["exynos-m1"])
+	exportedVars.ExportStringListStaticVariable("Arm64ExynosM2Cflags", arm64CpuVariantCflags["exynos-m2"])
 
-	pctx.StaticVariable("Arm64ClangKryoCflags",
-		strings.Join(arm64ClangCpuVariantCflags["kryo"], " "))
-
-	pctx.StaticVariable("Arm64ClangExynosM1Cflags",
-		strings.Join(arm64ClangCpuVariantCflags["exynos-m1"], " "))
-
-	pctx.StaticVariable("Arm64ClangExynosM2Cflags",
-		strings.Join(arm64ClangCpuVariantCflags["exynos-m2"], " "))
+	exportedVars.ExportStringListStaticVariable("Arm64FixCortexA53Ldflags", []string{"-Wl,--fix-cortex-a53-843419"})
 }
 
 var (
-	arm64ClangArchVariantCflagsVar = map[string]string{
-		"armv8-a":            "${config.Arm64ClangArmv8ACflags}",
-		"armv8-a-branchprot": "${config.Arm64ClangArmv8ABranchProtCflags}",
-		"armv8-2a":           "${config.Arm64ClangArmv82ACflags}",
-		"armv8-2a-dotprod":   "${config.Arm64ClangArmv82ADotprodCflags}",
+	arm64ArchVariantCflagsVar = map[string]string{
+		"armv8-a":            "${config.Arm64Armv8ACflags}",
+		"armv8-a-branchprot": "${config.Arm64Armv8ABranchProtCflags}",
+		"armv8-2a":           "${config.Arm64Armv82ACflags}",
+		"armv8-2a-dotprod":   "${config.Arm64Armv82ADotprodCflags}",
 	}
 
-	arm64ClangCpuVariantCflagsVar = map[string]string{
-		"":           "",
-		"cortex-a53": "${config.Arm64ClangCortexA53Cflags}",
-		"cortex-a55": "${config.Arm64ClangCortexA55Cflags}",
-		"cortex-a72": "${config.Arm64ClangCortexA53Cflags}",
-		"cortex-a73": "${config.Arm64ClangCortexA53Cflags}",
-		"cortex-a75": "${config.Arm64ClangCortexA55Cflags}",
-		"cortex-a76": "${config.Arm64ClangCortexA55Cflags}",
-		"kryo":       "${config.Arm64ClangKryoCflags}",
-		"kryo385":    "${config.Arm64ClangCortexA53Cflags}",
-		"exynos-m1":  "${config.Arm64ClangExynosM1Cflags}",
-		"exynos-m2":  "${config.Arm64ClangExynosM2Cflags}",
+	arm64CpuVariantCflagsVar = map[string]string{
+		"cortex-a53": "${config.Arm64CortexA53Cflags}",
+		"cortex-a55": "${config.Arm64CortexA55Cflags}",
+		"cortex-a72": "${config.Arm64CortexA53Cflags}",
+		"cortex-a73": "${config.Arm64CortexA53Cflags}",
+		"cortex-a75": "${config.Arm64CortexA55Cflags}",
+		"cortex-a76": "${config.Arm64CortexA55Cflags}",
+		"kryo":       "${config.Arm64KryoCflags}",
+		"kryo385":    "${config.Arm64CortexA53Cflags}",
+		"exynos-m1":  "${config.Arm64ExynosM1Cflags}",
+		"exynos-m2":  "${config.Arm64ExynosM2Cflags}",
+	}
+
+	arm64CpuVariantLdflags = map[string]string{
+		"cortex-a53": "${config.Arm64FixCortexA53Ldflags}",
+		"cortex-a72": "${config.Arm64FixCortexA53Ldflags}",
+		"cortex-a73": "${config.Arm64FixCortexA53Ldflags}",
+		"kryo":       "${config.Arm64FixCortexA53Ldflags}",
+		"exynos-m1":  "${config.Arm64FixCortexA53Ldflags}",
+		"exynos-m2":  "${config.Arm64FixCortexA53Ldflags}",
 	}
 )
 
 type toolchainArm64 struct {
+	toolchainBionic
 	toolchain64Bit
 
-	ldflags              string
-	lldflags             string
-	toolchainClangCflags string
+	ldflags         string
+	lldflags        string
+	toolchainCflags string
 }
 
 func (t *toolchainArm64) Name() string {
@@ -182,24 +184,24 @@
 	return t.GccTriple()
 }
 
-func (t *toolchainArm64) ClangCflags() string {
-	return "${config.Arm64ClangCflags}"
+func (t *toolchainArm64) Cflags() string {
+	return "${config.Arm64Cflags}"
 }
 
-func (t *toolchainArm64) ClangCppflags() string {
-	return "${config.Arm64ClangCppflags}"
+func (t *toolchainArm64) Cppflags() string {
+	return "${config.Arm64Cppflags}"
 }
 
-func (t *toolchainArm64) ClangLdflags() string {
+func (t *toolchainArm64) Ldflags() string {
 	return t.ldflags
 }
 
-func (t *toolchainArm64) ClangLldflags() string {
+func (t *toolchainArm64) Lldflags() string {
 	return t.lldflags
 }
 
-func (t *toolchainArm64) ToolchainClangCflags() string {
-	return t.toolchainClangCflags
+func (t *toolchainArm64) ToolchainCflags() string {
+	return t.toolchainCflags
 }
 
 func (toolchainArm64) LibclangRuntimeLibraryArch() string {
@@ -217,16 +219,11 @@
 		panic(fmt.Sprintf("Unknown ARM architecture version: %q", arch.ArchVariant))
 	}
 
-	toolchainClangCflags := []string{arm64ClangArchVariantCflagsVar[arch.ArchVariant]}
-	toolchainClangCflags = append(toolchainClangCflags,
-		variantOrDefault(arm64ClangCpuVariantCflagsVar, arch.CpuVariant))
+	toolchainCflags := []string{arm64ArchVariantCflagsVar[arch.ArchVariant]}
+	toolchainCflags = append(toolchainCflags,
+		variantOrDefault(arm64CpuVariantCflagsVar, arch.CpuVariant))
 
-	var extraLdflags string
-	switch arch.CpuVariant {
-	case "cortex-a53", "cortex-a72", "cortex-a73", "kryo", "exynos-m1", "exynos-m2":
-		extraLdflags = "-Wl,--fix-cortex-a53-843419"
-	}
-
+	extraLdflags := variantOrDefault(arm64CpuVariantLdflags, arch.CpuVariant)
 	return &toolchainArm64{
 		ldflags: strings.Join([]string{
 			"${config.Arm64Ldflags}",
@@ -236,7 +233,7 @@
 			"${config.Arm64Lldflags}",
 			extraLdflags,
 		}, " "),
-		toolchainClangCflags: strings.Join(toolchainClangCflags, " "),
+		toolchainCflags: strings.Join(toolchainCflags, " "),
 	}
 }
 
diff --git a/cc/config/arm64_fuchsia_device.go b/cc/config/arm64_fuchsia_device.go
deleted file mode 100644
index 02c0c14..0000000
--- a/cc/config/arm64_fuchsia_device.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// 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 config
-
-import (
-	"android/soong/android"
-)
-
-var fuchsiaArm64SysRoot string = "prebuilts/fuchsia_sdk/arch/arm64/sysroot"
-var fuchsiaArm64PrebuiltLibsRoot string = "fuchsia/prebuilt_libs/"
-
-type toolchainFuchsiaArm64 struct {
-	toolchain64Bit
-	toolchainFuchsia
-}
-
-func (t *toolchainFuchsiaArm64) Name() string {
-	return "arm64"
-}
-
-func (t *toolchainFuchsiaArm64) GccRoot() string {
-	return "${config.Arm64GccRoot}"
-}
-
-func (t *toolchainFuchsiaArm64) GccTriple() string {
-	return "aarch64-linux-android"
-}
-
-func (t *toolchainFuchsiaArm64) GccVersion() string {
-	return arm64GccVersion
-}
-
-func (t *toolchainFuchsiaArm64) Cflags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaArm64) Cppflags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaArm64) Ldflags() string {
-	return "-Wl,--fix-cortex-a53-843419"
-}
-
-func (t *toolchainFuchsiaArm64) IncludeFlags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaArm64) ToolchainCflags() string {
-	return "-mcpu=cortex-a53"
-}
-
-func (t *toolchainFuchsiaArm64) ClangTriple() string {
-	return "arm64-fuchsia-android"
-}
-
-func (t *toolchainFuchsiaArm64) ClangCppflags() string {
-	return "-Wno-error=deprecated-declarations"
-}
-
-func (t *toolchainFuchsiaArm64) ClangLdflags() string {
-	return "--target=arm64-fuchsia --sysroot=" + fuchsiaArm64SysRoot + " -L" + fuchsiaArm64PrebuiltLibsRoot + "/aarch64-fuchsia/lib " + "-Lprebuilts/fuchsia_sdk/arch/arm64/dist/"
-}
-
-func (t *toolchainFuchsiaArm64) ClangLldflags() string {
-	return "--target=arm64-fuchsia --sysroot=" + fuchsiaArm64SysRoot + " -L" + fuchsiaArm64PrebuiltLibsRoot + "/aarch64-fuchsia/lib " + "-Lprebuilts/fuchsia_sdk/arch/arm64/dist/"
-}
-
-func (t *toolchainFuchsiaArm64) ClangCflags() string {
-	return "--target=arm64-fuchsia --sysroot=" + fuchsiaArm64SysRoot + " -I" + fuchsiaArm64SysRoot + "/include"
-}
-
-func (t *toolchainFuchsiaArm64) Bionic() bool {
-	return false
-}
-
-func (t *toolchainFuchsiaArm64) ToolchainClangCflags() string {
-	return "-march=armv8-a"
-}
-
-var toolchainArm64FuchsiaSingleton Toolchain = &toolchainFuchsiaArm64{}
-
-func arm64FuchsiaToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainArm64FuchsiaSingleton
-}
-
-func init() {
-	registerToolchainFactory(android.Fuchsia, android.Arm64, arm64FuchsiaToolchainFactory)
-}
diff --git a/cc/config/arm64_linux_host.go b/cc/config/arm64_linux_host.go
index 59c52d1..5c7f926 100644
--- a/cc/config/arm64_linux_host.go
+++ b/cc/config/arm64_linux_host.go
@@ -15,14 +15,15 @@
 package config
 
 import (
-	"android/soong/android"
 	"strings"
+
+	"android/soong/android"
 )
 
 var (
 	// This is a host toolchain but flags for device toolchain are required
 	// as the flags are actually for Bionic-based builds.
-	linuxCrossCflags = ClangFilterUnknownCflags(append(deviceGlobalCflags,
+	linuxCrossCflags = append(deviceGlobalCflags,
 		// clang by default enables PIC when the clang triple is set to *-android.
 		// See toolchain/llvm-project/clang/lib/Driver/ToolChains/CommonArgs.cpp#920.
 		// However, for this host target, we don't set "-android" to avoid __ANDROID__ macro
@@ -33,18 +34,27 @@
 		// This is normally in ClangExtraTargetCflags, but that's for device and we need
 		// the same for host
 		"-nostdlibinc",
-	))
+	)
 
-	linuxCrossLdflags = ClangFilterUnknownCflags([]string{
+	linuxCrossLdflags = []string{
 		"-Wl,-z,noexecstack",
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--hash-style=gnu",
 		"-Wl,--no-undefined-version",
-	})
+	}
+
+	// Embed the linker into host bionic binaries. This is needed to support host bionic,
+	// as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
+	// either an absolute path, or relative from CWD. To work around this, we extract
+	// the load sections from the runtime linker ELF binary and embed them into each host
+	// bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
+	// binary, and then we use a special entry point to fix up the arguments passed by
+	// the kernel before jumping to the embedded linker.
+	linuxArm64CrtBeginSharedBinary = append(android.CopyOf(bionicCrtBeginSharedBinary),
+		"host_bionic_linker_script")
 )
 
 func init() {
@@ -63,14 +73,18 @@
 	return "aarch64-linux"
 }
 
-func (toolchainLinuxArm64) ClangCflags() string {
+func (toolchainLinuxArm64) Cflags() string {
 	// The inherited flags + extra flags
-	return "${config.Arm64ClangCflags} ${config.LinuxBionicArm64Cflags}"
+	return "${config.Arm64Cflags} ${config.LinuxBionicArm64Cflags}"
+}
+
+func (toolchainLinuxArm64) CrtBeginSharedBinary() []string {
+	return linuxArm64CrtBeginSharedBinary
 }
 
 func linuxArm64ToolchainFactory(arch android.Arch) Toolchain {
 	archVariant := "armv8-a" // for host, default to armv8-a
-	toolchainClangCflags := []string{arm64ClangArchVariantCflagsVar[archVariant]}
+	toolchainCflags := []string{arm64ArchVariantCflagsVar[archVariant]}
 
 	// We don't specify CPU architecture for host. Conservatively assume
 	// the host CPU needs the fix
@@ -89,7 +103,7 @@
 		"${config.LinuxBionicArm64Ldflags}",
 		extraLdflags,
 	}, " ")
-	ret.toolchainArm64.toolchainClangCflags = strings.Join(toolchainClangCflags, " ")
+	ret.toolchainArm64.toolchainCflags = strings.Join(toolchainCflags, " ")
 	return &ret
 }
 
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index a402f8f..d702c61 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -23,7 +23,6 @@
 
 var (
 	armToolchainCflags = []string{
-		"-mthumb-interwork",
 		"-msoft-float",
 	}
 
@@ -38,7 +37,11 @@
 		"-Wl,-m,armelf",
 	}
 
-	armLldflags = ClangFilterUnknownLldflags(armLdflags)
+	armLldflags = armLdflags
+
+	armFixCortexA8LdFlags = []string{"-Wl,--fix-cortex-a8"}
+
+	armNoFixCortexA8LdFlags = []string{"-Wl,--no-fix-cortex-a8"}
 
 	armArmCflags = []string{
 		"-fstrict-aliasing",
@@ -49,7 +52,7 @@
 		"-Os",
 	}
 
-	armClangArchVariantCflags = map[string][]string{
+	armArchVariantCflags = map[string][]string{
 		"armv7-a": []string{
 			"-march=armv7-a",
 			"-mfloat-abi=softfp",
@@ -72,7 +75,7 @@
 		},
 	}
 
-	armClangCpuVariantCflags = map[string][]string{
+	armCpuVariantCflags = map[string][]string{
 		"cortex-a7": []string{
 			"-mcpu=cortex-a7",
 			"-mfpu=neon-vfpv4",
@@ -163,95 +166,93 @@
 )
 
 const (
+	name          = "arm"
 	armGccVersion = "4.9"
+	gccTriple     = "arm-linux-androideabi"
+	clangTriple   = "armv7a-linux-androideabi"
 )
 
 func init() {
 	pctx.StaticVariable("armGccVersion", armGccVersion)
 
-	pctx.SourcePathVariable("ArmGccRoot",
-		"prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}")
+	pctx.SourcePathVariable("ArmGccRoot", "prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}")
 
-	pctx.StaticVariable("ArmLdflags", strings.Join(armLdflags, " "))
-	pctx.StaticVariable("ArmLldflags", strings.Join(armLldflags, " "))
+	// Just exported. Not created as a Ninja static variable.
+	exportedVars.ExportString("ArmClangTriple", clangTriple)
+
+	exportedVars.ExportStringListStaticVariable("ArmLdflags", armLdflags)
+	exportedVars.ExportStringListStaticVariable("ArmLldflags", armLldflags)
+
+	exportedVars.ExportStringListStaticVariable("ArmFixCortexA8LdFlags", armFixCortexA8LdFlags)
+	exportedVars.ExportStringListStaticVariable("ArmNoFixCortexA8LdFlags", armNoFixCortexA8LdFlags)
 
 	// Clang cflags
-	pctx.StaticVariable("ArmToolchainClangCflags", strings.Join(ClangFilterUnknownCflags(armToolchainCflags), " "))
-	pctx.StaticVariable("ArmClangCflags", strings.Join(ClangFilterUnknownCflags(armCflags), " "))
-	pctx.StaticVariable("ArmClangLdflags", strings.Join(ClangFilterUnknownCflags(armLdflags), " "))
-	pctx.StaticVariable("ArmClangLldflags", strings.Join(ClangFilterUnknownCflags(armLldflags), " "))
-	pctx.StaticVariable("ArmClangCppflags", strings.Join(ClangFilterUnknownCflags(armCppflags), " "))
+	exportedVars.ExportStringListStaticVariable("ArmToolchainCflags", armToolchainCflags)
+	exportedVars.ExportStringListStaticVariable("ArmCflags", armCflags)
+	exportedVars.ExportStringListStaticVariable("ArmCppflags", armCppflags)
 
 	// Clang ARM vs. Thumb instruction set cflags
-	pctx.StaticVariable("ArmClangArmCflags", strings.Join(ClangFilterUnknownCflags(armArmCflags), " "))
-	pctx.StaticVariable("ArmClangThumbCflags", strings.Join(ClangFilterUnknownCflags(armThumbCflags), " "))
+	exportedVars.ExportStringListStaticVariable("ArmArmCflags", armArmCflags)
+	exportedVars.ExportStringListStaticVariable("ArmThumbCflags", armThumbCflags)
+
+	exportedVars.ExportVariableReferenceDict("ArmArchVariantCflags", armArchVariantCflagsVar)
+	exportedVars.ExportVariableReferenceDict("ArmCpuVariantCflags", armCpuVariantCflagsVar)
 
 	// Clang arch variant cflags
-	pctx.StaticVariable("ArmClangArmv7ACflags",
-		strings.Join(armClangArchVariantCflags["armv7-a"], " "))
-	pctx.StaticVariable("ArmClangArmv7ANeonCflags",
-		strings.Join(armClangArchVariantCflags["armv7-a-neon"], " "))
-	pctx.StaticVariable("ArmClangArmv8ACflags",
-		strings.Join(armClangArchVariantCflags["armv8-a"], " "))
-	pctx.StaticVariable("ArmClangArmv82ACflags",
-		strings.Join(armClangArchVariantCflags["armv8-2a"], " "))
+	exportedVars.ExportStringListStaticVariable("ArmArmv7ACflags", armArchVariantCflags["armv7-a"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv7ANeonCflags", armArchVariantCflags["armv7-a-neon"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv8ACflags", armArchVariantCflags["armv8-a"])
+	exportedVars.ExportStringListStaticVariable("ArmArmv82ACflags", armArchVariantCflags["armv8-2a"])
 
 	// Clang cpu variant cflags
-	pctx.StaticVariable("ArmClangGenericCflags",
-		strings.Join(armClangCpuVariantCflags[""], " "))
-	pctx.StaticVariable("ArmClangCortexA7Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a7"], " "))
-	pctx.StaticVariable("ArmClangCortexA8Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a8"], " "))
-	pctx.StaticVariable("ArmClangCortexA15Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a15"], " "))
-	pctx.StaticVariable("ArmClangCortexA53Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a53"], " "))
-	pctx.StaticVariable("ArmClangCortexA55Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a55"], " "))
-	pctx.StaticVariable("ArmClangKraitCflags",
-		strings.Join(armClangCpuVariantCflags["krait"], " "))
-	pctx.StaticVariable("ArmClangKryoCflags",
-		strings.Join(armClangCpuVariantCflags["kryo"], " "))
+	exportedVars.ExportStringListStaticVariable("ArmGenericCflags", armCpuVariantCflags[""])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA7Cflags", armCpuVariantCflags["cortex-a7"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA8Cflags", armCpuVariantCflags["cortex-a8"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA15Cflags", armCpuVariantCflags["cortex-a15"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA53Cflags", armCpuVariantCflags["cortex-a53"])
+	exportedVars.ExportStringListStaticVariable("ArmCortexA55Cflags", armCpuVariantCflags["cortex-a55"])
+	exportedVars.ExportStringListStaticVariable("ArmKraitCflags", armCpuVariantCflags["krait"])
+	exportedVars.ExportStringListStaticVariable("ArmKryoCflags", armCpuVariantCflags["kryo"])
 }
 
 var (
-	armClangArchVariantCflagsVar = map[string]string{
-		"armv7-a":      "${config.ArmClangArmv7ACflags}",
-		"armv7-a-neon": "${config.ArmClangArmv7ANeonCflags}",
-		"armv8-a":      "${config.ArmClangArmv8ACflags}",
-		"armv8-2a":     "${config.ArmClangArmv82ACflags}",
+	armArchVariantCflagsVar = map[string]string{
+		"armv7-a":      "${config.ArmArmv7ACflags}",
+		"armv7-a-neon": "${config.ArmArmv7ANeonCflags}",
+		"armv8-a":      "${config.ArmArmv8ACflags}",
+		"armv8-2a":     "${config.ArmArmv82ACflags}",
 	}
 
-	armClangCpuVariantCflagsVar = map[string]string{
-		"":               "${config.ArmClangGenericCflags}",
-		"cortex-a7":      "${config.ArmClangCortexA7Cflags}",
-		"cortex-a8":      "${config.ArmClangCortexA8Cflags}",
-		"cortex-a15":     "${config.ArmClangCortexA15Cflags}",
-		"cortex-a53":     "${config.ArmClangCortexA53Cflags}",
-		"cortex-a53.a57": "${config.ArmClangCortexA53Cflags}",
-		"cortex-a55":     "${config.ArmClangCortexA55Cflags}",
-		"cortex-a72":     "${config.ArmClangCortexA53Cflags}",
-		"cortex-a73":     "${config.ArmClangCortexA53Cflags}",
-		"cortex-a75":     "${config.ArmClangCortexA55Cflags}",
-		"cortex-a76":     "${config.ArmClangCortexA55Cflags}",
-		"krait":          "${config.ArmClangKraitCflags}",
-		"kryo":           "${config.ArmClangKryoCflags}",
-		"kryo385":        "${config.ArmClangCortexA53Cflags}",
-		"exynos-m1":      "${config.ArmClangCortexA53Cflags}",
-		"exynos-m2":      "${config.ArmClangCortexA53Cflags}",
+	armCpuVariantCflagsVar = map[string]string{
+		"":               "${config.ArmGenericCflags}",
+		"cortex-a7":      "${config.ArmCortexA7Cflags}",
+		"cortex-a8":      "${config.ArmCortexA8Cflags}",
+		"cortex-a15":     "${config.ArmCortexA15Cflags}",
+		"cortex-a53":     "${config.ArmCortexA53Cflags}",
+		"cortex-a53.a57": "${config.ArmCortexA53Cflags}",
+		"cortex-a55":     "${config.ArmCortexA55Cflags}",
+		"cortex-a72":     "${config.ArmCortexA53Cflags}",
+		"cortex-a73":     "${config.ArmCortexA53Cflags}",
+		"cortex-a75":     "${config.ArmCortexA55Cflags}",
+		"cortex-a76":     "${config.ArmCortexA55Cflags}",
+		"krait":          "${config.ArmKraitCflags}",
+		"kryo":           "${config.ArmKryoCflags}",
+		"kryo385":        "${config.ArmCortexA53Cflags}",
+		"exynos-m1":      "${config.ArmCortexA53Cflags}",
+		"exynos-m2":      "${config.ArmCortexA53Cflags}",
 	}
 )
 
 type toolchainArm struct {
+	toolchainBionic
 	toolchain32Bit
-	ldflags              string
-	lldflags             string
-	toolchainClangCflags string
+	ldflags         string
+	lldflags        string
+	toolchainCflags string
 }
 
 func (t *toolchainArm) Name() string {
-	return "arm"
+	return name
 }
 
 func (t *toolchainArm) GccRoot() string {
@@ -259,7 +260,7 @@
 }
 
 func (t *toolchainArm) GccTriple() string {
-	return "arm-linux-androideabi"
+	return gccTriple
 }
 
 func (t *toolchainArm) GccVersion() string {
@@ -272,7 +273,7 @@
 
 func (t *toolchainArm) ClangTriple() string {
 	// http://b/72619014 work around llvm LTO bug.
-	return "armv7a-linux-androideabi"
+	return clangTriple
 }
 
 func (t *toolchainArm) ndkTriple() string {
@@ -280,62 +281,62 @@
 	return t.GccTriple()
 }
 
-func (t *toolchainArm) ToolchainClangCflags() string {
-	return t.toolchainClangCflags
+func (t *toolchainArm) ToolchainCflags() string {
+	return t.toolchainCflags
 }
 
-func (t *toolchainArm) ClangCflags() string {
-	return "${config.ArmClangCflags}"
+func (t *toolchainArm) Cflags() string {
+	return "${config.ArmCflags}"
 }
 
-func (t *toolchainArm) ClangCppflags() string {
-	return "${config.ArmClangCppflags}"
+func (t *toolchainArm) Cppflags() string {
+	return "${config.ArmCppflags}"
 }
 
-func (t *toolchainArm) ClangLdflags() string {
+func (t *toolchainArm) Ldflags() string {
 	return t.ldflags
 }
 
-func (t *toolchainArm) ClangLldflags() string {
+func (t *toolchainArm) Lldflags() string {
 	return t.lldflags // TODO: handle V8 cases
 }
 
-func (t *toolchainArm) ClangInstructionSetFlags(isa string) (string, error) {
+func (t *toolchainArm) InstructionSetFlags(isa string) (string, error) {
 	switch isa {
 	case "arm":
-		return "${config.ArmClangArmCflags}", nil
+		return "${config.ArmArmCflags}", nil
 	case "thumb", "":
-		return "${config.ArmClangThumbCflags}", nil
+		return "${config.ArmThumbCflags}", nil
 	default:
-		return t.toolchainBase.ClangInstructionSetFlags(isa)
+		return t.toolchainBase.InstructionSetFlags(isa)
 	}
 }
 
 func (toolchainArm) LibclangRuntimeLibraryArch() string {
-	return "arm"
+	return name
 }
 
 func armToolchainFactory(arch android.Arch) Toolchain {
 	var fixCortexA8 string
-	toolchainClangCflags := make([]string, 2, 3)
+	toolchainCflags := make([]string, 2, 3)
 
-	toolchainClangCflags[0] = "${config.ArmToolchainClangCflags}"
-	toolchainClangCflags[1] = armClangArchVariantCflagsVar[arch.ArchVariant]
+	toolchainCflags[0] = "${config.ArmToolchainCflags}"
+	toolchainCflags[1] = armArchVariantCflagsVar[arch.ArchVariant]
 
-	toolchainClangCflags = append(toolchainClangCflags,
-		variantOrDefault(armClangCpuVariantCflagsVar, arch.CpuVariant))
+	toolchainCflags = append(toolchainCflags,
+		variantOrDefault(armCpuVariantCflagsVar, arch.CpuVariant))
 
 	switch arch.ArchVariant {
 	case "armv7-a-neon":
 		switch arch.CpuVariant {
 		case "cortex-a8", "":
 			// Generic ARM might be a Cortex A8 -- better safe than sorry
-			fixCortexA8 = "-Wl,--fix-cortex-a8"
+			fixCortexA8 = "${config.ArmFixCortexA8LdFlags}"
 		default:
-			fixCortexA8 = "-Wl,--no-fix-cortex-a8"
+			fixCortexA8 = "${config.ArmNoFixCortexA8LdFlags}"
 		}
 	case "armv7-a":
-		fixCortexA8 = "-Wl,--fix-cortex-a8"
+		fixCortexA8 = "${config.ArmFixCortexA8LdFlags}"
 	case "armv8-a", "armv8-2a":
 		// Nothing extra for armv8-a/armv8-2a
 	default:
@@ -347,8 +348,8 @@
 			"${config.ArmLdflags}",
 			fixCortexA8,
 		}, " "),
-		lldflags:             "${config.ArmLldflags}",
-		toolchainClangCflags: strings.Join(toolchainClangCflags, " "),
+		lldflags:        "${config.ArmLldflags}",
+		toolchainCflags: strings.Join(toolchainCflags, " "),
 	}
 }
 
diff --git a/cc/config/bionic.go b/cc/config/bionic.go
new file mode 100644
index 0000000..e87f571
--- /dev/null
+++ b/cc/config/bionic.go
@@ -0,0 +1,37 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+type toolchainBionic struct {
+}
+
+var (
+	bionicDefaultSharedLibraries = []string{"libc", "libm", "libdl"}
+
+	bionicCrtBeginStaticBinary, bionicCrtEndStaticBinary   = []string{"crtbegin_static"}, []string{"crtend_android"}
+	bionicCrtBeginSharedBinary, bionicCrtEndSharedBinary   = []string{"crtbegin_dynamic"}, []string{"crtend_android"}
+	bionicCrtBeginSharedLibrary, bionicCrtEndSharedLibrary = []string{"crtbegin_so"}, []string{"crtend_so"}
+)
+
+func (toolchainBionic) Bionic() bool { return true }
+
+func (toolchainBionic) DefaultSharedLibraries() []string { return bionicDefaultSharedLibraries }
+
+func (toolchainBionic) CrtBeginStaticBinary() []string  { return bionicCrtBeginStaticBinary }
+func (toolchainBionic) CrtBeginSharedBinary() []string  { return bionicCrtBeginSharedBinary }
+func (toolchainBionic) CrtBeginSharedLibrary() []string { return bionicCrtBeginSharedLibrary }
+func (toolchainBionic) CrtEndStaticBinary() []string    { return bionicCrtEndStaticBinary }
+func (toolchainBionic) CrtEndSharedBinary() []string    { return bionicCrtEndSharedBinary }
+func (toolchainBionic) CrtEndSharedLibrary() []string   { return bionicCrtEndSharedLibrary }
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
deleted file mode 100644
index 16892e6..0000000
--- a/cc/config/bp2build.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// 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 config
-
-import (
-	"android/soong/android"
-	"fmt"
-	"regexp"
-	"strings"
-)
-
-// Helpers for exporting cc configuration information to Bazel.
-
-var (
-	// Map containing toolchain variables that are independent of the
-	// environment variables of the build.
-	exportedVars = exportedVariablesMap{}
-)
-
-// variableValue is a string slice because the exported variables are all lists
-// of string, and it's simpler to manipulate string lists before joining them
-// into their final string representation.
-type variableValue []string
-
-// envDependentVariable is a toolchain variable computed based on an environment variable.
-type exportedVariablesMap map[string]variableValue
-
-func (m exportedVariablesMap) Set(k string, v variableValue) {
-	m[k] = v
-}
-
-// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
-func staticVariableExportedToBazel(name string, value []string) {
-	pctx.StaticVariable(name, strings.Join(value, " "))
-	exportedVars.Set(name, variableValue(value))
-}
-
-// BazelCcToolchainVars generates bzl file content containing variables for
-// Bazel's cc_toolchain configuration.
-func BazelCcToolchainVars() string {
-	ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
-
-	// Ensure that string s has no invalid characters to be generated into the bzl file.
-	validateCharacters := func(s string) string {
-		for _, c := range []string{`\n`, `"`, `\`} {
-			if strings.Contains(s, c) {
-				panic(fmt.Errorf("%s contains illegal character %s", s, c))
-			}
-		}
-		return s
-	}
-
-	// For each exported variable, recursively expand elements in the variableValue
-	// list to ensure that interpolated variables are expanded according to their values
-	// in the exportedVars scope.
-	for _, k := range android.SortedStringKeys(exportedVars) {
-		variableValue := exportedVars[k]
-		var expandedVars []string
-		for _, v := range variableValue {
-			expandedVars = append(expandedVars, expandVar(v, exportedVars)...)
-		}
-		// Build the list for this variable.
-		list := "["
-		for _, flag := range expandedVars {
-			list += fmt.Sprintf("\n    \"%s\",", validateCharacters(flag))
-		}
-		list += "\n]"
-		// Assign the list as a bzl-private variable; this variable will be exported
-		// out through a constants struct later.
-		ret += fmt.Sprintf("_%s = %s\n", k, list)
-		ret += "\n"
-	}
-
-	// Build the exported constants struct.
-	ret += "constants = struct(\n"
-	for _, k := range android.SortedStringKeys(exportedVars) {
-		ret += fmt.Sprintf("    %s = _%s,\n", k, k)
-	}
-	ret += ")"
-	return ret
-}
-
-// expandVar recursively expand interpolated variables in the exportedVars scope.
-//
-// We're using a string slice to track the seen variables to avoid
-// stackoverflow errors with infinite recursion. it's simpler to use a
-// string slice than to handle a pass-by-referenced map, which would make it
-// quite complex to track depth-first interpolations. It's also unlikely the
-// interpolation stacks are deep (n > 1).
-func expandVar(toExpand string, exportedVars map[string]variableValue) []string {
-	// e.g. "${ClangExternalCflags}"
-	r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
-
-	// Internal recursive function.
-	var expandVarInternal func(string, map[string]bool) []string
-	expandVarInternal = func(toExpand string, seenVars map[string]bool) []string {
-		var ret []string
-		for _, v := range strings.Split(toExpand, " ") {
-			matches := r.FindStringSubmatch(v)
-			if len(matches) == 0 {
-				return []string{v}
-			}
-
-			if len(matches) != 2 {
-				panic(fmt.Errorf(
-					"Expected to only match 1 subexpression in %s, got %d",
-					v,
-					len(matches)-1))
-			}
-
-			// Index 1 of FindStringSubmatch contains the subexpression match
-			// (variable name) of the capture group.
-			variable := matches[1]
-			// toExpand contains a variable.
-			if _, ok := seenVars[variable]; ok {
-				panic(fmt.Errorf(
-					"Unbounded recursive interpolation of variable: %s", variable))
-			}
-			// A map is passed-by-reference. Create a new map for
-			// this scope to prevent variables seen in one depth-first expansion
-			// to be also treated as "seen" in other depth-first traversals.
-			newSeenVars := map[string]bool{}
-			for k := range seenVars {
-				newSeenVars[k] = true
-			}
-			newSeenVars[variable] = true
-			unexpandedVars := exportedVars[variable]
-			for _, unexpandedVar := range unexpandedVars {
-				ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
-			}
-		}
-		return ret
-	}
-
-	return expandVarInternal(toExpand, map[string]bool{})
-}
diff --git a/cc/config/bp2build_test.go b/cc/config/bp2build_test.go
deleted file mode 100644
index 7744b4b..0000000
--- a/cc/config/bp2build_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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 config
-
-import (
-	"testing"
-)
-
-func TestExpandVars(t *testing.T) {
-	testCases := []struct {
-		description    string
-		exportedVars   map[string]variableValue
-		toExpand       string
-		expectedValues []string
-	}{
-		{
-			description: "single level expansion",
-			exportedVars: map[string]variableValue{
-				"foo": variableValue([]string{"bar"}),
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"bar"},
-		},
-		{
-			description: "double level expansion",
-			exportedVars: map[string]variableValue{
-				"foo": variableValue([]string{"${bar}"}),
-				"bar": variableValue([]string{"baz"}),
-			},
-			toExpand:       "${foo}",
-			expectedValues: []string{"baz"},
-		},
-		{
-			description: "double level expansion with a literal",
-			exportedVars: map[string]variableValue{
-				"a": variableValue([]string{"${b}", "c"}),
-				"b": variableValue([]string{"d"}),
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"d", "c"},
-		},
-		{
-			description: "double level expansion, with two variables in a string",
-			exportedVars: map[string]variableValue{
-				"a": variableValue([]string{"${b} ${c}"}),
-				"b": variableValue([]string{"d"}),
-				"c": variableValue([]string{"e"}),
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"d", "e"},
-		},
-		{
-			description: "triple level expansion with two variables in a string",
-			exportedVars: map[string]variableValue{
-				"a": variableValue([]string{"${b} ${c}"}),
-				"b": variableValue([]string{"${c}", "${d}"}),
-				"c": variableValue([]string{"${d}"}),
-				"d": variableValue([]string{"foo"}),
-			},
-			toExpand:       "${a}",
-			expectedValues: []string{"foo", "foo", "foo"},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.description, func(t *testing.T) {
-			output := expandVar(testCase.toExpand, testCase.exportedVars)
-			if len(output) != len(testCase.expectedValues) {
-				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
-			}
-			for i, actual := range output {
-				expectedValue := testCase.expectedValues[i]
-				if actual != expectedValue {
-					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
-				}
-			}
-		})
-	}
-}
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 4fbb9c3..3caa688 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -15,9 +15,10 @@
 package config
 
 import (
-	"android/soong/android"
 	"sort"
 	"strings"
+
+	"android/soong/android"
 )
 
 // Cflags that should be filtered out when compiling with clang
@@ -30,23 +31,15 @@
 	"-fno-tree-sra",
 	"-fprefetch-loop-arrays",
 	"-funswitch-loops",
-	"-Werror=unused-but-set-parameter",
-	"-Werror=unused-but-set-variable",
 	"-Wmaybe-uninitialized",
 	"-Wno-error=clobbered",
 	"-Wno-error=maybe-uninitialized",
-	"-Wno-error=unused-but-set-parameter",
-	"-Wno-error=unused-but-set-variable",
 	"-Wno-extended-offsetof",
 	"-Wno-free-nonheap-object",
 	"-Wno-literal-suffix",
 	"-Wno-maybe-uninitialized",
 	"-Wno-old-style-declaration",
-	"-Wno-unused-but-set-parameter",
-	"-Wno-unused-but-set-variable",
 	"-Wno-unused-local-typedefs",
-	"-Wunused-but-set-parameter",
-	"-Wunused-but-set-variable",
 	"-fdiagnostics-color",
 	// http://b/153759688
 	"-fuse-init-array",
@@ -80,12 +73,6 @@
 	"--enable-stdcall-fixup",
 })
 
-// Ldflags that should be filtered out when linking with clang lld
-var ClangUnknownLldflags = sorted([]string{
-	"-Wl,--fix-cortex-a8",
-	"-Wl,--no-fix-cortex-a8",
-})
-
 var ClangLibToolingUnknownCflags = sorted([]string{})
 
 // List of tidy checks that should be disabled globally. When the compiler is
@@ -97,138 +84,6 @@
 	"readability-function-cognitive-complexity", // http://b/175055536
 }
 
-func init() {
-	staticVariableExportedToBazel("ClangExtraCflags", []string{
-		"-D__compiler_offsetof=__builtin_offsetof",
-
-		// Emit address-significance table which allows linker to perform safe ICF. Clang does
-		// not emit the table by default on Android since NDK still uses GNU binutils.
-		"-faddrsig",
-
-		// Turn on -fcommon explicitly, since Clang now defaults to -fno-common. The cleanup bug
-		// tracking this is http://b/151457797.
-		"-fcommon",
-
-		// Help catch common 32/64-bit errors.
-		"-Werror=int-conversion",
-
-		// Enable the new pass manager.
-		"-fexperimental-new-pass-manager",
-
-		// Disable overly aggressive warning for macros defined with a leading underscore
-		// This happens in AndroidConfig.h, which is included nearly everywhere.
-		// TODO: can we remove this now?
-		"-Wno-reserved-id-macro",
-
-		// Workaround for ccache with clang.
-		// See http://petereisentraut.blogspot.com/2011/05/ccache-and-clang.html.
-		"-Wno-unused-command-line-argument",
-
-		// Force clang to always output color diagnostics. Ninja will strip the ANSI
-		// color codes if it is not running in a terminal.
-		"-fcolor-diagnostics",
-
-		// Warnings from clang-7.0
-		"-Wno-sign-compare",
-
-		// Warnings from clang-8.0
-		"-Wno-defaulted-function-deleted",
-
-		// Disable -Winconsistent-missing-override until we can clean up the existing
-		// codebase for it.
-		"-Wno-inconsistent-missing-override",
-
-		// Warnings from clang-10
-		// Nested and array designated initialization is nice to have.
-		"-Wno-c99-designator",
-
-		// Warnings from clang-12
-		"-Wno-gnu-folding-constant",
-
-		// Calls to the APIs that are newer than the min sdk version of the caller should be
-		// guarded with __builtin_available.
-		"-Wunguarded-availability",
-		// This macro allows the bionic versioning.h to indirectly determine whether the
-		// option -Wunguarded-availability is on or not.
-		"-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__",
-	})
-
-	staticVariableExportedToBazel("ClangExtraCppflags", []string{
-		// -Wimplicit-fallthrough is not enabled by -Wall.
-		"-Wimplicit-fallthrough",
-
-		// Enable clang's thread-safety annotations in libcxx.
-		"-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
-
-		// libc++'s math.h has an #include_next outside of system_headers.
-		"-Wno-gnu-include-next",
-	})
-
-	staticVariableExportedToBazel("ClangExtraTargetCflags", []string{"-nostdlibinc"})
-
-	staticVariableExportedToBazel("ClangExtraNoOverrideCflags", []string{
-		"-Werror=address-of-temporary",
-		// Bug: http://b/29823425 Disable -Wnull-dereference until the
-		// new cases detected by this warning in Clang r271374 are
-		// fixed.
-		//"-Werror=null-dereference",
-		"-Werror=return-type",
-
-		// http://b/72331526 Disable -Wtautological-* until the instances detected by these
-		// new warnings are fixed.
-		"-Wno-tautological-constant-compare",
-		"-Wno-tautological-type-limit-compare",
-		// http://b/145210666
-		"-Wno-reorder-init-list",
-		// http://b/145211066
-		"-Wno-implicit-int-float-conversion",
-		// New warnings to be fixed after clang-r377782.
-		"-Wno-int-in-bool-context",          // http://b/148287349
-		"-Wno-sizeof-array-div",             // http://b/148815709
-		"-Wno-tautological-overlap-compare", // http://b/148815696
-		// New warnings to be fixed after clang-r383902.
-		"-Wno-deprecated-copy",                      // http://b/153746672
-		"-Wno-range-loop-construct",                 // http://b/153747076
-		"-Wno-misleading-indentation",               // http://b/153746954
-		"-Wno-zero-as-null-pointer-constant",        // http://b/68236239
-		"-Wno-deprecated-anon-enum-enum-conversion", // http://b/153746485
-		"-Wno-deprecated-enum-enum-conversion",      // http://b/153746563
-		"-Wno-string-compare",                       // http://b/153764102
-		"-Wno-enum-enum-conversion",                 // http://b/154138986
-		"-Wno-enum-float-conversion",                // http://b/154255917
-		"-Wno-pessimizing-move",                     // http://b/154270751
-		// New warnings to be fixed after clang-r399163
-		"-Wno-non-c-typedef-for-linkage", // http://b/161304145
-		// New warnings to be fixed after clang-r407598
-		"-Wno-string-concatenation", // http://b/175068488
-	})
-
-	// Extra cflags for external third-party projects to disable warnings that
-	// are infeasible to fix in all the external projects and their upstream repos.
-	staticVariableExportedToBazel("ClangExtraExternalCflags", []string{
-		"-Wno-enum-compare",
-		"-Wno-enum-compare-switch",
-
-		// http://b/72331524 Allow null pointer arithmetic until the instances detected by
-		// this new warning are fixed.
-		"-Wno-null-pointer-arithmetic",
-
-		// Bug: http://b/29823425 Disable -Wnull-dereference until the
-		// new instances detected by this warning are fixed.
-		"-Wno-null-dereference",
-
-		// http://b/145211477
-		"-Wno-pointer-compare",
-		// http://b/145211022
-		"-Wno-xor-used-as-pow",
-		// http://b/145211022
-		"-Wno-final-dtor-non-final-class",
-
-		// http://b/165945989
-		"-Wno-psabi",
-	})
-}
-
 func ClangFilterUnknownCflags(cflags []string) []string {
 	result, _ := android.FilterList(cflags, ClangUnknownCflags)
 	return result
@@ -255,26 +110,10 @@
 	return result
 }
 
-func ClangFilterUnknownLldflags(lldflags []string) []string {
-	result, _ := android.FilterList(lldflags, ClangUnknownLldflags)
-	return result
-}
-
 func ClangLibToolingFilterUnknownCflags(libToolingFlags []string) []string {
 	return android.RemoveListFromList(libToolingFlags, ClangLibToolingUnknownCflags)
 }
 
-func inListSorted(s string, list []string) bool {
-	for _, l := range list {
-		if s == l {
-			return true
-		} else if s < l {
-			return false
-		}
-	}
-	return false
-}
-
 func sorted(list []string) []string {
 	sort.Strings(list)
 	return list
diff --git a/cc/config/darwin_host.go b/cc/config/darwin_host.go
new file mode 100644
index 0000000..5e3f7c7
--- /dev/null
+++ b/cc/config/darwin_host.go
@@ -0,0 +1,283 @@
+// Copyright 2016 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 config
+
+import (
+	"fmt"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"android/soong/android"
+)
+
+var (
+	darwinCflags = []string{
+		"-fPIC",
+		"-funwind-tables",
+
+		// Workaround differences in inttypes.h between host and target.
+		//See bug 12708004.
+		"-D__STDC_FORMAT_MACROS",
+		"-D__STDC_CONSTANT_MACROS",
+
+		"-isysroot ${macSdkRoot}",
+		"-mmacosx-version-min=${macMinVersion}",
+		"-DMACOSX_DEPLOYMENT_TARGET=${macMinVersion}",
+
+		"-m64",
+
+		"-integrated-as",
+		"-fstack-protector-strong",
+	}
+
+	darwinLdflags = []string{
+		"-isysroot ${macSdkRoot}",
+		"-Wl,-syslibroot,${macSdkRoot}",
+		"-mmacosx-version-min=${macMinVersion}",
+		"-m64",
+		"-mlinker-version=305",
+	}
+
+	darwinSupportedSdkVersions = []string{
+		"11",
+		"12",
+	}
+
+	darwinAvailableLibraries = append(
+		addPrefix([]string{
+			"c",
+			"dl",
+			"m",
+			"ncurses",
+			"objc",
+			"pthread",
+		}, "-l"),
+		"-framework AppKit",
+		"-framework CoreFoundation",
+		"-framework Foundation",
+		"-framework IOKit",
+		"-framework Security",
+		"-framework SystemConfiguration",
+	)
+)
+
+const (
+	darwinGccVersion = "4.2.1"
+)
+
+func init() {
+	pctx.VariableFunc("macSdkRoot", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).sdkRoot
+	})
+	pctx.StaticVariable("macMinVersion", "10.13")
+	pctx.VariableFunc("MacArPath", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).arPath
+	})
+
+	pctx.VariableFunc("MacLipoPath", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).lipoPath
+	})
+
+	pctx.VariableFunc("MacStripPath", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).stripPath
+	})
+
+	pctx.VariableFunc("MacToolPath", func(ctx android.PackageVarContext) string {
+		return getMacTools(ctx).toolPath
+	})
+
+	pctx.StaticVariable("DarwinGccVersion", darwinGccVersion)
+	pctx.SourcePathVariable("DarwinGccRoot",
+		"prebuilts/gcc/${HostPrebuiltTag}/host/i686-apple-darwin-${DarwinGccVersion}")
+
+	pctx.StaticVariable("DarwinGccTriple", "i686-apple-darwin11")
+
+	pctx.StaticVariable("DarwinCflags", strings.Join(darwinCflags, " "))
+	pctx.StaticVariable("DarwinLdflags", strings.Join(darwinLdflags, " "))
+	pctx.StaticVariable("DarwinLldflags", strings.Join(darwinLdflags, " "))
+
+	pctx.StaticVariable("DarwinYasmFlags", "-f macho -m amd64")
+}
+
+func MacStripPath(ctx android.PathContext) string {
+	return getMacTools(ctx).stripPath
+}
+
+type macPlatformTools struct {
+	once sync.Once
+	err  error
+
+	sdkRoot   string
+	arPath    string
+	lipoPath  string
+	stripPath string
+	toolPath  string
+}
+
+var macTools = &macPlatformTools{}
+
+func getMacTools(ctx android.PathContext) *macPlatformTools {
+	macTools.once.Do(func() {
+		xcrunTool := "/usr/bin/xcrun"
+
+		xcrun := func(args ...string) string {
+			if macTools.err != nil {
+				return ""
+			}
+
+			bytes, err := exec.Command(xcrunTool, append([]string{"--sdk", "macosx"}, args...)...).Output()
+			if err != nil {
+				macTools.err = fmt.Errorf("xcrun %q failed with: %q", args, err)
+				return ""
+			}
+
+			return strings.TrimSpace(string(bytes))
+		}
+
+		sdkVersion := xcrun("--show-sdk-version")
+		sdkVersionSupported := false
+		for _, version := range darwinSupportedSdkVersions {
+			if version == sdkVersion || strings.HasPrefix(sdkVersion, version+".") {
+				sdkVersionSupported = true
+			}
+		}
+		if !sdkVersionSupported {
+			macTools.err = fmt.Errorf("Unsupported macOS SDK version %q not in %v", sdkVersion, darwinSupportedSdkVersions)
+			return
+		}
+
+		macTools.sdkRoot = xcrun("--show-sdk-path")
+
+		macTools.arPath = xcrun("--find", "ar")
+		macTools.lipoPath = xcrun("--find", "lipo")
+		macTools.stripPath = xcrun("--find", "strip")
+		macTools.toolPath = filepath.Dir(xcrun("--find", "ld"))
+	})
+	if macTools.err != nil {
+		android.ReportPathErrorf(ctx, "%q", macTools.err)
+	}
+	return macTools
+}
+
+type toolchainDarwin struct {
+	cFlags, ldFlags string
+	toolchain64Bit
+}
+
+type toolchainDarwinX86 struct {
+	toolchainDarwin
+}
+
+type toolchainDarwinArm struct {
+	toolchainDarwin
+}
+
+func (t *toolchainDarwinArm) Name() string {
+	return "arm64"
+}
+
+func (t *toolchainDarwinX86) Name() string {
+	return "x86_64"
+}
+
+func (t *toolchainDarwinArm) GccRoot() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinArm) GccTriple() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinArm) GccVersion() string {
+	panic("unimplemented")
+}
+
+func (t *toolchainDarwinX86) GccRoot() string {
+	return "${config.DarwinGccRoot}"
+}
+
+func (t *toolchainDarwinX86) GccTriple() string {
+	return "${config.DarwinGccTriple}"
+}
+
+func (t *toolchainDarwinX86) GccVersion() string {
+	return darwinGccVersion
+}
+
+func (t *toolchainDarwin) IncludeFlags() string {
+	return ""
+}
+
+func (t *toolchainDarwinArm) ClangTriple() string {
+	return "aarch64-apple-darwin"
+}
+
+func (t *toolchainDarwinX86) ClangTriple() string {
+	return "x86_64-apple-darwin"
+}
+
+func (t *toolchainDarwin) Cflags() string {
+	return "${config.DarwinCflags}"
+}
+
+func (t *toolchainDarwin) Cppflags() string {
+	return ""
+}
+
+func (t *toolchainDarwin) Ldflags() string {
+	return "${config.DarwinLdflags}"
+}
+
+func (t *toolchainDarwin) Lldflags() string {
+	return "${config.DarwinLldflags}"
+}
+
+func (t *toolchainDarwin) YasmFlags() string {
+	return "${config.DarwinYasmFlags}"
+}
+
+func (t *toolchainDarwin) ShlibSuffix() string {
+	return ".dylib"
+}
+
+func (t *toolchainDarwin) AvailableLibraries() []string {
+	return darwinAvailableLibraries
+}
+
+func (t *toolchainDarwin) ToolchainCflags() string {
+	return "-B${config.MacToolPath}"
+}
+
+func (t *toolchainDarwin) ToolchainLdflags() string {
+	return "-B${config.MacToolPath}"
+}
+
+var toolchainDarwinArmSingleton Toolchain = &toolchainDarwinArm{}
+var toolchainDarwinX86Singleton Toolchain = &toolchainDarwinX86{}
+
+func darwinArmToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinArmSingleton
+}
+
+func darwinX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinX86Singleton
+}
+
+func init() {
+	registerToolchainFactory(android.Darwin, android.Arm64, darwinArmToolchainFactory)
+	registerToolchainFactory(android.Darwin, android.X86_64, darwinX86ToolchainFactory)
+}
diff --git a/cc/config/global.go b/cc/config/global.go
index ae731b2..3caf327 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"runtime"
 	"strings"
 
 	"android/soong/android"
@@ -22,6 +23,9 @@
 )
 
 var (
+	pctx         = android.NewPackageContext("android/soong/cc/config")
+	exportedVars = android.NewExportedVariables(pctx)
+
 	// Flags used by lots of devices.  Putting them in package static variables
 	// will save bytes in build.ninja so they aren't repeated for every file
 	commonGlobalCflags = []string{
@@ -36,7 +40,6 @@
 
 		// Make paths in deps files relative
 		"-no-canonical-prefixes",
-		"-fno-canonical-system-headers",
 
 		"-DNDEBUG",
 		"-UDEBUG",
@@ -46,7 +49,7 @@
 
 		"-O2",
 		"-g",
-		"-fdebug-info-for-profiling",
+		"-fdebug-default-version=5",
 
 		"-fno-strict-aliasing",
 
@@ -55,13 +58,66 @@
 		"-Werror=pragma-pack-suspicious-include",
 		"-Werror=string-plus-int",
 		"-Werror=unreachable-code-loop-increment",
+
+		"-D__compiler_offsetof=__builtin_offsetof",
+
+		// Emit address-significance table which allows linker to perform safe ICF. Clang does
+		// not emit the table by default on Android since NDK still uses GNU binutils.
+		"-faddrsig",
+
+		// Turn on -fcommon explicitly, since Clang now defaults to -fno-common. The cleanup bug
+		// tracking this is http://b/151457797.
+		"-fcommon",
+
+		// Help catch common 32/64-bit errors.
+		"-Werror=int-conversion",
+
+		// Enable the new pass manager.
+		"-fexperimental-new-pass-manager",
+
+		// Disable overly aggressive warning for macros defined with a leading underscore
+		// This happens in AndroidConfig.h, which is included nearly everywhere.
+		// TODO: can we remove this now?
+		"-Wno-reserved-id-macro",
+
+		// Force clang to always output color diagnostics. Ninja will strip the ANSI
+		// color codes if it is not running in a terminal.
+		"-fcolor-diagnostics",
+
+		// Warnings from clang-7.0
+		"-Wno-sign-compare",
+
+		// Warnings from clang-8.0
+		"-Wno-defaulted-function-deleted",
+
+		// Disable -Winconsistent-missing-override until we can clean up the existing
+		// codebase for it.
+		"-Wno-inconsistent-missing-override",
+
+		// Warnings from clang-10
+		// Nested and array designated initialization is nice to have.
+		"-Wno-c99-designator",
+
+		// Many old files still have GNU designator syntax.
+		"-Wno-gnu-designator",
+
+		// Warnings from clang-12
+		"-Wno-gnu-folding-constant",
+
+		// Calls to the APIs that are newer than the min sdk version of the caller should be
+		// guarded with __builtin_available.
+		"-Wunguarded-availability",
+		// This macro allows the bionic versioning.h to indirectly determine whether the
+		// option -Wunguarded-availability is on or not.
+		"-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__",
+
+		// Turn off FMA which got enabled by default in clang-r445002 (http://b/218805949)
+		"-ffp-contract=off",
 	}
 
 	commonGlobalConlyflags = []string{}
 
 	deviceGlobalCflags = []string{
-		"-fdiagnostics-color",
-
 		"-ffunction-sections",
 		"-fdata-sections",
 		"-fno-short-enums",
@@ -77,6 +133,10 @@
 		"-Werror=address",
 		"-Werror=sequence-point",
 		"-Werror=format-security",
+		"-nostdlibinc",
+
+		// Emit additional debug info for AutoFDO
+		"-fdebug-info-for-profiling",
 	}
 
 	deviceGlobalCppflags = []string{
@@ -88,7 +148,6 @@
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--no-undefined-version",
 		// TODO: Eventually we should link against a libunwind.a with hidden symbols, and then these
@@ -100,7 +159,7 @@
 		"-Wl,--icf=safe",
 	}
 
-	deviceGlobalLldflags = append(ClangFilterUnknownLldflags(deviceGlobalLdflags),
+	deviceGlobalLldflags = append(deviceGlobalLdflags,
 		[]string{
 			"-fuse-ld=lld",
 		}...)
@@ -115,6 +174,15 @@
 
 	commonGlobalCppflags = []string{
 		"-Wsign-promo",
+
+		// -Wimplicit-fallthrough is not enabled by -Wall.
+		"-Wimplicit-fallthrough",
+
+		// Enable clang's thread-safety annotations in libcxx.
+		"-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS",
+
+		// libc++'s math.h has an #include_next outside of system_headers.
+		"-Wno-gnu-include-next",
 	}
 
 	noOverrideGlobalCflags = []string{
@@ -132,6 +200,79 @@
 		// http://b/161386391 for -Wno-pointer-to-int-cast
 		"-Wno-pointer-to-int-cast",
 		"-Werror=fortify-source",
+
+		"-Werror=address-of-temporary",
+		// Bug: http://b/29823425 Disable -Wnull-dereference until the
+		// new cases detected by this warning in Clang r271374 are
+		// fixed.
+		//"-Werror=null-dereference",
+		"-Werror=return-type",
+
+		// http://b/72331526 Disable -Wtautological-* until the instances detected by these
+		// new warnings are fixed.
+		"-Wno-tautological-constant-compare",
+		"-Wno-tautological-type-limit-compare",
+		// http://b/145210666
+		"-Wno-reorder-init-list",
+		// http://b/145211066
+		"-Wno-implicit-int-float-conversion",
+		// New warnings to be fixed after clang-r377782.
+		"-Wno-sizeof-array-div",             // http://b/148815709
+		"-Wno-tautological-overlap-compare", // http://b/148815696
+		// New warnings to be fixed after clang-r383902.
+		"-Wno-deprecated-copy",                      // http://b/153746672
+		"-Wno-range-loop-construct",                 // http://b/153747076
+		"-Wno-misleading-indentation",               // http://b/153746954
+		"-Wno-zero-as-null-pointer-constant",        // http://b/68236239
+		"-Wno-deprecated-anon-enum-enum-conversion", // http://b/153746485
+		"-Wno-string-compare",                       // http://b/153764102
+		"-Wno-pessimizing-move",                     // http://b/154270751
+		// New warnings to be fixed after clang-r399163
+		"-Wno-non-c-typedef-for-linkage", // http://b/161304145
+		// New warnings to be fixed after clang-r428724
+		"-Wno-align-mismatch", // http://b/193679946
+		// New warnings to be fixed after clang-r433403
+		"-Wno-error=unused-but-set-variable",  // http://b/197240255
+		"-Wno-error=unused-but-set-parameter", // http://b/197240255
+	}
+
+	noOverrideExternalGlobalCflags = []string{
+		// http://b/197240255
+		"-Wno-unused-but-set-variable",
+		"-Wno-unused-but-set-parameter",
+		// http://b/215753485
+		"-Wno-bitwise-instead-of-logical",
+	}
+
+	// Extra cflags for external third-party projects to disable warnings that
+	// are infeasible to fix in all the external projects and their upstream repos.
+	extraExternalCflags = []string{
+		"-Wno-enum-compare",
+		"-Wno-enum-compare-switch",
+
+		// http://b/72331524 Allow null pointer arithmetic until the instances detected by
+		// this new warning are fixed.
+		"-Wno-null-pointer-arithmetic",
+
+		// Bug: http://b/29823425 Disable -Wnull-dereference until the
+		// new instances detected by this warning are fixed.
+		"-Wno-null-dereference",
+
+		// http://b/145211477
+		"-Wno-pointer-compare",
+		// http://b/145211022
+		"-Wno-xor-used-as-pow",
+		// http://b/145211022
+		"-Wno-final-dtor-non-final-class",
+
+		// http://b/165945989
+		"-Wno-psabi",
+
+		// http://b/199369603
+		"-Wno-null-pointer-subtraction",
+
+		// http://b/175068488
+		"-Wno-string-concatenation",
 	}
 
 	IllegalFlags = []string{
@@ -145,8 +286,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r416183b1"
-	ClangDefaultShortVersion = "12.0.7"
+	ClangDefaultVersion      = "clang-r450784d"
+	ClangDefaultShortVersion = "14.0.6"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
@@ -158,36 +299,42 @@
 	WarningAllowedOldProjects = []string{}
 )
 
-var pctx = android.NewPackageContext("android/soong/cc/config")
+// BazelCcToolchainVars generates bzl file content containing variables for
+// Bazel's cc_toolchain configuration.
+func BazelCcToolchainVars(config android.Config) string {
+	return android.BazelToolchainVars(config, exportedVars)
+}
+
+func ExportStringList(name string, value []string) {
+	exportedVars.ExportStringList(name, value)
+}
 
 func init() {
-	if android.BuildOs == android.Linux {
+	if runtime.GOOS == "linux" {
 		commonGlobalCflags = append(commonGlobalCflags, "-fdebug-prefix-map=/proc/self/cwd=")
 	}
 
-	staticVariableExportedToBazel("CommonGlobalConlyflags", commonGlobalConlyflags)
-	staticVariableExportedToBazel("DeviceGlobalCppflags", deviceGlobalCppflags)
-	staticVariableExportedToBazel("DeviceGlobalLdflags", deviceGlobalLdflags)
-	staticVariableExportedToBazel("DeviceGlobalLldflags", deviceGlobalLldflags)
-	staticVariableExportedToBazel("HostGlobalCppflags", hostGlobalCppflags)
-	staticVariableExportedToBazel("HostGlobalLdflags", hostGlobalLdflags)
-	staticVariableExportedToBazel("HostGlobalLldflags", hostGlobalLldflags)
+	exportedVars.ExportStringListStaticVariable("CommonGlobalConlyflags", commonGlobalConlyflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalCppflags", deviceGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalLdflags", deviceGlobalLdflags)
+	exportedVars.ExportStringListStaticVariable("DeviceGlobalLldflags", deviceGlobalLldflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalCppflags", hostGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalLdflags", hostGlobalLdflags)
+	exportedVars.ExportStringListStaticVariable("HostGlobalLldflags", hostGlobalLldflags)
 
-	// Export the static default CommonClangGlobalCflags to Bazel.
+	// Export the static default CommonGlobalCflags to Bazel.
 	// TODO(187086342): handle cflags that are set in VariableFuncs.
-	commonClangGlobalCFlags := append(
-		ClangFilterUnknownCflags(commonGlobalCflags),
+	bazelCommonGlobalCflags := append(
+		commonGlobalCflags,
 		[]string{
-			"${ClangExtraCflags}",
 			// Default to zero initialization.
 			"-ftrivial-auto-var-init=zero",
 			"-enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang",
 		}...)
-	exportedVars.Set("CommonClangGlobalCflags", variableValue(commonClangGlobalCFlags))
+	exportedVars.ExportStringList("CommonGlobalCflags", bazelCommonGlobalCflags)
 
-	pctx.VariableFunc("CommonClangGlobalCflags", func(ctx android.PackageVarContext) string {
-		flags := ClangFilterUnknownCflags(commonGlobalCflags)
-		flags = append(flags, "${ClangExtraCflags}")
+	pctx.VariableFunc("CommonGlobalCflags", func(ctx android.PackageVarContext) string {
+		flags := commonGlobalCflags
 
 		// http://b/131390872
 		// Automatically initialize any uninitialized stack variables.
@@ -202,64 +349,54 @@
 			// Default to zero initialization.
 			flags = append(flags, "-ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang")
 		}
+
+		// Workaround for ccache with clang.
+		// See http://petereisentraut.blogspot.com/2011/05/ccache-and-clang.html.
+		if ctx.Config().IsEnvTrue("USE_CCACHE") {
+			flags = append(flags, "-Wno-unused-command-line-argument")
+		}
 		return strings.Join(flags, " ")
 	})
 
-	// Export the static default DeviceClangGlobalCflags to Bazel.
+	// Export the static default DeviceGlobalCflags to Bazel.
 	// TODO(187086342): handle cflags that are set in VariableFuncs.
-	deviceClangGlobalCflags := append(ClangFilterUnknownCflags(deviceGlobalCflags), "${ClangExtraTargetCflags}")
-	exportedVars.Set("DeviceClangGlobalCflags", variableValue(deviceClangGlobalCflags))
+	exportedVars.ExportStringList("DeviceGlobalCflags", deviceGlobalCflags)
 
-	pctx.VariableFunc("DeviceClangGlobalCflags", func(ctx android.PackageVarContext) string {
-		if ctx.Config().Fuchsia() {
-			return strings.Join(ClangFilterUnknownCflags(deviceGlobalCflags), " ")
-		} else {
-			return strings.Join(deviceClangGlobalCflags, " ")
-		}
+	pctx.VariableFunc("DeviceGlobalCflags", func(ctx android.PackageVarContext) string {
+		return strings.Join(deviceGlobalCflags, " ")
 	})
 
-	staticVariableExportedToBazel("HostClangGlobalCflags", ClangFilterUnknownCflags(hostGlobalCflags))
-	staticVariableExportedToBazel("NoOverrideClangGlobalCflags", append(ClangFilterUnknownCflags(noOverrideGlobalCflags), "${ClangExtraNoOverrideCflags}"))
-	staticVariableExportedToBazel("CommonClangGlobalCppflags", append(ClangFilterUnknownCflags(commonGlobalCppflags), "${ClangExtraCppflags}"))
-	staticVariableExportedToBazel("ClangExternalCflags", []string{"${ClangExtraExternalCflags}"})
+	exportedVars.ExportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("NoOverrideGlobalCflags", noOverrideGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
+	exportedVars.ExportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
+	exportedVars.ExportStringListStaticVariable("ExternalCflags", extraExternalCflags)
 
 	// Everything in these lists is a crime against abstraction and dependency tracking.
 	// Do not add anything to this list.
-	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I",
-		[]string{
-			"system/core/include",
-			"system/logging/liblog/include",
-			"system/media/audio/include",
-			"hardware/libhardware/include",
-			"hardware/libhardware_legacy/include",
-			"hardware/ril/include",
-			"frameworks/native/include",
-			"frameworks/native/opengl/include",
-			"frameworks/av/include",
-		})
+	commonGlobalIncludes := []string{
+		"system/core/include",
+		"system/logging/liblog/include",
+		"system/media/audio/include",
+		"hardware/libhardware/include",
+		"hardware/libhardware_legacy/include",
+		"hardware/ril/include",
+		"frameworks/native/include",
+		"frameworks/native/opengl/include",
+		"frameworks/av/include",
+	}
+	exportedVars.ExportStringList("CommonGlobalIncludes", commonGlobalIncludes)
+	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I", commonGlobalIncludes)
 
-	pctx.SourcePathVariable("ClangDefaultBase", ClangDefaultBase)
-	pctx.VariableFunc("ClangBase", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_PREBUILTS_BASE"); override != "" {
-			return override
-		}
-		return "${ClangDefaultBase}"
-	})
-	pctx.VariableFunc("ClangVersion", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_PREBUILTS_VERSION"); override != "" {
-			return override
-		}
-		return ClangDefaultVersion
-	})
+	exportedVars.ExportStringStaticVariable("CLANG_DEFAULT_VERSION", ClangDefaultVersion)
+	exportedVars.ExportStringStaticVariable("CLANG_DEFAULT_SHORT_VERSION", ClangDefaultShortVersion)
+
+	pctx.StaticVariableWithEnvOverride("ClangBase", "LLVM_PREBUILTS_BASE", ClangDefaultBase)
+	pctx.StaticVariableWithEnvOverride("ClangVersion", "LLVM_PREBUILTS_VERSION", ClangDefaultVersion)
 	pctx.StaticVariable("ClangPath", "${ClangBase}/${HostPrebuiltTag}/${ClangVersion}")
 	pctx.StaticVariable("ClangBin", "${ClangPath}/bin")
 
-	pctx.VariableFunc("ClangShortVersion", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("LLVM_RELEASE_VERSION"); override != "" {
-			return override
-		}
-		return ClangDefaultShortVersion
-	})
+	pctx.StaticVariableWithEnvOverride("ClangShortVersion", "LLVM_RELEASE_VERSION", ClangDefaultShortVersion)
 	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib64/clang/${ClangShortVersion}/lib/linux")
 
 	// These are tied to the version of LLVM directly in external/llvm, so they might trail the host prebuilts
@@ -292,13 +429,30 @@
 	pctx.StaticVariableWithEnvOverride("REAbiLinkerExecStrategy", "RBE_ABI_LINKER_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
 }
 
-var HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
+var HostPrebuiltTag = exportedVars.ExportVariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
 
-func envOverrideFunc(envVar, defaultVal string) func(ctx android.PackageVarContext) string {
-	return func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv(envVar); override != "" {
-			return override
+func ClangPath(ctx android.PathContext, file string) android.SourcePath {
+	type clangToolKey string
+
+	key := android.NewCustomOnceKey(clangToolKey(file))
+
+	return ctx.Config().OnceSourcePath(key, func() android.SourcePath {
+		return clangPath(ctx).Join(ctx, file)
+	})
+}
+
+var clangPathKey = android.NewOnceKey("clangPath")
+
+func clangPath(ctx android.PathContext) android.SourcePath {
+	return ctx.Config().OnceSourcePath(clangPathKey, func() android.SourcePath {
+		clangBase := ClangDefaultBase
+		if override := ctx.Config().Getenv("LLVM_PREBUILTS_BASE"); override != "" {
+			clangBase = override
 		}
-		return defaultVal
-	}
+		clangVersion := ClangDefaultVersion
+		if override := ctx.Config().Getenv("LLVM_PREBUILTS_VERSION"); override != "" {
+			clangVersion = override
+		}
+		return android.PathForSource(ctx, clangBase, ctx.Config().PrebuiltOS(), clangVersion)
+	})
 }
diff --git a/cc/config/tidy.go b/cc/config/tidy.go
index c4563e2..ba1043b 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -39,6 +39,7 @@
 			"misc-*",
 			"performance-*",
 			"portability-*",
+			"-bugprone-easily-swappable-parameters",
 			"-bugprone-narrowing-conversions",
 			"-google-readability*",
 			"-google-runtime-references",
@@ -61,8 +62,9 @@
 		}, ",")
 		// clang-analyzer-* checks are too slow to be in the default for WITH_TIDY=1.
 		// nightly builds add CLANG_ANALYZER_CHECKS=1 to run those checks.
+		// The insecureAPI.DeprecatedOrUnsafeBufferHandling warning does not apply to Android.
 		if ctx.Config().IsEnvTrue("CLANG_ANALYZER_CHECKS") {
-			checks += ",clang-analyzer-*"
+			checks += ",clang-analyzer-*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling"
 		}
 		return checks
 	})
@@ -106,6 +108,7 @@
 
 const tidyDefault = "${config.TidyDefaultGlobalChecks}"
 const tidyExternalVendor = "${config.TidyExternalVendorChecks}"
+const tidyDefaultNoAnalyzer = "${config.TidyDefaultGlobalChecks},-clang-analyzer-*"
 
 // This is a map of local path prefixes to the set of default clang-tidy checks
 // to be used.
@@ -114,6 +117,7 @@
 	{"external/", tidyExternalVendor},
 	{"external/google", tidyDefault},
 	{"external/webrtc", tidyDefault},
+	{"external/googletest/", tidyExternalVendor},
 	{"frameworks/compile/mclinker/", tidyExternalVendor},
 	{"hardware/qcom", tidyExternalVendor},
 	{"vendor/", tidyExternalVendor},
@@ -132,6 +136,7 @@
 }
 
 func TidyChecksForDir(dir string) string {
+	dir = dir + "/"
 	for _, pathCheck := range reversedDefaultLocalTidyChecks {
 		if strings.HasPrefix(dir, pathCheck.PathPrefix) {
 			return pathCheck.Checks
@@ -139,3 +144,17 @@
 	}
 	return tidyDefault
 }
+
+func TidyFlagsForSrcFile(srcFile android.Path, flags string) string {
+	// Disable clang-analyzer-* checks globally for generated source files
+	// because some of them are too huge. Local .bp files can add wanted
+	// clang-analyzer checks through the tidy_checks property.
+	// Need to do this patch per source file, because some modules
+	// have both generated and organic source files.
+	if _, ok := srcFile.(android.WritablePath); ok {
+		if strings.Contains(flags, tidyDefault) {
+			return strings.ReplaceAll(flags, tidyDefault, tidyDefaultNoAnalyzer)
+		}
+	}
+	return flags
+}
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index fce28c1..7175fdc 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"path/filepath"
 
 	"android/soong/android"
 )
@@ -77,26 +76,23 @@
 	GccTriple() string
 	// GccVersion should return a real value, not a ninja reference
 	GccVersion() string
-	ToolPath() string
 
 	IncludeFlags() string
 
 	ClangTriple() string
-	ToolchainClangCflags() string
-	ToolchainClangLdflags() string
-	ClangAsflags() string
-	ClangCflags() string
-	ClangCppflags() string
-	ClangLdflags() string
-	ClangLldflags() string
-	ClangInstructionSetFlags(string) (string, error)
+	ToolchainCflags() string
+	ToolchainLdflags() string
+	Asflags() string
+	Cflags() string
+	Cppflags() string
+	Ldflags() string
+	Lldflags() string
+	InstructionSetFlags(string) (string, error)
 
 	ndkTriple() string
 
 	YasmFlags() string
 
-	WindresFlags() string
-
 	Is64Bit() bool
 
 	ShlibSuffix() string
@@ -106,7 +102,20 @@
 
 	AvailableLibraries() []string
 
+	CrtBeginStaticBinary() []string
+	CrtBeginSharedBinary() []string
+	CrtBeginSharedLibrary() []string
+	CrtEndStaticBinary() []string
+	CrtEndSharedBinary() []string
+	CrtEndSharedLibrary() []string
+
+	// DefaultSharedLibraries returns the list of shared libraries that will be added to all
+	// targets unless they explicitly specify system_shared_libs.
+	DefaultSharedLibraries() []string
+
 	Bionic() bool
+	Glibc() bool
+	Musl() bool
 }
 
 type toolchainBase struct {
@@ -125,18 +134,18 @@
 	return triple
 }
 
-func (toolchainBase) ClangInstructionSetFlags(s string) (string, error) {
+func (toolchainBase) InstructionSetFlags(s string) (string, error) {
 	if s != "" {
 		return "", fmt.Errorf("instruction_set: %s is not a supported instruction set", s)
 	}
 	return "", nil
 }
 
-func (toolchainBase) ToolchainClangCflags() string {
+func (toolchainBase) ToolchainCflags() string {
 	return ""
 }
 
-func (toolchainBase) ToolchainClangLdflags() string {
+func (toolchainBase) ToolchainLdflags() string {
 	return ""
 }
 
@@ -148,7 +157,7 @@
 	return ""
 }
 
-func (toolchainBase) ClangAsflags() string {
+func (toolchainBase) Asflags() string {
 	return ""
 }
 
@@ -156,24 +165,35 @@
 	return ""
 }
 
-func (toolchainBase) WindresFlags() string {
-	return ""
-}
-
 func (toolchainBase) LibclangRuntimeLibraryArch() string {
 	return ""
 }
 
 func (toolchainBase) AvailableLibraries() []string {
-	return []string{}
+	return nil
+}
+
+func (toolchainBase) CrtBeginStaticBinary() []string  { return nil }
+func (toolchainBase) CrtBeginSharedBinary() []string  { return nil }
+func (toolchainBase) CrtBeginSharedLibrary() []string { return nil }
+func (toolchainBase) CrtEndStaticBinary() []string    { return nil }
+func (toolchainBase) CrtEndSharedBinary() []string    { return nil }
+func (toolchainBase) CrtEndSharedLibrary() []string   { return nil }
+
+func (toolchainBase) DefaultSharedLibraries() []string {
+	return nil
 }
 
 func (toolchainBase) Bionic() bool {
-	return true
+	return false
 }
 
-func (t toolchainBase) ToolPath() string {
-	return ""
+func (toolchainBase) Glibc() bool {
+	return false
+}
+
+func (toolchainBase) Musl() bool {
+	return false
 }
 
 type toolchain64Bit struct {
@@ -207,14 +227,7 @@
 }
 
 func LibclangRuntimeLibrary(t Toolchain, library string) string {
-	arch := t.LibclangRuntimeLibraryArch()
-	if arch == "" {
-		return ""
-	}
-	if !t.Bionic() {
-		return "libclang_rt." + library + "-" + arch
-	}
-	return "libclang_rt." + library + "-" + arch + "-android"
+	return "libclang_rt." + library
 }
 
 func BuiltinsRuntimeLibrary(t Toolchain) string {
@@ -257,11 +270,4 @@
 	return LibclangRuntimeLibrary(t, "fuzzer")
 }
 
-func ToolPath(t Toolchain) string {
-	if p := t.ToolPath(); p != "" {
-		return p
-	}
-	return filepath.Join(t.GccRoot(), t.GccTriple(), "bin")
-}
-
 var inList = android.InList
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
index 2894f89..db69ce7 100644
--- a/cc/config/vndk.go
+++ b/cc/config/vndk.go
@@ -19,55 +19,107 @@
 // has VndkUseCoreVariant set.
 // TODO(b/150578172): clean up unstable and non-versioned aidl module
 var VndkMustUseVendorVariantList = []string{
-	"android.hardware.authsecret-unstable-ndk_platform",
-	"android.hardware.authsecret-ndk_platform",
+	"android.hardware.authsecret-V1-ndk",
 	"android.hardware.authsecret-V1-ndk_platform",
-	"android.hardware.automotive.occupant_awareness-ndk_platform",
+	"android.hardware.authsecret-ndk_platform",
+	"android.hardware.authsecret-unstable-ndk_platform",
+	"android.hardware.automotive.occupant_awareness-V1-ndk",
 	"android.hardware.automotive.occupant_awareness-V1-ndk_platform",
-	"android.hardware.gnss-unstable-ndk_platform",
-	"android.hardware.gnss-ndk_platform",
+	"android.hardware.automotive.occupant_awareness-ndk_platform",
+	"android.hardware.drm-V1-ndk",
+	"android.hardware.dumpstate-V1-ndk",
+	"android.hardware.gnss-V1-ndk",
 	"android.hardware.gnss-V1-ndk_platform",
+	"android.hardware.gnss-V2-ndk",
+	"android.hardware.gnss-ndk_platform",
+	"android.hardware.gnss-unstable-ndk_platform",
+	"android.hardware.health-V1-ndk",
+	"android.hardware.health-ndk",
+	"android.hardware.health.storage-V1-ndk",
 	"android.hardware.health.storage-V1-ndk_platform",
 	"android.hardware.health.storage-ndk_platform",
 	"android.hardware.health.storage-unstable-ndk_platform",
-	"android.hardware.light-V1-ndk_platform",
-	"android.hardware.light-ndk_platform",
 	"android.hardware.identity-V2-ndk_platform",
+	"android.hardware.identity-V3-ndk",
 	"android.hardware.identity-V3-ndk_platform",
 	"android.hardware.identity-ndk_platform",
-	"android.hardware.nfc@1.2",
+	"android.hardware.light-V1-ndk",
+	"android.hardware.light-V1-ndk_platform",
+	"android.hardware.light-ndk_platform",
+	"android.hardware.memtrack-V1-ndk",
 	"android.hardware.memtrack-V1-ndk_platform",
 	"android.hardware.memtrack-ndk_platform",
 	"android.hardware.memtrack-unstable-ndk_platform",
+	"android.hardware.nfc@1.2",
+	"android.hardware.oemlock-V1-ndk",
 	"android.hardware.oemlock-V1-ndk_platform",
 	"android.hardware.oemlock-ndk_platform",
 	"android.hardware.oemlock-unstable-ndk_platform",
 	"android.hardware.power-V1-ndk_platform",
+	"android.hardware.power-V2-ndk",
 	"android.hardware.power-V2-ndk_platform",
 	"android.hardware.power-ndk_platform",
+	"android.hardware.power.stats-V1-ndk",
 	"android.hardware.power.stats-V1-ndk_platform",
 	"android.hardware.power.stats-ndk_platform",
 	"android.hardware.power.stats-unstable-ndk_platform",
+	"android.hardware.rebootescrow-V1-ndk",
 	"android.hardware.rebootescrow-V1-ndk_platform",
 	"android.hardware.rebootescrow-ndk_platform",
+	"android.hardware.radio-V1-ndk",
+	"android.hardware.radio-V1-ndk_platform",
+	"android.hardware.radio.config-V1-ndk",
+	"android.hardware.radio.config-V1-ndk_platform",
+	"android.hardware.radio.data-V1-ndk",
+	"android.hardware.radio.data-V1-ndk_platform",
+	"android.hardware.radio.messaging-V1-ndk",
+	"android.hardware.radio.messaging-V1-ndk_platform",
+	"android.hardware.radio.modem-V1-ndk",
+	"android.hardware.radio.modem-V1-ndk_platform",
+	"android.hardware.radio.network-V1-ndk",
+	"android.hardware.radio.network-V1-ndk_platform",
+	"android.hardware.radio.sim-V1-ndk",
+	"android.hardware.radio.sim-V1-ndk_platform",
+	"android.hardware.radio.voice-V1-ndk",
+	"android.hardware.radio.voice-V1-ndk_platform",
+	"android.hardware.security.keymint-V1-ndk",
 	"android.hardware.security.keymint-V1-ndk_platform",
 	"android.hardware.security.keymint-ndk_platform",
 	"android.hardware.security.keymint-unstable-ndk_platform",
+	"android.hardware.security.secureclock-V1-ndk",
 	"android.hardware.security.secureclock-V1-ndk_platform",
-	"android.hardware.security.secureclock-unstable-ndk_platform",
 	"android.hardware.security.secureclock-ndk_platform",
+	"android.hardware.security.secureclock-unstable-ndk_platform",
+	"android.hardware.security.sharedsecret-V1-ndk",
 	"android.hardware.security.sharedsecret-V1-ndk_platform",
 	"android.hardware.security.sharedsecret-ndk_platform",
 	"android.hardware.security.sharedsecret-unstable-ndk_platform",
+	"android.hardware.sensors-V1-ndk",
+	"android.hardware.soundtrigger3-V1-ndk",
+	"android.hardware.soundtrigger3-V1-ndk_platform",
+	"android.hardware.uwb-V1-ndk",
+	"android.hardware.uwb-V1-ndk_platform",
 	"android.hardware.vibrator-V1-ndk_platform",
+	"android.hardware.vibrator-V2-ndk",
 	"android.hardware.vibrator-V2-ndk_platform",
 	"android.hardware.vibrator-ndk_platform",
+	"android.hardware.weaver-V1-ndk",
 	"android.hardware.weaver-V1-ndk_platform",
 	"android.hardware.weaver-ndk_platform",
 	"android.hardware.weaver-unstable-ndk_platform",
+	"android.system.suspend-V1-ndk",
+	"android.system.keystore2-V1-ndk",
+	"android.se.omapi-V1-ndk_platform",
+	"android.se.omapi-ndk_platform",
+	"android.se.omapi-unstable-ndk_platform",
+	"android.hardware.wifi.hostapd-V1-ndk",
+	"android.hardware.wifi.hostapd-V1-ndk_platform",
+	"android.hardware.wifi.supplicant-V1-ndk",
 	"android.system.keystore2-V1-ndk_platform",
 	"android.system.keystore2-ndk_platform",
 	"android.system.keystore2-unstable-ndk_platform",
+	"android.system.suspend-V1-ndk",
+	"android.system.suspend-V1-ndk_platform",
 	"libbinder",
 	"libcrypto",
 	"libexpat",
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index 1e25a3b..aebda0b 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
@@ -32,16 +33,14 @@
 		"-Wl,--hash-style=gnu",
 	}
 
-	x86_64Lldflags = ClangFilterUnknownLldflags(x86_64Ldflags)
-
 	x86_64ArchVariantCflags = map[string][]string{
 		"": []string{
 			"-march=x86-64",
 		},
+
 		"broadwell": []string{
 			"-march=broadwell",
 		},
-
 		"haswell": []string{
 			"-march=core-avx2",
 		},
@@ -86,45 +85,43 @@
 )
 
 func init() {
-	android.RegisterDefaultArchVariantFeatures(android.Android, android.X86_64,
-		"ssse3",
-		"sse4",
-		"sse4_1",
-		"sse4_2",
-		"popcnt")
 
 	pctx.StaticVariable("x86_64GccVersion", x86_64GccVersion)
 
 	pctx.SourcePathVariable("X86_64GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/x86/x86_64-linux-android-${x86_64GccVersion}")
 
-	pctx.StaticVariable("X86_64ToolchainCflags", "-m64")
-	pctx.StaticVariable("X86_64ToolchainLdflags", "-m64")
+	exportedVars.ExportStringListStaticVariable("X86_64ToolchainCflags", []string{"-m64"})
+	exportedVars.ExportStringListStaticVariable("X86_64ToolchainLdflags", []string{"-m64"})
 
-	pctx.StaticVariable("X86_64Ldflags", strings.Join(x86_64Ldflags, " "))
-	pctx.StaticVariable("X86_64Lldflags", strings.Join(x86_64Lldflags, " "))
+	exportedVars.ExportStringListStaticVariable("X86_64Ldflags", x86_64Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Lldflags", x86_64Ldflags)
 
 	// Clang cflags
-	pctx.StaticVariable("X86_64ClangCflags", strings.Join(ClangFilterUnknownCflags(x86_64Cflags), " "))
-	pctx.StaticVariable("X86_64ClangLdflags", strings.Join(ClangFilterUnknownCflags(x86_64Ldflags), " "))
-	pctx.StaticVariable("X86_64ClangLldflags", strings.Join(ClangFilterUnknownCflags(x86_64Lldflags), " "))
-	pctx.StaticVariable("X86_64ClangCppflags", strings.Join(ClangFilterUnknownCflags(x86_64Cppflags), " "))
+	exportedVars.ExportStringListStaticVariable("X86_64Cflags", x86_64Cflags)
+	exportedVars.ExportStringListStaticVariable("X86_64Cppflags", x86_64Cppflags)
 
 	// Yasm flags
-	pctx.StaticVariable("X86_64YasmFlags", "-f elf64 -m amd64")
+	exportedVars.ExportStringListStaticVariable("X86_64YasmFlags", []string{
+		"-f elf64",
+		"-m amd64",
+	})
 
 	// Extended cflags
 
+	exportedVars.ExportStringListDict("X86_64ArchVariantCflags", x86_64ArchVariantCflags)
+	exportedVars.ExportStringListDict("X86_64ArchFeatureCflags", x86_64ArchFeatureCflags)
+
 	// Architecture variant cflags
 	for variant, cflags := range x86_64ArchVariantCflags {
-		pctx.StaticVariable("X86_64"+variant+"VariantClangCflags",
-			strings.Join(ClangFilterUnknownCflags(cflags), " "))
+		pctx.StaticVariable("X86_64"+variant+"VariantCflags", strings.Join(cflags, " "))
 	}
 }
 
 type toolchainX86_64 struct {
+	toolchainBionic
 	toolchain64Bit
-	toolchainClangCflags string
+	toolchainCflags string
 }
 
 func (t *toolchainX86_64) Name() string {
@@ -151,27 +148,27 @@
 	return t.GccTriple()
 }
 
-func (t *toolchainX86_64) ToolchainClangLdflags() string {
+func (t *toolchainX86_64) ToolchainLdflags() string {
 	return "${config.X86_64ToolchainLdflags}"
 }
 
-func (t *toolchainX86_64) ToolchainClangCflags() string {
-	return t.toolchainClangCflags
+func (t *toolchainX86_64) ToolchainCflags() string {
+	return t.toolchainCflags
 }
 
-func (t *toolchainX86_64) ClangCflags() string {
-	return "${config.X86_64ClangCflags}"
+func (t *toolchainX86_64) Cflags() string {
+	return "${config.X86_64Cflags}"
 }
 
-func (t *toolchainX86_64) ClangCppflags() string {
-	return "${config.X86_64ClangCppflags}"
+func (t *toolchainX86_64) Cppflags() string {
+	return "${config.X86_64Cppflags}"
 }
 
-func (t *toolchainX86_64) ClangLdflags() string {
+func (t *toolchainX86_64) Ldflags() string {
 	return "${config.X86_64Ldflags}"
 }
 
-func (t *toolchainX86_64) ClangLldflags() string {
+func (t *toolchainX86_64) Lldflags() string {
 	return "${config.X86_64Lldflags}"
 }
 
@@ -184,17 +181,22 @@
 }
 
 func x86_64ToolchainFactory(arch android.Arch) Toolchain {
-	toolchainClangCflags := []string{
+	// Error now rather than having a confusing Ninja error
+	if _, ok := x86_64ArchVariantCflags[arch.ArchVariant]; !ok {
+		panic(fmt.Sprintf("Unknown x86_64 architecture version: %q", arch.ArchVariant))
+	}
+
+	toolchainCflags := []string{
 		"${config.X86_64ToolchainCflags}",
-		"${config.X86_64" + arch.ArchVariant + "VariantClangCflags}",
+		"${config.X86_64" + arch.ArchVariant + "VariantCflags}",
 	}
 
 	for _, feature := range arch.ArchFeatures {
-		toolchainClangCflags = append(toolchainClangCflags, x86_64ArchFeatureCflags[feature]...)
+		toolchainCflags = append(toolchainCflags, x86_64ArchFeatureCflags[feature]...)
 	}
 
 	return &toolchainX86_64{
-		toolchainClangCflags: strings.Join(toolchainClangCflags, " "),
+		toolchainCflags: strings.Join(toolchainCflags, " "),
 	}
 }
 
diff --git a/cc/config/x86_64_fuchsia_device.go b/cc/config/x86_64_fuchsia_device.go
deleted file mode 100644
index 0f2013b..0000000
--- a/cc/config/x86_64_fuchsia_device.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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 config
-
-import (
-	"android/soong/android"
-)
-
-var fuchsiaSysRoot string = "prebuilts/fuchsia_sdk/arch/x64/sysroot"
-var fuchsiaPrebuiltLibsRoot string = "fuchsia/prebuilt_libs"
-
-type toolchainFuchsia struct {
-	cFlags, ldFlags string
-}
-
-type toolchainFuchsiaX8664 struct {
-	toolchain64Bit
-	toolchainFuchsia
-}
-
-func (t *toolchainFuchsiaX8664) Name() string {
-	return "x86_64"
-}
-
-func (t *toolchainFuchsiaX8664) GccRoot() string {
-	return "${config.X86_64GccRoot}"
-}
-
-func (t *toolchainFuchsiaX8664) GccTriple() string {
-	return "x86_64-linux-android"
-}
-
-func (t *toolchainFuchsiaX8664) GccVersion() string {
-	return x86_64GccVersion
-}
-
-func (t *toolchainFuchsiaX8664) Cflags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaX8664) Cppflags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaX8664) Ldflags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaX8664) IncludeFlags() string {
-	return ""
-}
-
-func (t *toolchainFuchsiaX8664) ClangTriple() string {
-	return "x86_64-fuchsia-android"
-}
-
-func (t *toolchainFuchsiaX8664) ClangCppflags() string {
-	return "-Wno-error=deprecated-declarations"
-}
-
-func (t *toolchainFuchsiaX8664) ClangLdflags() string {
-	return "--target=x86_64-fuchsia --sysroot=" + fuchsiaSysRoot + " -L" + fuchsiaPrebuiltLibsRoot + "/x86_64-fuchsia/lib " + "-Lprebuilts/fuchsia_sdk/arch/x64/dist/"
-
-}
-
-func (t *toolchainFuchsiaX8664) ClangLldflags() string {
-	return "--target=x86_64-fuchsia --sysroot=" + fuchsiaSysRoot + " -L" + fuchsiaPrebuiltLibsRoot + "/x86_64-fuchsia/lib " + "-Lprebuilts/fuchsia_sdk/arch/x64/dist/"
-}
-
-func (t *toolchainFuchsiaX8664) ClangCflags() string {
-	return "--target=x86_64-fuchsia --sysroot=" + fuchsiaSysRoot + " -I" + fuchsiaSysRoot + "/include"
-}
-
-func (t *toolchainFuchsiaX8664) Bionic() bool {
-	return false
-}
-
-func (t *toolchainFuchsiaX8664) YasmFlags() string {
-	return "-f elf64 -m amd64"
-}
-
-func (t *toolchainFuchsiaX8664) ToolchainClangCflags() string {
-	return "-mssse3"
-}
-
-var toolchainFuchsiaSingleton Toolchain = &toolchainFuchsiaX8664{}
-
-func fuchsiaToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainFuchsiaSingleton
-}
-
-func init() {
-	registerToolchainFactory(android.Fuchsia, android.X86_64, fuchsiaToolchainFactory)
-}
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
deleted file mode 100644
index b0344af..0000000
--- a/cc/config/x86_darwin_host.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2016 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 config
-
-import (
-	"fmt"
-	"os/exec"
-	"path/filepath"
-	"strings"
-	"sync"
-
-	"android/soong/android"
-)
-
-var (
-	darwinCflags = []string{
-		"-fdiagnostics-color",
-
-		"-fPIC",
-		"-funwind-tables",
-
-		// Workaround differences in inttypes.h between host and target.
-		//See bug 12708004.
-		"-D__STDC_FORMAT_MACROS",
-		"-D__STDC_CONSTANT_MACROS",
-
-		"-isysroot ${macSdkRoot}",
-		"-mmacosx-version-min=${macMinVersion}",
-		"-DMACOSX_DEPLOYMENT_TARGET=${macMinVersion}",
-
-		"-m64",
-	}
-
-	darwinLdflags = []string{
-		"-isysroot ${macSdkRoot}",
-		"-Wl,-syslibroot,${macSdkRoot}",
-		"-mmacosx-version-min=${macMinVersion}",
-		"-m64",
-	}
-
-	darwinClangCflags = append(ClangFilterUnknownCflags(darwinCflags), []string{
-		"-integrated-as",
-		"-fstack-protector-strong",
-	}...)
-
-	darwinClangLdflags = ClangFilterUnknownCflags(darwinLdflags)
-
-	darwinClangLldflags = ClangFilterUnknownLldflags(darwinClangLdflags)
-
-	darwinSupportedSdkVersions = []string{
-		"10.10",
-		"10.11",
-		"10.12",
-		"10.13",
-		"10.14",
-		"10.15",
-		"11.0",
-		"11.1",
-	}
-
-	darwinAvailableLibraries = append(
-		addPrefix([]string{
-			"c",
-			"dl",
-			"m",
-			"ncurses",
-			"objc",
-			"pthread",
-		}, "-l"),
-		"-framework AppKit",
-		"-framework CoreFoundation",
-		"-framework Foundation",
-		"-framework IOKit",
-		"-framework Security",
-		"-framework SystemConfiguration",
-	)
-)
-
-const (
-	darwinGccVersion = "4.2.1"
-)
-
-func init() {
-	pctx.VariableFunc("macSdkRoot", func(ctx android.PackageVarContext) string {
-		return getMacTools(ctx).sdkRoot
-	})
-	pctx.StaticVariable("macMinVersion", "10.10")
-	pctx.VariableFunc("MacArPath", func(ctx android.PackageVarContext) string {
-		return getMacTools(ctx).arPath
-	})
-
-	pctx.VariableFunc("MacStripPath", func(ctx android.PackageVarContext) string {
-		return getMacTools(ctx).stripPath
-	})
-
-	pctx.VariableFunc("MacToolPath", func(ctx android.PackageVarContext) string {
-		return getMacTools(ctx).toolPath
-	})
-
-	pctx.StaticVariable("DarwinGccVersion", darwinGccVersion)
-	pctx.SourcePathVariable("DarwinGccRoot",
-		"prebuilts/gcc/${HostPrebuiltTag}/host/i686-apple-darwin-${DarwinGccVersion}")
-
-	pctx.StaticVariable("DarwinGccTriple", "i686-apple-darwin11")
-
-	pctx.StaticVariable("DarwinClangCflags", strings.Join(darwinClangCflags, " "))
-	pctx.StaticVariable("DarwinClangLdflags", strings.Join(darwinClangLdflags, " "))
-	pctx.StaticVariable("DarwinClangLldflags", strings.Join(darwinClangLldflags, " "))
-
-	pctx.StaticVariable("DarwinYasmFlags", "-f macho -m amd64")
-}
-
-type macPlatformTools struct {
-	once sync.Once
-	err  error
-
-	sdkRoot   string
-	arPath    string
-	stripPath string
-	toolPath  string
-}
-
-var macTools = &macPlatformTools{}
-
-func getMacTools(ctx android.PackageVarContext) *macPlatformTools {
-	macTools.once.Do(func() {
-		xcrunTool := "/usr/bin/xcrun"
-
-		xcrun := func(args ...string) string {
-			if macTools.err != nil {
-				return ""
-			}
-
-			bytes, err := exec.Command(xcrunTool, args...).Output()
-			if err != nil {
-				macTools.err = fmt.Errorf("xcrun %q failed with: %q", args, err)
-				return ""
-			}
-
-			return strings.TrimSpace(string(bytes))
-		}
-
-		xcrunSdk := func(arg string) string {
-			if selected := ctx.Config().Getenv("MAC_SDK_VERSION"); selected != "" {
-				if !inList(selected, darwinSupportedSdkVersions) {
-					macTools.err = fmt.Errorf("MAC_SDK_VERSION %s isn't supported: %q", selected, darwinSupportedSdkVersions)
-					return ""
-				}
-
-				return xcrun("--sdk", "macosx"+selected, arg)
-			}
-
-			for _, sdk := range darwinSupportedSdkVersions {
-				bytes, err := exec.Command(xcrunTool, "--sdk", "macosx"+sdk, arg).Output()
-				if err == nil {
-					return strings.TrimSpace(string(bytes))
-				}
-			}
-			macTools.err = fmt.Errorf("Could not find a supported mac sdk: %q", darwinSupportedSdkVersions)
-			return ""
-		}
-
-		macTools.sdkRoot = xcrunSdk("--show-sdk-path")
-
-		macTools.arPath = xcrun("--find", "ar")
-		macTools.stripPath = xcrun("--find", "strip")
-		macTools.toolPath = filepath.Dir(xcrun("--find", "ld"))
-	})
-	if macTools.err != nil {
-		ctx.Errorf("%q", macTools.err)
-	}
-	return macTools
-}
-
-type toolchainDarwin struct {
-	cFlags, ldFlags string
-	toolchain64Bit
-}
-
-func (t *toolchainDarwin) Name() string {
-	return "x86_64"
-}
-
-func (t *toolchainDarwin) GccRoot() string {
-	return "${config.DarwinGccRoot}"
-}
-
-func (t *toolchainDarwin) GccTriple() string {
-	return "${config.DarwinGccTriple}"
-}
-
-func (t *toolchainDarwin) GccVersion() string {
-	return darwinGccVersion
-}
-
-func (t *toolchainDarwin) IncludeFlags() string {
-	return ""
-}
-
-func (t *toolchainDarwin) ClangTriple() string {
-	return "x86_64-apple-darwin"
-}
-
-func (t *toolchainDarwin) ClangCflags() string {
-	return "${config.DarwinClangCflags}"
-}
-
-func (t *toolchainDarwin) ClangCppflags() string {
-	return ""
-}
-
-func (t *toolchainDarwin) ClangLdflags() string {
-	return "${config.DarwinClangLdflags}"
-}
-
-func (t *toolchainDarwin) ClangLldflags() string {
-	return "${config.DarwinClangLldflags}"
-}
-
-func (t *toolchainDarwin) YasmFlags() string {
-	return "${config.DarwinYasmFlags}"
-}
-
-func (t *toolchainDarwin) ShlibSuffix() string {
-	return ".dylib"
-}
-
-func (t *toolchainDarwin) AvailableLibraries() []string {
-	return darwinAvailableLibraries
-}
-
-func (t *toolchainDarwin) Bionic() bool {
-	return false
-}
-
-func (t *toolchainDarwin) ToolPath() string {
-	return "${config.MacToolPath}"
-}
-
-var toolchainDarwinSingleton Toolchain = &toolchainDarwin{}
-
-func darwinToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainDarwinSingleton
-}
-
-func init() {
-	registerToolchainFactory(android.Darwin, android.X86_64, darwinToolchainFactory)
-}
diff --git a/cc/config/x86_device.go b/cc/config/x86_device.go
index fe83098..421b083 100644
--- a/cc/config/x86_device.go
+++ b/cc/config/x86_device.go
@@ -15,22 +15,21 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
 )
 
 var (
-	x86Cflags = []string{}
-
-	x86ClangCflags = append(x86Cflags, []string{
+	x86Cflags = []string{
 		"-msse3",
 
 		// -mstackrealign is needed to realign stack in native code
 		// that could be called from JNI, so that movaps instruction
 		// will work on assumed stack aligned local variables.
 		"-mstackrealign",
-	}...)
+	}
 
 	x86Cppflags = []string{}
 
@@ -38,8 +37,6 @@
 		"-Wl,--hash-style=gnu",
 	}
 
-	x86Lldflags = ClangFilterUnknownLldflags(x86Ldflags)
-
 	x86ArchVariantCflags = map[string][]string{
 		"": []string{
 			"-march=prescott",
@@ -49,35 +46,27 @@
 		},
 		"atom": []string{
 			"-march=atom",
-			"-mfpmath=sse",
 		},
 		"broadwell": []string{
 			"-march=broadwell",
-			"-mfpmath=sse",
 		},
 		"haswell": []string{
 			"-march=core-avx2",
-			"-mfpmath=sse",
 		},
 		"ivybridge": []string{
 			"-march=core-avx-i",
-			"-mfpmath=sse",
 		},
 		"sandybridge": []string{
 			"-march=corei7",
-			"-mfpmath=sse",
 		},
 		"silvermont": []string{
 			"-march=slm",
-			"-mfpmath=sse",
 		},
 		"skylake": []string{
 			"-march=skylake",
-			"-mfpmath=sse",
 		},
 		"stoneyridge": []string{
 			"-march=bdver4",
-			"-mfpmath=sse",
 		},
 	}
 
@@ -109,33 +98,36 @@
 	pctx.SourcePathVariable("X86GccRoot",
 		"prebuilts/gcc/${HostPrebuiltTag}/x86/x86_64-linux-android-${x86GccVersion}")
 
-	pctx.StaticVariable("X86ToolchainCflags", "-m32")
-	pctx.StaticVariable("X86ToolchainLdflags", "-m32")
+	exportedVars.ExportStringListStaticVariable("X86ToolchainCflags", []string{"-m32"})
+	exportedVars.ExportStringListStaticVariable("X86ToolchainLdflags", []string{"-m32"})
 
-	pctx.StaticVariable("X86Ldflags", strings.Join(x86Ldflags, " "))
-	pctx.StaticVariable("X86Lldflags", strings.Join(x86Lldflags, " "))
+	exportedVars.ExportStringListStaticVariable("X86Ldflags", x86Ldflags)
+	exportedVars.ExportStringListStaticVariable("X86Lldflags", x86Ldflags)
 
 	// Clang cflags
-	pctx.StaticVariable("X86ClangCflags", strings.Join(ClangFilterUnknownCflags(x86ClangCflags), " "))
-	pctx.StaticVariable("X86ClangLdflags", strings.Join(ClangFilterUnknownCflags(x86Ldflags), " "))
-	pctx.StaticVariable("X86ClangLldflags", strings.Join(ClangFilterUnknownCflags(x86Lldflags), " "))
-	pctx.StaticVariable("X86ClangCppflags", strings.Join(ClangFilterUnknownCflags(x86Cppflags), " "))
+	exportedVars.ExportStringListStaticVariable("X86Cflags", x86Cflags)
+	exportedVars.ExportStringListStaticVariable("X86Cppflags", x86Cppflags)
 
 	// Yasm flags
-	pctx.StaticVariable("X86YasmFlags", "-f elf32 -m x86")
+	exportedVars.ExportStringListStaticVariable("X86YasmFlags", []string{
+		"-f elf32",
+		"-m x86",
+	})
 
 	// Extended cflags
+	exportedVars.ExportStringListDict("X86ArchVariantCflags", x86ArchVariantCflags)
+	exportedVars.ExportStringListDict("X86ArchFeatureCflags", x86ArchFeatureCflags)
 
 	// Architecture variant cflags
 	for variant, cflags := range x86ArchVariantCflags {
-		pctx.StaticVariable("X86"+variant+"VariantClangCflags",
-			strings.Join(ClangFilterUnknownCflags(cflags), " "))
+		pctx.StaticVariable("X86"+variant+"VariantCflags", strings.Join(cflags, " "))
 	}
 }
 
 type toolchainX86 struct {
+	toolchainBionic
 	toolchain32Bit
-	toolchainClangCflags string
+	toolchainCflags string
 }
 
 func (t *toolchainX86) Name() string {
@@ -162,27 +154,27 @@
 	return "i686-linux-android"
 }
 
-func (t *toolchainX86) ToolchainClangLdflags() string {
+func (t *toolchainX86) ToolchainLdflags() string {
 	return "${config.X86ToolchainLdflags}"
 }
 
-func (t *toolchainX86) ToolchainClangCflags() string {
-	return t.toolchainClangCflags
+func (t *toolchainX86) ToolchainCflags() string {
+	return t.toolchainCflags
 }
 
-func (t *toolchainX86) ClangCflags() string {
-	return "${config.X86ClangCflags}"
+func (t *toolchainX86) Cflags() string {
+	return "${config.X86Cflags}"
 }
 
-func (t *toolchainX86) ClangCppflags() string {
-	return "${config.X86ClangCppflags}"
+func (t *toolchainX86) Cppflags() string {
+	return "${config.X86Cppflags}"
 }
 
-func (t *toolchainX86) ClangLdflags() string {
+func (t *toolchainX86) Ldflags() string {
 	return "${config.X86Ldflags}"
 }
 
-func (t *toolchainX86) ClangLldflags() string {
+func (t *toolchainX86) Lldflags() string {
 	return "${config.X86Lldflags}"
 }
 
@@ -195,17 +187,22 @@
 }
 
 func x86ToolchainFactory(arch android.Arch) Toolchain {
-	toolchainClangCflags := []string{
+	// Error now rather than having a confusing Ninja error
+	if _, ok := x86ArchVariantCflags[arch.ArchVariant]; !ok {
+		panic(fmt.Sprintf("Unknown x86 architecture version: %q", arch.ArchVariant))
+	}
+
+	toolchainCflags := []string{
 		"${config.X86ToolchainCflags}",
-		"${config.X86" + arch.ArchVariant + "VariantClangCflags}",
+		"${config.X86" + arch.ArchVariant + "VariantCflags}",
 	}
 
 	for _, feature := range arch.ArchFeatures {
-		toolchainClangCflags = append(toolchainClangCflags, x86ArchFeatureCflags[feature]...)
+		toolchainCflags = append(toolchainCflags, x86ArchFeatureCflags[feature]...)
 	}
 
 	return &toolchainX86{
-		toolchainClangCflags: strings.Join(toolchainClangCflags, " "),
+		toolchainCflags: strings.Join(toolchainCflags, " "),
 	}
 }
 
diff --git a/cc/config/x86_linux_bionic_host.go b/cc/config/x86_linux_bionic_host.go
index fa625e3..976cc25 100644
--- a/cc/config/x86_linux_bionic_host.go
+++ b/cc/config/x86_linux_bionic_host.go
@@ -21,9 +21,7 @@
 )
 
 var (
-	linuxBionicCflags = ClangFilterUnknownCflags([]string{
-		"-fdiagnostics-color",
-
+	linuxBionicCflags = []string{
 		"-Wa,--noexecstack",
 
 		"-fPIC",
@@ -34,41 +32,44 @@
 
 		// From x86_64_device
 		"-ffunction-sections",
-		"-finline-functions",
-		"-finline-limit=300",
 		"-fno-short-enums",
-		"-funswitch-loops",
 		"-funwind-tables",
-		"-fno-canonical-system-headers",
 
 		// Tell clang where the gcc toolchain is
 		"--gcc-toolchain=${LinuxBionicGccRoot}",
 
 		// This is normally in ClangExtraTargetCflags, but this is considered host
 		"-nostdlibinc",
-	})
+	}
 
-	linuxBionicLdflags = ClangFilterUnknownCflags([]string{
+	linuxBionicLdflags = []string{
 		"-Wl,-z,noexecstack",
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--hash-style=gnu",
 		"-Wl,--no-undefined-version",
 
 		// Use the device gcc toolchain
 		"--gcc-toolchain=${LinuxBionicGccRoot}",
-	})
+	}
 
-	linuxBionicLldflags = ClangFilterUnknownLldflags(linuxBionicLdflags)
+	// Embed the linker into host bionic binaries. This is needed to support host bionic,
+	// as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
+	// either an absolute path, or relative from CWD. To work around this, we extract
+	// the load sections from the runtime linker ELF binary and embed them into each host
+	// bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
+	// binary, and then we use a special entry point to fix up the arguments passed by
+	// the kernel before jumping to the embedded linker.
+	linuxBionicCrtBeginSharedBinary = append(android.CopyOf(bionicCrtBeginSharedBinary),
+		"host_bionic_linker_script")
 )
 
 func init() {
 	pctx.StaticVariable("LinuxBionicCflags", strings.Join(linuxBionicCflags, " "))
 	pctx.StaticVariable("LinuxBionicLdflags", strings.Join(linuxBionicLdflags, " "))
-	pctx.StaticVariable("LinuxBionicLldflags", strings.Join(linuxBionicLldflags, " "))
+	pctx.StaticVariable("LinuxBionicLldflags", strings.Join(linuxBionicLdflags, " "))
 
 	// Use the device gcc toolchain for now
 	pctx.StaticVariable("LinuxBionicGccRoot", "${X86_64GccRoot}")
@@ -76,6 +77,7 @@
 
 type toolchainLinuxBionic struct {
 	toolchain64Bit
+	toolchainBionic
 }
 
 func (t *toolchainLinuxBionic) Name() string {
@@ -103,29 +105,29 @@
 	return "x86_64-linux-android"
 }
 
-func (t *toolchainLinuxBionic) ClangCflags() string {
+func (t *toolchainLinuxBionic) Cflags() string {
 	return "${config.LinuxBionicCflags}"
 }
 
-func (t *toolchainLinuxBionic) ClangCppflags() string {
+func (t *toolchainLinuxBionic) Cppflags() string {
 	return ""
 }
 
-func (t *toolchainLinuxBionic) ClangLdflags() string {
+func (t *toolchainLinuxBionic) Ldflags() string {
 	return "${config.LinuxBionicLdflags}"
 }
 
-func (t *toolchainLinuxBionic) ClangLldflags() string {
+func (t *toolchainLinuxBionic) Lldflags() string {
 	return "${config.LinuxBionicLldflags}"
 }
 
-func (t *toolchainLinuxBionic) ToolchainClangCflags() string {
+func (t *toolchainLinuxBionic) ToolchainCflags() string {
 	return "-m64 -march=x86-64" +
 		// TODO: We're not really android, but we don't have a triple yet b/31393676
 		" -U__ANDROID__"
 }
 
-func (t *toolchainLinuxBionic) ToolchainClangLdflags() string {
+func (t *toolchainLinuxBionic) ToolchainLdflags() string {
 	return "-m64"
 }
 
@@ -133,14 +135,14 @@
 	return nil
 }
 
-func (t *toolchainLinuxBionic) Bionic() bool {
-	return true
-}
-
 func (toolchainLinuxBionic) LibclangRuntimeLibraryArch() string {
 	return "x86_64"
 }
 
+func (toolchainLinuxBionic) CrtBeginSharedBinary() []string {
+	return linuxBionicCrtBeginSharedBinary
+}
+
 var toolchainLinuxBionicSingleton Toolchain = &toolchainLinuxBionic{}
 
 func linuxBionicToolchainFactory(arch android.Arch) Toolchain {
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index 13b5511..4e8fd77 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -22,8 +22,6 @@
 
 var (
 	linuxCflags = []string{
-		"-fdiagnostics-color",
-
 		"-Wa,--noexecstack",
 
 		"-fPIC",
@@ -36,6 +34,20 @@
 		//See bug 12708004.
 		"-D__STDC_FORMAT_MACROS",
 		"-D__STDC_CONSTANT_MACROS",
+
+		"--gcc-toolchain=${LinuxGccRoot}",
+		"-fstack-protector-strong",
+	}
+
+	linuxGlibcCflags = []string{
+		"--sysroot ${LinuxGccRoot}/sysroot",
+	}
+
+	linuxMuslCflags = []string{
+		"-D_LIBCPP_HAS_MUSL_LIBC",
+		"-DANDROID_HOST_MUSL",
+		"-nostdlibinc",
+		"--sysroot /dev/null",
 	}
 
 	linuxLdflags = []string{
@@ -43,12 +55,22 @@
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--no-undefined-version",
+
+		"--gcc-toolchain=${LinuxGccRoot}",
+	}
+
+	linuxGlibcLdflags = []string{
+		"--sysroot ${LinuxGccRoot}/sysroot",
+	}
+
+	linuxMuslLdflags = []string{
+		"-nostdlib",
+		"--sysroot /dev/null",
 	}
 
 	// Extended cflags
 	linuxX86Cflags = []string{
 		"-msse3",
-		"-mfpmath=sse",
 		"-m32",
 		"-march=prescott",
 		"-D_FILE_OFFSET_BITS=64",
@@ -61,40 +83,17 @@
 
 	linuxX86Ldflags = []string{
 		"-m32",
+		"-B${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}/32",
+		"-L${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}/32",
+		"-L${LinuxGccRoot}/${LinuxGccTriple}/lib32",
 	}
 
 	linuxX8664Ldflags = []string{
 		"-m64",
-	}
-
-	linuxClangCflags = append(ClangFilterUnknownCflags(linuxCflags), []string{
-		"--gcc-toolchain=${LinuxGccRoot}",
-		"--sysroot ${LinuxGccRoot}/sysroot",
-		"-fstack-protector-strong",
-	}...)
-
-	linuxClangLdflags = append(ClangFilterUnknownCflags(linuxLdflags), []string{
-		"--gcc-toolchain=${LinuxGccRoot}",
-		"--sysroot ${LinuxGccRoot}/sysroot",
-	}...)
-
-	linuxClangLldflags = ClangFilterUnknownLldflags(linuxClangLdflags)
-
-	linuxX86ClangLdflags = append(ClangFilterUnknownCflags(linuxX86Ldflags), []string{
-		"-B${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}/32",
-		"-L${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}/32",
-		"-L${LinuxGccRoot}/${LinuxGccTriple}/lib32",
-	}...)
-
-	linuxX86ClangLldflags = ClangFilterUnknownLldflags(linuxX86ClangLdflags)
-
-	linuxX8664ClangLdflags = append(ClangFilterUnknownCflags(linuxX8664Ldflags), []string{
 		"-B${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}",
 		"-L${LinuxGccRoot}/lib/gcc/${LinuxGccTriple}/${LinuxGccVersion}",
 		"-L${LinuxGccRoot}/${LinuxGccTriple}/lib64",
-	}...)
-
-	linuxX8664ClangLldflags = ClangFilterUnknownLldflags(linuxX8664ClangLdflags)
+	}
 
 	linuxAvailableLibraries = addPrefix([]string{
 		"c",
@@ -108,6 +107,12 @@
 		"rt",
 		"util",
 	}, "-l")
+
+	muslCrtBeginStaticBinary, muslCrtEndStaticBinary   = []string{"libc_musl_crtbegin_static"}, []string{"libc_musl_crtend"}
+	muslCrtBeginSharedBinary, muslCrtEndSharedBinary   = []string{"libc_musl_crtbegin_dynamic", "musl_linker_script"}, []string{"libc_musl_crtend"}
+	muslCrtBeginSharedLibrary, muslCrtEndSharedLibrary = []string{"libc_musl_crtbegin_so"}, []string{"libc_musl_crtend_so"}
+
+	muslDefaultSharedLibraries = []string{"libc_musl"}
 )
 
 const (
@@ -116,35 +121,40 @@
 )
 
 func init() {
-	pctx.StaticVariable("LinuxGccVersion", linuxGccVersion)
-	pctx.StaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
+	exportedVars.ExportStringStaticVariable("LinuxGccVersion", linuxGccVersion)
+	exportedVars.ExportStringStaticVariable("LinuxGlibcVersion", linuxGlibcVersion)
+
 	// Most places use the full GCC version. A few only use up to the first two numbers.
 	if p := strings.Split(linuxGccVersion, "."); len(p) > 2 {
-		pctx.StaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
+		exportedVars.ExportStringStaticVariable("ShortLinuxGccVersion", strings.Join(p[:2], "."))
 	} else {
-		pctx.StaticVariable("ShortLinuxGccVersion", linuxGccVersion)
+		exportedVars.ExportStringStaticVariable("ShortLinuxGccVersion", linuxGccVersion)
 	}
 
-	pctx.SourcePathVariable("LinuxGccRoot",
-		"prebuilts/gcc/${HostPrebuiltTag}/host/x86_64-linux-glibc${LinuxGlibcVersion}-${ShortLinuxGccVersion}")
+	exportedVars.ExportSourcePathVariable("LinuxGccRoot",
+		"prebuilts/gcc/linux-x86/host/x86_64-linux-glibc${LinuxGlibcVersion}-${ShortLinuxGccVersion}")
 
-	pctx.StaticVariable("LinuxGccTriple", "x86_64-linux")
+	exportedVars.ExportStringListStaticVariable("LinuxGccTriple", []string{"x86_64-linux"})
 
-	pctx.StaticVariable("LinuxClangCflags", strings.Join(linuxClangCflags, " "))
-	pctx.StaticVariable("LinuxClangLdflags", strings.Join(linuxClangLdflags, " "))
-	pctx.StaticVariable("LinuxClangLldflags", strings.Join(linuxClangLldflags, " "))
+	exportedVars.ExportStringListStaticVariable("LinuxCflags", linuxCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxLdflags", linuxLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxLldflags", linuxLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcCflags", linuxGlibcCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcLdflags", linuxGlibcLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxGlibcLldflags", linuxGlibcLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslCflags", linuxMuslCflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslLdflags", linuxMuslLdflags)
+	exportedVars.ExportStringListStaticVariable("LinuxMuslLldflags", linuxMuslLdflags)
 
-	pctx.StaticVariable("LinuxX86ClangCflags",
-		strings.Join(ClangFilterUnknownCflags(linuxX86Cflags), " "))
-	pctx.StaticVariable("LinuxX8664ClangCflags",
-		strings.Join(ClangFilterUnknownCflags(linuxX8664Cflags), " "))
-	pctx.StaticVariable("LinuxX86ClangLdflags", strings.Join(linuxX86ClangLdflags, " "))
-	pctx.StaticVariable("LinuxX86ClangLldflags", strings.Join(linuxX86ClangLldflags, " "))
-	pctx.StaticVariable("LinuxX8664ClangLdflags", strings.Join(linuxX8664ClangLdflags, " "))
-	pctx.StaticVariable("LinuxX8664ClangLldflags", strings.Join(linuxX8664ClangLldflags, " "))
+	exportedVars.ExportStringListStaticVariable("LinuxX86Cflags", linuxX86Cflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Cflags", linuxX8664Cflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX86Ldflags", linuxX86Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX86Lldflags", linuxX86Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Ldflags", linuxX8664Ldflags)
+	exportedVars.ExportStringListStaticVariable("LinuxX8664Lldflags", linuxX8664Ldflags)
 	// Yasm flags
-	pctx.StaticVariable("LinuxX86YasmFlags", "-f elf32 -m x86")
-	pctx.StaticVariable("LinuxX8664YasmFlags", "-f elf64 -m amd64")
+	exportedVars.ExportStringListStaticVariable("LinuxX86YasmFlags", []string{"-f elf32 -m x86"})
+	exportedVars.ExportStringListStaticVariable("LinuxX8664YasmFlags", []string{"-f elf64 -m amd64"})
 }
 
 type toolchainLinux struct {
@@ -185,44 +195,36 @@
 	return ""
 }
 
-func (t *toolchainLinuxX86) ClangTriple() string {
-	return "i686-linux-gnu"
+func (t *toolchainLinuxX86) Cflags() string {
+	return "${config.LinuxCflags} ${config.LinuxX86Cflags}"
 }
 
-func (t *toolchainLinuxX86) ClangCflags() string {
-	return "${config.LinuxClangCflags} ${config.LinuxX86ClangCflags}"
-}
-
-func (t *toolchainLinuxX86) ClangCppflags() string {
+func (t *toolchainLinuxX86) Cppflags() string {
 	return ""
 }
 
-func (t *toolchainLinuxX8664) ClangTriple() string {
-	return "x86_64-linux-gnu"
+func (t *toolchainLinuxX8664) Cflags() string {
+	return "${config.LinuxCflags} ${config.LinuxX8664Cflags}"
 }
 
-func (t *toolchainLinuxX8664) ClangCflags() string {
-	return "${config.LinuxClangCflags} ${config.LinuxX8664ClangCflags}"
-}
-
-func (t *toolchainLinuxX8664) ClangCppflags() string {
+func (t *toolchainLinuxX8664) Cppflags() string {
 	return ""
 }
 
-func (t *toolchainLinuxX86) ClangLdflags() string {
-	return "${config.LinuxClangLdflags} ${config.LinuxX86ClangLdflags}"
+func (t *toolchainLinuxX86) Ldflags() string {
+	return "${config.LinuxLdflags} ${config.LinuxX86Ldflags}"
 }
 
-func (t *toolchainLinuxX86) ClangLldflags() string {
-	return "${config.LinuxClangLldflags} ${config.LinuxX86ClangLldflags}"
+func (t *toolchainLinuxX86) Lldflags() string {
+	return "${config.LinuxLldflags} ${config.LinuxX86Lldflags}"
 }
 
-func (t *toolchainLinuxX8664) ClangLdflags() string {
-	return "${config.LinuxClangLdflags} ${config.LinuxX8664ClangLdflags}"
+func (t *toolchainLinuxX8664) Ldflags() string {
+	return "${config.LinuxLdflags} ${config.LinuxX8664Ldflags}"
 }
 
-func (t *toolchainLinuxX8664) ClangLldflags() string {
-	return "${config.LinuxClangLldflags} ${config.LinuxX8664ClangLldflags}"
+func (t *toolchainLinuxX8664) Lldflags() string {
+	return "${config.LinuxLldflags} ${config.LinuxX8664Lldflags}"
 }
 
 func (t *toolchainLinuxX86) YasmFlags() string {
@@ -245,22 +247,162 @@
 	return linuxAvailableLibraries
 }
 
-func (t *toolchainLinux) Bionic() bool {
-	return false
+// glibc specialization of the linux toolchain
+
+type toolchainGlibc struct {
 }
 
-var toolchainLinuxX86Singleton Toolchain = &toolchainLinuxX86{}
-var toolchainLinuxX8664Singleton Toolchain = &toolchainLinuxX8664{}
+func (toolchainGlibc) Glibc() bool { return true }
 
-func linuxX86ToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainLinuxX86Singleton
+func (toolchainGlibc) Cflags() string {
+	return "${config.LinuxGlibcCflags}"
 }
 
-func linuxX8664ToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainLinuxX8664Singleton
+func (toolchainGlibc) Ldflags() string {
+	return "${config.LinuxGlibcLdflags}"
+}
+
+func (toolchainGlibc) Lldflags() string {
+	return "${config.LinuxGlibcLldflags}"
+}
+
+type toolchainLinuxGlibcX86 struct {
+	toolchainLinuxX86
+	toolchainGlibc
+}
+
+type toolchainLinuxGlibcX8664 struct {
+	toolchainLinuxX8664
+	toolchainGlibc
+}
+
+func (t *toolchainLinuxGlibcX86) ClangTriple() string {
+	return "i686-linux-gnu"
+}
+
+func (t *toolchainLinuxGlibcX86) Cflags() string {
+	return t.toolchainLinuxX86.Cflags() + " " + t.toolchainGlibc.Cflags()
+}
+
+func (t *toolchainLinuxGlibcX86) Ldflags() string {
+	return t.toolchainLinuxX86.Ldflags() + " " + t.toolchainGlibc.Ldflags()
+}
+
+func (t *toolchainLinuxGlibcX86) Lldflags() string {
+	return t.toolchainLinuxX86.Lldflags() + " " + t.toolchainGlibc.Lldflags()
+}
+
+func (t *toolchainLinuxGlibcX8664) ClangTriple() string {
+	return "x86_64-linux-gnu"
+}
+
+func (t *toolchainLinuxGlibcX8664) Cflags() string {
+	return t.toolchainLinuxX8664.Cflags() + " " + t.toolchainGlibc.Cflags()
+}
+
+func (t *toolchainLinuxGlibcX8664) Ldflags() string {
+	return t.toolchainLinuxX8664.Ldflags() + " " + t.toolchainGlibc.Ldflags()
+}
+
+func (t *toolchainLinuxGlibcX8664) Lldflags() string {
+	return t.toolchainLinuxX8664.Lldflags() + " " + t.toolchainGlibc.Lldflags()
+}
+
+var toolchainLinuxGlibcX86Singleton Toolchain = &toolchainLinuxGlibcX86{}
+var toolchainLinuxGlibcX8664Singleton Toolchain = &toolchainLinuxGlibcX8664{}
+
+func linuxGlibcX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxGlibcX86Singleton
+}
+
+func linuxGlibcX8664ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxGlibcX8664Singleton
+}
+
+// musl specialization of the linux toolchain
+
+type toolchainMusl struct {
+}
+
+func (toolchainMusl) Musl() bool { return true }
+
+func (toolchainMusl) CrtBeginStaticBinary() []string  { return muslCrtBeginStaticBinary }
+func (toolchainMusl) CrtBeginSharedBinary() []string  { return muslCrtBeginSharedBinary }
+func (toolchainMusl) CrtBeginSharedLibrary() []string { return muslCrtBeginSharedLibrary }
+func (toolchainMusl) CrtEndStaticBinary() []string    { return muslCrtEndStaticBinary }
+func (toolchainMusl) CrtEndSharedBinary() []string    { return muslCrtEndSharedBinary }
+func (toolchainMusl) CrtEndSharedLibrary() []string   { return muslCrtEndSharedLibrary }
+
+func (toolchainMusl) DefaultSharedLibraries() []string { return muslDefaultSharedLibraries }
+
+func (toolchainMusl) Cflags() string {
+	return "${config.LinuxMuslCflags}"
+}
+
+func (toolchainMusl) Ldflags() string {
+	return "${config.LinuxMuslLdflags}"
+}
+
+func (toolchainMusl) Lldflags() string {
+	return "${config.LinuxMuslLldflags}"
+}
+
+type toolchainLinuxMuslX86 struct {
+	toolchainLinuxX86
+	toolchainMusl
+}
+
+type toolchainLinuxMuslX8664 struct {
+	toolchainLinuxX8664
+	toolchainMusl
+}
+
+func (t *toolchainLinuxMuslX86) ClangTriple() string {
+	return "i686-linux-musl"
+}
+
+func (t *toolchainLinuxMuslX86) Cflags() string {
+	return t.toolchainLinuxX86.Cflags() + " " + t.toolchainMusl.Cflags()
+}
+
+func (t *toolchainLinuxMuslX86) Ldflags() string {
+	return t.toolchainLinuxX86.Ldflags() + " " + t.toolchainMusl.Ldflags()
+}
+
+func (t *toolchainLinuxMuslX86) Lldflags() string {
+	return t.toolchainLinuxX86.Lldflags() + " " + t.toolchainMusl.Lldflags()
+}
+
+func (t *toolchainLinuxMuslX8664) ClangTriple() string {
+	return "x86_64-linux-musl"
+}
+
+func (t *toolchainLinuxMuslX8664) Cflags() string {
+	return t.toolchainLinuxX8664.Cflags() + " " + t.toolchainMusl.Cflags()
+}
+
+func (t *toolchainLinuxMuslX8664) Ldflags() string {
+	return t.toolchainLinuxX8664.Ldflags() + " " + t.toolchainMusl.Ldflags()
+}
+
+func (t *toolchainLinuxMuslX8664) Lldflags() string {
+	return t.toolchainLinuxX8664.Lldflags() + " " + t.toolchainMusl.Lldflags()
+}
+
+var toolchainLinuxMuslX86Singleton Toolchain = &toolchainLinuxMuslX86{}
+var toolchainLinuxMuslX8664Singleton Toolchain = &toolchainLinuxMuslX8664{}
+
+func linuxMuslX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxMuslX86Singleton
+}
+
+func linuxMuslX8664ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxMuslX8664Singleton
 }
 
 func init() {
-	registerToolchainFactory(android.Linux, android.X86, linuxX86ToolchainFactory)
-	registerToolchainFactory(android.Linux, android.X86_64, linuxX8664ToolchainFactory)
+	registerToolchainFactory(android.Linux, android.X86, linuxGlibcX86ToolchainFactory)
+	registerToolchainFactory(android.Linux, android.X86_64, linuxGlibcX8664ToolchainFactory)
+	registerToolchainFactory(android.LinuxMusl, android.X86, linuxMuslX86ToolchainFactory)
+	registerToolchainFactory(android.LinuxMusl, android.X86_64, linuxMuslX8664ToolchainFactory)
 }
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index b77df79..2c83211 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"path/filepath"
 	"strings"
 
 	"android/soong/android"
@@ -44,32 +45,28 @@
 
 		"--sysroot ${WindowsGccRoot}/${WindowsGccTriple}",
 	}
-	windowsClangCflags = append(ClangFilterUnknownCflags(windowsCflags), []string{}...)
 
 	windowsIncludeFlags = []string{
 		"-isystem ${WindowsGccRoot}/${WindowsGccTriple}/include",
 	}
 
-	windowsClangCppflags = []string{}
+	windowsCppflags = []string{}
 
-	windowsX86ClangCppflags = []string{
+	windowsX86Cppflags = []string{
 		// Use SjLj exceptions for 32-bit.  libgcc_eh implements SjLj
 		// exception model for 32-bit.
 		"-fsjlj-exceptions",
 	}
 
-	windowsX8664ClangCppflags = []string{}
+	windowsX8664Cppflags = []string{}
 
 	windowsLdflags = []string{
-		"--enable-stdcall-fixup",
 		"-Wl,--dynamicbase",
 		"-Wl,--nxcompat",
 	}
-	windowsLldflags = []string{
+	windowsLldflags = append(windowsLdflags, []string{
 		"-Wl,--Xlink=-Brepro", // Enable deterministic build
-	}
-	windowsClangLdflags  = append(ClangFilterUnknownCflags(windowsLdflags), []string{}...)
-	windowsClangLldflags = append(ClangFilterUnknownLldflags(windowsClangLdflags), windowsLldflags...)
+	}...)
 
 	windowsX86Cflags = []string{
 		"-m32",
@@ -84,28 +81,24 @@
 		"-Wl,--large-address-aware",
 		"-L${WindowsGccRoot}/${WindowsGccTriple}/lib32",
 		"-static-libgcc",
-	}
-	windowsX86ClangLdflags = append(ClangFilterUnknownCflags(windowsX86Ldflags), []string{
+
 		"-B${WindowsGccRoot}/${WindowsGccTriple}/bin",
 		"-B${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3/32",
 		"-L${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3/32",
 		"-B${WindowsGccRoot}/${WindowsGccTriple}/lib32",
-	}...)
-	windowsX86ClangLldflags = ClangFilterUnknownLldflags(windowsX86ClangLdflags)
+	}
 
 	windowsX8664Ldflags = []string{
 		"-m64",
 		"-L${WindowsGccRoot}/${WindowsGccTriple}/lib64",
 		"-Wl,--high-entropy-va",
 		"-static-libgcc",
-	}
-	windowsX8664ClangLdflags = append(ClangFilterUnknownCflags(windowsX8664Ldflags), []string{
+
 		"-B${WindowsGccRoot}/${WindowsGccTriple}/bin",
 		"-B${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3",
 		"-L${WindowsGccRoot}/lib/gcc/${WindowsGccTriple}/4.8.3",
 		"-B${WindowsGccRoot}/${WindowsGccTriple}/lib64",
-	}...)
-	windowsX8664ClangLldflags = ClangFilterUnknownLldflags(windowsX8664ClangLdflags)
+	}
 
 	windowsAvailableLibraries = addPrefix([]string{
 		"gdi32",
@@ -138,21 +131,19 @@
 
 	pctx.StaticVariable("WindowsGccTriple", "x86_64-w64-mingw32")
 
-	pctx.StaticVariable("WindowsClangCflags", strings.Join(windowsClangCflags, " "))
-	pctx.StaticVariable("WindowsClangLdflags", strings.Join(windowsClangLdflags, " "))
-	pctx.StaticVariable("WindowsClangLldflags", strings.Join(windowsClangLldflags, " "))
-	pctx.StaticVariable("WindowsClangCppflags", strings.Join(windowsClangCppflags, " "))
+	pctx.StaticVariable("WindowsCflags", strings.Join(windowsCflags, " "))
+	pctx.StaticVariable("WindowsLdflags", strings.Join(windowsLdflags, " "))
+	pctx.StaticVariable("WindowsLldflags", strings.Join(windowsLldflags, " "))
+	pctx.StaticVariable("WindowsCppflags", strings.Join(windowsCppflags, " "))
 
-	pctx.StaticVariable("WindowsX86ClangCflags",
-		strings.Join(ClangFilterUnknownCflags(windowsX86Cflags), " "))
-	pctx.StaticVariable("WindowsX8664ClangCflags",
-		strings.Join(ClangFilterUnknownCflags(windowsX8664Cflags), " "))
-	pctx.StaticVariable("WindowsX86ClangLdflags", strings.Join(windowsX86ClangLdflags, " "))
-	pctx.StaticVariable("WindowsX86ClangLldflags", strings.Join(windowsX86ClangLldflags, " "))
-	pctx.StaticVariable("WindowsX8664ClangLdflags", strings.Join(windowsX8664ClangLdflags, " "))
-	pctx.StaticVariable("WindowsX8664ClangLldflags", strings.Join(windowsX8664ClangLldflags, " "))
-	pctx.StaticVariable("WindowsX86ClangCppflags", strings.Join(windowsX86ClangCppflags, " "))
-	pctx.StaticVariable("WindowsX8664ClangCppflags", strings.Join(windowsX8664ClangCppflags, " "))
+	pctx.StaticVariable("WindowsX86Cflags", strings.Join(windowsX86Cflags, " "))
+	pctx.StaticVariable("WindowsX8664Cflags", strings.Join(windowsX8664Cflags, " "))
+	pctx.StaticVariable("WindowsX86Ldflags", strings.Join(windowsX86Ldflags, " "))
+	pctx.StaticVariable("WindowsX86Lldflags", strings.Join(windowsX86Ldflags, " "))
+	pctx.StaticVariable("WindowsX8664Ldflags", strings.Join(windowsX8664Ldflags, " "))
+	pctx.StaticVariable("WindowsX8664Lldflags", strings.Join(windowsX8664Ldflags, " "))
+	pctx.StaticVariable("WindowsX86Cppflags", strings.Join(windowsX86Cppflags, " "))
+	pctx.StaticVariable("WindowsX8664Cppflags", strings.Join(windowsX8664Cppflags, " "))
 
 	pctx.StaticVariable("WindowsIncludeFlags", strings.Join(windowsIncludeFlags, " "))
 	// Yasm flags
@@ -190,6 +181,14 @@
 	return "${config.WindowsGccTriple}"
 }
 
+func (t *toolchainWindows) ToolchainCflags() string {
+	return "-B" + filepath.Join(t.GccRoot(), t.GccTriple(), "bin")
+}
+
+func (t *toolchainWindows) ToolchainLdflags() string {
+	return "-B" + filepath.Join(t.GccRoot(), t.GccTriple(), "bin")
+}
+
 func (t *toolchainWindows) GccVersion() string {
 	return windowsGccVersion
 }
@@ -198,14 +197,6 @@
 	return "${config.WindowsIncludeFlags}"
 }
 
-func (t *toolchainWindowsX86) WindresFlags() string {
-	return "-F pe-i386"
-}
-
-func (t *toolchainWindowsX8664) WindresFlags() string {
-	return "-F pe-x86-64"
-}
-
 func (t *toolchainWindowsX86) ClangTriple() string {
 	return "i686-windows-gnu"
 }
@@ -214,36 +205,36 @@
 	return "x86_64-pc-windows-gnu"
 }
 
-func (t *toolchainWindowsX86) ClangCflags() string {
-	return "${config.WindowsClangCflags} ${config.WindowsX86ClangCflags}"
+func (t *toolchainWindowsX86) Cflags() string {
+	return "${config.WindowsCflags} ${config.WindowsX86Cflags}"
 }
 
-func (t *toolchainWindowsX8664) ClangCflags() string {
-	return "${config.WindowsClangCflags} ${config.WindowsX8664ClangCflags}"
+func (t *toolchainWindowsX8664) Cflags() string {
+	return "${config.WindowsCflags} ${config.WindowsX8664Cflags}"
 }
 
-func (t *toolchainWindowsX86) ClangCppflags() string {
-	return "${config.WindowsClangCppflags} ${config.WindowsX86ClangCppflags}"
+func (t *toolchainWindowsX86) Cppflags() string {
+	return "${config.WindowsCppflags} ${config.WindowsX86Cppflags}"
 }
 
-func (t *toolchainWindowsX8664) ClangCppflags() string {
-	return "${config.WindowsClangCppflags} ${config.WindowsX8664ClangCppflags}"
+func (t *toolchainWindowsX8664) Cppflags() string {
+	return "${config.WindowsCppflags} ${config.WindowsX8664Cppflags}"
 }
 
-func (t *toolchainWindowsX86) ClangLdflags() string {
-	return "${config.WindowsClangLdflags} ${config.WindowsX86ClangLdflags}"
+func (t *toolchainWindowsX86) Ldflags() string {
+	return "${config.WindowsLdflags} ${config.WindowsX86Ldflags}"
 }
 
-func (t *toolchainWindowsX86) ClangLldflags() string {
-	return "${config.WindowsClangLldflags} ${config.WindowsX86ClangLldflags}"
+func (t *toolchainWindowsX86) Lldflags() string {
+	return "${config.WindowsLldflags} ${config.WindowsX86Lldflags}"
 }
 
-func (t *toolchainWindowsX8664) ClangLdflags() string {
-	return "${config.WindowsClangLdflags} ${config.WindowsX8664ClangLdflags}"
+func (t *toolchainWindowsX8664) Ldflags() string {
+	return "${config.WindowsLdflags} ${config.WindowsX8664Ldflags}"
 }
 
-func (t *toolchainWindowsX8664) ClangLldflags() string {
-	return "${config.WindowsClangLldflags} ${config.WindowsX8664ClangLldflags}"
+func (t *toolchainWindowsX8664) Lldflags() string {
+	return "${config.WindowsLldflags} ${config.WindowsX8664Lldflags}"
 }
 
 func (t *toolchainWindowsX86) YasmFlags() string {
diff --git a/cc/coverage.go b/cc/coverage.go
index baf4226..d0902ea 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -77,6 +77,10 @@
 	return deps
 }
 
+func EnableContinuousCoverage(ctx android.BaseModuleContext) bool {
+	return ctx.DeviceConfig().ClangCoverageContinuousMode()
+}
+
 func (cov *coverage) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
 	clangCoverage := ctx.DeviceConfig().ClangCoverageEnabled()
 	gcovCoverage := ctx.DeviceConfig().GcovCoverageEnabled()
@@ -98,6 +102,12 @@
 		} else if clangCoverage {
 			flags.Local.CommonFlags = append(flags.Local.CommonFlags, profileInstrFlag,
 				"-fcoverage-mapping", "-Wno-pass-failed", "-D__ANDROID_CLANG_COVERAGE__")
+			// Override -Wframe-larger-than.  We can expect frame size increase after
+			// coverage instrumentation.
+			flags.Local.CFlags = append(flags.Local.CFlags, "-Wno-frame-larger-than=")
+			if EnableContinuousCoverage(ctx) {
+				flags.Local.CommonFlags = append(flags.Local.CommonFlags, "-mllvm", "-runtime-counter-relocation")
+			}
 		}
 	}
 
@@ -149,6 +159,9 @@
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv")
 		} else if clangCoverage {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrFlag)
+			if EnableContinuousCoverage(ctx) {
+				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm=-runtime-counter-relocation")
+			}
 
 			coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), CoverageDepTag).(*Module)
 			deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
@@ -244,3 +257,19 @@
 		m[1].(Coverage).EnableCoverageIfNeeded()
 	}
 }
+
+func parseSymbolFileForAPICoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath {
+	apiLevelsJson := android.GetApiLevelsJson(ctx)
+	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
+	outputFile := ctx.baseModuleName() + ".xml"
+	parsedApiCoveragePath := android.PathForModuleOut(ctx, outputFile)
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("ndk_api_coverage_parser").
+		Input(symbolFilePath).
+		Output(parsedApiCoveragePath).
+		Implicit(apiLevelsJson).
+		FlagWithArg("--api-map ", apiLevelsJson.String())
+	rule.Build("native_library_api_list", "Generate native API list based on symbol files for coverage measurement")
+	return parsedApiCoveragePath
+}
diff --git a/cc/fuzz.go b/cc/fuzz.go
index c780b6f..23d81d6 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -15,7 +15,6 @@
 package cc
 
 import (
-	"encoding/json"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -24,55 +23,9 @@
 
 	"android/soong/android"
 	"android/soong/cc/config"
+	"android/soong/fuzz"
 )
 
-type FuzzConfig struct {
-	// Email address of people to CC on bugs or contact about this fuzz target.
-	Cc []string `json:"cc,omitempty"`
-	// Specify whether to enable continuous fuzzing on devices. Defaults to true.
-	Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
-	// Specify whether to enable continuous fuzzing on host. Defaults to true.
-	Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"`
-	// Component in Google's bug tracking system that bugs should be filed to.
-	Componentid *int64 `json:"componentid,omitempty"`
-	// Hotlists in Google's bug tracking system that bugs should be marked with.
-	Hotlists []string `json:"hotlists,omitempty"`
-	// Specify whether this fuzz target was submitted by a researcher. Defaults
-	// to false.
-	Researcher_submitted *bool `json:"researcher_submitted,omitempty"`
-	// Specify who should be acknowledged for CVEs in the Android Security
-	// Bulletin.
-	Acknowledgement []string `json:"acknowledgement,omitempty"`
-	// Additional options to be passed to libfuzzer when run in Haiku.
-	Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
-	// Additional options to be passed to HWASAN when running on-device in Haiku.
-	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"`
-}
-
-func (f *FuzzConfig) String() string {
-	b, err := json.Marshal(f)
-	if err != nil {
-		panic(err)
-	}
-
-	return string(b)
-}
-
-type FuzzProperties struct {
-	// Optional list of seed files to be installed to the fuzz target's output
-	// directory.
-	Corpus []string `android:"path"`
-	// Optional list of data files to be installed to the fuzz target's output
-	// directory. Directory structure relative to the module is preserved.
-	Data []string `android:"path"`
-	// Optional dictionary to be installed to the fuzz target's output directory.
-	Dictionary *string `android:"path"`
-	// Config for running the target on fuzzing infrastructure.
-	Fuzz_config *FuzzConfig
-}
-
 func init() {
 	android.RegisterModuleType("cc_fuzz", FuzzFactory)
 	android.RegisterSingletonType("cc_fuzz_packaging", fuzzPackagingFactory)
@@ -94,19 +47,18 @@
 	*binaryDecorator
 	*baseCompiler
 
-	Properties            FuzzProperties
-	dictionary            android.Path
-	corpus                android.Paths
-	corpusIntermediateDir android.Path
-	config                android.Path
-	data                  android.Paths
-	dataIntermediateDir   android.Path
-	installedSharedDeps   []string
+	fuzzPackagedModule fuzz.FuzzPackagedModule
+
+	installedSharedDeps []string
+}
+
+func (fuzz *fuzzBinary) fuzzBinary() bool {
+	return true
 }
 
 func (fuzz *fuzzBinary) linkerProps() []interface{} {
 	props := fuzz.binaryDecorator.linkerProps()
-	props = append(props, &fuzz.Properties)
+	props = append(props, &fuzz.fuzzPackagedModule.FuzzProperties)
 	return props
 }
 
@@ -131,54 +83,21 @@
 	return flags
 }
 
-// This function performs a breadth-first search over the provided module's
-// dependencies using `visitDirectDeps` to enumerate all shared library
-// dependencies. We require breadth-first expansion, as otherwise we may
-// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
-// from a dependency. This may cause issues when dependencies have explicit
-// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
-func collectAllSharedDependencies(ctx android.SingletonContext, module android.Module) android.Paths {
-	var fringe []android.Module
-
-	seen := make(map[string]bool)
-
-	// Enumerate the first level of dependencies, as we discard all non-library
-	// modules in the BFS loop below.
-	ctx.VisitDirectDeps(module, func(dep android.Module) {
-		if isValidSharedDependency(dep) {
-			fringe = append(fringe, dep)
-		}
-	})
-
-	var sharedLibraries android.Paths
-
-	for i := 0; i < len(fringe); i++ {
-		module := fringe[i]
-		if seen[module.Name()] {
-			continue
-		}
-		seen[module.Name()] = true
-
-		ccModule := module.(*Module)
-		sharedLibraries = append(sharedLibraries, ccModule.UnstrippedOutputFile())
-		ctx.VisitDirectDeps(module, func(dep android.Module) {
-			if isValidSharedDependency(dep) && !seen[dep.Name()] {
-				fringe = append(fringe, dep)
-			}
-		})
+func UnstrippedOutputFile(module android.Module) android.Path {
+	if mod, ok := module.(LinkableInterface); ok {
+		return mod.UnstrippedOutputFile()
 	}
-
-	return sharedLibraries
+	panic("UnstrippedOutputFile called on non-LinkableInterface module: " + module.Name())
 }
 
-// This function takes a module and determines if it is a unique shared library
+// IsValidSharedDependency takes a module and determines if it is a unique shared library
 // that should be installed in the fuzz target output directories. This function
 // returns true, unless:
 //  - The module is not an installable shared library, or
-//  - The module is a header, stub, or vendor-linked library, or
+//  - The module is a header or stub, or
 //  - The module is a prebuilt and its source is available, or
 //  - The module is a versioned member of an SDK snapshot.
-func isValidSharedDependency(dependency android.Module) bool {
+func IsValidSharedDependency(dependency android.Module) bool {
 	// TODO(b/144090547): We should be parsing these modules using
 	// ModuleDependencyTag instead of the current brute-force checking.
 
@@ -193,11 +112,6 @@
 		return false
 	}
 
-	if linkable.UseVndk() {
-		// Discard vendor linked libraries.
-		return false
-	}
-
 	if lib := moduleLibraryInterface(dependency); lib != nil && lib.buildStubs() && linkable.CcLibrary() {
 		// Discard stubs libs (only CCLibrary variants). Prebuilt libraries should not
 		// be excluded on the basis of they're not CCLibrary()'s.
@@ -213,7 +127,7 @@
 		}
 		// Discard installable:false libraries because they are expected to be absent
 		// in runtime.
-		if !proptools.BoolDefault(ccLibrary.Properties.Installable, true) {
+		if !proptools.BoolDefault(ccLibrary.Installable(), true) {
 			return false
 		}
 	}
@@ -257,41 +171,41 @@
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzz.binaryDecorator.baseInstaller.install(ctx, file)
 
-	fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
+	fuzz.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Corpus)
 	builder := android.NewRuleBuilder(pctx, ctx)
 	intermediateDir := android.PathForModuleOut(ctx, "corpus")
-	for _, entry := range fuzz.corpus {
+	for _, entry := range fuzz.fuzzPackagedModule.Corpus {
 		builder.Command().Text("cp").
 			Input(entry).
 			Output(intermediateDir.Join(ctx, entry.Base()))
 	}
 	builder.Build("copy_corpus", "copy corpus")
-	fuzz.corpusIntermediateDir = intermediateDir
+	fuzz.fuzzPackagedModule.CorpusIntermediateDir = intermediateDir
 
-	fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
+	fuzz.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Data)
 	builder = android.NewRuleBuilder(pctx, ctx)
 	intermediateDir = android.PathForModuleOut(ctx, "data")
-	for _, entry := range fuzz.data {
+	for _, entry := range fuzz.fuzzPackagedModule.Data {
 		builder.Command().Text("cp").
 			Input(entry).
 			Output(intermediateDir.Join(ctx, entry.Rel()))
 	}
 	builder.Build("copy_data", "copy data")
-	fuzz.dataIntermediateDir = intermediateDir
+	fuzz.fuzzPackagedModule.DataIntermediateDir = intermediateDir
 
-	if fuzz.Properties.Dictionary != nil {
-		fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary)
-		if fuzz.dictionary.Ext() != ".dict" {
+	if fuzz.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
+		fuzz.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzz.fuzzPackagedModule.FuzzProperties.Dictionary)
+		if fuzz.fuzzPackagedModule.Dictionary.Ext() != ".dict" {
 			ctx.PropertyErrorf("dictionary",
 				"Fuzzer dictionary %q does not have '.dict' extension",
-				fuzz.dictionary.String())
+				fuzz.fuzzPackagedModule.Dictionary.String())
 		}
 	}
 
-	if fuzz.Properties.Fuzz_config != nil {
+	if fuzz.fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
 		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
-		android.WriteFileRule(ctx, configPath, fuzz.Properties.Fuzz_config.String())
-		fuzz.config = configPath
+		android.WriteFileRule(ctx, configPath, fuzz.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
+		fuzz.fuzzPackagedModule.Config = configPath
 	}
 
 	// Grab the list of required shared libraries.
@@ -303,7 +217,7 @@
 		}
 		seen[child.Name()] = true
 
-		if isValidSharedDependency(child) {
+		if IsValidSharedDependency(child) {
 			sharedLibraries = append(sharedLibraries, child.(*Module).UnstrippedOutputFile())
 			return true
 		}
@@ -324,7 +238,7 @@
 }
 
 func NewFuzz(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 
 	binary.baseInstaller = NewFuzzInstaller()
 	module.sanitize.SetSanitizer(Fuzzer, true)
@@ -359,45 +273,36 @@
 
 // Responsible for generating GNU Make rules that package fuzz targets into
 // their architecture & target/host specific zip file.
-type fuzzPackager struct {
-	packages                android.Paths
-	sharedLibInstallStrings []string
-	fuzzTargets             map[string]bool
+type ccFuzzPackager struct {
+	fuzz.FuzzPackager
 }
 
 func fuzzPackagingFactory() android.Singleton {
-	return &fuzzPackager{}
+	return &ccFuzzPackager{}
 }
 
-type fileToZip struct {
-	SourceFilePath        android.Path
-	DestinationPathPrefix string
-}
-
-type archOs struct {
-	hostOrTarget string
-	arch         string
-	dir          string
-}
-
-func (s *fuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
+func (s *ccFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
 	// Map between each architecture + host/device combination, and the files that
 	// need to be packaged (in the tuple of {source file, destination folder in
 	// archive}).
-	archDirs := make(map[archOs][]fileToZip)
+	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
+
+	// List of individual fuzz targets, so that 'make fuzz' also installs the targets
+	// to the correct output directories as well.
+	s.FuzzTargets = make(map[string]bool)
 
 	// Map tracking whether each shared library has an install rule to avoid duplicate install rules from
 	// multiple fuzzers that depend on the same shared library.
 	sharedLibraryInstalled := make(map[string]bool)
 
-	// List of individual fuzz targets, so that 'make fuzz' also installs the targets
-	// to the correct output directories as well.
-	s.fuzzTargets = make(map[string]bool)
-
 	ctx.VisitAllModules(func(module android.Module) {
-		// Discard non-fuzz targets.
 		ccModule, ok := module.(*Module)
-		if !ok {
+		if !ok || ccModule.Properties.PreventInstall {
+			return
+		}
+
+		// Discard non-fuzz targets.
+		if ok := fuzz.IsValid(ccModule.FuzzModule); !ok {
 			return
 		}
 
@@ -406,18 +311,6 @@
 			return
 		}
 
-		// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
-		// fuzz targets we're going to package anyway.
-		if !ccModule.Enabled() || ccModule.Properties.PreventInstall ||
-			ccModule.InRamdisk() || ccModule.InVendorRamdisk() || ccModule.InRecovery() {
-			return
-		}
-
-		// Discard modules that are in an unavailable namespace.
-		if !ccModule.ExportedToMake() {
-			return
-		}
-
 		hostOrTargetString := "target"
 		if ccModule.Host() {
 			hostOrTargetString = "host"
@@ -425,165 +318,84 @@
 
 		archString := ccModule.Arch().ArchType.String()
 		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
-		archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()}
+		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
 
 		// Grab the list of required shared libraries.
-		sharedLibraries := collectAllSharedDependencies(ctx, module)
+		sharedLibraries := fuzz.CollectAllSharedDependencies(ctx, module, UnstrippedOutputFile, IsValidSharedDependency)
 
-		var files []fileToZip
+		var files []fuzz.FileToZip
 		builder := android.NewRuleBuilder(pctx, ctx)
 
-		// Package the corpora into a zipfile.
-		if fuzzModule.corpus != nil {
-			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
-			command := builder.Command().BuiltTool("soong_zip").
-				Flag("-j").
-				FlagWithOutput("-o ", corpusZip)
-			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
-			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
-			files = append(files, fileToZip{corpusZip, ""})
-		}
+		// Package the corpus, data, dict and config into a zipfile.
+		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
 
-		// Package the data into a zipfile.
-		if fuzzModule.data != nil {
-			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
-			command := builder.Command().BuiltTool("soong_zip").
-				FlagWithOutput("-o ", dataZip)
-			for _, f := range fuzzModule.data {
-				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
-				command.FlagWithArg("-C ", intermediateDir)
-				command.FlagWithInput("-f ", f)
-			}
-			files = append(files, fileToZip{dataZip, ""})
-		}
-
-		// Find and mark all the transiently-dependent shared libraries for
-		// packaging.
-		for _, library := range sharedLibraries {
-			files = append(files, fileToZip{library, "lib"})
-
-			// For each architecture-specific shared library dependency, we need to
-			// install it to the output directory. Setup the install destination here,
-			// which will be used by $(copy-many-files) in the Make backend.
-			installDestination := sharedLibraryInstallLocation(
-				library, ccModule.Host(), archString)
-			if sharedLibraryInstalled[installDestination] {
-				continue
-			}
-			sharedLibraryInstalled[installDestination] = true
-
-			// Escape all the variables, as the install destination here will be called
-			// via. $(eval) in Make.
-			installDestination = strings.ReplaceAll(
-				installDestination, "$", "$$")
-			s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
-				library.String()+":"+installDestination)
-
-			// Ensure that on device, the library is also reinstalled to the /symbols/
-			// dir. Symbolized DSO's are always installed to the device when fuzzing, but
-			// we want symbolization tools (like `stack`) to be able to find the symbols
-			// in $ANDROID_PRODUCT_OUT/symbols automagically.
-			if !ccModule.Host() {
-				symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, archString)
-				symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
-				s.sharedLibInstallStrings = append(s.sharedLibInstallStrings,
-					library.String()+":"+symbolsInstallDestination)
-			}
-		}
+		// Package shared libraries
+		files = append(files, GetSharedLibsToZip(sharedLibraries, ccModule, &s.FuzzPackager, archString, &sharedLibraryInstalled)...)
 
 		// The executable.
-		files = append(files, fileToZip{ccModule.UnstrippedOutputFile(), ""})
+		files = append(files, fuzz.FileToZip{ccModule.UnstrippedOutputFile(), ""})
 
-		// The dictionary.
-		if fuzzModule.dictionary != nil {
-			files = append(files, fileToZip{fuzzModule.dictionary, ""})
+		archDirs[archOs], ok = s.BuildZipFile(ctx, module, fuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
+		if !ok {
+			return
 		}
-
-		// Additional fuzz config.
-		if fuzzModule.config != nil {
-			files = append(files, fileToZip{fuzzModule.config, ""})
-		}
-
-		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
-		command := builder.Command().BuiltTool("soong_zip").
-			Flag("-j").
-			FlagWithOutput("-o ", fuzzZip)
-		for _, file := range files {
-			if file.DestinationPathPrefix != "" {
-				command.FlagWithArg("-P ", file.DestinationPathPrefix)
-			} else {
-				command.Flag("-P ''")
-			}
-			command.FlagWithInput("-f ", file.SourceFilePath)
-		}
-
-		builder.Build("create-"+fuzzZip.String(),
-			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
-
-		// Don't add modules to 'make haiku' that are set to not be exported to the
-		// fuzzing infrastructure.
-		if config := fuzzModule.Properties.Fuzz_config; config != nil {
-			if ccModule.Host() && !BoolDefault(config.Fuzz_on_haiku_host, true) {
-				return
-			} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
-				return
-			}
-		}
-
-		s.fuzzTargets[module.Name()] = true
-		archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""})
 	})
 
-	var archOsList []archOs
-	for archOs := range archDirs {
-		archOsList = append(archOsList, archOs)
-	}
-	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir })
+	s.CreateFuzzPackage(ctx, archDirs, fuzz.Cc, pctx)
 
-	for _, archOs := range archOsList {
-		filesToZip := archDirs[archOs]
-		arch := archOs.arch
-		hostOrTarget := archOs.hostOrTarget
-		builder := android.NewRuleBuilder(pctx, ctx)
-		outputFile := android.PathForOutput(ctx, "fuzz-"+hostOrTarget+"-"+arch+".zip")
-		s.packages = append(s.packages, outputFile)
-
-		command := builder.Command().BuiltTool("soong_zip").
-			Flag("-j").
-			FlagWithOutput("-o ", outputFile).
-			Flag("-L 0") // No need to try and re-compress the zipfiles.
-
-		for _, fileToZip := range filesToZip {
-			if fileToZip.DestinationPathPrefix != "" {
-				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
-			} else {
-				command.Flag("-P ''")
-			}
-			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
-		}
-
-		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
-			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
-	}
 }
 
-func (s *fuzzPackager) MakeVars(ctx android.MakeVarsContext) {
-	packages := s.packages.Strings()
+func (s *ccFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+	packages := s.Packages.Strings()
 	sort.Strings(packages)
-	sort.Strings(s.sharedLibInstallStrings)
+	sort.Strings(s.FuzzPackager.SharedLibInstallStrings)
 	// TODO(mitchp): Migrate this to use MakeVarsContext::DistForGoal() when it's
 	// ready to handle phony targets created in Soong. In the meantime, this
 	// exports the phony 'fuzz' target and dependencies on packages to
 	// core/main.mk so that we can use dist-for-goals.
 	ctx.Strict("SOONG_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
 	ctx.Strict("FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS",
-		strings.Join(s.sharedLibInstallStrings, " "))
+		strings.Join(s.FuzzPackager.SharedLibInstallStrings, " "))
 
 	// Preallocate the slice of fuzz targets to minimise memory allocations.
-	fuzzTargets := make([]string, 0, len(s.fuzzTargets))
-	for target, _ := range s.fuzzTargets {
-		fuzzTargets = append(fuzzTargets, target)
+	s.PreallocateSlice(ctx, "ALL_FUZZ_TARGETS")
+}
+
+// GetSharedLibsToZip finds and marks all the transiently-dependent shared libraries for
+// packaging.
+func GetSharedLibsToZip(sharedLibraries android.Paths, module LinkableInterface, s *fuzz.FuzzPackager, archString string, sharedLibraryInstalled *map[string]bool) []fuzz.FileToZip {
+	var files []fuzz.FileToZip
+
+	for _, library := range sharedLibraries {
+		files = append(files, fuzz.FileToZip{library, "lib"})
+
+		// For each architecture-specific shared library dependency, we need to
+		// install it to the output directory. Setup the install destination here,
+		// which will be used by $(copy-many-files) in the Make backend.
+		installDestination := sharedLibraryInstallLocation(
+			library, module.Host(), archString)
+		if (*sharedLibraryInstalled)[installDestination] {
+			continue
+		}
+		(*sharedLibraryInstalled)[installDestination] = true
+
+		// Escape all the variables, as the install destination here will be called
+		// via. $(eval) in Make.
+		installDestination = strings.ReplaceAll(
+			installDestination, "$", "$$")
+		s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
+			library.String()+":"+installDestination)
+
+		// Ensure that on device, the library is also reinstalled to the /symbols/
+		// dir. Symbolized DSO's are always installed to the device when fuzzing, but
+		// we want symbolization tools (like `stack`) to be able to find the symbols
+		// in $ANDROID_PRODUCT_OUT/symbols automagically.
+		if !module.Host() {
+			symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, archString)
+			symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
+			s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
+				library.String()+":"+symbolsInstallDestination)
+		}
 	}
-	sort.Strings(fuzzTargets)
-	ctx.Strict("ALL_FUZZ_TARGETS", strings.Join(fuzzTargets, " "))
+	return files
 }
diff --git a/cc/gen.go b/cc/gen.go
index b152e02..8f62363 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -45,13 +45,6 @@
 			CommandDeps: []string{"$syspropCmd"},
 		},
 		"headerOutDir", "publicOutDir", "srcOutDir", "includeName")
-
-	windmc = pctx.AndroidStaticRule("windmc",
-		blueprint.RuleParams{
-			Command:     "$windmcCmd -r$$(dirname $out) -h$$(dirname $out) $in",
-			CommandDeps: []string{"$windmcCmd"},
-		},
-		"windmcCmd")
 )
 
 type YaccProperties struct {
@@ -200,26 +193,6 @@
 	return cppFile, headers.Paths()
 }
 
-func genWinMsg(ctx android.ModuleContext, srcFile android.Path, flags builderFlags) (android.Path, android.Path) {
-	headerFile := android.GenPathWithExt(ctx, "windmc", srcFile, "h")
-	rcFile := android.GenPathWithExt(ctx, "windmc", srcFile, "rc")
-
-	windmcCmd := gccCmd(flags.toolchain, "windmc")
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:           windmc,
-		Description:    "windmc " + srcFile.Rel(),
-		Output:         rcFile,
-		ImplicitOutput: headerFile,
-		Input:          srcFile,
-		Args: map[string]string{
-			"windmcCmd": windmcCmd,
-		},
-	})
-
-	return rcFile, headerFile
-}
-
 // Used to communicate information from the genSources method back to the library code that uses
 // it.
 type generatedSourceInfo struct {
@@ -305,10 +278,6 @@
 			cppFile := rsGeneratedCppFile(ctx, srcFile)
 			rsFiles = append(rsFiles, srcFiles[i])
 			srcFiles[i] = cppFile
-		case ".mc":
-			rcFile, headerFile := genWinMsg(ctx, srcFile, buildFlags)
-			srcFiles[i] = rcFile
-			deps = append(deps, headerFile)
 		case ".sysprop":
 			cppFile, headerFiles := genSysprop(ctx, srcFile)
 			srcFiles[i] = cppFile
diff --git a/cc/genrule.go b/cc/genrule.go
index 82d7205..239064f 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -15,12 +15,15 @@
 package cc
 
 import (
+	"fmt"
+
 	"android/soong/android"
 	"android/soong/genrule"
+	"android/soong/snapshot"
 )
 
 func init() {
-	android.RegisterModuleType("cc_genrule", genRuleFactory)
+	android.RegisterModuleType("cc_genrule", GenRuleFactory)
 }
 
 type GenruleExtraProperties struct {
@@ -35,22 +38,40 @@
 
 // cc_genrule is a genrule that can depend on other cc_* objects.
 // The cmd may be run multiple times, once for each of the different arch/etc
-// variations.
-func genRuleFactory() android.Module {
+// variations.  The following environment variables will be set when the command
+// execute:
+//
+//   CC_ARCH           the name of the architecture the command is being executed for
+//
+//   CC_MULTILIB       "lib32" if the architecture the command is being executed for is 32-bit,
+//                     "lib64" if it is 64-bit.
+//
+//   CC_NATIVE_BRIDGE  the name of the subdirectory that native bridge libraries are stored in if
+//                     the architecture has native bridge enabled, empty if it is disabled.
+func GenRuleFactory() android.Module {
 	module := genrule.NewGenRule()
 
 	extra := &GenruleExtraProperties{}
 	module.Extra = extra
 	module.ImageInterface = extra
+	module.CmdModifier = genruleCmdModifier
 	module.AddProperties(module.Extra)
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibBoth)
 
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
 
+func genruleCmdModifier(ctx android.ModuleContext, cmd string) string {
+	target := ctx.Target()
+	arch := target.Arch.ArchType
+	return fmt.Sprintf("CC_ARCH=%s CC_NATIVE_BRIDGE=%s CC_MULTILIB=%s && %s",
+		arch.Name, target.NativeBridgeRelativePath, arch.Multilib, cmd)
+}
+
 var _ android.ImageInterface = (*GenruleExtraProperties)(nil)
 
 func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
@@ -84,7 +105,7 @@
 	// is not needed.
 	recoverySnapshotVersion := ctx.DeviceConfig().RecoverySnapshotVersion()
 	if recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" &&
-		!isRecoveryProprietaryModule(ctx) {
+		!snapshot.IsRecoveryProprietaryModule(ctx) {
 		return false
 	} else {
 		return Bool(g.Recovery_available)
@@ -103,7 +124,7 @@
 		// If not, we assume modules under proprietary paths are compatible for
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, that is
 		// PLATFORM_VNDK_VERSION.
-		if vndkVersion == "current" || !isVendorProprietaryModule(ctx) {
+		if vndkVersion == "current" || !snapshot.IsVendorProprietaryModule(ctx) {
 			variants = append(variants, VendorVariationPrefix+ctx.DeviceConfig().PlatformVndkVersion())
 		} else {
 			variants = append(variants, VendorVariationPrefix+vndkVersion)
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index 45b343b..f25f704 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -23,7 +23,7 @@
 
 func testGenruleContext(config android.Config) *android.TestContext {
 	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
+	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
 	ctx.Register()
 
 	return ctx
@@ -115,3 +115,75 @@
 		t.Errorf(`want inputs %v, got %v`, expected, got)
 	}
 }
+
+func TestCmdPrefix(t *testing.T) {
+	bp := `
+		cc_genrule {
+			name: "gen",
+			cmd: "echo foo",
+			out: ["out"],
+			native_bridge_supported: true,
+		}
+		`
+
+	testCases := []struct {
+		name     string
+		variant  string
+		preparer android.FixturePreparer
+
+		arch         string
+		nativeBridge string
+		multilib     string
+	}{
+		{
+			name:     "arm",
+			variant:  "android_arm_armv7-a-neon",
+			arch:     "arm",
+			multilib: "lib32",
+		},
+		{
+			name:     "arm64",
+			variant:  "android_arm64_armv8-a",
+			arch:     "arm64",
+			multilib: "lib64",
+		},
+		{
+			name:    "nativebridge",
+			variant: "android_native_bridge_arm_armv7-a-neon",
+			preparer: android.FixtureModifyConfig(func(config android.Config) {
+				config.Targets[android.Android] = []android.Target{
+					{
+						Os:           android.Android,
+						Arch:         android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
+						NativeBridge: android.NativeBridgeDisabled,
+					},
+					{
+						Os:                       android.Android,
+						Arch:                     android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
+						NativeBridge:             android.NativeBridgeEnabled,
+						NativeBridgeHostArchName: "x86",
+						NativeBridgeRelativePath: "arm",
+					},
+				}
+			}),
+			arch:         "arm",
+			multilib:     "lib32",
+			nativeBridge: "arm",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				PrepareForIntegrationTestWithCc,
+				android.OptionalFixturePreparer(tt.preparer),
+			).RunTestWithBp(t, bp)
+			gen := result.ModuleForTests("gen", tt.variant)
+			sboxProto := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+			cmd := *sboxProto.Commands[0].Command
+			android.AssertStringDoesContain(t, "incorrect CC_ARCH", cmd, "CC_ARCH="+tt.arch+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_NATIVE_BRIDGE", cmd, "CC_NATIVE_BRIDGE="+tt.nativeBridge+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_MULTILIB", cmd, "CC_MULTILIB="+tt.multilib+" ")
+		})
+	}
+}
diff --git a/cc/image.go b/cc/image.go
index 47a424b..3a0857b 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -22,6 +22,7 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/snapshot"
 )
 
 var _ android.ImageInterface = (*Module)(nil)
@@ -341,11 +342,11 @@
 }
 
 func (m *Module) ExtraVariants() []string {
-	return m.Properties.ExtraVariants
+	return m.Properties.ExtraVersionedImageVariations
 }
 
 func (m *Module) AppendExtraVariant(extraVariant string) {
-	m.Properties.ExtraVariants = append(m.Properties.ExtraVariants, extraVariant)
+	m.Properties.ExtraVersionedImageVariations = append(m.Properties.ExtraVersionedImageVariations, extraVariant)
 }
 
 func (m *Module) SetRamdiskVariantNeeded(b bool) {
@@ -365,8 +366,8 @@
 }
 
 func (m *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
-	if snapshot, ok := m.linker.(snapshotInterface); ok {
-		return snapshot.version()
+	if snapshot, ok := m.linker.(SnapshotInterface); ok {
+		return snapshot.Version()
 	} else {
 		mctx.ModuleErrorf("version is unknown for snapshot prebuilt")
 		// Should we be panicking here instead?
@@ -496,7 +497,7 @@
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, or
 		// PLATFORM_VNDK_VERSION.
 		if m.HasVendorVariant() {
-			if isVendorProprietaryModule(mctx) {
+			if snapshot.IsVendorProprietaryModule(mctx) {
 				vendorVariants = append(vendorVariants, boardVndkVersion)
 			} else {
 				vendorVariants = append(vendorVariants, platformVndkVersion)
@@ -525,7 +526,7 @@
 				platformVndkVersion,
 				boardVndkVersion,
 			)
-		} else if isVendorProprietaryModule(mctx) {
+		} else if snapshot.IsVendorProprietaryModule(mctx) {
 			vendorVariants = append(vendorVariants, boardVndkVersion)
 		} else {
 			vendorVariants = append(vendorVariants, platformVndkVersion)
@@ -579,10 +580,10 @@
 
 	// If using a snapshot, the recovery variant under AOSP directories is not needed,
 	// except for kernel headers, which needs all variants.
-	if m.KernelHeadersDecorator() &&
+	if !m.KernelHeadersDecorator() &&
 		!m.IsSnapshotPrebuilt() &&
 		usingRecoverySnapshot &&
-		!isRecoveryProprietaryModule(mctx) {
+		!snapshot.IsRecoveryProprietaryModule(mctx) {
 		recoveryVariantNeeded = false
 	}
 
@@ -629,7 +630,7 @@
 }
 
 func (c *Module) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	return c.Properties.ExtraVariants
+	return c.Properties.ExtraVersionedImageVariations
 }
 
 func squashVendorSrcs(m *Module) {
diff --git a/cc/image_sdk_traits.go b/cc/image_sdk_traits.go
new file mode 100644
index 0000000..1d28230
--- /dev/null
+++ b/cc/image_sdk_traits.go
@@ -0,0 +1,40 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import "android/soong/android"
+
+// This file contains support for the image variant sdk traits.
+
+func init() {
+	android.RegisterSdkMemberTrait(ramdiskImageRequiredSdkTrait)
+	android.RegisterSdkMemberTrait(recoveryImageRequiredSdkTrait)
+}
+
+type imageSdkTraitStruct struct {
+	android.SdkMemberTraitBase
+}
+
+var ramdiskImageRequiredSdkTrait android.SdkMemberTrait = &imageSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "ramdisk_image_required",
+	},
+}
+
+var recoveryImageRequiredSdkTrait android.SdkMemberTrait = &imageSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "recovery_image_required",
+	},
+}
diff --git a/cc/installer.go b/cc/installer.go
index e551c63..2522610 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -25,6 +25,13 @@
 type InstallerProperties struct {
 	// install to a subdirectory of the default install path for the module
 	Relative_install_path *string `android:"arch_variant"`
+
+	// Install output directly in {partition}/, not in any subdir.  This is only intended for use by
+	// init_first_stage.
+	Install_in_root *bool `android:"arch_variant"`
+
+	// Install output directly in {partition}/xbin
+	Install_in_xbin *bool `android:"arch_vvariant"`
 }
 
 type installLocation int
@@ -66,6 +73,13 @@
 	if ctx.toolchain().Is64Bit() && installer.dir64 != "" {
 		dir = installer.dir64
 	}
+
+	if installer.installInRoot() {
+		dir = ""
+	} else if installer.installInXbin() {
+		dir = "xbin"
+	}
+
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
 		dir = filepath.Join(dir, ctx.Target().NativeBridgeRelativePath)
 	} else if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
@@ -110,3 +124,11 @@
 func (installer *baseInstaller) makeUninstallable(mod *Module) {
 	mod.ModuleBase.MakeUninstallable()
 }
+
+func (installer *baseInstaller) installInRoot() bool {
+	return Bool(installer.Properties.Install_in_root)
+}
+
+func (installer *baseInstaller) installInXbin() bool {
+	return Bool(installer.Properties.Install_in_xbin)
+}
diff --git a/cc/libbuildversion/Android.bp b/cc/libbuildversion/Android.bp
index 4debb1c..b105a30 100644
--- a/cc/libbuildversion/Android.bp
+++ b/cc/libbuildversion/Android.bp
@@ -14,8 +14,10 @@
             enabled: true,
         },
     },
+    min_sdk_version: "26",
     apex_available: [
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
+    vendor_available: true,
 }
diff --git a/cc/libbuildversion/libbuildversion.cpp b/cc/libbuildversion/libbuildversion.cpp
index 5242025..1e01c11 100644
--- a/cc/libbuildversion/libbuildversion.cpp
+++ b/cc/libbuildversion/libbuildversion.cpp
@@ -36,7 +36,11 @@
     return soong_build_number;
   }
 
+#ifdef __ANDROID_VENDOR__
+  const prop_info* pi = __system_property_find("ro.vendor.build.version.incremental");
+#else
   const prop_info* pi = __system_property_find("ro.build.version.incremental");
+#endif
   if (pi == nullptr) return "";
 
   std::string property_value;
diff --git a/cc/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 1ba3597..f9bef6c 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -23,15 +23,16 @@
 	"strings"
 	"sync"
 
-	"github.com/google/blueprint"
-	"github.com/google/blueprint/pathtools"
-
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 	"android/soong/cc/config"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/pathtools"
 )
 
-// LibraryProperties is a collection of properties shared by cc library rules.
+// LibraryProperties is a collection of properties shared by cc library rules/cc.
 type LibraryProperties struct {
 	// local file name to pass to the linker as -unexported_symbols_list
 	Unexported_symbols_list *string `android:"path,arch_variant"`
@@ -109,10 +110,10 @@
 		// Run checks on all APIs (in addition to the ones referred by
 		// one of exported ELF symbols.)
 		Check_all_apis *bool
-	}
 
-	// Order symbols in .bss section by their sizes.  Only useful for shared libraries.
-	Sort_bss_symbols_by_size *bool
+		// Extra flags passed to header-abi-diff
+		Diff_flags []string
+	}
 
 	// Inject boringssl hash into the shared library.  This is only intended for use by external/boringssl.
 	Inject_bssl_hash *bool `android:"arch_variant"`
@@ -145,6 +146,10 @@
 type StaticOrSharedProperties struct {
 	Srcs []string `android:"path,arch_variant"`
 
+	Tidy_disabled_srcs []string `android:"path,arch_variant"`
+
+	Tidy_timeout_srcs []string `android:"path,arch_variant"`
+
 	Sanitized Sanitized `android:"arch_variant"`
 
 	Cflags []string `android:"arch_variant"`
@@ -159,6 +164,8 @@
 	Export_static_lib_headers []string `android:"arch_variant"`
 
 	Apex_available []string `android:"arch_variant"`
+
+	Installable *bool `android:"arch_variant"`
 }
 
 type LibraryMutatedProperties struct {
@@ -186,11 +193,11 @@
 	// be added to the include path (using -I) for this module and any module that links
 	// against this module.  Directories listed in export_include_dirs do not need to be
 	// listed in local_include_dirs.
-	Export_include_dirs []string `android:"arch_variant"`
+	Export_include_dirs []string `android:"arch_variant,variant_prepend"`
 
 	// list of directories that will be added to the system include path
 	// using -isystem for this module and any module that links against this module.
-	Export_system_include_dirs []string `android:"arch_variant"`
+	Export_system_include_dirs []string `android:"arch_variant,variant_prepend"`
 
 	Target struct {
 		Vendor, Product struct {
@@ -205,9 +212,6 @@
 
 func init() {
 	RegisterLibraryBuildComponents(android.InitRegistrationContext)
-
-	android.RegisterBp2BuildMutator("cc_library_static", CcLibraryStaticBp2Build)
-	android.RegisterBp2BuildMutator("cc_library", CcLibraryBp2Build)
 }
 
 func RegisterLibraryBuildComponents(ctx android.RegistrationContext) {
@@ -218,105 +222,209 @@
 	ctx.RegisterModuleType("cc_library_host_shared", LibraryHostSharedFactory)
 }
 
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
 // For bp2build conversion.
 type bazelCcLibraryAttributes struct {
 	// Attributes pertaining to both static and shared variants.
-	Srcs               bazel.LabelListAttribute
-	Hdrs               bazel.LabelListAttribute
-	Deps               bazel.LabelListAttribute
-	Dynamic_deps       bazel.LabelListAttribute
-	Whole_archive_deps bazel.LabelListAttribute
-	Copts              bazel.StringListAttribute
-	Includes           bazel.StringListAttribute
-	Linkopts           bazel.StringListAttribute
-	// Attributes pertaining to shared variant.
-	Shared_copts                  bazel.StringListAttribute
-	Shared_srcs                   bazel.LabelListAttribute
-	Static_deps_for_shared        bazel.LabelListAttribute
-	Dynamic_deps_for_shared       bazel.LabelListAttribute
-	Whole_archive_deps_for_shared bazel.LabelListAttribute
-	User_link_flags               bazel.StringListAttribute
-	Version_script                bazel.LabelAttribute
-	// Attributes pertaining to static variant.
-	Static_copts                  bazel.StringListAttribute
-	Static_srcs                   bazel.LabelListAttribute
-	Static_deps_for_static        bazel.LabelListAttribute
-	Dynamic_deps_for_static       bazel.LabelListAttribute
-	Whole_archive_deps_for_static bazel.LabelListAttribute
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+
+	Copts      bazel.StringListAttribute
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Hdrs bazel.LabelListAttribute
+
+	Deps                              bazel.LabelListAttribute
+	Implementation_deps               bazel.LabelListAttribute
+	Dynamic_deps                      bazel.LabelListAttribute
+	Implementation_dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps                bazel.LabelListAttribute
+	Implementation_whole_archive_deps bazel.LabelListAttribute
+	System_dynamic_deps               bazel.LabelListAttribute
+
+	Export_includes        bazel.StringListAttribute
+	Export_system_includes bazel.StringListAttribute
+	Local_includes         bazel.StringListAttribute
+	Absolute_includes      bazel.StringListAttribute
+	Linkopts               bazel.StringListAttribute
+	Use_libcrt             bazel.BoolAttribute
+	Rtti                   bazel.BoolAttribute
+
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
+
+	// This is shared only.
+	Link_crt                 bazel.BoolAttribute
+	Additional_linker_inputs bazel.LabelListAttribute
+
+	// Common properties shared between both shared and static variants.
+	Shared staticOrSharedAttributes
+	Static staticOrSharedAttributes
+
+	Strip stripAttributes
+
+	Features bazel.StringListAttribute
 }
 
-type bazelCcLibrary struct {
-	android.BazelTargetModuleBase
-	bazelCcLibraryAttributes
+type stripAttributes struct {
+	Keep_symbols                 bazel.BoolAttribute
+	Keep_symbols_and_debug_frame bazel.BoolAttribute
+	Keep_symbols_list            bazel.StringListAttribute
+	All                          bazel.BoolAttribute
+	None                         bazel.BoolAttribute
 }
 
-func (m *bazelCcLibrary) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelCcLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-func BazelCcLibraryFactory() android.Module {
-	module := &bazelCcLibrary{}
-	module.AddProperties(&module.bazelCcLibraryAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func CcLibraryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "cc_library" {
-		return
-	}
-
+func libraryBp2Build(ctx android.TopDownMutatorContext, m *Module) {
 	// For some cc_library modules, their static variants are ready to be
 	// converted, but not their shared variants. For these modules, delegate to
 	// the cc_library_static bp2build converter temporarily instead.
-	if android.GenerateCcLibraryStaticOnly(ctx) {
-		ccLibraryStaticBp2BuildInternal(ctx, m)
+	if android.GenerateCcLibraryStaticOnly(ctx.Module().Name()) {
+		sharedOrStaticLibraryBp2Build(ctx, m, true)
 		return
 	}
 
 	sharedAttrs := bp2BuildParseSharedProps(ctx, m)
 	staticAttrs := bp2BuildParseStaticProps(ctx, m)
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, m)
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, m)
+	baseAttributes := bp2BuildParseBaseProps(ctx, m)
+	compilerAttrs := baseAttributes.compilerAttributes
+	linkerAttrs := baseAttributes.linkerAttributes
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, m, compilerAttrs.includes)
 
-	var srcs bazel.LabelListAttribute
-	srcs.Append(compilerAttrs.srcs)
+	srcs := compilerAttrs.srcs
 
-	attrs := &bazelCcLibraryAttributes{
-		Srcs:                          srcs,
-		Deps:                          linkerAttrs.deps,
-		Dynamic_deps:                  linkerAttrs.dynamicDeps,
-		Whole_archive_deps:            linkerAttrs.wholeArchiveDeps,
-		Copts:                         compilerAttrs.copts,
-		Includes:                      exportedIncludes,
-		Linkopts:                      linkerAttrs.linkopts,
-		Shared_copts:                  sharedAttrs.copts,
-		Shared_srcs:                   sharedAttrs.srcs,
-		Static_deps_for_shared:        sharedAttrs.staticDeps,
-		Whole_archive_deps_for_shared: sharedAttrs.wholeArchiveDeps,
-		Dynamic_deps_for_shared:       sharedAttrs.dynamicDeps,
-		Version_script:                linkerAttrs.versionScript,
-		Static_copts:                  staticAttrs.copts,
-		Static_srcs:                   staticAttrs.srcs,
-		Static_deps_for_static:        staticAttrs.staticDeps,
-		Whole_archive_deps_for_static: staticAttrs.wholeArchiveDeps,
-		Dynamic_deps_for_static:       staticAttrs.dynamicDeps,
+	sharedAttrs.Dynamic_deps.Add(baseAttributes.protoDependency)
+	staticAttrs.Deps.Add(baseAttributes.protoDependency)
+
+	asFlags := compilerAttrs.asFlags
+	if compilerAttrs.asSrcs.IsEmpty() && sharedAttrs.Srcs_as.IsEmpty() && staticAttrs.Srcs_as.IsEmpty() {
+		// Skip asflags for BUILD file simplicity if there are no assembly sources.
+		asFlags = bazel.MakeStringListAttribute(nil)
 	}
 
-	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "cc_library",
-		Bzl_load_location: "//build/bazel/rules:full_cc_library.bzl",
+	staticCommonAttrs := staticOrSharedAttributes{
+		Srcs:    *srcs.Clone().Append(staticAttrs.Srcs),
+		Srcs_c:  *compilerAttrs.cSrcs.Clone().Append(staticAttrs.Srcs_c),
+		Srcs_as: *compilerAttrs.asSrcs.Clone().Append(staticAttrs.Srcs_as),
+		Copts:   *compilerAttrs.copts.Clone().Append(staticAttrs.Copts),
+		Hdrs:    *compilerAttrs.hdrs.Clone().Append(staticAttrs.Hdrs),
+
+		Deps:                              *linkerAttrs.deps.Clone().Append(staticAttrs.Deps),
+		Implementation_deps:               *linkerAttrs.implementationDeps.Clone().Append(staticAttrs.Implementation_deps),
+		Dynamic_deps:                      *linkerAttrs.dynamicDeps.Clone().Append(staticAttrs.Dynamic_deps),
+		Implementation_dynamic_deps:       *linkerAttrs.implementationDynamicDeps.Clone().Append(staticAttrs.Implementation_dynamic_deps),
+		Implementation_whole_archive_deps: linkerAttrs.implementationWholeArchiveDeps,
+		Whole_archive_deps:                *linkerAttrs.wholeArchiveDeps.Clone().Append(staticAttrs.Whole_archive_deps),
+		System_dynamic_deps:               *linkerAttrs.systemDynamicDeps.Clone().Append(staticAttrs.System_dynamic_deps),
+		sdkAttributes:                     bp2BuildParseSdkAttributes(m),
 	}
 
-	ctx.CreateBazelTargetModule(BazelCcLibraryFactory, m.Name(), props, attrs)
+	sharedCommonAttrs := staticOrSharedAttributes{
+		Srcs:    *srcs.Clone().Append(sharedAttrs.Srcs),
+		Srcs_c:  *compilerAttrs.cSrcs.Clone().Append(sharedAttrs.Srcs_c),
+		Srcs_as: *compilerAttrs.asSrcs.Clone().Append(sharedAttrs.Srcs_as),
+		Copts:   *compilerAttrs.copts.Clone().Append(sharedAttrs.Copts),
+		Hdrs:    *compilerAttrs.hdrs.Clone().Append(sharedAttrs.Hdrs),
+
+		Deps:                        *linkerAttrs.deps.Clone().Append(sharedAttrs.Deps),
+		Implementation_deps:         *linkerAttrs.implementationDeps.Clone().Append(sharedAttrs.Implementation_deps),
+		Dynamic_deps:                *linkerAttrs.dynamicDeps.Clone().Append(sharedAttrs.Dynamic_deps),
+		Implementation_dynamic_deps: *linkerAttrs.implementationDynamicDeps.Clone().Append(sharedAttrs.Implementation_dynamic_deps),
+		Whole_archive_deps:          *linkerAttrs.wholeArchiveDeps.Clone().Append(sharedAttrs.Whole_archive_deps),
+		System_dynamic_deps:         *linkerAttrs.systemDynamicDeps.Clone().Append(sharedAttrs.System_dynamic_deps),
+		sdkAttributes:               bp2BuildParseSdkAttributes(m),
+	}
+
+	staticTargetAttrs := &bazelCcLibraryStaticAttributes{
+		staticOrSharedAttributes: staticCommonAttrs,
+
+		Cppflags:   compilerAttrs.cppFlags,
+		Conlyflags: compilerAttrs.conlyFlags,
+		Asflags:    asFlags,
+
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Local_includes:           compilerAttrs.localIncludes,
+		Absolute_includes:        compilerAttrs.absoluteIncludes,
+		Use_libcrt:               linkerAttrs.useLibcrt,
+		Rtti:                     compilerAttrs.rtti,
+		Stl:                      compilerAttrs.stl,
+		Cpp_std:                  compilerAttrs.cppStd,
+		C_std:                    compilerAttrs.cStd,
+		Use_version_lib:          linkerAttrs.useVersionLib,
+
+		Features: linkerAttrs.features,
+	}
+
+	sharedTargetAttrs := &bazelCcLibrarySharedAttributes{
+		staticOrSharedAttributes: sharedCommonAttrs,
+		Cppflags:                 compilerAttrs.cppFlags,
+		Conlyflags:               compilerAttrs.conlyFlags,
+		Asflags:                  asFlags,
+
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Local_includes:           compilerAttrs.localIncludes,
+		Absolute_includes:        compilerAttrs.absoluteIncludes,
+		Linkopts:                 linkerAttrs.linkopts,
+		Link_crt:                 linkerAttrs.linkCrt,
+		Use_libcrt:               linkerAttrs.useLibcrt,
+		Rtti:                     compilerAttrs.rtti,
+		Stl:                      compilerAttrs.stl,
+		Cpp_std:                  compilerAttrs.cppStd,
+		C_std:                    compilerAttrs.cStd,
+		Use_version_lib:          linkerAttrs.useVersionLib,
+
+		Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
+
+		Strip: stripAttributes{
+			Keep_symbols:                 linkerAttrs.stripKeepSymbols,
+			Keep_symbols_and_debug_frame: linkerAttrs.stripKeepSymbolsAndDebugFrame,
+			Keep_symbols_list:            linkerAttrs.stripKeepSymbolsList,
+			All:                          linkerAttrs.stripAll,
+			None:                         linkerAttrs.stripNone,
+		},
+		Features: linkerAttrs.features,
+
+		Stubs_symbol_file: compilerAttrs.stubsSymbolFile,
+		Stubs_versions:    compilerAttrs.stubsVersions,
+	}
+
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &LibraryProperties{}) {
+		for config, props := range configToProps {
+			if props, ok := props.(*LibraryProperties); ok {
+				if props.Inject_bssl_hash != nil {
+					// This is an edge case applies only to libcrypto
+					if m.Name() == "libcrypto" {
+						sharedTargetAttrs.Inject_bssl_hash.SetSelectValue(axis, config, props.Inject_bssl_hash)
+					} else {
+						ctx.PropertyErrorf("inject_bssl_hash", "only applies to libcrypto")
+					}
+				}
+			}
+		}
+	}
+
+	staticProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library_static",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_library_static.bzl",
+	}
+	sharedProps := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library_shared",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_library_shared.bzl",
+	}
+
+	ctx.CreateBazelTargetModuleWithRestrictions(staticProps,
+		android.CommonAttributes{Name: m.Name() + "_bp2build_cc_library_static"},
+		staticTargetAttrs, staticAttrs.Enabled)
+	ctx.CreateBazelTargetModuleWithRestrictions(sharedProps,
+		android.CommonAttributes{Name: m.Name()},
+		sharedTargetAttrs, sharedAttrs.Enabled)
 }
 
 // cc_library creates both static and/or shared libraries for a device and/or
@@ -331,6 +439,8 @@
 		staticLibrarySdkMemberType,
 		staticAndSharedLibrarySdkMemberType,
 	}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -339,7 +449,8 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyStatic()
 	module.sdkMemberTypes = []android.SdkMemberType{staticLibrarySdkMemberType}
-	module.bazelHandler = &staticLibraryBazelHandler{module: module}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -348,6 +459,8 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyShared()
 	module.sdkMemberTypes = []android.SdkMemberType{sharedLibrarySdkMemberType}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -357,6 +470,8 @@
 	module, library := NewLibrary(android.HostSupported)
 	library.BuildOnlyStatic()
 	module.sdkMemberTypes = []android.SdkMemberType{staticLibrarySdkMemberType}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -365,6 +480,8 @@
 	module, library := NewLibrary(android.HostSupported)
 	library.BuildOnlyShared()
 	module.sdkMemberTypes = []android.SdkMemberType{sharedLibrarySdkMemberType}
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -478,7 +595,8 @@
 	stripper         Stripper
 
 	// For whole_static_libs
-	objects Objects
+	objects                      Objects
+	wholeStaticLibsFromPrebuilts android.Paths
 
 	// Uses the module's name if empty, but can be overridden. Does not include
 	// shlib suffix.
@@ -522,38 +640,28 @@
 	*baseInstaller
 
 	collectedSnapshotHeaders android.Paths
+
+	apiListCoverageXmlPath android.ModuleOutPath
 }
 
-type staticLibraryBazelHandler struct {
-	bazelHandler
+type ccLibraryBazelHandler struct {
+	android.BazelHandler
 
 	module *Module
 }
 
-func (handler *staticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
-	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
-	if err != nil {
-		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
+// generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
+// provider from a Bazel shared library's CcInfo provider.
+func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+	rootStaticArchives := ccInfo.RootStaticArchives
+	if len(rootStaticArchives) != 1 {
+		ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
 		return false
 	}
-	if !ok {
-		return ok
-	}
-	outputPaths := ccInfo.OutputFiles
-	objPaths := ccInfo.CcObjectFiles
-	if len(outputPaths) > 1 {
-		// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
-		// We should support this.
-		ctx.ModuleErrorf("expected at most one output file for '%s', but got %s", label, objPaths)
-		return false
-	} else if len(outputPaths) == 0 {
-		handler.module.outputFile = android.OptionalPath{}
-		return true
-	}
-	outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
+	outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 
+	objPaths := ccInfo.CcObjectFiles
 	objFiles := make(android.Paths, len(objPaths))
 	for i, objPath := range objPaths {
 		objFiles[i] = android.PathForBazelOut(ctx, objPath)
@@ -567,26 +675,177 @@
 		ReuseObjects:  objects,
 		Objects:       objects,
 
-		// TODO(cparsons): Include transitive static libraries in this provider to support
+		// TODO(b/190524881): Include transitive static libraries in this provider to support
 		// static libraries with deps.
 		TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
 			Direct(outputFilePath).
 			Build(),
 	})
 
-	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfoFromCcInfo(ctx, ccInfo))
+	return true
+}
+
+// generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
+// provider from a Bazel shared library's CcInfo provider.
+func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+	rootDynamicLibraries := ccInfo.RootDynamicLibraries
+
+	if len(rootDynamicLibraries) != 1 {
+		ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
+		return false
+	}
+	outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+
+	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = outputFilePath
+
+	var tocFile android.OptionalPath
+	if len(ccInfo.TocFile) > 0 {
+		tocFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, ccInfo.TocFile))
+	}
+	handler.module.linker.(*libraryDecorator).tocFile = tocFile
+
+	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
+		TableOfContents: tocFile,
+		SharedLibrary:   outputFilePath,
+		Target:          ctx.Target(),
+		// TODO(b/190524881): Include transitive static libraries in this provider to support
+		// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
+	})
+	return true
+}
+
+func (handler *ccLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
+		return false
+	}
+	if !ok {
+		return ok
+	}
+
+	if handler.module.static() {
+		if ok := handler.generateStaticBazelBuildActions(ctx, label, ccInfo); !ok {
+			return false
+		}
+	} else if handler.module.Shared() {
+		if ok := handler.generateSharedBazelBuildActions(ctx, label, ccInfo); !ok {
+			return false
+		}
+	} else {
+		return false
+	}
+
+	handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
+	handler.module.maybeUnhideFromMake()
+
 	if i, ok := handler.module.linker.(snapshotLibraryInterface); ok {
 		// Dependencies on this library will expect collectedSnapshotHeaders to
 		// be set, otherwise validation will fail. For now, set this to an empty
 		// list.
-		// TODO(cparsons): More closely mirror the collectHeadersForSnapshot
+		// TODO(b/190533363): More closely mirror the collectHeadersForSnapshot
 		// implementation.
 		i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
 	}
-
 	return ok
 }
 
+func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
+	flagExporterInfo := flagExporterInfoFromCcInfo(ctx, ccInfo)
+	// flag exporters consolidates properties like includes, flags, dependencies that should be
+	// exported from this module to other modules
+	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfo)
+	// Store flag info to be passed along to androidmk
+	// TODO(b/184387147): Androidmk should be done in Bazel, not Soong.
+	library.flagExporterInfo = &flagExporterInfo
+}
+
+func GlobHeadersForSnapshot(ctx android.ModuleContext, paths android.Paths) android.Paths {
+	ret := android.Paths{}
+
+	// Headers in the source tree should be globbed. On the contrast, generated headers
+	// can't be globbed, and they should be manually collected.
+	// So, we first filter out intermediate directories (which contains generated headers)
+	// from exported directories, and then glob headers under remaining directories.
+	for _, path := range paths {
+		dir := path.String()
+		// Skip if dir is for generated headers
+		if strings.HasPrefix(dir, android.PathForOutput(ctx).String()) {
+			continue
+		}
+		// libeigen wrongly exports the root directory "external/eigen". But only two
+		// subdirectories "Eigen" and "unsupported" contain exported header files. Even worse
+		// some of them have no extension. So we need special treatment for libeigen in order
+		// to glob correctly.
+		if dir == "external/eigen" {
+			// Only these two directories contains exported headers.
+			for _, subdir := range []string{"Eigen", "unsupported/Eigen"} {
+				globDir := "external/eigen/" + subdir + "/**/*"
+				glob, err := ctx.GlobWithDeps(globDir, nil)
+				if err != nil {
+					ctx.ModuleErrorf("glob of %q failed: %s", globDir, err)
+					return nil
+				}
+				for _, header := range glob {
+					if strings.HasSuffix(header, "/") {
+						continue
+					}
+					ext := filepath.Ext(header)
+					if ext != "" && ext != ".h" {
+						continue
+					}
+					ret = append(ret, android.PathForSource(ctx, header))
+				}
+			}
+			continue
+		}
+		globDir := dir + "/**/*"
+		glob, err := ctx.GlobWithDeps(globDir, nil)
+		if err != nil {
+			ctx.ModuleErrorf("glob of %q failed: %s", globDir, err)
+			return nil
+		}
+		isLibcxx := strings.HasPrefix(dir, "external/libcxx/include")
+		for _, header := range glob {
+			if isLibcxx {
+				// Glob all files under this special directory, because of C++ headers with no
+				// extension.
+				if strings.HasSuffix(header, "/") {
+					continue
+				}
+			} else {
+				// Filter out only the files with extensions that are headers.
+				found := false
+				for _, ext := range HeaderExts {
+					if strings.HasSuffix(header, ext) {
+						found = true
+						break
+					}
+				}
+				if !found {
+					continue
+				}
+			}
+			ret = append(ret, android.PathForSource(ctx, header))
+		}
+	}
+	return ret
+}
+
+func GlobGeneratedHeadersForSnapshot(ctx android.ModuleContext, paths android.Paths) android.Paths {
+	ret := android.Paths{}
+	for _, header := range paths {
+		// TODO(b/148123511): remove exportedDeps after cleaning up genrule
+		if strings.HasSuffix(header.Base(), "-phony") {
+			continue
+		}
+		ret = append(ret, header)
+	}
+	return ret
+}
+
 // collectHeadersForSnapshot collects all exported headers from library.
 // It globs header files in the source tree for exported include directories,
 // and tracks generated header files separately.
@@ -600,75 +859,10 @@
 	// can't be globbed, and they should be manually collected.
 	// So, we first filter out intermediate directories (which contains generated headers)
 	// from exported directories, and then glob headers under remaining directories.
-	for _, path := range append(android.CopyOfPaths(l.flagExporter.dirs), l.flagExporter.systemDirs...) {
-		dir := path.String()
-		// Skip if dir is for generated headers
-		if strings.HasPrefix(dir, android.PathForOutput(ctx).String()) {
-			continue
-		}
-		// libeigen wrongly exports the root directory "external/eigen". But only two
-		// subdirectories "Eigen" and "unsupported" contain exported header files. Even worse
-		// some of them have no extension. So we need special treatment for libeigen in order
-		// to glob correctly.
-		if dir == "external/eigen" {
-			// Only these two directories contains exported headers.
-			for _, subdir := range []string{"Eigen", "unsupported/Eigen"} {
-				glob, err := ctx.GlobWithDeps("external/eigen/"+subdir+"/**/*", nil)
-				if err != nil {
-					ctx.ModuleErrorf("glob failed: %#v", err)
-					return
-				}
-				for _, header := range glob {
-					if strings.HasSuffix(header, "/") {
-						continue
-					}
-					ext := filepath.Ext(header)
-					if ext != "" && ext != ".h" {
-						continue
-					}
-					ret = append(ret, android.PathForSource(ctx, header))
-				}
-			}
-			continue
-		}
-		glob, err := ctx.GlobWithDeps(dir+"/**/*", nil)
-		if err != nil {
-			ctx.ModuleErrorf("glob failed: %#v", err)
-			return
-		}
-		isLibcxx := strings.HasPrefix(dir, "external/libcxx/include")
-		for _, header := range glob {
-			if isLibcxx {
-				// Glob all files under this special directory, because of C++ headers with no
-				// extension.
-				if strings.HasSuffix(header, "/") {
-					continue
-				}
-			} else {
-				// Filter out only the files with extensions that are headers.
-				found := false
-				for _, ext := range headerExts {
-					if strings.HasSuffix(header, ext) {
-						found = true
-						break
-					}
-				}
-				if !found {
-					continue
-				}
-			}
-			ret = append(ret, android.PathForSource(ctx, header))
-		}
-	}
+	ret = append(ret, GlobHeadersForSnapshot(ctx, append(android.CopyOfPaths(l.flagExporter.dirs), l.flagExporter.systemDirs...))...)
 
 	// Collect generated headers
-	for _, header := range append(android.CopyOfPaths(l.flagExporter.headers), l.flagExporter.deps...) {
-		// TODO(b/148123511): remove exportedDeps after cleaning up genrule
-		if strings.HasSuffix(header.Base(), "-phony") {
-			continue
-		}
-		ret = append(ret, header)
-	}
+	ret = append(ret, GlobGeneratedHeadersForSnapshot(ctx, append(android.CopyOfPaths(l.flagExporter.headers), l.flagExporter.deps...))...)
 
 	l.collectedSnapshotHeaders = ret
 }
@@ -817,16 +1011,23 @@
 		if library.stubsVersion() != "" {
 			vndkVer = library.stubsVersion()
 		}
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Llndk.Symbol_file), vndkVer, "--llndk")
+		nativeAbiResult := parseNativeAbiDefinition(ctx,
+			String(library.Properties.Llndk.Symbol_file),
+			android.ApiLevelOrPanic(ctx, vndkVer), "--llndk")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Llndk.Unversioned) {
-			library.versionScriptPath = android.OptionalPathForPath(versionScript)
+			library.versionScriptPath = android.OptionalPathForPath(
+				nativeAbiResult.versionScript)
 		}
 		return objs
 	}
 	if ctx.IsVendorPublicLibrary() {
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Vendor_public_library.Symbol_file), "current", "")
+		nativeAbiResult := parseNativeAbiDefinition(ctx,
+			String(library.Properties.Vendor_public_library.Symbol_file),
+			android.FutureApiLevel, "")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Vendor_public_library.Unversioned) {
-			library.versionScriptPath = android.OptionalPathForPath(versionScript)
+			library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.versionScript)
 		}
 		return objs
 	}
@@ -836,8 +1037,18 @@
 			ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile)
 			return Objects{}
 		}
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Stubs.Symbol_file), library.MutatedProperties.StubsVersion, "--apex")
-		library.versionScriptPath = android.OptionalPathForPath(versionScript)
+		nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
+			android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion),
+			"--apex")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+		library.versionScriptPath = android.OptionalPathForPath(
+			nativeAbiResult.versionScript)
+
+		// Parse symbol file to get API list for coverage
+		if library.stubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
+			library.apiListCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
+		}
+
 		return objs
 	}
 
@@ -875,12 +1086,16 @@
 
 	if library.static() {
 		srcs := android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Srcs)
-		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary,
-			srcs, library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary, srcs,
+			android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Tidy_disabled_srcs),
+			android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Tidy_timeout_srcs),
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
 	} else if library.shared() {
 		srcs := android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Srcs)
-		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary,
-			srcs, library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary, srcs,
+			android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Tidy_disabled_srcs),
+			android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Tidy_timeout_srcs),
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
 	}
 
 	return objs
@@ -911,6 +1126,10 @@
 	androidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer)
 
 	availableFor(string) bool
+
+	getAPIListCoverageXMLPath() android.ModuleOutPath
+
+	installable() *bool
 }
 
 type versionedInterface interface {
@@ -1053,9 +1272,9 @@
 		deps.ReexportSharedLibHeaders = append(deps.ReexportSharedLibHeaders, library.StaticProperties.Static.Export_shared_lib_headers...)
 		deps.ReexportStaticLibHeaders = append(deps.ReexportStaticLibHeaders, library.StaticProperties.Static.Export_static_lib_headers...)
 	} else if library.shared() {
-		if ctx.toolchain().Bionic() && !Bool(library.baseLinker.Properties.Nocrt) {
-			deps.CrtBegin = "crtbegin_so"
-			deps.CrtEnd = "crtend_so"
+		if !Bool(library.baseLinker.Properties.Nocrt) {
+			deps.CrtBegin = append(deps.CrtBegin, ctx.toolchain().CrtBeginSharedLibrary()...)
+			deps.CrtEnd = append(deps.CrtEnd, ctx.toolchain().CrtEndSharedLibrary()...)
 		}
 		deps.WholeStaticLibs = append(deps.WholeStaticLibs, library.SharedProperties.Shared.Whole_static_libs...)
 		deps.StaticLibs = append(deps.StaticLibs, library.SharedProperties.Shared.Static_libs...)
@@ -1136,6 +1355,7 @@
 
 	library.objects = deps.WholeStaticLibObjs.Copy()
 	library.objects = library.objects.Append(objs)
+	library.wholeStaticLibsFromPrebuilts = android.CopyOfPaths(deps.WholeStaticLibsFromPrebuilts)
 
 	fileName := ctx.ModuleName() + staticLibraryExtension
 	outputFile := android.PathForModuleOut(ctx, fileName)
@@ -1153,7 +1373,7 @@
 		}
 	}
 
-	transformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, objs.tidyFiles)
+	transformObjToStaticLib(ctx, library.objects.objFiles, deps.WholeStaticLibsFromPrebuilts, builderFlags, outputFile, nil, objs.tidyDepFiles)
 
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, library.objects, ctx.ModuleName())
 
@@ -1161,9 +1381,10 @@
 
 	if library.static() {
 		ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
-			StaticLibrary: outputFile,
-			ReuseObjects:  library.reuseObjects,
-			Objects:       library.objects,
+			StaticLibrary:                outputFile,
+			ReuseObjects:                 library.reuseObjects,
+			Objects:                      library.objects,
+			WholeStaticLibsFromPrebuilts: library.wholeStaticLibsFromPrebuilts,
 
 			TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
 				Direct(outputFile).
@@ -1179,11 +1400,21 @@
 	return outputFile
 }
 
+func ndkSharedLibDeps(ctx ModuleContext) android.Paths {
+	if ctx.Module().(*Module).IsSdkVariant() {
+		// The NDK sysroot timestamp file depends on all the NDK
+		// sysroot header and shared library files.
+		return android.Paths{getNdkBaseTimestampFile(ctx)}
+	}
+	return nil
+}
+
 func (library *libraryDecorator) linkShared(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
 	var linkerDeps android.Paths
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
+	linkerDeps = append(linkerDeps, ndkSharedLibDeps(ctx)...)
 
 	unexportedSymbols := ctx.ExpandOptionalSource(library.Properties.Unexported_symbols_list, "unexported_symbols_list")
 	forceNotWeakSymbols := ctx.ExpandOptionalSource(library.Properties.Force_symbols_not_weak_list, "force_symbols_not_weak_list")
@@ -1232,11 +1463,17 @@
 
 	builderFlags := flagsToBuilderFlags(flags)
 
+	if ctx.Darwin() && deps.DarwinSecondArchOutput.Valid() {
+		fatOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "pre-fat", fileName)
+		transformDarwinUniversalBinary(ctx, fatOutputFile, outputFile, deps.DarwinSecondArchOutput.Path())
+	}
+
 	// Optimize out relinking against shared libraries whose interface hasn't changed by
 	// depending on a table of contents file instead of the library itself.
 	tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.ShlibSuffix()[1:]+".toc")
 	library.tocFile = android.OptionalPathForPath(tocFile)
-	transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+	TransformSharedObjectToToc(ctx, outputFile, tocFile)
 
 	stripFlags := flagsToStripFlags(flags)
 	needsStrip := library.stripper.NeedsStrip(ctx)
@@ -1282,23 +1519,9 @@
 	linkerDeps = append(linkerDeps, deps.EarlySharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
-	linkerDeps = append(linkerDeps, objs.tidyFiles...)
-
-	if Bool(library.Properties.Sort_bss_symbols_by_size) && !library.buildStubs() {
-		unsortedOutputFile := android.PathForModuleOut(ctx, "unsorted", fileName)
-		transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
-			deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-			linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, unsortedOutputFile, implicitOutputs)
-
-		symbolOrderingFile := android.PathForModuleOut(ctx, "unsorted", fileName+".symbol_order")
-		symbolOrderingFlag := library.baseLinker.sortBssSymbolsBySize(ctx, unsortedOutputFile, symbolOrderingFile, builderFlags)
-		builderFlags.localLdFlags += " " + symbolOrderingFlag
-		linkerDeps = append(linkerDeps, symbolOrderingFile)
-	}
-
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
 		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs)
+		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, objs.tidyDepFiles)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -1309,19 +1532,17 @@
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
 	library.linkSAbiDumpFiles(ctx, objs, fileName, unstrippedOutputFile)
 
-	var staticAnalogue *StaticLibraryInfo
+	var transitiveStaticLibrariesForOrdering *android.DepSet
 	if static := ctx.GetDirectDepsWithTag(staticVariantTag); len(static) > 0 {
 		s := ctx.OtherModuleProvider(static[0], StaticLibraryInfoProvider).(StaticLibraryInfo)
-		staticAnalogue = &s
+		transitiveStaticLibrariesForOrdering = s.TransitiveStaticLibrariesForOrdering
 	}
 
 	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-		TableOfContents:         android.OptionalPathForPath(tocFile),
-		SharedLibrary:           unstrippedOutputFile,
-		UnstrippedSharedLibrary: library.unstrippedOutputFile,
-		CoverageSharedLibrary:   library.coverageOutputFile,
-		StaticAnalogue:          staticAnalogue,
-		Target:                  ctx.Target(),
+		TableOfContents:                      android.OptionalPathForPath(tocFile),
+		SharedLibrary:                        unstrippedOutputFile,
+		TransitiveStaticLibrariesForOrdering: transitiveStaticLibrariesForOrdering,
+		Target:                               ctx.Target(),
 	})
 
 	stubs := ctx.GetDirectDepsWithTag(stubImplDepTag)
@@ -1420,6 +1641,7 @@
 		if refAbiDumpFile != nil {
 			library.sAbiDiff = sourceAbiDiff(ctx, library.sAbiOutputFile.Path(),
 				refAbiDumpFile, fileName, exportedHeaderFlags,
+				library.Properties.Header_abi_checker.Diff_flags,
 				Bool(library.Properties.Header_abi_checker.Check_all_apis),
 				ctx.IsLlndk(), ctx.isNdk(ctx.Config()), ctx.IsVndkExt())
 		}
@@ -1639,7 +1861,7 @@
 					mayUseCoreVariant = false
 				}
 
-				if ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) && ctx.Arch().ArchType == android.Arm64 {
+				if ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) {
 					mayUseCoreVariant = false
 				}
 
@@ -1794,6 +2016,11 @@
 		return nil
 	}
 
+	if library.hasLLNDKStubs() && ctx.Module().(*Module).UseVndk() {
+		// LLNDK libraries only need a single stubs variant.
+		return []string{android.FutureApiLevel.String()}
+	}
+
 	// Future API level is implicitly added if there isn't
 	vers := library.Properties.Stubs.Versions
 	if inList(android.FutureApiLevel.String(), vers) {
@@ -1846,6 +2073,15 @@
 	return android.CheckAvailableForApex(what, list)
 }
 
+func (library *libraryDecorator) installable() *bool {
+	if library.static() {
+		return library.StaticProperties.Static.Installable
+	} else if library.shared() {
+		return library.SharedProperties.Shared.Installable
+	}
+	return nil
+}
+
 func (library *libraryDecorator) makeUninstallable(mod *Module) {
 	if library.static() && library.buildStatic() && !library.buildStubs() {
 		// If we're asked to make a static library uninstallable we don't do
@@ -1857,6 +2093,10 @@
 	mod.ModuleBase.MakeUninstallable()
 }
 
+func (library *libraryDecorator) getAPIListCoverageXMLPath() android.ModuleOutPath {
+	return library.apiListCoverageXmlPath
+}
+
 var versioningMacroNamesListKey = android.NewOnceKey("versioningMacroNamesList")
 
 // versioningMacroNamesList returns a singleton map, where keys are "version macro names",
@@ -2135,8 +2375,7 @@
 	return nil
 }
 
-// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions,
-// and propagates the value from implementation libraries to llndk libraries with the same name.
+// 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() {
@@ -2150,15 +2389,6 @@
 				// depend on the implementation library and haven't been mutated yet.
 				library.setAllStubsVersions(versions)
 			}
-
-			if mctx.Module().(*Module).UseVndk() && library.hasLLNDKStubs() {
-				// Propagate the version to the llndk stubs module.
-				mctx.VisitDirectDepsWithTag(llndkStubDepTag, func(stubs android.Module) {
-					if stubsLib := moduleLibraryInterface(stubs); stubsLib != nil {
-						stubsLib.setAllStubsVersions(library.allStubsVersions())
-					}
-				})
-			}
 		}
 	}
 }
@@ -2207,7 +2437,6 @@
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
 			BuiltTool("bssl_inject_hash").
-			Flag("-sha256").
 			FlagWithInput("-in-object ", outputFile).
 			FlagWithOutput("-o ", hashedOutputfile)
 		rule.Build("injectCryptoHash", "inject crypto hash")
@@ -2216,71 +2445,192 @@
 	return outputFile
 }
 
-type bazelCcLibraryStaticAttributes struct {
-	Copts              bazel.StringListAttribute
-	Srcs               bazel.LabelListAttribute
-	Deps               bazel.LabelListAttribute
-	Whole_archive_deps bazel.LabelListAttribute
-	Linkopts           bazel.StringListAttribute
-	Linkstatic         bool
-	Includes           bazel.StringListAttribute
-	Hdrs               bazel.LabelListAttribute
-}
+func sharedOrStaticLibraryBp2Build(ctx android.TopDownMutatorContext, module *Module, isStatic bool) {
+	baseAttributes := bp2BuildParseBaseProps(ctx, module)
+	compilerAttrs := baseAttributes.compilerAttributes
+	linkerAttrs := baseAttributes.linkerAttributes
 
-type bazelCcLibraryStatic struct {
-	android.BazelTargetModuleBase
-	bazelCcLibraryStaticAttributes
-}
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module, compilerAttrs.includes)
 
-func BazelCcLibraryStaticFactory() android.Module {
-	module := &bazelCcLibraryStatic{}
-	module.AddProperties(&module.bazelCcLibraryStaticAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
+	// Append shared/static{} stanza properties. These won't be specified on
+	// cc_library_* itself, but may be specified in cc_defaults that this module
+	// depends on.
+	libSharedOrStaticAttrs := bp2BuildParseLibProps(ctx, module, isStatic)
 
-func ccLibraryStaticBp2BuildInternal(ctx android.TopDownMutatorContext, module *Module) {
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
+	compilerAttrs.srcs.Append(libSharedOrStaticAttrs.Srcs)
+	compilerAttrs.cSrcs.Append(libSharedOrStaticAttrs.Srcs_c)
+	compilerAttrs.asSrcs.Append(libSharedOrStaticAttrs.Srcs_as)
+	compilerAttrs.copts.Append(libSharedOrStaticAttrs.Copts)
 
-	attrs := &bazelCcLibraryStaticAttributes{
-		Copts:              compilerAttrs.copts,
-		Srcs:               compilerAttrs.srcs,
-		Deps:               linkerAttrs.deps,
-		Whole_archive_deps: linkerAttrs.wholeArchiveDeps,
+	linkerAttrs.deps.Append(libSharedOrStaticAttrs.Deps)
+	linkerAttrs.implementationDeps.Append(libSharedOrStaticAttrs.Implementation_deps)
+	linkerAttrs.dynamicDeps.Append(libSharedOrStaticAttrs.Dynamic_deps)
+	linkerAttrs.implementationDynamicDeps.Append(libSharedOrStaticAttrs.Implementation_dynamic_deps)
+	linkerAttrs.systemDynamicDeps.Append(libSharedOrStaticAttrs.System_dynamic_deps)
 
-		Linkopts:   linkerAttrs.linkopts,
-		Linkstatic: true,
-		Includes:   exportedIncludes,
+	asFlags := compilerAttrs.asFlags
+	if compilerAttrs.asSrcs.IsEmpty() {
+		// Skip asflags for BUILD file simplicity if there are no assembly sources.
+		asFlags = bazel.MakeStringListAttribute(nil)
 	}
 
+	commonAttrs := staticOrSharedAttributes{
+		Srcs:    compilerAttrs.srcs,
+		Srcs_c:  compilerAttrs.cSrcs,
+		Srcs_as: compilerAttrs.asSrcs,
+		Copts:   compilerAttrs.copts,
+		Hdrs:    compilerAttrs.hdrs,
+
+		Deps:                              linkerAttrs.deps,
+		Implementation_deps:               linkerAttrs.implementationDeps,
+		Dynamic_deps:                      linkerAttrs.dynamicDeps,
+		Implementation_dynamic_deps:       linkerAttrs.implementationDynamicDeps,
+		Whole_archive_deps:                linkerAttrs.wholeArchiveDeps,
+		Implementation_whole_archive_deps: linkerAttrs.implementationWholeArchiveDeps,
+		System_dynamic_deps:               linkerAttrs.systemDynamicDeps,
+		sdkAttributes:                     bp2BuildParseSdkAttributes(module),
+	}
+
+	var attrs interface{}
+	if isStatic {
+		commonAttrs.Deps.Add(baseAttributes.protoDependency)
+		attrs = &bazelCcLibraryStaticAttributes{
+			staticOrSharedAttributes: commonAttrs,
+
+			Use_libcrt:      linkerAttrs.useLibcrt,
+			Use_version_lib: linkerAttrs.useVersionLib,
+
+			Rtti:    compilerAttrs.rtti,
+			Stl:     compilerAttrs.stl,
+			Cpp_std: compilerAttrs.cppStd,
+			C_std:   compilerAttrs.cStd,
+
+			Export_includes:          exportedIncludes.Includes,
+			Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+			Export_system_includes:   exportedIncludes.SystemIncludes,
+			Local_includes:           compilerAttrs.localIncludes,
+			Absolute_includes:        compilerAttrs.absoluteIncludes,
+
+			Cppflags:   compilerAttrs.cppFlags,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Asflags:    asFlags,
+
+			Features: linkerAttrs.features,
+		}
+	} else {
+		commonAttrs.Dynamic_deps.Add(baseAttributes.protoDependency)
+
+		attrs = &bazelCcLibrarySharedAttributes{
+			staticOrSharedAttributes: commonAttrs,
+
+			Cppflags:   compilerAttrs.cppFlags,
+			Conlyflags: compilerAttrs.conlyFlags,
+			Asflags:    asFlags,
+
+			Linkopts:        linkerAttrs.linkopts,
+			Link_crt:        linkerAttrs.linkCrt,
+			Use_libcrt:      linkerAttrs.useLibcrt,
+			Use_version_lib: linkerAttrs.useVersionLib,
+
+			Rtti:    compilerAttrs.rtti,
+			Stl:     compilerAttrs.stl,
+			Cpp_std: compilerAttrs.cppStd,
+			C_std:   compilerAttrs.cStd,
+
+			Export_includes:          exportedIncludes.Includes,
+			Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+			Export_system_includes:   exportedIncludes.SystemIncludes,
+			Local_includes:           compilerAttrs.localIncludes,
+			Absolute_includes:        compilerAttrs.absoluteIncludes,
+			Additional_linker_inputs: linkerAttrs.additionalLinkerInputs,
+
+			Strip: stripAttributes{
+				Keep_symbols:                 linkerAttrs.stripKeepSymbols,
+				Keep_symbols_and_debug_frame: linkerAttrs.stripKeepSymbolsAndDebugFrame,
+				Keep_symbols_list:            linkerAttrs.stripKeepSymbolsList,
+				All:                          linkerAttrs.stripAll,
+				None:                         linkerAttrs.stripNone,
+			},
+
+			Features: linkerAttrs.features,
+
+			Stubs_symbol_file: compilerAttrs.stubsSymbolFile,
+			Stubs_versions:    compilerAttrs.stubsVersions,
+		}
+	}
+
+	var modType string
+	if isStatic {
+		modType = "cc_library_static"
+	} else {
+		modType = "cc_library_shared"
+	}
 	props := bazel.BazelTargetModuleProperties{
-		Rule_class:        "cc_library_static",
-		Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl",
+		Rule_class:        modType,
+		Bzl_load_location: fmt.Sprintf("//build/bazel/rules/cc:%s.bzl", modType),
 	}
 
-	ctx.CreateBazelTargetModule(BazelCcLibraryStaticFactory, module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
 
-func CcLibraryStaticBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*Module)
-	if !ok {
-		// Not a cc module
-		return
-	}
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-	if ctx.ModuleType() != "cc_library_static" {
-		return
-	}
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
+type bazelCcLibraryStaticAttributes struct {
+	staticOrSharedAttributes
 
-	ccLibraryStaticBp2BuildInternal(ctx, module)
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
+
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Local_includes           bazel.StringListAttribute
+	Absolute_includes        bazel.StringListAttribute
+	Hdrs                     bazel.LabelListAttribute
+
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Features bazel.StringListAttribute
 }
 
-func (m *bazelCcLibraryStatic) Name() string {
-	return m.BaseModuleName()
-}
+// TODO(b/199902614): Can this be factored to share with the other Attributes?
+type bazelCcLibrarySharedAttributes struct {
+	staticOrSharedAttributes
 
-func (m *bazelCcLibraryStatic) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+	Linkopts bazel.StringListAttribute
+	Link_crt bazel.BoolAttribute // Only for linking shared library (and cc_binary)
+
+	Use_libcrt      bazel.BoolAttribute
+	Use_version_lib bazel.BoolAttribute
+
+	Rtti    bazel.BoolAttribute
+	Stl     *string
+	Cpp_std *string
+	C_std   *string
+
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Local_includes           bazel.StringListAttribute
+	Absolute_includes        bazel.StringListAttribute
+	Hdrs                     bazel.LabelListAttribute
+
+	Strip                    stripAttributes
+	Additional_linker_inputs bazel.LabelListAttribute
+
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Features bazel.StringListAttribute
+
+	Stubs_symbol_file *string
+	Stubs_versions    bazel.StringListAttribute
+	Inject_bssl_hash  bazel.BoolAttribute
+}
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 0aba8de..41ebcc7 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -25,7 +25,6 @@
 	// Register sdk member types.
 	android.RegisterSdkMemberType(headersLibrarySdkMemberType)
 
-	android.RegisterBp2BuildMutator("cc_library_headers", CcLibraryHeadersBp2Build)
 }
 
 var headersLibrarySdkMemberType = &librarySdkMemberType{
@@ -33,6 +32,11 @@
 		PropertyName:    "native_header_libs",
 		SupportsSdk:     true,
 		HostOsDependent: true,
+		Traits: []android.SdkMemberTrait{
+			nativeBridgeSdkTrait,
+			ramdiskImageRequiredSdkTrait,
+			recoveryImageRequiredSdkTrait,
+		},
 	},
 	prebuiltModuleType: "cc_prebuilt_library_headers",
 	noOutputFiles:      true,
@@ -44,15 +48,15 @@
 }
 
 type libraryHeaderBazelHander struct {
-	bazelHandler
+	android.BazelHandler
 
 	module  *Module
 	library *libraryDecorator
 }
 
-func (h *libraryHeaderBazelHander) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *libraryHeaderBazelHander) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
 		return false
@@ -73,13 +77,7 @@
 	// HeaderLibraryInfo is an empty struct to indicate to dependencies that this is a header library
 	ctx.SetProvider(HeaderLibraryInfoProvider, HeaderLibraryInfo{})
 
-	flagExporterInfo := flagExporterInfoFromCcInfo(ctx, ccInfo)
-	// Store flag info to be passed along to androimk
-	// TODO(b/184387147): Androidmk should be done in Bazel, not Soong.
-	h.library.flagExporterInfo = &flagExporterInfo
-	// flag exporters consolidates properties like includes, flags, dependencies that should be
-	// exported from this module to other modules
-	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfo)
+	h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
 
 	// Dependencies on this library will expect collectedSnapshotHeaders to be set, otherwise
 	// validation will fail. For now, set this to an empty list.
@@ -97,71 +95,51 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.HeaderOnly()
 	module.sdkMemberTypes = []android.SdkMemberType{headersLibrarySdkMemberType}
+	module.bazelable = true
 	module.bazelHandler = &libraryHeaderBazelHander{module: module, library: library}
 	return module.Init()
 }
 
 // cc_prebuilt_library_headers is a prebuilt version of cc_library_headers
 func prebuiltLibraryHeaderFactory() android.Module {
-	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported, "")
 	library.HeaderOnly()
+	module.bazelable = true
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
 type bazelCcLibraryHeadersAttributes struct {
-	Copts    bazel.StringListAttribute
-	Hdrs     bazel.LabelListAttribute
-	Includes bazel.StringListAttribute
-	Deps     bazel.LabelListAttribute
+	Hdrs                     bazel.LabelListAttribute
+	Export_includes          bazel.StringListAttribute
+	Export_absolute_includes bazel.StringListAttribute
+	Export_system_includes   bazel.StringListAttribute
+	Deps                     bazel.LabelListAttribute
+	Implementation_deps      bazel.LabelListAttribute
+	System_dynamic_deps      bazel.LabelListAttribute
+	sdkAttributes
 }
 
-type bazelCcLibraryHeaders struct {
-	android.BazelTargetModuleBase
-	bazelCcLibraryHeadersAttributes
-}
-
-func BazelCcLibraryHeadersFactory() android.Module {
-	module := &bazelCcLibraryHeaders{}
-	module.AddProperties(&module.bazelCcLibraryHeadersAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func CcLibraryHeadersBp2Build(ctx android.TopDownMutatorContext) {
-	module, ok := ctx.Module().(*Module)
-	if !ok {
-		// Not a cc module
-		return
-	}
-
-	if !module.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "cc_library_headers" {
-		return
-	}
-
-	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
-	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
+func libraryHeadersBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	baseAttributes := bp2BuildParseBaseProps(ctx, module)
+	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module, baseAttributes.includes)
+	linkerAttrs := baseAttributes.linkerAttributes
 
 	attrs := &bazelCcLibraryHeadersAttributes{
-		Copts:    compilerAttrs.copts,
-		Includes: exportedIncludes,
-		Deps:     linkerAttrs.deps,
+		Export_includes:          exportedIncludes.Includes,
+		Export_absolute_includes: exportedIncludes.AbsoluteIncludes,
+		Export_system_includes:   exportedIncludes.SystemIncludes,
+		Implementation_deps:      linkerAttrs.implementationDeps,
+		Deps:                     linkerAttrs.deps,
+		System_dynamic_deps:      linkerAttrs.systemDynamicDeps,
+		Hdrs:                     baseAttributes.hdrs,
+		sdkAttributes:            bp2BuildParseSdkAttributes(module),
 	}
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "cc_library_headers",
-		Bzl_load_location: "//build/bazel/rules:cc_library_headers.bzl",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_library_headers.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(BazelCcLibraryHeadersFactory, module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
-
-func (m *bazelCcLibraryHeaders) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelCcLibraryHeaders) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
diff --git a/cc/library_headers_test.go b/cc/library_headers_test.go
index 564ef61..3e448ba 100644
--- a/cc/library_headers_test.go
+++ b/cc/library_headers_test.go
@@ -15,48 +15,85 @@
 package cc
 
 import (
-	"strings"
+	"fmt"
 	"testing"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
 )
 
 func TestLibraryHeaders(t *testing.T) {
-	ctx := testCc(t, `
-	cc_library_headers {
-		name: "headers",
-		export_include_dirs: ["my_include"],
-	}
-	cc_library_static {
-		name: "lib",
-		srcs: ["foo.c"],
-		header_libs: ["headers"],
-	}
-	`)
+	bp := `
+		%s {
+			name: "headers",
+			export_include_dirs: ["my_include"],
+		}
+		cc_library_static {
+			name: "lib",
+			srcs: ["foo.c"],
+			header_libs: ["headers"],
+		}
+	`
 
-	// test if header search paths are correctly added
-	cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
-	cflags := cc.Args["cFlags"]
-	if !strings.Contains(cflags, " -Imy_include ") {
-		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
+	for _, headerModule := range []string{"cc_library_headers", "cc_prebuilt_library_headers"} {
+		t.Run(headerModule, func(t *testing.T) {
+			ctx := testCc(t, fmt.Sprintf(bp, headerModule))
+
+			// test if header search paths are correctly added
+			cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
+			android.AssertStringDoesContain(t, "cFlags for lib module", cc.Args["cFlags"], " -Imy_include ")
+
+			// Test that there's a valid AndroidMk entry.
+			headers := ctx.ModuleForTests("headers", "android_arm64_armv8-a").Module()
+			e := android.AndroidMkEntriesForTest(t, ctx, headers)[0]
+
+			// This duplicates the tests done in AndroidMkEntries.write. It would be
+			// better to test its output, but there are no test functions that capture that.
+			android.AssertBoolEquals(t, "AndroidMkEntries.Disabled", false, e.Disabled)
+			android.AssertBoolEquals(t, "AndroidMkEntries.OutputFile.Valid()", true, e.OutputFile.Valid())
+
+			android.AssertStringListContains(t, "LOCAL_EXPORT_CFLAGS for headers module", e.EntryMap["LOCAL_EXPORT_CFLAGS"], "-Imy_include")
+		})
 	}
 }
 
-func TestPrebuiltLibraryHeaders(t *testing.T) {
-	ctx := testCc(t, `
-	cc_prebuilt_library_headers {
-		name: "headers",
-		export_include_dirs: ["my_include"],
-	}
-	cc_library_static {
-		name: "lib",
-		srcs: ["foo.c"],
-		header_libs: ["headers"],
-	}
-	`)
+func TestPrebuiltLibraryHeadersPreferred(t *testing.T) {
+	bp := `
+		cc_library_headers {
+			name: "headers",
+			export_include_dirs: ["my_include"],
+		}
+		cc_prebuilt_library_headers {
+			name: "headers",
+			prefer: %t,
+			export_include_dirs: ["my_include"],
+		}
+		cc_library_static {
+			name: "lib",
+			srcs: ["foo.c"],
+			header_libs: ["headers"],
+		}
+	`
 
-	// test if header search paths are correctly added
-	cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
-	cflags := cc.Args["cFlags"]
-	if !strings.Contains(cflags, " -Imy_include ") {
-		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
+	for _, prebuiltPreferred := range []bool{false, true} {
+		t.Run(fmt.Sprintf("prebuilt prefer %t", prebuiltPreferred), func(t *testing.T) {
+			ctx := testCc(t, fmt.Sprintf(bp, prebuiltPreferred))
+			lib := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static")
+			sourceDep := ctx.ModuleForTests("headers", "android_arm64_armv8-a")
+			prebuiltDep := ctx.ModuleForTests("prebuilt_headers", "android_arm64_armv8-a")
+			hasSourceDep := false
+			hasPrebuiltDep := false
+			ctx.VisitDirectDeps(lib.Module(), func(dep blueprint.Module) {
+				if dep == sourceDep.Module() {
+					hasSourceDep = true
+				}
+				if dep == prebuiltDep.Module() {
+					hasPrebuiltDep = true
+				}
+			})
+			android.AssertBoolEquals(t, "depends on source headers", !prebuiltPreferred, hasSourceDep)
+			android.AssertBoolEquals(t, "depends on prebuilt headers", prebuiltPreferred, hasPrebuiltDep)
+		})
 	}
 }
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index 9010a1a..8988de2 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -74,30 +74,113 @@
 	linkTypes []string
 }
 
-func (mt *librarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	targets := mctx.MultiTargets()
+func (mt *librarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	// The base set of targets which does not include native bridge targets.
+	defaultTargets := ctx.MultiTargets()
+
+	// The lazily created list of native bridge targets.
+	var includeNativeBridgeTargets []android.Target
+
 	for _, lib := range names {
-		for _, target := range targets {
-			name, version := StubsLibNameAndVersion(lib)
-			if version == "" {
-				version = "latest"
-			}
-			variations := target.Variations()
-			if mctx.Device() {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "image", Variation: android.CoreVariation})
-			}
-			if mt.linkTypes == nil {
-				mctx.AddFarVariationDependencies(variations, dependencyTag, name)
-			} else {
-				for _, linkType := range mt.linkTypes {
-					libVariations := append(variations,
-						blueprint.Variation{Mutator: "link", Variation: linkType})
-					if mctx.Device() && linkType == "shared" {
-						libVariations = append(libVariations,
-							blueprint.Variation{Mutator: "version", Variation: version})
+		targets := defaultTargets
+
+		// If native bridge support is required in the sdk snapshot then add native bridge targets to
+		// the basic list of targets that are required.
+		nativeBridgeSupport := ctx.RequiresTrait(lib, nativeBridgeSdkTrait)
+		if nativeBridgeSupport && ctx.Device() {
+			// If not already computed then compute the list of native bridge targets.
+			if includeNativeBridgeTargets == nil {
+				includeNativeBridgeTargets = append([]android.Target{}, defaultTargets...)
+				allAndroidTargets := ctx.Config().Targets[android.Android]
+				for _, possibleNativeBridgeTarget := range allAndroidTargets {
+					if possibleNativeBridgeTarget.NativeBridge == android.NativeBridgeEnabled {
+						includeNativeBridgeTargets = append(includeNativeBridgeTargets, possibleNativeBridgeTarget)
 					}
-					mctx.AddFarVariationDependencies(libVariations, dependencyTag, name)
+				}
+			}
+
+			// Include the native bridge targets as well.
+			targets = includeNativeBridgeTargets
+		}
+
+		// memberDependency encapsulates information about the dependencies to add for this member.
+		type memberDependency struct {
+			// The targets to depend upon.
+			targets []android.Target
+
+			// Additional image variations to depend upon, is either nil for no image variation or
+			// contains a single image variation.
+			imageVariations []blueprint.Variation
+		}
+
+		// Extract the name and version from the module name.
+		name, version := StubsLibNameAndVersion(lib)
+		if version == "" {
+			version = "latest"
+		}
+
+		// Compute the set of dependencies to add.
+		var memberDependencies []memberDependency
+		if ctx.Host() {
+			// Host does not support image variations so add a dependency without any.
+			memberDependencies = append(memberDependencies, memberDependency{
+				targets: targets,
+			})
+		} else {
+			// Otherwise, this is targeting the device so add a dependency on the core image variation
+			// (image:"").
+			memberDependencies = append(memberDependencies, memberDependency{
+				imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.CoreVariation}},
+				targets:         targets,
+			})
+
+			// If required add additional dependencies on the image:ramdisk variants.
+			if ctx.RequiresTrait(lib, ramdiskImageRequiredSdkTrait) {
+				memberDependencies = append(memberDependencies, memberDependency{
+					imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.RamdiskVariation}},
+					// Only add a dependency on the first target as that is the only one which will have an
+					// image:ramdisk variant.
+					targets: targets[:1],
+				})
+			}
+
+			// If required add additional dependencies on the image:recovery variants.
+			if ctx.RequiresTrait(lib, recoveryImageRequiredSdkTrait) {
+				memberDependencies = append(memberDependencies, memberDependency{
+					imageVariations: []blueprint.Variation{{Mutator: "image", Variation: android.RecoveryVariation}},
+					// Only add a dependency on the first target as that is the only one which will have an
+					// image:recovery variant.
+					targets: targets[:1],
+				})
+			}
+		}
+
+		// For each dependency in the list add dependencies on the targets with the correct variations.
+		for _, dependency := range memberDependencies {
+			// For each target add a dependency on the target with any additional dependencies.
+			for _, target := range dependency.targets {
+				// Get the variations for the target.
+				variations := target.Variations()
+
+				// Add any additional dependencies needed.
+				variations = append(variations, dependency.imageVariations...)
+
+				if mt.linkTypes == nil {
+					// No link types are supported so add a dependency directly.
+					ctx.AddFarVariationDependencies(variations, dependencyTag, name)
+				} else {
+					// Otherwise, add a dependency on each supported link type in turn.
+					for _, linkType := range mt.linkTypes {
+						libVariations := append(variations,
+							blueprint.Variation{Mutator: "link", Variation: linkType})
+						// If this is for the device and a shared link type then add a dependency onto the
+						// appropriate version specific variant of the module.
+						if ctx.Device() && linkType == "shared" {
+							libVariations = append(libVariations,
+								blueprint.Variation{Mutator: "version", Variation: version})
+						}
+						ctx.AddFarVariationDependencies(libVariations, dependencyTag, name)
+					}
 				}
 			}
 		}
@@ -122,7 +205,15 @@
 
 	ccModule := member.Variants()[0].(*Module)
 
-	if proptools.Bool(ccModule.Properties.Recovery_available) {
+	if ctx.RequiresTrait(nativeBridgeSdkTrait) {
+		pbm.AddProperty("native_bridge_supported", true)
+	}
+
+	if ctx.RequiresTrait(ramdiskImageRequiredSdkTrait) {
+		pbm.AddProperty("ramdisk_available", true)
+	}
+
+	if ctx.RequiresTrait(recoveryImageRequiredSdkTrait) {
 		pbm.AddProperty("recovery_available", true)
 	}
 
@@ -265,8 +356,8 @@
 	// values where necessary.
 	for _, propertyInfo := range includeDirProperties {
 		// Calculate the base directory in the snapshot into which the files will be copied.
-		// lib.archType is "" for common properties.
-		targetDir := filepath.Join(libInfo.OsPrefix(), libInfo.archType, propertyInfo.snapshotDir)
+		// lib.archSubDir is "" for common properties.
+		targetDir := filepath.Join(libInfo.OsPrefix(), libInfo.archSubDir, propertyInfo.snapshotDir)
 
 		propertyName := propertyInfo.propertyName
 
@@ -334,7 +425,7 @@
 
 // path to the native library. Relative to <sdk_root>/<api_dir>
 func nativeLibraryPathFor(lib *nativeLibInfoProperties) string {
-	return filepath.Join(lib.OsPrefix(), lib.archType,
+	return filepath.Join(lib.OsPrefix(), lib.archSubDir,
 		nativeStubDir, lib.outputFile.Base())
 }
 
@@ -347,9 +438,12 @@
 
 	memberType *librarySdkMemberType
 
-	// archType is not exported as if set (to a non default value) it is always arch specific.
-	// This is "" for common properties.
-	archType string
+	// archSubDir is the subdirectory within the OS directory in the sdk snapshot into which arch
+	// specific files will be copied.
+	//
+	// It is not exported since any value other than "" is always going to be arch specific.
+	// This is "" for non-arch specific common properties.
+	archSubDir string
 
 	// The list of possibly common exported include dirs.
 	//
@@ -433,7 +527,11 @@
 	exportedIncludeDirs, exportedGeneratedIncludeDirs := android.FilterPathListPredicate(
 		exportedInfo.IncludeDirs, isGeneratedHeaderDirectory)
 
-	p.archType = ccModule.Target().Arch.ArchType.String()
+	target := ccModule.Target()
+	p.archSubDir = target.Arch.ArchType.String()
+	if target.NativeBridge == android.NativeBridgeEnabled {
+		p.archSubDir += "_native_bridge"
+	}
 
 	// Make sure that the include directories are unique.
 	p.ExportedIncludeDirs = android.FirstUniquePaths(exportedIncludeDirs)
diff --git a/cc/library_test.go b/cc/library_test.go
index 7975275..6d5eda2 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/bazel/cquery"
 )
 
 func TestLibraryReuse(t *testing.T) {
@@ -240,3 +241,215 @@
 
 	testCcError(t, `"libfoo" .*: versions: "X" could not be parsed as an integer and is not a recognized codename`, bp)
 }
+
+func TestCcLibraryWithBazel(t *testing.T) {
+	bp := `
+cc_library {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				Headers:              []string{"foo.h"},
+				RootDynamicLibraries: []string{"foo.so"},
+			},
+			"//foo/bar:bar_bp2build_cc_library_static": cquery.CcInfo{
+				CcObjectFiles:      []string{"foo.o"},
+				Includes:           []string{"include"},
+				SystemIncludes:     []string{"system_include"},
+				Headers:            []string{"foo.h"},
+				RootStaticArchives: []string{"foo.a"},
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	staticFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	outputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+
+	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo.a"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	flagExporter := ctx.ModuleProvider(staticFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	outputFiles, err = sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_library outputfiles %s", err)
+	}
+	expectedOutputFiles = []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	flagExporter = ctx.ModuleProvider(sharedFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+}
+
+func TestLibraryVersionScript(t *testing.T) {
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_library {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			version_script: "foo.map.txt",
+		}`)
+
+	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+
+	android.AssertStringListContains(t, "missing dependency on version_script",
+		libfoo.Implicits.Strings(), "foo.map.txt")
+	android.AssertStringDoesContain(t, "missing flag for version_script",
+		libfoo.Args["ldFlags"], "-Wl,--version-script,foo.map.txt")
+
+}
+
+func TestLibraryDynamicList(t *testing.T) {
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_library {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			dynamic_list: "foo.dynamic.txt",
+		}`)
+
+	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+
+	android.AssertStringListContains(t, "missing dependency on dynamic_list",
+		libfoo.Implicits.Strings(), "foo.dynamic.txt")
+	android.AssertStringDoesContain(t, "missing flag for dynamic_list",
+		libfoo.Args["ldFlags"], "-Wl,--dynamic-list,foo.dynamic.txt")
+
+}
+
+func TestCcLibrarySharedWithBazel(t *testing.T) {
+	bp := `
+cc_library_shared {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				RootDynamicLibraries: []string{"foo.so"},
+				TocFile:              "foo.so.toc",
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	producer := sharedFoo.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	tocFilePath := sharedFoo.(*Module).Toc()
+	if !tocFilePath.Valid() {
+		t.Errorf("Invalid tocFilePath: %s", tocFilePath)
+	}
+	tocFile := tocFilePath.Path()
+	expectedToc := "outputbase/execroot/__main__/foo.so.toc"
+	android.AssertStringEquals(t, "toc file", expectedToc, tocFile.String())
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, sharedFoo)[0]
+	expectedFlags := []string{"-Ioutputbase/execroot/__main__/include", "-isystem outputbase/execroot/__main__/system_include"}
+	gotFlags := entries.EntryMap["LOCAL_EXPORT_CFLAGS"]
+	android.AssertDeepEquals(t, "androidmk exported cflags", expectedFlags, gotFlags)
+}
+
+func TestWholeStaticLibPrebuilts(t *testing.T) {
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_prebuilt_library_static {
+			name: "libprebuilt",
+			srcs: ["foo.a"],
+		}
+
+		cc_library_static {
+			name: "libdirect",
+			whole_static_libs: ["libprebuilt"],
+		}
+
+		cc_library_static {
+			name: "libtransitive",
+			whole_static_libs: ["libdirect"],
+		}
+
+		cc_library_static {
+			name: "libdirect_with_srcs",
+			srcs: ["bar.c"],
+			whole_static_libs: ["libprebuilt"],
+		}
+
+		cc_library_static {
+			name: "libtransitive_with_srcs",
+			srcs: ["baz.c"],
+			whole_static_libs: ["libdirect_with_srcs"],
+		}
+	`)
+
+	libdirect := result.ModuleForTests("libdirect", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libtransitive := result.ModuleForTests("libtransitive", "android_arm64_armv8-a_static").Rule("arWithLibs")
+
+	libdirectWithSrcs := result.ModuleForTests("libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libtransitiveWithSrcs := result.ModuleForTests("libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
+
+	barObj := result.ModuleForTests("libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
+	bazObj := result.ModuleForTests("libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
+
+	android.AssertStringListContains(t, "missing dependency on foo.a",
+		libdirect.Inputs.Strings(), "foo.a")
+	android.AssertStringDoesContain(t, "missing flag for foo.a",
+		libdirect.Args["arLibs"], "foo.a")
+
+	android.AssertStringListContains(t, "missing dependency on foo.a",
+		libtransitive.Inputs.Strings(), "foo.a")
+	android.AssertStringDoesContain(t, "missing flag for foo.a",
+		libtransitive.Args["arLibs"], "foo.a")
+
+	android.AssertStringListContains(t, "missing dependency on foo.a",
+		libdirectWithSrcs.Inputs.Strings(), "foo.a")
+	android.AssertStringDoesContain(t, "missing flag for foo.a",
+		libdirectWithSrcs.Args["arLibs"], "foo.a")
+	android.AssertStringListContains(t, "missing dependency on bar.o",
+		libdirectWithSrcs.Inputs.Strings(), barObj.Output.String())
+	android.AssertStringDoesContain(t, "missing flag for bar.o",
+		libdirectWithSrcs.Args["arObjs"], barObj.Output.String())
+
+	android.AssertStringListContains(t, "missing dependency on foo.a",
+		libtransitiveWithSrcs.Inputs.Strings(), "foo.a")
+	android.AssertStringDoesContain(t, "missing flag for foo.a",
+		libtransitiveWithSrcs.Args["arLibs"], "foo.a")
+
+	android.AssertStringListContains(t, "missing dependency on bar.o",
+		libtransitiveWithSrcs.Inputs.Strings(), barObj.Output.String())
+	android.AssertStringDoesContain(t, "missing flag for bar.o",
+		libtransitiveWithSrcs.Args["arObjs"], barObj.Output.String())
+
+	android.AssertStringListContains(t, "missing dependency on baz.o",
+		libtransitiveWithSrcs.Inputs.Strings(), bazObj.Output.String())
+	android.AssertStringDoesContain(t, "missing flag for baz.o",
+		libtransitiveWithSrcs.Args["arObjs"], bazObj.Output.String())
+}
diff --git a/cc/linkable.go b/cc/linkable.go
index b583b69..6bec30c 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -3,6 +3,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel/cquery"
+	"android/soong/snapshot"
 
 	"github.com/google/blueprint"
 )
@@ -14,11 +15,6 @@
 	// SanitizePropDefined returns whether the Sanitizer properties struct for this module is defined.
 	SanitizePropDefined() bool
 
-	// IsDependencyRoot returns whether a module is of a type which cannot be a linkage dependency
-	// of another module. For example, cc_binary and rust_binary represent dependency roots as other
-	// modules cannot have linkage dependencies against these types.
-	IsDependencyRoot() bool
-
 	// IsSanitizerEnabled returns whether a sanitizer is enabled.
 	IsSanitizerEnabled(t SanitizerType) bool
 
@@ -76,24 +72,27 @@
 
 // Snapshottable defines those functions necessary for handling module snapshots.
 type Snapshottable interface {
+	snapshot.VendorSnapshotModuleInterface
+	snapshot.RecoverySnapshotModuleInterface
+
 	// SnapshotHeaders returns a list of header paths provided by this module.
 	SnapshotHeaders() android.Paths
 
-	// ExcludeFromVendorSnapshot returns true if this module should be otherwise excluded from the vendor snapshot.
-	ExcludeFromVendorSnapshot() bool
-
-	// ExcludeFromRecoverySnapshot returns true if this module should be otherwise excluded from the recovery snapshot.
-	ExcludeFromRecoverySnapshot() bool
-
 	// SnapshotLibrary returns true if this module is a snapshot library.
 	IsSnapshotLibrary() bool
 
+	// EffectiveLicenseFiles returns the list of License files for this module.
+	EffectiveLicenseFiles() android.Paths
+
 	// SnapshotRuntimeLibs returns a list of libraries needed by this module at runtime but which aren't build dependencies.
 	SnapshotRuntimeLibs() []string
 
 	// SnapshotSharedLibs returns the list of shared library dependencies for this module.
 	SnapshotSharedLibs() []string
 
+	// SnapshotStaticLibs returns the list of static library dependencies for this module.
+	SnapshotStaticLibs() []string
+
 	// IsSnapshotPrebuilt returns true if this module is a snapshot prebuilt.
 	IsSnapshotPrebuilt() bool
 }
@@ -111,6 +110,7 @@
 	BaseModuleName() string
 
 	OutputFile() android.OptionalPath
+	UnstrippedOutputFile() android.Path
 	CoverageFiles() android.Paths
 
 	NonCcVariants() bool
@@ -124,6 +124,7 @@
 	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
+	Device() bool
 	Host() bool
 
 	InRamdisk() bool
@@ -165,6 +166,9 @@
 	// "product_specific: true" modules are included here.
 	UseVndk() bool
 
+	// Bootstrap tests if this module is allowed to use non-APEX version of libraries.
+	Bootstrap() bool
+
 	// IsVndkSp returns true if this is a VNDK-SP module.
 	IsVndkSp() bool
 
@@ -172,10 +176,14 @@
 	IsVndk() bool
 	IsVndkExt() bool
 	IsVndkPrivate() bool
+	IsVendorPublicLibrary() bool
+	IsVndkPrebuiltLibrary() bool
 	HasVendorVariant() bool
 	HasProductVariant() bool
 	HasNonSystemVariants() bool
+	ProductSpecific() bool
 	InProduct() bool
+	SdkAndPlatformVariantVisibleToMake() bool
 
 	// SubName returns the modules SubName, used for image and NDK/SDK variations.
 	SubName() string
@@ -223,6 +231,9 @@
 	// Header returns true if this is a library headers module.
 	Header() bool
 
+	// StaticExecutable returns true if this is a binary module with "static_executable: true".
+	StaticExecutable() bool
+
 	// EverInstallable returns true if the module is ever installable
 	EverInstallable() bool
 
@@ -305,14 +316,13 @@
 
 // SharedLibraryInfo is a provider to propagate information about a shared C++ library.
 type SharedLibraryInfo struct {
-	SharedLibrary           android.Path
-	UnstrippedSharedLibrary android.Path
-	Target                  android.Target
+	SharedLibrary android.Path
+	Target        android.Target
 
-	TableOfContents       android.OptionalPath
-	CoverageSharedLibrary android.OptionalPath
+	TableOfContents android.OptionalPath
 
-	StaticAnalogue *StaticLibraryInfo
+	// should be obtained from static analogue
+	TransitiveStaticLibrariesForOrdering *android.DepSet
 }
 
 var SharedLibraryInfoProvider = blueprint.NewProvider(SharedLibraryInfo{})
@@ -346,6 +356,11 @@
 	Objects       Objects
 	ReuseObjects  Objects
 
+	// A static library may contain prebuilt static libraries included with whole_static_libs
+	// that won't appear in Objects.  They are transitively available in
+	// WholeStaticLibsFromPrebuilts.
+	WholeStaticLibsFromPrebuilts android.Paths
+
 	// This isn't the actual transitive DepSet, shared library dependencies have been
 	// converted into static library analogues.  It is only used to order the static
 	// library dependencies that were specified for the current module.
@@ -378,9 +393,13 @@
 
 	includes := android.PathsForBazelOut(ctx, ccInfo.Includes)
 	systemIncludes := android.PathsForBazelOut(ctx, ccInfo.SystemIncludes)
+	headers := android.PathsForBazelOut(ctx, ccInfo.Headers)
 
 	return FlagExporterInfo{
 		IncludeDirs:       android.FirstUniquePaths(includes),
 		SystemIncludeDirs: android.FirstUniquePaths(systemIncludes),
+		GeneratedHeaders:  headers,
+		// necessary to ensure generated headers are considered implicit deps of dependent actions
+		Deps: headers,
 	}
 }
diff --git a/cc/linker.go b/cc/linker.go
index 88619f3..bea65d4 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -15,9 +15,10 @@
 package cc
 
 import (
+	"fmt"
+
 	"android/soong/android"
 	"android/soong/cc/config"
-	"fmt"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -26,6 +27,10 @@
 // This file contains the basic functionality for linking against static libraries and shared
 // libraries.  Final linking into libraries or executables is handled in library.go, binary.go, etc.
 
+const (
+	packRelocationsDefault = true
+)
+
 type BaseLinkerProperties struct {
 	// list of modules whose object files should be linked into this module
 	// in their entirety.  For static library modules, all of the .o files from the intermediate
@@ -85,8 +90,7 @@
 	// compiling crt or libc.
 	Nocrt *bool `android:"arch_variant"`
 
-	// group static libraries.  This can resolve missing symbols issues with interdependencies
-	// between static libraries, but it is generally better to order them correctly instead.
+	// deprecated and ignored because lld makes it unnecessary. See b/189475744.
 	Group_static_libs *bool `android:"arch_variant"`
 
 	// list of modules that should be installed with this module.  This is similar to 'required'
@@ -107,6 +111,10 @@
 			// product variant of the C/C++ module.
 			Static_libs []string
 
+			// list of ehader libs that only should be used to build vendor or product
+			// variant of the C/C++ module.
+			Header_libs []string
+
 			// list of shared libs that should not be used to build vendor or
 			// product variant of the C/C++ module.
 			Exclude_shared_libs []string
@@ -187,6 +195,14 @@
 			// in most cases the same libraries are available for the SDK and platform
 			// variants.
 			Shared_libs []string
+
+			// list of ehader libs that only should be used to build platform variant of
+			// the C/C++ module.
+			Header_libs []string
+
+			// list of shared libs that should not be used to build the platform variant
+			// of the C/C++ module.
+			Exclude_shared_libs []string
 		}
 		Apex struct {
 			// list of shared libs that should not be used to build the apex variant of
@@ -208,6 +224,9 @@
 	// local file name to pass to the linker as --version_script
 	Version_script *string `android:"path,arch_variant"`
 
+	// local file name to pass to the linker as --dynamic-list
+	Dynamic_list *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"`
 
@@ -215,6 +234,31 @@
 	Exclude_shared_libs []string `android:"arch_variant"`
 }
 
+func invertBoolPtr(value *bool) *bool {
+	if value == nil {
+		return nil
+	}
+	ret := !(*value)
+	return &ret
+}
+
+func (blp *BaseLinkerProperties) crt() *bool {
+	val := invertBoolPtr(blp.Nocrt)
+	if val != nil && *val {
+		// == True
+		//
+		// Since crt is enabled for almost every module compiling against the Bionic runtime,
+		// use `nil` when it's enabled, and rely on the Starlark macro to set it to True by default.
+		// This keeps the BUILD files clean.
+		return nil
+	}
+	return val // can be False or nil
+}
+
+func (blp *BaseLinkerProperties) libCrt() *bool {
+	return invertBoolPtr(blp.No_libcrt)
+}
+
 func NewBaseLinker(sanitize *sanitize) *baseLinker {
 	return &baseLinker{sanitize: sanitize}
 }
@@ -284,6 +328,7 @@
 		deps.ReexportSharedLibHeaders = removeListFromList(deps.ReexportSharedLibHeaders, linker.Properties.Target.Vendor.Exclude_shared_libs)
 		deps.StaticLibs = append(deps.StaticLibs, linker.Properties.Target.Vendor.Static_libs...)
 		deps.StaticLibs = removeListFromList(deps.StaticLibs, linker.Properties.Target.Vendor.Exclude_static_libs)
+		deps.HeaderLibs = append(deps.HeaderLibs, linker.Properties.Target.Vendor.Header_libs...)
 		deps.HeaderLibs = removeListFromList(deps.HeaderLibs, linker.Properties.Target.Vendor.Exclude_header_libs)
 		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, linker.Properties.Target.Vendor.Exclude_static_libs)
 		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Vendor.Exclude_static_libs)
@@ -336,6 +381,18 @@
 
 	if !ctx.useSdk() {
 		deps.SharedLibs = append(deps.SharedLibs, linker.Properties.Target.Platform.Shared_libs...)
+		deps.SharedLibs = removeListFromList(deps.SharedLibs, linker.Properties.Target.Platform.Exclude_shared_libs)
+		deps.HeaderLibs = append(deps.HeaderLibs, linker.Properties.Target.Platform.Header_libs...)
+	}
+
+	deps.SystemSharedLibs = linker.Properties.System_shared_libs
+	// In Bazel conversion mode, variations have not been specified, so SystemSharedLibs may
+	// inaccuarately appear unset, which can cause issues with circular dependencies.
+	if deps.SystemSharedLibs == nil && !ctx.BazelConversionMode() {
+		// Provide a default system_shared_libs if it is unspecified. Note: If an
+		// empty list [] is specified, it implies that the module declines the
+		// default system_shared_libs.
+		deps.SystemSharedLibs = append(deps.SystemSharedLibs, ctx.toolchain().DefaultSharedLibraries()...)
 	}
 
 	if ctx.toolchain().Bionic() {
@@ -344,16 +401,6 @@
 			deps.LateStaticLibs = append(deps.LateStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
 		}
 
-		deps.SystemSharedLibs = linker.Properties.System_shared_libs
-		// In Bazel conversion mode, variations have not been specified, so SystemSharedLibs may
-		// inaccuarately appear unset, which can cause issues with circular dependencies.
-		if deps.SystemSharedLibs == nil && !ctx.BazelConversionMode() {
-			// Provide a default system_shared_libs if it is unspecified. Note: If an
-			// empty list [] is specified, it implies that the module declines the
-			// default system_shared_libs.
-			deps.SystemSharedLibs = []string{"libc", "libm", "libdl"}
-		}
-
 		if inList("libdl", deps.SharedLibs) {
 			// If system_shared_libs has libc but not libdl, make sure shared_libs does not
 			// have libdl to avoid loading libdl before libc.
@@ -372,23 +419,15 @@
 			indexList("libdl", deps.SystemSharedLibs) < indexList("libc", deps.SystemSharedLibs) {
 			ctx.PropertyErrorf("system_shared_libs", "libdl must be after libc")
 		}
-
-		deps.LateSharedLibs = append(deps.LateSharedLibs, deps.SystemSharedLibs...)
+	} else if ctx.toolchain().Musl() {
+		if !Bool(linker.Properties.No_libcrt) && !ctx.header() {
+			deps.LateStaticLibs = append(deps.LateStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
+		}
 	}
 
-	if ctx.Fuchsia() {
-		if ctx.ModuleName() != "libbioniccompat" &&
-			ctx.ModuleName() != "libcompiler_rt-extras" &&
-			ctx.ModuleName() != "libcompiler_rt" {
-			deps.StaticLibs = append(deps.StaticLibs, "libbioniccompat")
-		}
-		if ctx.ModuleName() != "libcompiler_rt" && ctx.ModuleName() != "libcompiler_rt-extras" {
-			deps.LateStaticLibs = append(deps.LateStaticLibs, "libcompiler_rt")
-		}
+	deps.LateSharedLibs = append(deps.LateSharedLibs, deps.SystemSharedLibs...)
 
-	}
-
-	if ctx.Windows() {
+	if ctx.Windows() && ctx.ModuleName() != "libwinpthread" {
 		deps.LateStaticLibs = append(deps.LateStaticLibs, "libwinpthread")
 	}
 
@@ -436,7 +475,7 @@
 
 	if linker.useClangLld(ctx) {
 		flags.Global.LdFlags = append(flags.Global.LdFlags, fmt.Sprintf("${config.%sGlobalLldflags}", hod))
-		if !BoolDefault(linker.Properties.Pack_relocations, true) {
+		if !BoolDefault(linker.Properties.Pack_relocations, packRelocationsDefault) {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=none")
 		} else if ctx.Device() {
 			// SHT_RELR relocations are only supported at API level >= 30.
@@ -466,12 +505,12 @@
 	}
 
 	if linker.useClangLld(ctx) {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ClangLldflags())
+		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Lldflags())
 	} else {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ClangLdflags())
+		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Ldflags())
 	}
 
-	if !ctx.toolchain().Bionic() && !ctx.Fuchsia() {
+	if !ctx.toolchain().Bionic() && ctx.Os() != android.LinuxMusl {
 		CheckBadHostLdlibs(ctx, "host_ldlibs", linker.Properties.Host_ldlibs)
 
 		flags.Local.LdFlags = append(flags.Local.LdFlags, linker.Properties.Host_ldlibs...)
@@ -490,10 +529,6 @@
 		}
 	}
 
-	if ctx.Fuchsia() {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, "-lfdio", "-lzircon")
-	}
-
 	if ctx.toolchain().LibclangRuntimeLibraryArch() != "" {
 		flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--exclude-libs="+config.BuiltinsRuntimeLibrary(ctx.toolchain())+".a")
 	}
@@ -522,11 +557,7 @@
 		flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--hash-style=both")
 	}
 
-	flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ToolchainClangLdflags())
-
-	if Bool(linker.Properties.Group_static_libs) {
-		flags.GroupStaticLibs = true
-	}
+	flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ToolchainLdflags())
 
 	// Version_script is not needed when linking stubs lib where the version
 	// script is created from the symbol map file.
@@ -560,6 +591,17 @@
 				}
 			}
 		}
+
+		dynamicList := android.OptionalPathForModuleSrc(ctx, linker.Properties.Dynamic_list)
+		if dynamicList.Valid() {
+			if ctx.Darwin() {
+				ctx.PropertyErrorf("dynamic_list", "Not supported on Darwin")
+			} else {
+				flags.Local.LdFlags = append(flags.Local.LdFlags,
+					"-Wl,--dynamic-list,"+dynamicList.String())
+				flags.LdFlagsDeps = append(flags.LdFlagsDeps, dynamicList.Path())
+			}
+		}
 	}
 
 	return flags
@@ -613,28 +655,3 @@
 		},
 	})
 }
-
-// Rule to generate .bss symbol ordering file.
-
-var (
-	_                   = pctx.SourcePathVariable("genSortedBssSymbolsPath", "build/soong/scripts/gen_sorted_bss_symbols.sh")
-	genSortedBssSymbols = pctx.AndroidStaticRule("gen_sorted_bss_symbols",
-		blueprint.RuleParams{
-			Command:     "CLANG_BIN=${clangBin} $genSortedBssSymbolsPath ${in} ${out}",
-			CommandDeps: []string{"$genSortedBssSymbolsPath", "${clangBin}/llvm-nm"},
-		},
-		"clangBin")
-)
-
-func (linker *baseLinker) sortBssSymbolsBySize(ctx ModuleContext, in android.Path, symbolOrderingFile android.ModuleOutPath, flags builderFlags) string {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        genSortedBssSymbols,
-		Description: "generate bss symbol order " + symbolOrderingFile.Base(),
-		Output:      symbolOrderingFile,
-		Input:       in,
-		Args: map[string]string{
-			"clangBin": "${config.ClangBin}",
-		},
-	})
-	return "-Wl,--symbol-ordering-file," + symbolOrderingFile.String()
-}
diff --git a/cc/lto.go b/cc/lto.go
index a3b28d9..2c274bd 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -15,6 +15,8 @@
 package cc
 
 import (
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 )
 
@@ -47,8 +49,9 @@
 
 	// Dep properties indicate that this module needs to be built with LTO
 	// since it is an object dependency of an LTO module.
-	FullDep bool `blueprint:"mutated"`
-	ThinDep bool `blueprint:"mutated"`
+	FullDep  bool `blueprint:"mutated"`
+	ThinDep  bool `blueprint:"mutated"`
+	NoLtoDep bool `blueprint:"mutated"`
 
 	// Use clang lld instead of gnu ld.
 	Use_clang_lld *bool
@@ -67,23 +70,10 @@
 
 func (lto *lto) begin(ctx BaseModuleContext) {
 	if ctx.Config().IsEnvTrue("DISABLE_LTO") {
-		lto.Properties.Lto.Never = boolPtr(true)
-	} else if ctx.Config().IsEnvTrue("GLOBAL_THINLTO") {
-		staticLib := ctx.static() && !ctx.staticBinary()
-		hostBin := ctx.Host()
-		vndk := ctx.isVndk() // b/169217596
-		if !staticLib && !hostBin && !vndk {
-			if !lto.Never() && !lto.FullLTO() {
-				lto.Properties.Lto.Thin = boolPtr(true)
-			}
-		}
+		lto.Properties.Lto.Never = proptools.BoolPtr(true)
 	}
 }
 
-func (lto *lto) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (lto *lto) useClangLld(ctx BaseModuleContext) bool {
 	if lto.Properties.Use_clang_lld != nil {
 		return Bool(lto.Properties.Use_clang_lld)
@@ -98,22 +88,27 @@
 		return flags
 	}
 
-	if lto.LTO() {
-		var ltoFlag string
+	if lto.LTO(ctx) {
+		var ltoCFlag string
+		var ltoLdFlag string
 		if lto.ThinLTO() {
-			ltoFlag = "-flto=thin -fsplit-lto-unit"
+			ltoCFlag = "-flto=thin -fsplit-lto-unit"
+		} else if lto.FullLTO() {
+			ltoCFlag = "-flto"
 		} else {
-			ltoFlag = "-flto"
+			ltoCFlag = "-flto=thin -fsplit-lto-unit"
+			ltoLdFlag = "-Wl,--lto-O0"
 		}
 
-		flags.Local.CFlags = append(flags.Local.CFlags, ltoFlag)
-		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoFlag)
+		flags.Local.CFlags = append(flags.Local.CFlags, ltoCFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoCFlag)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, ltoLdFlag)
 
 		if Bool(lto.Properties.Whole_program_vtables) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fwhole-program-vtables")
 		}
 
-		if lto.ThinLTO() && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) {
+		if (lto.DefaultThinLTO(ctx) || lto.ThinLTO()) && ctx.Config().IsEnvTrue("USE_THINLTO_CACHE") && lto.useClangLld(ctx) {
 			// Set appropriate ThinLTO cache policy
 			cacheDirFormat := "-Wl,--thinlto-cache-dir="
 			cacheDir := android.PathForOutput(ctx, "thinlto-cache").String()
@@ -128,7 +123,7 @@
 
 		// If the module does not have a profile, be conservative and limit cross TU inline
 		// limit to 5 LLVM IR instructions, to balance binary size increase and performance.
-		if !ctx.isPgoCompile() {
+		if !ctx.isPgoCompile() && !ctx.isAfdoCompile() {
 			flags.Local.LdFlags = append(flags.Local.LdFlags,
 				"-Wl,-plugin-opt,-import-instr-limit=5")
 		}
@@ -136,33 +131,40 @@
 	return flags
 }
 
-// Can be called with a null receiver
-func (lto *lto) LTO() bool {
-	if lto == nil || lto.Never() {
-		return false
-	}
+func (lto *lto) LTO(ctx BaseModuleContext) bool {
+	return lto.ThinLTO() || lto.FullLTO() || lto.DefaultThinLTO(ctx)
+}
 
-	return lto.FullLTO() || lto.ThinLTO()
+func (lto *lto) DefaultThinLTO(ctx BaseModuleContext) bool {
+	host := ctx.Host()
+	vndk := ctx.isVndk() // b/169217596
+	return GlobalThinLTO(ctx) && !lto.Never() && !host && !vndk
 }
 
 func (lto *lto) FullLTO() bool {
-	return Bool(lto.Properties.Lto.Full)
+	return lto != nil && Bool(lto.Properties.Lto.Full)
 }
 
 func (lto *lto) ThinLTO() bool {
-	return Bool(lto.Properties.Lto.Thin)
+	return lto != nil && Bool(lto.Properties.Lto.Thin)
 }
 
-// Is lto.never explicitly set to true?
 func (lto *lto) Never() bool {
-	return Bool(lto.Properties.Lto.Never)
+	return lto != nil && Bool(lto.Properties.Lto.Never)
+}
+
+func GlobalThinLTO(ctx android.BaseModuleContext) bool {
+	return ctx.Config().IsEnvTrue("GLOBAL_THINLTO")
 }
 
 // Propagate lto requirements down from binaries
 func ltoDepsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.lto.LTO() {
+	globalThinLTO := GlobalThinLTO(mctx)
+
+	if m, ok := mctx.Module().(*Module); ok {
 		full := m.lto.FullLTO()
 		thin := m.lto.ThinLTO()
+		never := m.lto.Never()
 		if full && thin {
 			mctx.PropertyErrorf("LTO", "FullLTO and ThinLTO are mutually exclusive")
 		}
@@ -182,14 +184,16 @@
 				}
 			}
 
-			if dep, ok := dep.(*Module); ok && dep.lto != nil &&
-				!dep.lto.Never() {
+			if dep, ok := dep.(*Module); ok {
 				if full && !dep.lto.FullLTO() {
 					dep.lto.Properties.FullDep = true
 				}
-				if thin && !dep.lto.ThinLTO() {
+				if !globalThinLTO && thin && !dep.lto.ThinLTO() {
 					dep.lto.Properties.ThinDep = true
 				}
+				if globalThinLTO && never && !dep.lto.Never() {
+					dep.lto.Properties.NoLtoDep = true
+				}
 			}
 
 			// Recursively walk static dependencies
@@ -200,6 +204,8 @@
 
 // Create lto variants for modules that need them
 func ltoMutator(mctx android.BottomUpMutatorContext) {
+	globalThinLTO := GlobalThinLTO(mctx)
+
 	if m, ok := mctx.Module().(*Module); ok && m.lto != nil {
 		// Create variations for LTO types required as static
 		// dependencies
@@ -207,18 +213,25 @@
 		if m.lto.Properties.FullDep && !m.lto.FullLTO() {
 			variationNames = append(variationNames, "lto-full")
 		}
-		if m.lto.Properties.ThinDep && !m.lto.ThinLTO() {
+		if !globalThinLTO && m.lto.Properties.ThinDep && !m.lto.ThinLTO() {
 			variationNames = append(variationNames, "lto-thin")
 		}
+		if globalThinLTO && m.lto.Properties.NoLtoDep && !m.lto.Never() {
+			variationNames = append(variationNames, "lto-none")
+		}
 
 		// Use correct dependencies if LTO property is explicitly set
 		// (mutually exclusive)
 		if m.lto.FullLTO() {
 			mctx.SetDependencyVariation("lto-full")
 		}
-		if m.lto.ThinLTO() {
+		if !globalThinLTO && m.lto.ThinLTO() {
 			mctx.SetDependencyVariation("lto-thin")
 		}
+		// Never must be the last, it overrides Thin or Full.
+		if globalThinLTO && m.lto.Never() {
+			mctx.SetDependencyVariation("lto-none")
+		}
 
 		if len(variationNames) > 1 {
 			modules := mctx.CreateVariations(variationNames...)
@@ -233,17 +246,19 @@
 
 				// LTO properties for dependencies
 				if name == "lto-full" {
-					variation.lto.Properties.Lto.Full = boolPtr(true)
-					variation.lto.Properties.Lto.Thin = boolPtr(false)
+					variation.lto.Properties.Lto.Full = proptools.BoolPtr(true)
 				}
 				if name == "lto-thin" {
-					variation.lto.Properties.Lto.Full = boolPtr(false)
-					variation.lto.Properties.Lto.Thin = boolPtr(true)
+					variation.lto.Properties.Lto.Thin = proptools.BoolPtr(true)
+				}
+				if name == "lto-none" {
+					variation.lto.Properties.Lto.Never = proptools.BoolPtr(true)
 				}
 				variation.Properties.PreventInstall = true
 				variation.Properties.HideFromMake = true
 				variation.lto.Properties.FullDep = false
 				variation.lto.Properties.ThinDep = false
+				variation.lto.Properties.NoLtoDep = false
 			}
 		}
 	}
diff --git a/cc/makevars.go b/cc/makevars.go
index da5f1fd..6752f8c 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -92,9 +92,10 @@
 	ctx.Strict("RS_LLVM_AS", "${config.RSLLVMPrebuiltsPath}/llvm-as")
 	ctx.Strict("RS_LLVM_LINK", "${config.RSLLVMPrebuiltsPath}/llvm-link")
 
-	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ClangExternalCflags}")
-	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideClangGlobalCflags}")
+	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ExternalCflags}")
+	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE", "")
+	ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
 
 	ctx.Strict("BOARD_VNDK_VERSION", ctx.DeviceConfig().VndkVersion())
 	ctx.Strict("RECOVERY_SNAPSHOT_VERSION", ctx.DeviceConfig().RecoverySnapshotVersion())
@@ -165,7 +166,7 @@
 	sort.Strings(ndkKnownLibs)
 	ctx.Strict("NDK_KNOWN_LIBS", strings.Join(ndkKnownLibs, " "))
 
-	hostTargets := ctx.Config().Targets[android.BuildOs]
+	hostTargets := ctx.Config().Targets[ctx.Config().BuildOS]
 	makeVarsToolchain(ctx, "", hostTargets[0])
 	if len(hostTargets) > 1 {
 		makeVarsToolchain(ctx, "2ND_", hostTargets[1])
@@ -212,13 +213,13 @@
 	ctx.StrictRaw(makePrefix+"C_SYSTEM_INCLUDES", strings.Join(systemIncludes, " "))
 
 	if target.Arch.ArchType == android.Arm {
-		flags, err := toolchain.ClangInstructionSetFlags("arm")
+		flags, err := toolchain.InstructionSetFlags("arm")
 		if err != nil {
 			panic(err)
 		}
 		ctx.Strict(makePrefix+"arm_CFLAGS", flags)
 
-		flags, err = toolchain.ClangInstructionSetFlags("thumb")
+		flags, err = toolchain.InstructionSetFlags("thumb")
 		if err != nil {
 			panic(err)
 		}
@@ -226,46 +227,71 @@
 	}
 
 	clangPrefix := secondPrefix + "CLANG_" + typePrefix
-	clangExtras := "-B" + config.ToolPath(toolchain)
 
 	ctx.Strict(clangPrefix+"TRIPLE", toolchain.ClangTriple())
 	ctx.Strict(clangPrefix+"GLOBAL_CFLAGS", strings.Join([]string{
-		toolchain.ClangCflags(),
-		"${config.CommonClangGlobalCflags}",
-		fmt.Sprintf("${config.%sClangGlobalCflags}", hod),
-		toolchain.ToolchainClangCflags(),
-		clangExtras,
+		toolchain.Cflags(),
+		"${config.CommonGlobalCflags}",
+		fmt.Sprintf("${config.%sGlobalCflags}", hod),
+		toolchain.ToolchainCflags(),
 		productExtraCflags,
 	}, " "))
 	ctx.Strict(clangPrefix+"GLOBAL_CPPFLAGS", strings.Join([]string{
-		"${config.CommonClangGlobalCppflags}",
+		"${config.CommonGlobalCppflags}",
 		fmt.Sprintf("${config.%sGlobalCppflags}", hod),
-		toolchain.ClangCppflags(),
+		toolchain.Cppflags(),
 	}, " "))
 	ctx.Strict(clangPrefix+"GLOBAL_LDFLAGS", strings.Join([]string{
 		fmt.Sprintf("${config.%sGlobalLdflags}", hod),
-		toolchain.ClangLdflags(),
-		toolchain.ToolchainClangLdflags(),
+		toolchain.Ldflags(),
+		toolchain.ToolchainLdflags(),
 		productExtraLdflags,
-		clangExtras,
 	}, " "))
 	ctx.Strict(clangPrefix+"GLOBAL_LLDFLAGS", strings.Join([]string{
 		fmt.Sprintf("${config.%sGlobalLldflags}", hod),
-		toolchain.ClangLldflags(),
-		toolchain.ToolchainClangLdflags(),
+		toolchain.Lldflags(),
+		toolchain.ToolchainLdflags(),
 		productExtraLdflags,
-		clangExtras,
 	}, " "))
 
 	if target.Os.Class == android.Device {
-		ctx.Strict(secondPrefix+"ADDRESS_SANITIZER_RUNTIME_LIBRARY", strings.TrimSuffix(config.AddressSanitizerRuntimeLibrary(toolchain), ".so"))
-		ctx.Strict(secondPrefix+"HWADDRESS_SANITIZER_RUNTIME_LIBRARY", strings.TrimSuffix(config.HWAddressSanitizerRuntimeLibrary(toolchain), ".so"))
-		ctx.Strict(secondPrefix+"HWADDRESS_SANITIZER_STATIC_LIBRARY", strings.TrimSuffix(config.HWAddressSanitizerStaticLibrary(toolchain), ".a"))
-		ctx.Strict(secondPrefix+"UBSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain), ".so"))
-		ctx.Strict(secondPrefix+"UBSAN_MINIMAL_RUNTIME_LIBRARY", strings.TrimSuffix(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain), ".a"))
-		ctx.Strict(secondPrefix+"TSAN_RUNTIME_LIBRARY", strings.TrimSuffix(config.ThreadSanitizerRuntimeLibrary(toolchain), ".so"))
-		ctx.Strict(secondPrefix+"SCUDO_RUNTIME_LIBRARY", strings.TrimSuffix(config.ScudoRuntimeLibrary(toolchain), ".so"))
-		ctx.Strict(secondPrefix+"SCUDO_MINIMAL_RUNTIME_LIBRARY", strings.TrimSuffix(config.ScudoMinimalRuntimeLibrary(toolchain), ".so"))
+		sanitizerVariables := map[string]string{
+			"ADDRESS_SANITIZER_RUNTIME_LIBRARY":   config.AddressSanitizerRuntimeLibrary(toolchain),
+			"HWADDRESS_SANITIZER_RUNTIME_LIBRARY": config.HWAddressSanitizerRuntimeLibrary(toolchain),
+			"HWADDRESS_SANITIZER_STATIC_LIBRARY":  config.HWAddressSanitizerStaticLibrary(toolchain),
+			"UBSAN_RUNTIME_LIBRARY":               config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain),
+			"UBSAN_MINIMAL_RUNTIME_LIBRARY":       config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain),
+			"TSAN_RUNTIME_LIBRARY":                config.ThreadSanitizerRuntimeLibrary(toolchain),
+			"SCUDO_RUNTIME_LIBRARY":               config.ScudoRuntimeLibrary(toolchain),
+			"SCUDO_MINIMAL_RUNTIME_LIBRARY":       config.ScudoMinimalRuntimeLibrary(toolchain),
+		}
+
+		for variable, value := range sanitizerVariables {
+			ctx.Strict(secondPrefix+variable, value)
+		}
+
+		sanitizerLibs := android.SortedStringValues(sanitizerVariables)
+		var sanitizerLibStems []string
+		ctx.VisitAllModules(func(m android.Module) {
+			if !m.Enabled() {
+				return
+			}
+
+			ccModule, _ := m.(*Module)
+			if ccModule == nil || ccModule.library == nil || !ccModule.library.shared() {
+				return
+			}
+
+			if android.InList(strings.TrimPrefix(ctx.ModuleName(m), "prebuilt_"), sanitizerLibs) &&
+				m.Target().Os == target.Os && m.Target().Arch.ArchType == target.Arch.ArchType {
+				outputFile := ccModule.outputFile
+				if outputFile.Valid() {
+					sanitizerLibStems = append(sanitizerLibStems, outputFile.Path().Base())
+				}
+			}
+		})
+		sanitizerLibStems = android.SortedUniqueStrings(sanitizerLibStems)
+		ctx.Strict(secondPrefix+"SANITIZER_STEMS", strings.Join(sanitizerLibStems, " "))
 	}
 
 	// This is used by external/gentoo/...
@@ -288,9 +314,7 @@
 		ctx.Strict(makePrefix+"OBJCOPY", "${config.ClangBin}/llvm-objcopy")
 		ctx.Strict(makePrefix+"LD", "${config.ClangBin}/lld")
 		ctx.Strict(makePrefix+"NDK_TRIPLE", config.NDKTriple(toolchain))
-		// TODO: work out whether to make this "${config.ClangBin}/llvm-", which
-		// should mostly work, or remove it.
-		ctx.Strict(makePrefix+"TOOLS_PREFIX", gccCmd(toolchain, ""))
+		ctx.Strict(makePrefix+"TOOLS_PREFIX", "${config.ClangBin}/llvm-")
 		// TODO: GCC version is obsolete now that GCC has been removed.
 		ctx.Strict(makePrefix+"GCC_VERSION", toolchain.GccVersion())
 	}
diff --git a/cc/native_bridge_sdk_trait.go b/cc/native_bridge_sdk_trait.go
new file mode 100644
index 0000000..1326d57
--- /dev/null
+++ b/cc/native_bridge_sdk_trait.go
@@ -0,0 +1,33 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import "android/soong/android"
+
+// This file contains support for the native bridge sdk trait.
+
+func init() {
+	android.RegisterSdkMemberTrait(nativeBridgeSdkTrait)
+}
+
+type nativeBridgeSdkTraitStruct struct {
+	android.SdkMemberTraitBase
+}
+
+var nativeBridgeSdkTrait android.SdkMemberTrait = &nativeBridgeSdkTraitStruct{
+	SdkMemberTraitBase: android.SdkMemberTraitBase{
+		PropertyName: "native_bridge_support",
+	},
+}
diff --git a/cc/ndk_abi.go b/cc/ndk_abi.go
new file mode 100644
index 0000000..3456c32
--- /dev/null
+++ b/cc/ndk_abi.go
@@ -0,0 +1,102 @@
+// 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"
+)
+
+func init() {
+	android.RegisterSingletonType("ndk_abi_dump", NdkAbiDumpSingleton)
+	android.RegisterSingletonType("ndk_abi_diff", NdkAbiDiffSingleton)
+}
+
+func getNdkAbiDumpInstallBase(ctx android.PathContext) android.OutputPath {
+	return android.PathForOutput(ctx).Join(ctx, "abi-dumps/ndk")
+}
+
+func getNdkAbiDumpTimestampFile(ctx android.PathContext) android.OutputPath {
+	return android.PathForOutput(ctx, "ndk_abi_dump.timestamp")
+}
+
+func NdkAbiDumpSingleton() android.Singleton {
+	return &ndkAbiDumpSingleton{}
+}
+
+type ndkAbiDumpSingleton struct{}
+
+func (n *ndkAbiDumpSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var depPaths android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		if !module.Enabled() {
+			return
+		}
+
+		if m, ok := module.(*Module); ok {
+			if installer, ok := m.installer.(*stubDecorator); ok {
+				if canDumpAbi(ctx.Config()) {
+					depPaths = append(depPaths, installer.abiDumpPath)
+				}
+			}
+		}
+	})
+
+	// `m dump-ndk-abi` will dump the NDK ABI.
+	// `development/tools/ndk/update_ndk_abi.sh` will dump the NDK ABI and
+	// update the golden copies in prebuilts/abi-dumps/ndk.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    getNdkAbiDumpTimestampFile(ctx),
+		Implicits: depPaths,
+	})
+
+	ctx.Phony("dump-ndk-abi", getNdkAbiDumpTimestampFile(ctx))
+}
+
+func getNdkAbiDiffTimestampFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForOutput(ctx, "ndk_abi_diff.timestamp")
+}
+
+func NdkAbiDiffSingleton() android.Singleton {
+	return &ndkAbiDiffSingleton{}
+}
+
+type ndkAbiDiffSingleton struct{}
+
+func (n *ndkAbiDiffSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var depPaths android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		if m, ok := module.(android.Module); ok && !m.Enabled() {
+			return
+		}
+
+		if m, ok := module.(*Module); ok {
+			if installer, ok := m.installer.(*stubDecorator); ok {
+				depPaths = append(depPaths, installer.abiDiffPaths...)
+			}
+		}
+	})
+
+	depPaths = append(depPaths, getNdkAbiDumpTimestampFile(ctx))
+
+	// `m diff-ndk-abi` will diff the NDK ABI.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    getNdkAbiDiffTimestampFile(ctx),
+		Implicits: depPaths,
+	})
+
+	ctx.Phony("diff-ndk-abi", getNdkAbiDiffTimestampFile(ctx))
+}
diff --git a/cc/ndk_api_coverage_parser/__init__.py b/cc/ndk_api_coverage_parser/__init__.py
index 7817c78..8b9cd66 100755
--- a/cc/ndk_api_coverage_parser/__init__.py
+++ b/cc/ndk_api_coverage_parser/__init__.py
@@ -21,7 +21,12 @@
 import sys
 
 from xml.etree.ElementTree import Element, SubElement, tostring
-from symbolfile import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser
+from symbolfile import (
+    ALL_ARCHITECTURES,
+    FUTURE_API_LEVEL,
+    MultiplyDefinedSymbolError,
+    SymbolFileParser,
+)
 
 
 ROOT_ELEMENT_TAG = 'ndk-library'
@@ -63,6 +68,7 @@
 
 class XmlGenerator(object):
     """Output generator that writes parsed symbol file to a xml file."""
+
     def __init__(self, output_file):
         self.output_file = output_file
 
@@ -74,10 +80,14 @@
                 continue
             version_attributes = parse_tags(version.tags)
             _, _, postfix = version.name.partition('_')
-            is_platform = postfix == 'PRIVATE' or postfix == 'PLATFORM'
+            is_platform = postfix in ('PRIVATE' , 'PLATFORM')
             is_deprecated = postfix == 'DEPRECATED'
-            version_attributes.update({PLATFORM_ATTRIBUTE_KEY: str(is_platform)})
-            version_attributes.update({DEPRECATED_ATTRIBUTE_KEY: str(is_deprecated)})
+            version_attributes.update(
+                {PLATFORM_ATTRIBUTE_KEY: str(is_platform)}
+            )
+            version_attributes.update(
+                {DEPRECATED_ATTRIBUTE_KEY: str(is_deprecated)}
+            )
             for symbol in version.symbols:
                 if VARIABLE_TAG in symbol.tags:
                     continue
@@ -103,13 +113,20 @@
     """Parses and returns command line arguments."""
     parser = argparse.ArgumentParser()
 
-    parser.add_argument('symbol_file', type=os.path.realpath, help='Path to symbol file.')
     parser.add_argument(
-        'output_file', type=os.path.realpath,
-        help='The output parsed api coverage file.')
+        'symbol_file', type=os.path.realpath, help='Path to symbol file.'
+    )
     parser.add_argument(
-        '--api-map', type=os.path.realpath, required=True,
-        help='Path to the API level map JSON file.')
+        'output_file',
+        type=os.path.realpath,
+        help='The output parsed api coverage file.',
+    )
+    parser.add_argument(
+        '--api-map',
+        type=os.path.realpath,
+        required=True,
+        help='Path to the API level map JSON file.',
+    )
     return parser.parse_args()
 
 
@@ -122,13 +139,15 @@
 
     with open(args.symbol_file) as symbol_file:
         try:
-            versions = SymbolFileParser(symbol_file, api_map, "", FUTURE_API_LEVEL,
-                                        True, True).parse()
+            versions = SymbolFileParser(
+                symbol_file, api_map, "", FUTURE_API_LEVEL, True, True
+            ).parse()
         except MultiplyDefinedSymbolError as ex:
             sys.exit('{}: error: {}'.format(args.symbol_file, ex))
 
     generator = XmlGenerator(args.output_file)
     generator.write(versions)
 
+
 if __name__ == '__main__':
     main()
diff --git a/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
index 3ec14c1..141059c 100644
--- a/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
+++ b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py
@@ -50,10 +50,12 @@
         return False
     return all(etree_equal(c1, c2) for c1, c2 in zip(elem1, elem2))
 
-
+# pylint: disable=line-too-long
 class ApiCoverageSymbolFileParserTest(unittest.TestCase):
     def test_parse(self):
-        input_file = io.StringIO(textwrap.dedent(u"""\
+        input_file = io.StringIO(
+            textwrap.dedent(
+                u"""\
             LIBLOG { # introduced-arm64=24 introduced-x86=24 introduced-x86_64=24
               global:
                 android_name_to_log_id; # apex llndk introduced=23
@@ -64,22 +66,28 @@
               local:
                 *;
             };
-            
+
             LIBLOG_PLATFORM {
                 android_fdtrack; # llndk
                 android_net; # introduced=23
             };
-            
+
             LIBLOG_FOO { # var
                 android_var;
             };
-        """))
-        parser = SymbolFileParser(input_file, {}, "", FUTURE_API_LEVEL, True, True)
+        """
+            )
+        )
+        parser = SymbolFileParser(
+            input_file, {}, "", FUTURE_API_LEVEL, True, True
+        )
         generator = nparser.XmlGenerator(io.StringIO())
         result = generator.convertToXml(parser.parse())
-        expected = fromstring('<ndk-library><symbol apex="True" arch="" introduced="23" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_name_to_log_id" /><symbol arch="arm" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_log_id_to_name" /><symbol arch="" introduced-arm64="24" introduced-x86="23" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_assert" /><symbol arch="" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_buf_write" /><symbol arch="" is_deprecated="False" is_platform="True" llndk="True" name="android_fdtrack" /><symbol arch="" introduced="23" is_deprecated="False" is_platform="True" name="android_net" /></ndk-library>')
+        expected = fromstring(
+            '<ndk-library><symbol apex="True" arch="" introduced="23" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_name_to_log_id" /><symbol arch="arm" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" llndk="True" name="android_log_id_to_name" /><symbol arch="" introduced-arm64="24" introduced-x86="23" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_assert" /><symbol arch="" introduced-arm64="24" introduced-x86="24" introduced-x86_64="24" is_deprecated="False" is_platform="False" name="__android_log_buf_write" /><symbol arch="" is_deprecated="False" is_platform="True" llndk="True" name="android_fdtrack" /><symbol arch="" introduced="23" is_deprecated="False" is_platform="True" name="android_net" /></ndk-library>'
+        )
         self.assertTrue(etree_equal(expected, result))
-
+# pylint: enable=line-too-long
 
 def main():
     suite = unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 95d8477..5ef41ea 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"path/filepath"
+	"runtime"
 	"strings"
 	"sync"
 
@@ -23,11 +25,14 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/cc/config"
 )
 
 func init() {
 	pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
-	pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser")
+	pctx.HostBinToolVariable("abidiff", "abidiff")
+	pctx.HostBinToolVariable("abitidy", "abitidy")
+	pctx.HostBinToolVariable("abidw", "abidw")
 }
 
 var (
@@ -38,17 +43,37 @@
 			CommandDeps: []string{"$ndkStubGenerator"},
 		}, "arch", "apiLevel", "apiMap", "flags")
 
-	parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule",
+	abidw = pctx.AndroidStaticRule("abidw",
 		blueprint.RuleParams{
-			Command:     "$ndk_api_coverage_parser $in $out --api-map $apiMap",
-			CommandDeps: []string{"$ndk_api_coverage_parser"},
-		}, "apiMap")
+			Command: "$abidw --type-id-style hash --no-corpus-path " +
+				"--no-show-locs --no-comp-dir-path -w $symbolList " +
+				"$in --out-file $out",
+			CommandDeps: []string{"$abidw"},
+		}, "symbolList")
+
+	abitidy = pctx.AndroidStaticRule("abitidy",
+		blueprint.RuleParams{
+			Command:     "$abitidy --all $flags -i $in -o $out",
+			CommandDeps: []string{"$abitidy"},
+		}, "flags")
+
+	abidiff = pctx.AndroidStaticRule("abidiff",
+		blueprint.RuleParams{
+			// Need to create *some* output for ninja. We don't want to use tee
+			// because we don't want to spam the build output with "nothing
+			// changed" messages, so redirect output message to $out, and if
+			// changes were detected print the output and fail.
+			Command:     "$abidiff $args $in > $out || (cat $out && false)",
+			CommandDeps: []string{"$abidiff"},
+		}, "args")
 
 	ndkLibrarySuffix = ".ndk"
 
 	ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
 	// protects ndkKnownLibs writes during parallel BeginMutator.
 	ndkKnownLibsLock sync.Mutex
+
+	stubImplementation = dependencyTag{name: "stubImplementation"}
 )
 
 // The First_version and Unversioned_until properties of this struct should not
@@ -79,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 {
@@ -89,6 +120,8 @@
 	versionScriptPath     android.ModuleGenPath
 	parsedCoverageXmlPath android.ModuleOutPath
 	installPath           android.Path
+	abiDumpPath           android.OutputPath
+	abiDiffPaths          android.Paths
 
 	apiLevel         android.ApiLevel
 	firstVersion     android.ApiLevel
@@ -123,6 +156,10 @@
 	if !ctx.Module().Enabled() {
 		return nil
 	}
+	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+		ctx.Module().Disable()
+		return nil
+	}
 	firstVersion, err := nativeApiLevelFromUser(ctx,
 		String(this.properties.First_version))
 	if err != nil {
@@ -178,20 +215,26 @@
 	*ndkKnownLibs = append(*ndkKnownLibs, name)
 }
 
+var stubLibraryCompilerFlags = []string{
+	// We're knowingly doing some otherwise unsightly things with builtin
+	// functions here. We're just generating stub libraries, so ignore it.
+	"-Wno-incompatible-library-redeclaration",
+	"-Wno-incomplete-setjmp-declaration",
+	"-Wno-builtin-requires-header",
+	"-Wno-invalid-noreturn",
+	"-Wall",
+	"-Werror",
+	// These libraries aren't actually used. Don't worry about unwinding
+	// (avoids the need to link an unwinder into a fake library).
+	"-fno-unwind-tables",
+}
+
+func init() {
+	config.ExportStringList("StubLibraryCompilerFlags", stubLibraryCompilerFlags)
+}
+
 func addStubLibraryCompilerFlags(flags Flags) Flags {
-	flags.Global.CFlags = append(flags.Global.CFlags,
-		// We're knowingly doing some otherwise unsightly things with builtin
-		// functions here. We're just generating stub libraries, so ignore it.
-		"-Wno-incompatible-library-redeclaration",
-		"-Wno-incomplete-setjmp-declaration",
-		"-Wno-builtin-requires-header",
-		"-Wno-invalid-noreturn",
-		"-Wall",
-		"-Werror",
-		// These libraries aren't actually used. Don't worry about unwinding
-		// (avoids the need to link an unwinder into a fake library).
-		"-fno-unwind-tables",
-	)
+	flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...)
 	// All symbols in the stubs library should be visible.
 	if inList("-fvisibility=hidden", flags.Local.CFlags) {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default")
@@ -204,48 +247,206 @@
 	return addStubLibraryCompilerFlags(flags)
 }
 
-func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) {
-	arch := ctx.Arch().ArchType.String()
+type ndkApiOutputs struct {
+	stubSrc       android.ModuleGenPath
+	versionScript android.ModuleGenPath
+	symbolList    android.ModuleGenPath
+}
+
+func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string,
+	apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs {
 
 	stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
 	versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
 	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
+	symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt")
 	apiLevelsJson := android.GetApiLevelsJson(ctx)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        genStubSrc,
 		Description: "generate stubs " + symbolFilePath.Rel(),
-		Outputs:     []android.WritablePath{stubSrcPath, versionScriptPath},
-		Input:       symbolFilePath,
-		Implicits:   []android.Path{apiLevelsJson},
+		Outputs: []android.WritablePath{stubSrcPath, versionScriptPath,
+			symbolListPath},
+		Input:     symbolFilePath,
+		Implicits: []android.Path{apiLevelsJson},
 		Args: map[string]string{
-			"arch":     arch,
-			"apiLevel": apiLevel,
+			"arch":     ctx.Arch().ArchType.String(),
+			"apiLevel": apiLevel.String(),
 			"apiMap":   apiLevelsJson.String(),
 			"flags":    genstubFlags,
 		},
 	})
 
-	subdir := ""
-	srcs := []android.Path{stubSrcPath}
-	return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath
+	return ndkApiOutputs{
+		stubSrc:       stubSrcPath,
+		versionScript: versionScriptPath,
+		symbolList:    symbolListPath,
+	}
 }
 
-func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath {
-	apiLevelsJson := android.GetApiLevelsJson(ctx)
-	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
-	outputFileName := strings.Split(symbolFilePath.Base(), ".")[0]
-	parsedApiCoveragePath := android.PathForModuleOut(ctx, outputFileName+".xml")
+func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
+	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
+		android.Paths{src}, nil, nil, nil, nil)
+}
+
+func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
+	dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix),
+		stubImplementation)
+	if dep == nil {
+		ctx.ModuleErrorf("Could not find implementation for stub")
+		return nil
+	}
+	impl, ok := dep.(*Module)
+	if !ok {
+		ctx.ModuleErrorf("Implementation for stub is not correct module type")
+	}
+	output := impl.UnstrippedOutputFile()
+	if output == nil {
+		ctx.ModuleErrorf("implementation module (%s) has no output", impl)
+		return nil
+	}
+
+	return output
+}
+
+func (this *stubDecorator) libraryName(ctx ModuleContext) string {
+	return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix)
+}
+
+func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext,
+	apiLevel android.ApiLevel) android.OptionalPath {
+
+	subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(),
+		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml")
+	return android.ExistentPathForSource(ctx, subpath)
+}
+
+// Feature flag.
+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.
+func canDiffAbi() bool {
+	return false
+}
+
+func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
+	implementationLibrary := this.findImplementationLibrary(ctx)
+	abiRawPath := getNdkAbiDumpInstallBase(ctx).Join(ctx,
+		this.apiLevel.String(), ctx.Arch().ArchType.String(),
+		this.libraryName(ctx), "abi.raw.xml")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        parseNdkApiRule,
-		Description: "parse ndk api symbol file for api coverage: " + symbolFilePath.Rel(),
-		Outputs:     []android.WritablePath{parsedApiCoveragePath},
-		Input:       symbolFilePath,
-		Implicits:   []android.Path{apiLevelsJson},
+		Rule:        abidw,
+		Description: fmt.Sprintf("abidw %s", implementationLibrary),
+		Input:       implementationLibrary,
+		Output:      abiRawPath,
+		Implicit:    symbolList,
 		Args: map[string]string{
-			"apiMap": apiLevelsJson.String(),
+			"symbolList": symbolList.String(),
 		},
 	})
-	return parsedApiCoveragePath
+
+	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,
+		},
+	})
+}
+
+func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel {
+	apiLevels := append(ctx.Config().AllSupportedApiLevels(),
+		android.FutureApiLevel)
+	for _, api := range apiLevels {
+		if api.GreaterThan(apiLevel) {
+			return &api
+		}
+	}
+	return nil
+}
+
+func (this *stubDecorator) diffAbi(ctx ModuleContext) {
+	missingPrebuiltError := fmt.Sprintf(
+		"Did not find prebuilt ABI dump for %q. Generate with "+
+			"//development/tools/ndk/update_ndk_abi.sh.", this.libraryName(ctx))
+
+	// Catch any ABI changes compared to the checked-in definition of this API
+	// level.
+	abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp")
+	prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel)
+	if !prebuiltAbiDump.Valid() {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.ErrorRule,
+			Output: abiDiffPath,
+			Args: map[string]string{
+				"error": missingPrebuiltError,
+			},
+		})
+	} else {
+		ctx.Build(pctx, android.BuildParams{
+			Rule: abidiff,
+			Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump,
+				this.abiDumpPath),
+			Output: abiDiffPath,
+			Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath},
+		})
+	}
+	this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath)
+
+	// Also ensure that the ABI of the next API level (if there is one) matches
+	// this API level. *New* ABI is allowed, but any changes to APIs that exist
+	// in this API level are disallowed.
+	if !this.apiLevel.IsCurrent() {
+		nextApiLevel := findNextApiLevel(ctx, this.apiLevel)
+		if nextApiLevel == nil {
+			panic(fmt.Errorf("could not determine which API level follows "+
+				"non-current API level %s", this.apiLevel))
+		}
+		nextAbiDiffPath := android.PathForModuleOut(ctx,
+			"abidiff_next.timestamp")
+		nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel)
+		if !nextAbiDump.Valid() {
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.ErrorRule,
+				Output: nextAbiDiffPath,
+				Args: map[string]string{
+					"error": missingPrebuiltError,
+				},
+			})
+		} else {
+			ctx.Build(pctx, android.BuildParams{
+				Rule: abidiff,
+				Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath,
+					nextAbiDump),
+				Output: nextAbiDiffPath,
+				Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()},
+				Args: map[string]string{
+					"args": "--no-added-syms",
+				},
+			})
+		}
+		this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
+	}
 }
 
 func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
@@ -264,11 +465,17 @@
 	}
 
 	symbolFile := String(c.properties.Symbol_file)
-	objs, versionScript := compileStubLibrary(ctx, flags, symbolFile,
-		c.apiLevel.String(), "")
-	c.versionScriptPath = versionScript
+	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
+	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+	c.versionScriptPath = nativeAbiResult.versionScript
+	if canDumpAbi(ctx.Config()) {
+		c.dumpAbi(ctx, nativeAbiResult.symbolList)
+		if canDiffAbi() {
+			c.diffAbi(ctx)
+		}
+	}
 	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
-		c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile)
+		c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
 	}
 	return objs
 }
diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go
index b91c737..51ec6b8 100644
--- a/cc/ndk_prebuilt.go
+++ b/cc/ndk_prebuilt.go
@@ -186,9 +186,8 @@
 		})
 	} else {
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           lib,
-			UnstrippedSharedLibrary: lib,
-			Target:                  ctx.Target(),
+			SharedLibrary: lib,
+			Target:        ctx.Target(),
 		})
 	}
 
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index d8c500e..6c200f5 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -33,7 +33,7 @@
 // Component 3: Stub Libraries
 // The shared libraries in the NDK are not the actual shared libraries they
 // refer to (to prevent people from accidentally loading them), but stub
-// libraries with dummy implementations of everything for use at build time
+// libraries with placeholder implementations of everything for use at build time
 // only.
 //
 // Since we don't actually need to know anything about the stub libraries aside
@@ -82,6 +82,12 @@
 	return android.PathForOutput(ctx, "ndk_base.timestamp")
 }
 
+// The headers timestamp file depends only on the NDK headers.
+// This is used mainly for .tidy files that do not need any stub libraries.
+func getNdkHeadersTimestampFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForOutput(ctx, "ndk_headers.timestamp")
+}
+
 // The full timestamp file depends on the base timestamp *and* the static
 // libraries.
 func getNdkFullTimestampFile(ctx android.PathContext) android.WritablePath {
@@ -96,6 +102,7 @@
 
 func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var staticLibInstallPaths android.Paths
+	var headerPaths android.Paths
 	var installPaths android.Paths
 	var licensePaths android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
@@ -104,16 +111,19 @@
 		}
 
 		if m, ok := module.(*headerModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*versionedHeaderModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
 
 		if m, ok := module.(*preprocessedHeadersModule); ok {
+			headerPaths = append(headerPaths, m.installPaths...)
 			installPaths = append(installPaths, m.installPaths...)
 			licensePaths = append(licensePaths, m.licensePath)
 		}
@@ -146,16 +156,26 @@
 
 	baseDepPaths := append(installPaths, combinedLicense)
 
-	// There's a dummy "ndk" rule defined in ndk/Android.mk that depends on
-	// this. `m ndk` will build the sysroots.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:       android.Touch,
+		Output:     getNdkBaseTimestampFile(ctx),
+		Implicits:  baseDepPaths,
+		Validation: getNdkAbiDiffTimestampFile(ctx),
+	})
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:      android.Touch,
-		Output:    getNdkBaseTimestampFile(ctx),
-		Implicits: baseDepPaths,
+		Output:    getNdkHeadersTimestampFile(ctx),
+		Implicits: headerPaths,
 	})
 
 	fullDepPaths := append(staticLibInstallPaths, getNdkBaseTimestampFile(ctx))
 
+	// There's a phony "ndk" rule defined in core/main.mk that depends on this.
+	// `m ndk` will build the sysroots for the architectures in the current
+	// lunch target. `build/soong/scripts/build-ndk-prebuilts.sh` will build the
+	// sysroots for all the NDK architectures and package them so they can be
+	// imported into the NDK's build.
 	ctx.Build(pctx, android.BuildParams{
 		Rule:      android.Touch,
 		Output:    getNdkFullTimestampFile(ctx),
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
index 86bf6ff..5e6b8f5 100755
--- a/cc/ndkstubgen/__init__.py
+++ b/cc/ndkstubgen/__init__.py
@@ -18,7 +18,7 @@
 import argparse
 import json
 import logging
-import os
+from pathlib import Path
 import sys
 from typing import Iterable, TextIO
 
@@ -28,10 +28,12 @@
 
 class Generator:
     """Output generator that writes stub source files and version scripts."""
-    def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch,
-                 api: int, llndk: bool, apex: bool) -> None:
+    def __init__(self, src_file: TextIO, version_script: TextIO,
+                 symbol_list: TextIO, arch: Arch, api: int, llndk: bool,
+                 apex: bool) -> None:
         self.src_file = src_file
         self.version_script = version_script
+        self.symbol_list = symbol_list
         self.arch = arch
         self.api = api
         self.llndk = llndk
@@ -39,6 +41,7 @@
 
     def write(self, versions: Iterable[Version]) -> None:
         """Writes all symbol data to the output files."""
+        self.symbol_list.write('[abi_symbol_list]\n')
         for version in versions:
             self.write_version(version)
 
@@ -76,11 +79,11 @@
                     weak = '__attribute__((weak)) '
 
                 if 'var' in symbol.tags:
-                    self.src_file.write('{}int {} = 0;\n'.format(
-                        weak, symbol.name))
+                    self.src_file.write(f'{weak}int {symbol.name} = 0;\n')
                 else:
-                    self.src_file.write('{}void {}() {{}}\n'.format(
-                        weak, symbol.name))
+                    self.src_file.write(f'{weak}void {symbol.name}() {{}}\n')
+
+                self.symbol_list.write(f'{symbol.name}\n')
 
             if not version_empty and section_versioned:
                 base = '' if version.base is None else ' ' + version.base
@@ -91,6 +94,10 @@
     """Parses and returns command line arguments."""
     parser = argparse.ArgumentParser()
 
+    def resolved_path(raw: str) -> Path:
+        """Returns a resolved Path for the given string."""
+        return Path(raw).resolve()
+
     parser.add_argument('-v', '--verbose', action='count', default=0)
 
     parser.add_argument(
@@ -101,28 +108,32 @@
     parser.add_argument(
         '--llndk', action='store_true', help='Use the LLNDK variant.')
     parser.add_argument(
-        '--apex', action='store_true', help='Use the APEX variant.')
+        '--apex',
+        action='store_true',
+        help='Use the APEX variant. Note: equivalent to --system-api.')
+    parser.add_argument(
+        '--system-api',
+        action='store_true',
+        dest='apex',
+        help='Use the SystemAPI variant. Note: equivalent to --apex.')
 
-    # https://github.com/python/mypy/issues/1317
-    # mypy has issues with using os.path.realpath as an argument here.
-    parser.add_argument(
-        '--api-map',
-        type=os.path.realpath,  # type: ignore
-        required=True,
-        help='Path to the API level map JSON file.')
+    parser.add_argument('--api-map',
+                        type=resolved_path,
+                        required=True,
+                        help='Path to the API level map JSON file.')
 
-    parser.add_argument(
-        'symbol_file',
-        type=os.path.realpath,  # type: ignore
-        help='Path to symbol file.')
-    parser.add_argument(
-        'stub_src',
-        type=os.path.realpath,  # type: ignore
-        help='Path to output stub source file.')
-    parser.add_argument(
-        'version_script',
-        type=os.path.realpath,  # type: ignore
-        help='Path to output version script.')
+    parser.add_argument('symbol_file',
+                        type=resolved_path,
+                        help='Path to symbol file.')
+    parser.add_argument('stub_src',
+                        type=resolved_path,
+                        help='Path to output stub source file.')
+    parser.add_argument('version_script',
+                        type=resolved_path,
+                        help='Path to output version script.')
+    parser.add_argument('symbol_list',
+                        type=resolved_path,
+                        help='Path to output abigail symbol list.')
 
     return parser.parse_args()
 
@@ -131,7 +142,7 @@
     """Program entry point."""
     args = parse_args()
 
-    with open(args.api_map) as map_file:
+    with args.api_map.open() as map_file:
         api_map = json.load(map_file)
     api = symbolfile.decode_api_level(args.api, api_map)
 
@@ -141,19 +152,20 @@
         verbosity = 2
     logging.basicConfig(level=verbose_map[verbosity])
 
-    with open(args.symbol_file) as symbol_file:
+    with args.symbol_file.open() as symbol_file:
         try:
             versions = symbolfile.SymbolFileParser(symbol_file, api_map,
                                                    args.arch, api, args.llndk,
                                                    args.apex).parse()
         except symbolfile.MultiplyDefinedSymbolError as ex:
-            sys.exit('{}: error: {}'.format(args.symbol_file, ex))
+            sys.exit(f'{args.symbol_file}: error: {ex}')
 
-    with open(args.stub_src, 'w') as src_file:
-        with open(args.version_script, 'w') as version_file:
-            generator = Generator(src_file, version_file, args.arch, api,
-                                  args.llndk, args.apex)
-            generator.write(versions)
+    with args.stub_src.open('w') as src_file:
+        with args.version_script.open('w') as version_script:
+            with args.symbol_list.open('w') as symbol_list:
+                generator = Generator(src_file, version_script, symbol_list,
+                                      args.arch, api, args.llndk, args.apex)
+                generator.write(versions)
 
 
 if __name__ == '__main__':
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 6d2c9d6..c8cd056 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -19,9 +19,10 @@
 import textwrap
 import unittest
 
-import ndkstubgen
 import symbolfile
-from symbolfile import Arch, Tag
+from symbolfile import Arch, Tags
+
+import ndkstubgen
 
 
 # pylint: disable=missing-docstring
@@ -33,26 +34,30 @@
         # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
-        version = symbolfile.Version('VERSION_PRIVATE', None, [], [
-            symbolfile.Symbol('foo', []),
+        version = symbolfile.Version('VERSION_PRIVATE', None, Tags(), [
+            symbolfile.Symbol('foo', Tags()),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, [Tag('x86')], [
-            symbolfile.Symbol('foo', []),
-        ])
+        version = symbolfile.Version('VERSION', None, Tags.from_strs(['x86']),
+                                     [
+                                         symbolfile.Symbol('foo', Tags()),
+                                     ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [
-            symbolfile.Symbol('foo', []),
-        ])
+        version = symbolfile.Version('VERSION', None,
+                                     Tags.from_strs(['introduced=14']), [
+                                         symbolfile.Symbol('foo', Tags()),
+                                     ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
@@ -62,32 +67,34 @@
         # SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('x86')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['x86'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('introduced=14')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('llndk')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('apex')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
@@ -96,22 +103,23 @@
     def test_write(self) -> None:
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
         versions = [
-            symbolfile.Version('VERSION_1', None, [], [
-                symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', [Tag('var')]),
-                symbolfile.Symbol('woodly', [Tag('weak')]),
-                symbolfile.Symbol('doodly',
-                                  [Tag('weak'), Tag('var')]),
+            symbolfile.Version('VERSION_1', None, Tags(), [
+                symbolfile.Symbol('foo', Tags()),
+                symbolfile.Symbol('bar', Tags.from_strs(['var'])),
+                symbolfile.Symbol('woodly', Tags.from_strs(['weak'])),
+                symbolfile.Symbol('doodly', Tags.from_strs(['weak', 'var'])),
             ]),
-            symbolfile.Version('VERSION_2', 'VERSION_1', [], [
-                symbolfile.Symbol('baz', []),
+            symbolfile.Version('VERSION_2', 'VERSION_1', Tags(), [
+                symbolfile.Symbol('baz', Tags()),
             ]),
-            symbolfile.Version('VERSION_3', 'VERSION_1', [], [
-                symbolfile.Symbol('qux', [Tag('versioned=14')]),
+            symbolfile.Version('VERSION_3', 'VERSION_1', Tags(), [
+                symbolfile.Symbol('qux', Tags.from_strs(['versioned=14'])),
             ]),
         ]
 
@@ -141,6 +149,17 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            bar
+            woodly
+            doodly
+            baz
+            qux
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
 
 class IntegrationTest(unittest.TestCase):
     def test_integration(self) -> None:
@@ -186,8 +205,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -215,6 +236,16 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            baz
+            qux
+            wibble
+            wobble
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
     def test_integration_future_api(self) -> None:
         api_map = {
             'O': 9000,
@@ -238,8 +269,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9001, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9001, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -257,6 +290,13 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            bar
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
     def test_multiple_definition(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
@@ -336,8 +376,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, True)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, True)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -371,6 +413,40 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+    def test_empty_stub(self) -> None:
+        """Tests that empty stubs can be generated.
+
+        This is not a common case, but libraries whose only behavior is to
+        interpose symbols to alter existing behavior do not need to expose
+        their interposing symbols as API, so it's possible for the stub to be
+        empty while still needing a stub to link against. libsigchain is an
+        example of this.
+        """
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 {
+                local:
+                    *;
+            };
+        """))
+        parser = symbolfile.SymbolFileParser(input_file, {}, Arch('arm'),
+                                             9, llndk=False, apex=True)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file,
+                                         symbol_list_file,
+                                         Arch('arm'),
+                                         9,
+                                         llndk=False,
+                                         apex=True)
+        generator.write(versions)
+
+        self.assertEqual('', src_file.getvalue())
+        self.assertEqual('', version_file.getvalue())
+
 
 def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/cc/object.go b/cc/object.go
index d8f1aba..bd5bd45 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -29,7 +29,6 @@
 	android.RegisterModuleType("cc_object", ObjectFactory)
 	android.RegisterSdkMemberType(ccObjectSdkMemberType)
 
-	android.RegisterBp2BuildMutator("cc_object", ObjectBp2Build)
 }
 
 var ccObjectSdkMemberType = &librarySdkMemberType{
@@ -47,14 +46,14 @@
 }
 
 type objectBazelHandler struct {
-	bazelHandler
+	android.BazelHandler
 
 	module *Module
 }
 
-func (handler *objectBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (handler *objectBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	objPaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
+	objPaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
 	if ok {
 		if len(objPaths) != 1 {
 			ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
@@ -67,9 +66,19 @@
 }
 
 type ObjectLinkerProperties struct {
+	// list of static library modules that should only provide headers for this module.
+	Static_libs []string `android:"arch_variant,variant_prepend"`
+
+	// list of shared library modules should only provide headers for this module.
+	Shared_libs []string `android:"arch_variant"`
+
 	// list of modules that should only provide headers for this module.
 	Header_libs []string `android:"arch_variant,variant_prepend"`
 
+	// list of default libraries that will provide headers for this module.  If unset, generally
+	// defaults to libc, libm, and libdl.  Set to [] to prevent using headers from the defaults.
+	System_shared_libs []string `android:"arch_variant"`
+
 	// names of other cc_object modules to link into this module using partial linking
 	Objs []string `android:"arch_variant"`
 
@@ -84,8 +93,8 @@
 	Crt *bool
 }
 
-func newObject() *Module {
-	module := newBaseModule(android.HostAndDeviceSupported, android.MultilibBoth)
+func newObject(hod android.HostOrDeviceSupported) *Module {
+	module := newBaseModule(hod, android.MultilibBoth)
 	module.sanitize = &sanitize{}
 	module.stl = &stl{}
 	return module
@@ -95,7 +104,7 @@
 // necessary, but sometimes used to generate .s files from .c files to use as
 // input to a cc_genrule module.
 func ObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.HostAndDeviceSupported)
 	module.linker = &objectLinker{
 		baseLinker: NewBaseLinker(module.sanitize),
 	}
@@ -107,96 +116,91 @@
 
 	module.sdkMemberTypes = []android.SdkMemberType{ccObjectSdkMemberType}
 
+	module.bazelable = true
 	return module.Init()
 }
 
 // For bp2build conversion.
 type bazelObjectAttributes struct {
-	Srcs    bazel.LabelListAttribute
-	Hdrs    bazel.LabelListAttribute
-	Deps    bazel.LabelListAttribute
-	Copts   bazel.StringListAttribute
-	Asflags []string
+	Srcs                bazel.LabelListAttribute
+	Srcs_as             bazel.LabelListAttribute
+	Hdrs                bazel.LabelListAttribute
+	Deps                bazel.LabelListAttribute
+	System_dynamic_deps bazel.LabelListAttribute
+	Copts               bazel.StringListAttribute
+	Asflags             bazel.StringListAttribute
+	Local_includes      bazel.StringListAttribute
+	Absolute_includes   bazel.StringListAttribute
+	Stl                 *string
+	Linker_script       bazel.LabelAttribute
+	sdkAttributes
 }
 
-type bazelObject struct {
-	android.BazelTargetModuleBase
-	bazelObjectAttributes
-}
-
-func (m *bazelObject) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelObject) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-func BazelObjectFactory() android.Module {
-	module := &bazelObject{}
-	module.AddProperties(&module.bazelObjectAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-// ObjectBp2Build is the bp2build converter from cc_object modules to the
+// objectBp2Build is the bp2build converter from cc_object modules to the
 // Bazel equivalent target, plus any necessary include deps for the cc_object.
-func ObjectBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	// a Module can be something other than a cc_object.
-	if ctx.ModuleType() != "cc_object" {
-		return
-	}
-
+func objectBp2Build(ctx android.TopDownMutatorContext, m *Module) {
 	if m.compiler == nil {
 		// a cc_object must have access to the compiler decorator for its props.
 		ctx.ModuleErrorf("compiler must not be nil for a cc_object module")
 	}
 
 	// Set arch-specific configurable attributes
-	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	var asFlags []string
-
+	baseAttributes := bp2BuildParseBaseProps(ctx, m)
+	compilerAttrs := baseAttributes.compilerAttributes
 	var deps bazel.LabelListAttribute
-	for _, props := range m.linker.linkerProps() {
-		if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
-			deps = bazel.MakeLabelListAttribute(
-				android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
-		}
-	}
+	systemDynamicDeps := bazel.LabelListAttribute{ForceSpecifyEmptyList: true}
 
-	productVariableProps := android.ProductVariableProperties(ctx)
-	if props, exists := productVariableProps["Asflags"]; exists {
-		// TODO(b/183595873): consider deduplicating handling of product variable properties
-		for _, prop := range props {
-			flags, ok := prop.Property.([]string)
-			if !ok {
-				ctx.ModuleErrorf("Could not convert product variable asflag property")
-				return
-			}
-			// TODO(b/183595873) handle other product variable usages -- as selects?
-			if newFlags, subbed := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable); subbed {
-				asFlags = append(asFlags, newFlags...)
+	var linkerScript bazel.LabelAttribute
+
+	for axis, configToProps := range m.GetArchVariantProperties(ctx, &ObjectLinkerProperties{}) {
+		for config, props := range configToProps {
+			if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
+				if objectLinkerProps.Linker_script != nil {
+					label := android.BazelLabelForModuleSrcSingle(ctx, *objectLinkerProps.Linker_script)
+					linkerScript.SetSelectValue(axis, config, label)
+				}
+				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
+				systemSharedLibs := objectLinkerProps.System_shared_libs
+				if len(systemSharedLibs) > 0 {
+					systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
+				}
+				systemDynamicDeps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, systemSharedLibs))
 			}
 		}
 	}
-	// TODO(b/183595872) warn/error if we're not handling product variables
+	deps.ResolveExcludes()
+
+	// Don't split cc_object srcs across languages. Doing so would add complexity,
+	// and this isn't typically done for cc_object.
+	srcs := compilerAttrs.srcs
+	srcs.Append(compilerAttrs.cSrcs)
+
+	asFlags := compilerAttrs.asFlags
+	if compilerAttrs.asSrcs.IsEmpty() {
+		// Skip asflags for BUILD file simplicity if there are no assembly sources.
+		asFlags = bazel.MakeStringListAttribute(nil)
+	}
 
 	attrs := &bazelObjectAttributes{
-		Srcs:    compilerAttrs.srcs,
-		Deps:    deps,
-		Copts:   compilerAttrs.copts,
-		Asflags: asFlags,
+		Srcs:                srcs,
+		Srcs_as:             compilerAttrs.asSrcs,
+		Deps:                deps,
+		System_dynamic_deps: systemDynamicDeps,
+		Copts:               compilerAttrs.copts,
+		Asflags:             asFlags,
+		Local_includes:      compilerAttrs.localIncludes,
+		Absolute_includes:   compilerAttrs.absoluteIncludes,
+		Stl:                 compilerAttrs.stl,
+		Linker_script:       linkerScript,
+		sdkAttributes:       bp2BuildParseSdkAttributes(m),
 	}
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "cc_object",
-		Bzl_load_location: "//build/bazel/rules:cc_object.bzl",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_object.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(BazelObjectFactory, m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 func (object *objectLinker) appendLdflags(flags []string) {
@@ -211,12 +215,23 @@
 
 func (object *objectLinker) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.HeaderLibs = append(deps.HeaderLibs, object.Properties.Header_libs...)
+	deps.SharedLibs = append(deps.SharedLibs, object.Properties.Shared_libs...)
+	deps.StaticLibs = append(deps.StaticLibs, object.Properties.Static_libs...)
 	deps.ObjFiles = append(deps.ObjFiles, object.Properties.Objs...)
+
+	deps.SystemSharedLibs = object.Properties.System_shared_libs
+	if deps.SystemSharedLibs == nil {
+		// Provide a default set of shared libraries if system_shared_libs is unspecified.
+		// Note: If an empty list [] is specified, it implies that the module declines the
+		// default shared libraries.
+		deps.SystemSharedLibs = append(deps.SystemSharedLibs, ctx.toolchain().DefaultSharedLibraries()...)
+	}
+	deps.LateSharedLibs = append(deps.LateSharedLibs, deps.SystemSharedLibs...)
 	return deps
 }
 
 func (object *objectLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.Global.LdFlags = append(flags.Global.LdFlags, ctx.toolchain().ToolchainClangLdflags())
+	flags.Global.LdFlags = append(flags.Global.LdFlags, ctx.toolchain().ToolchainLdflags())
 
 	if lds := android.OptionalPathForModuleSrc(ctx, object.Properties.Linker_script); lds.Valid() {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-T,"+lds.String())
@@ -260,6 +275,20 @@
 	return outputFile
 }
 
+func (object *objectLinker) linkerSpecifiedDeps(specifiedDeps specifiedDeps) specifiedDeps {
+	specifiedDeps.sharedLibs = append(specifiedDeps.sharedLibs, object.Properties.Shared_libs...)
+
+	// Must distinguish nil and [] in system_shared_libs - ensure that [] in
+	// either input list doesn't come out as nil.
+	if specifiedDeps.systemSharedLibs == nil {
+		specifiedDeps.systemSharedLibs = object.Properties.System_shared_libs
+	} else {
+		specifiedDeps.systemSharedLibs = append(specifiedDeps.systemSharedLibs, object.Properties.System_shared_libs...)
+	}
+
+	return specifiedDeps
+}
+
 func (object *objectLinker) unstrippedOutputFilePath() android.Path {
 	return nil
 }
diff --git a/cc/object_test.go b/cc/object_test.go
index 0e5508a..259a892 100644
--- a/cc/object_test.go
+++ b/cc/object_test.go
@@ -15,8 +15,9 @@
 package cc
 
 import (
-	"android/soong/android"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestMinSdkVersionsOfCrtObjects(t *testing.T) {
@@ -27,24 +28,23 @@
 			crt: true,
 			stl: "none",
 			min_sdk_version: "28",
-
+			vendor_available: true,
 		}`)
 
-	arch := "android_arm64_armv8-a"
-	for _, v := range []string{"", "28", "29", "30", "current"} {
-		var variant string
-		// platform variant
-		if v == "" {
-			variant = arch
-		} else {
-			variant = arch + "_sdk_" + v
-		}
-		cflags := ctx.ModuleForTests("crt_foo", variant).Rule("cc").Args["cFlags"]
-		vNum := v
-		if v == "current" || v == "" {
-			vNum = "10000"
-		}
-		expected := "-target aarch64-linux-android" + vNum + " "
+	variants := []struct {
+		variant string
+		num     string
+	}{
+		{"android_arm64_armv8-a", "10000"},
+		{"android_arm64_armv8-a_sdk_28", "28"},
+		{"android_arm64_armv8-a_sdk_29", "29"},
+		{"android_arm64_armv8-a_sdk_30", "30"},
+		{"android_arm64_armv8-a_sdk_current", "10000"},
+		{"android_vendor.29_arm64_armv8-a", "29"},
+	}
+	for _, v := range variants {
+		cflags := ctx.ModuleForTests("crt_foo", v.variant).Rule("cc").Args["cFlags"]
+		expected := "-target aarch64-linux-android" + v.num + " "
 		android.AssertStringDoesContain(t, "cflag", cflags, expected)
 	}
 }
diff --git a/cc/pgo.go b/cc/pgo.go
index e78549e..0632c15 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -32,8 +32,8 @@
 	}
 
 	globalPgoProfileProjects = []string{
-		"toolchain/pgo-profiles",
-		"vendor/google_data/pgo_profile",
+		"toolchain/pgo-profiles/pgo",
+		"vendor/google_data/pgo_profile/pgo",
 	}
 )
 
@@ -96,10 +96,6 @@
 	flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrumentFlag)
 	return flags
 }
-func (props *PgoProperties) addSamplingProfileGatherFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...)
-	return flags
-}
 
 func (props *PgoProperties) getPgoProfileFile(ctx BaseModuleContext) android.OptionalPath {
 	profileFile := *props.Pgo.Profile_file
@@ -212,6 +208,10 @@
 		ctx.ModuleErrorf("Instrumentation PGO specification is missing benchmark property")
 	}
 
+	if isSampling {
+		ctx.ModuleErrorf("Sampling PGO is deprecated, use AFDO instead")
+	}
+
 	if isSampling && isInstrumentation {
 		ctx.PropertyErrorf("pgo", "Exactly one of \"instrumentation\" and \"sampling\" properties must be set")
 	}
@@ -313,10 +313,6 @@
 	if (props.ShouldProfileModule && props.isInstrumentation()) || props.PgoInstrLink {
 		// Instrumentation PGO use and gather flags cannot coexist.
 		return props.addInstrumentationProfileGatherFlags(ctx, flags)
-	} else if props.ShouldProfileModule && props.isSampling() {
-		flags = props.addSamplingProfileGatherFlags(ctx, flags)
-	} else if ctx.DeviceConfig().SamplingPGO() {
-		flags = props.addSamplingProfileGatherFlags(ctx, flags)
 	}
 
 	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index bea1782..f54c6f8 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -15,9 +15,11 @@
 package cc
 
 import (
-	"android/soong/android"
 	"path/filepath"
 	"strings"
+
+	"android/soong/android"
+	"android/soong/bazel"
 )
 
 func init() {
@@ -111,8 +113,6 @@
 	// TODO(ccross): verify shared library dependencies
 	srcs := p.prebuiltSrcs(ctx)
 	if len(srcs) > 0 {
-		builderFlags := flagsToBuilderFlags(flags)
-
 		if len(srcs) > 1 {
 			ctx.PropertyErrorf("srcs", "multiple prebuilt source files")
 			return nil
@@ -149,7 +149,7 @@
 			// depending on a table of contents file instead of the library itself.
 			tocFile := android.PathForModuleOut(ctx, libName+".toc")
 			p.tocFile = android.OptionalPathForPath(tocFile)
-			transformSharedObjectToToc(ctx, outputFile, tocFile, builderFlags)
+			TransformSharedObjectToToc(ctx, outputFile, tocFile)
 
 			if ctx.Windows() && p.properties.Windows_import_lib != nil {
 				// Consumers of this library actually links to the import library in build
@@ -183,13 +183,22 @@
 			})
 
 			ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-				SharedLibrary:           outputFile,
-				UnstrippedSharedLibrary: p.unstrippedOutputFile,
-				Target:                  ctx.Target(),
+				SharedLibrary: outputFile,
+				Target:        ctx.Target(),
 
 				TableOfContents: p.tocFile,
 			})
 
+			// TODO(b/220898484): Mainline module sdk prebuilts of stub libraries use a stub
+			// library as their source and must not be installed, but libclang_rt.* libraries
+			// have stubs because they are LLNDK libraries, but use an implementation library
+			// as their source and need to be installed.  This discrepancy should be resolved
+			// without the prefix hack below.
+			if p.hasStubsVariants() && !p.buildStubs() && !ctx.Host() &&
+				!strings.HasPrefix(ctx.baseModuleName(), "libclang_rt.") {
+				ctx.Module().MakeUninstallable()
+			}
+
 			return outputFile
 		}
 	}
@@ -197,7 +206,13 @@
 	if p.header() {
 		ctx.SetProvider(HeaderLibraryInfoProvider, HeaderLibraryInfo{})
 
-		return nil
+		// Need to return an output path so that the AndroidMk logic doesn't skip
+		// the prebuilt header. For compatibility, in case Android.mk files use a
+		// header lib in LOCAL_STATIC_LIBRARIES, create an empty ar file as
+		// placeholder, just like non-prebuilt header modules do in linkStatic().
+		ph := android.PathForModuleOut(ctx, ctx.ModuleName()+staticLibraryExtension)
+		transformObjToStaticLib(ctx, nil, nil, builderFlags{}, ph, nil, nil)
+		return ph
 	}
 
 	return nil
@@ -232,12 +247,13 @@
 
 // Implements versionedInterface
 func (p *prebuiltLibraryLinker) implementationModuleName(name string) string {
-	return strings.TrimPrefix(name, "prebuilt_")
+	return android.RemoveOptionalPrebuiltPrefix(name)
 }
 
-func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
+func NewPrebuiltLibrary(hod android.HostOrDeviceSupported, srcsProperty string) (*Module, *libraryDecorator) {
 	module, library := NewLibrary(hod)
 	module.compiler = nil
+	module.bazelable = true
 
 	prebuilt := &prebuiltLibraryLinker{
 		libraryDecorator: library,
@@ -247,11 +263,15 @@
 
 	module.AddProperties(&prebuilt.properties)
 
-	srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
-		return prebuilt.prebuiltSrcs(ctx)
-	}
+	if srcsProperty == "" {
+		android.InitPrebuiltModuleWithoutSrcs(module)
+	} else {
+		srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
+			return prebuilt.prebuiltSrcs(ctx)
+		}
 
-	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "srcs")
+		android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, srcsProperty)
+	}
 
 	// Prebuilt libraries can be used in SDKs.
 	android.InitSdkAwareModule(module)
@@ -261,7 +281,7 @@
 // cc_prebuilt_library installs a precompiled shared library that are
 // listed in the srcs property in the device's directory.
 func PrebuiltLibraryFactory() android.Module {
-	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported, "srcs")
 
 	// Prebuilt shared libraries can be included in APEXes
 	android.InitApexModule(module)
@@ -280,15 +300,17 @@
 // to be used as a data dependency of a test-related module (such as cc_test, or
 // cc_test_library).
 func PrebuiltSharedTestLibraryFactory() android.Module {
-	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported)
+	module, library := NewPrebuiltLibrary(android.HostAndDeviceSupported, "srcs")
 	library.BuildOnlyShared()
 	library.baseInstaller = NewTestInstaller()
 	return module.Init()
 }
 
 func NewPrebuiltSharedLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
-	module, library := NewPrebuiltLibrary(hod)
+	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyShared()
+	module.bazelable = true
+	module.bazelHandler = &prebuiltSharedLibraryBazelHandler{module: module, library: library}
 
 	// Prebuilt shared libraries can be included in APEXes
 	android.InitApexModule(module)
@@ -304,12 +326,74 @@
 }
 
 func NewPrebuiltStaticLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
-	module, library := NewPrebuiltLibrary(hod)
+	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyStatic()
+	module.bazelable = true
 	module.bazelHandler = &prebuiltStaticLibraryBazelHandler{module: module, library: library}
 	return module, library
 }
 
+type bazelPrebuiltLibraryStaticAttributes struct {
+	Static_library         bazel.LabelAttribute
+	Export_includes        bazel.StringListAttribute
+	Export_system_includes bazel.StringListAttribute
+}
+
+// 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{
+		Static_library:         prebuiltAttrs.Src,
+		Export_includes:        exportedIncludes.Includes,
+		Export_system_includes: exportedIncludes.SystemIncludes,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "prebuilt_library_static",
+		Bzl_load_location: "//build/bazel/rules/cc:prebuilt_library_static.bzl",
+	}
+
+	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+	if fullBuild {
+		name += "_bp2build_cc_library_static"
+	}
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
+}
+
+type bazelPrebuiltLibrarySharedAttributes struct {
+	Shared_library bazel.LabelAttribute
+}
+
+func prebuiltLibrarySharedBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	prebuiltAttrs := Bp2BuildParsePrebuiltLibraryProps(ctx, module, false)
+
+	attrs := &bazelPrebuiltLibrarySharedAttributes{
+		Shared_library: prebuiltAttrs.Src,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "prebuilt_library_shared",
+		Bzl_load_location: "//build/bazel/rules/cc:prebuilt_library_shared.bzl",
+	}
+
+	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+	ctx.CreateBazelTargetModuleWithRestrictions(props, android.CommonAttributes{Name: name}, attrs, prebuiltAttrs.Enabled)
+}
+
 type prebuiltObjectProperties struct {
 	Srcs []string `android:"path,arch_variant"`
 }
@@ -322,15 +406,15 @@
 }
 
 type prebuiltStaticLibraryBazelHandler struct {
-	bazelHandler
+	android.BazelHandler
 
 	module  *Module
 	library *libraryDecorator
 }
 
-func (h *prebuiltStaticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (h *prebuiltStaticLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
 	if err != nil {
 		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
 	}
@@ -371,6 +455,69 @@
 	return true
 }
 
+type prebuiltSharedLibraryBazelHandler struct {
+	android.BazelHandler
+
+	module  *Module
+	library *libraryDecorator
+}
+
+func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf("Error getting Bazel CcInfo for %s: %s", label, err)
+	}
+	if !ok {
+		return false
+	}
+	sharedLibs := ccInfo.CcSharedLibraryFiles
+	if len(sharedLibs) != 1 {
+		ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
+		return false
+	}
+
+	// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
+
+	// TODO(eakammer):Add stub-related flags if this library is a stub library.
+	// h.library.exportVersioningMacroIfNeeded(ctx)
+
+	// Dependencies on this library will expect collectedSnapshotHeaders to be set, otherwise
+	// validation will fail. For now, set this to an empty list.
+	// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
+	h.library.collectedSnapshotHeaders = android.Paths{}
+
+	if len(sharedLibs) == 0 {
+		h.module.outputFile = android.OptionalPath{}
+		return true
+	}
+
+	out := android.PathForBazelOut(ctx, sharedLibs[0])
+	h.module.outputFile = android.OptionalPathForPath(out)
+
+	// FIXME(b/214600441): We don't yet strip prebuilt shared libraries
+	h.library.unstrippedOutputFile = out
+
+	var toc android.Path
+	if len(ccInfo.TocFile) > 0 {
+		toc = android.PathForBazelOut(ctx, ccInfo.TocFile)
+	} else {
+		toc = out // Just reuse `out` so ninja still gets an input but won't matter
+	}
+
+	info := SharedLibraryInfo{
+		SharedLibrary:   out,
+		TableOfContents: android.OptionalPathForPath(toc),
+		Target:          ctx.Target(),
+	}
+	ctx.SetProvider(SharedLibraryInfoProvider, info)
+
+	h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
+	h.module.maybeUnhideFromMake()
+
+	return true
+}
+
 func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
 	return &p.Prebuilt
 }
@@ -380,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
 }
@@ -389,8 +545,8 @@
 	return true
 }
 
-func newPrebuiltObject() *Module {
-	module := newObject()
+func NewPrebuiltObject(hod android.HostOrDeviceSupported) *Module {
+	module := newObject(hod)
 	prebuilt := &prebuiltObjectLinker{
 		objectLinker: objectLinker{
 			baseLinker: NewBaseLinker(nil),
@@ -404,7 +560,7 @@
 }
 
 func prebuiltObjectFactory() android.Module {
-	module := newPrebuiltObject()
+	module := NewPrebuiltObject(android.HostAndDeviceSupported)
 	return module.Init()
 }
 
@@ -497,7 +653,7 @@
 }
 
 func NewPrebuiltBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.compiler = nil
 
 	prebuilt := &prebuiltBinaryLinker{
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index fa6dd87..901f458 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -15,9 +15,11 @@
 package cc
 
 import (
+	"runtime"
 	"testing"
 
 	"android/soong/android"
+	"android/soong/bazel/cquery"
 
 	"github.com/google/blueprint"
 )
@@ -28,6 +30,7 @@
 )
 
 func testPrebuilt(t *testing.T, bp string, fs android.MockFS, handlers ...android.FixturePreparer) *android.TestContext {
+	t.Helper()
 	result := android.GroupFixturePreparers(
 		prepareForPrebuiltTest,
 		fs.AddToFixture(),
@@ -271,8 +274,8 @@
 }
 
 func TestPrebuiltSymlinkedHostBinary(t *testing.T) {
-	if android.BuildOs != android.Linux {
-		t.Skipf("Skipping host prebuilt testing that is only supported on %s not %s", android.Linux, android.BuildOs)
+	if runtime.GOOS != "linux" {
+		t.Skipf("Skipping host prebuilt testing that is only supported on linux not %s", runtime.GOOS)
 	}
 
 	ctx := testPrebuilt(t, `
@@ -377,3 +380,143 @@
 	static2 = ctx.ModuleForTests("libtest_static", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
 	assertString(t, static2.OutputFile().Path().Base(), "libf.hwasan.a")
 }
+
+func TestPrebuiltLibrarySharedWithBazelWithoutToc(t *testing.T) {
+	const bp = `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["foo.so"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	outBaseDir := "outputbase"
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: outBaseDir,
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcSharedLibraryFiles: []string{"foo.so"},
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	pathPrefix := outBaseDir + "/execroot/__main__/"
+
+	info := ctx.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library",
+		pathPrefix+"foo.so", info.SharedLibrary)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt's 'nullary' ToC",
+		pathPrefix+"foo.so", info.TableOfContents.Path())
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{pathPrefix + "foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+}
+
+func TestPrebuiltLibrarySharedWithBazelWithToc(t *testing.T) {
+	const bp = `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["foo.so"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	outBaseDir := "outputbase"
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: outBaseDir,
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcSharedLibraryFiles: []string{"foo.so"},
+				TocFile:              "toc",
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	pathPrefix := outBaseDir + "/execroot/__main__/"
+
+	info := ctx.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library's ToC",
+		pathPrefix+"toc", info.TableOfContents.Path())
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library",
+		pathPrefix+"foo.so", info.SharedLibrary)
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{pathPrefix + "foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+}
+
+func TestPrebuiltStubNoinstall(t *testing.T) {
+	testFunc := func(t *testing.T, bp string) {
+		result := android.GroupFixturePreparers(
+			prepareForPrebuiltTest,
+			android.PrepareForTestWithMakevars,
+		).RunTestWithBp(t, bp)
+
+		installRules := result.InstallMakeRulesForTesting(t)
+		var installedlibRule *android.InstallMakeRule
+		for i, rule := range installRules {
+			if rule.Target == "out/target/product/test_device/system/lib/installedlib.so" {
+				if installedlibRule != nil {
+					t.Errorf("Duplicate install rules for %s", rule.Target)
+				}
+				installedlibRule = &installRules[i]
+			}
+		}
+		if installedlibRule == nil {
+			t.Errorf("No install rule found for installedlib")
+			return
+		}
+
+		android.AssertStringListDoesNotContain(t,
+			"installedlib has install dependency on stub",
+			installedlibRule.Deps,
+			"out/target/product/test_device/system/lib/stublib.so")
+		android.AssertStringListDoesNotContain(t,
+			"installedlib has order-only install dependency on stub",
+			installedlibRule.OrderOnlyDeps,
+			"out/target/product/test_device/system/lib/stublib.so")
+	}
+
+	const prebuiltStublibBp = `
+		cc_prebuilt_library {
+			name: "stublib",
+			prefer: true,
+			srcs: ["foo.so"],
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`
+
+	const installedlibBp = `
+		cc_library {
+			name: "installedlib",
+			shared_libs: ["stublib"],
+		}
+	`
+
+	t.Run("prebuilt without source", func(t *testing.T) {
+		testFunc(t, prebuiltStublibBp+installedlibBp)
+	})
+
+	const disabledSourceStublibBp = `
+		cc_library {
+			name: "stublib",
+			enabled: false,
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`
+
+	t.Run("prebuilt with disabled source", func(t *testing.T) {
+		testFunc(t, disabledSourceStublibBp+prebuiltStublibBp+installedlibBp)
+	})
+}
diff --git a/cc/proto.go b/cc/proto.go
index 4466144..8e6d5ed 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -16,8 +16,14 @@
 
 import (
 	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
+)
+
+const (
+	protoTypeDefault = "lite"
 )
 
 // genProto creates a rule to convert a .proto file to generated .pb.cc and .pb.h files and returns
@@ -63,7 +69,7 @@
 	var lib string
 
 	if String(p.Proto.Plugin) == "" {
-		switch String(p.Proto.Type) {
+		switch proptools.StringDefault(p.Proto.Type, protoTypeDefault) {
 		case "full":
 			if ctx.useSdk() {
 				lib = "libprotobuf-cpp-full-ndk"
@@ -71,7 +77,7 @@
 			} else {
 				lib = "libprotobuf-cpp-full"
 			}
-		case "lite", "":
+		case "lite":
 			if ctx.useSdk() {
 				lib = "libprotobuf-cpp-lite-ndk"
 				static = true
@@ -157,3 +163,69 @@
 
 	return flags
 }
+
+type protoAttributes struct {
+	Deps bazel.LabelListAttribute
+}
+
+type bp2buildProtoDeps struct {
+	wholeStaticLib               *bazel.LabelAttribute
+	implementationWholeStaticLib *bazel.LabelAttribute
+	protoDep                     *bazel.LabelAttribute
+}
+
+func bp2buildProto(ctx android.Bp2buildMutatorContext, m *Module, protoSrcs bazel.LabelListAttribute) bp2buildProtoDeps {
+	var ret bp2buildProtoDeps
+
+	protoInfo, ok := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, protoSrcs)
+	if !ok {
+		return ret
+	}
+
+	var depName string
+	typ := proptools.StringDefault(protoInfo.Type, protoTypeDefault)
+	var rule_class string
+	suffix := "_cc_proto"
+	switch typ {
+	case "lite":
+		suffix += "_lite"
+		rule_class = "cc_lite_proto_library"
+		depName = "libprotobuf-cpp-lite"
+	case "full":
+		rule_class = "cc_proto_library"
+		depName = "libprotobuf-cpp-full"
+	default:
+		ctx.PropertyErrorf("proto.type", "cannot handle conversion at this time: %q", typ)
+	}
+
+	dep := android.BazelLabelForModuleDepSingle(ctx, depName)
+	ret.protoDep = &bazel.LabelAttribute{Value: &dep}
+
+	protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
+	var protoAttrs protoAttributes
+	protoAttrs.Deps.SetValue(bazel.LabelList{Includes: []bazel.Label{protoLabel}})
+
+	name := m.Name() + suffix
+
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{
+			Rule_class:        rule_class,
+			Bzl_load_location: "//build/bazel/rules/cc:cc_proto.bzl",
+		},
+		android.CommonAttributes{Name: name},
+		&protoAttrs)
+
+	var privateHdrs bool
+	if lib, ok := m.linker.(*libraryDecorator); ok {
+		privateHdrs = !proptools.Bool(lib.Properties.Proto.Export_proto_headers)
+	}
+
+	labelAttr := &bazel.LabelAttribute{Value: &bazel.Label{Label: ":" + name}}
+	if privateHdrs {
+		ret.implementationWholeStaticLib = labelAttr
+	} else {
+		ret.wholeStaticLib = labelAttr
+	}
+
+	return ret
+}
diff --git a/cc/proto_test.go b/cc/proto_test.go
index b9c89c7..abcb273 100644
--- a/cc/proto_test.go
+++ b/cc/proto_test.go
@@ -51,7 +51,7 @@
 			},
 		}`)
 
-		buildOS := android.BuildOs.String()
+		buildOS := ctx.Config().BuildOS.String()
 
 		proto := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("proto/a.pb.cc")
 		foobar := ctx.ModuleForTests("protoc-gen-foobar", buildOS+"_x86_64")
diff --git a/cc/rs.go b/cc/rs.go
index ba69f23..fbc86e2 100644
--- a/cc/rs.go
+++ b/cc/rs.go
@@ -36,7 +36,8 @@
 
 var rsCppCmdLine = strings.Replace(`
 ${rsCmd} -o ${outDir} -d ${outDir} -a ${out} -MD -reflect-c++ ${rsFlags} $in &&
-(echo '${out}: \' && cat ${depFiles} | awk 'start { sub(/( \\)?$$/, " \\"); print } /:/ { start=1 }') > ${out}.d &&
+echo '${out}: \' > ${out}.d &&
+for f in ${depFiles}; do cat $${f} | awk 'start { sub(/( \\)?$$/, " \\"); print } /:/ { start=1 }' >> ${out}.d; done &&
 touch $out
 `, "\n", "", -1)
 
diff --git a/cc/sabi.go b/cc/sabi.go
index 1f331cb..e62ca66 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -45,12 +45,6 @@
 	return []interface{}{&sabi.Properties}
 }
 
-func (sabi *sabi) begin(ctx BaseModuleContext) {}
-
-func (sabi *sabi) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (sabi *sabi) flags(ctx ModuleContext, flags Flags) Flags {
 	// Filter out flags which libTooling don't understand.
 	// This is here for legacy reasons and future-proof, in case the version of libTooling and clang
@@ -107,10 +101,6 @@
 // Called from sabiDepsMutator to check whether ABI dumps should be created for this module.
 // ctx should be wrapping a native library type module.
 func shouldCreateSourceAbiDumpForLibrary(ctx android.BaseModuleContext) bool {
-	if ctx.Fuchsia() {
-		return false
-	}
-
 	// Only generate ABI dump for device modules.
 	if !ctx.Device() {
 		return false
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 941a955..814fef6 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -21,9 +21,11 @@
 	"sync"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 	"android/soong/cc/config"
+	"android/soong/snapshot"
 )
 
 var (
@@ -33,13 +35,19 @@
 
 	asanCflags = []string{
 		"-fno-omit-frame-pointer",
-		"-fno-experimental-new-pass-manager",
 	}
 	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
 
-	hwasanCflags = []string{"-fno-omit-frame-pointer", "-Wno-frame-larger-than=",
+	hwasanCflags = []string{
+		"-fno-omit-frame-pointer",
+		"-Wno-frame-larger-than=",
 		"-fsanitize-hwaddress-abi=platform",
-		"-fno-experimental-new-pass-manager",
+		"-mllvm", "-hwasan-use-after-scope=1",
+	}
+
+	// ThinLTO performs codegen during link time, thus these flags need to
+	// passed to both CFLAGS and LDFLAGS.
+	hwasanCommonflags = []string{
 		// The following improves debug location information
 		// availability at the cost of its accuracy. It increases
 		// the likelihood of a stack variable's frame offset
@@ -47,15 +55,15 @@
 		// for the quality of hwasan reports. The downside is a
 		// higher number of "optimized out" stack variables.
 		// b/112437883.
-		"-mllvm", "-instcombine-lower-dbg-declare=0",
+		"-instcombine-lower-dbg-declare=0",
 		// TODO(b/159343917): HWASan and GlobalISel don't play nicely, and
 		// GlobalISel is the default at -O0 on aarch64.
-		"-mllvm", "--aarch64-enable-global-isel-at-O=-1",
-		"-mllvm", "-fast-isel=false",
+		"--aarch64-enable-global-isel-at-O=-1",
+		"-fast-isel=false",
 	}
 
 	cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso",
-		"-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blocklist.txt"}
+		"-fsanitize-ignorelist=external/compiler-rt/lib/cfi/cfi_blocklist.txt"}
 	// -flto and -fvisibility are required by clang when -fsanitize=cfi is
 	// used, but have no effect on assembly files
 	cfiAsflags = []string{"-flto", "-fvisibility=default"}
@@ -63,35 +71,38 @@
 		"-Wl,-plugin-opt,O1"}
 	cfiExportsMapPath = "build/soong/cc/config/cfi_exports.map"
 
-	intOverflowCflags = []string{"-fsanitize-blacklist=build/soong/cc/config/integer_overflow_blocklist.txt"}
+	intOverflowCflags = []string{"-fsanitize-ignorelist=build/soong/cc/config/integer_overflow_blocklist.txt"}
 
 	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
 		"-fno-sanitize-recover=integer,undefined"}
 	hwasanGlobalOptions = []string{"heap_history_size=1023", "stack_history_size=512",
-		"export_memory_stats=0", "max_malloc_fill_size=0"}
+		"export_memory_stats=0", "max_malloc_fill_size=4096", "malloc_fill_byte=0"}
 )
 
 type SanitizerType int
 
-func boolPtr(v bool) *bool {
-	if v {
-		return &v
-	} else {
-		return nil
-	}
-}
-
 const (
 	Asan SanitizerType = iota + 1
 	Hwasan
 	tsan
 	intOverflow
-	cfi
 	scs
 	Fuzzer
-	memtag_heap
+	Memtag_heap
+	cfi // cfi is last to prevent it running before incompatible mutators
 )
 
+var Sanitizers = []SanitizerType{
+	Asan,
+	Hwasan,
+	tsan,
+	intOverflow,
+	scs,
+	Fuzzer,
+	Memtag_heap,
+	cfi, // cfi is last to prevent it running before incompatible mutators
+}
+
 // Name of the sanitizer variation for this sanitizer type
 func (t SanitizerType) variationName() string {
 	switch t {
@@ -107,7 +118,7 @@
 		return "cfi"
 	case scs:
 		return "scs"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case Fuzzer:
 		return "fuzzer"
@@ -123,7 +134,7 @@
 		return "address"
 	case Hwasan:
 		return "hwaddress"
-	case memtag_heap:
+	case Memtag_heap:
 		return "memtag_heap"
 	case tsan:
 		return "thread"
@@ -140,6 +151,18 @@
 	}
 }
 
+func (t SanitizerType) registerMutators(ctx android.RegisterMutatorsContext) {
+	switch t {
+	case Asan, Hwasan, Fuzzer, scs, tsan, cfi:
+		ctx.TopDown(t.variationName()+"_deps", sanitizerDepsMutator(t))
+		ctx.BottomUp(t.variationName(), sanitizerMutator(t))
+	case Memtag_heap, intOverflow:
+		// do nothing
+	default:
+		panic(fmt.Errorf("unknown SanitizerType %d", t))
+	}
+}
+
 func (*Module) SanitizerSupported(t SanitizerType) bool {
 	switch t {
 	case Asan:
@@ -156,6 +179,8 @@
 		return true
 	case Fuzzer:
 		return true
+	case Memtag_heap:
+		return true
 	default:
 		return false
 	}
@@ -167,24 +192,46 @@
 }
 
 type SanitizeUserProps struct {
+	// Prevent use of any sanitizers on this module
 	Never *bool `android:"arch_variant"`
 
-	// main sanitizers
-	Address   *bool `android:"arch_variant"`
-	Thread    *bool `android:"arch_variant"`
+	// ASan (Address sanitizer), incompatible with static binaries.
+	// Always runs in a diagnostic mode.
+	// Use of address sanitizer disables cfi sanitizer.
+	// Hwaddress sanitizer takes precedence over this sanitizer.
+	Address *bool `android:"arch_variant"`
+	// TSan (Thread sanitizer), incompatible with static binaries and 32 bit architectures.
+	// Always runs in a diagnostic mode.
+	// Use of thread sanitizer disables cfi and scudo sanitizers.
+	// Hwaddress sanitizer takes precedence over this sanitizer.
+	Thread *bool `android:"arch_variant"`
+	// HWASan (Hardware Address sanitizer).
+	// Use of hwasan sanitizer disables cfi, address, thread, and scudo sanitizers.
 	Hwaddress *bool `android:"arch_variant"`
 
-	// local sanitizers
-	Undefined        *bool    `android:"arch_variant"`
-	All_undefined    *bool    `android:"arch_variant"`
-	Misc_undefined   []string `android:"arch_variant"`
-	Fuzzer           *bool    `android:"arch_variant"`
-	Safestack        *bool    `android:"arch_variant"`
-	Cfi              *bool    `android:"arch_variant"`
-	Integer_overflow *bool    `android:"arch_variant"`
-	Scudo            *bool    `android:"arch_variant"`
-	Scs              *bool    `android:"arch_variant"`
-	Memtag_heap      *bool    `android:"arch_variant"`
+	// Undefined behavior sanitizer
+	All_undefined *bool `android:"arch_variant"`
+	// Subset of undefined behavior sanitizer
+	Undefined *bool `android:"arch_variant"`
+	// List of specific undefined behavior sanitizers to enable
+	Misc_undefined []string `android:"arch_variant"`
+	// Fuzzer, incompatible with static binaries.
+	Fuzzer *bool `android:"arch_variant"`
+	// safe-stack sanitizer, incompatible with 32-bit architectures.
+	Safestack *bool `android:"arch_variant"`
+	// cfi sanitizer, incompatible with asan, hwasan, fuzzer, or Darwin
+	Cfi *bool `android:"arch_variant"`
+	// signed/unsigned integer overflow sanitizer, incompatible with Darwin.
+	Integer_overflow *bool `android:"arch_variant"`
+	// scudo sanitizer, incompatible with asan, hwasan, tsan
+	// This should not be used in Android 11+ : https://source.android.com/devices/tech/debug/scudo
+	// deprecated
+	Scudo *bool `android:"arch_variant"`
+	// shadow-call-stack sanitizer, only available on arm64
+	Scs *bool `android:"arch_variant"`
+	// Memory-tagging, only available on arm64
+	// if diag.memtag unset or false, enables async memory tagging
+	Memtag_heap *bool `android:"arch_variant"`
 
 	// A modifier for ASAN and HWASAN for write only instrumentation
 	Writeonly *bool `android:"arch_variant"`
@@ -193,12 +240,22 @@
 	// Replaces abort() on error with a human-readable error message.
 	// Address and Thread sanitizers always run in diagnostic mode.
 	Diag struct {
-		Undefined        *bool    `android:"arch_variant"`
-		Cfi              *bool    `android:"arch_variant"`
-		Integer_overflow *bool    `android:"arch_variant"`
-		Memtag_heap      *bool    `android:"arch_variant"`
-		Misc_undefined   []string `android:"arch_variant"`
-		No_recover       []string `android:"arch_variant"`
+		// Undefined behavior sanitizer, diagnostic mode
+		Undefined *bool `android:"arch_variant"`
+		// cfi sanitizer, diagnostic mode, incompatible with asan, hwasan, fuzzer, or Darwin
+		Cfi *bool `android:"arch_variant"`
+		// signed/unsigned integer overflow sanitizer, diagnostic mode, incompatible with Darwin.
+		Integer_overflow *bool `android:"arch_variant"`
+		// Memory-tagging, only available on arm64
+		// requires sanitizer.memtag: true
+		// if set, enables sync memory tagging
+		Memtag_heap *bool `android:"arch_variant"`
+		// List of specific undefined behavior sanitizers to enable in diagnostic mode
+		Misc_undefined []string `android:"arch_variant"`
+		// List of sanitizers to pass to -fno-sanitize-recover
+		// results in only the first detected error for these sanitizers being reported and program then
+		// exits with a non-zero exit code.
+		No_recover []string `android:"arch_variant"`
 	} `android:"arch_variant"`
 
 	// Sanitizers to run with flag configuration specified
@@ -207,17 +264,16 @@
 		Cfi_assembly_support *bool `android:"arch_variant"`
 	} `android:"arch_variant"`
 
-	// value to pass to -fsanitize-recover=
+	// List of sanitizers to pass to -fsanitize-recover
+	// allows execution to continue for these sanitizers to detect multiple errors rather than only
+	// the first one
 	Recover []string
 
-	// value to pass to -fsanitize-blacklist
+	// value to pass to -fsanitize-ignorelist
 	Blocklist *string
 }
 
 type SanitizeProperties struct {
-	// Enable AddressSanitizer, ThreadSanitizer, UndefinedBehaviorSanitizer, and
-	// others. Please see SanitizerUserProps in build/soong/cc/sanitize.go for
-	// details.
 	Sanitize          SanitizeUserProps `android:"arch_variant"`
 	SanitizerEnabled  bool              `blueprint:"mutated"`
 	SanitizeDep       bool              `blueprint:"mutated"`
@@ -257,20 +313,19 @@
 		s.Never = BoolPtr(true)
 	}
 
-	// Sanitizers do not work on Fuchsia yet.
-	if ctx.Fuchsia() {
-		s.Never = BoolPtr(true)
-	}
-
 	// Never always wins.
 	if Bool(s.Never) {
 		return
 	}
 
 	// cc_test targets default to SYNC MemTag unless explicitly set to ASYNC (via diag: {memtag_heap}).
-	if ctx.testBinary() && s.Memtag_heap == nil {
-		s.Memtag_heap = boolPtr(true)
-		s.Diag.Memtag_heap = boolPtr(true)
+	if ctx.testBinary() {
+		if s.Memtag_heap == nil {
+			s.Memtag_heap = proptools.BoolPtr(true)
+		}
+		if s.Diag.Memtag_heap == nil {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
 	}
 
 	var globalSanitizers []string
@@ -291,48 +346,48 @@
 	if len(globalSanitizers) > 0 {
 		var found bool
 		if found, globalSanitizers = removeFromList("undefined", globalSanitizers); found && s.All_undefined == nil {
-			s.All_undefined = boolPtr(true)
+			s.All_undefined = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("default-ub", globalSanitizers); found && s.Undefined == nil {
-			s.Undefined = boolPtr(true)
+			s.Undefined = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("address", globalSanitizers); found && s.Address == nil {
-			s.Address = boolPtr(true)
+			s.Address = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("thread", globalSanitizers); found && s.Thread == nil {
-			s.Thread = boolPtr(true)
+			s.Thread = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("fuzzer", globalSanitizers); found && s.Fuzzer == nil {
-			s.Fuzzer = boolPtr(true)
+			s.Fuzzer = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("safe-stack", globalSanitizers); found && s.Safestack == nil {
-			s.Safestack = boolPtr(true)
+			s.Safestack = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("cfi", globalSanitizers); found && s.Cfi == nil {
 			if !ctx.Config().CFIDisabledForPath(ctx.ModuleDir()) {
-				s.Cfi = boolPtr(true)
+				s.Cfi = proptools.BoolPtr(true)
 			}
 		}
 
 		// Global integer_overflow builds do not support static libraries.
 		if found, globalSanitizers = removeFromList("integer_overflow", globalSanitizers); found && s.Integer_overflow == nil {
 			if !ctx.Config().IntegerOverflowDisabledForPath(ctx.ModuleDir()) && !ctx.static() {
-				s.Integer_overflow = boolPtr(true)
+				s.Integer_overflow = proptools.BoolPtr(true)
 			}
 		}
 
 		if found, globalSanitizers = removeFromList("scudo", globalSanitizers); found && s.Scudo == nil {
-			s.Scudo = boolPtr(true)
+			s.Scudo = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("hwaddress", globalSanitizers); found && s.Hwaddress == nil {
-			s.Hwaddress = boolPtr(true)
+			s.Hwaddress = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizers = removeFromList("writeonly", globalSanitizers); found && s.Writeonly == nil {
@@ -341,11 +396,11 @@
 			if s.Address == nil && s.Hwaddress == nil {
 				ctx.ModuleErrorf("writeonly modifier cannot be used without 'address' or 'hwaddress'")
 			}
-			s.Writeonly = boolPtr(true)
+			s.Writeonly = proptools.BoolPtr(true)
 		}
 		if found, globalSanitizers = removeFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
 			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
-				s.Memtag_heap = boolPtr(true)
+				s.Memtag_heap = proptools.BoolPtr(true)
 			}
 		}
 
@@ -356,17 +411,17 @@
 		// Global integer_overflow builds do not support static library diagnostics.
 		if found, globalSanitizersDiag = removeFromList("integer_overflow", globalSanitizersDiag); found &&
 			s.Diag.Integer_overflow == nil && Bool(s.Integer_overflow) && !ctx.static() {
-			s.Diag.Integer_overflow = boolPtr(true)
+			s.Diag.Integer_overflow = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizersDiag = removeFromList("cfi", globalSanitizersDiag); found &&
 			s.Diag.Cfi == nil && Bool(s.Cfi) {
-			s.Diag.Cfi = boolPtr(true)
+			s.Diag.Cfi = proptools.BoolPtr(true)
 		}
 
 		if found, globalSanitizersDiag = removeFromList("memtag_heap", globalSanitizersDiag); found &&
 			s.Diag.Memtag_heap == nil && Bool(s.Memtag_heap) {
-			s.Diag.Memtag_heap = boolPtr(true)
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
 		}
 
 		if len(globalSanitizersDiag) > 0 {
@@ -378,30 +433,30 @@
 	if ctx.Arch().ArchType == android.Arm64 {
 		if ctx.Config().MemtagHeapSyncEnabledForPath(ctx.ModuleDir()) {
 			if s.Memtag_heap == nil {
-				s.Memtag_heap = boolPtr(true)
+				s.Memtag_heap = proptools.BoolPtr(true)
 			}
 			if s.Diag.Memtag_heap == nil {
-				s.Diag.Memtag_heap = boolPtr(true)
+				s.Diag.Memtag_heap = proptools.BoolPtr(true)
 			}
 		} else if ctx.Config().MemtagHeapAsyncEnabledForPath(ctx.ModuleDir()) {
 			if s.Memtag_heap == nil {
-				s.Memtag_heap = boolPtr(true)
+				s.Memtag_heap = proptools.BoolPtr(true)
 			}
 		}
 	}
 
-	// Enable CFI for all components in the include paths (for Aarch64 only)
-	if s.Cfi == nil && ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) && ctx.Arch().ArchType == android.Arm64 {
-		s.Cfi = boolPtr(true)
+	// Enable CFI for non-host components in the include paths
+	if s.Cfi == nil && ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) && !ctx.Host() {
+		s.Cfi = proptools.BoolPtr(true)
 		if inList("cfi", ctx.Config().SanitizeDeviceDiag()) {
-			s.Diag.Cfi = boolPtr(true)
+			s.Diag.Cfi = proptools.BoolPtr(true)
 		}
 	}
 
 	// Is CFI actually enabled?
 	if !ctx.Config().EnableCFI() {
-		s.Cfi = boolPtr(false)
-		s.Diag.Cfi = boolPtr(false)
+		s.Cfi = nil
+		s.Diag.Cfi = nil
 	}
 
 	// HWASan requires AArch64 hardware feature (top-byte-ignore).
@@ -414,27 +469,33 @@
 		s.Scs = nil
 	}
 
-	// memtag_heap is only implemented on AArch64.
+	// Memtag_heap is only implemented on AArch64.
 	if ctx.Arch().ArchType != android.Arm64 {
 		s.Memtag_heap = nil
 	}
 
 	// Also disable CFI if ASAN is enabled.
 	if Bool(s.Address) || Bool(s.Hwaddress) {
-		s.Cfi = boolPtr(false)
-		s.Diag.Cfi = boolPtr(false)
+		s.Cfi = nil
+		s.Diag.Cfi = nil
 	}
 
 	// Disable sanitizers that depend on the UBSan runtime for windows/darwin builds.
 	if !ctx.Os().Linux() {
-		s.Cfi = boolPtr(false)
-		s.Diag.Cfi = boolPtr(false)
+		s.Cfi = nil
+		s.Diag.Cfi = nil
 		s.Misc_undefined = nil
 		s.Undefined = nil
 		s.All_undefined = nil
 		s.Integer_overflow = nil
 	}
 
+	// Disable CFI for musl
+	if ctx.toolchain().Musl() {
+		s.Cfi = nil
+		s.Diag.Cfi = nil
+	}
+
 	// Also disable CFI for VNDK variants of components
 	if ctx.isVndk() && ctx.useVndk() {
 		if ctx.static() {
@@ -443,8 +504,8 @@
 			s.Cfi = nil
 			s.Diag.Cfi = nil
 		} else {
-			s.Cfi = boolPtr(false)
-			s.Diag.Cfi = boolPtr(false)
+			s.Cfi = nil
+			s.Diag.Cfi = nil
 		}
 	}
 
@@ -485,28 +546,15 @@
 	if Bool(s.Hwaddress) {
 		s.Address = nil
 		s.Thread = nil
-		// Disable ubsan diagnosic as a workaround for a compiler bug.
-		// TODO(b/191808836): re-enable.
-		s.Diag.Undefined = nil
-		s.Diag.Integer_overflow = nil
-		s.Diag.Misc_undefined = nil
 	}
 
 	// TODO(b/131771163): CFI transiently depends on LTO, and thus Fuzzer is
 	// mutually incompatible.
 	if Bool(s.Fuzzer) {
-		s.Cfi = boolPtr(false)
+		s.Cfi = nil
 	}
 }
 
-func (sanitize *sanitize) deps(ctx BaseModuleContext, deps Deps) Deps {
-	if !sanitize.Properties.SanitizerEnabled { // || c.static() {
-		return deps
-	}
-
-	return deps
-}
-
 func toDisableImplicitIntegerChange(flags []string) bool {
 	// Returns true if any flag is fsanitize*integer, and there is
 	// no explicit flag about sanitize=implicit-integer-sign-change.
@@ -541,20 +589,12 @@
 
 func (sanitize *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
 	minimalRuntimeLib := config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(ctx.toolchain()) + ".a"
-	minimalRuntimePath := "${config.ClangAsanLibDir}/" + minimalRuntimeLib
-	builtinsRuntimeLib := config.BuiltinsRuntimeLibrary(ctx.toolchain()) + ".a"
-	builtinsRuntimePath := "${config.ClangAsanLibDir}/" + builtinsRuntimeLib
 
 	if sanitize.Properties.MinimalRuntimeDep {
 		flags.Local.LdFlags = append(flags.Local.LdFlags,
-			minimalRuntimePath,
 			"-Wl,--exclude-libs,"+minimalRuntimeLib)
 	}
 
-	if sanitize.Properties.BuiltinsDep {
-		flags.libFlags = append([]string{builtinsRuntimePath}, flags.libFlags...)
-	}
-
 	if !sanitize.Properties.SanitizerEnabled && !sanitize.Properties.UbsanRuntimeDep {
 		return flags
 	}
@@ -591,6 +631,14 @@
 
 	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
 		flags.Local.CFlags = append(flags.Local.CFlags, hwasanCflags...)
+
+		for _, flag := range hwasanCommonflags {
+			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", flag)
+		}
+		for _, flag := range hwasanCommonflags {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
+		}
+
 		if Bool(sanitize.Properties.Sanitize.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-hwasan-instrument-reads=0")
 		}
@@ -611,9 +659,6 @@
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-coverage=stack-depth")
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-coverage=stack-depth")
 
-		// TODO(b/133876586): Experimental PM breaks sanitizer coverage.
-		flags.Local.CFlags = append(flags.Local.CFlags, "-fno-experimental-new-pass-manager")
-
 		// Disable fortify for fuzzing builds. Generally, we'll be building with
 		// UBSan or ASan here and the fortify checks pollute the stack traces.
 		flags.Local.CFlags = append(flags.Local.CFlags, "-U_FORTIFY_SOURCE")
@@ -659,28 +704,27 @@
 
 	if len(sanitize.Properties.Sanitizers) > 0 {
 		sanitizeArg := "-fsanitize=" + strings.Join(sanitize.Properties.Sanitizers, ",")
-
 		flags.Local.CFlags = append(flags.Local.CFlags, sanitizeArg)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, sanitizeArg)
-		if ctx.Host() {
+		flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)
+
+		if ctx.toolchain().Bionic() || ctx.toolchain().Musl() {
+			// Bionic and musl sanitizer runtimes have already been added as dependencies so that
+			// the right variant of the runtime will be used (with the "-android" or "-musl"
+			// suffixes), so don't let clang the runtime library.
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-fno-sanitize-link-runtime")
+		} else {
 			// Host sanitizers only link symbols in the final executable, so
 			// there will always be undefined symbols in intermediate libraries.
 			_, flags.Global.LdFlags = removeFromList("-Wl,--no-undefined", flags.Global.LdFlags)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, sanitizeArg)
 
-			// non-Bionic toolchain prebuilts are missing UBSan's vptr and function sanitizers
-			if !ctx.toolchain().Bionic() {
-				flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=vptr,function")
-			}
+			// non-Bionic toolchain prebuilts are missing UBSan's vptr and function san
+			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize=vptr,function")
 		}
 
 		if enableMinimalRuntime(sanitize) {
 			flags.Local.CFlags = append(flags.Local.CFlags, strings.Join(minimalRuntimeFlags, " "))
-			flags.libFlags = append([]string{minimalRuntimePath}, flags.libFlags...)
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--exclude-libs,"+minimalRuntimeLib)
-			if !ctx.toolchain().Bionic() {
-				flags.libFlags = append([]string{builtinsRuntimePath}, flags.libFlags...)
-			}
 		}
 
 		if Bool(sanitize.Properties.Sanitize.Fuzzer) {
@@ -718,7 +762,7 @@
 
 	blocklist := android.OptionalPathForModuleSrc(ctx, sanitize.Properties.Sanitize.Blocklist)
 	if blocklist.Valid() {
-		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-blacklist="+blocklist.String())
+		flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-ignorelist="+blocklist.String())
 		flags.CFlagsDeps = append(flags.CFlagsDeps, blocklist.Path())
 	}
 
@@ -760,7 +804,7 @@
 		return sanitize.Properties.Sanitize.Cfi
 	case scs:
 		return sanitize.Properties.Sanitize.Scs
-	case memtag_heap:
+	case Memtag_heap:
 		return sanitize.Properties.Sanitize.Memtag_heap
 	case Fuzzer:
 		return sanitize.Properties.Sanitize.Fuzzer
@@ -776,7 +820,7 @@
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(cfi) &&
 		!sanitize.isSanitizerEnabled(scs) &&
-		!sanitize.isSanitizerEnabled(memtag_heap) &&
+		!sanitize.isSanitizerEnabled(Memtag_heap) &&
 		!sanitize.isSanitizerEnabled(Fuzzer)
 }
 
@@ -789,23 +833,27 @@
 }
 
 func (sanitize *sanitize) SetSanitizer(t SanitizerType, b bool) {
+	bPtr := proptools.BoolPtr(b)
+	if !b {
+		bPtr = nil
+	}
 	switch t {
 	case Asan:
-		sanitize.Properties.Sanitize.Address = boolPtr(b)
+		sanitize.Properties.Sanitize.Address = bPtr
 	case Hwasan:
-		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
+		sanitize.Properties.Sanitize.Hwaddress = bPtr
 	case tsan:
-		sanitize.Properties.Sanitize.Thread = boolPtr(b)
+		sanitize.Properties.Sanitize.Thread = bPtr
 	case intOverflow:
-		sanitize.Properties.Sanitize.Integer_overflow = boolPtr(b)
+		sanitize.Properties.Sanitize.Integer_overflow = bPtr
 	case cfi:
-		sanitize.Properties.Sanitize.Cfi = boolPtr(b)
+		sanitize.Properties.Sanitize.Cfi = bPtr
 	case scs:
-		sanitize.Properties.Sanitize.Scs = boolPtr(b)
-	case memtag_heap:
-		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+		sanitize.Properties.Sanitize.Scs = bPtr
+	case Memtag_heap:
+		sanitize.Properties.Sanitize.Memtag_heap = bPtr
 	case Fuzzer:
-		sanitize.Properties.Sanitize.Fuzzer = boolPtr(b)
+		sanitize.Properties.Sanitize.Fuzzer = bPtr
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
 	}
@@ -859,7 +907,7 @@
 // as vendor snapshot. Such modules must create both cfi and non-cfi variants,
 // except for ones which explicitly disable cfi.
 func needsCfiForVendorSnapshot(mctx android.TopDownMutatorContext) bool {
-	if isVendorProprietaryModule(mctx) {
+	if snapshot.IsVendorProprietaryModule(mctx) {
 		return false
 	}
 
@@ -1091,7 +1139,7 @@
 			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
 				noteDep = lib
 			}
-			depTag := libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: true}
+			depTag := StaticDepTag(true)
 			variations := append(mctx.Target().Variations(),
 				blueprint.Variation{Mutator: "link", Variation: "static"})
 			if c.Device() {
@@ -1145,7 +1193,37 @@
 			}
 		}
 
-		if runtimeLibrary != "" && (toolchain.Bionic() || c.sanitize.Properties.UbsanRuntimeDep) {
+		addStaticDeps := func(deps ...string) {
+			// If we're using snapshots, redirect to snapshot whenever possible
+			snapshot := mctx.Provider(SnapshotInfoProvider).(SnapshotInfo)
+			for idx, dep := range deps {
+				if lib, ok := snapshot.StaticLibs[dep]; ok {
+					deps[idx] = lib
+				}
+			}
+
+			// static executable gets static runtime libs
+			depTag := libraryDependencyTag{Kind: staticLibraryDependency}
+			variations := append(mctx.Target().Variations(),
+				blueprint.Variation{Mutator: "link", Variation: "static"})
+			if c.Device() {
+				variations = append(variations, c.ImageVariation())
+			}
+			if c.UseSdk() {
+				variations = append(variations,
+					blueprint.Variation{Mutator: "sdk", Variation: "sdk"})
+			}
+			mctx.AddFarVariationDependencies(variations, depTag, deps...)
+
+		}
+		if enableMinimalRuntime(c.sanitize) || c.sanitize.Properties.MinimalRuntimeDep {
+			addStaticDeps(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain))
+		}
+		if c.sanitize.Properties.BuiltinsDep {
+			addStaticDeps(config.BuiltinsRuntimeLibrary(toolchain))
+		}
+
+		if runtimeLibrary != "" && (toolchain.Bionic() || toolchain.Musl() || c.sanitize.Properties.UbsanRuntimeDep) {
 			// UBSan is supported on non-bionic linux host builds as well
 
 			// Adding dependency to the runtime library. We are using *FarVariation*
@@ -1156,23 +1234,8 @@
 			// Note that by adding dependency with {static|shared}DepTag, the lib is
 			// added to libFlags and LOCAL_SHARED_LIBRARIES by cc.Module
 			if c.staticBinary() {
-				deps := append(extraStaticDeps, runtimeLibrary)
-				// If we're using snapshots, redirect to snapshot whenever possible
-				snapshot := mctx.Provider(SnapshotInfoProvider).(SnapshotInfo)
-				for idx, dep := range deps {
-					if lib, ok := snapshot.StaticLibs[dep]; ok {
-						deps[idx] = lib
-					}
-				}
-
-				// static executable gets static runtime libs
-				depTag := libraryDependencyTag{Kind: staticLibraryDependency}
-				variations := append(mctx.Target().Variations(),
-					blueprint.Variation{Mutator: "link", Variation: "static"})
-				if c.Device() {
-					variations = append(variations, c.ImageVariation())
-				}
-				mctx.AddFarVariationDependencies(variations, depTag, deps...)
+				addStaticDeps(runtimeLibrary)
+				addStaticDeps(extraStaticDeps...)
 			} else if !c.static() && !c.Header() {
 				// If we're using snapshots, redirect to snapshot whenever possible
 				snapshot := mctx.Provider(SnapshotInfoProvider).(SnapshotInfo)
@@ -1197,7 +1260,11 @@
 				if c.Device() {
 					variations = append(variations, c.ImageVariation())
 				}
-				c.addSharedLibDependenciesWithVersions(mctx, variations, depTag, runtimeLibrary, "", true)
+				if c.UseSdk() {
+					variations = append(variations,
+						blueprint.Variation{Mutator: "sdk", Variation: "sdk"})
+				}
+				AddSharedLibDependenciesWithVersions(mctx, c, variations, depTag, runtimeLibrary, "", true)
 			}
 			// static lib does not have dependency to the runtime library. The
 			// dependency will be added to the executables or shared libs using
@@ -1261,7 +1328,11 @@
 func sanitizerMutator(t SanitizerType) func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
 		if c, ok := mctx.Module().(PlatformSanitizeable); ok && c.SanitizePropDefined() {
-			if c.IsDependencyRoot() && c.IsSanitizerEnabled(t) {
+
+			// Make sure we're not setting CFI to any value if it's not supported.
+			cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
+
+			if c.Binary() && c.IsSanitizerEnabled(t) {
 				modules := mctx.CreateVariations(t.variationName())
 				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
 			} else if c.IsSanitizerEnabled(t) || c.SanitizeDep() {
@@ -1281,7 +1352,6 @@
 					// is redirected to the sanitized variant of the dependent module.
 					defaultVariation := t.variationName()
 					// Not all PlatformSanitizeable modules support the CFI sanitizer
-					cfiSupported := mctx.Module().(PlatformSanitizeable).SanitizerSupported(cfi)
 					mctx.SetDefaultDependencyVariation(&defaultVariation)
 
 					modules := mctx.CreateVariations("", t.variationName())
@@ -1328,7 +1398,7 @@
 						modules[0].(PlatformSanitizeable).SetInSanitizerDir()
 					}
 
-					if mctx.Device() && t.incompatibleWithCfi() {
+					if mctx.Device() && t.incompatibleWithCfi() && cfiSupported {
 						// TODO: Make sure that cfi mutator runs "after" any of the sanitizers that
 						// are incompatible with cfi
 						modules[0].(PlatformSanitizeable).SetSanitizer(cfi, false)
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index f126346..c1ca034 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -15,6 +15,8 @@
 package cc
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -22,11 +24,7 @@
 
 var prepareForAsanTest = android.FixtureAddFile("asan/Android.bp", []byte(`
 	cc_library_shared {
-		name: "libclang_rt.asan-aarch64-android",
-	}
-
-	cc_library_shared {
-		name: "libclang_rt.asan-arm-android",
+		name: "libclang_rt.asan",
 	}
 `))
 
@@ -202,3 +200,354 @@
 	t.Run("host", func(t *testing.T) { check(t, result, result.Config.BuildOSTarget.String()) })
 	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
 }
+
+type MemtagNoteType int
+
+const (
+	None MemtagNoteType = iota + 1
+	Sync
+	Async
+)
+
+func (t MemtagNoteType) str() string {
+	switch t {
+	case None:
+		return "none"
+	case Sync:
+		return "sync"
+	case Async:
+		return "async"
+	default:
+		panic("type_note_invalid")
+	}
+}
+
+func checkHasMemtagNote(t *testing.T, m android.TestingModule, expected MemtagNoteType) {
+	t.Helper()
+	note_async := "note_memtag_heap_async"
+	note_sync := "note_memtag_heap_sync"
+
+	found := None
+	implicits := m.Rule("ld").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), note_async) {
+			found = Async
+			break
+		} else if strings.Contains(lib.Rel(), note_sync) {
+			found = Sync
+			break
+		}
+	}
+
+	if found != expected {
+		t.Errorf("Wrong Memtag note in target %q: found %q, expected %q", m.Module().(*Module).Name(), found.str(), expected.str())
+	}
+}
+
+var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
+	android.FixtureModifyMockFS(func(fs android.MockFS) {
+		templateBp := `
+		cc_test {
+			name: "unset_test_%[1]s",
+			gtest: false,
+		}
+
+		cc_test {
+			name: "no_memtag_test_%[1]s",
+			gtest: false,
+			sanitize: { memtag_heap: false },
+		}
+
+		cc_test {
+			name: "set_memtag_test_%[1]s",
+			gtest: false,
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_test {
+			name: "set_memtag_set_async_test_%[1]s",
+			gtest: false,
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		cc_test {
+			name: "set_memtag_set_sync_test_%[1]s",
+			gtest: false,
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		cc_test {
+			name: "unset_memtag_set_sync_test_%[1]s",
+			gtest: false,
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+
+		cc_binary {
+			name: "unset_binary_%[1]s",
+		}
+
+		cc_binary {
+			name: "no_memtag_binary_%[1]s",
+			sanitize: { memtag_heap: false },
+		}
+
+		cc_binary {
+			name: "set_memtag_binary_%[1]s",
+			sanitize: { memtag_heap: true },
+		}
+
+		cc_binary {
+			name: "set_memtag_set_async_binary_%[1]s",
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		cc_binary {
+			name: "set_memtag_set_sync_binary_%[1]s",
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		cc_binary {
+			name: "unset_memtag_set_sync_binary_%[1]s",
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+		`
+		subdirNoOverrideBp := fmt.Sprintf(templateBp, "no_override")
+		subdirOverrideDefaultDisableBp := fmt.Sprintf(templateBp, "override_default_disable")
+		subdirSyncBp := fmt.Sprintf(templateBp, "override_default_sync")
+		subdirAsyncBp := fmt.Sprintf(templateBp, "override_default_async")
+
+		fs.Merge(android.MockFS{
+			"subdir_no_override/Android.bp":              []byte(subdirNoOverrideBp),
+			"subdir_override_default_disable/Android.bp": []byte(subdirOverrideDefaultDisableBp),
+			"subdir_sync/Android.bp":                     []byte(subdirSyncBp),
+			"subdir_async/Android.bp":                    []byte(subdirAsyncBp),
+		})
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.MemtagHeapExcludePaths = []string{"subdir_override_default_disable"}
+		// "subdir_override_default_disable" is covered by both include and override_default_disable paths. override_default_disable wins.
+		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync", "subdir_override_default_disable"}
+		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async", "subdir_override_default_disable"}
+	}),
+)
+
+func TestSanitizeMemtagHeap(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForTestWithMemtagHeap,
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
diff --git a/cc/sdk.go b/cc/sdk.go
index aec950b..a83e5ad 100644
--- a/cc/sdk.go
+++ b/cc/sdk.go
@@ -35,7 +35,8 @@
 			if !m.UseSdk() && !m.SplitPerApiLevel() {
 				ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
 			}
-			ctx.CreateVariations("sdk")
+			modules := ctx.CreateVariations("sdk")
+			modules[0].(*Module).Properties.IsSdkVariant = true
 		} else if m.UseSdk() || m.SplitPerApiLevel() {
 			modules := ctx.CreateVariations("", "sdk")
 
@@ -75,7 +76,7 @@
 			}
 			ctx.AliasVariation("")
 		}
-	case *snapshot:
+	case *snapshotModule:
 		ctx.CreateVariations("")
 	}
 }
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index a7351a9..9d40ad0 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -18,57 +18,17 @@
 // snapshot mutators and snapshot information maps which are also defined in this file.
 
 import (
-	"path/filepath"
 	"strings"
 
 	"android/soong/android"
+	"android/soong/snapshot"
 
 	"github.com/google/blueprint"
 )
 
-// Defines the specifics of different images to which the snapshot process is applicable, e.g.,
-// vendor, recovery, ramdisk.
-type snapshotImage interface {
-	// Returns true if a snapshot should be generated for this image.
-	shouldGenerateSnapshot(ctx android.SingletonContext) bool
-
-	// Function that returns true if the module is included in this image.
-	// Using a function return instead of a value to prevent early
-	// evalution of a function that may be not be defined.
-	inImage(m LinkableInterface) func() bool
-
-	// Returns true if the module is private and must not be included in the
-	// snapshot. For example VNDK-private modules must return true for the
-	// vendor snapshots. But false for the recovery snapshots.
-	private(m LinkableInterface) bool
-
-	// Returns true if a dir under source tree is an SoC-owned proprietary
-	// directory, such as device/, vendor/, etc.
-	//
-	// For a given snapshot (e.g., vendor, recovery, etc.) if
-	// isProprietaryPath(dir, deviceConfig) returns true, then the module in dir
-	// will be built from sources.
-	isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool
-
-	// Whether to include VNDK in the snapshot for this image.
-	includeVndk() bool
-
-	// Whether a given module has been explicitly excluded from the
-	// snapshot, e.g., using the exclude_from_vendor_snapshot or
-	// exclude_from_recovery_snapshot properties.
-	excludeFromSnapshot(m LinkableInterface) bool
-
-	// Returns true if the build is using a snapshot for this image.
-	isUsingSnapshot(cfg android.DeviceConfig) bool
-
-	// Returns a version of which the snapshot should be used in this target.
-	// This will only be meaningful when isUsingSnapshot is true.
-	targetSnapshotVersion(cfg android.DeviceConfig) string
-
-	// Whether to exclude a given module from the directed snapshot or not.
-	// If the makefile variable DIRECTED_{IMAGE}_SNAPSHOT is true, directed snapshot is turned on,
-	// and only modules listed in {IMAGE}_SNAPSHOT_MODULES will be captured.
-	excludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool
+// This interface overrides snapshot.SnapshotImage to implement cc module specific functions
+type SnapshotImage interface {
+	snapshot.SnapshotImage
 
 	// The image variant name for this snapshot image.
 	// For example, recovery snapshot image will return "recovery", and vendor snapshot image will
@@ -80,106 +40,12 @@
 	moduleNameSuffix() string
 }
 
-type vendorSnapshotImage struct{}
-type recoverySnapshotImage struct{}
-
-type directoryMap map[string]bool
-
-var (
-	// Modules under following directories are ignored. They are OEM's and vendor's
-	// proprietary modules(device/, kernel/, vendor/, and hardware/).
-	defaultDirectoryExcludedMap = directoryMap{
-		"device":   true,
-		"hardware": true,
-		"kernel":   true,
-		"vendor":   true,
-	}
-
-	// Modules under following directories are included as they are in AOSP,
-	// although hardware/ and kernel/ are normally for vendor's own.
-	defaultDirectoryIncludedMap = directoryMap{
-		"kernel/configs":              true,
-		"kernel/prebuilts":            true,
-		"kernel/tests":                true,
-		"hardware/interfaces":         true,
-		"hardware/libhardware":        true,
-		"hardware/libhardware_legacy": true,
-		"hardware/ril":                true,
-	}
-)
-
-func (vendorSnapshotImage) init(ctx android.RegistrationContext) {
-	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
-	ctx.RegisterModuleType("vendor_snapshot", vendorSnapshotFactory)
-	ctx.RegisterModuleType("vendor_snapshot_shared", VendorSnapshotSharedFactory)
-	ctx.RegisterModuleType("vendor_snapshot_static", VendorSnapshotStaticFactory)
-	ctx.RegisterModuleType("vendor_snapshot_header", VendorSnapshotHeaderFactory)
-	ctx.RegisterModuleType("vendor_snapshot_binary", VendorSnapshotBinaryFactory)
-	ctx.RegisterModuleType("vendor_snapshot_object", VendorSnapshotObjectFactory)
-
-	ctx.RegisterSingletonType("vendor-fake-snapshot", VendorFakeSnapshotSingleton)
+type vendorSnapshotImage struct {
+	*snapshot.VendorSnapshotImage
 }
 
-func (vendorSnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
-	// BOARD_VNDK_VERSION must be set to 'current' in order to generate a snapshot.
-	return ctx.DeviceConfig().VndkVersion() == "current"
-}
-
-func (vendorSnapshotImage) inImage(m LinkableInterface) func() bool {
-	return m.InVendor
-}
-
-func (vendorSnapshotImage) private(m LinkableInterface) bool {
-	return m.IsVndkPrivate()
-}
-
-func isDirectoryExcluded(dir string, excludedMap directoryMap, includedMap directoryMap) bool {
-	if dir == "." || dir == "/" {
-		return false
-	}
-	if includedMap[dir] {
-		return false
-	} else if excludedMap[dir] {
-		return true
-	} else if defaultDirectoryIncludedMap[dir] {
-		return false
-	} else if defaultDirectoryExcludedMap[dir] {
-		return true
-	} else {
-		return isDirectoryExcluded(filepath.Dir(dir), excludedMap, includedMap)
-	}
-}
-
-func (vendorSnapshotImage) isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
-	return isDirectoryExcluded(dir, deviceConfig.VendorSnapshotDirsExcludedMap(), deviceConfig.VendorSnapshotDirsIncludedMap())
-}
-
-// vendor snapshot includes static/header libraries with vndk: {enabled: true}.
-func (vendorSnapshotImage) includeVndk() bool {
-	return true
-}
-
-func (vendorSnapshotImage) excludeFromSnapshot(m LinkableInterface) bool {
-	return m.ExcludeFromVendorSnapshot()
-}
-
-func (vendorSnapshotImage) isUsingSnapshot(cfg android.DeviceConfig) bool {
-	vndkVersion := cfg.VndkVersion()
-	return vndkVersion != "current" && vndkVersion != ""
-}
-
-func (vendorSnapshotImage) targetSnapshotVersion(cfg android.DeviceConfig) string {
-	return cfg.VndkVersion()
-}
-
-// returns true iff a given module SHOULD BE EXCLUDED, false if included
-func (vendorSnapshotImage) excludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool {
-	// If we're using full snapshot, not directed snapshot, capture every module
-	if !cfg.DirectedVendorSnapshot() {
-		return false
-	}
-	// Else, checks if name is in VENDOR_SNAPSHOT_MODULES.
-	return !cfg.VendorSnapshotModules()[name]
+type recoverySnapshotImage struct {
+	*snapshot.RecoverySnapshotImage
 }
 
 func (vendorSnapshotImage) imageVariantName(cfg android.DeviceConfig) string {
@@ -190,8 +56,28 @@
 	return VendorSuffix
 }
 
-func (recoverySnapshotImage) init(ctx android.RegistrationContext) {
-	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
+func (recoverySnapshotImage) imageVariantName(cfg android.DeviceConfig) string {
+	return android.RecoveryVariation
+}
+
+func (recoverySnapshotImage) moduleNameSuffix() string {
+	return RecoverySuffix
+}
+
+// Override existing vendor and recovery snapshot for cc module specific extra functions
+var VendorSnapshotImageSingleton vendorSnapshotImage = vendorSnapshotImage{&snapshot.VendorSnapshotImageSingleton}
+var RecoverySnapshotImageSingleton recoverySnapshotImage = recoverySnapshotImage{&snapshot.RecoverySnapshotImageSingleton}
+
+func RegisterVendorSnapshotModules(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("vendor_snapshot", vendorSnapshotFactory)
+	ctx.RegisterModuleType("vendor_snapshot_shared", VendorSnapshotSharedFactory)
+	ctx.RegisterModuleType("vendor_snapshot_static", VendorSnapshotStaticFactory)
+	ctx.RegisterModuleType("vendor_snapshot_header", VendorSnapshotHeaderFactory)
+	ctx.RegisterModuleType("vendor_snapshot_binary", VendorSnapshotBinaryFactory)
+	ctx.RegisterModuleType("vendor_snapshot_object", VendorSnapshotObjectFactory)
+}
+
+func RegisterRecoverySnapshotModules(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("recovery_snapshot", recoverySnapshotFactory)
 	ctx.RegisterModuleType("recovery_snapshot_shared", RecoverySnapshotSharedFactory)
 	ctx.RegisterModuleType("recovery_snapshot_static", RecoverySnapshotStaticFactory)
@@ -200,130 +86,75 @@
 	ctx.RegisterModuleType("recovery_snapshot_object", RecoverySnapshotObjectFactory)
 }
 
-func (recoverySnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
-	// RECOVERY_SNAPSHOT_VERSION must be set to 'current' in order to generate a
-	// snapshot.
-	return ctx.DeviceConfig().RecoverySnapshotVersion() == "current"
-}
-
-func (recoverySnapshotImage) inImage(m LinkableInterface) func() bool {
-	return m.InRecovery
-}
-
-// recovery snapshot does not have private libraries.
-func (recoverySnapshotImage) private(m LinkableInterface) bool {
-	return false
-}
-
-func (recoverySnapshotImage) isProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
-	return isDirectoryExcluded(dir, deviceConfig.RecoverySnapshotDirsExcludedMap(), deviceConfig.RecoverySnapshotDirsIncludedMap())
-}
-
-// recovery snapshot does NOT treat vndk specially.
-func (recoverySnapshotImage) includeVndk() bool {
-	return false
-}
-
-func (recoverySnapshotImage) excludeFromSnapshot(m LinkableInterface) bool {
-	return m.ExcludeFromRecoverySnapshot()
-}
-
-func (recoverySnapshotImage) isUsingSnapshot(cfg android.DeviceConfig) bool {
-	recoverySnapshotVersion := cfg.RecoverySnapshotVersion()
-	return recoverySnapshotVersion != "current" && recoverySnapshotVersion != ""
-}
-
-func (recoverySnapshotImage) targetSnapshotVersion(cfg android.DeviceConfig) string {
-	return cfg.RecoverySnapshotVersion()
-}
-
-func (recoverySnapshotImage) excludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool {
-	// If we're using full snapshot, not directed snapshot, capture every module
-	if !cfg.DirectedRecoverySnapshot() {
-		return false
-	}
-	// Else, checks if name is in RECOVERY_SNAPSHOT_MODULES.
-	return !cfg.RecoverySnapshotModules()[name]
-}
-
-func (recoverySnapshotImage) imageVariantName(cfg android.DeviceConfig) string {
-	return android.RecoveryVariation
-}
-
-func (recoverySnapshotImage) moduleNameSuffix() string {
-	return recoverySuffix
-}
-
-var vendorSnapshotImageSingleton vendorSnapshotImage
-var recoverySnapshotImageSingleton recoverySnapshotImage
-
 func init() {
-	vendorSnapshotImageSingleton.init(android.InitRegistrationContext)
-	recoverySnapshotImageSingleton.init(android.InitRegistrationContext)
+	RegisterVendorSnapshotModules(android.InitRegistrationContext)
+	RegisterRecoverySnapshotModules(android.InitRegistrationContext)
+	android.RegisterMakeVarsProvider(pctx, snapshotMakeVarsProvider)
 }
 
 const (
 	snapshotHeaderSuffix = "_header."
-	snapshotSharedSuffix = "_shared."
-	snapshotStaticSuffix = "_static."
+	SnapshotSharedSuffix = "_shared."
+	SnapshotStaticSuffix = "_static."
 	snapshotBinarySuffix = "_binary."
 	snapshotObjectSuffix = "_object."
+	SnapshotRlibSuffix   = "_rlib."
 )
 
 type SnapshotProperties struct {
 	Header_libs []string `android:"arch_variant"`
 	Static_libs []string `android:"arch_variant"`
 	Shared_libs []string `android:"arch_variant"`
+	Rlibs       []string `android:"arch_variant"`
 	Vndk_libs   []string `android:"arch_variant"`
 	Binaries    []string `android:"arch_variant"`
 	Objects     []string `android:"arch_variant"`
 }
-
-type snapshot struct {
+type snapshotModule struct {
 	android.ModuleBase
 
 	properties SnapshotProperties
 
-	baseSnapshot baseSnapshotDecorator
+	baseSnapshot BaseSnapshotDecorator
 
-	image snapshotImage
+	image SnapshotImage
 }
 
-func (s *snapshot) ImageMutatorBegin(ctx android.BaseModuleContext) {
+func (s *snapshotModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
 	cfg := ctx.DeviceConfig()
-	if !s.image.isUsingSnapshot(cfg) || s.image.targetSnapshotVersion(cfg) != s.baseSnapshot.version() {
+	if !s.image.IsUsingSnapshot(cfg) || s.image.TargetSnapshotVersion(cfg) != s.baseSnapshot.Version() {
 		s.Disable()
 	}
 }
 
-func (s *snapshot) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *snapshotModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return false
 }
 
-func (s *snapshot) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *snapshotModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
 	return false
 }
 
-func (s *snapshot) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *snapshotModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
 	return false
 }
 
-func (s *snapshot) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *snapshotModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
 	return false
 }
 
-func (s *snapshot) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *snapshotModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
 	return false
 }
 
-func (s *snapshot) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (s *snapshotModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
 	return []string{s.image.imageVariantName(ctx.DeviceConfig())}
 }
 
-func (s *snapshot) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (s *snapshotModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
 }
 
-func (s *snapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+func (s *snapshotModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Nothing, the snapshot module is only used to forward dependency information in DepsMutator.
 }
 
@@ -335,13 +166,13 @@
 	return moduleSuffix + versionSuffix
 }
 
-func (s *snapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
+func (s *snapshotModule) DepsMutator(ctx android.BottomUpMutatorContext) {
 	collectSnapshotMap := func(names []string, snapshotSuffix, moduleSuffix string) map[string]string {
 		snapshotMap := make(map[string]string)
 		for _, name := range names {
 			snapshotMap[name] = name +
 				getSnapshotNameSuffix(snapshotSuffix+moduleSuffix,
-					s.baseSnapshot.version(),
+					s.baseSnapshot.Version(),
 					ctx.DeviceConfig().Arches()[0].ArchType.String())
 		}
 		return snapshotMap
@@ -351,8 +182,9 @@
 	headers := collectSnapshotMap(s.properties.Header_libs, snapshotSuffix, snapshotHeaderSuffix)
 	binaries := collectSnapshotMap(s.properties.Binaries, snapshotSuffix, snapshotBinarySuffix)
 	objects := collectSnapshotMap(s.properties.Objects, snapshotSuffix, snapshotObjectSuffix)
-	staticLibs := collectSnapshotMap(s.properties.Static_libs, snapshotSuffix, snapshotStaticSuffix)
-	sharedLibs := collectSnapshotMap(s.properties.Shared_libs, snapshotSuffix, snapshotSharedSuffix)
+	staticLibs := collectSnapshotMap(s.properties.Static_libs, snapshotSuffix, SnapshotStaticSuffix)
+	sharedLibs := collectSnapshotMap(s.properties.Shared_libs, snapshotSuffix, SnapshotSharedSuffix)
+	rlibs := collectSnapshotMap(s.properties.Rlibs, snapshotSuffix, SnapshotRlibSuffix)
 	vndkLibs := collectSnapshotMap(s.properties.Vndk_libs, "", vndkSuffix)
 	for k, v := range vndkLibs {
 		sharedLibs[k] = v
@@ -364,36 +196,55 @@
 		Objects:    objects,
 		StaticLibs: staticLibs,
 		SharedLibs: sharedLibs,
+		Rlibs:      rlibs,
 	})
 }
 
 type SnapshotInfo struct {
-	HeaderLibs, Binaries, Objects, StaticLibs, SharedLibs map[string]string
+	HeaderLibs, Binaries, Objects, StaticLibs, SharedLibs, Rlibs map[string]string
 }
 
 var SnapshotInfoProvider = blueprint.NewMutatorProvider(SnapshotInfo{}, "deps")
 
-var _ android.ImageInterface = (*snapshot)(nil)
+var _ android.ImageInterface = (*snapshotModule)(nil)
+
+func snapshotMakeVarsProvider(ctx android.MakeVarsContext) {
+	snapshotSet := map[string]struct{}{}
+	ctx.VisitAllModules(func(m android.Module) {
+		if s, ok := m.(*snapshotModule); ok {
+			if _, ok := snapshotSet[s.Name()]; ok {
+				// arch variant generates duplicated modules
+				// skip this as we only need to know the path of the module.
+				return
+			}
+			snapshotSet[s.Name()] = struct{}{}
+			imageNameVersion := strings.Split(s.image.imageVariantName(ctx.DeviceConfig()), ".")
+			ctx.Strict(
+				strings.Join([]string{strings.ToUpper(imageNameVersion[0]), s.baseSnapshot.Version(), "SNAPSHOT_DIR"}, "_"),
+				ctx.ModuleDir(s))
+		}
+	})
+}
 
 func vendorSnapshotFactory() android.Module {
-	return snapshotFactory(vendorSnapshotImageSingleton)
+	return snapshotFactory(VendorSnapshotImageSingleton)
 }
 
 func recoverySnapshotFactory() android.Module {
-	return snapshotFactory(recoverySnapshotImageSingleton)
+	return snapshotFactory(RecoverySnapshotImageSingleton)
 }
 
-func snapshotFactory(image snapshotImage) android.Module {
-	snapshot := &snapshot{}
-	snapshot.image = image
-	snapshot.AddProperties(
-		&snapshot.properties,
-		&snapshot.baseSnapshot.baseProperties)
-	android.InitAndroidArchModule(snapshot, android.DeviceSupported, android.MultilibBoth)
-	return snapshot
+func snapshotFactory(image SnapshotImage) android.Module {
+	snapshotModule := &snapshotModule{}
+	snapshotModule.image = image
+	snapshotModule.AddProperties(
+		&snapshotModule.properties,
+		&snapshotModule.baseSnapshot.baseProperties)
+	android.InitAndroidArchModule(snapshotModule, android.DeviceSupported, android.MultilibBoth)
+	return snapshotModule
 }
 
-type baseSnapshotDecoratorProperties struct {
+type BaseSnapshotDecoratorProperties struct {
 	// snapshot version.
 	Version string
 
@@ -408,7 +259,7 @@
 	ModuleSuffix string `blueprint:"mutated"`
 }
 
-// baseSnapshotDecorator provides common basic functions for all snapshot modules, such as snapshot
+// BaseSnapshotDecorator provides common basic functions for all snapshot modules, such as snapshot
 // version, snapshot arch, etc. It also adds a special suffix to Soong module name, so it doesn't
 // collide with source modules. e.g. the following example module,
 //
@@ -420,70 +271,89 @@
 // }
 //
 // will be seen as "libbase.vendor_static.30.arm64" by Soong.
-type baseSnapshotDecorator struct {
-	baseProperties baseSnapshotDecoratorProperties
-	image          snapshotImage
+type BaseSnapshotDecorator struct {
+	baseProperties BaseSnapshotDecoratorProperties
+	Image          SnapshotImage
 }
 
-func (p *baseSnapshotDecorator) Name(name string) string {
+func (p *BaseSnapshotDecorator) Name(name string) string {
 	return name + p.NameSuffix()
 }
 
-func (p *baseSnapshotDecorator) NameSuffix() string {
-	return getSnapshotNameSuffix(p.moduleSuffix(), p.version(), p.arch())
+func (p *BaseSnapshotDecorator) NameSuffix() string {
+	return getSnapshotNameSuffix(p.moduleSuffix(), p.Version(), p.Arch())
 }
 
-func (p *baseSnapshotDecorator) version() string {
+func (p *BaseSnapshotDecorator) Version() string {
 	return p.baseProperties.Version
 }
 
-func (p *baseSnapshotDecorator) arch() string {
+func (p *BaseSnapshotDecorator) Arch() string {
 	return p.baseProperties.Target_arch
 }
 
-func (p *baseSnapshotDecorator) moduleSuffix() string {
+func (p *BaseSnapshotDecorator) moduleSuffix() string {
 	return p.baseProperties.ModuleSuffix
 }
 
-func (p *baseSnapshotDecorator) isSnapshotPrebuilt() bool {
+func (p *BaseSnapshotDecorator) IsSnapshotPrebuilt() bool {
 	return true
 }
 
-func (p *baseSnapshotDecorator) snapshotAndroidMkSuffix() string {
+func (p *BaseSnapshotDecorator) SnapshotAndroidMkSuffix() string {
 	return p.baseProperties.Androidmk_suffix
 }
 
-func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext) {
-	coreVariations := append(ctx.Target().Variations(), blueprint.Variation{
+func (p *BaseSnapshotDecorator) SetSnapshotAndroidMkSuffix(ctx android.ModuleContext, variant string) {
+	// If there are any 2 or more variations among {core, product, vendor, recovery}
+	// we have to add the androidmk suffix to avoid duplicate modules with the same
+	// name.
+	variations := append(ctx.Target().Variations(), blueprint.Variation{
 		Mutator:   "image",
 		Variation: android.CoreVariation})
 
-	if ctx.OtherModuleFarDependencyVariantExists(coreVariations, ctx.Module().(*Module).BaseModuleName()) {
-		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(LinkableInterface).BaseModuleName()) {
+		p.baseProperties.Androidmk_suffix = p.Image.moduleNameSuffix()
 		return
 	}
 
-	// If there is no matching core variation, there could still be a
-	// product variation, for example if a module is product specific and
-	// vendor available. In that case, we also want to add the androidmk
-	// suffix.
-
-	productVariations := append(ctx.Target().Variations(), blueprint.Variation{
+	variations = append(ctx.Target().Variations(), blueprint.Variation{
 		Mutator:   "image",
 		Variation: ProductVariationPrefix + ctx.DeviceConfig().PlatformVndkVersion()})
 
-	if ctx.OtherModuleFarDependencyVariantExists(productVariations, ctx.Module().(*Module).BaseModuleName()) {
-		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(LinkableInterface).BaseModuleName()) {
+		p.baseProperties.Androidmk_suffix = p.Image.moduleNameSuffix()
 		return
 	}
 
+	images := []SnapshotImage{VendorSnapshotImageSingleton, RecoverySnapshotImageSingleton}
+
+	for _, image := range images {
+		if p.Image == image {
+			continue
+		}
+		variations = append(ctx.Target().Variations(), blueprint.Variation{
+			Mutator:   "image",
+			Variation: image.imageVariantName(ctx.DeviceConfig())})
+
+		if ctx.OtherModuleFarDependencyVariantExists(variations,
+			ctx.Module().(LinkableInterface).BaseModuleName()+
+				getSnapshotNameSuffix(
+					image.moduleNameSuffix()+variant,
+					p.Version(),
+					ctx.DeviceConfig().Arches()[0].ArchType.String())) {
+			p.baseProperties.Androidmk_suffix = p.Image.moduleNameSuffix()
+			return
+		}
+	}
+
 	p.baseProperties.Androidmk_suffix = ""
 }
 
 // Call this with a module suffix after creating a snapshot module, such as
 // vendorSnapshotSharedSuffix, recoverySnapshotBinarySuffix, etc.
-func (p *baseSnapshotDecorator) init(m *Module, image snapshotImage, moduleSuffix string) {
-	p.image = image
+func (p *BaseSnapshotDecorator) Init(m LinkableInterface, image SnapshotImage, moduleSuffix string) {
+	p.Image = image
 	p.baseProperties.ModuleSuffix = image.moduleNameSuffix() + moduleSuffix
 	m.AddProperties(&p.baseProperties)
 	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
@@ -493,8 +363,8 @@
 
 // vendorSnapshotLoadHook disables snapshots if it's not BOARD_VNDK_VERSION.
 // As vendor snapshot is only for vendor, such modules won't be used at all.
-func vendorSnapshotLoadHook(ctx android.LoadHookContext, p *baseSnapshotDecorator) {
-	if p.version() != ctx.DeviceConfig().VndkVersion() {
+func vendorSnapshotLoadHook(ctx android.LoadHookContext, p *BaseSnapshotDecorator) {
+	if p.Version() != ctx.DeviceConfig().VndkVersion() {
 		ctx.Module().Disable()
 		return
 	}
@@ -509,7 +379,7 @@
 // include directories, c flags, sanitize dependency information, etc.
 //
 // These modules are auto-generated by development/vendor_snapshot/update.py.
-type snapshotLibraryProperties struct {
+type SnapshotLibraryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 
@@ -535,14 +405,14 @@
 }
 
 type snapshotLibraryDecorator struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	*libraryDecorator
-	properties          snapshotLibraryProperties
+	properties          SnapshotLibraryProperties
 	sanitizerProperties struct {
 		CfiEnabled bool `blueprint:"mutated"`
 
 		// Library flags for cfi variant.
-		Cfi snapshotLibraryProperties `android:"arch_variant"`
+		Cfi SnapshotLibraryProperties `android:"arch_variant"`
 	}
 }
 
@@ -551,9 +421,9 @@
 	return p.libraryDecorator.linkerFlags(ctx, flags)
 }
 
-func (p *snapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
-	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
+	if len(arches) == 0 || arches[0].ArchType.String() != p.Arch() {
 		return false
 	}
 	if !p.header() && p.properties.Src == nil {
@@ -566,7 +436,16 @@
 // As snapshots are prebuilts, this just returns the prebuilt binary after doing things which are
 // done by normal library decorator, e.g. exporting flags.
 func (p *snapshotLibraryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	var variant string
+	if p.shared() {
+		variant = SnapshotSharedSuffix
+	} else if p.static() {
+		variant = SnapshotStaticSuffix
+	} else {
+		variant = snapshotHeaderSuffix
+	}
+
+	p.SetSnapshotAndroidMkSuffix(ctx, variant)
 
 	if p.header() {
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
@@ -576,7 +455,7 @@
 		p.properties = p.sanitizerProperties.Cfi
 	}
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -597,18 +476,16 @@
 
 	if p.shared() {
 		libName := in.Base()
-		builderFlags := flagsToBuilderFlags(flags)
 
 		// Optimize out relinking against shared libraries whose interface hasn't changed by
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		TransformSharedObjectToToc(ctx, in, tocFile)
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           in,
-			UnstrippedSharedLibrary: p.unstrippedOutputFile,
-			Target:                  ctx.Target(),
+			SharedLibrary: in,
+			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
 		})
@@ -629,7 +506,7 @@
 }
 
 func (p *snapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
-	if p.matchesWithDevice(ctx.DeviceConfig()) && (p.shared() || p.static()) {
+	if p.MatchesWithDevice(ctx.DeviceConfig()) && p.shared() {
 		p.baseInstaller.install(ctx, file)
 	}
 }
@@ -659,7 +536,7 @@
 	}
 }
 
-func snapshotLibraryFactory(image snapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+func snapshotLibraryFactory(image SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
@@ -682,7 +559,7 @@
 	module.linker = prebuilt
 	module.installer = prebuilt
 
-	prebuilt.init(module, image, moduleSuffix)
+	prebuilt.Init(module, image, moduleSuffix)
 	module.AddProperties(
 		&prebuilt.properties,
 		&prebuilt.sanitizerProperties,
@@ -696,7 +573,7 @@
 // overrides the vendor variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, SnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -706,7 +583,7 @@
 // overrides the recovery variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, SnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -716,7 +593,7 @@
 // overrides the vendor variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, SnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -726,7 +603,7 @@
 // overrides the recovery variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, SnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -736,7 +613,7 @@
 // overrides the vendor variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -746,7 +623,7 @@
 // overrides the recovery variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(RecoverySnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -766,13 +643,13 @@
 }
 
 type snapshotBinaryDecorator struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	*binaryDecorator
 	properties snapshotBinaryProperties
 }
 
-func (p *snapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
-	if config.DeviceArch() != p.arch() {
+func (p *snapshotBinaryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
+	if config.DeviceArch() != p.Arch() {
 		return false
 	}
 	if p.properties.Src == nil {
@@ -784,9 +661,9 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotBinaryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	p.SetSnapshotAndroidMkSuffix(ctx, snapshotBinarySuffix)
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -803,6 +680,9 @@
 		Input:       in,
 	})
 
+	// binary snapshots need symlinking
+	p.setSymlinkList(ctx)
+
 	return outputFile
 }
 
@@ -814,17 +694,17 @@
 // development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_binary
 // overrides the vendor variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func VendorSnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(vendorSnapshotImageSingleton, snapshotBinarySuffix)
+	return snapshotBinaryFactory(VendorSnapshotImageSingleton, snapshotBinarySuffix)
 }
 
 // recovery_snapshot_binary is a special prebuilt executable binary which is auto-generated by
 // development/vendor_snapshot/update.py. As a part of recovery snapshot, recovery_snapshot_binary
 // overrides the recovery variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func RecoverySnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(recoverySnapshotImageSingleton, snapshotBinarySuffix)
+	return snapshotBinaryFactory(RecoverySnapshotImageSingleton, snapshotBinarySuffix)
 }
 
-func snapshotBinaryFactory(image snapshotImage, moduleSuffix string) android.Module {
+func snapshotBinaryFactory(image SnapshotImage, moduleSuffix string) android.Module {
 	module, binary := NewBinary(android.DeviceSupported)
 	binary.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	binary.baseLinker.Properties.Nocrt = BoolPtr(true)
@@ -843,7 +723,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, image, moduleSuffix)
+	prebuilt.Init(module, image, moduleSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -861,13 +741,13 @@
 }
 
 type snapshotObjectLinker struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	objectLinker
 	properties vendorSnapshotObjectProperties
 }
 
-func (p *snapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
-	if config.DeviceArch() != p.arch() {
+func (p *snapshotObjectLinker) MatchesWithDevice(config android.DeviceConfig) bool {
+	if config.DeviceArch() != p.Arch() {
 		return false
 	}
 	if p.properties.Src == nil {
@@ -879,9 +759,9 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotObjectLinker) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	p.SetSnapshotAndroidMkSuffix(ctx, snapshotObjectSuffix)
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -896,7 +776,7 @@
 // development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_object
 // overrides the vendor variant of the cc object with the same name, if BOARD_VNDK_VERSION is set.
 func VendorSnapshotObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.DeviceSupported)
 
 	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
@@ -905,7 +785,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotImageSingleton, snapshotObjectSuffix)
+	prebuilt.Init(module, VendorSnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -914,7 +794,7 @@
 // development/vendor_snapshot/update.py. As a part of recovery snapshot, recovery_snapshot_object
 // overrides the recovery variant of the cc object with the same name, if BOARD_VNDK_VERSION is set.
 func RecoverySnapshotObjectFactory() android.Module {
-	module := newObject()
+	module := newObject(android.DeviceSupported)
 
 	prebuilt := &snapshotObjectLinker{
 		objectLinker: objectLinker{
@@ -923,19 +803,19 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
+	prebuilt.Init(module, RecoverySnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
 
-type snapshotInterface interface {
-	matchesWithDevice(config android.DeviceConfig) bool
-	isSnapshotPrebuilt() bool
-	version() string
-	snapshotAndroidMkSuffix() string
+type SnapshotInterface interface {
+	MatchesWithDevice(config android.DeviceConfig) bool
+	IsSnapshotPrebuilt() bool
+	Version() string
+	SnapshotAndroidMkSuffix() string
 }
 
-var _ snapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
-var _ snapshotInterface = (*snapshotLibraryDecorator)(nil)
-var _ snapshotInterface = (*snapshotBinaryDecorator)(nil)
-var _ snapshotInterface = (*snapshotObjectLinker)(nil)
+var _ SnapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotLibraryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotBinaryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotObjectLinker)(nil)
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index 8eb6164..de50ef5 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	headerExts = []string{".h", ".hh", ".hpp", ".hxx", ".h++", ".inl", ".inc", ".ipp", ".h.generic"}
+	HeaderExts = []string{".h", ".hh", ".hpp", ".hxx", ".h++", ".inl", ".inc", ".ipp", ".h.generic"}
 )
 
 func (m *Module) IsSnapshotLibrary() bool {
@@ -53,6 +53,10 @@
 	return m.Properties.SnapshotSharedLibs
 }
 
+func (m *Module) SnapshotStaticLibs() []string {
+	return m.Properties.SnapshotStaticLibs
+}
+
 // snapshotLibraryInterface is an interface for libraries captured to VNDK / vendor snapshots.
 type snapshotLibraryInterface interface {
 	libraryInterface
@@ -109,8 +113,8 @@
 		return ctx.Config().VndkSnapshotBuildArtifacts()
 	}
 
-	for _, image := range []snapshotImage{vendorSnapshotImageSingleton, recoverySnapshotImageSingleton} {
-		if isSnapshotAware(ctx.DeviceConfig(), m, image.isProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()), apexInfo, image) {
+	for _, image := range []SnapshotImage{VendorSnapshotImageSingleton, RecoverySnapshotImageSingleton} {
+		if isSnapshotAware(ctx.DeviceConfig(), m, image.IsProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()), apexInfo, image) {
 			return true
 		}
 	}
diff --git a/cc/stl.go b/cc/stl.go
index 75921c6..0f2a878 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -15,8 +15,9 @@
 package cc
 
 import (
-	"android/soong/android"
 	"fmt"
+
+	"android/soong/android"
 )
 
 func getNdkStlFamily(m LinkableInterface) string {
@@ -92,26 +93,6 @@
 				ctx.ModuleErrorf("stl: %q is not a supported STL for windows", s)
 				return ""
 			}
-		} else if ctx.Fuchsia() {
-			switch s {
-			case "c++_static":
-				return "libc++_static"
-			case "c++_shared":
-				return "libc++"
-			case "libc++", "libc++_static":
-				return s
-			case "none":
-				return ""
-			case "":
-				if ctx.static() {
-					return "libc++_static"
-				} else {
-					return "libc++"
-				}
-			default:
-				ctx.ModuleErrorf("stl: %q is not a supported STL on Fuchsia", s)
-				return ""
-			}
 		} else {
 			switch s {
 			case "libc++", "libc++_static":
@@ -199,7 +180,9 @@
 			deps.StaticLibs = append(deps.StaticLibs, stl.Properties.SelectedStl, "ndk_libc++abi")
 		}
 		if needsLibAndroidSupport(ctx) {
-			deps.StaticLibs = append(deps.StaticLibs, "ndk_libandroid_support")
+			// Use LateStaticLibs for ndk_libandroid_support so that its include directories
+			// come after ndk_libc++_static or ndk_libc++_shared.
+			deps.LateStaticLibs = append(deps.LateStaticLibs, "ndk_libandroid_support")
 		}
 		deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
 	default:
diff --git a/cc/strip.go b/cc/strip.go
index b1f34bb..c60e135 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -59,6 +59,7 @@
 	return !forceDisable && (forceEnable || defaultEnable)
 }
 
+// Keep this consistent with //build/bazel/rules/stripped_shared_library.bzl.
 func (stripper *Stripper) strip(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
 	flags StripFlags, isStaticLib bool) {
 	if actx.Darwin() {
diff --git a/cc/stub_library.go b/cc/stub_library.go
index 1722c80..76da782 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"sort"
 	"strings"
 
 	"android/soong/android"
@@ -27,6 +28,8 @@
 
 type stubLibraries struct {
 	stubLibraryMap map[string]bool
+
+	apiListCoverageXmlPaths []string
 }
 
 // Check if the module defines stub, or itself is stub
@@ -53,6 +56,11 @@
 					s.stubLibraryMap[name] = true
 				}
 			}
+			if m.library != nil {
+				if p := m.library.getAPIListCoverageXMLPath().String(); p != "" {
+					s.apiListCoverageXmlPaths = append(s.apiListCoverageXmlPaths, p)
+				}
+			}
 		}
 	})
 }
@@ -66,4 +74,8 @@
 func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
 	// Convert stub library file names into Makefile variable.
 	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+
+	// Export the list of API XML files to Make.
+	sort.Strings(s.apiListCoverageXmlPaths)
+	ctx.Strict("SOONG_CC_API_XML", strings.Join(s.apiListCoverageXmlPaths, " "))
 }
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 5678e7d..f8d1841 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -14,18 +14,22 @@
 # limitations under the License.
 #
 """Parser for Android's version script information."""
-from dataclasses import dataclass
+from __future__ import annotations
+
+from dataclasses import dataclass, field
 import logging
 import re
 from typing import (
     Dict,
     Iterable,
+    Iterator,
     List,
     Mapping,
     NewType,
     Optional,
     TextIO,
     Tuple,
+    Union,
 )
 
 
@@ -52,11 +56,52 @@
 
 
 @dataclass
+class Tags:
+    """Container class for the tags attached to a symbol or version."""
+
+    tags: tuple[Tag, ...] = field(default_factory=tuple)
+
+    @classmethod
+    def from_strs(cls, strs: Iterable[str]) -> Tags:
+        """Constructs tags from a collection of strings.
+
+        Does not decode API levels.
+        """
+        return Tags(tuple(Tag(s) for s in strs))
+
+    def __contains__(self, tag: Union[Tag, str]) -> bool:
+        return tag in self.tags
+
+    def __iter__(self) -> Iterator[Tag]:
+        yield from self.tags
+
+    @property
+    def has_mode_tags(self) -> bool:
+        """Returns True if any mode tags (apex, llndk, etc) are set."""
+        return self.has_apex_tags or self.has_llndk_tags
+
+    @property
+    def has_apex_tags(self) -> bool:
+        """Returns True if any APEX tags are set."""
+        return 'apex' in self.tags or 'systemapi' in self.tags
+
+    @property
+    def has_llndk_tags(self) -> bool:
+        """Returns True if any LL-NDK tags are set."""
+        return 'llndk' in self.tags
+
+    @property
+    def has_platform_only_tags(self) -> bool:
+        """Returns True if any platform-only tags are set."""
+        return 'platform-only' in self.tags
+
+
+@dataclass
 class Symbol:
     """A symbol definition from a symbol file."""
 
     name: str
-    tags: List[Tag]
+    tags: Tags
 
 
 @dataclass
@@ -65,14 +110,22 @@
 
     name: str
     base: Optional[str]
-    tags: List[Tag]
+    tags: Tags
     symbols: List[Symbol]
 
+    @property
+    def is_private(self) -> bool:
+        """Returns True if this version block is private (platform only)."""
+        return self.name.endswith('_PRIVATE') or self.name.endswith('_PLATFORM')
 
-def get_tags(line: str) -> List[Tag]:
+
+def get_tags(line: str, api_map: ApiMap) -> Tags:
     """Returns a list of all tags on this line."""
     _, _, all_tags = line.strip().partition('#')
-    return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()]
+    return Tags(tuple(
+        decode_api_level_tag(Tag(e), api_map)
+        for e in re.split(r'\s+', all_tags) if e.strip()
+    ))
 
 
 def is_api_level_tag(tag: Tag) -> bool:
@@ -104,24 +157,21 @@
     return api_map[api]
 
 
-def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]:
-    """Decodes API level code names in a list of tags.
+def decode_api_level_tag(tag: Tag, api_map: ApiMap) -> Tag:
+    """Decodes API level code name in a tag.
 
     Raises:
         ParseError: An unknown version name was found in a tag.
     """
-    decoded_tags = list(tags)
-    for idx, tag in enumerate(tags):
-        if not is_api_level_tag(tag):
-            continue
-        name, value = split_tag(tag)
+    if not is_api_level_tag(tag):
+        return tag
 
-        try:
-            decoded = str(decode_api_level(value, api_map))
-            decoded_tags[idx] = Tag('='.join([name, decoded]))
-        except KeyError:
-            raise ParseError(f'Unknown version name in tag: {tag}')
-    return decoded_tags
+    name, value = split_tag(tag)
+    try:
+        decoded = str(decode_api_level(value, api_map))
+        return Tag(f'{name}={decoded}')
+    except KeyError as ex:
+        raise ParseError(f'Unknown version name in tag: {tag}') from ex
 
 
 def split_tag(tag: Tag) -> Tuple[str, str]:
@@ -149,55 +199,52 @@
     return split_tag(tag)[1]
 
 
-def version_is_private(version: str) -> bool:
-    """Returns True if the version name should be treated as private."""
-    return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
+def _should_omit_tags(tags: Tags, arch: Arch, api: int, llndk: bool,
+                      apex: bool) -> bool:
+    """Returns True if the tagged object should be omitted.
+
+    This defines the rules shared between version tagging and symbol tagging.
+    """
+    # The apex and llndk tags will only exclude APIs from other modes. If in
+    # APEX or LLNDK mode and neither tag is provided, we fall back to the
+    # default behavior because all NDK symbols are implicitly available to APEX
+    # and LLNDK.
+    if tags.has_mode_tags:
+        if not apex and not llndk:
+            return True
+        if apex and not tags.has_apex_tags:
+            return True
+        if llndk and not tags.has_llndk_tags:
+            return True
+    if not symbol_in_arch(tags, arch):
+        return True
+    if not symbol_in_api(tags, arch, api):
+        return True
+    return False
 
 
 def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
                         apex: bool) -> bool:
-    """Returns True if the version section should be ommitted.
+    """Returns True if the version section should be omitted.
 
     We want to omit any sections that do not have any symbols we'll have in the
     stub library. Sections that contain entirely future symbols or only symbols
     for certain architectures.
     """
-    if version_is_private(version.name):
+    if version.is_private:
         return True
-    if 'platform-only' in version.tags:
+    if version.tags.has_platform_only_tags:
         return True
-
-    no_llndk_no_apex = ('llndk' not in version.tags
-                        and 'apex' not in version.tags)
-    keep = no_llndk_no_apex or \
-           ('llndk' in version.tags and llndk) or \
-           ('apex' in version.tags and apex)
-    if not keep:
-        return True
-    if not symbol_in_arch(version.tags, arch):
-        return True
-    if not symbol_in_api(version.tags, arch, api):
-        return True
-    return False
+    return _should_omit_tags(version.tags, arch, api, llndk, apex)
 
 
 def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
                        apex: bool) -> bool:
     """Returns True if the symbol should be omitted."""
-    no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags
-    keep = no_llndk_no_apex or \
-           ('llndk' in symbol.tags and llndk) or \
-           ('apex' in symbol.tags and apex)
-    if not keep:
-        return True
-    if not symbol_in_arch(symbol.tags, arch):
-        return True
-    if not symbol_in_api(symbol.tags, arch, api):
-        return True
-    return False
+    return _should_omit_tags(symbol.tags, arch, api, llndk, apex)
 
 
-def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool:
+def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
     """Returns true if the symbol is present for the given architecture."""
     has_arch_tags = False
     for tag in tags:
@@ -282,7 +329,7 @@
     def parse(self) -> List[Version]:
         """Parses the symbol file and returns a list of Version objects."""
         versions = []
-        while self.next_line() != '':
+        while self.next_line():
             assert self.current_line is not None
             if '{' in self.current_line:
                 versions.append(self.parse_version())
@@ -325,12 +372,11 @@
         """Parses a single version section and returns a Version object."""
         assert self.current_line is not None
         name = self.current_line.split('{')[0].strip()
-        tags = get_tags(self.current_line)
-        tags = decode_api_level_tags(tags, self.api_map)
+        tags = get_tags(self.current_line, self.api_map)
         symbols: List[Symbol] = []
         global_scope = True
         cpp_symbols = False
-        while self.next_line() != '':
+        while self.next_line():
             if '}' in self.current_line:
                 # Line is something like '} BASE; # tags'. Both base and tags
                 # are optional here.
@@ -373,8 +419,7 @@
                 'Wildcard global symbols are not permitted.')
         # Line is now in the format "<symbol-name>; # tags"
         name, _, _ = self.current_line.strip().partition(';')
-        tags = get_tags(self.current_line)
-        tags = decode_api_level_tags(tags, self.api_map)
+        tags = get_tags(self.current_line, self.api_map)
         return Symbol(name, tags)
 
     def next_line(self) -> str:
@@ -383,11 +428,11 @@
         A return value of '' indicates EOF.
         """
         line = self.input_file.readline()
-        while line.strip() == '' or line.strip().startswith('#'):
+        while not line.strip() or line.strip().startswith('#'):
             line = self.input_file.readline()
 
             # We want to skip empty lines, but '' indicates EOF.
-            if line == '':
+            if not line:
                 break
         self.current_line = line
         return self.current_line
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index 92b1399..c1e8219 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -19,7 +19,7 @@
 import unittest
 
 import symbolfile
-from symbolfile import Arch, Tag
+from symbolfile import Arch, Tag, Tags, Version
 
 # pylint: disable=missing-docstring
 
@@ -35,12 +35,14 @@
 
 class TagsTest(unittest.TestCase):
     def test_get_tags_no_tags(self) -> None:
-        self.assertEqual([], symbolfile.get_tags(''))
-        self.assertEqual([], symbolfile.get_tags('foo bar baz'))
+        self.assertEqual(Tags(), symbolfile.get_tags('', {}))
+        self.assertEqual(Tags(), symbolfile.get_tags('foo bar baz', {}))
 
     def test_get_tags(self) -> None:
-        self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar'))
-        self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz'))
+        self.assertEqual(Tags.from_strs(['foo', 'bar']),
+                         symbolfile.get_tags('# foo bar', {}))
+        self.assertEqual(Tags.from_strs(['bar', 'baz']),
+                         symbolfile.get_tags('foo # bar baz', {}))
 
     def test_split_tag(self) -> None:
         self.assertTupleEqual(('foo', 'bar'),
@@ -77,12 +79,14 @@
         }
 
         tags = [
-            Tag('introduced=9'),
-            Tag('introduced-arm=14'),
-            Tag('versioned=16'),
-            Tag('arm'),
-            Tag('introduced=O'),
-            Tag('introduced=P'),
+            symbolfile.decode_api_level_tag(t, api_map) for t in (
+                Tag('introduced=9'),
+                Tag('introduced-arm=14'),
+                Tag('versioned=16'),
+                Tag('arm'),
+                Tag('introduced=O'),
+                Tag('introduced=P'),
+            )
         ]
         expected_tags = [
             Tag('introduced=9'),
@@ -92,33 +96,37 @@
             Tag('introduced=9000'),
             Tag('introduced=9001'),
         ]
-        self.assertListEqual(
-            expected_tags, symbolfile.decode_api_level_tags(tags, api_map))
+        self.assertListEqual(expected_tags, tags)
 
         with self.assertRaises(symbolfile.ParseError):
-            symbolfile.decode_api_level_tags([Tag('introduced=O')], {})
+            symbolfile.decode_api_level_tag(Tag('introduced=O'), {})
 
 
 class PrivateVersionTest(unittest.TestCase):
     def test_version_is_private(self) -> None:
-        self.assertFalse(symbolfile.version_is_private('foo'))
-        self.assertFalse(symbolfile.version_is_private('PRIVATE'))
-        self.assertFalse(symbolfile.version_is_private('PLATFORM'))
-        self.assertFalse(symbolfile.version_is_private('foo_private'))
-        self.assertFalse(symbolfile.version_is_private('foo_platform'))
-        self.assertFalse(symbolfile.version_is_private('foo_PRIVATE_'))
-        self.assertFalse(symbolfile.version_is_private('foo_PLATFORM_'))
+        def mock_version(name: str) -> Version:
+            return Version(name, base=None, tags=Tags(), symbols=[])
 
-        self.assertTrue(symbolfile.version_is_private('foo_PRIVATE'))
-        self.assertTrue(symbolfile.version_is_private('foo_PLATFORM'))
+        self.assertFalse(mock_version('foo').is_private)
+        self.assertFalse(mock_version('PRIVATE').is_private)
+        self.assertFalse(mock_version('PLATFORM').is_private)
+        self.assertFalse(mock_version('foo_private').is_private)
+        self.assertFalse(mock_version('foo_platform').is_private)
+        self.assertFalse(mock_version('foo_PRIVATE_').is_private)
+        self.assertFalse(mock_version('foo_PLATFORM_').is_private)
+
+        self.assertTrue(mock_version('foo_PRIVATE').is_private)
+        self.assertTrue(mock_version('foo_PLATFORM').is_private)
 
 
 class SymbolPresenceTest(unittest.TestCase):
     def test_symbol_in_arch(self) -> None:
-        self.assertTrue(symbolfile.symbol_in_arch([], Arch('arm')))
-        self.assertTrue(symbolfile.symbol_in_arch([Tag('arm')], Arch('arm')))
+        self.assertTrue(symbolfile.symbol_in_arch(Tags(), Arch('arm')))
+        self.assertTrue(
+            symbolfile.symbol_in_arch(Tags.from_strs(['arm']), Arch('arm')))
 
-        self.assertFalse(symbolfile.symbol_in_arch([Tag('x86')], Arch('arm')))
+        self.assertFalse(
+            symbolfile.symbol_in_arch(Tags.from_strs(['x86']), Arch('arm')))
 
     def test_symbol_in_api(self) -> None:
         self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9))
@@ -197,81 +205,99 @@
     def test_omit_private(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PRIVATE', None, [], []), Arch('arm'),
-                9, False, False))
+                symbolfile.Version('foo_PRIVATE', None, Tags(), []),
+                Arch('arm'), 9, False, False))
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'),
-                9, False, False))
+                symbolfile.Version('foo_PLATFORM', None, Tags(), []),
+                Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('platform-only')], []),
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['platform-only']), []),
                 Arch('arm'), 9, False, False))
 
     def test_omit_llndk(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, True,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                True, False))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
                 Arch('arm'), 9, True, False))
 
     def test_omit_apex(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('apex')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                True))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, True))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('apex')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
                 Arch('arm'), 9, False, True))
 
+    def test_omit_systemapi(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
+                                   []), Arch('arm'), 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, True))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
+                                   []), Arch('arm'), 9, False, True))
+
     def test_omit_arch(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'),
-                9, False, False))
-
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'),
-                9, False, False))
-
-    def test_omit_api(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('introduced=9')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['arm']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('introduced=14')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['x86']), []),
+                Arch('arm'), 9, False, False))
+
+    def test_omit_api(self) -> None:
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['introduced=9']), []),
+                Arch('arm'), 9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['introduced=14']), []),
                 Arch('arm'), 9, False, False))
 
 
@@ -279,58 +305,72 @@
     def test_omit_llndk(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9,
-                False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
+                Arch('arm'), 9, False, False))
 
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, True, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, True,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
+                Arch('arm'), 9, True, False))
 
     def test_omit_apex(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
+                Arch('arm'), 9, False, False))
 
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, True))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
-                True))
+                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
+                Arch('arm'), 9, False, True))
+
+    def test_omit_systemapi(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
+                Arch('arm'), 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
+                                          Arch('arm'), 9, False, True))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
+                Arch('arm'), 9, False, True))
 
     def test_omit_arch(self) -> None:
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('arm')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['arm'])), Arch('arm'),
+                9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['x86'])), Arch('arm'),
+                9, False, False))
 
     def test_omit_api(self) -> None:
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('introduced=9')]), Arch('arm'),
-                9, False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['introduced=9'])),
+                Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('introduced=14')]), Arch('arm'),
-                9, False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
+                Arch('arm'), 9, False, False))
 
 
 class SymbolFileParseTest(unittest.TestCase):
@@ -376,11 +416,11 @@
         version = parser.parse_version()
         self.assertEqual('VERSION_1', version.name)
         self.assertIsNone(version.base)
-        self.assertEqual(['foo', 'bar'], version.tags)
+        self.assertEqual(Tags.from_strs(['foo', 'bar']), version.tags)
 
         expected_symbols = [
-            symbolfile.Symbol('baz', []),
-            symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]),
+            symbolfile.Symbol('baz', Tags()),
+            symbolfile.Symbol('qux', Tags.from_strs(['woodly', 'doodly'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -388,7 +428,7 @@
         version = parser.parse_version()
         self.assertEqual('VERSION_2', version.name)
         self.assertEqual('VERSION_1', version.base)
-        self.assertEqual([], version.tags)
+        self.assertEqual(Tags(), version.tags)
 
     def test_parse_version_eof(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
@@ -423,12 +463,12 @@
         parser.next_line()
         symbol = parser.parse_symbol()
         self.assertEqual('foo', symbol.name)
-        self.assertEqual([], symbol.tags)
+        self.assertEqual(Tags(), symbol.tags)
 
         parser.next_line()
         symbol = parser.parse_symbol()
         self.assertEqual('bar', symbol.name)
-        self.assertEqual(['baz', 'qux'], symbol.tags)
+        self.assertEqual(Tags.from_strs(['baz', 'qux']), symbol.tags)
 
     def test_wildcard_symbol_global(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
@@ -497,14 +537,15 @@
         versions = parser.parse()
 
         expected = [
-            symbolfile.Version('VERSION_1', None, [], [
-                symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', [Tag('baz')]),
+            symbolfile.Version('VERSION_1', None, Tags(), [
+                symbolfile.Symbol('foo', Tags()),
+                symbolfile.Symbol('bar', Tags.from_strs(['baz'])),
             ]),
-            symbolfile.Version('VERSION_2', 'VERSION_1', [Tag('wasd')], [
-                symbolfile.Symbol('woodly', []),
-                symbolfile.Symbol('doodly', [Tag('asdf')]),
-            ]),
+            symbolfile.Version(
+                'VERSION_2', 'VERSION_1', Tags.from_strs(['wasd']), [
+                    symbolfile.Symbol('woodly', Tags()),
+                    symbolfile.Symbol('doodly', Tags.from_strs(['asdf'])),
+                ]),
         ]
 
         self.assertEqual(expected, versions)
@@ -527,10 +568,10 @@
         self.assertIsNone(version.base)
 
         expected_symbols = [
-            symbolfile.Symbol('foo', []),
-            symbolfile.Symbol('bar', [Tag('llndk')]),
-            symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]),
-            symbolfile.Symbol('qux', [Tag('apex')]),
+            symbolfile.Symbol('foo', Tags()),
+            symbolfile.Symbol('bar', Tags.from_strs(['llndk'])),
+            symbolfile.Symbol('baz', Tags.from_strs(['llndk', 'apex'])),
+            symbolfile.Symbol('qux', Tags.from_strs(['apex'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
diff --git a/cc/test.go b/cc/test.go
index 047a69e..ead7877 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -25,7 +25,8 @@
 	"android/soong/tradefed"
 )
 
-type TestProperties struct {
+// TestLinkerProperties properties to be registered via the linker
+type TestLinkerProperties struct {
 	// if set, build against the gtest library. Defaults to true.
 	Gtest *bool
 
@@ -33,6 +34,12 @@
 	Isolated *bool
 }
 
+// TestInstallerProperties properties to be registered via the installer
+type TestInstallerProperties struct {
+	// list of compatibility suites (for example "cts", "vts") that the module should be installed into.
+	Test_suites []string `android:"arch_variant"`
+}
+
 // Test option struct.
 type TestOptions struct {
 	// The UID that you want to run the test as on a device.
@@ -80,9 +87,8 @@
 	// list of shared library modules that should be installed alongside the test
 	Data_libs []string `android:"arch_variant"`
 
-	// list of compatibility suites (for example "cts", "vts") that the module should be
-	// installed into.
-	Test_suites []string `android:"arch_variant"`
+	// list of binary modules that should be installed alongside the test
+	Data_bins []string `android:"arch_variant"`
 
 	// the name of the test configuration (for example "AndroidTest.xml") that should be
 	// installed with the module.
@@ -102,11 +108,6 @@
 	// Add RunCommandTargetPreparer to stop framework before the test and start it after the test.
 	Disable_framework *bool
 
-	// Add ShippingApiLevelModuleController to auto generated test config. If the device properties
-	// for the shipping api level is less than the test_min_api_level, skip this module.
-	// Deprecated (b/187258404). Use test_options.min_shipping_api_level instead.
-	Test_min_api_level *int64
-
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -115,6 +116,9 @@
 	// Add parameterized mainline modules to auto generated test config. The options will be
 	// handled by TradeFed to download and install the specified modules on the device.
 	Test_mainline_modules []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 func init() {
@@ -242,12 +246,14 @@
 }
 
 type testDecorator struct {
-	Properties TestProperties
-	linker     *baseLinker
+	LinkerProperties    TestLinkerProperties
+	InstallerProperties TestInstallerProperties
+	installer           *baseInstaller
+	linker              *baseLinker
 }
 
 func (test *testDecorator) gtest() bool {
-	return BoolDefault(test.Properties.Gtest, true)
+	return BoolDefault(test.LinkerProperties.Gtest, true)
 }
 
 func (test *testDecorator) testBinary() bool {
@@ -282,7 +288,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.Properties.Isolated, false) {
+		} else if BoolDefault(test.LinkerProperties.Isolated, false) {
 			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
@@ -315,7 +321,11 @@
 }
 
 func (test *testDecorator) linkerProps() []interface{} {
-	return []interface{}{&test.Properties}
+	return []interface{}{&test.LinkerProperties}
+}
+
+func (test *testDecorator) installerProps() []interface{} {
+	return []interface{}{&test.InstallerProperties}
 }
 
 func NewTestInstaller() *baseInstaller {
@@ -323,7 +333,7 @@
 }
 
 type testBinary struct {
-	testDecorator
+	*testDecorator
 	*binaryDecorator
 	*baseCompiler
 	Properties       TestBinaryProperties
@@ -347,6 +357,7 @@
 	deps = test.testDecorator.linkerDeps(ctx, deps)
 	deps = test.binaryDecorator.linkerDeps(ctx, deps)
 	deps.DataLibs = append(deps.DataLibs, test.Properties.Data_libs...)
+	deps.DataBins = append(deps.DataBins, test.Properties.Data_bins...)
 	return deps
 }
 
@@ -356,6 +367,10 @@
 	return flags
 }
 
+func (test *testBinary) installerProps() []interface{} {
+	return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
 func (test *testBinary) install(ctx ModuleContext, file android.Path) {
 	// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
 	testInstallBase := "/data/local/tmp"
@@ -371,19 +386,26 @@
 
 	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
-		ccDep, ok := dep.(LinkableInterface)
-
+		linkableDep, ok := dep.(LinkableInterface)
 		if !ok {
-			ctx.ModuleErrorf("data_lib %q is not a linkable cc module", depName)
+			ctx.ModuleErrorf("data_lib %q is not a LinkableInterface module", depName)
 		}
-		ccModule, ok := dep.(*Module)
-		if !ok {
-			ctx.ModuleErrorf("data_lib %q is not a cc module", depName)
-		}
-		if ccDep.OutputFile().Valid() {
+		if linkableDep.OutputFile().Valid() {
 			test.data = append(test.data,
-				android.DataPath{SrcPath: ccDep.OutputFile().Path(),
-					RelativeInstallPath: ccModule.installer.relativeInstallPath()})
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_bin %q is not a LinkableInterface module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
 		}
 	})
 
@@ -402,7 +424,7 @@
 		var options []tradefed.Option
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
 	}
-	if Bool(test.testDecorator.Properties.Isolated) {
+	if Bool(test.testDecorator.LinkerProperties.Isolated) {
 		configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
 	}
 	if test.Properties.Test_options.Run_test_as != nil {
@@ -418,14 +440,6 @@
 		var options []tradefed.Option
 		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_shipping_api_level), 10)})
 		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
-	} else if test.Properties.Test_min_api_level != nil {
-		// TODO: (b/187258404) Remove test.Properties.Test_min_api_level
-		if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
-			ctx.PropertyErrorf("test_min_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
-		}
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
 	}
 	if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
 		var options []tradefed.Option
@@ -440,7 +454,7 @@
 	}
 
 	test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
-		test.Properties.Test_config_template, test.Properties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
+		test.Properties.Test_config_template, test.testDecorator.InstallerProperties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
 
 	test.extraTestConfigs = android.PathsForModuleSrc(ctx, test.Properties.Test_options.Extra_test_configs)
 
@@ -460,13 +474,14 @@
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.multilib = android.MultilibBoth
 	binary.baseInstaller = NewTestInstaller()
 
 	test := &testBinary{
-		testDecorator: testDecorator{
-			linker: binary.baseLinker,
+		testDecorator: &testDecorator{
+			linker:    binary.baseLinker,
+			installer: binary.baseInstaller,
 		},
 		binaryDecorator: binary,
 		baseCompiler:    NewBaseCompiler(),
@@ -478,12 +493,14 @@
 }
 
 type testLibrary struct {
-	testDecorator
+	*testDecorator
 	*libraryDecorator
 }
 
 func (test *testLibrary) linkerProps() []interface{} {
-	return append(test.testDecorator.linkerProps(), test.libraryDecorator.linkerProps()...)
+	var props []interface{}
+	props = append(props, test.testDecorator.linkerProps()...)
+	return append(props, test.libraryDecorator.linkerProps()...)
 }
 
 func (test *testLibrary) linkerInit(ctx BaseModuleContext) {
@@ -503,16 +520,22 @@
 	return flags
 }
 
+func (test *testLibrary) installerProps() []interface{} {
+	return append(test.baseInstaller.installerProps(), test.testDecorator.installerProps()...)
+}
+
 func NewTestLibrary(hod android.HostOrDeviceSupported) *Module {
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.baseInstaller = NewTestInstaller()
 	test := &testLibrary{
-		testDecorator: testDecorator{
-			linker: library.baseLinker,
+		testDecorator: &testDecorator{
+			linker:    library.baseLinker,
+			installer: library.baseInstaller,
 		},
 		libraryDecorator: library,
 	}
 	module.linker = test
+	module.installer = test
 	return module
 }
 
@@ -550,6 +573,10 @@
 	testConfig android.Path
 }
 
+func (benchmark *benchmarkDecorator) benchmarkBinary() bool {
+	return true
+}
+
 func (benchmark *benchmarkDecorator) linkerInit(ctx BaseModuleContext) {
 	runpath := "../../lib"
 	if ctx.toolchain().Is64Bit() {
@@ -587,7 +614,7 @@
 }
 
 func NewBenchmark(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
+	module, binary := newBinary(hod, false)
 	module.multilib = android.MultilibBoth
 	binary.baseInstaller = NewBaseInstaller("benchmarktest", "benchmarktest64", InstallInData)
 
diff --git a/cc/test_data_test.go b/cc/test_data_test.go
index 426dfc5..a621166 100644
--- a/cc/test_data_test.go
+++ b/cc/test_data_test.go
@@ -127,7 +127,7 @@
 			ctx.RegisterModuleType("test", newTest)
 			ctx.Register()
 
-			_, errs := ctx.ParseBlueprintsFiles("Blueprints")
+			_, errs := ctx.ParseBlueprintsFiles("Android.bp")
 			android.FailIfErrored(t, errs)
 			_, errs = ctx.PrepareBuildActions(config)
 			android.FailIfErrored(t, errs)
diff --git a/cc/testing.go b/cc/testing.go
index 15f7ebb..32f7c60 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -15,8 +15,12 @@
 package cc
 
 import (
+	"path/filepath"
+	"testing"
+
 	"android/soong/android"
 	"android/soong/genrule"
+	"android/soong/snapshot"
 )
 
 func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
@@ -26,11 +30,11 @@
 	RegisterLibraryBuildComponents(ctx)
 	RegisterLibraryHeadersBuildComponents(ctx)
 
-	ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
 	ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
-	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
+	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
+	ctx.RegisterModuleType("ndk_prebuilt_static_stl", NdkPrebuiltStaticStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_object", NdkPrebuiltObjectFactory)
 	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
 }
@@ -40,9 +44,6 @@
 
 	supportLinuxBionic := false
 	for _, os := range oses {
-		if os == android.Fuchsia {
-			ret += withFuchsiaModules()
-		}
 		if os == android.Windows {
 			ret += withWindowsModules()
 		}
@@ -61,41 +62,14 @@
 
 func commonDefaultModules() string {
 	return `
-		toolchain_library {
-			name: "libcompiler_rt-extras",
+		cc_defaults {
+			name: "toolchain_libs_defaults",
 			vendor_available: true,
-			vendor_ramdisk_available: true,
 			product_available: true,
 			recovery_available: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.builtins-arm-android",
-			vendor_available: true,
-			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
-			native_bridge_supported: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.builtins-aarch64-android",
-			vendor_available: true,
-			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
-			native_bridge_supported: true,
-			src: "",
-		}
-
-		cc_prebuilt_library_shared {
-			name: "libclang_rt.hwasan-aarch64-android",
+			no_libcrt: true,
+			sdk_version: "minimum",
 			nocrt: true,
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
 			system_shared_libs: [],
 			stl: "none",
 			srcs: [""],
@@ -105,87 +79,53 @@
 			},
 		}
 
-		toolchain_library {
-			name: "libclang_rt.builtins-i686-android",
-			vendor_available: true,
+		cc_prebuilt_library_static {
+			name: "libcompiler_rt-extras",
+			defaults: ["toolchain_libs_defaults"],
 			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
-			native_bridge_supported: true,
-			src: "",
 		}
 
-		toolchain_library {
-			name: "libclang_rt.builtins-x86_64-android",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
+		cc_prebuilt_library_static {
+			name: "libclang_rt.builtins",
+			defaults: ["toolchain_libs_defaults"],
+			host_supported: true,
+	        vendor_available: true,
 			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
 			native_bridge_supported: true,
-			src: "",
 		}
 
-		toolchain_library {
+		cc_prebuilt_library_shared {
+			name: "libclang_rt.hwasan",
+			defaults: ["toolchain_libs_defaults"],
+		}
+
+		cc_prebuilt_library_static {
 			name: "libunwind",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
+			defaults: [
+				"linux_bionic_supported",
+				"toolchain_libs_defaults",
+			],
 			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
 			native_bridge_supported: true,
-			src: "",
 		}
 
-		toolchain_library {
-			name: "libclang_rt.fuzzer-arm-android",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.fuzzer-aarch64-android",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.fuzzer-i686-android",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.fuzzer-x86_64-android",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
-		}
-
-		toolchain_library {
-			name: "libclang_rt.fuzzer-x86_64",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
+		cc_prebuilt_library_static {
+			name: "libclang_rt.fuzzer",
+			defaults: [
+				"linux_bionic_supported",
+				"toolchain_libs_defaults",
+			],
 		}
 
 		// Needed for sanitizer
 		cc_prebuilt_library_shared {
-			name: "libclang_rt.ubsan_standalone-aarch64-android",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			system_shared_libs: [],
-			srcs: [""],
+			name: "libclang_rt.ubsan_standalone",
+			defaults: ["toolchain_libs_defaults"],
+		}
+
+		cc_prebuilt_library_static {
+			name: "libclang_rt.ubsan_minimal",
+			defaults: ["toolchain_libs_defaults"],
 		}
 
 		cc_library {
@@ -363,6 +303,7 @@
 			stl: "none",
 			min_sdk_version: "16",
 			crt: true,
+			system_shared_libs: [],
 			apex_available: [
 				"//apex_available:platform",
 				"//apex_available:anyapex",
@@ -372,26 +313,42 @@
 		cc_object {
 			name: "crtbegin_so",
 			defaults: ["crt_defaults"],
+			srcs: ["crtbegin_so.c"],
+			objs: ["crtbrand"],
 		}
 
 		cc_object {
 			name: "crtbegin_dynamic",
 			defaults: ["crt_defaults"],
+			srcs: ["crtbegin.c"],
+			objs: ["crtbrand"],
 		}
 
 		cc_object {
 			name: "crtbegin_static",
 			defaults: ["crt_defaults"],
+			srcs: ["crtbegin.c"],
+			objs: ["crtbrand"],
 		}
 
 		cc_object {
 			name: "crtend_so",
 			defaults: ["crt_defaults"],
+			srcs: ["crtend_so.c"],
+			objs: ["crtbrand"],
 		}
 
 		cc_object {
 			name: "crtend_android",
 			defaults: ["crt_defaults"],
+			srcs: ["crtend.c"],
+			objs: ["crtbrand"],
+		}
+
+		cc_object {
+			name: "crtbrand",
+			defaults: ["crt_defaults"],
+			srcs: ["crtbrand.c"],
 		}
 
 		cc_library {
@@ -400,7 +357,7 @@
 
 		cc_library {
 			name: "ndk_libunwind",
-			sdk_version: "current",
+			sdk_version: "minimum",
 			stl: "none",
 			system_shared_libs: [],
 		}
@@ -425,6 +382,12 @@
 
 		ndk_prebuilt_shared_stl {
 			name: "ndk_libc++_shared",
+			export_include_dirs: ["ndk_libc++_shared"],
+		}
+
+		ndk_prebuilt_static_stl {
+			name: "ndk_libandroid_support",
+			export_include_dirs: ["ndk_libandroid_support"],
 		}
 
 		cc_library_static {
@@ -441,12 +404,21 @@
 		cc_library_static {
 			name: "note_memtag_heap_sync",
 		}
+
+		cc_library {
+			name: "libc_musl",
+			host_supported: true,
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+		}
 	`
 }
 
 func withWindowsModules() string {
 	return `
-		toolchain_library {
+		cc_prebuilt_library_static {
 			name: "libwinpthread",
 			host_supported: true,
 			enabled: false,
@@ -455,20 +427,8 @@
 					enabled: true,
 				},
 			},
-			src: "",
-		}
-		`
-}
-
-func withFuchsiaModules() string {
-	return `
-		cc_library {
-			name: "libbioniccompat",
 			stl: "none",
-		}
-		cc_library {
-			name: "libcompiler_rt",
-			stl: "none",
+			srcs:[""],
 		}
 		`
 }
@@ -487,7 +447,7 @@
 				}
 
 				cc_genrule {
-					name: "host_bionic_linker_flags",
+					name: "host_bionic_linker_script",
 					host_supported: true,
 					device_supported: false,
 					target: {
@@ -498,7 +458,7 @@
 							enabled: true,
 						},
 					},
-					out: ["linker.flags"],
+					out: ["linker.script"],
 				}
 
 				cc_defaults {
@@ -575,9 +535,16 @@
 
 	// Additional files needed in tests that disallow non-existent source.
 	android.MockFS{
-		"defaults/cc/common/libc.map.txt":  nil,
-		"defaults/cc/common/libdl.map.txt": nil,
-		"defaults/cc/common/libm.map.txt":  nil,
+		"defaults/cc/common/libc.map.txt":           nil,
+		"defaults/cc/common/libdl.map.txt":          nil,
+		"defaults/cc/common/libm.map.txt":           nil,
+		"defaults/cc/common/ndk_libandroid_support": nil,
+		"defaults/cc/common/ndk_libc++_shared":      nil,
+		"defaults/cc/common/crtbegin_so.c":          nil,
+		"defaults/cc/common/crtbegin.c":             nil,
+		"defaults/cc/common/crtend_so.c":            nil,
+		"defaults/cc/common/crtend.c":               nil,
+		"defaults/cc/common/crtbrand.c":             nil,
 	}.AddToFixture(),
 
 	// Place the default cc test modules that are common to all platforms in a location that will not
@@ -612,21 +579,15 @@
 	android.FixtureOverrideTextFile(linuxBionicDefaultsPath, withLinuxBionic()),
 )
 
-// The preparer to include if running a cc related test for fuchsia.
-var PrepareForTestOnFuchsia = android.GroupFixturePreparers(
-	// Place the default cc test modules for fuschia in a location that will not conflict with default
-	// test modules defined by other packages.
-	android.FixtureAddTextFile("defaults/cc/fuschia/Android.bp", withFuchsiaModules()),
-	android.PrepareForTestSetDeviceToFuchsia,
-)
-
 // This adds some additional modules and singletons which might negatively impact the performance
 // of tests so they are not included in the PrepareForIntegrationTestWithCc.
 var PrepareForTestWithCcIncludeVndk = android.GroupFixturePreparers(
 	PrepareForIntegrationTestWithCc,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-		vendorSnapshotImageSingleton.init(ctx)
-		recoverySnapshotImageSingleton.init(ctx)
+		snapshot.VendorSnapshotImageSingleton.Init(ctx)
+		snapshot.RecoverySnapshotImageSingleton.Init(ctx)
+		RegisterVendorSnapshotModules(ctx)
+		RegisterRecoverySnapshotModules(ctx)
 		ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	}),
 )
@@ -650,14 +611,7 @@
 		mockFS[k] = v
 	}
 
-	var config android.Config
-	if os == android.Fuchsia {
-		panic("Fuchsia not supported use test fixture instead")
-	} else {
-		config = android.TestArchConfig(buildDir, env, bp, mockFS)
-	}
-
-	return config
+	return android.TestArchConfig(buildDir, env, bp, mockFS)
 }
 
 // CreateTestContext is the legacy way of creating a TestContext for testing cc modules.
@@ -674,8 +628,10 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
 
-	vendorSnapshotImageSingleton.init(ctx)
-	recoverySnapshotImageSingleton.init(ctx)
+	snapshot.VendorSnapshotImageSingleton.Init(ctx)
+	snapshot.RecoverySnapshotImageSingleton.Init(ctx)
+	RegisterVendorSnapshotModules(ctx)
+	RegisterRecoverySnapshotModules(ctx)
 	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	RegisterVndkLibraryTxtTypes(ctx)
 
@@ -685,3 +641,72 @@
 
 	return ctx
 }
+
+func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
+	t.Helper()
+	mod := ctx.ModuleForTests(moduleName, variant)
+	outputFiles := mod.OutputFiles(t, "")
+	if len(outputFiles) != 1 {
+		t.Errorf("%q must have single output\n", moduleName)
+		return
+	}
+	snapshotPath := filepath.Join(subDir, snapshotFilename)
+
+	if include {
+		out := singleton.Output(snapshotPath)
+		if fake {
+			if out.Rule == nil {
+				t.Errorf("Missing rule for module %q output file %q", moduleName, outputFiles[0])
+			}
+		} else {
+			if out.Input.String() != outputFiles[0].String() {
+				t.Errorf("The input of snapshot %q must be %q, but %q", moduleName, out.Input.String(), outputFiles[0])
+			}
+		}
+	} else {
+		out := singleton.MaybeOutput(snapshotPath)
+		if out.Rule != nil {
+			t.Errorf("There must be no rule for module %q output file %q", moduleName, outputFiles[0])
+		}
+	}
+}
+
+func CheckSnapshot(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, false)
+}
+
+func CheckSnapshotExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, false, false)
+}
+
+func CheckSnapshotRule(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
+}
+
+func AssertExcludeFromVendorSnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool, variant string) {
+	t.Helper()
+	m := ctx.ModuleForTests(name, variant).Module().(LinkableInterface)
+	if m.ExcludeFromVendorSnapshot() != expected {
+		t.Errorf("expected %q ExcludeFromVendorSnapshot to be %t", m.String(), expected)
+	}
+}
+
+func GetOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
+	for _, moduleName := range moduleNames {
+		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
+		output := module.outputFile.Path().RelativeToTop()
+		paths = append(paths, output)
+	}
+	return paths
+}
+
+func AssertExcludeFromRecoverySnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool, variant string) {
+	t.Helper()
+	m := ctx.ModuleForTests(name, variant).Module().(LinkableInterface)
+	if m.ExcludeFromRecoverySnapshot() != expected {
+		t.Errorf("expected %q ExcludeFromRecoverySnapshot to be %t", m.String(), expected)
+	}
+}
diff --git a/cc/tidy.go b/cc/tidy.go
index 616cf8a..750e9de 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"path/filepath"
 	"regexp"
 	"strings"
 
@@ -61,13 +62,6 @@
 	return []interface{}{&tidy.Properties}
 }
 
-func (tidy *tidyFeature) begin(ctx BaseModuleContext) {
-}
-
-func (tidy *tidyFeature) deps(ctx DepsContext, deps Deps) Deps {
-	return deps
-}
-
 func (tidy *tidyFeature) flags(ctx ModuleContext, flags Flags) Flags {
 	CheckBadTidyFlags(ctx, "tidy_flags", tidy.Properties.Tidy_flags)
 	CheckBadTidyChecks(ctx, "tidy_checks", tidy.Properties.Tidy_checks)
@@ -77,13 +71,17 @@
 		return flags
 	}
 
-	// If not explicitly set, check the global tidy flag
-	if tidy.Properties.Tidy == nil && !ctx.Config().ClangTidy() {
-		return flags
-	}
-
+	// If not explicitly disabled, set flags.Tidy to generate .tidy rules.
+	// Note that libraries and binaries will depend on .tidy files ONLY if
+	// the global WITH_TIDY or module 'tidy' property is true.
 	flags.Tidy = true
 
+	// If explicitly enabled, by global default or local tidy property,
+	// set flags.NeedTidyFiles to make this module depend on .tidy files.
+	if ctx.Config().ClangTidy() || Bool(tidy.Properties.Tidy) {
+		flags.NeedTidyFiles = true
+	}
+
 	// Add global WITH_TIDY_FLAGS and local tidy_flags.
 	withTidyFlags := ctx.Config().Getenv("WITH_TIDY_FLAGS")
 	if len(withTidyFlags) > 0 {
@@ -104,6 +102,12 @@
 		}
 		flags.TidyFlags = append(flags.TidyFlags, headerFilter)
 	}
+	// Work around RBE bug in parsing clang-tidy flags, replace "--flag" with "-flag".
+	// Some C/C++ modules added local tidy flags like --header-filter= and --extra-arg-before=.
+	doubleDash := regexp.MustCompile("^('?)--(.*)$")
+	for i, s := range flags.TidyFlags {
+		flags.TidyFlags[i] = doubleDash.ReplaceAllString(s, "$1-$2")
+	}
 
 	// If clang-tidy is not enabled globally, add the -quiet flag.
 	if !ctx.Config().ClangTidy() {
@@ -153,6 +157,14 @@
 	tidyChecks = tidyChecks + ",-bugprone-signed-char-misuse"
 	// http://b/155034972
 	tidyChecks = tidyChecks + ",-bugprone-branch-clone"
+	// http://b/193716442
+	tidyChecks = tidyChecks + ",-bugprone-implicit-widening-of-multiplication-result"
+	// Too many existing functions trigger this rule, and fixing it requires large code
+	// refactoring. The cost of maintaining this tidy rule outweighs the benefit it brings.
+	tidyChecks = tidyChecks + ",-bugprone-easily-swappable-parameters"
+	// http://b/216364337 - TODO: Follow-up after compiler update to
+	// disable or fix individual instances.
+	tidyChecks = tidyChecks + ",-cert-err33-c"
 	flags.TidyFlags = append(flags.TidyFlags, tidyChecks)
 
 	if ctx.Config().IsEnvTrue("WITH_TIDY") {
@@ -185,3 +197,154 @@
 	}
 	return flags
 }
+
+func init() {
+	android.RegisterSingletonType("tidy_phony_targets", TidyPhonySingleton)
+}
+
+// This TidyPhonySingleton generates both tidy-* and obj-* phony targets for C/C++ files.
+func TidyPhonySingleton() android.Singleton {
+	return &tidyPhonySingleton{}
+}
+
+type tidyPhonySingleton struct{}
+
+// Given a final module, add its tidy/obj phony targets to tidy/objModulesInDirGroup.
+func collectTidyObjModuleTargets(ctx android.SingletonContext, module android.Module,
+	tidyModulesInDirGroup, objModulesInDirGroup map[string]map[string]android.Paths) {
+	allObjFileGroups := make(map[string]android.Paths)     // variant group name => obj file Paths
+	allTidyFileGroups := make(map[string]android.Paths)    // variant group name => tidy file Paths
+	subsetObjFileGroups := make(map[string]android.Paths)  // subset group name => obj file Paths
+	subsetTidyFileGroups := make(map[string]android.Paths) // subset group name => tidy file Paths
+
+	// (1) Collect all obj/tidy files into OS-specific groups.
+	ctx.VisitAllModuleVariants(module, func(variant android.Module) {
+		if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(variant) {
+			return
+		}
+		if m, ok := variant.(*Module); ok {
+			osName := variant.Target().Os.Name
+			addToOSGroup(osName, m.objFiles, allObjFileGroups, subsetObjFileGroups)
+			addToOSGroup(osName, m.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
+		}
+	})
+
+	// (2) Add an all-OS group, with "" or "subset" name, to include all os-specific phony targets.
+	addAllOSGroup(ctx, module, allObjFileGroups, "", "obj")
+	addAllOSGroup(ctx, module, allTidyFileGroups, "", "tidy")
+	addAllOSGroup(ctx, module, subsetObjFileGroups, "subset", "obj")
+	addAllOSGroup(ctx, module, subsetTidyFileGroups, "subset", "tidy")
+
+	tidyTargetGroups := make(map[string]android.Path)
+	objTargetGroups := make(map[string]android.Path)
+	genObjTidyPhonyTargets(ctx, module, "obj", allObjFileGroups, objTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "obj", subsetObjFileGroups, objTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "tidy", allTidyFileGroups, tidyTargetGroups)
+	genObjTidyPhonyTargets(ctx, module, "tidy", subsetTidyFileGroups, tidyTargetGroups)
+
+	moduleDir := ctx.ModuleDir(module)
+	appendToModulesInDirGroup(tidyTargetGroups, moduleDir, tidyModulesInDirGroup)
+	appendToModulesInDirGroup(objTargetGroups, moduleDir, objModulesInDirGroup)
+}
+
+func (m *tidyPhonySingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// For tidy-* directory phony targets, there are different variant groups.
+	// tidyModulesInDirGroup[G][D] is for group G, directory D, with Paths
+	// of all phony targets to be included into direct dependents of tidy-D_G.
+	tidyModulesInDirGroup := make(map[string]map[string]android.Paths)
+	// Also for obj-* directory phony targets.
+	objModulesInDirGroup := make(map[string]map[string]android.Paths)
+
+	// Collect tidy/obj targets from the 'final' modules.
+	ctx.VisitAllModules(func(module android.Module) {
+		if module == ctx.FinalModule(module) {
+			collectTidyObjModuleTargets(ctx, module, tidyModulesInDirGroup, objModulesInDirGroup)
+		}
+	})
+
+	suffix := ""
+	if ctx.Config().KatiEnabled() {
+		suffix = "-soong"
+	}
+	generateObjTidyPhonyTargets(ctx, suffix, "obj", objModulesInDirGroup)
+	generateObjTidyPhonyTargets(ctx, suffix, "tidy", tidyModulesInDirGroup)
+}
+
+// The name for an obj/tidy module variant group phony target is Name_group-obj/tidy,
+func objTidyModuleGroupName(module android.Module, group string, suffix string) string {
+	if group == "" {
+		return module.Name() + "-" + suffix
+	}
+	return module.Name() + "_" + group + "-" + suffix
+}
+
+// Generate obj-* or tidy-* phony targets.
+func generateObjTidyPhonyTargets(ctx android.SingletonContext, suffix string, prefix string, objTidyModulesInDirGroup map[string]map[string]android.Paths) {
+	// For each variant group, create a <prefix>-<directory>_group target that
+	// depends on all subdirectories and modules in the directory.
+	for group, modulesInDir := range objTidyModulesInDirGroup {
+		groupSuffix := ""
+		if group != "" {
+			groupSuffix = "_" + group
+		}
+		mmTarget := func(dir string) string {
+			return prefix + "-" + strings.Replace(filepath.Clean(dir), "/", "-", -1) + groupSuffix
+		}
+		dirs, topDirs := android.AddAncestors(ctx, modulesInDir, mmTarget)
+		// Create a <prefix>-soong_group target that depends on all <prefix>-dir_group of top level dirs.
+		var topDirPaths android.Paths
+		for _, dir := range topDirs {
+			topDirPaths = append(topDirPaths, android.PathForPhony(ctx, mmTarget(dir)))
+		}
+		ctx.Phony(prefix+suffix+groupSuffix, topDirPaths...)
+		// Create a <prefix>-dir_group target that depends on all targets in modulesInDir[dir]
+		for _, dir := range dirs {
+			if dir != "." && dir != "" {
+				ctx.Phony(mmTarget(dir), modulesInDir[dir]...)
+			}
+		}
+	}
+}
+
+// Append (obj|tidy)TargetGroups[group] into (obj|tidy)ModulesInDirGroups[group][moduleDir].
+func appendToModulesInDirGroup(targetGroups map[string]android.Path, moduleDir string, modulesInDirGroup map[string]map[string]android.Paths) {
+	for group, phonyPath := range targetGroups {
+		if _, found := modulesInDirGroup[group]; !found {
+			modulesInDirGroup[group] = make(map[string]android.Paths)
+		}
+		modulesInDirGroup[group][moduleDir] = append(modulesInDirGroup[group][moduleDir], phonyPath)
+	}
+}
+
+// Add given files to the OS group and subset group.
+func addToOSGroup(osName string, files android.Paths, allGroups, subsetGroups map[string]android.Paths) {
+	if len(files) > 0 {
+		subsetName := osName + "_subset"
+		allGroups[osName] = append(allGroups[osName], files...)
+		// Now include only the first variant in the subsetGroups.
+		// If clang and clang-tidy get faster, we might include more variants.
+		if _, found := subsetGroups[subsetName]; !found {
+			subsetGroups[subsetName] = files
+		}
+	}
+}
+
+// Add an all-OS group, with groupName, to include all os-specific phony targets.
+func addAllOSGroup(ctx android.SingletonContext, module android.Module, phonyTargetGroups map[string]android.Paths, groupName string, objTidyName string) {
+	if len(phonyTargetGroups) > 0 {
+		var targets android.Paths
+		for group, _ := range phonyTargetGroups {
+			targets = append(targets, android.PathForPhony(ctx, objTidyModuleGroupName(module, group, objTidyName)))
+		}
+		phonyTargetGroups[groupName] = targets
+	}
+}
+
+// Create one phony targets for each group and add them to the targetGroups.
+func genObjTidyPhonyTargets(ctx android.SingletonContext, module android.Module, objTidyName string, fileGroups map[string]android.Paths, targetGroups map[string]android.Path) {
+	for group, files := range fileGroups {
+		groupName := objTidyModuleGroupName(module, group, objTidyName)
+		ctx.Phony(groupName, files...)
+		targetGroups[group] = android.PathForPhony(ctx, groupName)
+	}
+}
diff --git a/cc/toolchain_library.go b/cc/toolchain_library.go
deleted file mode 100644
index bda73ea..0000000
--- a/cc/toolchain_library.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2016 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"
-)
-
-//
-// Device libraries shipped with gcc
-//
-
-func init() {
-	android.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
-}
-
-type toolchainLibraryProperties struct {
-	// the prebuilt toolchain library, as a path from the top of the source tree
-	Src *string `android:"arch_variant"`
-
-	// Repack the archive with only the selected objects.
-	Repack_objects_to_keep []string `android:"arch_variant"`
-}
-
-type toolchainLibraryDecorator struct {
-	*libraryDecorator
-	stripper Stripper
-
-	Properties toolchainLibraryProperties
-}
-
-func (*toolchainLibraryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
-	// toolchain libraries can't have any dependencies
-	return deps
-}
-
-func (library *toolchainLibraryDecorator) linkerProps() []interface{} {
-	var props []interface{}
-	props = append(props, library.libraryDecorator.linkerProps()...)
-	return append(props, &library.Properties, &library.stripper.StripProperties)
-}
-
-// toolchain_library is used internally by the build tool to link the specified
-// static library in src property to the device libraries that are shipped with
-// gcc.
-func ToolchainLibraryFactory() android.Module {
-	module, library := NewLibrary(android.HostAndDeviceSupported)
-	library.BuildOnlyStatic()
-	toolchainLibrary := &toolchainLibraryDecorator{
-		libraryDecorator: library,
-	}
-	module.compiler = toolchainLibrary
-	module.linker = toolchainLibrary
-	module.stl = nil
-	module.sanitize = nil
-	module.installer = nil
-	module.library = toolchainLibrary
-	module.Properties.Sdk_version = StringPtr("current")
-	return module.Init()
-}
-
-func (library *toolchainLibraryDecorator) compile(ctx ModuleContext, flags Flags,
-	deps PathDeps) Objects {
-	return Objects{}
-}
-
-func (library *toolchainLibraryDecorator) link(ctx ModuleContext,
-	flags Flags, deps PathDeps, objs Objects) android.Path {
-
-	if library.Properties.Src == nil {
-		ctx.PropertyErrorf("src", "No library source specified")
-		return android.PathForSource(ctx, "")
-	}
-
-	srcPath := android.PathForSource(ctx, *library.Properties.Src)
-	outputFile := android.Path(srcPath)
-
-	if library.Properties.Repack_objects_to_keep != nil {
-		fileName := ctx.ModuleName() + staticLibraryExtension
-		repackedPath := android.PathForModuleOut(ctx, fileName)
-		transformArchiveRepack(ctx, outputFile, repackedPath, library.Properties.Repack_objects_to_keep)
-		outputFile = repackedPath
-	}
-
-	if library.stripper.StripProperties.Strip.Keep_symbols_list != nil {
-		fileName := ctx.ModuleName() + staticLibraryExtension
-		strippedPath := android.PathForModuleOut(ctx, fileName)
-		stripFlags := flagsToStripFlags(flags)
-		library.stripper.StripStaticLib(ctx, outputFile, strippedPath, stripFlags)
-		outputFile = strippedPath
-	}
-
-	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(outputFile).Build()
-	ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
-		StaticLibrary: outputFile,
-
-		TransitiveStaticLibrariesForOrdering: depSet,
-	})
-
-	return outputFile
-}
-
-func (library *toolchainLibraryDecorator) nativeCoverage() bool {
-	return false
-}
diff --git a/cc/util.go b/cc/util.go
index 1220d84..b256b9a 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -21,6 +21,7 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/snapshot"
 )
 
 // Efficiently converts a list of include directories to a single string
@@ -84,13 +85,13 @@
 		toolchain:     in.Toolchain,
 		gcovCoverage:  in.GcovCoverage,
 		tidy:          in.Tidy,
+		needTidyFiles: in.NeedTidyFiles,
 		sAbiDump:      in.SAbiDump,
 		emitXrefs:     in.EmitXrefs,
 
 		systemIncludeFlags: strings.Join(in.SystemIncludeFlags, " "),
 
 		assemblerWithCpp: in.AssemblerWithCpp,
-		groupStaticLibs:  in.GroupStaticLibs,
 
 		proto:            in.proto,
 		protoC:           in.protoC,
@@ -126,20 +127,6 @@
 		"ln -sf " + target + " " + filepath.Join(dir, linkName)
 }
 
-func copyFileRule(ctx android.SingletonContext, path android.Path, out string) android.OutputPath {
-	outPath := android.PathForOutput(ctx, out)
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        android.Cp,
-		Input:       path,
-		Output:      outPath,
-		Description: "copy " + path.String() + " -> " + out,
-		Args: map[string]string{
-			"cpFlags": "-f -L",
-		},
-	})
-	return outPath
-}
-
 func combineNoticesRule(ctx android.SingletonContext, paths android.Paths, out string) android.OutputPath {
 	outPath := android.PathForOutput(ctx, out)
 	ctx.Build(pctx, android.BuildParams{
@@ -151,12 +138,6 @@
 	return outPath
 }
 
-func writeStringToFileRule(ctx android.SingletonContext, content, out string) android.OutputPath {
-	outPath := android.PathForOutput(ctx, out)
-	android.WriteFileRule(ctx, outPath, content)
-	return outPath
-}
-
 // Dump a map to a list file as:
 //
 // {key1} {value1}
@@ -172,5 +153,5 @@
 		txtBuilder.WriteString(" ")
 		txtBuilder.WriteString(m[k])
 	}
-	return writeStringToFileRule(ctx, txtBuilder.String(), path)
+	return snapshot.WriteStringToFileRule(ctx, txtBuilder.String(), path)
 }
diff --git a/cc/vendor_public_library_test.go b/cc/vendor_public_library_test.go
index 01959b4..769be09 100644
--- a/cc/vendor_public_library_test.go
+++ b/cc/vendor_public_library_test.go
@@ -79,7 +79,7 @@
 	// test if libsystem is linked to the stub
 	ld := ctx.ModuleForTests("libsystem", coreVariant).Rule("ld")
 	libflags := ld.Args["libFlags"]
-	stubPaths := getOutputPaths(ctx, coreVariant, []string{"libvendorpublic"})
+	stubPaths := GetOutputPaths(ctx, coreVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libsystem must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
@@ -87,7 +87,7 @@
 	// test if libsystem is linked to the stub
 	ld = ctx.ModuleForTests("libproduct", productVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = getOutputPaths(ctx, productVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(ctx, productVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libproduct must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
@@ -95,7 +95,8 @@
 	// test if libvendor is linked to the real shared lib
 	ld = ctx.ModuleForTests("libvendor", vendorVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = getOutputPaths(ctx, vendorVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(ctx, vendorVariant, []string{"libvendorpublic"})
+
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libvendor must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 4e59a95..e7c05ac 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -13,141 +13,46 @@
 // limitations under the License.
 package cc
 
-// This file contains singletons to capture vendor and recovery snapshot. They consist of prebuilt
-// modules under AOSP so older vendor and recovery can be built with a newer system in a single
-// source tree.
-
 import (
 	"encoding/json"
 	"path/filepath"
-	"sort"
 	"strings"
 
 	"android/soong/android"
+	"android/soong/snapshot"
 )
 
-var vendorSnapshotSingleton = snapshotSingleton{
-	"vendor",
-	"SOONG_VENDOR_SNAPSHOT_ZIP",
-	android.OptionalPath{},
-	true,
-	vendorSnapshotImageSingleton,
-	false, /* fake */
-}
+// This file defines how to capture cc modules into snapshot package.
 
-var vendorFakeSnapshotSingleton = snapshotSingleton{
-	"vendor",
-	"SOONG_VENDOR_FAKE_SNAPSHOT_ZIP",
-	android.OptionalPath{},
-	true,
-	vendorSnapshotImageSingleton,
-	true, /* fake */
-}
-
-var recoverySnapshotSingleton = snapshotSingleton{
-	"recovery",
-	"SOONG_RECOVERY_SNAPSHOT_ZIP",
-	android.OptionalPath{},
-	false,
-	recoverySnapshotImageSingleton,
-	false, /* fake */
-}
-
-func VendorSnapshotSingleton() android.Singleton {
-	return &vendorSnapshotSingleton
-}
-
-func VendorFakeSnapshotSingleton() android.Singleton {
-	return &vendorFakeSnapshotSingleton
-}
-
-func RecoverySnapshotSingleton() android.Singleton {
-	return &recoverySnapshotSingleton
-}
-
-type snapshotSingleton struct {
-	// Name, e.g., "vendor", "recovery", "ramdisk".
-	name string
-
-	// Make variable that points to the snapshot file, e.g.,
-	// "SOONG_RECOVERY_SNAPSHOT_ZIP".
-	makeVar string
-
-	// Path to the snapshot zip file.
-	snapshotZipFile android.OptionalPath
-
-	// Whether the image supports VNDK extension modules.
-	supportsVndkExt bool
-
-	// Implementation of the image interface specific to the image
-	// associated with this snapshot (e.g., specific to the vendor image,
-	// recovery image, etc.).
-	image snapshotImage
-
-	// Whether this singleton is for fake snapshot or not.
-	// Fake snapshot is a snapshot whose prebuilt binaries and headers are empty.
-	// It is much faster to generate, and can be used to inspect dependencies.
-	fake bool
-}
-
-// Determine if a dir under source tree is an SoC-owned proprietary directory based
-// on vendor snapshot configuration
-// Examples: device/, vendor/
-func isVendorProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
-	return VendorSnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
-}
-
-// Determine if a dir under source tree is an SoC-owned proprietary directory based
-// on recovery snapshot configuration
-// Examples: device/, vendor/
-func isRecoveryProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
-	return RecoverySnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
-}
-
-func isVendorProprietaryModule(ctx android.BaseModuleContext) bool {
-	// Any module in a vendor proprietary path is a vendor proprietary
-	// module.
-	if isVendorProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
+// Checks if the target image would contain VNDK
+func includeVndk(image snapshot.SnapshotImage) bool {
+	if image.ImageName() == snapshot.VendorSnapshotImageName {
 		return true
 	}
 
-	// However if the module is not in a vendor proprietary path, it may
-	// still be a vendor proprietary module. This happens for cc modules
-	// that are excluded from the vendor snapshot, and it means that the
-	// vendor has assumed control of the framework-provided module.
-	if c, ok := ctx.Module().(LinkableInterface); ok {
-		if c.ExcludeFromVendorSnapshot() {
-			return true
-		}
-	}
-
 	return false
 }
 
-func isRecoveryProprietaryModule(ctx android.BaseModuleContext) bool {
-
-	// Any module in a recovery proprietary path is a recovery proprietary
-	// module.
-	if isRecoveryProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
+// Check if the module is VNDK private
+func isPrivate(image snapshot.SnapshotImage, m LinkableInterface) bool {
+	if image.ImageName() == snapshot.VendorSnapshotImageName && m.IsVndkPrivate() {
 		return true
 	}
 
-	// However if the module is not in a recovery proprietary path, it may
-	// still be a recovery proprietary module. This happens for cc modules
-	// that are excluded from the recovery snapshot, and it means that the
-	// vendor has assumed control of the framework-provided module.
+	return false
+}
 
-	if c, ok := ctx.Module().(LinkableInterface); ok {
-		if c.ExcludeFromRecoverySnapshot() {
-			return true
-		}
+// Checks if target image supports VNDK Ext
+func supportsVndkExt(image snapshot.SnapshotImage) bool {
+	if image.ImageName() == snapshot.VendorSnapshotImageName {
+		return true
 	}
 
 	return false
 }
 
 // Determines if the module is a candidate for snapshot.
-func isSnapshotAware(cfg android.DeviceConfig, m LinkableInterface, inProprietaryPath bool, apexInfo android.ApexInfo, image snapshotImage) bool {
+func isSnapshotAware(cfg android.DeviceConfig, m LinkableInterface, inProprietaryPath bool, apexInfo android.ApexInfo, image snapshot.SnapshotImage) bool {
 	if !m.Enabled() || m.HiddenFromMake() {
 		return false
 	}
@@ -158,12 +63,12 @@
 	}
 	// skip proprietary modules, but (for the vendor snapshot only)
 	// include all VNDK (static)
-	if inProprietaryPath && (!image.includeVndk() || !m.IsVndk()) {
+	if inProprietaryPath && (!includeVndk(image) || !m.IsVndk()) {
 		return false
 	}
 	// If the module would be included based on its path, check to see if
 	// the module is marked to be excluded. If so, skip it.
-	if image.excludeFromSnapshot(m) {
+	if image.ExcludeFromSnapshot(m) {
 		return false
 	}
 	if m.Target().Os.Class != android.Device {
@@ -173,7 +78,7 @@
 		return false
 	}
 	// the module must be installed in target image
-	if !apexInfo.IsForPlatform() || m.IsSnapshotPrebuilt() || !image.inImage(m)() {
+	if !apexInfo.IsForPlatform() || m.IsSnapshotPrebuilt() || !image.InImage(m)() {
 		return false
 	}
 	// skip kernel_headers which always depend on vendor
@@ -203,13 +108,13 @@
 			}
 		}
 		if sanitizable.Static() {
-			return sanitizable.OutputFile().Valid() && !image.private(m)
+			return sanitizable.OutputFile().Valid() && !isPrivate(image, m)
 		}
-		if sanitizable.Shared() {
+		if sanitizable.Shared() || sanitizable.Rlib() {
 			if !sanitizable.OutputFile().Valid() {
 				return false
 			}
-			if image.includeVndk() {
+			if includeVndk(image) {
 				if !sanitizable.IsVndk() {
 					return true
 				}
@@ -227,12 +132,9 @@
 	return false
 }
 
-// This is to be saved as .json files, which is for development/vendor_snapshot/update.py.
-// These flags become Android.bp snapshot module properties.
+// Extend the snapshot.SnapshotJsonFlags to include cc specific fields.
 type snapshotJsonFlags struct {
-	ModuleName          string `json:",omitempty"`
-	RelativeInstallPath string `json:",omitempty"`
-
+	snapshot.SnapshotJsonFlags
 	// library flags
 	ExportedDirs       []string `json:",omitempty"`
 	ExportedSystemDirs []string `json:",omitempty"`
@@ -242,27 +144,23 @@
 	SanitizeUbsanDep   bool     `json:",omitempty"`
 
 	// binary flags
-	Symlinks []string `json:",omitempty"`
+	Symlinks         []string `json:",omitempty"`
+	StaticExecutable bool     `json:",omitempty"`
+	InstallInRoot    bool     `json:",omitempty"`
 
 	// dependencies
 	SharedLibs  []string `json:",omitempty"`
+	StaticLibs  []string `json:",omitempty"`
 	RuntimeLibs []string `json:",omitempty"`
-	Required    []string `json:",omitempty"`
 
 	// extra config files
 	InitRc         []string `json:",omitempty"`
 	VintfFragments []string `json:",omitempty"`
 }
 
-func (c *snapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !c.image.shouldGenerateSnapshot(ctx) {
-		return
-	}
-
-	var snapshotOutputs android.Paths
-
+var ccSnapshotAction snapshot.GenerateSnapshotAction = func(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths {
 	/*
-		Vendor snapshot zipped artifacts directory structure:
+		Vendor snapshot zipped artifacts directory structure for cc modules:
 		{SNAPSHOT_ARCH}/
 			arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
 				shared/
@@ -294,13 +192,7 @@
 				(header files of same directory structure with source tree)
 	*/
 
-	snapshotDir := c.name + "-snapshot"
-	if c.fake {
-		// If this is a fake snapshot singleton, place all files under fake/ subdirectory to avoid
-		// collision with real snapshot files
-		snapshotDir = filepath.Join("fake", snapshotDir)
-	}
-	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
+	var snapshotOutputs android.Paths
 
 	includeDir := filepath.Join(snapshotArchDir, "include")
 	configsDir := filepath.Join(snapshotArchDir, "configs")
@@ -315,9 +207,9 @@
 		if fake {
 			// All prebuilt binaries and headers are installed by copyFile function. This makes a fake
 			// snapshot just touch prebuilts and headers, rather than installing real files.
-			return writeStringToFileRule(ctx, "", out)
+			return snapshot.WriteStringToFileRule(ctx, "", out)
 		} else {
-			return copyFileRule(ctx, path, out)
+			return snapshot.CopyFileRule(pctx, ctx, path, out)
 		}
 	}
 
@@ -335,7 +227,7 @@
 
 		// Common properties among snapshots.
 		prop.ModuleName = ctx.ModuleName(m)
-		if c.supportsVndkExt && m.IsVndkExt() {
+		if supportsVndkExt(s.Image) && m.IsVndkExt() {
 			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
 			if m.IsVndkSp() {
 				prop.RelativeInstallPath = "vndk-sp"
@@ -381,6 +273,8 @@
 			if m.Shared() {
 				prop.SharedLibs = m.SnapshotSharedLibs()
 			}
+			// static libs dependencies are required to collect the NOTICE files.
+			prop.StaticLibs = m.SnapshotStaticLibs()
 			if sanitizable, ok := m.(PlatformSanitizeable); ok {
 				if sanitizable.Static() && sanitizable.SanitizePropDefined() {
 					prop.SanitizeMinimalDep = sanitizable.MinimalRuntimeDep() || sanitizable.MinimalRuntimeNeeded()
@@ -393,6 +287,8 @@
 				libType = "static"
 			} else if m.Shared() {
 				libType = "shared"
+			} else if m.Rlib() {
+				libType = "rlib"
 			} else {
 				libType = "header"
 			}
@@ -404,7 +300,7 @@
 				libPath := m.OutputFile().Path()
 				stem = libPath.Base()
 				if sanitizable, ok := m.(PlatformSanitizeable); ok {
-					if sanitizable.Static() && sanitizable.SanitizePropDefined() && sanitizable.IsSanitizerEnabled(cfi) {
+					if (sanitizable.Static() || sanitizable.Rlib()) && sanitizable.SanitizePropDefined() && sanitizable.IsSanitizerEnabled(cfi) {
 						// both cfi and non-cfi variant for static libraries can exist.
 						// attach .cfi to distinguish between cfi and non-cfi.
 						// e.g. libbase.a -> libbase.cfi.a
@@ -424,8 +320,11 @@
 		} else if m.Binary() {
 			// binary flags
 			prop.Symlinks = m.Symlinks()
+			prop.StaticExecutable = m.StaticExecutable()
+			prop.InstallInRoot = m.InstallInRoot()
 			prop.SharedLibs = m.SnapshotSharedLibs()
-
+			// static libs dependencies are required to collect the NOTICE files.
+			prop.StaticLibs = m.SnapshotStaticLibs()
 			// install bin
 			binPath := m.OutputFile().Path()
 			snapshotBinOut := filepath.Join(snapshotArchDir, targetArch, "binary", binPath.Base())
@@ -449,7 +348,7 @@
 			ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
 			return nil
 		}
-		ret = append(ret, writeStringToFileRule(ctx, string(j), propOut))
+		ret = append(ret, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
 
 		return ret
 	}
@@ -461,10 +360,10 @@
 		}
 
 		moduleDir := ctx.ModuleDir(module)
-		inProprietaryPath := c.image.isProprietaryPath(moduleDir, ctx.DeviceConfig())
+		inProprietaryPath := s.Image.IsProprietaryPath(moduleDir, ctx.DeviceConfig())
 		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
-		if c.image.excludeFromSnapshot(m) {
+		if s.Image.ExcludeFromSnapshot(m) {
 			if inProprietaryPath {
 				// Error: exclude_from_vendor_snapshot applies
 				// to framework-path modules only.
@@ -473,7 +372,7 @@
 			}
 		}
 
-		if !isSnapshotAware(ctx.DeviceConfig(), m, inProprietaryPath, apexInfo, c.image) {
+		if !isSnapshotAware(ctx.DeviceConfig(), m, inProprietaryPath, apexInfo, s.Image) {
 			return
 		}
 
@@ -481,8 +380,8 @@
 		// list, we will still include the module as if it was a fake module.
 		// The reason is that soong needs all the dependencies to be present, even
 		// if they are not using during the build.
-		installAsFake := c.fake
-		if c.image.excludeFromDirectedSnapshot(ctx.DeviceConfig(), m.BaseModuleName()) {
+		installAsFake := s.Fake
+		if s.Image.ExcludeFromDirectedSnapshot(ctx.DeviceConfig(), m.BaseModuleName()) {
 			installAsFake = true
 		}
 
@@ -493,60 +392,25 @@
 			headers = append(headers, m.SnapshotHeaders()...)
 		}
 
-		if len(m.NoticeFiles()) > 0 {
+		if len(m.EffectiveLicenseFiles()) > 0 {
 			noticeName := ctx.ModuleName(m) + ".txt"
 			noticeOut := filepath.Join(noticeDir, noticeName)
 			// skip already copied notice file
 			if !installedNotices[noticeOut] {
 				installedNotices[noticeOut] = true
-				snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.NoticeFiles(), noticeOut))
+				snapshotOutputs = append(snapshotOutputs, combineNoticesRule(ctx, m.EffectiveLicenseFiles(), noticeOut))
 			}
 		}
 	})
 
 	// install all headers after removing duplicates
 	for _, header := range android.FirstUniquePaths(headers) {
-		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, header, filepath.Join(includeDir, header.String()), c.fake))
+		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, header, filepath.Join(includeDir, header.String()), s.Fake))
 	}
 
-	// All artifacts are ready. Sort them to normalize ninja and then zip.
-	sort.Slice(snapshotOutputs, func(i, j int) bool {
-		return snapshotOutputs[i].String() < snapshotOutputs[j].String()
-	})
-
-	zipPath := android.PathForOutput(
-		ctx,
-		snapshotDir,
-		c.name+"-"+ctx.Config().DeviceName()+".zip")
-	zipRule := android.NewRuleBuilder(pctx, ctx)
-
-	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
-	snapshotOutputList := android.PathForOutput(
-		ctx,
-		snapshotDir,
-		c.name+"-"+ctx.Config().DeviceName()+"_list")
-	rspFile := snapshotOutputList.ReplaceExtension(ctx, "rsp")
-	zipRule.Command().
-		Text("tr").
-		FlagWithArg("-d ", "\\'").
-		FlagWithRspFileInputList("< ", rspFile, snapshotOutputs).
-		FlagWithOutput("> ", snapshotOutputList)
-
-	zipRule.Temporary(snapshotOutputList)
-
-	zipRule.Command().
-		BuiltTool("soong_zip").
-		FlagWithOutput("-o ", zipPath).
-		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
-		FlagWithInput("-l ", snapshotOutputList)
-
-	zipRule.Build(zipPath.String(), c.name+" snapshot "+zipPath.String())
-	zipRule.DeleteTemporaryFiles()
-	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
+	return snapshotOutputs
 }
 
-func (c *snapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.Strict(
-		c.makeVar,
-		c.snapshotZipFile.String())
+func init() {
+	snapshot.RegisterSnapshotAction(ccSnapshotAction)
 }
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index c3b5e8c..2bb43ab 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -65,10 +65,13 @@
 		nocrt: true,
 	}
 
-	toolchain_library {
+	cc_prebuilt_library_static {
 		name: "libb",
 		vendor_available: true,
-		src: "libb.a",
+		srcs: ["libb.a"],
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
 	}
 
 	cc_object {
@@ -108,27 +111,27 @@
 		// For shared libraries, only non-VNDK vendor_available modules are captured
 		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(sharedDir, "libvendor.so.json"),
 			filepath.Join(sharedDir, "libvendor_available.so.json"))
 
 		// LLNDK modules are not captured
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", sharedDir, sharedVariant)
 
 		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
 		// Also cfi variants are captured, except for prebuilts like toolchain_library
 		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
 		staticCfiVariant := fmt.Sprintf("android_vendor.29_%s_%s_static_cfi", archType, archVariant)
 		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
-		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.cfi.a", staticDir, staticCfiVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.cfi.a", staticDir, staticCfiVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.cfi.a", staticDir, staticCfiVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(staticDir, "libb.a.json"),
 			filepath.Join(staticDir, "libvndk.a.json"),
@@ -142,8 +145,8 @@
 		if archType == "arm64" {
 			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
-			checkSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
-			checkSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
 			jsonFiles = append(jsonFiles,
 				filepath.Join(binaryDir, "vendor_bin.json"),
 				filepath.Join(binaryDir, "vendor_available_bin.json"))
@@ -156,7 +159,7 @@
 		// For object modules, all vendor:true and vendor_available modules are captured.
 		objectVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
-		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
 		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
 	}
 
@@ -239,15 +242,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libvendor.so.json"))
 		// Check that snapshot captures "prefer: true" prebuilt
-		checkSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo.so.json"))
 
 		// Excluded modules. Modules not included in the directed vendor snapshot
 		// are still include as fake modules.
-		checkSnapshotRule(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
+		CheckSnapshotRule(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libvendor_available.so.json"))
 	}
 
@@ -459,6 +462,19 @@
 		srcs: ["client.cpp"],
 	}
 
+	cc_library_shared {
+		name: "libvndkext",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		vndk: {
+			extends: "libvndk",
+			enabled: true,
+		}
+	}
+
 	cc_binary {
 		name: "bin_without_snapshot",
 		vendor: true,
@@ -725,6 +741,7 @@
 				src: "bin",
 			},
 		},
+		symlinks: ["binfoo", "binbar"],
 	}
 
 	vendor_snapshot_binary {
@@ -839,10 +856,11 @@
 		[]string{staticVariant, "libvendor.vendor_static.31.arm64"},
 		[]string{staticVariant, "libvendor_without_snapshot"},
 	} {
-		outputPaths := getOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
+		outputPaths := GetOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
 		if !strings.Contains(libclientLdFlags, outputPaths[0].String()) {
 			t.Errorf("libflags for libclient must contain %#v, but was %#v", outputPaths[0], libclientLdFlags)
 		}
+
 	}
 
 	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
@@ -868,7 +886,7 @@
 	}
 
 	libclientCfiLdFlags := ctx.ModuleForTests("libclient_cfi", sharedCfiVariant).Rule("ld").Args["libFlags"]
-	libvendorCfiOutputPaths := getOutputPaths(ctx, staticCfiVariant, []string{"libvendor.vendor_static.31.arm64"})
+	libvendorCfiOutputPaths := GetOutputPaths(ctx, staticCfiVariant, []string{"libvendor.vendor_static.31.arm64"})
 	if !strings.Contains(libclientCfiLdFlags, libvendorCfiOutputPaths[0].String()) {
 		t.Errorf("libflags for libclientCfi must contain %#v, but was %#v", libvendorCfiOutputPaths[0], libclientCfiLdFlags)
 	}
@@ -881,7 +899,7 @@
 	}
 
 	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("ld").Args["libFlags"]
-	libVndkStaticOutputPaths := getOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.31.arm64"})
+	libVndkStaticOutputPaths := GetOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.31.arm64"})
 	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
 		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
 			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
@@ -903,7 +921,21 @@
 	ctx.ModuleForTests("libvendor_without_snapshot", sharedVariant).Output("libvendor_without_snapshot.so")
 
 	// bin is installed by bin.vendor_binary.31.arm64
-	ctx.ModuleForTests("bin.vendor_binary.31.arm64", binaryVariant).Output("bin")
+	bin64Module := ctx.ModuleForTests("bin.vendor_binary.31.arm64", binaryVariant)
+	bin64Module.Output("bin")
+
+	// also test symlinks
+	bin64MkEntries := android.AndroidMkEntriesForTest(t, ctx, bin64Module.Module())
+	bin64KatiSymlinks := bin64MkEntries[0].EntryMap["LOCAL_SOONG_INSTALL_SYMLINKS"]
+
+	// Either AndroidMk entries contain symlinks, or symlinks should be installed by Soong
+	for _, symlink := range []string{"binfoo", "binbar"} {
+		if inList(symlink, bin64KatiSymlinks) {
+			continue
+		}
+
+		bin64Module.Output(symlink)
+	}
 
 	// bin32 is installed by bin32.vendor_binary.31.arm64
 	ctx.ModuleForTests("bin32.vendor_binary.31.arm64", binary32Variant).Output("bin32")
@@ -1006,22 +1038,6 @@
 	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
 }
 
-func assertExcludeFromVendorSnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool) {
-	t.Helper()
-	m := ctx.ModuleForTests(name, vendorVariant).Module().(*Module)
-	if m.ExcludeFromVendorSnapshot() != expected {
-		t.Errorf("expected %q ExcludeFromVendorSnapshot to be %t", m.String(), expected)
-	}
-}
-
-func assertExcludeFromRecoverySnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool) {
-	t.Helper()
-	m := ctx.ModuleForTests(name, recoveryVariant).Module().(*Module)
-	if m.ExcludeFromRecoverySnapshot() != expected {
-		t.Errorf("expected %q ExcludeFromRecoverySnapshot to be %t", m.String(), expected)
-	}
-}
-
 func TestVendorSnapshotExclude(t *testing.T) {
 
 	// This test verifies that the exclude_from_vendor_snapshot property
@@ -1081,13 +1097,13 @@
 	android.FailIfErrored(t, errs)
 
 	// Test an include and exclude framework module.
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false)
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true)
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false, vendorVariant)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true, vendorVariant)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true, vendorVariant)
 
 	// A vendor module is excluded, but by its path, not the
 	// exclude_from_vendor_snapshot property.
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libvendor", false)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libvendor", false, vendorVariant)
 
 	// Verify the content of the vendor snapshot.
 
@@ -1110,15 +1126,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
 
 		// Excluded modules
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libvendor.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
 	}
 
@@ -1224,10 +1240,13 @@
 		nocrt: true,
 	}
 
-	toolchain_library {
+	cc_prebuilt_library_static {
 		name: "libb",
 		recovery_available: true,
-		src: "libb.a",
+		srcs: ["libb.a"],
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
 	}
 
 	cc_object {
@@ -1258,9 +1277,9 @@
 		// For shared libraries, only recovery_available modules are captured.
 		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(sharedDir, "libvndk.so.json"),
 			filepath.Join(sharedDir, "librecovery.so.json"),
@@ -1269,9 +1288,9 @@
 		// For static libraries, all recovery:true and recovery_available modules are captured.
 		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
 		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
-		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(staticDir, "libb.a.json"),
 			filepath.Join(staticDir, "librecovery.a.json"),
@@ -1281,8 +1300,8 @@
 		if archType == "arm64" {
 			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
 			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
-			checkSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
-			checkSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
 			jsonFiles = append(jsonFiles,
 				filepath.Join(binaryDir, "recovery_bin.json"),
 				filepath.Join(binaryDir, "recovery_available_bin.json"))
@@ -1295,7 +1314,7 @@
 		// For object modules, all vendor:true and vendor_available modules are captured.
 		objectVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
 		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
-		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
 		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
 	}
 
@@ -1365,13 +1384,13 @@
 	android.FailIfErrored(t, errs)
 
 	// Test an include and exclude framework module.
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false)
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true)
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false, recoveryVariant)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true, recoveryVariant)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true, recoveryVariant)
 
 	// A recovery module is excluded, but by its path, not the
 	// exclude_from_recovery_snapshot property.
-	assertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false)
+	AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false, recoveryVariant)
 
 	// Verify the content of the recovery snapshot.
 
@@ -1393,15 +1412,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
 
 		// Excluded modules
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
 	}
 
@@ -1482,15 +1501,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
 		// Check that snapshot captures "prefer: true" prebuilt
-		checkSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo.so.json"))
 
 		// Excluded modules. Modules not included in the directed recovery snapshot
 		// are still include as fake modules.
-		checkSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery_available.so.json"))
 	}
 
diff --git a/cc/vndk.go b/cc/vndk.go
index 6a56c34..bf6148b 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -25,6 +25,7 @@
 	"android/soong/android"
 	"android/soong/cc/config"
 	"android/soong/etc"
+	"android/soong/snapshot"
 
 	"github.com/google/blueprint"
 )
@@ -100,12 +101,6 @@
 	return []interface{}{&vndk.Properties}
 }
 
-func (vndk *vndkdep) begin(ctx BaseModuleContext) {}
-
-func (vndk *vndkdep) deps(ctx BaseModuleContext, deps Deps) Deps {
-	return deps
-}
-
 func (vndk *vndkdep) isVndk() bool {
 	return Bool(vndk.Properties.Vndk.Enabled)
 }
@@ -360,7 +355,7 @@
 	// prebuilt vndk modules should match with device
 	// TODO(b/142675459): Use enabled: to select target device in vndk_prebuilt_shared
 	// When b/142675459 is landed, remove following check
-	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok && !p.matchesWithDevice(mctx.DeviceConfig()) {
+	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok && !p.MatchesWithDevice(mctx.DeviceConfig()) {
 		return false
 	}
 
@@ -371,7 +366,7 @@
 			if mctx.ModuleName() == "libz" {
 				return false
 			}
-			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp()
+			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp() && !m.IsVndkExt()
 		}
 
 		useCoreVariant := m.VndkVersion() == mctx.DeviceConfig().PlatformVndkVersion() &&
@@ -455,7 +450,7 @@
 // Therefore, by removing the library here, we cause it to only be installed if libc
 // depends on it.
 func llndkLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "LLNDK_LIBRARIES", "libclang_rt.hwasan-")
+	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "LLNDK_LIBRARIES", "libclang_rt.hwasan")
 }
 
 // vndksp_libraries_txt is a singleton module whose content is a list of VNDKSP libraries
@@ -500,7 +495,7 @@
 		filterOutFromMakeVar: filter,
 	}
 	m.AddProperties(&m.properties)
-	android.InitAndroidModule(m)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
 
@@ -704,7 +699,7 @@
 
 		libPath := m.outputFile.Path()
 		snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "shared", vndkType, libPath.Base())
-		ret = append(ret, copyFileRule(ctx, libPath, snapshotLibOut))
+		ret = append(ret, snapshot.CopyFileRule(pctx, ctx, libPath, snapshotLibOut))
 
 		if ctx.Config().VndkSnapshotBuildArtifacts() {
 			prop := struct {
@@ -726,7 +721,7 @@
 				ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
 				return nil, false
 			}
-			ret = append(ret, writeStringToFileRule(ctx, string(j), propOut))
+			ret = append(ret, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
 		}
 		return ret, true
 	}
@@ -784,8 +779,8 @@
 
 	// install all headers after removing duplicates
 	for _, header := range android.FirstUniquePaths(headers) {
-		snapshotOutputs = append(snapshotOutputs, copyFileRule(
-			ctx, header, filepath.Join(includeDir, header.String())))
+		snapshotOutputs = append(snapshotOutputs, snapshot.CopyFileRule(
+			pctx, ctx, header, filepath.Join(includeDir, header.String())))
 	}
 
 	// install *.libraries.txt except vndkcorevariant.libraries.txt
@@ -794,8 +789,8 @@
 		if !ok || !m.Enabled() || m.Name() == vndkUsingCoreVariantLibrariesTxt {
 			return
 		}
-		snapshotOutputs = append(snapshotOutputs, copyFileRule(
-			ctx, m.OutputFile(), filepath.Join(configsDir, m.Name())))
+		snapshotOutputs = append(snapshotOutputs, snapshot.CopyFileRule(
+			pctx, ctx, m.OutputFile(), filepath.Join(configsDir, m.Name())))
 	})
 
 	/*
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index fc4412a..31b6d10 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -82,7 +82,7 @@
 }
 
 func (p *vndkPrebuiltLibraryDecorator) NameSuffix() string {
-	suffix := p.version()
+	suffix := p.Version()
 	if p.arch() != "" {
 		suffix += "." + p.arch()
 	}
@@ -92,7 +92,7 @@
 	return vndkSuffix + suffix
 }
 
-func (p *vndkPrebuiltLibraryDecorator) version() string {
+func (p *vndkPrebuiltLibraryDecorator) Version() string {
 	return String(p.properties.Version)
 }
 
@@ -107,7 +107,7 @@
 	return "64"
 }
 
-func (p *vndkPrebuiltLibraryDecorator) snapshotAndroidMkSuffix() string {
+func (p *vndkPrebuiltLibraryDecorator) SnapshotAndroidMkSuffix() string {
 	return ".vendor"
 }
 
@@ -133,7 +133,7 @@
 func (p *vndkPrebuiltLibraryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		ctx.Module().HideFromMake()
 		return nil
 	}
@@ -144,7 +144,6 @@
 		// current VNDK prebuilts are only shared libs.
 
 		in := p.singleSourcePath(ctx)
-		builderFlags := flagsToBuilderFlags(flags)
 		p.unstrippedOutputFile = in
 		libName := in.Base()
 		if p.stripper.NeedsStrip(ctx) {
@@ -158,19 +157,18 @@
 		// depending on a table of contents file instead of the library itself.
 		tocFile := android.PathForModuleOut(ctx, libName+".toc")
 		p.tocFile = android.OptionalPathForPath(tocFile)
-		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
+		TransformSharedObjectToToc(ctx, in, tocFile)
 
 		p.androidMkSuffix = p.NameSuffix()
 
 		vndkVersion := ctx.DeviceConfig().VndkVersion()
-		if vndkVersion == p.version() {
+		if vndkVersion == p.Version() {
 			p.androidMkSuffix = ""
 		}
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           in,
-			UnstrippedSharedLibrary: p.unstrippedOutputFile,
-			Target:                  ctx.Target(),
+			SharedLibrary: in,
+			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
 		})
@@ -184,7 +182,7 @@
 	return nil
 }
 
-func (p *vndkPrebuiltLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *vndkPrebuiltLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
 	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
 		return false
@@ -202,7 +200,7 @@
 	return false
 }
 
-func (p *vndkPrebuiltLibraryDecorator) isSnapshotPrebuilt() bool {
+func (p *vndkPrebuiltLibraryDecorator) IsSnapshotPrebuilt() bool {
 	return true
 }
 
diff --git a/cmd/extract_apks/Android.bp b/cmd/extract_apks/Android.bp
index 8a4ed63..21064c0 100644
--- a/cmd/extract_apks/Android.bp
+++ b/cmd/extract_apks/Android.bp
@@ -8,6 +8,7 @@
     deps: [
         "android-archive-zip",
         "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
         "soong-cmd-extract_apks-proto",
     ],
     testSrcs: ["main_test.go"],
@@ -16,7 +17,10 @@
 bootstrap_go_package {
     name: "soong-cmd-extract_apks-proto",
     pkgPath: "android/soong/cmd/extract_apks/bundle_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "bundle_proto/commands.pb.go",
         "bundle_proto/config.pb.go",
diff --git a/cmd/extract_apks/bundle_proto/commands.pb.go b/cmd/extract_apks/bundle_proto/commands.pb.go
index bbf3314..2cf8e6d 100644
--- a/cmd/extract_apks/bundle_proto/commands.pb.go
+++ b/cmd/extract_apks/bundle_proto/commands.pb.go
@@ -1,24 +1,29 @@
+// Messages describing APK Set's table of contents (toc.pb entry).
+// Please be advised that the ultimate source is at
+// https://github.com/google/bundletool/tree/master/src/main/proto
+// so you have been warned.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: commands.proto
 
-package android_bundle_proto
+package bundle_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type DeliveryType int32
 
@@ -29,26 +34,47 @@
 	DeliveryType_FAST_FOLLOW           DeliveryType = 3
 )
 
-var DeliveryType_name = map[int32]string{
-	0: "UNKNOWN_DELIVERY_TYPE",
-	1: "INSTALL_TIME",
-	2: "ON_DEMAND",
-	3: "FAST_FOLLOW",
-}
+// Enum value maps for DeliveryType.
+var (
+	DeliveryType_name = map[int32]string{
+		0: "UNKNOWN_DELIVERY_TYPE",
+		1: "INSTALL_TIME",
+		2: "ON_DEMAND",
+		3: "FAST_FOLLOW",
+	}
+	DeliveryType_value = map[string]int32{
+		"UNKNOWN_DELIVERY_TYPE": 0,
+		"INSTALL_TIME":          1,
+		"ON_DEMAND":             2,
+		"FAST_FOLLOW":           3,
+	}
+)
 
-var DeliveryType_value = map[string]int32{
-	"UNKNOWN_DELIVERY_TYPE": 0,
-	"INSTALL_TIME":          1,
-	"ON_DEMAND":             2,
-	"FAST_FOLLOW":           3,
+func (x DeliveryType) Enum() *DeliveryType {
+	p := new(DeliveryType)
+	*p = x
+	return p
 }
 
 func (x DeliveryType) String() string {
-	return proto.EnumName(DeliveryType_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (DeliveryType) Descriptor() protoreflect.EnumDescriptor {
+	return file_commands_proto_enumTypes[0].Descriptor()
+}
+
+func (DeliveryType) Type() protoreflect.EnumType {
+	return &file_commands_proto_enumTypes[0]
+}
+
+func (x DeliveryType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use DeliveryType.Descriptor instead.
 func (DeliveryType) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{0}
+	return file_commands_proto_rawDescGZIP(), []int{0}
 }
 
 type SystemApkMetadata_SystemApkType int32
@@ -64,30 +90,55 @@
 	SystemApkMetadata_SYSTEM_COMPRESSED SystemApkMetadata_SystemApkType = 3
 )
 
-var SystemApkMetadata_SystemApkType_name = map[int32]string{
-	0: "UNSPECIFIED_VALUE",
-	1: "SYSTEM",
-	2: "SYSTEM_STUB",
-	3: "SYSTEM_COMPRESSED",
-}
+// Enum value maps for SystemApkMetadata_SystemApkType.
+var (
+	SystemApkMetadata_SystemApkType_name = map[int32]string{
+		0: "UNSPECIFIED_VALUE",
+		1: "SYSTEM",
+		2: "SYSTEM_STUB",
+		3: "SYSTEM_COMPRESSED",
+	}
+	SystemApkMetadata_SystemApkType_value = map[string]int32{
+		"UNSPECIFIED_VALUE": 0,
+		"SYSTEM":            1,
+		"SYSTEM_STUB":       2,
+		"SYSTEM_COMPRESSED": 3,
+	}
+)
 
-var SystemApkMetadata_SystemApkType_value = map[string]int32{
-	"UNSPECIFIED_VALUE": 0,
-	"SYSTEM":            1,
-	"SYSTEM_STUB":       2,
-	"SYSTEM_COMPRESSED": 3,
+func (x SystemApkMetadata_SystemApkType) Enum() *SystemApkMetadata_SystemApkType {
+	p := new(SystemApkMetadata_SystemApkType)
+	*p = x
+	return p
 }
 
 func (x SystemApkMetadata_SystemApkType) String() string {
-	return proto.EnumName(SystemApkMetadata_SystemApkType_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (SystemApkMetadata_SystemApkType) Descriptor() protoreflect.EnumDescriptor {
+	return file_commands_proto_enumTypes[1].Descriptor()
+}
+
+func (SystemApkMetadata_SystemApkType) Type() protoreflect.EnumType {
+	return &file_commands_proto_enumTypes[1]
+}
+
+func (x SystemApkMetadata_SystemApkType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use SystemApkMetadata_SystemApkType.Descriptor instead.
 func (SystemApkMetadata_SystemApkType) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{10, 0}
+	return file_commands_proto_rawDescGZIP(), []int{10, 0}
 }
 
 // Describes the output of the "build-apks" command.
 type BuildApksResult struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The package name of this app.
 	PackageName string `protobuf:"bytes,4,opt,name=package_name,json=packageName,proto3" json:"package_name,omitempty"`
 	// List of the created variants.
@@ -97,68 +148,72 @@
 	// List of the created asset slices.
 	AssetSliceSet []*AssetSliceSet `protobuf:"bytes,3,rep,name=asset_slice_set,json=assetSliceSet,proto3" json:"asset_slice_set,omitempty"`
 	// Information about local testing mode.
-	LocalTestingInfo     *LocalTestingInfo `protobuf:"bytes,5,opt,name=local_testing_info,json=localTestingInfo,proto3" json:"local_testing_info,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	LocalTestingInfo *LocalTestingInfo `protobuf:"bytes,5,opt,name=local_testing_info,json=localTestingInfo,proto3" json:"local_testing_info,omitempty"`
 }
 
-func (m *BuildApksResult) Reset()         { *m = BuildApksResult{} }
-func (m *BuildApksResult) String() string { return proto.CompactTextString(m) }
-func (*BuildApksResult) ProtoMessage()    {}
+func (x *BuildApksResult) Reset() {
+	*x = BuildApksResult{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildApksResult) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildApksResult) ProtoMessage() {}
+
+func (x *BuildApksResult) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildApksResult.ProtoReflect.Descriptor instead.
 func (*BuildApksResult) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{0}
+	return file_commands_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *BuildApksResult) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BuildApksResult.Unmarshal(m, b)
-}
-func (m *BuildApksResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BuildApksResult.Marshal(b, m, deterministic)
-}
-func (m *BuildApksResult) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BuildApksResult.Merge(m, src)
-}
-func (m *BuildApksResult) XXX_Size() int {
-	return xxx_messageInfo_BuildApksResult.Size(m)
-}
-func (m *BuildApksResult) XXX_DiscardUnknown() {
-	xxx_messageInfo_BuildApksResult.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BuildApksResult proto.InternalMessageInfo
-
-func (m *BuildApksResult) GetPackageName() string {
-	if m != nil {
-		return m.PackageName
+func (x *BuildApksResult) GetPackageName() string {
+	if x != nil {
+		return x.PackageName
 	}
 	return ""
 }
 
-func (m *BuildApksResult) GetVariant() []*Variant {
-	if m != nil {
-		return m.Variant
+func (x *BuildApksResult) GetVariant() []*Variant {
+	if x != nil {
+		return x.Variant
 	}
 	return nil
 }
 
-func (m *BuildApksResult) GetBundletool() *Bundletool {
-	if m != nil {
-		return m.Bundletool
+func (x *BuildApksResult) GetBundletool() *Bundletool {
+	if x != nil {
+		return x.Bundletool
 	}
 	return nil
 }
 
-func (m *BuildApksResult) GetAssetSliceSet() []*AssetSliceSet {
-	if m != nil {
-		return m.AssetSliceSet
+func (x *BuildApksResult) GetAssetSliceSet() []*AssetSliceSet {
+	if x != nil {
+		return x.AssetSliceSet
 	}
 	return nil
 }
 
-func (m *BuildApksResult) GetLocalTestingInfo() *LocalTestingInfo {
-	if m != nil {
-		return m.LocalTestingInfo
+func (x *BuildApksResult) GetLocalTestingInfo() *LocalTestingInfo {
+	if x != nil {
+		return x.LocalTestingInfo
 	}
 	return nil
 }
@@ -166,6 +221,10 @@
 // Variant is a group of APKs that covers a part of the device configuration
 // space. APKs from multiple variants are never combined on one device.
 type Variant struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Variant-level targeting.
 	// This targeting is fairly high-level and each APK has its own targeting as
 	// well.
@@ -176,54 +235,58 @@
 	// A device will receive APKs from the first variant that matches the device
 	// configuration, with higher variant numbers having priority over lower
 	// variant numbers.
-	VariantNumber        uint32   `protobuf:"varint,3,opt,name=variant_number,json=variantNumber,proto3" json:"variant_number,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	VariantNumber uint32 `protobuf:"varint,3,opt,name=variant_number,json=variantNumber,proto3" json:"variant_number,omitempty"`
 }
 
-func (m *Variant) Reset()         { *m = Variant{} }
-func (m *Variant) String() string { return proto.CompactTextString(m) }
-func (*Variant) ProtoMessage()    {}
+func (x *Variant) Reset() {
+	*x = Variant{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Variant) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Variant) ProtoMessage() {}
+
+func (x *Variant) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Variant.ProtoReflect.Descriptor instead.
 func (*Variant) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{1}
+	return file_commands_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *Variant) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Variant.Unmarshal(m, b)
-}
-func (m *Variant) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Variant.Marshal(b, m, deterministic)
-}
-func (m *Variant) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Variant.Merge(m, src)
-}
-func (m *Variant) XXX_Size() int {
-	return xxx_messageInfo_Variant.Size(m)
-}
-func (m *Variant) XXX_DiscardUnknown() {
-	xxx_messageInfo_Variant.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Variant proto.InternalMessageInfo
-
-func (m *Variant) GetTargeting() *VariantTargeting {
-	if m != nil {
-		return m.Targeting
+func (x *Variant) GetTargeting() *VariantTargeting {
+	if x != nil {
+		return x.Targeting
 	}
 	return nil
 }
 
-func (m *Variant) GetApkSet() []*ApkSet {
-	if m != nil {
-		return m.ApkSet
+func (x *Variant) GetApkSet() []*ApkSet {
+	if x != nil {
+		return x.ApkSet
 	}
 	return nil
 }
 
-func (m *Variant) GetVariantNumber() uint32 {
-	if m != nil {
-		return m.VariantNumber
+func (x *Variant) GetVariantNumber() uint32 {
+	if x != nil {
+		return x.VariantNumber
 	}
 	return 0
 }
@@ -231,54 +294,66 @@
 // Represents a module.
 // For pre-L devices multiple modules (possibly all) may be merged into one.
 type ApkSet struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	ModuleMetadata *ModuleMetadata `protobuf:"bytes,1,opt,name=module_metadata,json=moduleMetadata,proto3" json:"module_metadata,omitempty"`
 	// APKs.
-	ApkDescription       []*ApkDescription `protobuf:"bytes,2,rep,name=apk_description,json=apkDescription,proto3" json:"apk_description,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	ApkDescription []*ApkDescription `protobuf:"bytes,2,rep,name=apk_description,json=apkDescription,proto3" json:"apk_description,omitempty"`
 }
 
-func (m *ApkSet) Reset()         { *m = ApkSet{} }
-func (m *ApkSet) String() string { return proto.CompactTextString(m) }
-func (*ApkSet) ProtoMessage()    {}
+func (x *ApkSet) Reset() {
+	*x = ApkSet{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApkSet) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApkSet) ProtoMessage() {}
+
+func (x *ApkSet) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ApkSet.ProtoReflect.Descriptor instead.
 func (*ApkSet) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{2}
+	return file_commands_proto_rawDescGZIP(), []int{2}
 }
 
-func (m *ApkSet) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApkSet.Unmarshal(m, b)
-}
-func (m *ApkSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApkSet.Marshal(b, m, deterministic)
-}
-func (m *ApkSet) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApkSet.Merge(m, src)
-}
-func (m *ApkSet) XXX_Size() int {
-	return xxx_messageInfo_ApkSet.Size(m)
-}
-func (m *ApkSet) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApkSet.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApkSet proto.InternalMessageInfo
-
-func (m *ApkSet) GetModuleMetadata() *ModuleMetadata {
-	if m != nil {
-		return m.ModuleMetadata
+func (x *ApkSet) GetModuleMetadata() *ModuleMetadata {
+	if x != nil {
+		return x.ModuleMetadata
 	}
 	return nil
 }
 
-func (m *ApkSet) GetApkDescription() []*ApkDescription {
-	if m != nil {
-		return m.ApkDescription
+func (x *ApkSet) GetApkDescription() []*ApkDescription {
+	if x != nil {
+		return x.ApkDescription
 	}
 	return nil
 }
 
 type ModuleMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Module name.
 	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 	// Indicates the delivery type (e.g. on-demand) of the module.
@@ -292,131 +367,149 @@
 	// Relevant only for Split APKs.
 	Targeting *ModuleTargeting `protobuf:"bytes,5,opt,name=targeting,proto3" json:"targeting,omitempty"`
 	// Deprecated. Please use delivery_type.
-	OnDemandDeprecated   bool     `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"` // Deprecated: Do not use.
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	//
+	// Deprecated: Do not use.
+	OnDemandDeprecated bool `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"`
 }
 
-func (m *ModuleMetadata) Reset()         { *m = ModuleMetadata{} }
-func (m *ModuleMetadata) String() string { return proto.CompactTextString(m) }
-func (*ModuleMetadata) ProtoMessage()    {}
+func (x *ModuleMetadata) Reset() {
+	*x = ModuleMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ModuleMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ModuleMetadata) ProtoMessage() {}
+
+func (x *ModuleMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[3]
+	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 ModuleMetadata.ProtoReflect.Descriptor instead.
 func (*ModuleMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{3}
+	return file_commands_proto_rawDescGZIP(), []int{3}
 }
 
-func (m *ModuleMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ModuleMetadata.Unmarshal(m, b)
-}
-func (m *ModuleMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ModuleMetadata.Marshal(b, m, deterministic)
-}
-func (m *ModuleMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ModuleMetadata.Merge(m, src)
-}
-func (m *ModuleMetadata) XXX_Size() int {
-	return xxx_messageInfo_ModuleMetadata.Size(m)
-}
-func (m *ModuleMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_ModuleMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ModuleMetadata proto.InternalMessageInfo
-
-func (m *ModuleMetadata) GetName() string {
-	if m != nil {
-		return m.Name
+func (x *ModuleMetadata) GetName() string {
+	if x != nil {
+		return x.Name
 	}
 	return ""
 }
 
-func (m *ModuleMetadata) GetDeliveryType() DeliveryType {
-	if m != nil {
-		return m.DeliveryType
+func (x *ModuleMetadata) GetDeliveryType() DeliveryType {
+	if x != nil {
+		return x.DeliveryType
 	}
 	return DeliveryType_UNKNOWN_DELIVERY_TYPE
 }
 
-func (m *ModuleMetadata) GetIsInstant() bool {
-	if m != nil {
-		return m.IsInstant
+func (x *ModuleMetadata) GetIsInstant() bool {
+	if x != nil {
+		return x.IsInstant
 	}
 	return false
 }
 
-func (m *ModuleMetadata) GetDependencies() []string {
-	if m != nil {
-		return m.Dependencies
+func (x *ModuleMetadata) GetDependencies() []string {
+	if x != nil {
+		return x.Dependencies
 	}
 	return nil
 }
 
-func (m *ModuleMetadata) GetTargeting() *ModuleTargeting {
-	if m != nil {
-		return m.Targeting
+func (x *ModuleMetadata) GetTargeting() *ModuleTargeting {
+	if x != nil {
+		return x.Targeting
 	}
 	return nil
 }
 
 // Deprecated: Do not use.
-func (m *ModuleMetadata) GetOnDemandDeprecated() bool {
-	if m != nil {
-		return m.OnDemandDeprecated
+func (x *ModuleMetadata) GetOnDemandDeprecated() bool {
+	if x != nil {
+		return x.OnDemandDeprecated
 	}
 	return false
 }
 
 // Set of asset slices belonging to a single asset module.
 type AssetSliceSet struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Module level metadata.
 	AssetModuleMetadata *AssetModuleMetadata `protobuf:"bytes,1,opt,name=asset_module_metadata,json=assetModuleMetadata,proto3" json:"asset_module_metadata,omitempty"`
 	// Asset slices.
-	ApkDescription       []*ApkDescription `protobuf:"bytes,2,rep,name=apk_description,json=apkDescription,proto3" json:"apk_description,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	ApkDescription []*ApkDescription `protobuf:"bytes,2,rep,name=apk_description,json=apkDescription,proto3" json:"apk_description,omitempty"`
 }
 
-func (m *AssetSliceSet) Reset()         { *m = AssetSliceSet{} }
-func (m *AssetSliceSet) String() string { return proto.CompactTextString(m) }
-func (*AssetSliceSet) ProtoMessage()    {}
+func (x *AssetSliceSet) Reset() {
+	*x = AssetSliceSet{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AssetSliceSet) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AssetSliceSet) ProtoMessage() {}
+
+func (x *AssetSliceSet) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[4]
+	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 AssetSliceSet.ProtoReflect.Descriptor instead.
 func (*AssetSliceSet) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{4}
+	return file_commands_proto_rawDescGZIP(), []int{4}
 }
 
-func (m *AssetSliceSet) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_AssetSliceSet.Unmarshal(m, b)
-}
-func (m *AssetSliceSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_AssetSliceSet.Marshal(b, m, deterministic)
-}
-func (m *AssetSliceSet) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_AssetSliceSet.Merge(m, src)
-}
-func (m *AssetSliceSet) XXX_Size() int {
-	return xxx_messageInfo_AssetSliceSet.Size(m)
-}
-func (m *AssetSliceSet) XXX_DiscardUnknown() {
-	xxx_messageInfo_AssetSliceSet.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AssetSliceSet proto.InternalMessageInfo
-
-func (m *AssetSliceSet) GetAssetModuleMetadata() *AssetModuleMetadata {
-	if m != nil {
-		return m.AssetModuleMetadata
+func (x *AssetSliceSet) GetAssetModuleMetadata() *AssetModuleMetadata {
+	if x != nil {
+		return x.AssetModuleMetadata
 	}
 	return nil
 }
 
-func (m *AssetSliceSet) GetApkDescription() []*ApkDescription {
-	if m != nil {
-		return m.ApkDescription
+func (x *AssetSliceSet) GetApkDescription() []*ApkDescription {
+	if x != nil {
+		return x.ApkDescription
 	}
 	return nil
 }
 
 type AssetModuleMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Module name.
 	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
 	// Indicates the delivery type for persistent install.
@@ -424,133 +517,153 @@
 	// Metadata for instant installs.
 	InstantMetadata *InstantMetadata `protobuf:"bytes,3,opt,name=instant_metadata,json=instantMetadata,proto3" json:"instant_metadata,omitempty"`
 	// Deprecated. Use delivery_type.
-	OnDemandDeprecated   bool     `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"` // Deprecated: Do not use.
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	//
+	// Deprecated: Do not use.
+	OnDemandDeprecated bool `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"`
 }
 
-func (m *AssetModuleMetadata) Reset()         { *m = AssetModuleMetadata{} }
-func (m *AssetModuleMetadata) String() string { return proto.CompactTextString(m) }
-func (*AssetModuleMetadata) ProtoMessage()    {}
+func (x *AssetModuleMetadata) Reset() {
+	*x = AssetModuleMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AssetModuleMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AssetModuleMetadata) ProtoMessage() {}
+
+func (x *AssetModuleMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[5]
+	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 AssetModuleMetadata.ProtoReflect.Descriptor instead.
 func (*AssetModuleMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{5}
+	return file_commands_proto_rawDescGZIP(), []int{5}
 }
 
-func (m *AssetModuleMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_AssetModuleMetadata.Unmarshal(m, b)
-}
-func (m *AssetModuleMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_AssetModuleMetadata.Marshal(b, m, deterministic)
-}
-func (m *AssetModuleMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_AssetModuleMetadata.Merge(m, src)
-}
-func (m *AssetModuleMetadata) XXX_Size() int {
-	return xxx_messageInfo_AssetModuleMetadata.Size(m)
-}
-func (m *AssetModuleMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_AssetModuleMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AssetModuleMetadata proto.InternalMessageInfo
-
-func (m *AssetModuleMetadata) GetName() string {
-	if m != nil {
-		return m.Name
+func (x *AssetModuleMetadata) GetName() string {
+	if x != nil {
+		return x.Name
 	}
 	return ""
 }
 
-func (m *AssetModuleMetadata) GetDeliveryType() DeliveryType {
-	if m != nil {
-		return m.DeliveryType
+func (x *AssetModuleMetadata) GetDeliveryType() DeliveryType {
+	if x != nil {
+		return x.DeliveryType
 	}
 	return DeliveryType_UNKNOWN_DELIVERY_TYPE
 }
 
-func (m *AssetModuleMetadata) GetInstantMetadata() *InstantMetadata {
-	if m != nil {
-		return m.InstantMetadata
+func (x *AssetModuleMetadata) GetInstantMetadata() *InstantMetadata {
+	if x != nil {
+		return x.InstantMetadata
 	}
 	return nil
 }
 
 // Deprecated: Do not use.
-func (m *AssetModuleMetadata) GetOnDemandDeprecated() bool {
-	if m != nil {
-		return m.OnDemandDeprecated
+func (x *AssetModuleMetadata) GetOnDemandDeprecated() bool {
+	if x != nil {
+		return x.OnDemandDeprecated
 	}
 	return false
 }
 
 type InstantMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Indicates whether this module is marked "instant".
 	IsInstant bool `protobuf:"varint,1,opt,name=is_instant,json=isInstant,proto3" json:"is_instant,omitempty"`
 	// Indicates the delivery type for instant install.
 	DeliveryType DeliveryType `protobuf:"varint,3,opt,name=delivery_type,json=deliveryType,proto3,enum=android.bundle.DeliveryType" json:"delivery_type,omitempty"`
 	// Deprecated. Use delivery_type.
-	OnDemandDeprecated   bool     `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"` // Deprecated: Do not use.
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	//
+	// Deprecated: Do not use.
+	OnDemandDeprecated bool `protobuf:"varint,2,opt,name=on_demand_deprecated,json=onDemandDeprecated,proto3" json:"on_demand_deprecated,omitempty"`
 }
 
-func (m *InstantMetadata) Reset()         { *m = InstantMetadata{} }
-func (m *InstantMetadata) String() string { return proto.CompactTextString(m) }
-func (*InstantMetadata) ProtoMessage()    {}
+func (x *InstantMetadata) Reset() {
+	*x = InstantMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *InstantMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*InstantMetadata) ProtoMessage() {}
+
+func (x *InstantMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[6]
+	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 InstantMetadata.ProtoReflect.Descriptor instead.
 func (*InstantMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{6}
+	return file_commands_proto_rawDescGZIP(), []int{6}
 }
 
-func (m *InstantMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_InstantMetadata.Unmarshal(m, b)
-}
-func (m *InstantMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_InstantMetadata.Marshal(b, m, deterministic)
-}
-func (m *InstantMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_InstantMetadata.Merge(m, src)
-}
-func (m *InstantMetadata) XXX_Size() int {
-	return xxx_messageInfo_InstantMetadata.Size(m)
-}
-func (m *InstantMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_InstantMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_InstantMetadata proto.InternalMessageInfo
-
-func (m *InstantMetadata) GetIsInstant() bool {
-	if m != nil {
-		return m.IsInstant
+func (x *InstantMetadata) GetIsInstant() bool {
+	if x != nil {
+		return x.IsInstant
 	}
 	return false
 }
 
-func (m *InstantMetadata) GetDeliveryType() DeliveryType {
-	if m != nil {
-		return m.DeliveryType
+func (x *InstantMetadata) GetDeliveryType() DeliveryType {
+	if x != nil {
+		return x.DeliveryType
 	}
 	return DeliveryType_UNKNOWN_DELIVERY_TYPE
 }
 
 // Deprecated: Do not use.
-func (m *InstantMetadata) GetOnDemandDeprecated() bool {
-	if m != nil {
-		return m.OnDemandDeprecated
+func (x *InstantMetadata) GetOnDemandDeprecated() bool {
+	if x != nil {
+		return x.OnDemandDeprecated
 	}
 	return false
 }
 
 type ApkDescription struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Targeting *ApkTargeting `protobuf:"bytes,1,opt,name=targeting,proto3" json:"targeting,omitempty"`
 	// Path to the APK file.
 	// BEGIN-INTERNAL
 	// The path may be a blobkey if the proto is not constructed by bundletool.
 	// END-INTERNAL
 	Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
-	// Types that are valid to be assigned to ApkMetadataOneofValue:
+	// Types that are assignable to ApkMetadataOneofValue:
 	//	*ApkDescription_SplitApkMetadata
 	//	*ApkDescription_StandaloneApkMetadata
 	//	*ApkDescription_InstantApkMetadata
@@ -558,75 +671,134 @@
 	//	*ApkDescription_AssetSliceMetadata
 	//	*ApkDescription_ApexApkMetadata
 	ApkMetadataOneofValue isApkDescription_ApkMetadataOneofValue `protobuf_oneof:"apk_metadata_oneof_value"`
-	XXX_NoUnkeyedLiteral  struct{}                               `json:"-"`
-	XXX_unrecognized      []byte                                 `json:"-"`
-	XXX_sizecache         int32                                  `json:"-"`
 }
 
-func (m *ApkDescription) Reset()         { *m = ApkDescription{} }
-func (m *ApkDescription) String() string { return proto.CompactTextString(m) }
-func (*ApkDescription) ProtoMessage()    {}
+func (x *ApkDescription) Reset() {
+	*x = ApkDescription{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApkDescription) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApkDescription) ProtoMessage() {}
+
+func (x *ApkDescription) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[7]
+	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 ApkDescription.ProtoReflect.Descriptor instead.
 func (*ApkDescription) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{7}
+	return file_commands_proto_rawDescGZIP(), []int{7}
 }
 
-func (m *ApkDescription) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApkDescription.Unmarshal(m, b)
-}
-func (m *ApkDescription) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApkDescription.Marshal(b, m, deterministic)
-}
-func (m *ApkDescription) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApkDescription.Merge(m, src)
-}
-func (m *ApkDescription) XXX_Size() int {
-	return xxx_messageInfo_ApkDescription.Size(m)
-}
-func (m *ApkDescription) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApkDescription.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApkDescription proto.InternalMessageInfo
-
-func (m *ApkDescription) GetTargeting() *ApkTargeting {
-	if m != nil {
-		return m.Targeting
+func (x *ApkDescription) GetTargeting() *ApkTargeting {
+	if x != nil {
+		return x.Targeting
 	}
 	return nil
 }
 
-func (m *ApkDescription) GetPath() string {
-	if m != nil {
-		return m.Path
+func (x *ApkDescription) GetPath() string {
+	if x != nil {
+		return x.Path
 	}
 	return ""
 }
 
+func (m *ApkDescription) GetApkMetadataOneofValue() isApkDescription_ApkMetadataOneofValue {
+	if m != nil {
+		return m.ApkMetadataOneofValue
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetSplitApkMetadata() *SplitApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_SplitApkMetadata); ok {
+		return x.SplitApkMetadata
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetStandaloneApkMetadata() *StandaloneApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_StandaloneApkMetadata); ok {
+		return x.StandaloneApkMetadata
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetInstantApkMetadata() *SplitApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_InstantApkMetadata); ok {
+		return x.InstantApkMetadata
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetSystemApkMetadata() *SystemApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_SystemApkMetadata); ok {
+		return x.SystemApkMetadata
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetAssetSliceMetadata() *SplitApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_AssetSliceMetadata); ok {
+		return x.AssetSliceMetadata
+	}
+	return nil
+}
+
+func (x *ApkDescription) GetApexApkMetadata() *ApexApkMetadata {
+	if x, ok := x.GetApkMetadataOneofValue().(*ApkDescription_ApexApkMetadata); ok {
+		return x.ApexApkMetadata
+	}
+	return nil
+}
+
 type isApkDescription_ApkMetadataOneofValue interface {
 	isApkDescription_ApkMetadataOneofValue()
 }
 
 type ApkDescription_SplitApkMetadata struct {
+	// Set only for Split APKs.
 	SplitApkMetadata *SplitApkMetadata `protobuf:"bytes,3,opt,name=split_apk_metadata,json=splitApkMetadata,proto3,oneof"`
 }
 
 type ApkDescription_StandaloneApkMetadata struct {
+	// Set only for standalone APKs.
 	StandaloneApkMetadata *StandaloneApkMetadata `protobuf:"bytes,4,opt,name=standalone_apk_metadata,json=standaloneApkMetadata,proto3,oneof"`
 }
 
 type ApkDescription_InstantApkMetadata struct {
+	// Set only for Instant split APKs.
 	InstantApkMetadata *SplitApkMetadata `protobuf:"bytes,5,opt,name=instant_apk_metadata,json=instantApkMetadata,proto3,oneof"`
 }
 
 type ApkDescription_SystemApkMetadata struct {
+	// Set only for system APKs.
 	SystemApkMetadata *SystemApkMetadata `protobuf:"bytes,6,opt,name=system_apk_metadata,json=systemApkMetadata,proto3,oneof"`
 }
 
 type ApkDescription_AssetSliceMetadata struct {
+	// Set only for asset slices.
 	AssetSliceMetadata *SplitApkMetadata `protobuf:"bytes,7,opt,name=asset_slice_metadata,json=assetSliceMetadata,proto3,oneof"`
 }
 
 type ApkDescription_ApexApkMetadata struct {
+	// Set only for APEX APKs.
 	ApexApkMetadata *ApexApkMetadata `protobuf:"bytes,8,opt,name=apex_apk_metadata,json=apexApkMetadata,proto3,oneof"`
 }
 
@@ -642,58 +814,708 @@
 
 func (*ApkDescription_ApexApkMetadata) isApkDescription_ApkMetadataOneofValue() {}
 
-func (m *ApkDescription) GetApkMetadataOneofValue() isApkDescription_ApkMetadataOneofValue {
-	if m != nil {
-		return m.ApkMetadataOneofValue
+// Holds data specific to Split APKs.
+type SplitApkMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	SplitId string `protobuf:"bytes,1,opt,name=split_id,json=splitId,proto3" json:"split_id,omitempty"`
+	// Indicates whether this APK is the master split of the module.
+	IsMasterSplit bool `protobuf:"varint,2,opt,name=is_master_split,json=isMasterSplit,proto3" json:"is_master_split,omitempty"`
+}
+
+func (x *SplitApkMetadata) Reset() {
+	*x = SplitApkMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SplitApkMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SplitApkMetadata) ProtoMessage() {}
+
+func (x *SplitApkMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[8]
+	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 SplitApkMetadata.ProtoReflect.Descriptor instead.
+func (*SplitApkMetadata) Descriptor() ([]byte, []int) {
+	return file_commands_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *SplitApkMetadata) GetSplitId() string {
+	if x != nil {
+		return x.SplitId
+	}
+	return ""
+}
+
+func (x *SplitApkMetadata) GetIsMasterSplit() bool {
+	if x != nil {
+		return x.IsMasterSplit
+	}
+	return false
+}
+
+// Holds data specific to Standalone APKs.
+type StandaloneApkMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Names of the modules fused in this standalone APK.
+	FusedModuleName []string `protobuf:"bytes,1,rep,name=fused_module_name,json=fusedModuleName,proto3" json:"fused_module_name,omitempty"`
+}
+
+func (x *StandaloneApkMetadata) Reset() {
+	*x = StandaloneApkMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StandaloneApkMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StandaloneApkMetadata) ProtoMessage() {}
+
+func (x *StandaloneApkMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[9]
+	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 StandaloneApkMetadata.ProtoReflect.Descriptor instead.
+func (*StandaloneApkMetadata) Descriptor() ([]byte, []int) {
+	return file_commands_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *StandaloneApkMetadata) GetFusedModuleName() []string {
+	if x != nil {
+		return x.FusedModuleName
 	}
 	return nil
 }
 
-func (m *ApkDescription) GetSplitApkMetadata() *SplitApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_SplitApkMetadata); ok {
-		return x.SplitApkMetadata
+// Holds data specific to system APKs.
+type SystemApkMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Names of the modules fused in this system APK.
+	FusedModuleName []string `protobuf:"bytes,1,rep,name=fused_module_name,json=fusedModuleName,proto3" json:"fused_module_name,omitempty"`
+	// Indicates whether the APK is uncompressed system APK, stub APK or
+	// compressed system APK.
+	SystemApkType SystemApkMetadata_SystemApkType `protobuf:"varint,2,opt,name=system_apk_type,json=systemApkType,proto3,enum=android.bundle.SystemApkMetadata_SystemApkType" json:"system_apk_type,omitempty"`
+}
+
+func (x *SystemApkMetadata) Reset() {
+	*x = SystemApkMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SystemApkMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SystemApkMetadata) ProtoMessage() {}
+
+func (x *SystemApkMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_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 SystemApkMetadata.ProtoReflect.Descriptor instead.
+func (*SystemApkMetadata) Descriptor() ([]byte, []int) {
+	return file_commands_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *SystemApkMetadata) GetFusedModuleName() []string {
+	if x != nil {
+		return x.FusedModuleName
 	}
 	return nil
 }
 
-func (m *ApkDescription) GetStandaloneApkMetadata() *StandaloneApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_StandaloneApkMetadata); ok {
-		return x.StandaloneApkMetadata
+func (x *SystemApkMetadata) GetSystemApkType() SystemApkMetadata_SystemApkType {
+	if x != nil {
+		return x.SystemApkType
+	}
+	return SystemApkMetadata_UNSPECIFIED_VALUE
+}
+
+// Holds data specific to APEX APKs.
+type ApexApkMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Configuration for processing of APKs embedded in an APEX image.
+	ApexEmbeddedApkConfig []*ApexEmbeddedApkConfig `protobuf:"bytes,1,rep,name=apex_embedded_apk_config,json=apexEmbeddedApkConfig,proto3" json:"apex_embedded_apk_config,omitempty"`
+}
+
+func (x *ApexApkMetadata) Reset() {
+	*x = ApexApkMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApexApkMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApexApkMetadata) ProtoMessage() {}
+
+func (x *ApexApkMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[11]
+	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 ApexApkMetadata.ProtoReflect.Descriptor instead.
+func (*ApexApkMetadata) Descriptor() ([]byte, []int) {
+	return file_commands_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *ApexApkMetadata) GetApexEmbeddedApkConfig() []*ApexEmbeddedApkConfig {
+	if x != nil {
+		return x.ApexEmbeddedApkConfig
 	}
 	return nil
 }
 
-func (m *ApkDescription) GetInstantApkMetadata() *SplitApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_InstantApkMetadata); ok {
-		return x.InstantApkMetadata
-	}
-	return nil
+type LocalTestingInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Indicates if the bundle is built in local testing mode.
+	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
+	// The local testing path, as specified in the base manifest.
+	// This refers to the relative path on the external directory of the app where
+	// APKs will be pushed for local testing.
+	// Set only if local testing is enabled.
+	LocalTestingPath string `protobuf:"bytes,2,opt,name=local_testing_path,json=localTestingPath,proto3" json:"local_testing_path,omitempty"`
 }
 
-func (m *ApkDescription) GetSystemApkMetadata() *SystemApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_SystemApkMetadata); ok {
-		return x.SystemApkMetadata
+func (x *LocalTestingInfo) Reset() {
+	*x = LocalTestingInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_commands_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
 	}
-	return nil
 }
 
-func (m *ApkDescription) GetAssetSliceMetadata() *SplitApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_AssetSliceMetadata); ok {
-		return x.AssetSliceMetadata
-	}
-	return nil
+func (x *LocalTestingInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-func (m *ApkDescription) GetApexApkMetadata() *ApexApkMetadata {
-	if x, ok := m.GetApkMetadataOneofValue().(*ApkDescription_ApexApkMetadata); ok {
-		return x.ApexApkMetadata
+func (*LocalTestingInfo) ProtoMessage() {}
+
+func (x *LocalTestingInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_commands_proto_msgTypes[12]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
 	}
-	return nil
+	return mi.MessageOf(x)
 }
 
-// XXX_OneofWrappers is for the internal use of the proto package.
-func (*ApkDescription) XXX_OneofWrappers() []interface{} {
-	return []interface{}{
+// Deprecated: Use LocalTestingInfo.ProtoReflect.Descriptor instead.
+func (*LocalTestingInfo) Descriptor() ([]byte, []int) {
+	return file_commands_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *LocalTestingInfo) GetEnabled() bool {
+	if x != nil {
+		return x.Enabled
+	}
+	return false
+}
+
+func (x *LocalTestingInfo) GetLocalTestingPath() string {
+	if x != nil {
+		return x.LocalTestingPath
+	}
+	return ""
+}
+
+var File_commands_proto protoreflect.FileDescriptor
+
+var file_commands_proto_rawDesc = []byte{
+	0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x0e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x1a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f,
+	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
+	0xba, 0x02, 0x0a, 0x0f, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x41, 0x70, 0x6b, 0x73, 0x52, 0x65, 0x73,
+	0x75, 0x6c, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b, 0x61,
+	0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x07, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e,
+	0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74,
+	0x52, 0x07, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x42,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x52, 0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x45, 0x0a, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73,
+	0x6c, 0x69, 0x63, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d,
+	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e,
+	0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x52, 0x0d, 0x61,
+	0x73, 0x73, 0x65, 0x74, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x4e, 0x0a, 0x12,
+	0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e,
+	0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x54,
+	0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61,
+	0x6c, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0xa1, 0x01, 0x0a,
+	0x07, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x6e,
+	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x56, 0x61, 0x72,
+	0x69, 0x61, 0x6e, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x09, 0x74,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x0a, 0x07, 0x61, 0x70, 0x6b, 0x5f,
+	0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x70, 0x6b, 0x53, 0x65,
+	0x74, 0x52, 0x06, 0x61, 0x70, 0x6b, 0x53, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x61, 0x72,
+	0x69, 0x61, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0d, 0x52, 0x0d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+	0x22, 0x9a, 0x01, 0x0a, 0x06, 0x41, 0x70, 0x6b, 0x53, 0x65, 0x74, 0x12, 0x47, 0x0a, 0x0f, 0x6d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x0f, 0x61, 0x70, 0x6b, 0x5f, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41,
+	0x70, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61,
+	0x70, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x9f, 0x02,
+	0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x0d, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79,
+	0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x61, 0x6e,
+	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x44, 0x65, 0x6c,
+	0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x64, 0x65, 0x6c, 0x69, 0x76,
+	0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x69, 0x6e,
+	0x73, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x49,
+	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64,
+	0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65,
+	0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x3d, 0x0a, 0x09, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x09,
+	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x14, 0x6f, 0x6e, 0x5f,
+	0x64, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65,
+	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x6f, 0x6e, 0x44,
+	0x65, 0x6d, 0x61, 0x6e, 0x64, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x22,
+	0xb1, 0x01, 0x0a, 0x0d, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x53, 0x65,
+	0x74, 0x12, 0x57, 0x0a, 0x15, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x13, 0x61, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x0f, 0x61, 0x70,
+	0x6b, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75,
+	0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x70, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x70, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x22, 0xee, 0x01, 0x0a, 0x13, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x41, 0x0a, 0x0d, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79,
+	0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x64, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x79,
+	0x70, 0x65, 0x12, 0x4a, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x5f, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x49, 0x6e,
+	0x73, 0x74, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0f, 0x69,
+	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x34,
+	0x0a, 0x14, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x72,
+	0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01,
+	0x52, 0x12, 0x6f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63,
+	0x61, 0x74, 0x65, 0x64, 0x22, 0xa9, 0x01, 0x0a, 0x0f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74,
+	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x69,
+	0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73,
+	0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x12, 0x41, 0x0a, 0x0d, 0x64, 0x65, 0x6c, 0x69, 0x76,
+	0x65, 0x72, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c,
+	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e,
+	0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x64, 0x65,
+	0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x14, 0x6f, 0x6e,
+	0x5f, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74,
+	0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x12, 0x6f, 0x6e,
+	0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64,
+	0x22, 0xff, 0x04, 0x0a, 0x0e, 0x41, 0x70, 0x6b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x70, 0x6b, 0x54, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x69, 0x6e, 0x67, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12,
+	0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70,
+	0x61, 0x74, 0x68, 0x12, 0x50, 0x0a, 0x12, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f, 0x61, 0x70, 0x6b,
+	0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x20, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x48, 0x00, 0x52, 0x10, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74,
+	0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x5f, 0x0a, 0x17, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c,
+	0x6f, 0x6e, 0x65, 0x5f, 0x61, 0x70, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f,
+	0x6e, 0x65, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52,
+	0x15, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x41, 0x70, 0x6b, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x54, 0x0a, 0x14, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
+	0x74, 0x5f, 0x61, 0x70, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x12, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e,
+	0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, 0x13,
+	0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x61, 0x70, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65,
+	0x6d, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x11,
+	0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x12, 0x54, 0x0a, 0x14, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65,
+	0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x20, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
+	0x61, 0x48, 0x00, 0x52, 0x12, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x4d,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4d, 0x0a, 0x11, 0x61, 0x70, 0x65, 0x78, 0x5f,
+	0x61, 0x70, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x2e, 0x41, 0x70, 0x65, 0x78, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x65, 0x78, 0x41, 0x70, 0x6b, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x1a, 0x0a, 0x18, 0x61, 0x70, 0x6b, 0x5f, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x22, 0x55, 0x0a, 0x10, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x6b, 0x4d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x49,
+	0x64, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x73,
+	0x70, 0x6c, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x73, 0x4d, 0x61,
+	0x73, 0x74, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x22, 0x49, 0x0a, 0x15, 0x53, 0x74, 0x61,
+	0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x66,
+	0x75, 0x73, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04,
+	0x08, 0x02, 0x10, 0x03, 0x22, 0xf4, 0x01, 0x0a, 0x11, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41,
+	0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x75,
+	0x73, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x75, 0x73, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x5f, 0x61, 0x70, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x2f, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x70, 0x6b, 0x54, 0x79, 0x70, 0x65,
+	0x52, 0x0d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x70, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x22,
+	0x5a, 0x0a, 0x0d, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x41, 0x70, 0x6b, 0x54, 0x79, 0x70, 0x65,
+	0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f,
+	0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x59, 0x53, 0x54, 0x45,
+	0x4d, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x53, 0x54,
+	0x55, 0x42, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x43,
+	0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x45, 0x44, 0x10, 0x03, 0x22, 0x71, 0x0a, 0x0f, 0x41,
+	0x70, 0x65, 0x78, 0x41, 0x70, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x5e,
+	0x0a, 0x18, 0x61, 0x70, 0x65, 0x78, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f,
+	0x61, 0x70, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x25, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x41, 0x70, 0x65, 0x78, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x41, 0x70,
+	0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x15, 0x61, 0x70, 0x65, 0x78, 0x45, 0x6d, 0x62,
+	0x65, 0x64, 0x64, 0x65, 0x64, 0x41, 0x70, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5a,
+	0x0a, 0x10, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x49, 0x6e,
+	0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12,
+	0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61,
+	0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54,
+	0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x74, 0x68, 0x2a, 0x5b, 0x0a, 0x0c, 0x44, 0x65,
+	0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e,
+	0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x49, 0x56, 0x45, 0x52, 0x59, 0x5f, 0x54,
+	0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c,
+	0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x4e, 0x5f, 0x44, 0x45,
+	0x4d, 0x41, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x41, 0x53, 0x54, 0x5f, 0x46,
+	0x4f, 0x4c, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x42, 0x41, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5a, 0x2b, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64,
+	0x2f, 0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x70, 0x6b, 0x73, 0x2f, 0x62, 0x75,
+	0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_commands_proto_rawDescOnce sync.Once
+	file_commands_proto_rawDescData = file_commands_proto_rawDesc
+)
+
+func file_commands_proto_rawDescGZIP() []byte {
+	file_commands_proto_rawDescOnce.Do(func() {
+		file_commands_proto_rawDescData = protoimpl.X.CompressGZIP(file_commands_proto_rawDescData)
+	})
+	return file_commands_proto_rawDescData
+}
+
+var file_commands_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_commands_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
+var file_commands_proto_goTypes = []interface{}{
+	(DeliveryType)(0),                    // 0: android.bundle.DeliveryType
+	(SystemApkMetadata_SystemApkType)(0), // 1: android.bundle.SystemApkMetadata.SystemApkType
+	(*BuildApksResult)(nil),              // 2: android.bundle.BuildApksResult
+	(*Variant)(nil),                      // 3: android.bundle.Variant
+	(*ApkSet)(nil),                       // 4: android.bundle.ApkSet
+	(*ModuleMetadata)(nil),               // 5: android.bundle.ModuleMetadata
+	(*AssetSliceSet)(nil),                // 6: android.bundle.AssetSliceSet
+	(*AssetModuleMetadata)(nil),          // 7: android.bundle.AssetModuleMetadata
+	(*InstantMetadata)(nil),              // 8: android.bundle.InstantMetadata
+	(*ApkDescription)(nil),               // 9: android.bundle.ApkDescription
+	(*SplitApkMetadata)(nil),             // 10: android.bundle.SplitApkMetadata
+	(*StandaloneApkMetadata)(nil),        // 11: android.bundle.StandaloneApkMetadata
+	(*SystemApkMetadata)(nil),            // 12: android.bundle.SystemApkMetadata
+	(*ApexApkMetadata)(nil),              // 13: android.bundle.ApexApkMetadata
+	(*LocalTestingInfo)(nil),             // 14: android.bundle.LocalTestingInfo
+	(*Bundletool)(nil),                   // 15: android.bundle.Bundletool
+	(*VariantTargeting)(nil),             // 16: android.bundle.VariantTargeting
+	(*ModuleTargeting)(nil),              // 17: android.bundle.ModuleTargeting
+	(*ApkTargeting)(nil),                 // 18: android.bundle.ApkTargeting
+	(*ApexEmbeddedApkConfig)(nil),        // 19: android.bundle.ApexEmbeddedApkConfig
+}
+var file_commands_proto_depIdxs = []int32{
+	3,  // 0: android.bundle.BuildApksResult.variant:type_name -> android.bundle.Variant
+	15, // 1: android.bundle.BuildApksResult.bundletool:type_name -> android.bundle.Bundletool
+	6,  // 2: android.bundle.BuildApksResult.asset_slice_set:type_name -> android.bundle.AssetSliceSet
+	14, // 3: android.bundle.BuildApksResult.local_testing_info:type_name -> android.bundle.LocalTestingInfo
+	16, // 4: android.bundle.Variant.targeting:type_name -> android.bundle.VariantTargeting
+	4,  // 5: android.bundle.Variant.apk_set:type_name -> android.bundle.ApkSet
+	5,  // 6: android.bundle.ApkSet.module_metadata:type_name -> android.bundle.ModuleMetadata
+	9,  // 7: android.bundle.ApkSet.apk_description:type_name -> android.bundle.ApkDescription
+	0,  // 8: android.bundle.ModuleMetadata.delivery_type:type_name -> android.bundle.DeliveryType
+	17, // 9: android.bundle.ModuleMetadata.targeting:type_name -> android.bundle.ModuleTargeting
+	7,  // 10: android.bundle.AssetSliceSet.asset_module_metadata:type_name -> android.bundle.AssetModuleMetadata
+	9,  // 11: android.bundle.AssetSliceSet.apk_description:type_name -> android.bundle.ApkDescription
+	0,  // 12: android.bundle.AssetModuleMetadata.delivery_type:type_name -> android.bundle.DeliveryType
+	8,  // 13: android.bundle.AssetModuleMetadata.instant_metadata:type_name -> android.bundle.InstantMetadata
+	0,  // 14: android.bundle.InstantMetadata.delivery_type:type_name -> android.bundle.DeliveryType
+	18, // 15: android.bundle.ApkDescription.targeting:type_name -> android.bundle.ApkTargeting
+	10, // 16: android.bundle.ApkDescription.split_apk_metadata:type_name -> android.bundle.SplitApkMetadata
+	11, // 17: android.bundle.ApkDescription.standalone_apk_metadata:type_name -> android.bundle.StandaloneApkMetadata
+	10, // 18: android.bundle.ApkDescription.instant_apk_metadata:type_name -> android.bundle.SplitApkMetadata
+	12, // 19: android.bundle.ApkDescription.system_apk_metadata:type_name -> android.bundle.SystemApkMetadata
+	10, // 20: android.bundle.ApkDescription.asset_slice_metadata:type_name -> android.bundle.SplitApkMetadata
+	13, // 21: android.bundle.ApkDescription.apex_apk_metadata:type_name -> android.bundle.ApexApkMetadata
+	1,  // 22: android.bundle.SystemApkMetadata.system_apk_type:type_name -> android.bundle.SystemApkMetadata.SystemApkType
+	19, // 23: android.bundle.ApexApkMetadata.apex_embedded_apk_config:type_name -> android.bundle.ApexEmbeddedApkConfig
+	24, // [24:24] is the sub-list for method output_type
+	24, // [24:24] is the sub-list for method input_type
+	24, // [24:24] is the sub-list for extension type_name
+	24, // [24:24] is the sub-list for extension extendee
+	0,  // [0:24] is the sub-list for field type_name
+}
+
+func init() { file_commands_proto_init() }
+func file_commands_proto_init() {
+	if File_commands_proto != nil {
+		return
+	}
+	file_config_proto_init()
+	file_targeting_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_commands_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildApksResult); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Variant); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApkSet); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ModuleMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AssetSliceSet); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AssetModuleMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*InstantMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApkDescription); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SplitApkMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StandaloneApkMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SystemApkMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApexApkMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_commands_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LocalTestingInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_commands_proto_msgTypes[7].OneofWrappers = []interface{}{
 		(*ApkDescription_SplitApkMetadata)(nil),
 		(*ApkDescription_StandaloneApkMetadata)(nil),
 		(*ApkDescription_InstantApkMetadata)(nil),
@@ -701,333 +1523,23 @@
 		(*ApkDescription_AssetSliceMetadata)(nil),
 		(*ApkDescription_ApexApkMetadata)(nil),
 	}
-}
-
-// Holds data specific to Split APKs.
-type SplitApkMetadata struct {
-	SplitId string `protobuf:"bytes,1,opt,name=split_id,json=splitId,proto3" json:"split_id,omitempty"`
-	// Indicates whether this APK is the master split of the module.
-	IsMasterSplit        bool     `protobuf:"varint,2,opt,name=is_master_split,json=isMasterSplit,proto3" json:"is_master_split,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *SplitApkMetadata) Reset()         { *m = SplitApkMetadata{} }
-func (m *SplitApkMetadata) String() string { return proto.CompactTextString(m) }
-func (*SplitApkMetadata) ProtoMessage()    {}
-func (*SplitApkMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{8}
-}
-
-func (m *SplitApkMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SplitApkMetadata.Unmarshal(m, b)
-}
-func (m *SplitApkMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SplitApkMetadata.Marshal(b, m, deterministic)
-}
-func (m *SplitApkMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SplitApkMetadata.Merge(m, src)
-}
-func (m *SplitApkMetadata) XXX_Size() int {
-	return xxx_messageInfo_SplitApkMetadata.Size(m)
-}
-func (m *SplitApkMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_SplitApkMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SplitApkMetadata proto.InternalMessageInfo
-
-func (m *SplitApkMetadata) GetSplitId() string {
-	if m != nil {
-		return m.SplitId
-	}
-	return ""
-}
-
-func (m *SplitApkMetadata) GetIsMasterSplit() bool {
-	if m != nil {
-		return m.IsMasterSplit
-	}
-	return false
-}
-
-// Holds data specific to Standalone APKs.
-type StandaloneApkMetadata struct {
-	// Names of the modules fused in this standalone APK.
-	FusedModuleName      []string `protobuf:"bytes,1,rep,name=fused_module_name,json=fusedModuleName,proto3" json:"fused_module_name,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *StandaloneApkMetadata) Reset()         { *m = StandaloneApkMetadata{} }
-func (m *StandaloneApkMetadata) String() string { return proto.CompactTextString(m) }
-func (*StandaloneApkMetadata) ProtoMessage()    {}
-func (*StandaloneApkMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{9}
-}
-
-func (m *StandaloneApkMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_StandaloneApkMetadata.Unmarshal(m, b)
-}
-func (m *StandaloneApkMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_StandaloneApkMetadata.Marshal(b, m, deterministic)
-}
-func (m *StandaloneApkMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_StandaloneApkMetadata.Merge(m, src)
-}
-func (m *StandaloneApkMetadata) XXX_Size() int {
-	return xxx_messageInfo_StandaloneApkMetadata.Size(m)
-}
-func (m *StandaloneApkMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_StandaloneApkMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_StandaloneApkMetadata proto.InternalMessageInfo
-
-func (m *StandaloneApkMetadata) GetFusedModuleName() []string {
-	if m != nil {
-		return m.FusedModuleName
-	}
-	return nil
-}
-
-// Holds data specific to system APKs.
-type SystemApkMetadata struct {
-	// Names of the modules fused in this system APK.
-	FusedModuleName []string `protobuf:"bytes,1,rep,name=fused_module_name,json=fusedModuleName,proto3" json:"fused_module_name,omitempty"`
-	// Indicates whether the APK is uncompressed system APK, stub APK or
-	// compressed system APK.
-	SystemApkType        SystemApkMetadata_SystemApkType `protobuf:"varint,2,opt,name=system_apk_type,json=systemApkType,proto3,enum=android.bundle.SystemApkMetadata_SystemApkType" json:"system_apk_type,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                        `json:"-"`
-	XXX_unrecognized     []byte                          `json:"-"`
-	XXX_sizecache        int32                           `json:"-"`
-}
-
-func (m *SystemApkMetadata) Reset()         { *m = SystemApkMetadata{} }
-func (m *SystemApkMetadata) String() string { return proto.CompactTextString(m) }
-func (*SystemApkMetadata) ProtoMessage()    {}
-func (*SystemApkMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{10}
-}
-
-func (m *SystemApkMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SystemApkMetadata.Unmarshal(m, b)
-}
-func (m *SystemApkMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SystemApkMetadata.Marshal(b, m, deterministic)
-}
-func (m *SystemApkMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SystemApkMetadata.Merge(m, src)
-}
-func (m *SystemApkMetadata) XXX_Size() int {
-	return xxx_messageInfo_SystemApkMetadata.Size(m)
-}
-func (m *SystemApkMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_SystemApkMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SystemApkMetadata proto.InternalMessageInfo
-
-func (m *SystemApkMetadata) GetFusedModuleName() []string {
-	if m != nil {
-		return m.FusedModuleName
-	}
-	return nil
-}
-
-func (m *SystemApkMetadata) GetSystemApkType() SystemApkMetadata_SystemApkType {
-	if m != nil {
-		return m.SystemApkType
-	}
-	return SystemApkMetadata_UNSPECIFIED_VALUE
-}
-
-// Holds data specific to APEX APKs.
-type ApexApkMetadata struct {
-	// Configuration for processing of APKs embedded in an APEX image.
-	ApexEmbeddedApkConfig []*ApexEmbeddedApkConfig `protobuf:"bytes,1,rep,name=apex_embedded_apk_config,json=apexEmbeddedApkConfig,proto3" json:"apex_embedded_apk_config,omitempty"`
-	XXX_NoUnkeyedLiteral  struct{}                 `json:"-"`
-	XXX_unrecognized      []byte                   `json:"-"`
-	XXX_sizecache         int32                    `json:"-"`
-}
-
-func (m *ApexApkMetadata) Reset()         { *m = ApexApkMetadata{} }
-func (m *ApexApkMetadata) String() string { return proto.CompactTextString(m) }
-func (*ApexApkMetadata) ProtoMessage()    {}
-func (*ApexApkMetadata) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{11}
-}
-
-func (m *ApexApkMetadata) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApexApkMetadata.Unmarshal(m, b)
-}
-func (m *ApexApkMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApexApkMetadata.Marshal(b, m, deterministic)
-}
-func (m *ApexApkMetadata) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApexApkMetadata.Merge(m, src)
-}
-func (m *ApexApkMetadata) XXX_Size() int {
-	return xxx_messageInfo_ApexApkMetadata.Size(m)
-}
-func (m *ApexApkMetadata) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApexApkMetadata.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApexApkMetadata proto.InternalMessageInfo
-
-func (m *ApexApkMetadata) GetApexEmbeddedApkConfig() []*ApexEmbeddedApkConfig {
-	if m != nil {
-		return m.ApexEmbeddedApkConfig
-	}
-	return nil
-}
-
-type LocalTestingInfo struct {
-	// Indicates if the bundle is built in local testing mode.
-	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
-	// The local testing path, as specified in the base manifest.
-	// This refers to the relative path on the external directory of the app where
-	// APKs will be pushed for local testing.
-	// Set only if local testing is enabled.
-	LocalTestingPath     string   `protobuf:"bytes,2,opt,name=local_testing_path,json=localTestingPath,proto3" json:"local_testing_path,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
-}
-
-func (m *LocalTestingInfo) Reset()         { *m = LocalTestingInfo{} }
-func (m *LocalTestingInfo) String() string { return proto.CompactTextString(m) }
-func (*LocalTestingInfo) ProtoMessage()    {}
-func (*LocalTestingInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_0dff099eb2e3dfdb, []int{12}
-}
-
-func (m *LocalTestingInfo) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_LocalTestingInfo.Unmarshal(m, b)
-}
-func (m *LocalTestingInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_LocalTestingInfo.Marshal(b, m, deterministic)
-}
-func (m *LocalTestingInfo) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_LocalTestingInfo.Merge(m, src)
-}
-func (m *LocalTestingInfo) XXX_Size() int {
-	return xxx_messageInfo_LocalTestingInfo.Size(m)
-}
-func (m *LocalTestingInfo) XXX_DiscardUnknown() {
-	xxx_messageInfo_LocalTestingInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_LocalTestingInfo proto.InternalMessageInfo
-
-func (m *LocalTestingInfo) GetEnabled() bool {
-	if m != nil {
-		return m.Enabled
-	}
-	return false
-}
-
-func (m *LocalTestingInfo) GetLocalTestingPath() string {
-	if m != nil {
-		return m.LocalTestingPath
-	}
-	return ""
-}
-
-func init() {
-	proto.RegisterEnum("android.bundle.DeliveryType", DeliveryType_name, DeliveryType_value)
-	proto.RegisterEnum("android.bundle.SystemApkMetadata_SystemApkType", SystemApkMetadata_SystemApkType_name, SystemApkMetadata_SystemApkType_value)
-	proto.RegisterType((*BuildApksResult)(nil), "android.bundle.BuildApksResult")
-	proto.RegisterType((*Variant)(nil), "android.bundle.Variant")
-	proto.RegisterType((*ApkSet)(nil), "android.bundle.ApkSet")
-	proto.RegisterType((*ModuleMetadata)(nil), "android.bundle.ModuleMetadata")
-	proto.RegisterType((*AssetSliceSet)(nil), "android.bundle.AssetSliceSet")
-	proto.RegisterType((*AssetModuleMetadata)(nil), "android.bundle.AssetModuleMetadata")
-	proto.RegisterType((*InstantMetadata)(nil), "android.bundle.InstantMetadata")
-	proto.RegisterType((*ApkDescription)(nil), "android.bundle.ApkDescription")
-	proto.RegisterType((*SplitApkMetadata)(nil), "android.bundle.SplitApkMetadata")
-	proto.RegisterType((*StandaloneApkMetadata)(nil), "android.bundle.StandaloneApkMetadata")
-	proto.RegisterType((*SystemApkMetadata)(nil), "android.bundle.SystemApkMetadata")
-	proto.RegisterType((*ApexApkMetadata)(nil), "android.bundle.ApexApkMetadata")
-	proto.RegisterType((*LocalTestingInfo)(nil), "android.bundle.LocalTestingInfo")
-}
-
-func init() {
-	proto.RegisterFile("commands.proto", fileDescriptor_0dff099eb2e3dfdb)
-}
-
-var fileDescriptor_0dff099eb2e3dfdb = []byte{
-	// 1104 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcf, 0x6f, 0xe2, 0x46,
-	0x14, 0x5e, 0x03, 0x0b, 0xe1, 0x05, 0xb0, 0x33, 0x1b, 0xba, 0xde, 0x68, 0x77, 0xcb, 0xba, 0x4a,
-	0x85, 0xa2, 0x2a, 0xab, 0xa6, 0x3d, 0xad, 0xd4, 0x4a, 0x10, 0x9c, 0x96, 0x2d, 0x90, 0xc8, 0x26,
-	0x89, 0x92, 0x4a, 0x1d, 0x4d, 0x98, 0x49, 0xd6, 0xc2, 0xbf, 0xca, 0x98, 0x28, 0xf9, 0x57, 0x7a,
-	0xa9, 0x7a, 0xec, 0xb1, 0xd7, 0xfe, 0x51, 0x3d, 0xf5, 0xde, 0xca, 0x63, 0x03, 0xb6, 0xb1, 0xd4,
-	0x64, 0xd5, 0x13, 0x7e, 0x6f, 0xbe, 0xf9, 0xe6, 0xbd, 0xf7, 0xbd, 0x79, 0x0c, 0x34, 0x26, 0x9e,
-	0xe3, 0x10, 0x97, 0xf2, 0x7d, 0x7f, 0xe6, 0x05, 0x1e, 0x6a, 0x10, 0x97, 0xce, 0x3c, 0x8b, 0xee,
-	0x5f, 0xcd, 0x5d, 0x6a, 0xb3, 0x9d, 0xda, 0xc4, 0x73, 0xaf, 0xad, 0x9b, 0x68, 0x75, 0x47, 0x0e,
-	0xc8, 0xec, 0x86, 0x05, 0x96, 0x1b, 0x3b, 0xb4, 0x3f, 0x0b, 0x20, 0x77, 0xe7, 0x96, 0x4d, 0x3b,
-	0xfe, 0x94, 0x1b, 0x8c, 0xcf, 0xed, 0x00, 0xbd, 0x81, 0x9a, 0x4f, 0x26, 0x53, 0x72, 0xc3, 0xb0,
-	0x4b, 0x1c, 0xa6, 0x96, 0x5a, 0x52, 0xbb, 0x6a, 0x6c, 0xc6, 0xbe, 0x11, 0x71, 0x18, 0xfa, 0x12,
-	0x2a, 0xb7, 0x64, 0x66, 0x11, 0x37, 0x50, 0xa5, 0x56, 0xb1, 0xbd, 0x79, 0xf0, 0x7c, 0x3f, 0x7d,
-	0xee, 0xfe, 0x59, 0xb4, 0x6c, 0x2c, 0x70, 0xe8, 0x1d, 0x40, 0xb4, 0x14, 0x78, 0x9e, 0xad, 0x16,
-	0x5a, 0x52, 0x7b, 0xf3, 0x60, 0x27, 0xbb, 0xab, 0xbb, 0x44, 0x18, 0x09, 0x34, 0xd2, 0x41, 0x26,
-	0x9c, 0xb3, 0x00, 0x73, 0xdb, 0x9a, 0x30, 0xcc, 0x59, 0xa0, 0x16, 0xc5, 0xb1, 0xaf, 0xb2, 0x04,
-	0x9d, 0x10, 0x66, 0x86, 0x28, 0x93, 0x05, 0x46, 0x9d, 0x24, 0x4d, 0x34, 0x02, 0x64, 0x7b, 0x13,
-	0x62, 0xe3, 0x80, 0xf1, 0xb0, 0x06, 0xd8, 0x72, 0xaf, 0x3d, 0xf5, 0xa9, 0x08, 0xa5, 0x95, 0x65,
-	0x1a, 0x84, 0xc8, 0x71, 0x04, 0xec, 0xbb, 0xd7, 0x9e, 0xa1, 0xd8, 0x19, 0x8f, 0xf6, 0x9b, 0x04,
-	0x95, 0x38, 0x4f, 0xf4, 0x2d, 0x54, 0x97, 0xb5, 0x55, 0xa5, 0x7c, 0xca, 0x18, 0x3b, 0x5e, 0xe0,
-	0x8c, 0xd5, 0x16, 0xf4, 0x16, 0x2a, 0xc4, 0x9f, 0x8a, 0xd4, 0x0a, 0x22, 0xb5, 0x4f, 0xd6, 0x52,
-	0xf3, 0xa7, 0x61, 0x4e, 0x65, 0x22, 0x7e, 0xd1, 0x2e, 0x34, 0xe2, 0xd2, 0x62, 0x77, 0xee, 0x5c,
-	0xb1, 0x99, 0x5a, 0x6c, 0x49, 0xed, 0xba, 0x51, 0x8f, 0xbd, 0x23, 0xe1, 0xd4, 0x7e, 0x91, 0xa0,
-	0x1c, 0xed, 0x44, 0xdf, 0x81, 0xec, 0x78, 0x74, 0x6e, 0x33, 0xec, 0xb0, 0x80, 0x50, 0x12, 0x90,
-	0x38, 0xd0, 0xd7, 0xd9, 0xa3, 0x86, 0x02, 0x36, 0x8c, 0x51, 0x46, 0xc3, 0x49, 0xd9, 0x21, 0x51,
-	0x18, 0x2b, 0x65, 0x7c, 0x32, 0xb3, 0xfc, 0xc0, 0xf2, 0xdc, 0x38, 0xe6, 0xd7, 0x39, 0x31, 0xf7,
-	0x56, 0x28, 0xa3, 0x41, 0x52, 0xb6, 0xf6, 0x6b, 0x01, 0x1a, 0xe9, 0xb3, 0x10, 0x82, 0x92, 0x68,
-	0x3a, 0x49, 0x34, 0x9d, 0xf8, 0x46, 0x1d, 0xa8, 0x53, 0x66, 0x5b, 0xb7, 0x6c, 0x76, 0x8f, 0x83,
-	0x7b, 0x9f, 0xa9, 0xe5, 0x96, 0xd4, 0x6e, 0x1c, 0xbc, 0xcc, 0x9e, 0xd6, 0x8b, 0x41, 0xe3, 0x7b,
-	0x9f, 0x19, 0x35, 0x9a, 0xb0, 0xd0, 0x2b, 0x00, 0x8b, 0x63, 0xcb, 0xe5, 0x41, 0xd8, 0xb3, 0x61,
-	0xa5, 0x36, 0x8c, 0xaa, 0xc5, 0xfb, 0x91, 0x03, 0x69, 0x50, 0xa3, 0xcc, 0x67, 0x2e, 0x65, 0xee,
-	0xc4, 0x62, 0x5c, 0x2d, 0xb5, 0x8a, 0xed, 0xaa, 0x91, 0xf2, 0xa1, 0x6f, 0x92, 0x0a, 0x47, 0x4d,
-	0xf3, 0x69, 0x7e, 0xe1, 0x72, 0x05, 0xfe, 0x1a, 0xb6, 0x3d, 0x17, 0x53, 0x16, 0x5e, 0x56, 0x4c,
-	0x99, 0x3f, 0x63, 0x13, 0x12, 0x30, 0x2a, 0x6e, 0xc2, 0x46, 0xb7, 0xa0, 0x4a, 0x06, 0xf2, 0xdc,
-	0x9e, 0x58, 0xee, 0x2d, 0x57, 0xb5, 0x3f, 0x24, 0xa8, 0xa7, 0x7a, 0x1a, 0x9d, 0x43, 0x33, 0xba,
-	0x0b, 0xf9, 0x5a, 0x7e, 0x96, 0x7b, 0x23, 0x32, 0x82, 0x3e, 0x23, 0xeb, 0xce, 0xff, 0x4f, 0xd5,
-	0xbf, 0x24, 0x78, 0x96, 0x73, 0xea, 0xc3, 0xa4, 0x2d, 0x3d, 0x5a, 0xda, 0xf7, 0xa0, 0xc4, 0xba,
-	0xae, 0x6a, 0x51, 0xcc, 0x97, 0x27, 0x96, 0x7b, 0x59, 0x07, 0xd9, 0x4a, 0x3b, 0x3e, 0x52, 0xa4,
-	0xdf, 0x25, 0x90, 0x33, 0xd4, 0x99, 0x86, 0x93, 0xb2, 0x0d, 0xb7, 0x96, 0x77, 0xf1, 0xd1, 0x79,
-	0x7f, 0x5c, 0xac, 0xff, 0x94, 0xa0, 0x91, 0xd6, 0x0f, 0xbd, 0x5b, 0x1f, 0x5d, 0x2f, 0x73, 0x24,
-	0xcf, 0xed, 0x6a, 0x04, 0x25, 0x9f, 0x04, 0x1f, 0xc4, 0xa1, 0x55, 0x43, 0x7c, 0xa3, 0x13, 0x40,
-	0xdc, 0xb7, 0xad, 0x00, 0x87, 0xed, 0x94, 0x91, 0x64, 0x6d, 0x26, 0x9a, 0x21, 0xb2, 0xe3, 0x4f,
-	0x17, 0x85, 0xfb, 0xfe, 0x89, 0xa1, 0xf0, 0x8c, 0x0f, 0x61, 0x78, 0x1e, 0x96, 0x8d, 0x12, 0xdb,
-	0x73, 0x59, 0x9a, 0xb6, 0x24, 0x68, 0x77, 0xd7, 0x68, 0x97, 0xf0, 0x34, 0x77, 0x93, 0xe7, 0x2d,
-	0xa0, 0x31, 0x6c, 0x2f, 0x7a, 0x28, 0xc5, 0xfe, 0xf4, 0xc1, 0x41, 0xa3, 0x78, 0x7f, 0x92, 0xd5,
-	0x84, 0x67, 0xfc, 0x9e, 0x07, 0xcc, 0x49, 0x93, 0x96, 0x05, 0xe9, 0x9b, 0x35, 0x52, 0x01, 0x4d,
-	0xb3, 0x6e, 0xf1, 0xac, 0x33, 0x0c, 0x35, 0xf9, 0x5f, 0xb8, 0x64, 0xad, 0x3c, 0x3c, 0xd4, 0xd5,
-	0xbf, 0xe2, 0x92, 0x75, 0x08, 0x5b, 0xc4, 0x67, 0x77, 0xe9, 0x40, 0x37, 0xf2, 0x6f, 0x51, 0xc7,
-	0x67, 0x77, 0x69, 0x46, 0x99, 0xa4, 0x5d, 0xdd, 0x1d, 0x50, 0x93, 0x4c, 0xd8, 0x73, 0x99, 0x77,
-	0x8d, 0x6f, 0x89, 0x3d, 0x67, 0xda, 0x29, 0x28, 0xd9, 0xa0, 0xd0, 0x0b, 0xd8, 0x88, 0x5a, 0xc6,
-	0xa2, 0xf1, 0x78, 0xa8, 0x08, 0xbb, 0x4f, 0xd1, 0xe7, 0x20, 0x5b, 0x1c, 0x3b, 0x84, 0x07, 0x6c,
-	0x86, 0x85, 0x33, 0xea, 0x70, 0xa3, 0x6e, 0xf1, 0xa1, 0xf0, 0x0a, 0x36, 0xad, 0x0f, 0xcd, 0x5c,
-	0xd1, 0xd1, 0x1e, 0x6c, 0x5d, 0xcf, 0x39, 0xa3, 0x8b, 0x81, 0x19, 0xcf, 0xa0, 0x70, 0xc0, 0xcb,
-	0x62, 0x21, 0x1a, 0x53, 0xe1, 0xbb, 0xe6, 0x7d, 0x69, 0xa3, 0xa0, 0x14, 0xb5, 0xbf, 0x25, 0xd8,
-	0x5a, 0x53, 0xe3, 0x31, 0x3c, 0xe8, 0x1c, 0xe4, 0x84, 0xf2, 0xe2, 0x82, 0x17, 0xc4, 0x05, 0x7f,
-	0xfb, 0x9f, 0xaa, 0xaf, 0x3c, 0xe2, 0xce, 0xd7, 0x79, 0xd2, 0xd4, 0x2e, 0xa1, 0x9e, 0x5a, 0x47,
-	0x4d, 0xd8, 0x3a, 0x1d, 0x99, 0x27, 0xfa, 0x61, 0xff, 0xa8, 0xaf, 0xf7, 0xf0, 0x59, 0x67, 0x70,
-	0xaa, 0x2b, 0x4f, 0x10, 0x40, 0xd9, 0xbc, 0x30, 0xc7, 0xfa, 0x50, 0x91, 0x90, 0x0c, 0x9b, 0xd1,
-	0x37, 0x36, 0xc7, 0xa7, 0x5d, 0xa5, 0x10, 0xee, 0x89, 0x1d, 0x87, 0xc7, 0xc3, 0x13, 0x43, 0x37,
-	0x4d, 0xbd, 0xa7, 0x14, 0xb5, 0x9f, 0x41, 0xce, 0x48, 0x8b, 0x7e, 0x0a, 0x75, 0x64, 0x77, 0x98,
-	0x39, 0x57, 0x8c, 0x52, 0x46, 0x45, 0x3a, 0xd1, 0x8b, 0x32, 0x7e, 0xf8, 0xed, 0xe6, 0x75, 0x87,
-	0x1e, 0xc3, 0x3b, 0xfe, 0xf4, 0x50, 0x80, 0x8d, 0x26, 0xc9, 0x73, 0x6b, 0x97, 0xa0, 0x64, 0xdf,
-	0x59, 0x48, 0x85, 0x0a, 0x73, 0xc9, 0x95, 0xcd, 0x68, 0x3c, 0x36, 0x17, 0x26, 0xfa, 0x22, 0xfb,
-	0x7e, 0x4b, 0x8c, 0x9e, 0xd4, 0xeb, 0xec, 0x84, 0x04, 0x1f, 0xf6, 0x7e, 0x84, 0x5a, 0x72, 0x7a,
-	0xa2, 0x17, 0xd0, 0x3c, 0x1d, 0xfd, 0x30, 0x3a, 0x3e, 0x1f, 0xe1, 0x9e, 0x3e, 0xe8, 0x9f, 0xe9,
-	0xc6, 0x05, 0x1e, 0x5f, 0x9c, 0x84, 0xd5, 0x52, 0xa0, 0xd6, 0x1f, 0x99, 0xe3, 0xce, 0x60, 0x80,
-	0xc7, 0xfd, 0xa1, 0xae, 0x48, 0xa8, 0x0e, 0xd5, 0xe3, 0x10, 0x37, 0xec, 0x8c, 0x7a, 0x4a, 0x21,
-	0x2c, 0xe1, 0x51, 0xc7, 0x1c, 0xe3, 0xa3, 0xe3, 0xc1, 0xe0, 0xf8, 0x5c, 0x29, 0x76, 0xf7, 0x00,
-	0x4d, 0x3c, 0x27, 0x93, 0xfb, 0xe5, 0x76, 0x6c, 0xe3, 0xc8, 0xc6, 0xe2, 0x8d, 0x7d, 0x55, 0x16,
-	0x3f, 0x5f, 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xe5, 0xcb, 0x87, 0xab, 0x0b, 0x00, 0x00,
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_commands_proto_rawDesc,
+			NumEnums:      2,
+			NumMessages:   13,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_commands_proto_goTypes,
+		DependencyIndexes: file_commands_proto_depIdxs,
+		EnumInfos:         file_commands_proto_enumTypes,
+		MessageInfos:      file_commands_proto_msgTypes,
+	}.Build()
+	File_commands_proto = out.File
+	file_commands_proto_rawDesc = nil
+	file_commands_proto_goTypes = nil
+	file_commands_proto_depIdxs = nil
 }
diff --git a/cmd/extract_apks/bundle_proto/commands.proto b/cmd/extract_apks/bundle_proto/commands.proto
index b36340b..a24e26d 100644
--- a/cmd/extract_apks/bundle_proto/commands.proto
+++ b/cmd/extract_apks/bundle_proto/commands.proto
@@ -9,7 +9,7 @@
 import "config.proto";
 import "targeting.proto";
 
-option go_package = "android_bundle_proto";
+option go_package = "android/soong/cmd/extract_apks/bundle_proto";
 option java_package = "com.android.bundle";
 
 // Describes the output of the "build-apks" command.
diff --git a/cmd/extract_apks/bundle_proto/config.pb.go b/cmd/extract_apks/bundle_proto/config.pb.go
index a28147a..e358c4b 100644
--- a/cmd/extract_apks/bundle_proto/config.pb.go
+++ b/cmd/extract_apks/bundle_proto/config.pb.go
@@ -1,24 +1,29 @@
+// Messages describing APK Set's table of contents (toc.pb entry).
+// Please be advised that the ultimate source is at
+// https://github.com/google/bundletool/tree/master/src/main/proto
+// so you have been warned.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: config.proto
 
-package android_bundle_proto
+package bundle_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type BundleConfig_BundleType int32
 
@@ -28,24 +33,45 @@
 	BundleConfig_ASSET_ONLY BundleConfig_BundleType = 2
 )
 
-var BundleConfig_BundleType_name = map[int32]string{
-	0: "REGULAR",
-	1: "APEX",
-	2: "ASSET_ONLY",
-}
+// Enum value maps for BundleConfig_BundleType.
+var (
+	BundleConfig_BundleType_name = map[int32]string{
+		0: "REGULAR",
+		1: "APEX",
+		2: "ASSET_ONLY",
+	}
+	BundleConfig_BundleType_value = map[string]int32{
+		"REGULAR":    0,
+		"APEX":       1,
+		"ASSET_ONLY": 2,
+	}
+)
 
-var BundleConfig_BundleType_value = map[string]int32{
-	"REGULAR":    0,
-	"APEX":       1,
-	"ASSET_ONLY": 2,
+func (x BundleConfig_BundleType) Enum() *BundleConfig_BundleType {
+	p := new(BundleConfig_BundleType)
+	*p = x
+	return p
 }
 
 func (x BundleConfig_BundleType) String() string {
-	return proto.EnumName(BundleConfig_BundleType_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (BundleConfig_BundleType) Descriptor() protoreflect.EnumDescriptor {
+	return file_config_proto_enumTypes[0].Descriptor()
+}
+
+func (BundleConfig_BundleType) Type() protoreflect.EnumType {
+	return &file_config_proto_enumTypes[0]
+}
+
+func (x BundleConfig_BundleType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use BundleConfig_BundleType.Descriptor instead.
 func (BundleConfig_BundleType) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{0, 0}
+	return file_config_proto_rawDescGZIP(), []int{0, 0}
 }
 
 type SplitDimension_Value int32
@@ -57,36 +83,61 @@
 	SplitDimension_LANGUAGE                   SplitDimension_Value = 3
 	SplitDimension_TEXTURE_COMPRESSION_FORMAT SplitDimension_Value = 4
 	// BEGIN-INTERNAL
-	SplitDimension_GRAPHICS_API SplitDimension_Value = 5
+	SplitDimension_GRAPHICS_API SplitDimension_Value = 5 // END-INTERNAL
 )
 
-var SplitDimension_Value_name = map[int32]string{
-	0: "UNSPECIFIED_VALUE",
-	1: "ABI",
-	2: "SCREEN_DENSITY",
-	3: "LANGUAGE",
-	4: "TEXTURE_COMPRESSION_FORMAT",
-	5: "GRAPHICS_API",
-}
+// Enum value maps for SplitDimension_Value.
+var (
+	SplitDimension_Value_name = map[int32]string{
+		0: "UNSPECIFIED_VALUE",
+		1: "ABI",
+		2: "SCREEN_DENSITY",
+		3: "LANGUAGE",
+		4: "TEXTURE_COMPRESSION_FORMAT",
+		5: "GRAPHICS_API",
+	}
+	SplitDimension_Value_value = map[string]int32{
+		"UNSPECIFIED_VALUE":          0,
+		"ABI":                        1,
+		"SCREEN_DENSITY":             2,
+		"LANGUAGE":                   3,
+		"TEXTURE_COMPRESSION_FORMAT": 4,
+		"GRAPHICS_API":               5,
+	}
+)
 
-var SplitDimension_Value_value = map[string]int32{
-	"UNSPECIFIED_VALUE":          0,
-	"ABI":                        1,
-	"SCREEN_DENSITY":             2,
-	"LANGUAGE":                   3,
-	"TEXTURE_COMPRESSION_FORMAT": 4,
-	"GRAPHICS_API":               5,
+func (x SplitDimension_Value) Enum() *SplitDimension_Value {
+	p := new(SplitDimension_Value)
+	*p = x
+	return p
 }
 
 func (x SplitDimension_Value) String() string {
-	return proto.EnumName(SplitDimension_Value_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (SplitDimension_Value) Descriptor() protoreflect.EnumDescriptor {
+	return file_config_proto_enumTypes[1].Descriptor()
+}
+
+func (SplitDimension_Value) Type() protoreflect.EnumType {
+	return &file_config_proto_enumTypes[1]
+}
+
+func (x SplitDimension_Value) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use SplitDimension_Value.Descriptor instead.
 func (SplitDimension_Value) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{9, 0}
+	return file_config_proto_rawDescGZIP(), []int{9, 0}
 }
 
 type BundleConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Bundletool    *Bundletool    `protobuf:"bytes,1,opt,name=bundletool,proto3" json:"bundletool,omitempty"`
 	Optimizations *Optimizations `protobuf:"bytes,2,opt,name=optimizations,proto3" json:"optimizations,omitempty"`
 	Compression   *Compression   `protobuf:"bytes,3,opt,name=compression,proto3" json:"compression,omitempty"`
@@ -97,226 +148,258 @@
 	UnsignedEmbeddedApkConfig []*UnsignedEmbeddedApkConfig `protobuf:"bytes,6,rep,name=unsigned_embedded_apk_config,json=unsignedEmbeddedApkConfig,proto3" json:"unsigned_embedded_apk_config,omitempty"`
 	AssetModulesConfig        *AssetModulesConfig          `protobuf:"bytes,7,opt,name=asset_modules_config,json=assetModulesConfig,proto3" json:"asset_modules_config,omitempty"`
 	Type                      BundleConfig_BundleType      `protobuf:"varint,8,opt,name=type,proto3,enum=android.bundle.BundleConfig_BundleType" json:"type,omitempty"`
-	XXX_NoUnkeyedLiteral      struct{}                     `json:"-"`
-	XXX_unrecognized          []byte                       `json:"-"`
-	XXX_sizecache             int32                        `json:"-"`
 }
 
-func (m *BundleConfig) Reset()         { *m = BundleConfig{} }
-func (m *BundleConfig) String() string { return proto.CompactTextString(m) }
-func (*BundleConfig) ProtoMessage()    {}
+func (x *BundleConfig) Reset() {
+	*x = BundleConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BundleConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BundleConfig) ProtoMessage() {}
+
+func (x *BundleConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BundleConfig.ProtoReflect.Descriptor instead.
 func (*BundleConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{0}
+	return file_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *BundleConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BundleConfig.Unmarshal(m, b)
-}
-func (m *BundleConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BundleConfig.Marshal(b, m, deterministic)
-}
-func (m *BundleConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BundleConfig.Merge(m, src)
-}
-func (m *BundleConfig) XXX_Size() int {
-	return xxx_messageInfo_BundleConfig.Size(m)
-}
-func (m *BundleConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_BundleConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BundleConfig proto.InternalMessageInfo
-
-func (m *BundleConfig) GetBundletool() *Bundletool {
-	if m != nil {
-		return m.Bundletool
+func (x *BundleConfig) GetBundletool() *Bundletool {
+	if x != nil {
+		return x.Bundletool
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetOptimizations() *Optimizations {
-	if m != nil {
-		return m.Optimizations
+func (x *BundleConfig) GetOptimizations() *Optimizations {
+	if x != nil {
+		return x.Optimizations
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetCompression() *Compression {
-	if m != nil {
-		return m.Compression
+func (x *BundleConfig) GetCompression() *Compression {
+	if x != nil {
+		return x.Compression
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetMasterResources() *MasterResources {
-	if m != nil {
-		return m.MasterResources
+func (x *BundleConfig) GetMasterResources() *MasterResources {
+	if x != nil {
+		return x.MasterResources
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetApexConfig() *ApexConfig {
-	if m != nil {
-		return m.ApexConfig
+func (x *BundleConfig) GetApexConfig() *ApexConfig {
+	if x != nil {
+		return x.ApexConfig
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetUnsignedEmbeddedApkConfig() []*UnsignedEmbeddedApkConfig {
-	if m != nil {
-		return m.UnsignedEmbeddedApkConfig
+func (x *BundleConfig) GetUnsignedEmbeddedApkConfig() []*UnsignedEmbeddedApkConfig {
+	if x != nil {
+		return x.UnsignedEmbeddedApkConfig
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetAssetModulesConfig() *AssetModulesConfig {
-	if m != nil {
-		return m.AssetModulesConfig
+func (x *BundleConfig) GetAssetModulesConfig() *AssetModulesConfig {
+	if x != nil {
+		return x.AssetModulesConfig
 	}
 	return nil
 }
 
-func (m *BundleConfig) GetType() BundleConfig_BundleType {
-	if m != nil {
-		return m.Type
+func (x *BundleConfig) GetType() BundleConfig_BundleType {
+	if x != nil {
+		return x.Type
 	}
 	return BundleConfig_REGULAR
 }
 
 type Bundletool struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Version of BundleTool used to build the Bundle.
-	Version              string   `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
 }
 
-func (m *Bundletool) Reset()         { *m = Bundletool{} }
-func (m *Bundletool) String() string { return proto.CompactTextString(m) }
-func (*Bundletool) ProtoMessage()    {}
+func (x *Bundletool) Reset() {
+	*x = Bundletool{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Bundletool) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Bundletool) ProtoMessage() {}
+
+func (x *Bundletool) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Bundletool.ProtoReflect.Descriptor instead.
 func (*Bundletool) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{1}
+	return file_config_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *Bundletool) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Bundletool.Unmarshal(m, b)
-}
-func (m *Bundletool) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Bundletool.Marshal(b, m, deterministic)
-}
-func (m *Bundletool) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Bundletool.Merge(m, src)
-}
-func (m *Bundletool) XXX_Size() int {
-	return xxx_messageInfo_Bundletool.Size(m)
-}
-func (m *Bundletool) XXX_DiscardUnknown() {
-	xxx_messageInfo_Bundletool.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Bundletool proto.InternalMessageInfo
-
-func (m *Bundletool) GetVersion() string {
-	if m != nil {
-		return m.Version
+func (x *Bundletool) GetVersion() string {
+	if x != nil {
+		return x.Version
 	}
 	return ""
 }
 
 type Compression struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Glob matching the list of files to leave uncompressed in the APKs.
 	// The matching is done against the path of files in the APK, thus excluding
 	// the name of the modules, and using forward slash ("/") as a name separator.
 	// Examples: "res/raw/**", "assets/**/*.uncompressed", etc.
-	UncompressedGlob     []string `protobuf:"bytes,1,rep,name=uncompressed_glob,json=uncompressedGlob,proto3" json:"uncompressed_glob,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	UncompressedGlob []string `protobuf:"bytes,1,rep,name=uncompressed_glob,json=uncompressedGlob,proto3" json:"uncompressed_glob,omitempty"`
 }
 
-func (m *Compression) Reset()         { *m = Compression{} }
-func (m *Compression) String() string { return proto.CompactTextString(m) }
-func (*Compression) ProtoMessage()    {}
+func (x *Compression) Reset() {
+	*x = Compression{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Compression) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Compression) ProtoMessage() {}
+
+func (x *Compression) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Compression.ProtoReflect.Descriptor instead.
 func (*Compression) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{2}
+	return file_config_proto_rawDescGZIP(), []int{2}
 }
 
-func (m *Compression) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Compression.Unmarshal(m, b)
-}
-func (m *Compression) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Compression.Marshal(b, m, deterministic)
-}
-func (m *Compression) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Compression.Merge(m, src)
-}
-func (m *Compression) XXX_Size() int {
-	return xxx_messageInfo_Compression.Size(m)
-}
-func (m *Compression) XXX_DiscardUnknown() {
-	xxx_messageInfo_Compression.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Compression proto.InternalMessageInfo
-
-func (m *Compression) GetUncompressedGlob() []string {
-	if m != nil {
-		return m.UncompressedGlob
+func (x *Compression) GetUncompressedGlob() []string {
+	if x != nil {
+		return x.UncompressedGlob
 	}
 	return nil
 }
 
 // Resources to keep in the master split.
 type MasterResources struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Resource IDs to be kept in master split.
 	ResourceIds []int32 `protobuf:"varint,1,rep,packed,name=resource_ids,json=resourceIds,proto3" json:"resource_ids,omitempty"`
 	// Resource names to be kept in master split.
-	ResourceNames        []string `protobuf:"bytes,2,rep,name=resource_names,json=resourceNames,proto3" json:"resource_names,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	ResourceNames []string `protobuf:"bytes,2,rep,name=resource_names,json=resourceNames,proto3" json:"resource_names,omitempty"`
 }
 
-func (m *MasterResources) Reset()         { *m = MasterResources{} }
-func (m *MasterResources) String() string { return proto.CompactTextString(m) }
-func (*MasterResources) ProtoMessage()    {}
+func (x *MasterResources) Reset() {
+	*x = MasterResources{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MasterResources) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MasterResources) ProtoMessage() {}
+
+func (x *MasterResources) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[3]
+	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 MasterResources.ProtoReflect.Descriptor instead.
 func (*MasterResources) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{3}
+	return file_config_proto_rawDescGZIP(), []int{3}
 }
 
-func (m *MasterResources) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_MasterResources.Unmarshal(m, b)
-}
-func (m *MasterResources) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_MasterResources.Marshal(b, m, deterministic)
-}
-func (m *MasterResources) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_MasterResources.Merge(m, src)
-}
-func (m *MasterResources) XXX_Size() int {
-	return xxx_messageInfo_MasterResources.Size(m)
-}
-func (m *MasterResources) XXX_DiscardUnknown() {
-	xxx_messageInfo_MasterResources.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_MasterResources proto.InternalMessageInfo
-
-func (m *MasterResources) GetResourceIds() []int32 {
-	if m != nil {
-		return m.ResourceIds
+func (x *MasterResources) GetResourceIds() []int32 {
+	if x != nil {
+		return x.ResourceIds
 	}
 	return nil
 }
 
-func (m *MasterResources) GetResourceNames() []string {
-	if m != nil {
-		return m.ResourceNames
+func (x *MasterResources) GetResourceNames() []string {
+	if x != nil {
+		return x.ResourceNames
 	}
 	return nil
 }
 
 type Optimizations struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	SplitsConfig *SplitsConfig `protobuf:"bytes,1,opt,name=splits_config,json=splitsConfig,proto3" json:"splits_config,omitempty"`
 	// This is for uncompressing native libraries on M+ devices (L+ devices on
 	// instant apps).
@@ -326,293 +409,341 @@
 	// Configuration for the generation of standalone APKs.
 	// If no StandaloneConfig is set, the configuration is inherited from
 	// splits_config.
-	StandaloneConfig     *StandaloneConfig `protobuf:"bytes,4,opt,name=standalone_config,json=standaloneConfig,proto3" json:"standalone_config,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	StandaloneConfig *StandaloneConfig `protobuf:"bytes,4,opt,name=standalone_config,json=standaloneConfig,proto3" json:"standalone_config,omitempty"`
 }
 
-func (m *Optimizations) Reset()         { *m = Optimizations{} }
-func (m *Optimizations) String() string { return proto.CompactTextString(m) }
-func (*Optimizations) ProtoMessage()    {}
+func (x *Optimizations) Reset() {
+	*x = Optimizations{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Optimizations) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Optimizations) ProtoMessage() {}
+
+func (x *Optimizations) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[4]
+	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 Optimizations.ProtoReflect.Descriptor instead.
 func (*Optimizations) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{4}
+	return file_config_proto_rawDescGZIP(), []int{4}
 }
 
-func (m *Optimizations) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Optimizations.Unmarshal(m, b)
-}
-func (m *Optimizations) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Optimizations.Marshal(b, m, deterministic)
-}
-func (m *Optimizations) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Optimizations.Merge(m, src)
-}
-func (m *Optimizations) XXX_Size() int {
-	return xxx_messageInfo_Optimizations.Size(m)
-}
-func (m *Optimizations) XXX_DiscardUnknown() {
-	xxx_messageInfo_Optimizations.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Optimizations proto.InternalMessageInfo
-
-func (m *Optimizations) GetSplitsConfig() *SplitsConfig {
-	if m != nil {
-		return m.SplitsConfig
+func (x *Optimizations) GetSplitsConfig() *SplitsConfig {
+	if x != nil {
+		return x.SplitsConfig
 	}
 	return nil
 }
 
-func (m *Optimizations) GetUncompressNativeLibraries() *UncompressNativeLibraries {
-	if m != nil {
-		return m.UncompressNativeLibraries
+func (x *Optimizations) GetUncompressNativeLibraries() *UncompressNativeLibraries {
+	if x != nil {
+		return x.UncompressNativeLibraries
 	}
 	return nil
 }
 
-func (m *Optimizations) GetUncompressDexFiles() *UncompressDexFiles {
-	if m != nil {
-		return m.UncompressDexFiles
+func (x *Optimizations) GetUncompressDexFiles() *UncompressDexFiles {
+	if x != nil {
+		return x.UncompressDexFiles
 	}
 	return nil
 }
 
-func (m *Optimizations) GetStandaloneConfig() *StandaloneConfig {
-	if m != nil {
-		return m.StandaloneConfig
+func (x *Optimizations) GetStandaloneConfig() *StandaloneConfig {
+	if x != nil {
+		return x.StandaloneConfig
 	}
 	return nil
 }
 
 type UncompressNativeLibraries struct {
-	Enabled              bool     `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
 }
 
-func (m *UncompressNativeLibraries) Reset()         { *m = UncompressNativeLibraries{} }
-func (m *UncompressNativeLibraries) String() string { return proto.CompactTextString(m) }
-func (*UncompressNativeLibraries) ProtoMessage()    {}
+func (x *UncompressNativeLibraries) Reset() {
+	*x = UncompressNativeLibraries{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UncompressNativeLibraries) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UncompressNativeLibraries) ProtoMessage() {}
+
+func (x *UncompressNativeLibraries) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[5]
+	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 UncompressNativeLibraries.ProtoReflect.Descriptor instead.
 func (*UncompressNativeLibraries) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{5}
+	return file_config_proto_rawDescGZIP(), []int{5}
 }
 
-func (m *UncompressNativeLibraries) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_UncompressNativeLibraries.Unmarshal(m, b)
-}
-func (m *UncompressNativeLibraries) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_UncompressNativeLibraries.Marshal(b, m, deterministic)
-}
-func (m *UncompressNativeLibraries) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_UncompressNativeLibraries.Merge(m, src)
-}
-func (m *UncompressNativeLibraries) XXX_Size() int {
-	return xxx_messageInfo_UncompressNativeLibraries.Size(m)
-}
-func (m *UncompressNativeLibraries) XXX_DiscardUnknown() {
-	xxx_messageInfo_UncompressNativeLibraries.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_UncompressNativeLibraries proto.InternalMessageInfo
-
-func (m *UncompressNativeLibraries) GetEnabled() bool {
-	if m != nil {
-		return m.Enabled
+func (x *UncompressNativeLibraries) GetEnabled() bool {
+	if x != nil {
+		return x.Enabled
 	}
 	return false
 }
 
 type UncompressDexFiles struct {
-	Enabled              bool     `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
 }
 
-func (m *UncompressDexFiles) Reset()         { *m = UncompressDexFiles{} }
-func (m *UncompressDexFiles) String() string { return proto.CompactTextString(m) }
-func (*UncompressDexFiles) ProtoMessage()    {}
+func (x *UncompressDexFiles) Reset() {
+	*x = UncompressDexFiles{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UncompressDexFiles) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UncompressDexFiles) ProtoMessage() {}
+
+func (x *UncompressDexFiles) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[6]
+	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 UncompressDexFiles.ProtoReflect.Descriptor instead.
 func (*UncompressDexFiles) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{6}
+	return file_config_proto_rawDescGZIP(), []int{6}
 }
 
-func (m *UncompressDexFiles) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_UncompressDexFiles.Unmarshal(m, b)
-}
-func (m *UncompressDexFiles) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_UncompressDexFiles.Marshal(b, m, deterministic)
-}
-func (m *UncompressDexFiles) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_UncompressDexFiles.Merge(m, src)
-}
-func (m *UncompressDexFiles) XXX_Size() int {
-	return xxx_messageInfo_UncompressDexFiles.Size(m)
-}
-func (m *UncompressDexFiles) XXX_DiscardUnknown() {
-	xxx_messageInfo_UncompressDexFiles.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_UncompressDexFiles proto.InternalMessageInfo
-
-func (m *UncompressDexFiles) GetEnabled() bool {
-	if m != nil {
-		return m.Enabled
+func (x *UncompressDexFiles) GetEnabled() bool {
+	if x != nil {
+		return x.Enabled
 	}
 	return false
 }
 
 // Optimization configuration used to generate Split APKs.
 type SplitsConfig struct {
-	SplitDimension       []*SplitDimension `protobuf:"bytes,1,rep,name=split_dimension,json=splitDimension,proto3" json:"split_dimension,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
-	XXX_unrecognized     []byte            `json:"-"`
-	XXX_sizecache        int32             `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	SplitDimension []*SplitDimension `protobuf:"bytes,1,rep,name=split_dimension,json=splitDimension,proto3" json:"split_dimension,omitempty"`
 }
 
-func (m *SplitsConfig) Reset()         { *m = SplitsConfig{} }
-func (m *SplitsConfig) String() string { return proto.CompactTextString(m) }
-func (*SplitsConfig) ProtoMessage()    {}
+func (x *SplitsConfig) Reset() {
+	*x = SplitsConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SplitsConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SplitsConfig) ProtoMessage() {}
+
+func (x *SplitsConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[7]
+	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 SplitsConfig.ProtoReflect.Descriptor instead.
 func (*SplitsConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{7}
+	return file_config_proto_rawDescGZIP(), []int{7}
 }
 
-func (m *SplitsConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SplitsConfig.Unmarshal(m, b)
-}
-func (m *SplitsConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SplitsConfig.Marshal(b, m, deterministic)
-}
-func (m *SplitsConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SplitsConfig.Merge(m, src)
-}
-func (m *SplitsConfig) XXX_Size() int {
-	return xxx_messageInfo_SplitsConfig.Size(m)
-}
-func (m *SplitsConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_SplitsConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SplitsConfig proto.InternalMessageInfo
-
-func (m *SplitsConfig) GetSplitDimension() []*SplitDimension {
-	if m != nil {
-		return m.SplitDimension
+func (x *SplitsConfig) GetSplitDimension() []*SplitDimension {
+	if x != nil {
+		return x.SplitDimension
 	}
 	return nil
 }
 
 // Optimization configuration used to generate Standalone APKs.
 type StandaloneConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Device targeting dimensions to shard.
 	SplitDimension []*SplitDimension `protobuf:"bytes,1,rep,name=split_dimension,json=splitDimension,proto3" json:"split_dimension,omitempty"`
 	// Whether 64 bit libraries should be stripped from Standalone APKs.
-	Strip_64BitLibraries bool     `protobuf:"varint,2,opt,name=strip_64_bit_libraries,json=strip64BitLibraries,proto3" json:"strip_64_bit_libraries,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Strip_64BitLibraries bool `protobuf:"varint,2,opt,name=strip_64_bit_libraries,json=strip64BitLibraries,proto3" json:"strip_64_bit_libraries,omitempty"`
 }
 
-func (m *StandaloneConfig) Reset()         { *m = StandaloneConfig{} }
-func (m *StandaloneConfig) String() string { return proto.CompactTextString(m) }
-func (*StandaloneConfig) ProtoMessage()    {}
+func (x *StandaloneConfig) Reset() {
+	*x = StandaloneConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StandaloneConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StandaloneConfig) ProtoMessage() {}
+
+func (x *StandaloneConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[8]
+	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 StandaloneConfig.ProtoReflect.Descriptor instead.
 func (*StandaloneConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{8}
+	return file_config_proto_rawDescGZIP(), []int{8}
 }
 
-func (m *StandaloneConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_StandaloneConfig.Unmarshal(m, b)
-}
-func (m *StandaloneConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_StandaloneConfig.Marshal(b, m, deterministic)
-}
-func (m *StandaloneConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_StandaloneConfig.Merge(m, src)
-}
-func (m *StandaloneConfig) XXX_Size() int {
-	return xxx_messageInfo_StandaloneConfig.Size(m)
-}
-func (m *StandaloneConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_StandaloneConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_StandaloneConfig proto.InternalMessageInfo
-
-func (m *StandaloneConfig) GetSplitDimension() []*SplitDimension {
-	if m != nil {
-		return m.SplitDimension
+func (x *StandaloneConfig) GetSplitDimension() []*SplitDimension {
+	if x != nil {
+		return x.SplitDimension
 	}
 	return nil
 }
 
-func (m *StandaloneConfig) GetStrip_64BitLibraries() bool {
-	if m != nil {
-		return m.Strip_64BitLibraries
+func (x *StandaloneConfig) GetStrip_64BitLibraries() bool {
+	if x != nil {
+		return x.Strip_64BitLibraries
 	}
 	return false
 }
 
 type SplitDimension struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value SplitDimension_Value `protobuf:"varint,1,opt,name=value,proto3,enum=android.bundle.SplitDimension_Value" json:"value,omitempty"`
 	// If set to 'true', indicates that APKs should *not* be split by this
 	// dimension.
 	Negate bool `protobuf:"varint,2,opt,name=negate,proto3" json:"negate,omitempty"`
 	// Optional transformation to be applied to asset directories where
 	// the targeting is encoded in the directory name (e.g: assets/foo#tcf_etc1)
-	SuffixStripping      *SuffixStripping `protobuf:"bytes,3,opt,name=suffix_stripping,json=suffixStripping,proto3" json:"suffix_stripping,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
-	XXX_unrecognized     []byte           `json:"-"`
-	XXX_sizecache        int32            `json:"-"`
+	SuffixStripping *SuffixStripping `protobuf:"bytes,3,opt,name=suffix_stripping,json=suffixStripping,proto3" json:"suffix_stripping,omitempty"`
 }
 
-func (m *SplitDimension) Reset()         { *m = SplitDimension{} }
-func (m *SplitDimension) String() string { return proto.CompactTextString(m) }
-func (*SplitDimension) ProtoMessage()    {}
+func (x *SplitDimension) Reset() {
+	*x = SplitDimension{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SplitDimension) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SplitDimension) ProtoMessage() {}
+
+func (x *SplitDimension) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[9]
+	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 SplitDimension.ProtoReflect.Descriptor instead.
 func (*SplitDimension) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{9}
+	return file_config_proto_rawDescGZIP(), []int{9}
 }
 
-func (m *SplitDimension) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SplitDimension.Unmarshal(m, b)
-}
-func (m *SplitDimension) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SplitDimension.Marshal(b, m, deterministic)
-}
-func (m *SplitDimension) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SplitDimension.Merge(m, src)
-}
-func (m *SplitDimension) XXX_Size() int {
-	return xxx_messageInfo_SplitDimension.Size(m)
-}
-func (m *SplitDimension) XXX_DiscardUnknown() {
-	xxx_messageInfo_SplitDimension.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SplitDimension proto.InternalMessageInfo
-
-func (m *SplitDimension) GetValue() SplitDimension_Value {
-	if m != nil {
-		return m.Value
+func (x *SplitDimension) GetValue() SplitDimension_Value {
+	if x != nil {
+		return x.Value
 	}
 	return SplitDimension_UNSPECIFIED_VALUE
 }
 
-func (m *SplitDimension) GetNegate() bool {
-	if m != nil {
-		return m.Negate
+func (x *SplitDimension) GetNegate() bool {
+	if x != nil {
+		return x.Negate
 	}
 	return false
 }
 
-func (m *SplitDimension) GetSuffixStripping() *SuffixStripping {
-	if m != nil {
-		return m.SuffixStripping
+func (x *SplitDimension) GetSuffixStripping() *SuffixStripping {
+	if x != nil {
+		return x.SuffixStripping
 	}
 	return nil
 }
 
 type SuffixStripping struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// If set to 'true', indicates that the targeting suffix should be removed
 	// from assets paths for this dimension when splits (or asset slices) are
 	// generated.
@@ -632,47 +763,51 @@
 	// used (for example, if both "assets/level1_textures#tcf_etc1" and
 	// "assets/level1_textures" are present and the default suffix is empty,
 	// then only "assets/level1_textures" will be used).
-	DefaultSuffix        string   `protobuf:"bytes,2,opt,name=default_suffix,json=defaultSuffix,proto3" json:"default_suffix,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	DefaultSuffix string `protobuf:"bytes,2,opt,name=default_suffix,json=defaultSuffix,proto3" json:"default_suffix,omitempty"`
 }
 
-func (m *SuffixStripping) Reset()         { *m = SuffixStripping{} }
-func (m *SuffixStripping) String() string { return proto.CompactTextString(m) }
-func (*SuffixStripping) ProtoMessage()    {}
+func (x *SuffixStripping) Reset() {
+	*x = SuffixStripping{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SuffixStripping) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SuffixStripping) ProtoMessage() {}
+
+func (x *SuffixStripping) ProtoReflect() protoreflect.Message {
+	mi := &file_config_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 SuffixStripping.ProtoReflect.Descriptor instead.
 func (*SuffixStripping) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{10}
+	return file_config_proto_rawDescGZIP(), []int{10}
 }
 
-func (m *SuffixStripping) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SuffixStripping.Unmarshal(m, b)
-}
-func (m *SuffixStripping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SuffixStripping.Marshal(b, m, deterministic)
-}
-func (m *SuffixStripping) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SuffixStripping.Merge(m, src)
-}
-func (m *SuffixStripping) XXX_Size() int {
-	return xxx_messageInfo_SuffixStripping.Size(m)
-}
-func (m *SuffixStripping) XXX_DiscardUnknown() {
-	xxx_messageInfo_SuffixStripping.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SuffixStripping proto.InternalMessageInfo
-
-func (m *SuffixStripping) GetEnabled() bool {
-	if m != nil {
-		return m.Enabled
+func (x *SuffixStripping) GetEnabled() bool {
+	if x != nil {
+		return x.Enabled
 	}
 	return false
 }
 
-func (m *SuffixStripping) GetDefaultSuffix() string {
-	if m != nil {
-		return m.DefaultSuffix
+func (x *SuffixStripping) GetDefaultSuffix() string {
+	if x != nil {
+		return x.DefaultSuffix
 	}
 	return ""
 }
@@ -680,273 +815,634 @@
 // Configuration for processing APEX bundles.
 // https://source.android.com/devices/tech/ota/apex
 type ApexConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Configuration for processing of APKs embedded in an APEX image.
 	ApexEmbeddedApkConfig []*ApexEmbeddedApkConfig `protobuf:"bytes,1,rep,name=apex_embedded_apk_config,json=apexEmbeddedApkConfig,proto3" json:"apex_embedded_apk_config,omitempty"`
-	XXX_NoUnkeyedLiteral  struct{}                 `json:"-"`
-	XXX_unrecognized      []byte                   `json:"-"`
-	XXX_sizecache         int32                    `json:"-"`
 }
 
-func (m *ApexConfig) Reset()         { *m = ApexConfig{} }
-func (m *ApexConfig) String() string { return proto.CompactTextString(m) }
-func (*ApexConfig) ProtoMessage()    {}
+func (x *ApexConfig) Reset() {
+	*x = ApexConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApexConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApexConfig) ProtoMessage() {}
+
+func (x *ApexConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[11]
+	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 ApexConfig.ProtoReflect.Descriptor instead.
 func (*ApexConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{11}
+	return file_config_proto_rawDescGZIP(), []int{11}
 }
 
-func (m *ApexConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApexConfig.Unmarshal(m, b)
-}
-func (m *ApexConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApexConfig.Marshal(b, m, deterministic)
-}
-func (m *ApexConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApexConfig.Merge(m, src)
-}
-func (m *ApexConfig) XXX_Size() int {
-	return xxx_messageInfo_ApexConfig.Size(m)
-}
-func (m *ApexConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApexConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApexConfig proto.InternalMessageInfo
-
-func (m *ApexConfig) GetApexEmbeddedApkConfig() []*ApexEmbeddedApkConfig {
-	if m != nil {
-		return m.ApexEmbeddedApkConfig
+func (x *ApexConfig) GetApexEmbeddedApkConfig() []*ApexEmbeddedApkConfig {
+	if x != nil {
+		return x.ApexEmbeddedApkConfig
 	}
 	return nil
 }
 
 type ApexEmbeddedApkConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Android package name of the APK.
 	PackageName string `protobuf:"bytes,1,opt,name=package_name,json=packageName,proto3" json:"package_name,omitempty"`
 	// Path to the APK within the APEX system image.
-	Path                 string   `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
 }
 
-func (m *ApexEmbeddedApkConfig) Reset()         { *m = ApexEmbeddedApkConfig{} }
-func (m *ApexEmbeddedApkConfig) String() string { return proto.CompactTextString(m) }
-func (*ApexEmbeddedApkConfig) ProtoMessage()    {}
+func (x *ApexEmbeddedApkConfig) Reset() {
+	*x = ApexEmbeddedApkConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApexEmbeddedApkConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApexEmbeddedApkConfig) ProtoMessage() {}
+
+func (x *ApexEmbeddedApkConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[12]
+	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 ApexEmbeddedApkConfig.ProtoReflect.Descriptor instead.
 func (*ApexEmbeddedApkConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{12}
+	return file_config_proto_rawDescGZIP(), []int{12}
 }
 
-func (m *ApexEmbeddedApkConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApexEmbeddedApkConfig.Unmarshal(m, b)
-}
-func (m *ApexEmbeddedApkConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApexEmbeddedApkConfig.Marshal(b, m, deterministic)
-}
-func (m *ApexEmbeddedApkConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApexEmbeddedApkConfig.Merge(m, src)
-}
-func (m *ApexEmbeddedApkConfig) XXX_Size() int {
-	return xxx_messageInfo_ApexEmbeddedApkConfig.Size(m)
-}
-func (m *ApexEmbeddedApkConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApexEmbeddedApkConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApexEmbeddedApkConfig proto.InternalMessageInfo
-
-func (m *ApexEmbeddedApkConfig) GetPackageName() string {
-	if m != nil {
-		return m.PackageName
+func (x *ApexEmbeddedApkConfig) GetPackageName() string {
+	if x != nil {
+		return x.PackageName
 	}
 	return ""
 }
 
-func (m *ApexEmbeddedApkConfig) GetPath() string {
-	if m != nil {
-		return m.Path
+func (x *ApexEmbeddedApkConfig) GetPath() string {
+	if x != nil {
+		return x.Path
 	}
 	return ""
 }
 
 type UnsignedEmbeddedApkConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Path to the APK inside the module (e.g. if the path inside the bundle
 	// is split/assets/example.apk, this will be assets/example.apk).
-	Path                 string   `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
 }
 
-func (m *UnsignedEmbeddedApkConfig) Reset()         { *m = UnsignedEmbeddedApkConfig{} }
-func (m *UnsignedEmbeddedApkConfig) String() string { return proto.CompactTextString(m) }
-func (*UnsignedEmbeddedApkConfig) ProtoMessage()    {}
+func (x *UnsignedEmbeddedApkConfig) Reset() {
+	*x = UnsignedEmbeddedApkConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[13]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UnsignedEmbeddedApkConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnsignedEmbeddedApkConfig) ProtoMessage() {}
+
+func (x *UnsignedEmbeddedApkConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[13]
+	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 UnsignedEmbeddedApkConfig.ProtoReflect.Descriptor instead.
 func (*UnsignedEmbeddedApkConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{13}
+	return file_config_proto_rawDescGZIP(), []int{13}
 }
 
-func (m *UnsignedEmbeddedApkConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_UnsignedEmbeddedApkConfig.Unmarshal(m, b)
-}
-func (m *UnsignedEmbeddedApkConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_UnsignedEmbeddedApkConfig.Marshal(b, m, deterministic)
-}
-func (m *UnsignedEmbeddedApkConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_UnsignedEmbeddedApkConfig.Merge(m, src)
-}
-func (m *UnsignedEmbeddedApkConfig) XXX_Size() int {
-	return xxx_messageInfo_UnsignedEmbeddedApkConfig.Size(m)
-}
-func (m *UnsignedEmbeddedApkConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_UnsignedEmbeddedApkConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_UnsignedEmbeddedApkConfig proto.InternalMessageInfo
-
-func (m *UnsignedEmbeddedApkConfig) GetPath() string {
-	if m != nil {
-		return m.Path
+func (x *UnsignedEmbeddedApkConfig) GetPath() string {
+	if x != nil {
+		return x.Path
 	}
 	return ""
 }
 
 type AssetModulesConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// App versionCodes that will be updated with these asset modules.
 	// Only relevant for asset-only bundles.
 	AppVersion []int64 `protobuf:"varint,1,rep,packed,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
 	// Version tag for the asset upload.
 	// Only relevant for asset-only bundles.
-	AssetVersionTag      string   `protobuf:"bytes,2,opt,name=asset_version_tag,json=assetVersionTag,proto3" json:"asset_version_tag,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	AssetVersionTag string `protobuf:"bytes,2,opt,name=asset_version_tag,json=assetVersionTag,proto3" json:"asset_version_tag,omitempty"`
 }
 
-func (m *AssetModulesConfig) Reset()         { *m = AssetModulesConfig{} }
-func (m *AssetModulesConfig) String() string { return proto.CompactTextString(m) }
-func (*AssetModulesConfig) ProtoMessage()    {}
+func (x *AssetModulesConfig) Reset() {
+	*x = AssetModulesConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_config_proto_msgTypes[14]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AssetModulesConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AssetModulesConfig) ProtoMessage() {}
+
+func (x *AssetModulesConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_config_proto_msgTypes[14]
+	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 AssetModulesConfig.ProtoReflect.Descriptor instead.
 func (*AssetModulesConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_3eaf2c85e69e9ea4, []int{14}
+	return file_config_proto_rawDescGZIP(), []int{14}
 }
 
-func (m *AssetModulesConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_AssetModulesConfig.Unmarshal(m, b)
-}
-func (m *AssetModulesConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_AssetModulesConfig.Marshal(b, m, deterministic)
-}
-func (m *AssetModulesConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_AssetModulesConfig.Merge(m, src)
-}
-func (m *AssetModulesConfig) XXX_Size() int {
-	return xxx_messageInfo_AssetModulesConfig.Size(m)
-}
-func (m *AssetModulesConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_AssetModulesConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AssetModulesConfig proto.InternalMessageInfo
-
-func (m *AssetModulesConfig) GetAppVersion() []int64 {
-	if m != nil {
-		return m.AppVersion
+func (x *AssetModulesConfig) GetAppVersion() []int64 {
+	if x != nil {
+		return x.AppVersion
 	}
 	return nil
 }
 
-func (m *AssetModulesConfig) GetAssetVersionTag() string {
-	if m != nil {
-		return m.AssetVersionTag
+func (x *AssetModulesConfig) GetAssetVersionTag() string {
+	if x != nil {
+		return x.AssetVersionTag
 	}
 	return ""
 }
 
-func init() {
-	proto.RegisterEnum("android.bundle.BundleConfig_BundleType", BundleConfig_BundleType_name, BundleConfig_BundleType_value)
-	proto.RegisterEnum("android.bundle.SplitDimension_Value", SplitDimension_Value_name, SplitDimension_Value_value)
-	proto.RegisterType((*BundleConfig)(nil), "android.bundle.BundleConfig")
-	proto.RegisterType((*Bundletool)(nil), "android.bundle.Bundletool")
-	proto.RegisterType((*Compression)(nil), "android.bundle.Compression")
-	proto.RegisterType((*MasterResources)(nil), "android.bundle.MasterResources")
-	proto.RegisterType((*Optimizations)(nil), "android.bundle.Optimizations")
-	proto.RegisterType((*UncompressNativeLibraries)(nil), "android.bundle.UncompressNativeLibraries")
-	proto.RegisterType((*UncompressDexFiles)(nil), "android.bundle.UncompressDexFiles")
-	proto.RegisterType((*SplitsConfig)(nil), "android.bundle.SplitsConfig")
-	proto.RegisterType((*StandaloneConfig)(nil), "android.bundle.StandaloneConfig")
-	proto.RegisterType((*SplitDimension)(nil), "android.bundle.SplitDimension")
-	proto.RegisterType((*SuffixStripping)(nil), "android.bundle.SuffixStripping")
-	proto.RegisterType((*ApexConfig)(nil), "android.bundle.ApexConfig")
-	proto.RegisterType((*ApexEmbeddedApkConfig)(nil), "android.bundle.ApexEmbeddedApkConfig")
-	proto.RegisterType((*UnsignedEmbeddedApkConfig)(nil), "android.bundle.UnsignedEmbeddedApkConfig")
-	proto.RegisterType((*AssetModulesConfig)(nil), "android.bundle.AssetModulesConfig")
+var File_config_proto protoreflect.FileDescriptor
+
+var file_config_proto_rawDesc = []byte{
+	0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x8b,
+	0x05, 0x0a, 0x0c, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+	0x3a, 0x0a, 0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75,
+	0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x52,
+	0x0a, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x43, 0x0a, 0x0d, 0x6f,
+	0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x52, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x12, 0x3d, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12,
+	0x4a, 0x0a, 0x10, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d, 0x61, 0x73, 0x74, 0x65,
+	0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x0f, 0x6d, 0x61, 0x73, 0x74,
+	0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x61,
+	0x70, 0x65, 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x41, 0x70, 0x65, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0a, 0x61, 0x70,
+	0x65, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x6a, 0x0a, 0x1c, 0x75, 0x6e, 0x73, 0x69,
+	0x67, 0x6e, 0x65, 0x64, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x70,
+	0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29,
+	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e,
+	0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64,
+	0x41, 0x70, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x19, 0x75, 0x6e, 0x73, 0x69, 0x67,
+	0x6e, 0x65, 0x64, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x41, 0x70, 0x6b, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x54, 0x0a, 0x14, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x22, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x12, 0x61, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x54, 0x79, 0x70,
+	0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x33, 0x0a, 0x0a, 0x42, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x45, 0x47, 0x55, 0x4c, 0x41, 0x52,
+	0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x50, 0x45, 0x58, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a,
+	0x41, 0x53, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x02, 0x22, 0x2c, 0x0a, 0x0a,
+	0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x3a, 0x0a, 0x0b, 0x43, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x6e, 0x63,
+	0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73,
+	0x65, 0x64, 0x47, 0x6c, 0x6f, 0x62, 0x22, 0x5b, 0x0a, 0x0f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72,
+	0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x52,
+	0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e,
+	0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61,
+	0x6d, 0x65, 0x73, 0x22, 0xe2, 0x02, 0x0a, 0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x73, 0x5f,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x70,
+	0x6c, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0c, 0x73, 0x70, 0x6c, 0x69,
+	0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 0x0a, 0x1b, 0x75, 0x6e, 0x63, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6c, 0x69,
+	0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x55,
+	0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x4c,
+	0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x19, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70,
+	0x72, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72,
+	0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x14, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73,
+	0x73, 0x5f, 0x64, 0x65, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x22, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64,
+	0x6c, 0x65, 0x2e, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x44, 0x65, 0x78,
+	0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73,
+	0x73, 0x44, 0x65, 0x78, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x4d, 0x0a, 0x11, 0x73, 0x74, 0x61,
+	0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f,
+	0x6e, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x35, 0x0a, 0x19, 0x55, 0x6e, 0x63, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x4c, 0x69, 0x62, 0x72,
+	0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22,
+	0x2e, 0x0a, 0x12, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x44, 0x65, 0x78,
+	0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22,
+	0x57, 0x0a, 0x0c, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+	0x47, 0x0a, 0x0f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x44,
+	0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x44,
+	0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x90, 0x01, 0x0a, 0x10, 0x53, 0x74, 0x61,
+	0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x47, 0x0a,
+	0x0f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x5f, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x44, 0x69, 0x6d,
+	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x44, 0x69, 0x6d,
+	0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x16, 0x73, 0x74, 0x72, 0x69, 0x70, 0x5f,
+	0x36, 0x34, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x73, 0x74, 0x72, 0x69, 0x70, 0x36, 0x34, 0x42,
+	0x69, 0x74, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0xad, 0x02, 0x0a, 0x0e,
+	0x53, 0x70, 0x6c, 0x69, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3a,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53,
+	0x70, 0x6c, 0x69, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x65,
+	0x67, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6e, 0x65, 0x67, 0x61,
+	0x74, 0x65, 0x12, 0x4a, 0x0a, 0x10, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x5f, 0x73, 0x74, 0x72,
+	0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x75,
+	0x66, 0x66, 0x69, 0x78, 0x53, 0x74, 0x72, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0f, 0x73,
+	0x75, 0x66, 0x66, 0x69, 0x78, 0x53, 0x74, 0x72, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x22, 0x7b,
+	0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45,
+	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x56, 0x41, 0x4c, 0x55, 0x45, 0x10, 0x00, 0x12, 0x07,
+	0x0a, 0x03, 0x41, 0x42, 0x49, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x43, 0x52, 0x45, 0x45,
+	0x4e, 0x5f, 0x44, 0x45, 0x4e, 0x53, 0x49, 0x54, 0x59, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c,
+	0x41, 0x4e, 0x47, 0x55, 0x41, 0x47, 0x45, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x58,
+	0x54, 0x55, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e,
+	0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x10, 0x04, 0x12, 0x10, 0x0a, 0x0c, 0x47, 0x52, 0x41,
+	0x50, 0x48, 0x49, 0x43, 0x53, 0x5f, 0x41, 0x50, 0x49, 0x10, 0x05, 0x22, 0x52, 0x0a, 0x0f, 0x53,
+	0x75, 0x66, 0x66, 0x69, 0x78, 0x53, 0x74, 0x72, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x18,
+	0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61,
+	0x75, 0x6c, 0x74, 0x5f, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x22,
+	0x6c, 0x0a, 0x0a, 0x41, 0x70, 0x65, 0x78, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5e, 0x0a,
+	0x18, 0x61, 0x70, 0x65, 0x78, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x61,
+	0x70, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x25, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x41, 0x70, 0x65, 0x78, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x41, 0x70, 0x6b,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x15, 0x61, 0x70, 0x65, 0x78, 0x45, 0x6d, 0x62, 0x65,
+	0x64, 0x64, 0x65, 0x64, 0x41, 0x70, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x4e, 0x0a,
+	0x15, 0x41, 0x70, 0x65, 0x78, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x41, 0x70, 0x6b,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,
+	0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61,
+	0x63, 0x6b, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
+	0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x2f, 0x0a,
+	0x19, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65,
+	0x64, 0x41, 0x70, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61,
+	0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x61,
+	0x0a, 0x12, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x76,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61,
+	0x67, 0x42, 0x41, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5a, 0x2b, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x65, 0x78, 0x74, 0x72, 0x61,
+	0x63, 0x74, 0x5f, 0x61, 0x70, 0x6b, 0x73, 0x2f, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
-func init() {
-	proto.RegisterFile("config.proto", fileDescriptor_3eaf2c85e69e9ea4)
+var (
+	file_config_proto_rawDescOnce sync.Once
+	file_config_proto_rawDescData = file_config_proto_rawDesc
+)
+
+func file_config_proto_rawDescGZIP() []byte {
+	file_config_proto_rawDescOnce.Do(func() {
+		file_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)
+	})
+	return file_config_proto_rawDescData
 }
 
-var fileDescriptor_3eaf2c85e69e9ea4 = []byte{
-	// 1001 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdb, 0x6e, 0xdb, 0x46,
-	0x10, 0x0d, 0x75, 0xb1, 0xe5, 0x91, 0x2c, 0xd1, 0xdb, 0x38, 0x50, 0x2e, 0x4d, 0x5c, 0xa2, 0x41,
-	0xdd, 0xb4, 0x50, 0x01, 0x3b, 0xcd, 0x83, 0x83, 0x3e, 0xd0, 0x32, 0xad, 0x2a, 0xd0, 0x0d, 0x4b,
-	0xc9, 0x4d, 0x5a, 0xa0, 0x8b, 0x95, 0xb8, 0x52, 0xb7, 0xa6, 0x48, 0x82, 0x4b, 0x1a, 0x4a, 0xfb,
-	0x09, 0x7d, 0xe9, 0x8f, 0xf4, 0xa7, 0xfa, 0x25, 0x05, 0x97, 0xa4, 0x2c, 0x51, 0x52, 0x9e, 0xfa,
-	0x24, 0xce, 0xec, 0x39, 0xb3, 0x3b, 0xb3, 0x67, 0x67, 0x04, 0x95, 0x89, 0xeb, 0x4c, 0xf9, 0xac,
-	0xe1, 0xf9, 0x6e, 0xe0, 0xa2, 0x2a, 0x75, 0x2c, 0xdf, 0xe5, 0x56, 0x63, 0x1c, 0x3a, 0x96, 0xcd,
-	0xb4, 0xbf, 0x8a, 0x50, 0xb9, 0x94, 0x9f, 0x4d, 0x09, 0x43, 0x17, 0x00, 0xf1, 0x52, 0xe0, 0xba,
-	0x76, 0x5d, 0x39, 0x51, 0x4e, 0xcb, 0x67, 0x4f, 0x1a, 0xeb, 0xac, 0xc6, 0xe5, 0x12, 0x81, 0x57,
-	0xd0, 0xa8, 0x09, 0x87, 0xae, 0x17, 0xf0, 0x39, 0xff, 0x83, 0x06, 0xdc, 0x75, 0x44, 0x3d, 0x27,
-	0xe9, 0x9f, 0x67, 0xe9, 0xfd, 0x55, 0x10, 0x5e, 0xe7, 0xa0, 0x1f, 0xa0, 0x3c, 0x71, 0xe7, 0x9e,
-	0xcf, 0x84, 0xe0, 0xae, 0x53, 0xcf, 0xcb, 0x10, 0x4f, 0xb3, 0x21, 0x9a, 0xf7, 0x10, 0xbc, 0x8a,
-	0x47, 0xef, 0x40, 0x9d, 0x53, 0x11, 0x30, 0x9f, 0xf8, 0x4c, 0xb8, 0xa1, 0x3f, 0x61, 0xa2, 0x5e,
-	0x90, 0x31, 0x5e, 0x64, 0x63, 0x74, 0x25, 0x0e, 0xa7, 0x30, 0x5c, 0x9b, 0xaf, 0x3b, 0xd0, 0x5b,
-	0x28, 0x53, 0x8f, 0x2d, 0x48, 0x5c, 0xc1, 0x7a, 0x71, 0x7b, 0x31, 0x74, 0x8f, 0x2d, 0xe2, 0xe2,
-	0x61, 0xa0, 0xcb, 0x6f, 0xf4, 0x3b, 0x3c, 0x0b, 0x1d, 0xc1, 0x67, 0x0e, 0xb3, 0x08, 0x9b, 0x8f,
-	0x99, 0x65, 0x31, 0x8b, 0x50, 0xef, 0x36, 0x8d, 0xb6, 0x77, 0x92, 0x3f, 0x2d, 0x9f, 0x7d, 0x9d,
-	0x8d, 0x36, 0x4a, 0x38, 0x46, 0x42, 0xd1, 0xbd, 0xdb, 0x24, 0xf8, 0xe3, 0x70, 0xd7, 0x12, 0x1a,
-	0xc2, 0x43, 0x2a, 0x04, 0x0b, 0xc8, 0xdc, 0xb5, 0x42, 0x9b, 0x89, 0x74, 0x8f, 0x7d, 0x79, 0x62,
-	0x6d, 0xe3, 0xc4, 0x11, 0xb6, 0x1b, 0x43, 0x93, 0xe0, 0x88, 0x6e, 0xf8, 0xd0, 0x5b, 0x28, 0x04,
-	0x1f, 0x3d, 0x56, 0x2f, 0x9d, 0x28, 0xa7, 0xd5, 0xb3, 0xaf, 0xb6, 0x8b, 0x20, 0xc6, 0x26, 0xc6,
-	0xf0, 0xa3, 0xc7, 0xb0, 0x24, 0x69, 0xe7, 0x00, 0xf7, 0x3e, 0x54, 0x86, 0x7d, 0x6c, 0xb4, 0x46,
-	0x1d, 0x1d, 0xab, 0x0f, 0x50, 0x09, 0x0a, 0xfa, 0xc0, 0x78, 0xaf, 0x2a, 0xa8, 0x0a, 0xa0, 0x9b,
-	0xa6, 0x31, 0x24, 0xfd, 0x5e, 0xe7, 0x83, 0x9a, 0xd3, 0xbe, 0x4d, 0x49, 0x52, 0x4e, 0x75, 0xd8,
-	0xbf, 0x63, 0xbe, 0x54, 0x41, 0x24, 0xa4, 0x03, 0x9c, 0x9a, 0xef, 0x0a, 0x25, 0x45, 0xcd, 0x69,
-	0x17, 0x50, 0x5e, 0x91, 0x01, 0xfa, 0x06, 0x8e, 0x42, 0x27, 0x95, 0x02, 0xb3, 0xc8, 0xcc, 0x76,
-	0xc7, 0x75, 0xe5, 0x24, 0x7f, 0x7a, 0x80, 0xd5, 0xd5, 0x85, 0x96, 0xed, 0x8e, 0xb5, 0x5f, 0xa0,
-	0x96, 0xb9, 0x7e, 0xf4, 0x05, 0x54, 0x52, 0xc9, 0x10, 0x6e, 0x09, 0x49, 0x2d, 0xe2, 0x72, 0xea,
-	0x6b, 0x5b, 0x02, 0xbd, 0x84, 0xea, 0x12, 0xe2, 0xd0, 0x39, 0x8b, 0x14, 0x1e, 0xc5, 0x3f, 0x4c,
-	0xbd, 0xbd, 0xc8, 0xa9, 0xfd, 0x9b, 0x83, 0xc3, 0x35, 0x8d, 0x23, 0x1d, 0x0e, 0x85, 0x67, 0xf3,
-	0x60, 0x79, 0x33, 0xf1, 0xc3, 0x7a, 0x96, 0xad, 0xa9, 0x29, 0x41, 0xc9, 0x9d, 0x54, 0xc4, 0x8a,
-	0x85, 0x38, 0x3c, 0xbd, 0xcf, 0x82, 0x38, 0x34, 0xe0, 0x77, 0x8c, 0xd8, 0x7c, 0xec, 0x53, 0x9f,
-	0xb3, 0xf4, 0xa9, 0x6d, 0x91, 0x53, 0x4a, 0xe9, 0x49, 0x46, 0x27, 0x25, 0x44, 0x72, 0xda, 0xb1,
-	0x14, 0xc9, 0x69, 0x65, 0x2b, 0x8b, 0x2d, 0xc8, 0x94, 0xdb, 0x4c, 0x24, 0x6f, 0x51, 0xdb, 0xbd,
-	0xc7, 0x15, 0x5b, 0x5c, 0x47, 0x48, 0x8c, 0xc2, 0x0d, 0x1f, 0xea, 0xc2, 0x91, 0x08, 0xa8, 0x63,
-	0x51, 0xdb, 0x75, 0x58, 0x5a, 0x87, 0xf8, 0x69, 0x9e, 0x6c, 0xd4, 0x61, 0x09, 0x4c, 0x6a, 0xa1,
-	0x8a, 0x8c, 0x47, 0xfb, 0x1e, 0x1e, 0xef, 0x4c, 0x2e, 0x92, 0x0e, 0x73, 0xe8, 0xd8, 0x66, 0x96,
-	0xac, 0x74, 0x09, 0xa7, 0xa6, 0xd6, 0x00, 0xb4, 0x79, 0xde, 0x4f, 0xe0, 0x7f, 0x82, 0xca, 0xea,
-	0xa5, 0xa0, 0x16, 0xd4, 0xe4, 0xb5, 0x10, 0x8b, 0xcf, 0x99, 0x23, 0xc5, 0xa9, 0xc8, 0x97, 0xfc,
-	0x7c, 0xeb, 0x5d, 0x5e, 0xa5, 0x28, 0x5c, 0x15, 0x6b, 0xb6, 0xf6, 0xb7, 0x02, 0x6a, 0x36, 0xcd,
-	0xff, 0x2d, 0x3a, 0x3a, 0x87, 0x47, 0x22, 0xf0, 0xb9, 0x47, 0xde, 0xbc, 0x26, 0x63, 0x1e, 0x64,
-	0x84, 0x52, 0xc2, 0x9f, 0xc9, 0xd5, 0x37, 0xaf, 0x2f, 0x79, 0xb0, 0xac, 0x9a, 0xf6, 0x4f, 0x0e,
-	0xaa, 0xeb, 0x71, 0xd1, 0x05, 0x14, 0xef, 0xa8, 0x1d, 0x32, 0x59, 0x96, 0xea, 0xd9, 0x97, 0x9f,
-	0x3e, 0x46, 0xe3, 0x26, 0xc2, 0xe2, 0x98, 0x82, 0x1e, 0xc1, 0x9e, 0xc3, 0x66, 0x34, 0x60, 0xc9,
-	0x9e, 0x89, 0x15, 0xb5, 0x68, 0x11, 0x4e, 0xa7, 0x7c, 0x41, 0xe4, 0x21, 0x3c, 0xee, 0xcc, 0x12,
-	0x69, 0x6d, 0xb4, 0x68, 0x53, 0xe2, 0xcc, 0x14, 0x86, 0x6b, 0x62, 0xdd, 0xa1, 0xfd, 0x09, 0x45,
-	0xb9, 0x27, 0x3a, 0x86, 0xa3, 0x51, 0xcf, 0x1c, 0x18, 0xcd, 0xf6, 0x75, 0xdb, 0xb8, 0x22, 0x37,
-	0x7a, 0x67, 0x64, 0xa8, 0x0f, 0xd0, 0x3e, 0xe4, 0xf5, 0xcb, 0xb6, 0xaa, 0x20, 0x04, 0x55, 0xb3,
-	0x89, 0x0d, 0xa3, 0x47, 0xae, 0x8c, 0x9e, 0xd9, 0x1e, 0x7e, 0x50, 0x73, 0xa8, 0x02, 0xa5, 0x8e,
-	0xde, 0x6b, 0x8d, 0xf4, 0x96, 0xa1, 0xe6, 0xd1, 0x73, 0x78, 0x32, 0x34, 0xde, 0x0f, 0x47, 0xd8,
-	0x20, 0xcd, 0x7e, 0x77, 0x80, 0x0d, 0xd3, 0x6c, 0xf7, 0x7b, 0xe4, 0xba, 0x8f, 0xbb, 0xfa, 0x50,
-	0x2d, 0x20, 0x15, 0x2a, 0x2d, 0xac, 0x0f, 0x7e, 0x6c, 0x37, 0x4d, 0xa2, 0x0f, 0xda, 0x6a, 0x51,
-	0xc3, 0x50, 0xcb, 0x1c, 0x70, 0xb7, 0x90, 0xa2, 0xde, 0x61, 0xb1, 0x29, 0x0d, 0xed, 0x80, 0xc4,
-	0x49, 0x24, 0x4d, 0xed, 0x30, 0xf1, 0xc6, 0x91, 0x34, 0x1b, 0xe0, 0x7e, 0xa0, 0xa0, 0x5f, 0xa1,
-	0x2e, 0x27, 0xd0, 0xb6, 0x01, 0x12, 0x0b, 0xe3, 0xe5, 0xb6, 0x71, 0xb4, 0x39, 0x3c, 0x8e, 0xe9,
-	0x36, 0xb7, 0xd6, 0x83, 0xe3, 0xad, 0xf8, 0xa8, 0x19, 0x7a, 0x74, 0x72, 0x4b, 0x67, 0x71, 0xa3,
-	0x93, 0xc9, 0x1c, 0xe0, 0x72, 0xe2, 0x8b, 0xda, 0x1c, 0x42, 0x50, 0xf0, 0x68, 0xf0, 0x5b, 0x92,
-	0x86, 0xfc, 0xd6, 0xbe, 0x8b, 0x1e, 0xe5, 0xae, 0x29, 0x95, 0x12, 0x94, 0x15, 0x02, 0x05, 0xb4,
-	0x39, 0x8d, 0xd0, 0x8b, 0x68, 0xf0, 0x7a, 0x24, 0xed, 0xfe, 0x51, 0xa6, 0xf9, 0x68, 0xb8, 0x7a,
-	0x37, 0xb1, 0x07, 0xbd, 0x82, 0xa3, 0x78, 0xe0, 0x25, 0x10, 0x12, 0xd0, 0x59, 0x72, 0x90, 0x9a,
-	0x5c, 0x48, 0x80, 0x43, 0x3a, 0xbb, 0x7c, 0x05, 0x68, 0xe2, 0xce, 0x33, 0x65, 0xfa, 0xf9, 0x61,
-	0x62, 0x93, 0xd8, 0x26, 0xf2, 0xef, 0xd1, 0x78, 0x4f, 0xfe, 0x9c, 0xff, 0x17, 0x00, 0x00, 0xff,
-	0xff, 0x6b, 0x05, 0xbf, 0x99, 0x35, 0x09, 0x00, 0x00,
+var file_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
+var file_config_proto_goTypes = []interface{}{
+	(BundleConfig_BundleType)(0),      // 0: android.bundle.BundleConfig.BundleType
+	(SplitDimension_Value)(0),         // 1: android.bundle.SplitDimension.Value
+	(*BundleConfig)(nil),              // 2: android.bundle.BundleConfig
+	(*Bundletool)(nil),                // 3: android.bundle.Bundletool
+	(*Compression)(nil),               // 4: android.bundle.Compression
+	(*MasterResources)(nil),           // 5: android.bundle.MasterResources
+	(*Optimizations)(nil),             // 6: android.bundle.Optimizations
+	(*UncompressNativeLibraries)(nil), // 7: android.bundle.UncompressNativeLibraries
+	(*UncompressDexFiles)(nil),        // 8: android.bundle.UncompressDexFiles
+	(*SplitsConfig)(nil),              // 9: android.bundle.SplitsConfig
+	(*StandaloneConfig)(nil),          // 10: android.bundle.StandaloneConfig
+	(*SplitDimension)(nil),            // 11: android.bundle.SplitDimension
+	(*SuffixStripping)(nil),           // 12: android.bundle.SuffixStripping
+	(*ApexConfig)(nil),                // 13: android.bundle.ApexConfig
+	(*ApexEmbeddedApkConfig)(nil),     // 14: android.bundle.ApexEmbeddedApkConfig
+	(*UnsignedEmbeddedApkConfig)(nil), // 15: android.bundle.UnsignedEmbeddedApkConfig
+	(*AssetModulesConfig)(nil),        // 16: android.bundle.AssetModulesConfig
+}
+var file_config_proto_depIdxs = []int32{
+	3,  // 0: android.bundle.BundleConfig.bundletool:type_name -> android.bundle.Bundletool
+	6,  // 1: android.bundle.BundleConfig.optimizations:type_name -> android.bundle.Optimizations
+	4,  // 2: android.bundle.BundleConfig.compression:type_name -> android.bundle.Compression
+	5,  // 3: android.bundle.BundleConfig.master_resources:type_name -> android.bundle.MasterResources
+	13, // 4: android.bundle.BundleConfig.apex_config:type_name -> android.bundle.ApexConfig
+	15, // 5: android.bundle.BundleConfig.unsigned_embedded_apk_config:type_name -> android.bundle.UnsignedEmbeddedApkConfig
+	16, // 6: android.bundle.BundleConfig.asset_modules_config:type_name -> android.bundle.AssetModulesConfig
+	0,  // 7: android.bundle.BundleConfig.type:type_name -> android.bundle.BundleConfig.BundleType
+	9,  // 8: android.bundle.Optimizations.splits_config:type_name -> android.bundle.SplitsConfig
+	7,  // 9: android.bundle.Optimizations.uncompress_native_libraries:type_name -> android.bundle.UncompressNativeLibraries
+	8,  // 10: android.bundle.Optimizations.uncompress_dex_files:type_name -> android.bundle.UncompressDexFiles
+	10, // 11: android.bundle.Optimizations.standalone_config:type_name -> android.bundle.StandaloneConfig
+	11, // 12: android.bundle.SplitsConfig.split_dimension:type_name -> android.bundle.SplitDimension
+	11, // 13: android.bundle.StandaloneConfig.split_dimension:type_name -> android.bundle.SplitDimension
+	1,  // 14: android.bundle.SplitDimension.value:type_name -> android.bundle.SplitDimension.Value
+	12, // 15: android.bundle.SplitDimension.suffix_stripping:type_name -> android.bundle.SuffixStripping
+	14, // 16: android.bundle.ApexConfig.apex_embedded_apk_config:type_name -> android.bundle.ApexEmbeddedApkConfig
+	17, // [17:17] is the sub-list for method output_type
+	17, // [17:17] is the sub-list for method input_type
+	17, // [17:17] is the sub-list for extension type_name
+	17, // [17:17] is the sub-list for extension extendee
+	0,  // [0:17] is the sub-list for field type_name
+}
+
+func init() { file_config_proto_init() }
+func file_config_proto_init() {
+	if File_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BundleConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Bundletool); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Compression); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MasterResources); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Optimizations); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UncompressNativeLibraries); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UncompressDexFiles); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SplitsConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StandaloneConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SplitDimension); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SuffixStripping); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApexConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApexEmbeddedApkConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UnsignedEmbeddedApkConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_config_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AssetModulesConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_config_proto_rawDesc,
+			NumEnums:      2,
+			NumMessages:   15,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_config_proto_goTypes,
+		DependencyIndexes: file_config_proto_depIdxs,
+		EnumInfos:         file_config_proto_enumTypes,
+		MessageInfos:      file_config_proto_msgTypes,
+	}.Build()
+	File_config_proto = out.File
+	file_config_proto_rawDesc = nil
+	file_config_proto_goTypes = nil
+	file_config_proto_depIdxs = nil
 }
diff --git a/cmd/extract_apks/bundle_proto/config.proto b/cmd/extract_apks/bundle_proto/config.proto
index 1c161aa..d6fac03 100644
--- a/cmd/extract_apks/bundle_proto/config.proto
+++ b/cmd/extract_apks/bundle_proto/config.proto
@@ -6,7 +6,7 @@
 
 package android.bundle;
 
-option go_package = "android_bundle_proto";
+option go_package = "android/soong/cmd/extract_apks/bundle_proto";
 option java_package = "com.android.bundle";
 
 message BundleConfig {
diff --git a/cmd/extract_apks/bundle_proto/targeting.pb.go b/cmd/extract_apks/bundle_proto/targeting.pb.go
index 187bc44..66e6f0d 100644
--- a/cmd/extract_apks/bundle_proto/targeting.pb.go
+++ b/cmd/extract_apks/bundle_proto/targeting.pb.go
@@ -1,24 +1,29 @@
+// Messages describing APK Set's table of contents (toc.pb entry).
+// Please be advised that the ultimate source is at
+// https://github.com/google/bundletool/tree/master/src/main/proto
+// so you have been warned.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: targeting.proto
 
-package android_bundle_proto
+package bundle_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type ScreenDensity_DensityAlias int32
 
@@ -34,36 +39,57 @@
 	ScreenDensity_XXXHDPI             ScreenDensity_DensityAlias = 8
 )
 
-var ScreenDensity_DensityAlias_name = map[int32]string{
-	0: "DENSITY_UNSPECIFIED",
-	1: "NODPI",
-	2: "LDPI",
-	3: "MDPI",
-	4: "TVDPI",
-	5: "HDPI",
-	6: "XHDPI",
-	7: "XXHDPI",
-	8: "XXXHDPI",
-}
+// Enum value maps for ScreenDensity_DensityAlias.
+var (
+	ScreenDensity_DensityAlias_name = map[int32]string{
+		0: "DENSITY_UNSPECIFIED",
+		1: "NODPI",
+		2: "LDPI",
+		3: "MDPI",
+		4: "TVDPI",
+		5: "HDPI",
+		6: "XHDPI",
+		7: "XXHDPI",
+		8: "XXXHDPI",
+	}
+	ScreenDensity_DensityAlias_value = map[string]int32{
+		"DENSITY_UNSPECIFIED": 0,
+		"NODPI":               1,
+		"LDPI":                2,
+		"MDPI":                3,
+		"TVDPI":               4,
+		"HDPI":                5,
+		"XHDPI":               6,
+		"XXHDPI":              7,
+		"XXXHDPI":             8,
+	}
+)
 
-var ScreenDensity_DensityAlias_value = map[string]int32{
-	"DENSITY_UNSPECIFIED": 0,
-	"NODPI":               1,
-	"LDPI":                2,
-	"MDPI":                3,
-	"TVDPI":               4,
-	"HDPI":                5,
-	"XHDPI":               6,
-	"XXHDPI":              7,
-	"XXXHDPI":             8,
+func (x ScreenDensity_DensityAlias) Enum() *ScreenDensity_DensityAlias {
+	p := new(ScreenDensity_DensityAlias)
+	*p = x
+	return p
 }
 
 func (x ScreenDensity_DensityAlias) String() string {
-	return proto.EnumName(ScreenDensity_DensityAlias_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (ScreenDensity_DensityAlias) Descriptor() protoreflect.EnumDescriptor {
+	return file_targeting_proto_enumTypes[0].Descriptor()
+}
+
+func (ScreenDensity_DensityAlias) Type() protoreflect.EnumType {
+	return &file_targeting_proto_enumTypes[0]
+}
+
+func (x ScreenDensity_DensityAlias) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ScreenDensity_DensityAlias.Descriptor instead.
 func (ScreenDensity_DensityAlias) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{4, 0}
+	return file_targeting_proto_rawDescGZIP(), []int{4, 0}
 }
 
 type TextureCompressionFormat_TextureCompressionFormatAlias int32
@@ -82,40 +108,61 @@
 	TextureCompressionFormat_ETC2                                   TextureCompressionFormat_TextureCompressionFormatAlias = 10
 )
 
-var TextureCompressionFormat_TextureCompressionFormatAlias_name = map[int32]string{
-	0:  "UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT",
-	1:  "ETC1_RGB8",
-	2:  "PALETTED",
-	3:  "THREE_DC",
-	4:  "ATC",
-	5:  "LATC",
-	6:  "DXT1",
-	7:  "S3TC",
-	8:  "PVRTC",
-	9:  "ASTC",
-	10: "ETC2",
-}
+// Enum value maps for TextureCompressionFormat_TextureCompressionFormatAlias.
+var (
+	TextureCompressionFormat_TextureCompressionFormatAlias_name = map[int32]string{
+		0:  "UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT",
+		1:  "ETC1_RGB8",
+		2:  "PALETTED",
+		3:  "THREE_DC",
+		4:  "ATC",
+		5:  "LATC",
+		6:  "DXT1",
+		7:  "S3TC",
+		8:  "PVRTC",
+		9:  "ASTC",
+		10: "ETC2",
+	}
+	TextureCompressionFormat_TextureCompressionFormatAlias_value = map[string]int32{
+		"UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT": 0,
+		"ETC1_RGB8":                              1,
+		"PALETTED":                               2,
+		"THREE_DC":                               3,
+		"ATC":                                    4,
+		"LATC":                                   5,
+		"DXT1":                                   6,
+		"S3TC":                                   7,
+		"PVRTC":                                  8,
+		"ASTC":                                   9,
+		"ETC2":                                   10,
+	}
+)
 
-var TextureCompressionFormat_TextureCompressionFormatAlias_value = map[string]int32{
-	"UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT": 0,
-	"ETC1_RGB8":                              1,
-	"PALETTED":                               2,
-	"THREE_DC":                               3,
-	"ATC":                                    4,
-	"LATC":                                   5,
-	"DXT1":                                   6,
-	"S3TC":                                   7,
-	"PVRTC":                                  8,
-	"ASTC":                                   9,
-	"ETC2":                                   10,
+func (x TextureCompressionFormat_TextureCompressionFormatAlias) Enum() *TextureCompressionFormat_TextureCompressionFormatAlias {
+	p := new(TextureCompressionFormat_TextureCompressionFormatAlias)
+	*p = x
+	return p
 }
 
 func (x TextureCompressionFormat_TextureCompressionFormatAlias) String() string {
-	return proto.EnumName(TextureCompressionFormat_TextureCompressionFormatAlias_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (TextureCompressionFormat_TextureCompressionFormatAlias) Descriptor() protoreflect.EnumDescriptor {
+	return file_targeting_proto_enumTypes[1].Descriptor()
+}
+
+func (TextureCompressionFormat_TextureCompressionFormatAlias) Type() protoreflect.EnumType {
+	return &file_targeting_proto_enumTypes[1]
+}
+
+func (x TextureCompressionFormat_TextureCompressionFormatAlias) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use TextureCompressionFormat_TextureCompressionFormatAlias.Descriptor instead.
 func (TextureCompressionFormat_TextureCompressionFormatAlias) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{10, 0}
+	return file_targeting_proto_rawDescGZIP(), []int{10, 0}
 }
 
 type Abi_AbiAlias int32
@@ -131,34 +178,55 @@
 	Abi_MIPS64                       Abi_AbiAlias = 7
 )
 
-var Abi_AbiAlias_name = map[int32]string{
-	0: "UNSPECIFIED_CPU_ARCHITECTURE",
-	1: "ARMEABI",
-	2: "ARMEABI_V7A",
-	3: "ARM64_V8A",
-	4: "X86",
-	5: "X86_64",
-	6: "MIPS",
-	7: "MIPS64",
-}
+// Enum value maps for Abi_AbiAlias.
+var (
+	Abi_AbiAlias_name = map[int32]string{
+		0: "UNSPECIFIED_CPU_ARCHITECTURE",
+		1: "ARMEABI",
+		2: "ARMEABI_V7A",
+		3: "ARM64_V8A",
+		4: "X86",
+		5: "X86_64",
+		6: "MIPS",
+		7: "MIPS64",
+	}
+	Abi_AbiAlias_value = map[string]int32{
+		"UNSPECIFIED_CPU_ARCHITECTURE": 0,
+		"ARMEABI":                      1,
+		"ARMEABI_V7A":                  2,
+		"ARM64_V8A":                    3,
+		"X86":                          4,
+		"X86_64":                       5,
+		"MIPS":                         6,
+		"MIPS64":                       7,
+	}
+)
 
-var Abi_AbiAlias_value = map[string]int32{
-	"UNSPECIFIED_CPU_ARCHITECTURE": 0,
-	"ARMEABI":                      1,
-	"ARMEABI_V7A":                  2,
-	"ARM64_V8A":                    3,
-	"X86":                          4,
-	"X86_64":                       5,
-	"MIPS":                         6,
-	"MIPS64":                       7,
+func (x Abi_AbiAlias) Enum() *Abi_AbiAlias {
+	p := new(Abi_AbiAlias)
+	*p = x
+	return p
 }
 
 func (x Abi_AbiAlias) String() string {
-	return proto.EnumName(Abi_AbiAlias_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Abi_AbiAlias) Descriptor() protoreflect.EnumDescriptor {
+	return file_targeting_proto_enumTypes[2].Descriptor()
+}
+
+func (Abi_AbiAlias) Type() protoreflect.EnumType {
+	return &file_targeting_proto_enumTypes[2]
+}
+
+func (x Abi_AbiAlias) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Abi_AbiAlias.Descriptor instead.
 func (Abi_AbiAlias) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{11, 0}
+	return file_targeting_proto_rawDescGZIP(), []int{11, 0}
 }
 
 type Sanitizer_SanitizerAlias int32
@@ -168,98 +236,131 @@
 	Sanitizer_HWADDRESS Sanitizer_SanitizerAlias = 1
 )
 
-var Sanitizer_SanitizerAlias_name = map[int32]string{
-	0: "NONE",
-	1: "HWADDRESS",
-}
+// Enum value maps for Sanitizer_SanitizerAlias.
+var (
+	Sanitizer_SanitizerAlias_name = map[int32]string{
+		0: "NONE",
+		1: "HWADDRESS",
+	}
+	Sanitizer_SanitizerAlias_value = map[string]int32{
+		"NONE":      0,
+		"HWADDRESS": 1,
+	}
+)
 
-var Sanitizer_SanitizerAlias_value = map[string]int32{
-	"NONE":      0,
-	"HWADDRESS": 1,
+func (x Sanitizer_SanitizerAlias) Enum() *Sanitizer_SanitizerAlias {
+	p := new(Sanitizer_SanitizerAlias)
+	*p = x
+	return p
 }
 
 func (x Sanitizer_SanitizerAlias) String() string {
-	return proto.EnumName(Sanitizer_SanitizerAlias_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
+func (Sanitizer_SanitizerAlias) Descriptor() protoreflect.EnumDescriptor {
+	return file_targeting_proto_enumTypes[3].Descriptor()
+}
+
+func (Sanitizer_SanitizerAlias) Type() protoreflect.EnumType {
+	return &file_targeting_proto_enumTypes[3]
+}
+
+func (x Sanitizer_SanitizerAlias) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Sanitizer_SanitizerAlias.Descriptor instead.
 func (Sanitizer_SanitizerAlias) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{13, 0}
+	return file_targeting_proto_rawDescGZIP(), []int{13, 0}
 }
 
 // Targeting on the level of variants.
 type VariantTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	SdkVersionTargeting               *SdkVersionTargeting               `protobuf:"bytes,1,opt,name=sdk_version_targeting,json=sdkVersionTargeting,proto3" json:"sdk_version_targeting,omitempty"`
 	AbiTargeting                      *AbiTargeting                      `protobuf:"bytes,2,opt,name=abi_targeting,json=abiTargeting,proto3" json:"abi_targeting,omitempty"`
 	ScreenDensityTargeting            *ScreenDensityTargeting            `protobuf:"bytes,3,opt,name=screen_density_targeting,json=screenDensityTargeting,proto3" json:"screen_density_targeting,omitempty"`
 	MultiAbiTargeting                 *MultiAbiTargeting                 `protobuf:"bytes,4,opt,name=multi_abi_targeting,json=multiAbiTargeting,proto3" json:"multi_abi_targeting,omitempty"`
 	TextureCompressionFormatTargeting *TextureCompressionFormatTargeting `protobuf:"bytes,5,opt,name=texture_compression_format_targeting,json=textureCompressionFormatTargeting,proto3" json:"texture_compression_format_targeting,omitempty"`
-	XXX_NoUnkeyedLiteral              struct{}                           `json:"-"`
-	XXX_unrecognized                  []byte                             `json:"-"`
-	XXX_sizecache                     int32                              `json:"-"`
 }
 
-func (m *VariantTargeting) Reset()         { *m = VariantTargeting{} }
-func (m *VariantTargeting) String() string { return proto.CompactTextString(m) }
-func (*VariantTargeting) ProtoMessage()    {}
+func (x *VariantTargeting) Reset() {
+	*x = VariantTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *VariantTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*VariantTargeting) ProtoMessage() {}
+
+func (x *VariantTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use VariantTargeting.ProtoReflect.Descriptor instead.
 func (*VariantTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{0}
+	return file_targeting_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *VariantTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_VariantTargeting.Unmarshal(m, b)
-}
-func (m *VariantTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_VariantTargeting.Marshal(b, m, deterministic)
-}
-func (m *VariantTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_VariantTargeting.Merge(m, src)
-}
-func (m *VariantTargeting) XXX_Size() int {
-	return xxx_messageInfo_VariantTargeting.Size(m)
-}
-func (m *VariantTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_VariantTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_VariantTargeting proto.InternalMessageInfo
-
-func (m *VariantTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
-	if m != nil {
-		return m.SdkVersionTargeting
+func (x *VariantTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
+	if x != nil {
+		return x.SdkVersionTargeting
 	}
 	return nil
 }
 
-func (m *VariantTargeting) GetAbiTargeting() *AbiTargeting {
-	if m != nil {
-		return m.AbiTargeting
+func (x *VariantTargeting) GetAbiTargeting() *AbiTargeting {
+	if x != nil {
+		return x.AbiTargeting
 	}
 	return nil
 }
 
-func (m *VariantTargeting) GetScreenDensityTargeting() *ScreenDensityTargeting {
-	if m != nil {
-		return m.ScreenDensityTargeting
+func (x *VariantTargeting) GetScreenDensityTargeting() *ScreenDensityTargeting {
+	if x != nil {
+		return x.ScreenDensityTargeting
 	}
 	return nil
 }
 
-func (m *VariantTargeting) GetMultiAbiTargeting() *MultiAbiTargeting {
-	if m != nil {
-		return m.MultiAbiTargeting
+func (x *VariantTargeting) GetMultiAbiTargeting() *MultiAbiTargeting {
+	if x != nil {
+		return x.MultiAbiTargeting
 	}
 	return nil
 }
 
-func (m *VariantTargeting) GetTextureCompressionFormatTargeting() *TextureCompressionFormatTargeting {
-	if m != nil {
-		return m.TextureCompressionFormatTargeting
+func (x *VariantTargeting) GetTextureCompressionFormatTargeting() *TextureCompressionFormatTargeting {
+	if x != nil {
+		return x.TextureCompressionFormatTargeting
 	}
 	return nil
 }
 
 // Targeting on the level of individual APKs.
 type ApkTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	AbiTargeting                      *AbiTargeting                      `protobuf:"bytes,1,opt,name=abi_targeting,json=abiTargeting,proto3" json:"abi_targeting,omitempty"`
 	GraphicsApiTargeting              *GraphicsApiTargeting              `protobuf:"bytes,2,opt,name=graphics_api_targeting,json=graphicsApiTargeting,proto3" json:"graphics_api_targeting,omitempty"`
 	LanguageTargeting                 *LanguageTargeting                 `protobuf:"bytes,3,opt,name=language_targeting,json=languageTargeting,proto3" json:"language_targeting,omitempty"`
@@ -268,88 +369,92 @@
 	TextureCompressionFormatTargeting *TextureCompressionFormatTargeting `protobuf:"bytes,6,opt,name=texture_compression_format_targeting,json=textureCompressionFormatTargeting,proto3" json:"texture_compression_format_targeting,omitempty"`
 	MultiAbiTargeting                 *MultiAbiTargeting                 `protobuf:"bytes,7,opt,name=multi_abi_targeting,json=multiAbiTargeting,proto3" json:"multi_abi_targeting,omitempty"`
 	SanitizerTargeting                *SanitizerTargeting                `protobuf:"bytes,8,opt,name=sanitizer_targeting,json=sanitizerTargeting,proto3" json:"sanitizer_targeting,omitempty"`
-	XXX_NoUnkeyedLiteral              struct{}                           `json:"-"`
-	XXX_unrecognized                  []byte                             `json:"-"`
-	XXX_sizecache                     int32                              `json:"-"`
 }
 
-func (m *ApkTargeting) Reset()         { *m = ApkTargeting{} }
-func (m *ApkTargeting) String() string { return proto.CompactTextString(m) }
-func (*ApkTargeting) ProtoMessage()    {}
+func (x *ApkTargeting) Reset() {
+	*x = ApkTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApkTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApkTargeting) ProtoMessage() {}
+
+func (x *ApkTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ApkTargeting.ProtoReflect.Descriptor instead.
 func (*ApkTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{1}
+	return file_targeting_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *ApkTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApkTargeting.Unmarshal(m, b)
-}
-func (m *ApkTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApkTargeting.Marshal(b, m, deterministic)
-}
-func (m *ApkTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApkTargeting.Merge(m, src)
-}
-func (m *ApkTargeting) XXX_Size() int {
-	return xxx_messageInfo_ApkTargeting.Size(m)
-}
-func (m *ApkTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApkTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApkTargeting proto.InternalMessageInfo
-
-func (m *ApkTargeting) GetAbiTargeting() *AbiTargeting {
-	if m != nil {
-		return m.AbiTargeting
+func (x *ApkTargeting) GetAbiTargeting() *AbiTargeting {
+	if x != nil {
+		return x.AbiTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetGraphicsApiTargeting() *GraphicsApiTargeting {
-	if m != nil {
-		return m.GraphicsApiTargeting
+func (x *ApkTargeting) GetGraphicsApiTargeting() *GraphicsApiTargeting {
+	if x != nil {
+		return x.GraphicsApiTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetLanguageTargeting() *LanguageTargeting {
-	if m != nil {
-		return m.LanguageTargeting
+func (x *ApkTargeting) GetLanguageTargeting() *LanguageTargeting {
+	if x != nil {
+		return x.LanguageTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetScreenDensityTargeting() *ScreenDensityTargeting {
-	if m != nil {
-		return m.ScreenDensityTargeting
+func (x *ApkTargeting) GetScreenDensityTargeting() *ScreenDensityTargeting {
+	if x != nil {
+		return x.ScreenDensityTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
-	if m != nil {
-		return m.SdkVersionTargeting
+func (x *ApkTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
+	if x != nil {
+		return x.SdkVersionTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetTextureCompressionFormatTargeting() *TextureCompressionFormatTargeting {
-	if m != nil {
-		return m.TextureCompressionFormatTargeting
+func (x *ApkTargeting) GetTextureCompressionFormatTargeting() *TextureCompressionFormatTargeting {
+	if x != nil {
+		return x.TextureCompressionFormatTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetMultiAbiTargeting() *MultiAbiTargeting {
-	if m != nil {
-		return m.MultiAbiTargeting
+func (x *ApkTargeting) GetMultiAbiTargeting() *MultiAbiTargeting {
+	if x != nil {
+		return x.MultiAbiTargeting
 	}
 	return nil
 }
 
-func (m *ApkTargeting) GetSanitizerTargeting() *SanitizerTargeting {
-	if m != nil {
-		return m.SanitizerTargeting
+func (x *ApkTargeting) GetSanitizerTargeting() *SanitizerTargeting {
+	if x != nil {
+		return x.SanitizerTargeting
 	}
 	return nil
 }
@@ -357,56 +462,64 @@
 // Targeting on the module level.
 // The semantic of the targeting is the "AND" rule on all immediate values.
 type ModuleTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	SdkVersionTargeting    *SdkVersionTargeting      `protobuf:"bytes,1,opt,name=sdk_version_targeting,json=sdkVersionTargeting,proto3" json:"sdk_version_targeting,omitempty"`
 	DeviceFeatureTargeting []*DeviceFeatureTargeting `protobuf:"bytes,2,rep,name=device_feature_targeting,json=deviceFeatureTargeting,proto3" json:"device_feature_targeting,omitempty"`
 	UserCountriesTargeting *UserCountriesTargeting   `protobuf:"bytes,3,opt,name=user_countries_targeting,json=userCountriesTargeting,proto3" json:"user_countries_targeting,omitempty"`
-	XXX_NoUnkeyedLiteral   struct{}                  `json:"-"`
-	XXX_unrecognized       []byte                    `json:"-"`
-	XXX_sizecache          int32                     `json:"-"`
 }
 
-func (m *ModuleTargeting) Reset()         { *m = ModuleTargeting{} }
-func (m *ModuleTargeting) String() string { return proto.CompactTextString(m) }
-func (*ModuleTargeting) ProtoMessage()    {}
+func (x *ModuleTargeting) Reset() {
+	*x = ModuleTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ModuleTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ModuleTargeting) ProtoMessage() {}
+
+func (x *ModuleTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ModuleTargeting.ProtoReflect.Descriptor instead.
 func (*ModuleTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{2}
+	return file_targeting_proto_rawDescGZIP(), []int{2}
 }
 
-func (m *ModuleTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ModuleTargeting.Unmarshal(m, b)
-}
-func (m *ModuleTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ModuleTargeting.Marshal(b, m, deterministic)
-}
-func (m *ModuleTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ModuleTargeting.Merge(m, src)
-}
-func (m *ModuleTargeting) XXX_Size() int {
-	return xxx_messageInfo_ModuleTargeting.Size(m)
-}
-func (m *ModuleTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_ModuleTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ModuleTargeting proto.InternalMessageInfo
-
-func (m *ModuleTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
-	if m != nil {
-		return m.SdkVersionTargeting
+func (x *ModuleTargeting) GetSdkVersionTargeting() *SdkVersionTargeting {
+	if x != nil {
+		return x.SdkVersionTargeting
 	}
 	return nil
 }
 
-func (m *ModuleTargeting) GetDeviceFeatureTargeting() []*DeviceFeatureTargeting {
-	if m != nil {
-		return m.DeviceFeatureTargeting
+func (x *ModuleTargeting) GetDeviceFeatureTargeting() []*DeviceFeatureTargeting {
+	if x != nil {
+		return x.DeviceFeatureTargeting
 	}
 	return nil
 }
 
-func (m *ModuleTargeting) GetUserCountriesTargeting() *UserCountriesTargeting {
-	if m != nil {
-		return m.UserCountriesTargeting
+func (x *ModuleTargeting) GetUserCountriesTargeting() *UserCountriesTargeting {
+	if x != nil {
+		return x.UserCountriesTargeting
 	}
 	return nil
 }
@@ -414,88 +527,125 @@
 // User Countries targeting describing an inclusive/exclusive list of country
 // codes that module targets.
 type UserCountriesTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// List of country codes in the two-letter CLDR territory format.
 	CountryCodes []string `protobuf:"bytes,1,rep,name=country_codes,json=countryCodes,proto3" json:"country_codes,omitempty"`
 	// Indicates if the list above is exclusive.
-	Exclude              bool     `protobuf:"varint,2,opt,name=exclude,proto3" json:"exclude,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Exclude bool `protobuf:"varint,2,opt,name=exclude,proto3" json:"exclude,omitempty"`
 }
 
-func (m *UserCountriesTargeting) Reset()         { *m = UserCountriesTargeting{} }
-func (m *UserCountriesTargeting) String() string { return proto.CompactTextString(m) }
-func (*UserCountriesTargeting) ProtoMessage()    {}
+func (x *UserCountriesTargeting) Reset() {
+	*x = UserCountriesTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UserCountriesTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UserCountriesTargeting) ProtoMessage() {}
+
+func (x *UserCountriesTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[3]
+	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 UserCountriesTargeting.ProtoReflect.Descriptor instead.
 func (*UserCountriesTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{3}
+	return file_targeting_proto_rawDescGZIP(), []int{3}
 }
 
-func (m *UserCountriesTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_UserCountriesTargeting.Unmarshal(m, b)
-}
-func (m *UserCountriesTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_UserCountriesTargeting.Marshal(b, m, deterministic)
-}
-func (m *UserCountriesTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_UserCountriesTargeting.Merge(m, src)
-}
-func (m *UserCountriesTargeting) XXX_Size() int {
-	return xxx_messageInfo_UserCountriesTargeting.Size(m)
-}
-func (m *UserCountriesTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_UserCountriesTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_UserCountriesTargeting proto.InternalMessageInfo
-
-func (m *UserCountriesTargeting) GetCountryCodes() []string {
-	if m != nil {
-		return m.CountryCodes
+func (x *UserCountriesTargeting) GetCountryCodes() []string {
+	if x != nil {
+		return x.CountryCodes
 	}
 	return nil
 }
 
-func (m *UserCountriesTargeting) GetExclude() bool {
-	if m != nil {
-		return m.Exclude
+func (x *UserCountriesTargeting) GetExclude() bool {
+	if x != nil {
+		return x.Exclude
 	}
 	return false
 }
 
 type ScreenDensity struct {
-	// Types that are valid to be assigned to DensityOneof:
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to DensityOneof:
 	//	*ScreenDensity_DensityAlias_
 	//	*ScreenDensity_DensityDpi
-	DensityOneof         isScreenDensity_DensityOneof `protobuf_oneof:"density_oneof"`
-	XXX_NoUnkeyedLiteral struct{}                     `json:"-"`
-	XXX_unrecognized     []byte                       `json:"-"`
-	XXX_sizecache        int32                        `json:"-"`
+	DensityOneof isScreenDensity_DensityOneof `protobuf_oneof:"density_oneof"`
 }
 
-func (m *ScreenDensity) Reset()         { *m = ScreenDensity{} }
-func (m *ScreenDensity) String() string { return proto.CompactTextString(m) }
-func (*ScreenDensity) ProtoMessage()    {}
+func (x *ScreenDensity) Reset() {
+	*x = ScreenDensity{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ScreenDensity) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ScreenDensity) ProtoMessage() {}
+
+func (x *ScreenDensity) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[4]
+	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 ScreenDensity.ProtoReflect.Descriptor instead.
 func (*ScreenDensity) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{4}
+	return file_targeting_proto_rawDescGZIP(), []int{4}
 }
 
-func (m *ScreenDensity) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ScreenDensity.Unmarshal(m, b)
-}
-func (m *ScreenDensity) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ScreenDensity.Marshal(b, m, deterministic)
-}
-func (m *ScreenDensity) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ScreenDensity.Merge(m, src)
-}
-func (m *ScreenDensity) XXX_Size() int {
-	return xxx_messageInfo_ScreenDensity.Size(m)
-}
-func (m *ScreenDensity) XXX_DiscardUnknown() {
-	xxx_messageInfo_ScreenDensity.DiscardUnknown(m)
+func (m *ScreenDensity) GetDensityOneof() isScreenDensity_DensityOneof {
+	if m != nil {
+		return m.DensityOneof
+	}
+	return nil
 }
 
-var xxx_messageInfo_ScreenDensity proto.InternalMessageInfo
+func (x *ScreenDensity) GetDensityAlias() ScreenDensity_DensityAlias {
+	if x, ok := x.GetDensityOneof().(*ScreenDensity_DensityAlias_); ok {
+		return x.DensityAlias
+	}
+	return ScreenDensity_DENSITY_UNSPECIFIED
+}
+
+func (x *ScreenDensity) GetDensityDpi() int32 {
+	if x, ok := x.GetDensityOneof().(*ScreenDensity_DensityDpi); ok {
+		return x.DensityDpi
+	}
+	return 0
+}
 
 type isScreenDensity_DensityOneof interface {
 	isScreenDensity_DensityOneof()
@@ -513,169 +663,148 @@
 
 func (*ScreenDensity_DensityDpi) isScreenDensity_DensityOneof() {}
 
-func (m *ScreenDensity) GetDensityOneof() isScreenDensity_DensityOneof {
-	if m != nil {
-		return m.DensityOneof
-	}
-	return nil
-}
-
-func (m *ScreenDensity) GetDensityAlias() ScreenDensity_DensityAlias {
-	if x, ok := m.GetDensityOneof().(*ScreenDensity_DensityAlias_); ok {
-		return x.DensityAlias
-	}
-	return ScreenDensity_DENSITY_UNSPECIFIED
-}
-
-func (m *ScreenDensity) GetDensityDpi() int32 {
-	if x, ok := m.GetDensityOneof().(*ScreenDensity_DensityDpi); ok {
-		return x.DensityDpi
-	}
-	return 0
-}
-
-// XXX_OneofWrappers is for the internal use of the proto package.
-func (*ScreenDensity) XXX_OneofWrappers() []interface{} {
-	return []interface{}{
-		(*ScreenDensity_DensityAlias_)(nil),
-		(*ScreenDensity_DensityDpi)(nil),
-	}
-}
-
 // Wrapper message for `int32`.
 //
 // The JSON representation for `Int32Value` is JSON number.
 type Int32Value struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The int32 value.
-	Value                int32    `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Value int32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
 }
 
-func (m *Int32Value) Reset()         { *m = Int32Value{} }
-func (m *Int32Value) String() string { return proto.CompactTextString(m) }
-func (*Int32Value) ProtoMessage()    {}
+func (x *Int32Value) Reset() {
+	*x = Int32Value{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Int32Value) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Int32Value) ProtoMessage() {}
+
+func (x *Int32Value) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[5]
+	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 Int32Value.ProtoReflect.Descriptor instead.
 func (*Int32Value) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{5}
+	return file_targeting_proto_rawDescGZIP(), []int{5}
 }
 
-func (m *Int32Value) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Int32Value.Unmarshal(m, b)
-}
-func (m *Int32Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Int32Value.Marshal(b, m, deterministic)
-}
-func (m *Int32Value) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Int32Value.Merge(m, src)
-}
-func (m *Int32Value) XXX_Size() int {
-	return xxx_messageInfo_Int32Value.Size(m)
-}
-func (m *Int32Value) XXX_DiscardUnknown() {
-	xxx_messageInfo_Int32Value.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Int32Value proto.InternalMessageInfo
-
-func (m *Int32Value) GetValue() int32 {
-	if m != nil {
-		return m.Value
+func (x *Int32Value) GetValue() int32 {
+	if x != nil {
+		return x.Value
 	}
 	return 0
 }
 
 type SdkVersion struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Inclusive.
-	Min                  *Int32Value `protobuf:"bytes,1,opt,name=min,proto3" json:"min,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
-	XXX_unrecognized     []byte      `json:"-"`
-	XXX_sizecache        int32       `json:"-"`
+	Min *Int32Value `protobuf:"bytes,1,opt,name=min,proto3" json:"min,omitempty"`
 }
 
-func (m *SdkVersion) Reset()         { *m = SdkVersion{} }
-func (m *SdkVersion) String() string { return proto.CompactTextString(m) }
-func (*SdkVersion) ProtoMessage()    {}
+func (x *SdkVersion) Reset() {
+	*x = SdkVersion{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SdkVersion) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SdkVersion) ProtoMessage() {}
+
+func (x *SdkVersion) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[6]
+	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 SdkVersion.ProtoReflect.Descriptor instead.
 func (*SdkVersion) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{6}
+	return file_targeting_proto_rawDescGZIP(), []int{6}
 }
 
-func (m *SdkVersion) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SdkVersion.Unmarshal(m, b)
-}
-func (m *SdkVersion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SdkVersion.Marshal(b, m, deterministic)
-}
-func (m *SdkVersion) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SdkVersion.Merge(m, src)
-}
-func (m *SdkVersion) XXX_Size() int {
-	return xxx_messageInfo_SdkVersion.Size(m)
-}
-func (m *SdkVersion) XXX_DiscardUnknown() {
-	xxx_messageInfo_SdkVersion.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SdkVersion proto.InternalMessageInfo
-
-func (m *SdkVersion) GetMin() *Int32Value {
-	if m != nil {
-		return m.Min
+func (x *SdkVersion) GetMin() *Int32Value {
+	if x != nil {
+		return x.Min
 	}
 	return nil
 }
 
 type GraphicsApi struct {
-	// Types that are valid to be assigned to ApiOneof:
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to ApiOneof:
 	//	*GraphicsApi_MinOpenGlVersion
 	//	*GraphicsApi_MinVulkanVersion
-	ApiOneof             isGraphicsApi_ApiOneof `protobuf_oneof:"api_oneof"`
-	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
-	XXX_unrecognized     []byte                 `json:"-"`
-	XXX_sizecache        int32                  `json:"-"`
+	ApiOneof isGraphicsApi_ApiOneof `protobuf_oneof:"api_oneof"`
 }
 
-func (m *GraphicsApi) Reset()         { *m = GraphicsApi{} }
-func (m *GraphicsApi) String() string { return proto.CompactTextString(m) }
-func (*GraphicsApi) ProtoMessage()    {}
+func (x *GraphicsApi) Reset() {
+	*x = GraphicsApi{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GraphicsApi) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GraphicsApi) ProtoMessage() {}
+
+func (x *GraphicsApi) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[7]
+	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 GraphicsApi.ProtoReflect.Descriptor instead.
 func (*GraphicsApi) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{7}
+	return file_targeting_proto_rawDescGZIP(), []int{7}
 }
 
-func (m *GraphicsApi) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_GraphicsApi.Unmarshal(m, b)
-}
-func (m *GraphicsApi) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_GraphicsApi.Marshal(b, m, deterministic)
-}
-func (m *GraphicsApi) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_GraphicsApi.Merge(m, src)
-}
-func (m *GraphicsApi) XXX_Size() int {
-	return xxx_messageInfo_GraphicsApi.Size(m)
-}
-func (m *GraphicsApi) XXX_DiscardUnknown() {
-	xxx_messageInfo_GraphicsApi.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_GraphicsApi proto.InternalMessageInfo
-
-type isGraphicsApi_ApiOneof interface {
-	isGraphicsApi_ApiOneof()
-}
-
-type GraphicsApi_MinOpenGlVersion struct {
-	MinOpenGlVersion *OpenGlVersion `protobuf:"bytes,1,opt,name=min_open_gl_version,json=minOpenGlVersion,proto3,oneof"`
-}
-
-type GraphicsApi_MinVulkanVersion struct {
-	MinVulkanVersion *VulkanVersion `protobuf:"bytes,2,opt,name=min_vulkan_version,json=minVulkanVersion,proto3,oneof"`
-}
-
-func (*GraphicsApi_MinOpenGlVersion) isGraphicsApi_ApiOneof() {}
-
-func (*GraphicsApi_MinVulkanVersion) isGraphicsApi_ApiOneof() {}
-
 func (m *GraphicsApi) GetApiOneof() isGraphicsApi_ApiOneof {
 	if m != nil {
 		return m.ApiOneof
@@ -683,874 +812,1028 @@
 	return nil
 }
 
-func (m *GraphicsApi) GetMinOpenGlVersion() *OpenGlVersion {
-	if x, ok := m.GetApiOneof().(*GraphicsApi_MinOpenGlVersion); ok {
+func (x *GraphicsApi) GetMinOpenGlVersion() *OpenGlVersion {
+	if x, ok := x.GetApiOneof().(*GraphicsApi_MinOpenGlVersion); ok {
 		return x.MinOpenGlVersion
 	}
 	return nil
 }
 
-func (m *GraphicsApi) GetMinVulkanVersion() *VulkanVersion {
-	if x, ok := m.GetApiOneof().(*GraphicsApi_MinVulkanVersion); ok {
+func (x *GraphicsApi) GetMinVulkanVersion() *VulkanVersion {
+	if x, ok := x.GetApiOneof().(*GraphicsApi_MinVulkanVersion); ok {
 		return x.MinVulkanVersion
 	}
 	return nil
 }
 
-// XXX_OneofWrappers is for the internal use of the proto package.
-func (*GraphicsApi) XXX_OneofWrappers() []interface{} {
-	return []interface{}{
-		(*GraphicsApi_MinOpenGlVersion)(nil),
-		(*GraphicsApi_MinVulkanVersion)(nil),
+type isGraphicsApi_ApiOneof interface {
+	isGraphicsApi_ApiOneof()
+}
+
+type GraphicsApi_MinOpenGlVersion struct {
+	// Inclusive.
+	MinOpenGlVersion *OpenGlVersion `protobuf:"bytes,1,opt,name=min_open_gl_version,json=minOpenGlVersion,proto3,oneof"`
+}
+
+type GraphicsApi_MinVulkanVersion struct {
+	// Inclusive.
+	MinVulkanVersion *VulkanVersion `protobuf:"bytes,2,opt,name=min_vulkan_version,json=minVulkanVersion,proto3,oneof"`
+}
+
+func (*GraphicsApi_MinOpenGlVersion) isGraphicsApi_ApiOneof() {}
+
+func (*GraphicsApi_MinVulkanVersion) isGraphicsApi_ApiOneof() {}
+
+type VulkanVersion struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Major int32 `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"` // VK_VERSION_MAJOR
+	Minor int32 `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"` // VK_VERSION_MINOR
+}
+
+func (x *VulkanVersion) Reset() {
+	*x = VulkanVersion{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
 	}
 }
 
-type VulkanVersion struct {
-	Major                int32    `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"`
-	Minor                int32    `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+func (x *VulkanVersion) String() string {
+	return protoimpl.X.MessageStringOf(x)
 }
 
-func (m *VulkanVersion) Reset()         { *m = VulkanVersion{} }
-func (m *VulkanVersion) String() string { return proto.CompactTextString(m) }
-func (*VulkanVersion) ProtoMessage()    {}
+func (*VulkanVersion) ProtoMessage() {}
+
+func (x *VulkanVersion) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[8]
+	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 VulkanVersion.ProtoReflect.Descriptor instead.
 func (*VulkanVersion) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{8}
+	return file_targeting_proto_rawDescGZIP(), []int{8}
 }
 
-func (m *VulkanVersion) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_VulkanVersion.Unmarshal(m, b)
-}
-func (m *VulkanVersion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_VulkanVersion.Marshal(b, m, deterministic)
-}
-func (m *VulkanVersion) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_VulkanVersion.Merge(m, src)
-}
-func (m *VulkanVersion) XXX_Size() int {
-	return xxx_messageInfo_VulkanVersion.Size(m)
-}
-func (m *VulkanVersion) XXX_DiscardUnknown() {
-	xxx_messageInfo_VulkanVersion.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_VulkanVersion proto.InternalMessageInfo
-
-func (m *VulkanVersion) GetMajor() int32 {
-	if m != nil {
-		return m.Major
+func (x *VulkanVersion) GetMajor() int32 {
+	if x != nil {
+		return x.Major
 	}
 	return 0
 }
 
-func (m *VulkanVersion) GetMinor() int32 {
-	if m != nil {
-		return m.Minor
+func (x *VulkanVersion) GetMinor() int32 {
+	if x != nil {
+		return x.Minor
 	}
 	return 0
 }
 
 type OpenGlVersion struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// e.g. OpenGL ES 3.2 is represented as { major: 3, minor: 2 }
-	Major                int32    `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"`
-	Minor                int32    `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Major int32 `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"` // GL_MAJOR_VERSION
+	Minor int32 `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"` // GL_MINOR_VERSION
 }
 
-func (m *OpenGlVersion) Reset()         { *m = OpenGlVersion{} }
-func (m *OpenGlVersion) String() string { return proto.CompactTextString(m) }
-func (*OpenGlVersion) ProtoMessage()    {}
+func (x *OpenGlVersion) Reset() {
+	*x = OpenGlVersion{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OpenGlVersion) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OpenGlVersion) ProtoMessage() {}
+
+func (x *OpenGlVersion) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[9]
+	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 OpenGlVersion.ProtoReflect.Descriptor instead.
 func (*OpenGlVersion) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{9}
+	return file_targeting_proto_rawDescGZIP(), []int{9}
 }
 
-func (m *OpenGlVersion) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_OpenGlVersion.Unmarshal(m, b)
-}
-func (m *OpenGlVersion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_OpenGlVersion.Marshal(b, m, deterministic)
-}
-func (m *OpenGlVersion) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_OpenGlVersion.Merge(m, src)
-}
-func (m *OpenGlVersion) XXX_Size() int {
-	return xxx_messageInfo_OpenGlVersion.Size(m)
-}
-func (m *OpenGlVersion) XXX_DiscardUnknown() {
-	xxx_messageInfo_OpenGlVersion.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_OpenGlVersion proto.InternalMessageInfo
-
-func (m *OpenGlVersion) GetMajor() int32 {
-	if m != nil {
-		return m.Major
+func (x *OpenGlVersion) GetMajor() int32 {
+	if x != nil {
+		return x.Major
 	}
 	return 0
 }
 
-func (m *OpenGlVersion) GetMinor() int32 {
-	if m != nil {
-		return m.Minor
+func (x *OpenGlVersion) GetMinor() int32 {
+	if x != nil {
+		return x.Minor
 	}
 	return 0
 }
 
 type TextureCompressionFormat struct {
-	Alias                TextureCompressionFormat_TextureCompressionFormatAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.TextureCompressionFormat_TextureCompressionFormatAlias" json:"alias,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                                               `json:"-"`
-	XXX_unrecognized     []byte                                                 `json:"-"`
-	XXX_sizecache        int32                                                  `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Alias TextureCompressionFormat_TextureCompressionFormatAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.TextureCompressionFormat_TextureCompressionFormatAlias" json:"alias,omitempty"`
 }
 
-func (m *TextureCompressionFormat) Reset()         { *m = TextureCompressionFormat{} }
-func (m *TextureCompressionFormat) String() string { return proto.CompactTextString(m) }
-func (*TextureCompressionFormat) ProtoMessage()    {}
+func (x *TextureCompressionFormat) Reset() {
+	*x = TextureCompressionFormat{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TextureCompressionFormat) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TextureCompressionFormat) ProtoMessage() {}
+
+func (x *TextureCompressionFormat) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_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 TextureCompressionFormat.ProtoReflect.Descriptor instead.
 func (*TextureCompressionFormat) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{10}
+	return file_targeting_proto_rawDescGZIP(), []int{10}
 }
 
-func (m *TextureCompressionFormat) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_TextureCompressionFormat.Unmarshal(m, b)
-}
-func (m *TextureCompressionFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_TextureCompressionFormat.Marshal(b, m, deterministic)
-}
-func (m *TextureCompressionFormat) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_TextureCompressionFormat.Merge(m, src)
-}
-func (m *TextureCompressionFormat) XXX_Size() int {
-	return xxx_messageInfo_TextureCompressionFormat.Size(m)
-}
-func (m *TextureCompressionFormat) XXX_DiscardUnknown() {
-	xxx_messageInfo_TextureCompressionFormat.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TextureCompressionFormat proto.InternalMessageInfo
-
-func (m *TextureCompressionFormat) GetAlias() TextureCompressionFormat_TextureCompressionFormatAlias {
-	if m != nil {
-		return m.Alias
+func (x *TextureCompressionFormat) GetAlias() TextureCompressionFormat_TextureCompressionFormatAlias {
+	if x != nil {
+		return x.Alias
 	}
 	return TextureCompressionFormat_UNSPECIFIED_TEXTURE_COMPRESSION_FORMAT
 }
 
 type Abi struct {
-	Alias                Abi_AbiAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.Abi_AbiAlias" json:"alias,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
-	XXX_unrecognized     []byte       `json:"-"`
-	XXX_sizecache        int32        `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Alias Abi_AbiAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.Abi_AbiAlias" json:"alias,omitempty"`
 }
 
-func (m *Abi) Reset()         { *m = Abi{} }
-func (m *Abi) String() string { return proto.CompactTextString(m) }
-func (*Abi) ProtoMessage()    {}
+func (x *Abi) Reset() {
+	*x = Abi{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Abi) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Abi) ProtoMessage() {}
+
+func (x *Abi) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[11]
+	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 Abi.ProtoReflect.Descriptor instead.
 func (*Abi) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{11}
+	return file_targeting_proto_rawDescGZIP(), []int{11}
 }
 
-func (m *Abi) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Abi.Unmarshal(m, b)
-}
-func (m *Abi) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Abi.Marshal(b, m, deterministic)
-}
-func (m *Abi) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Abi.Merge(m, src)
-}
-func (m *Abi) XXX_Size() int {
-	return xxx_messageInfo_Abi.Size(m)
-}
-func (m *Abi) XXX_DiscardUnknown() {
-	xxx_messageInfo_Abi.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Abi proto.InternalMessageInfo
-
-func (m *Abi) GetAlias() Abi_AbiAlias {
-	if m != nil {
-		return m.Alias
+func (x *Abi) GetAlias() Abi_AbiAlias {
+	if x != nil {
+		return x.Alias
 	}
 	return Abi_UNSPECIFIED_CPU_ARCHITECTURE
 }
 
 type MultiAbi struct {
-	Abi                  []*Abi   `protobuf:"bytes,1,rep,name=abi,proto3" json:"abi,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Abi []*Abi `protobuf:"bytes,1,rep,name=abi,proto3" json:"abi,omitempty"`
 }
 
-func (m *MultiAbi) Reset()         { *m = MultiAbi{} }
-func (m *MultiAbi) String() string { return proto.CompactTextString(m) }
-func (*MultiAbi) ProtoMessage()    {}
+func (x *MultiAbi) Reset() {
+	*x = MultiAbi{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MultiAbi) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MultiAbi) ProtoMessage() {}
+
+func (x *MultiAbi) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[12]
+	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 MultiAbi.ProtoReflect.Descriptor instead.
 func (*MultiAbi) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{12}
+	return file_targeting_proto_rawDescGZIP(), []int{12}
 }
 
-func (m *MultiAbi) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_MultiAbi.Unmarshal(m, b)
-}
-func (m *MultiAbi) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_MultiAbi.Marshal(b, m, deterministic)
-}
-func (m *MultiAbi) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_MultiAbi.Merge(m, src)
-}
-func (m *MultiAbi) XXX_Size() int {
-	return xxx_messageInfo_MultiAbi.Size(m)
-}
-func (m *MultiAbi) XXX_DiscardUnknown() {
-	xxx_messageInfo_MultiAbi.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_MultiAbi proto.InternalMessageInfo
-
-func (m *MultiAbi) GetAbi() []*Abi {
-	if m != nil {
-		return m.Abi
+func (x *MultiAbi) GetAbi() []*Abi {
+	if x != nil {
+		return x.Abi
 	}
 	return nil
 }
 
 type Sanitizer struct {
-	Alias                Sanitizer_SanitizerAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.Sanitizer_SanitizerAlias" json:"alias,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                 `json:"-"`
-	XXX_unrecognized     []byte                   `json:"-"`
-	XXX_sizecache        int32                    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Alias Sanitizer_SanitizerAlias `protobuf:"varint,1,opt,name=alias,proto3,enum=android.bundle.Sanitizer_SanitizerAlias" json:"alias,omitempty"`
 }
 
-func (m *Sanitizer) Reset()         { *m = Sanitizer{} }
-func (m *Sanitizer) String() string { return proto.CompactTextString(m) }
-func (*Sanitizer) ProtoMessage()    {}
+func (x *Sanitizer) Reset() {
+	*x = Sanitizer{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[13]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Sanitizer) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Sanitizer) ProtoMessage() {}
+
+func (x *Sanitizer) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[13]
+	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 Sanitizer.ProtoReflect.Descriptor instead.
 func (*Sanitizer) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{13}
+	return file_targeting_proto_rawDescGZIP(), []int{13}
 }
 
-func (m *Sanitizer) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Sanitizer.Unmarshal(m, b)
-}
-func (m *Sanitizer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Sanitizer.Marshal(b, m, deterministic)
-}
-func (m *Sanitizer) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Sanitizer.Merge(m, src)
-}
-func (m *Sanitizer) XXX_Size() int {
-	return xxx_messageInfo_Sanitizer.Size(m)
-}
-func (m *Sanitizer) XXX_DiscardUnknown() {
-	xxx_messageInfo_Sanitizer.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Sanitizer proto.InternalMessageInfo
-
-func (m *Sanitizer) GetAlias() Sanitizer_SanitizerAlias {
-	if m != nil {
-		return m.Alias
+func (x *Sanitizer) GetAlias() Sanitizer_SanitizerAlias {
+	if x != nil {
+		return x.Alias
 	}
 	return Sanitizer_NONE
 }
 
 type DeviceFeature struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	FeatureName string `protobuf:"bytes,1,opt,name=feature_name,json=featureName,proto3" json:"feature_name,omitempty"`
 	// Equivalent of android:glEsVersion or android:version in <uses-feature>.
-	FeatureVersion       int32    `protobuf:"varint,2,opt,name=feature_version,json=featureVersion,proto3" json:"feature_version,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	FeatureVersion int32 `protobuf:"varint,2,opt,name=feature_version,json=featureVersion,proto3" json:"feature_version,omitempty"`
 }
 
-func (m *DeviceFeature) Reset()         { *m = DeviceFeature{} }
-func (m *DeviceFeature) String() string { return proto.CompactTextString(m) }
-func (*DeviceFeature) ProtoMessage()    {}
+func (x *DeviceFeature) Reset() {
+	*x = DeviceFeature{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[14]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DeviceFeature) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeviceFeature) ProtoMessage() {}
+
+func (x *DeviceFeature) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[14]
+	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 DeviceFeature.ProtoReflect.Descriptor instead.
 func (*DeviceFeature) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{14}
+	return file_targeting_proto_rawDescGZIP(), []int{14}
 }
 
-func (m *DeviceFeature) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_DeviceFeature.Unmarshal(m, b)
-}
-func (m *DeviceFeature) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_DeviceFeature.Marshal(b, m, deterministic)
-}
-func (m *DeviceFeature) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_DeviceFeature.Merge(m, src)
-}
-func (m *DeviceFeature) XXX_Size() int {
-	return xxx_messageInfo_DeviceFeature.Size(m)
-}
-func (m *DeviceFeature) XXX_DiscardUnknown() {
-	xxx_messageInfo_DeviceFeature.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_DeviceFeature proto.InternalMessageInfo
-
-func (m *DeviceFeature) GetFeatureName() string {
-	if m != nil {
-		return m.FeatureName
+func (x *DeviceFeature) GetFeatureName() string {
+	if x != nil {
+		return x.FeatureName
 	}
 	return ""
 }
 
-func (m *DeviceFeature) GetFeatureVersion() int32 {
-	if m != nil {
-		return m.FeatureVersion
+func (x *DeviceFeature) GetFeatureVersion() int32 {
+	if x != nil {
+		return x.FeatureVersion
 	}
 	return 0
 }
 
 // Targeting specific for directories under assets/.
 type AssetsDirectoryTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Abi                      *AbiTargeting                      `protobuf:"bytes,1,opt,name=abi,proto3" json:"abi,omitempty"`
 	GraphicsApi              *GraphicsApiTargeting              `protobuf:"bytes,2,opt,name=graphics_api,json=graphicsApi,proto3" json:"graphics_api,omitempty"`
 	TextureCompressionFormat *TextureCompressionFormatTargeting `protobuf:"bytes,3,opt,name=texture_compression_format,json=textureCompressionFormat,proto3" json:"texture_compression_format,omitempty"`
 	Language                 *LanguageTargeting                 `protobuf:"bytes,4,opt,name=language,proto3" json:"language,omitempty"`
-	XXX_NoUnkeyedLiteral     struct{}                           `json:"-"`
-	XXX_unrecognized         []byte                             `json:"-"`
-	XXX_sizecache            int32                              `json:"-"`
 }
 
-func (m *AssetsDirectoryTargeting) Reset()         { *m = AssetsDirectoryTargeting{} }
-func (m *AssetsDirectoryTargeting) String() string { return proto.CompactTextString(m) }
-func (*AssetsDirectoryTargeting) ProtoMessage()    {}
+func (x *AssetsDirectoryTargeting) Reset() {
+	*x = AssetsDirectoryTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[15]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AssetsDirectoryTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AssetsDirectoryTargeting) ProtoMessage() {}
+
+func (x *AssetsDirectoryTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[15]
+	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 AssetsDirectoryTargeting.ProtoReflect.Descriptor instead.
 func (*AssetsDirectoryTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{15}
+	return file_targeting_proto_rawDescGZIP(), []int{15}
 }
 
-func (m *AssetsDirectoryTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_AssetsDirectoryTargeting.Unmarshal(m, b)
-}
-func (m *AssetsDirectoryTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_AssetsDirectoryTargeting.Marshal(b, m, deterministic)
-}
-func (m *AssetsDirectoryTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_AssetsDirectoryTargeting.Merge(m, src)
-}
-func (m *AssetsDirectoryTargeting) XXX_Size() int {
-	return xxx_messageInfo_AssetsDirectoryTargeting.Size(m)
-}
-func (m *AssetsDirectoryTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_AssetsDirectoryTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AssetsDirectoryTargeting proto.InternalMessageInfo
-
-func (m *AssetsDirectoryTargeting) GetAbi() *AbiTargeting {
-	if m != nil {
-		return m.Abi
+func (x *AssetsDirectoryTargeting) GetAbi() *AbiTargeting {
+	if x != nil {
+		return x.Abi
 	}
 	return nil
 }
 
-func (m *AssetsDirectoryTargeting) GetGraphicsApi() *GraphicsApiTargeting {
-	if m != nil {
-		return m.GraphicsApi
+func (x *AssetsDirectoryTargeting) GetGraphicsApi() *GraphicsApiTargeting {
+	if x != nil {
+		return x.GraphicsApi
 	}
 	return nil
 }
 
-func (m *AssetsDirectoryTargeting) GetTextureCompressionFormat() *TextureCompressionFormatTargeting {
-	if m != nil {
-		return m.TextureCompressionFormat
+func (x *AssetsDirectoryTargeting) GetTextureCompressionFormat() *TextureCompressionFormatTargeting {
+	if x != nil {
+		return x.TextureCompressionFormat
 	}
 	return nil
 }
 
-func (m *AssetsDirectoryTargeting) GetLanguage() *LanguageTargeting {
-	if m != nil {
-		return m.Language
+func (x *AssetsDirectoryTargeting) GetLanguage() *LanguageTargeting {
+	if x != nil {
+		return x.Language
 	}
 	return nil
 }
 
 // Targeting specific for directories under lib/.
 type NativeDirectoryTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Abi                      *Abi                      `protobuf:"bytes,1,opt,name=abi,proto3" json:"abi,omitempty"`
 	GraphicsApi              *GraphicsApi              `protobuf:"bytes,2,opt,name=graphics_api,json=graphicsApi,proto3" json:"graphics_api,omitempty"`
 	TextureCompressionFormat *TextureCompressionFormat `protobuf:"bytes,3,opt,name=texture_compression_format,json=textureCompressionFormat,proto3" json:"texture_compression_format,omitempty"`
 	Sanitizer                *Sanitizer                `protobuf:"bytes,4,opt,name=sanitizer,proto3" json:"sanitizer,omitempty"`
-	XXX_NoUnkeyedLiteral     struct{}                  `json:"-"`
-	XXX_unrecognized         []byte                    `json:"-"`
-	XXX_sizecache            int32                     `json:"-"`
 }
 
-func (m *NativeDirectoryTargeting) Reset()         { *m = NativeDirectoryTargeting{} }
-func (m *NativeDirectoryTargeting) String() string { return proto.CompactTextString(m) }
-func (*NativeDirectoryTargeting) ProtoMessage()    {}
+func (x *NativeDirectoryTargeting) Reset() {
+	*x = NativeDirectoryTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[16]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *NativeDirectoryTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NativeDirectoryTargeting) ProtoMessage() {}
+
+func (x *NativeDirectoryTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[16]
+	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 NativeDirectoryTargeting.ProtoReflect.Descriptor instead.
 func (*NativeDirectoryTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{16}
+	return file_targeting_proto_rawDescGZIP(), []int{16}
 }
 
-func (m *NativeDirectoryTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_NativeDirectoryTargeting.Unmarshal(m, b)
-}
-func (m *NativeDirectoryTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_NativeDirectoryTargeting.Marshal(b, m, deterministic)
-}
-func (m *NativeDirectoryTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_NativeDirectoryTargeting.Merge(m, src)
-}
-func (m *NativeDirectoryTargeting) XXX_Size() int {
-	return xxx_messageInfo_NativeDirectoryTargeting.Size(m)
-}
-func (m *NativeDirectoryTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_NativeDirectoryTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_NativeDirectoryTargeting proto.InternalMessageInfo
-
-func (m *NativeDirectoryTargeting) GetAbi() *Abi {
-	if m != nil {
-		return m.Abi
+func (x *NativeDirectoryTargeting) GetAbi() *Abi {
+	if x != nil {
+		return x.Abi
 	}
 	return nil
 }
 
-func (m *NativeDirectoryTargeting) GetGraphicsApi() *GraphicsApi {
-	if m != nil {
-		return m.GraphicsApi
+func (x *NativeDirectoryTargeting) GetGraphicsApi() *GraphicsApi {
+	if x != nil {
+		return x.GraphicsApi
 	}
 	return nil
 }
 
-func (m *NativeDirectoryTargeting) GetTextureCompressionFormat() *TextureCompressionFormat {
-	if m != nil {
-		return m.TextureCompressionFormat
+func (x *NativeDirectoryTargeting) GetTextureCompressionFormat() *TextureCompressionFormat {
+	if x != nil {
+		return x.TextureCompressionFormat
 	}
 	return nil
 }
 
-func (m *NativeDirectoryTargeting) GetSanitizer() *Sanitizer {
-	if m != nil {
-		return m.Sanitizer
+func (x *NativeDirectoryTargeting) GetSanitizer() *Sanitizer {
+	if x != nil {
+		return x.Sanitizer
 	}
 	return nil
 }
 
 // Targeting specific for image files under apex/.
 type ApexImageTargeting struct {
-	MultiAbi             *MultiAbiTargeting `protobuf:"bytes,1,opt,name=multi_abi,json=multiAbi,proto3" json:"multi_abi,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}           `json:"-"`
-	XXX_unrecognized     []byte             `json:"-"`
-	XXX_sizecache        int32              `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MultiAbi *MultiAbiTargeting `protobuf:"bytes,1,opt,name=multi_abi,json=multiAbi,proto3" json:"multi_abi,omitempty"`
 }
 
-func (m *ApexImageTargeting) Reset()         { *m = ApexImageTargeting{} }
-func (m *ApexImageTargeting) String() string { return proto.CompactTextString(m) }
-func (*ApexImageTargeting) ProtoMessage()    {}
+func (x *ApexImageTargeting) Reset() {
+	*x = ApexImageTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[17]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ApexImageTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ApexImageTargeting) ProtoMessage() {}
+
+func (x *ApexImageTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[17]
+	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 ApexImageTargeting.ProtoReflect.Descriptor instead.
 func (*ApexImageTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{17}
+	return file_targeting_proto_rawDescGZIP(), []int{17}
 }
 
-func (m *ApexImageTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ApexImageTargeting.Unmarshal(m, b)
-}
-func (m *ApexImageTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ApexImageTargeting.Marshal(b, m, deterministic)
-}
-func (m *ApexImageTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ApexImageTargeting.Merge(m, src)
-}
-func (m *ApexImageTargeting) XXX_Size() int {
-	return xxx_messageInfo_ApexImageTargeting.Size(m)
-}
-func (m *ApexImageTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_ApexImageTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ApexImageTargeting proto.InternalMessageInfo
-
-func (m *ApexImageTargeting) GetMultiAbi() *MultiAbiTargeting {
-	if m != nil {
-		return m.MultiAbi
+func (x *ApexImageTargeting) GetMultiAbi() *MultiAbiTargeting {
+	if x != nil {
+		return x.MultiAbi
 	}
 	return nil
 }
 
 type AbiTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*Abi `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*Abi   `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Alternatives []*Abi `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *AbiTargeting) Reset()         { *m = AbiTargeting{} }
-func (m *AbiTargeting) String() string { return proto.CompactTextString(m) }
-func (*AbiTargeting) ProtoMessage()    {}
+func (x *AbiTargeting) Reset() {
+	*x = AbiTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[18]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AbiTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AbiTargeting) ProtoMessage() {}
+
+func (x *AbiTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[18]
+	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 AbiTargeting.ProtoReflect.Descriptor instead.
 func (*AbiTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{18}
+	return file_targeting_proto_rawDescGZIP(), []int{18}
 }
 
-func (m *AbiTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_AbiTargeting.Unmarshal(m, b)
-}
-func (m *AbiTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_AbiTargeting.Marshal(b, m, deterministic)
-}
-func (m *AbiTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_AbiTargeting.Merge(m, src)
-}
-func (m *AbiTargeting) XXX_Size() int {
-	return xxx_messageInfo_AbiTargeting.Size(m)
-}
-func (m *AbiTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_AbiTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AbiTargeting proto.InternalMessageInfo
-
-func (m *AbiTargeting) GetValue() []*Abi {
-	if m != nil {
-		return m.Value
+func (x *AbiTargeting) GetValue() []*Abi {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *AbiTargeting) GetAlternatives() []*Abi {
-	if m != nil {
-		return m.Alternatives
+func (x *AbiTargeting) GetAlternatives() []*Abi {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type MultiAbiTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*MultiAbi `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*MultiAbi `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
-	XXX_unrecognized     []byte      `json:"-"`
-	XXX_sizecache        int32       `json:"-"`
+	Alternatives []*MultiAbi `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *MultiAbiTargeting) Reset()         { *m = MultiAbiTargeting{} }
-func (m *MultiAbiTargeting) String() string { return proto.CompactTextString(m) }
-func (*MultiAbiTargeting) ProtoMessage()    {}
+func (x *MultiAbiTargeting) Reset() {
+	*x = MultiAbiTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[19]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MultiAbiTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MultiAbiTargeting) ProtoMessage() {}
+
+func (x *MultiAbiTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[19]
+	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 MultiAbiTargeting.ProtoReflect.Descriptor instead.
 func (*MultiAbiTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{19}
+	return file_targeting_proto_rawDescGZIP(), []int{19}
 }
 
-func (m *MultiAbiTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_MultiAbiTargeting.Unmarshal(m, b)
-}
-func (m *MultiAbiTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_MultiAbiTargeting.Marshal(b, m, deterministic)
-}
-func (m *MultiAbiTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_MultiAbiTargeting.Merge(m, src)
-}
-func (m *MultiAbiTargeting) XXX_Size() int {
-	return xxx_messageInfo_MultiAbiTargeting.Size(m)
-}
-func (m *MultiAbiTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_MultiAbiTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_MultiAbiTargeting proto.InternalMessageInfo
-
-func (m *MultiAbiTargeting) GetValue() []*MultiAbi {
-	if m != nil {
-		return m.Value
+func (x *MultiAbiTargeting) GetValue() []*MultiAbi {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *MultiAbiTargeting) GetAlternatives() []*MultiAbi {
-	if m != nil {
-		return m.Alternatives
+func (x *MultiAbiTargeting) GetAlternatives() []*MultiAbi {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type ScreenDensityTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*ScreenDensity `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*ScreenDensity `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
-	XXX_unrecognized     []byte           `json:"-"`
-	XXX_sizecache        int32            `json:"-"`
+	Alternatives []*ScreenDensity `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *ScreenDensityTargeting) Reset()         { *m = ScreenDensityTargeting{} }
-func (m *ScreenDensityTargeting) String() string { return proto.CompactTextString(m) }
-func (*ScreenDensityTargeting) ProtoMessage()    {}
+func (x *ScreenDensityTargeting) Reset() {
+	*x = ScreenDensityTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[20]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ScreenDensityTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ScreenDensityTargeting) ProtoMessage() {}
+
+func (x *ScreenDensityTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[20]
+	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 ScreenDensityTargeting.ProtoReflect.Descriptor instead.
 func (*ScreenDensityTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{20}
+	return file_targeting_proto_rawDescGZIP(), []int{20}
 }
 
-func (m *ScreenDensityTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ScreenDensityTargeting.Unmarshal(m, b)
-}
-func (m *ScreenDensityTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ScreenDensityTargeting.Marshal(b, m, deterministic)
-}
-func (m *ScreenDensityTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ScreenDensityTargeting.Merge(m, src)
-}
-func (m *ScreenDensityTargeting) XXX_Size() int {
-	return xxx_messageInfo_ScreenDensityTargeting.Size(m)
-}
-func (m *ScreenDensityTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_ScreenDensityTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ScreenDensityTargeting proto.InternalMessageInfo
-
-func (m *ScreenDensityTargeting) GetValue() []*ScreenDensity {
-	if m != nil {
-		return m.Value
+func (x *ScreenDensityTargeting) GetValue() []*ScreenDensity {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *ScreenDensityTargeting) GetAlternatives() []*ScreenDensity {
-	if m != nil {
-		return m.Alternatives
+func (x *ScreenDensityTargeting) GetAlternatives() []*ScreenDensity {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type LanguageTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// ISO-639: 2 or 3 letter language code.
 	Value []string `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []string `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Alternatives []string `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *LanguageTargeting) Reset()         { *m = LanguageTargeting{} }
-func (m *LanguageTargeting) String() string { return proto.CompactTextString(m) }
-func (*LanguageTargeting) ProtoMessage()    {}
+func (x *LanguageTargeting) Reset() {
+	*x = LanguageTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[21]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LanguageTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LanguageTargeting) ProtoMessage() {}
+
+func (x *LanguageTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[21]
+	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 LanguageTargeting.ProtoReflect.Descriptor instead.
 func (*LanguageTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{21}
+	return file_targeting_proto_rawDescGZIP(), []int{21}
 }
 
-func (m *LanguageTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_LanguageTargeting.Unmarshal(m, b)
-}
-func (m *LanguageTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_LanguageTargeting.Marshal(b, m, deterministic)
-}
-func (m *LanguageTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_LanguageTargeting.Merge(m, src)
-}
-func (m *LanguageTargeting) XXX_Size() int {
-	return xxx_messageInfo_LanguageTargeting.Size(m)
-}
-func (m *LanguageTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_LanguageTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_LanguageTargeting proto.InternalMessageInfo
-
-func (m *LanguageTargeting) GetValue() []string {
-	if m != nil {
-		return m.Value
+func (x *LanguageTargeting) GetValue() []string {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *LanguageTargeting) GetAlternatives() []string {
-	if m != nil {
-		return m.Alternatives
+func (x *LanguageTargeting) GetAlternatives() []string {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type GraphicsApiTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*GraphicsApi `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*GraphicsApi `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
-	XXX_unrecognized     []byte         `json:"-"`
-	XXX_sizecache        int32          `json:"-"`
+	Alternatives []*GraphicsApi `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *GraphicsApiTargeting) Reset()         { *m = GraphicsApiTargeting{} }
-func (m *GraphicsApiTargeting) String() string { return proto.CompactTextString(m) }
-func (*GraphicsApiTargeting) ProtoMessage()    {}
+func (x *GraphicsApiTargeting) Reset() {
+	*x = GraphicsApiTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[22]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GraphicsApiTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GraphicsApiTargeting) ProtoMessage() {}
+
+func (x *GraphicsApiTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[22]
+	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 GraphicsApiTargeting.ProtoReflect.Descriptor instead.
 func (*GraphicsApiTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{22}
+	return file_targeting_proto_rawDescGZIP(), []int{22}
 }
 
-func (m *GraphicsApiTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_GraphicsApiTargeting.Unmarshal(m, b)
-}
-func (m *GraphicsApiTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_GraphicsApiTargeting.Marshal(b, m, deterministic)
-}
-func (m *GraphicsApiTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_GraphicsApiTargeting.Merge(m, src)
-}
-func (m *GraphicsApiTargeting) XXX_Size() int {
-	return xxx_messageInfo_GraphicsApiTargeting.Size(m)
-}
-func (m *GraphicsApiTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_GraphicsApiTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_GraphicsApiTargeting proto.InternalMessageInfo
-
-func (m *GraphicsApiTargeting) GetValue() []*GraphicsApi {
-	if m != nil {
-		return m.Value
+func (x *GraphicsApiTargeting) GetValue() []*GraphicsApi {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *GraphicsApiTargeting) GetAlternatives() []*GraphicsApi {
-	if m != nil {
-		return m.Alternatives
+func (x *GraphicsApiTargeting) GetAlternatives() []*GraphicsApi {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type SdkVersionTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*SdkVersion `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*SdkVersion `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
-	XXX_unrecognized     []byte        `json:"-"`
-	XXX_sizecache        int32         `json:"-"`
+	Alternatives []*SdkVersion `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *SdkVersionTargeting) Reset()         { *m = SdkVersionTargeting{} }
-func (m *SdkVersionTargeting) String() string { return proto.CompactTextString(m) }
-func (*SdkVersionTargeting) ProtoMessage()    {}
+func (x *SdkVersionTargeting) Reset() {
+	*x = SdkVersionTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[23]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SdkVersionTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SdkVersionTargeting) ProtoMessage() {}
+
+func (x *SdkVersionTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[23]
+	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 SdkVersionTargeting.ProtoReflect.Descriptor instead.
 func (*SdkVersionTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{23}
+	return file_targeting_proto_rawDescGZIP(), []int{23}
 }
 
-func (m *SdkVersionTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SdkVersionTargeting.Unmarshal(m, b)
-}
-func (m *SdkVersionTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SdkVersionTargeting.Marshal(b, m, deterministic)
-}
-func (m *SdkVersionTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SdkVersionTargeting.Merge(m, src)
-}
-func (m *SdkVersionTargeting) XXX_Size() int {
-	return xxx_messageInfo_SdkVersionTargeting.Size(m)
-}
-func (m *SdkVersionTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_SdkVersionTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SdkVersionTargeting proto.InternalMessageInfo
-
-func (m *SdkVersionTargeting) GetValue() []*SdkVersion {
-	if m != nil {
-		return m.Value
+func (x *SdkVersionTargeting) GetValue() []*SdkVersion {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *SdkVersionTargeting) GetAlternatives() []*SdkVersion {
-	if m != nil {
-		return m.Alternatives
+func (x *SdkVersionTargeting) GetAlternatives() []*SdkVersion {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type TextureCompressionFormatTargeting struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	Value []*TextureCompressionFormat `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 	// Targeting of other sibling directories that were in the Bundle.
 	// For master splits this is targeting of other master splits.
-	Alternatives         []*TextureCompressionFormat `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                    `json:"-"`
-	XXX_unrecognized     []byte                      `json:"-"`
-	XXX_sizecache        int32                       `json:"-"`
+	Alternatives []*TextureCompressionFormat `protobuf:"bytes,2,rep,name=alternatives,proto3" json:"alternatives,omitempty"`
 }
 
-func (m *TextureCompressionFormatTargeting) Reset()         { *m = TextureCompressionFormatTargeting{} }
-func (m *TextureCompressionFormatTargeting) String() string { return proto.CompactTextString(m) }
-func (*TextureCompressionFormatTargeting) ProtoMessage()    {}
+func (x *TextureCompressionFormatTargeting) Reset() {
+	*x = TextureCompressionFormatTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[24]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TextureCompressionFormatTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TextureCompressionFormatTargeting) ProtoMessage() {}
+
+func (x *TextureCompressionFormatTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[24]
+	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 TextureCompressionFormatTargeting.ProtoReflect.Descriptor instead.
 func (*TextureCompressionFormatTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{24}
+	return file_targeting_proto_rawDescGZIP(), []int{24}
 }
 
-func (m *TextureCompressionFormatTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_TextureCompressionFormatTargeting.Unmarshal(m, b)
-}
-func (m *TextureCompressionFormatTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_TextureCompressionFormatTargeting.Marshal(b, m, deterministic)
-}
-func (m *TextureCompressionFormatTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_TextureCompressionFormatTargeting.Merge(m, src)
-}
-func (m *TextureCompressionFormatTargeting) XXX_Size() int {
-	return xxx_messageInfo_TextureCompressionFormatTargeting.Size(m)
-}
-func (m *TextureCompressionFormatTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_TextureCompressionFormatTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_TextureCompressionFormatTargeting proto.InternalMessageInfo
-
-func (m *TextureCompressionFormatTargeting) GetValue() []*TextureCompressionFormat {
-	if m != nil {
-		return m.Value
+func (x *TextureCompressionFormatTargeting) GetValue() []*TextureCompressionFormat {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
 
-func (m *TextureCompressionFormatTargeting) GetAlternatives() []*TextureCompressionFormat {
-	if m != nil {
-		return m.Alternatives
+func (x *TextureCompressionFormatTargeting) GetAlternatives() []*TextureCompressionFormat {
+	if x != nil {
+		return x.Alternatives
 	}
 	return nil
 }
 
 type SanitizerTargeting struct {
-	Value                []*Sanitizer `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
-	XXX_unrecognized     []byte       `json:"-"`
-	XXX_sizecache        int32        `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Value []*Sanitizer `protobuf:"bytes,1,rep,name=value,proto3" json:"value,omitempty"`
 }
 
-func (m *SanitizerTargeting) Reset()         { *m = SanitizerTargeting{} }
-func (m *SanitizerTargeting) String() string { return proto.CompactTextString(m) }
-func (*SanitizerTargeting) ProtoMessage()    {}
+func (x *SanitizerTargeting) Reset() {
+	*x = SanitizerTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[25]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SanitizerTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SanitizerTargeting) ProtoMessage() {}
+
+func (x *SanitizerTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[25]
+	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 SanitizerTargeting.ProtoReflect.Descriptor instead.
 func (*SanitizerTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{25}
+	return file_targeting_proto_rawDescGZIP(), []int{25}
 }
 
-func (m *SanitizerTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SanitizerTargeting.Unmarshal(m, b)
-}
-func (m *SanitizerTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SanitizerTargeting.Marshal(b, m, deterministic)
-}
-func (m *SanitizerTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SanitizerTargeting.Merge(m, src)
-}
-func (m *SanitizerTargeting) XXX_Size() int {
-	return xxx_messageInfo_SanitizerTargeting.Size(m)
-}
-func (m *SanitizerTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_SanitizerTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SanitizerTargeting proto.InternalMessageInfo
-
-func (m *SanitizerTargeting) GetValue() []*Sanitizer {
-	if m != nil {
-		return m.Value
+func (x *SanitizerTargeting) GetValue() []*Sanitizer {
+	if x != nil {
+		return x.Value
 	}
 	return nil
 }
@@ -1559,176 +1842,829 @@
 // the DeviceFeatureTargeting represents only one device feature to retain
 // that convention.
 type DeviceFeatureTargeting struct {
-	RequiredFeature      *DeviceFeature `protobuf:"bytes,1,opt,name=required_feature,json=requiredFeature,proto3" json:"required_feature,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
-	XXX_unrecognized     []byte         `json:"-"`
-	XXX_sizecache        int32          `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	RequiredFeature *DeviceFeature `protobuf:"bytes,1,opt,name=required_feature,json=requiredFeature,proto3" json:"required_feature,omitempty"`
 }
 
-func (m *DeviceFeatureTargeting) Reset()         { *m = DeviceFeatureTargeting{} }
-func (m *DeviceFeatureTargeting) String() string { return proto.CompactTextString(m) }
-func (*DeviceFeatureTargeting) ProtoMessage()    {}
+func (x *DeviceFeatureTargeting) Reset() {
+	*x = DeviceFeatureTargeting{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_targeting_proto_msgTypes[26]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DeviceFeatureTargeting) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeviceFeatureTargeting) ProtoMessage() {}
+
+func (x *DeviceFeatureTargeting) ProtoReflect() protoreflect.Message {
+	mi := &file_targeting_proto_msgTypes[26]
+	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 DeviceFeatureTargeting.ProtoReflect.Descriptor instead.
 func (*DeviceFeatureTargeting) Descriptor() ([]byte, []int) {
-	return fileDescriptor_df45b505afdf471e, []int{26}
+	return file_targeting_proto_rawDescGZIP(), []int{26}
 }
 
-func (m *DeviceFeatureTargeting) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_DeviceFeatureTargeting.Unmarshal(m, b)
-}
-func (m *DeviceFeatureTargeting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_DeviceFeatureTargeting.Marshal(b, m, deterministic)
-}
-func (m *DeviceFeatureTargeting) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_DeviceFeatureTargeting.Merge(m, src)
-}
-func (m *DeviceFeatureTargeting) XXX_Size() int {
-	return xxx_messageInfo_DeviceFeatureTargeting.Size(m)
-}
-func (m *DeviceFeatureTargeting) XXX_DiscardUnknown() {
-	xxx_messageInfo_DeviceFeatureTargeting.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_DeviceFeatureTargeting proto.InternalMessageInfo
-
-func (m *DeviceFeatureTargeting) GetRequiredFeature() *DeviceFeature {
-	if m != nil {
-		return m.RequiredFeature
+func (x *DeviceFeatureTargeting) GetRequiredFeature() *DeviceFeature {
+	if x != nil {
+		return x.RequiredFeature
 	}
 	return nil
 }
 
-func init() {
-	proto.RegisterEnum("android.bundle.ScreenDensity_DensityAlias", ScreenDensity_DensityAlias_name, ScreenDensity_DensityAlias_value)
-	proto.RegisterEnum("android.bundle.TextureCompressionFormat_TextureCompressionFormatAlias", TextureCompressionFormat_TextureCompressionFormatAlias_name, TextureCompressionFormat_TextureCompressionFormatAlias_value)
-	proto.RegisterEnum("android.bundle.Abi_AbiAlias", Abi_AbiAlias_name, Abi_AbiAlias_value)
-	proto.RegisterEnum("android.bundle.Sanitizer_SanitizerAlias", Sanitizer_SanitizerAlias_name, Sanitizer_SanitizerAlias_value)
-	proto.RegisterType((*VariantTargeting)(nil), "android.bundle.VariantTargeting")
-	proto.RegisterType((*ApkTargeting)(nil), "android.bundle.ApkTargeting")
-	proto.RegisterType((*ModuleTargeting)(nil), "android.bundle.ModuleTargeting")
-	proto.RegisterType((*UserCountriesTargeting)(nil), "android.bundle.UserCountriesTargeting")
-	proto.RegisterType((*ScreenDensity)(nil), "android.bundle.ScreenDensity")
-	proto.RegisterType((*Int32Value)(nil), "android.bundle.Int32Value")
-	proto.RegisterType((*SdkVersion)(nil), "android.bundle.SdkVersion")
-	proto.RegisterType((*GraphicsApi)(nil), "android.bundle.GraphicsApi")
-	proto.RegisterType((*VulkanVersion)(nil), "android.bundle.VulkanVersion")
-	proto.RegisterType((*OpenGlVersion)(nil), "android.bundle.OpenGlVersion")
-	proto.RegisterType((*TextureCompressionFormat)(nil), "android.bundle.TextureCompressionFormat")
-	proto.RegisterType((*Abi)(nil), "android.bundle.Abi")
-	proto.RegisterType((*MultiAbi)(nil), "android.bundle.MultiAbi")
-	proto.RegisterType((*Sanitizer)(nil), "android.bundle.Sanitizer")
-	proto.RegisterType((*DeviceFeature)(nil), "android.bundle.DeviceFeature")
-	proto.RegisterType((*AssetsDirectoryTargeting)(nil), "android.bundle.AssetsDirectoryTargeting")
-	proto.RegisterType((*NativeDirectoryTargeting)(nil), "android.bundle.NativeDirectoryTargeting")
-	proto.RegisterType((*ApexImageTargeting)(nil), "android.bundle.ApexImageTargeting")
-	proto.RegisterType((*AbiTargeting)(nil), "android.bundle.AbiTargeting")
-	proto.RegisterType((*MultiAbiTargeting)(nil), "android.bundle.MultiAbiTargeting")
-	proto.RegisterType((*ScreenDensityTargeting)(nil), "android.bundle.ScreenDensityTargeting")
-	proto.RegisterType((*LanguageTargeting)(nil), "android.bundle.LanguageTargeting")
-	proto.RegisterType((*GraphicsApiTargeting)(nil), "android.bundle.GraphicsApiTargeting")
-	proto.RegisterType((*SdkVersionTargeting)(nil), "android.bundle.SdkVersionTargeting")
-	proto.RegisterType((*TextureCompressionFormatTargeting)(nil), "android.bundle.TextureCompressionFormatTargeting")
-	proto.RegisterType((*SanitizerTargeting)(nil), "android.bundle.SanitizerTargeting")
-	proto.RegisterType((*DeviceFeatureTargeting)(nil), "android.bundle.DeviceFeatureTargeting")
+var File_targeting_proto protoreflect.FileDescriptor
+
+var file_targeting_proto_rawDesc = []byte{
+	0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x12, 0x0e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x22, 0xe8, 0x03, 0x0a, 0x10, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x57, 0x0a, 0x15, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x13, 0x73, 0x64, 0x6b, 0x56,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12,
+	0x41, 0x0a, 0x0d, 0x61, 0x62, 0x69, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x69, 0x6e, 0x67, 0x52, 0x0c, 0x61, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x12, 0x60, 0x0a, 0x18, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x64, 0x65, 0x6e,
+	0x73, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73,
+	0x69, 0x74, 0x79, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x16, 0x73, 0x63,
+	0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x54, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x69, 0x6e, 0x67, 0x12, 0x51, 0x0a, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x61, 0x62,
+	0x69, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x21, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64,
+	0x6c, 0x65, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x69, 0x6e, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x54, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x82, 0x01, 0x0a, 0x24, 0x74, 0x65, 0x78, 0x74,
+	0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
+	0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43,
+	0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74,
+	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x21, 0x74, 0x65, 0x78, 0x74, 0x75,
+	0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72,
+	0x6d, 0x61, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xe7, 0x05, 0x0a,
+	0x0c, 0x41, 0x70, 0x6b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x41, 0x0a,
+	0x0d, 0x61, 0x62, 0x69, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x52, 0x0c, 0x61, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x12, 0x5a, 0x0a, 0x16, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x61, 0x70, 0x69,
+	0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x24, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x14, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73,
+	0x41, 0x70, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x50, 0x0a, 0x12,
+	0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61,
+	0x67, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x11, 0x6c, 0x61, 0x6e,
+	0x67, 0x75, 0x61, 0x67, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x60,
+	0x0a, 0x18, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x5f, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
+	0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x54,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x16, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e,
+	0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x12, 0x57, 0x0a, 0x15, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f,
+	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x53, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x69, 0x6e, 0x67, 0x52, 0x13, 0x73, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x82, 0x01, 0x0a, 0x24, 0x74, 0x65,
+	0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f,
+	0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72,
+	0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d,
+	0x61, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x21, 0x74, 0x65, 0x78,
+	0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46,
+	0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x51,
+	0x0a, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x61, 0x62, 0x69, 0x5f, 0x74, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x6e,
+	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d, 0x75, 0x6c,
+	0x74, 0x69, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x11,
+	0x6d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e,
+	0x67, 0x12, 0x53, 0x0a, 0x13, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x5f, 0x74,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22,
+	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e,
+	0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x52, 0x12, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x22, 0xae, 0x02, 0x0a, 0x0f, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x57, 0x0a, 0x15, 0x73, 0x64,
+	0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
+	0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x64, 0x6b, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x13,
+	0x73, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
+	0x69, 0x6e, 0x67, 0x12, 0x60, 0x0a, 0x18, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x66, 0x65,
+	0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x18,
+	0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x46, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x16, 0x64,
+	0x65, 0x76, 0x69, 0x63, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x60, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x6f,
+	0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e,
+	0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75,
+	0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52,
+	0x16, 0x75, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x54, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x57, 0x0a, 0x16, 0x55, 0x73, 0x65, 0x72, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e,
+	0x67, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64,
+	0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72,
+	0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,
+	0x22, 0x97, 0x02, 0x0a, 0x0d, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73, 0x69,
+	0x74, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x5f, 0x61, 0x6c,
+	0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65,
+	0x6e, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
+	0x41, 0x6c, 0x69, 0x61, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
+	0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x21, 0x0a, 0x0b, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
+	0x5f, 0x64, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0a, 0x64, 0x65,
+	0x6e, 0x73, 0x69, 0x74, 0x79, 0x44, 0x70, 0x69, 0x22, 0x7f, 0x0a, 0x0c, 0x44, 0x65, 0x6e, 0x73,
+	0x69, 0x74, 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x45, 0x4e, 0x53,
+	0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+	0x00, 0x12, 0x09, 0x0a, 0x05, 0x4e, 0x4f, 0x44, 0x50, 0x49, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04,
+	0x4c, 0x44, 0x50, 0x49, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x44, 0x50, 0x49, 0x10, 0x03,
+	0x12, 0x09, 0x0a, 0x05, 0x54, 0x56, 0x44, 0x50, 0x49, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x48,
+	0x44, 0x50, 0x49, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x58, 0x48, 0x44, 0x50, 0x49, 0x10, 0x06,
+	0x12, 0x0a, 0x0a, 0x06, 0x58, 0x58, 0x48, 0x44, 0x50, 0x49, 0x10, 0x07, 0x12, 0x0b, 0x0a, 0x07,
+	0x58, 0x58, 0x58, 0x48, 0x44, 0x50, 0x49, 0x10, 0x08, 0x42, 0x0f, 0x0a, 0x0d, 0x64, 0x65, 0x6e,
+	0x73, 0x69, 0x74, 0x79, 0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x22, 0x22, 0x0a, 0x0a, 0x49, 0x6e,
+	0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a,
+	0x0a, 0x0a, 0x53, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x03,
+	0x6d, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32,
+	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x6d, 0x69, 0x6e, 0x22, 0xb9, 0x01, 0x0a, 0x0b, 0x47,
+	0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69, 0x12, 0x4e, 0x0a, 0x13, 0x6d, 0x69,
+	0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x67, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x47, 0x6c, 0x56,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x69, 0x6e, 0x4f, 0x70, 0x65,
+	0x6e, 0x47, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x12, 0x6d, 0x69,
+	0x6e, 0x5f, 0x76, 0x75, 0x6c, 0x6b, 0x61, 0x6e, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x56, 0x75, 0x6c, 0x6b, 0x61, 0x6e, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x69, 0x6e, 0x56, 0x75, 0x6c, 0x6b,
+	0x61, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x61, 0x70, 0x69,
+	0x5f, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x22, 0x3b, 0x0a, 0x0d, 0x56, 0x75, 0x6c, 0x6b, 0x61, 0x6e,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a,
+	0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x69,
+	0x6e, 0x6f, 0x72, 0x22, 0x3b, 0x0a, 0x0d, 0x4f, 0x70, 0x65, 0x6e, 0x47, 0x6c, 0x56, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69,
+	0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72,
+	0x22, 0xb7, 0x02, 0x0a, 0x18, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70,
+	0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x5c, 0x0a,
+	0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x46, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65,
+	0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+	0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f,
+	0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x41,
+	0x6c, 0x69, 0x61, 0x73, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x1d,
+	0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x2a, 0x0a,
+	0x26, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x45, 0x58,
+	0x54, 0x55, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x49, 0x4f, 0x4e,
+	0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x41, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x54, 0x43,
+	0x31, 0x5f, 0x52, 0x47, 0x42, 0x38, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x41, 0x4c, 0x45,
+	0x54, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x48, 0x52, 0x45, 0x45, 0x5f,
+	0x44, 0x43, 0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x54, 0x43, 0x10, 0x04, 0x12, 0x08, 0x0a,
+	0x04, 0x4c, 0x41, 0x54, 0x43, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x58, 0x54, 0x31, 0x10,
+	0x06, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x33, 0x54, 0x43, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x50,
+	0x56, 0x52, 0x54, 0x43, 0x10, 0x08, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x53, 0x54, 0x43, 0x10, 0x09,
+	0x12, 0x08, 0x0a, 0x04, 0x45, 0x54, 0x43, 0x32, 0x10, 0x0a, 0x22, 0xc0, 0x01, 0x0a, 0x03, 0x41,
+	0x62, 0x69, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64,
+	0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x2e, 0x41, 0x62, 0x69, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52,
+	0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x08, 0x41, 0x62, 0x69, 0x41, 0x6c,
+	0x69, 0x61, 0x73, 0x12, 0x20, 0x0a, 0x1c, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+	0x45, 0x44, 0x5f, 0x43, 0x50, 0x55, 0x5f, 0x41, 0x52, 0x43, 0x48, 0x49, 0x54, 0x45, 0x43, 0x54,
+	0x55, 0x52, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x52, 0x4d, 0x45, 0x41, 0x42, 0x49,
+	0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x52, 0x4d, 0x45, 0x41, 0x42, 0x49, 0x5f, 0x56, 0x37,
+	0x41, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x52, 0x4d, 0x36, 0x34, 0x5f, 0x56, 0x38, 0x41,
+	0x10, 0x03, 0x12, 0x07, 0x0a, 0x03, 0x58, 0x38, 0x36, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x58,
+	0x38, 0x36, 0x5f, 0x36, 0x34, 0x10, 0x05, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x49, 0x50, 0x53, 0x10,
+	0x06, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x49, 0x50, 0x53, 0x36, 0x34, 0x10, 0x07, 0x22, 0x31, 0x0a,
+	0x08, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x12, 0x25, 0x0a, 0x03, 0x61, 0x62, 0x69,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x52, 0x03, 0x61, 0x62, 0x69,
+	0x22, 0x76, 0x0a, 0x09, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x3e, 0x0a,
+	0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x61,
+	0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65,
+	0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x22, 0x29, 0x0a,
+	0x0e, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12,
+	0x08, 0x0a, 0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x57, 0x41,
+	0x44, 0x44, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x22, 0x5b, 0x0a, 0x0d, 0x44, 0x65, 0x76, 0x69,
+	0x63, 0x65, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61,
+	0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f,
+	0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc3, 0x02, 0x0a, 0x18, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73,
+	0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x03, 0x61, 0x62, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x1c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x03, 0x61,
+	0x62, 0x69, 0x12, 0x47, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x61,
+	0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69,
+	0x63, 0x73, 0x41, 0x70, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x0b,
+	0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69, 0x12, 0x6f, 0x0a, 0x1a, 0x74,
+	0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69,
+	0x6f, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x31, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73,
+	0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69,
+	0x6e, 0x67, 0x52, 0x18, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72,
+	0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x3d, 0x0a, 0x08,
+	0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21,
+	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e,
+	0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e,
+	0x67, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0xa2, 0x02, 0x0a, 0x18,
+	0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x54,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x0a, 0x03, 0x61, 0x62, 0x69, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x52, 0x03, 0x61, 0x62, 0x69, 0x12,
+	0x3e, 0x0a, 0x0c, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x5f, 0x61, 0x70, 0x69, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41,
+	0x70, 0x69, 0x52, 0x0b, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69, 0x12,
+	0x66, 0x0a, 0x1a, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72,
+	0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75,
+	0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70,
+	0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x18, 0x74,
+	0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f,
+	0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x61, 0x6e, 0x69, 0x74,
+	0x69, 0x7a, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x61, 0x6e, 0x69,
+	0x74, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x09, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72,
+	0x22, 0x54, 0x0a, 0x12, 0x41, 0x70, 0x65, 0x78, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f,
+	0x61, 0x62, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69,
+	0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x6d, 0x75,
+	0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x22, 0x72, 0x0a, 0x0c, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x12, 0x37, 0x0a, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x41, 0x62, 0x69, 0x52, 0x0c, 0x61, 0x6c,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x4d,
+	0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67,
+	0x12, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x18, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65,
+	0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x12, 0x3c, 0x0a, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x41, 0x62, 0x69,
+	0x52, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x22, 0x90,
+	0x01, 0x0a, 0x16, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
+	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e,
+	0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x41,
+	0x0a, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x44, 0x65, 0x6e, 0x73,
+	0x69, 0x74, 0x79, 0x52, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65,
+	0x73, 0x22, 0x4d, 0x0a, 0x11, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x54, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0c,
+	0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73,
+	0x22, 0x8a, 0x01, 0x0a, 0x14, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69,
+	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69,
+	0x63, 0x73, 0x41, 0x70, 0x69, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0c,
+	0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x41, 0x70, 0x69, 0x52,
+	0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x22, 0x87, 0x01,
+	0x0a, 0x13, 0x53, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62,
+	0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53, 0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72,
+	0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x53,
+	0x64, 0x6b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72,
+	0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x22, 0xb1, 0x01, 0x0a, 0x21, 0x54, 0x65, 0x78, 0x74,
+	0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f,
+	0x72, 0x6d, 0x61, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3e, 0x0a,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65,
+	0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+	0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a,
+	0x0c, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75,
+	0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6d, 0x70,
+	0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x0c, 0x61,
+	0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x22, 0x45, 0x0a, 0x12, 0x53,
+	0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e,
+	0x67, 0x12, 0x2f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x19, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c,
+	0x65, 0x2e, 0x53, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x22, 0x62, 0x0a, 0x16, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x46, 0x65, 0x61, 0x74,
+	0x75, 0x72, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x48, 0x0a, 0x10,
+	0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x46, 0x65,
+	0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,
+	0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x41, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6e,
+	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x5a, 0x2b, 0x61, 0x6e,
+	0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f,
+	0x65, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x70, 0x6b, 0x73, 0x2f, 0x62, 0x75, 0x6e,
+	0x64, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x33,
 }
 
-func init() {
-	proto.RegisterFile("targeting.proto", fileDescriptor_df45b505afdf471e)
+var (
+	file_targeting_proto_rawDescOnce sync.Once
+	file_targeting_proto_rawDescData = file_targeting_proto_rawDesc
+)
+
+func file_targeting_proto_rawDescGZIP() []byte {
+	file_targeting_proto_rawDescOnce.Do(func() {
+		file_targeting_proto_rawDescData = protoimpl.X.CompressGZIP(file_targeting_proto_rawDescData)
+	})
+	return file_targeting_proto_rawDescData
 }
 
-var fileDescriptor_df45b505afdf471e = []byte{
-	// 1504 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x5b, 0x6f, 0xe3, 0xc4,
-	0x17, 0xaf, 0x93, 0xa6, 0x4d, 0x4e, 0x92, 0xd6, 0x3b, 0xe9, 0xbf, 0xff, 0xb0, 0xec, 0x4a, 0x5b,
-	0xef, 0x85, 0xee, 0x0a, 0x05, 0xda, 0xae, 0xba, 0x15, 0x97, 0x22, 0xd7, 0x71, 0x9b, 0x48, 0x4d,
-	0x9a, 0x75, 0xdc, 0x6c, 0x59, 0x90, 0xbc, 0x4e, 0x3c, 0x0d, 0xa6, 0x89, 0x1d, 0x6c, 0xa7, 0xda,
-	0xe5, 0x05, 0x81, 0x90, 0x90, 0x78, 0xe2, 0x8d, 0x77, 0x3e, 0x00, 0x12, 0x4f, 0x08, 0x89, 0x07,
-	0x24, 0x3e, 0x0c, 0x7c, 0x0c, 0x34, 0xbe, 0x24, 0x9e, 0xc4, 0x4e, 0xb3, 0x2c, 0xf0, 0x50, 0xe5,
-	0xcc, 0xf1, 0x39, 0xbf, 0x73, 0x99, 0x73, 0x66, 0xce, 0x14, 0x56, 0x1d, 0xd5, 0xea, 0x62, 0x47,
-	0x37, 0xba, 0xa5, 0x81, 0x65, 0x3a, 0x26, 0x5a, 0x51, 0x0d, 0xcd, 0x32, 0x75, 0xad, 0xd4, 0x1e,
-	0x1a, 0x5a, 0x0f, 0x73, 0x7f, 0x26, 0x81, 0x6d, 0xa9, 0x96, 0xae, 0x1a, 0x8e, 0x1c, 0x88, 0xa2,
-	0x27, 0xf0, 0x3f, 0x5b, 0xbb, 0x50, 0x2e, 0xb1, 0x65, 0xeb, 0xa6, 0xa1, 0x8c, 0x30, 0x8a, 0xcc,
-	0x2d, 0x66, 0x33, 0xbb, 0x7d, 0xbb, 0x44, 0x83, 0x94, 0x9a, 0xda, 0x45, 0xcb, 0x93, 0x1d, 0x61,
-	0x48, 0x05, 0x7b, 0x9a, 0x89, 0x78, 0xc8, 0xab, 0x6d, 0x3d, 0x04, 0x98, 0x70, 0x01, 0x6f, 0x4c,
-	0x02, 0xf2, 0x6d, 0x7d, 0x8c, 0x94, 0x53, 0x43, 0x2b, 0xf4, 0x0c, 0x8a, 0x76, 0xc7, 0xc2, 0xd8,
-	0x50, 0x34, 0x6c, 0xd8, 0xba, 0xf3, 0x22, 0x84, 0x96, 0x74, 0xd1, 0xee, 0x4d, 0xb9, 0xe7, 0xca,
-	0x97, 0x3d, 0xf1, 0x31, 0xee, 0xba, 0x1d, 0xc9, 0x47, 0x8f, 0xa1, 0xd0, 0x1f, 0xf6, 0x1c, 0x5d,
-	0xa1, 0x5d, 0x5d, 0x74, 0xc1, 0x37, 0x26, 0xc1, 0x6b, 0x44, 0x94, 0xf2, 0xf7, 0x5a, 0x7f, 0x92,
-	0x85, 0xbe, 0x62, 0xe0, 0x8e, 0x83, 0x9f, 0x3b, 0x43, 0x0b, 0x2b, 0x1d, 0xb3, 0x3f, 0xb0, 0xb0,
-	0xed, 0x66, 0xf6, 0xdc, 0xb4, 0xfa, 0xaa, 0x13, 0x32, 0x92, 0x72, 0x8d, 0x6c, 0x4d, 0x1a, 0x91,
-	0x3d, 0x5d, 0x61, 0xac, 0x7a, 0xe8, 0x6a, 0x8e, 0x8d, 0x6e, 0x38, 0x57, 0x89, 0x70, 0x7f, 0xa4,
-	0x20, 0xc7, 0x0f, 0x2e, 0x66, 0xec, 0x06, 0xf3, 0xd2, 0xbb, 0xf1, 0x14, 0xd6, 0xbb, 0x96, 0x3a,
-	0xf8, 0x44, 0xef, 0xd8, 0x8a, 0x3a, 0x98, 0xde, 0xd9, 0x3b, 0x93, 0x58, 0x47, 0xbe, 0x34, 0x3f,
-	0x08, 0x61, 0xae, 0x75, 0x23, 0xb8, 0xa8, 0x01, 0xa8, 0xa7, 0x1a, 0xdd, 0xa1, 0xda, 0xc5, 0x53,
-	0x7b, 0x3c, 0xb5, 0x0d, 0xc7, 0xbe, 0x64, 0x68, 0x1b, 0x7a, 0x93, 0xac, 0x99, 0xb5, 0xb3, 0xf8,
-	0x8f, 0xd4, 0x4e, 0x6c, 0xe7, 0xa4, 0x5e, 0xb1, 0x73, 0xe6, 0xae, 0xa0, 0xa5, 0x7f, 0xaf, 0x82,
-	0xe2, 0x3a, 0x63, 0xf9, 0x15, 0x3a, 0xa3, 0x09, 0x05, 0x5b, 0x35, 0x74, 0x47, 0xff, 0x1c, 0x5b,
-	0x21, 0xc8, 0xb4, 0x0b, 0xc9, 0x4d, 0xa5, 0x2b, 0x10, 0x1d, 0x63, 0x22, 0x7b, 0x8a, 0xc7, 0xfd,
-	0x98, 0x80, 0xd5, 0x9a, 0xa9, 0x0d, 0x7b, 0xf8, 0x3f, 0x38, 0xd3, 0x9e, 0x41, 0x51, 0xc3, 0x97,
-	0x7a, 0x07, 0x2b, 0xe7, 0x58, 0x75, 0xf7, 0x27, 0xdc, 0x04, 0xc9, 0xa8, 0xa2, 0x2a, 0xbb, 0xf2,
-	0x87, 0x9e, 0x78, 0xa8, 0xa8, 0xb4, 0x48, 0x3e, 0xb1, 0x30, 0xb4, 0xb1, 0xa5, 0x74, 0xcc, 0xa1,
-	0xe1, 0x58, 0x3a, 0xb6, 0xaf, 0x3e, 0xf2, 0x4e, 0x6d, 0x6c, 0x09, 0x81, 0x78, 0xc8, 0xc2, 0x30,
-	0x92, 0xcf, 0x3d, 0x81, 0xf5, 0x68, 0x0d, 0x74, 0x1b, 0xf2, 0x9e, 0xd9, 0x17, 0x4a, 0xc7, 0xd4,
-	0xb0, 0x5d, 0x64, 0x6e, 0x25, 0x37, 0x33, 0x52, 0xce, 0x67, 0x0a, 0x84, 0x87, 0x8a, 0xb0, 0x8c,
-	0x9f, 0x77, 0x7a, 0x43, 0x0d, 0xbb, 0x6d, 0x9f, 0x96, 0x82, 0x25, 0xf7, 0x7d, 0x02, 0xf2, 0x54,
-	0x0b, 0xa1, 0xc7, 0x90, 0x0f, 0x9a, 0x4f, 0xed, 0xe9, 0xaa, 0xed, 0xe6, 0x7f, 0x65, 0xfb, 0xc1,
-	0xcc, 0xc6, 0x2b, 0xf9, 0xbf, 0x3c, 0xd1, 0xa8, 0x2c, 0x48, 0x39, 0x2d, 0xb4, 0x46, 0x1b, 0x90,
-	0x0d, 0x20, 0xb5, 0x81, 0xee, 0xba, 0x90, 0xaa, 0x2c, 0x48, 0xe0, 0x33, 0xcb, 0x03, 0x9d, 0xfb,
-	0x02, 0x72, 0x61, 0x08, 0xf4, 0x7f, 0x28, 0x94, 0xc5, 0x7a, 0xb3, 0x2a, 0x7f, 0xa8, 0x9c, 0xd6,
-	0x9b, 0x0d, 0x51, 0xa8, 0x1e, 0x56, 0xc5, 0x32, 0xbb, 0x80, 0x32, 0x90, 0xaa, 0x9f, 0x94, 0x1b,
-	0x55, 0x96, 0x41, 0x69, 0x58, 0x3c, 0x26, 0x54, 0x82, 0x50, 0x35, 0x42, 0x25, 0xc9, 0x67, 0xb9,
-	0x45, 0xc8, 0x45, 0xc2, 0xac, 0x10, 0x2a, 0x45, 0x98, 0x67, 0x2e, 0xb9, 0x84, 0x00, 0x96, 0xce,
-	0x3c, 0x7a, 0x19, 0x65, 0x61, 0xf9, 0xcc, 0x5f, 0xa4, 0x0f, 0x56, 0xc7, 0x61, 0x9b, 0x06, 0x36,
-	0xcf, 0x39, 0x0e, 0xa0, 0x6a, 0x38, 0x3b, 0xdb, 0x2d, 0xb5, 0x37, 0xc4, 0x68, 0x0d, 0x52, 0x97,
-	0x84, 0x70, 0xb3, 0x91, 0x92, 0xbc, 0x05, 0xf7, 0x0e, 0xc0, 0xb8, 0x0c, 0xd1, 0x9b, 0x90, 0xec,
-	0xeb, 0x86, 0x5f, 0xaf, 0xd7, 0x27, 0xf3, 0x35, 0x06, 0x93, 0x88, 0x18, 0xf7, 0x0b, 0x03, 0xd9,
-	0xd0, 0x61, 0x8b, 0xea, 0x50, 0xe8, 0xeb, 0x86, 0x62, 0x0e, 0xb0, 0xa1, 0x74, 0x7b, 0x41, 0x1f,
-	0xf8, 0x68, 0x37, 0x27, 0xd1, 0x4e, 0x06, 0xd8, 0x38, 0xea, 0xf9, 0x96, 0x2b, 0x0b, 0x12, 0xdb,
-	0xd7, 0x0d, 0x8a, 0x87, 0x6a, 0x80, 0x08, 0xde, 0xe5, 0xb0, 0x77, 0xa1, 0x1a, 0x23, 0xb8, 0x44,
-	0x34, 0x5c, 0xcb, 0x95, 0xa2, 0xe1, 0x28, 0xde, 0x41, 0x16, 0x32, 0xe4, 0xfe, 0xf0, 0x72, 0xf3,
-	0x2e, 0xe4, 0xa9, 0xaf, 0x24, 0x3d, 0x7d, 0xf5, 0x53, 0xd3, 0x0a, 0xd2, 0xe3, 0x2e, 0x5c, 0xae,
-	0x6e, 0x98, 0x96, 0xb7, 0xe3, 0x92, 0xb7, 0x20, 0xca, 0xb4, 0xa7, 0x2f, 0xa3, 0xfc, 0x73, 0x02,
-	0x8a, 0x71, 0x47, 0x25, 0xfa, 0x18, 0x52, 0xe1, 0x92, 0x3d, 0x9c, 0xf7, 0x8c, 0x8d, 0xfd, 0xe0,
-	0xd6, 0xa2, 0xe4, 0x81, 0x72, 0xbf, 0x32, 0x70, 0x73, 0xa6, 0x20, 0x7a, 0x00, 0xf7, 0x42, 0xc5,
-	0xaa, 0xc8, 0xe2, 0x99, 0x7c, 0x2a, 0x89, 0x8a, 0x70, 0x52, 0x6b, 0x48, 0x62, 0xb3, 0x59, 0x3d,
-	0xa9, 0x2b, 0x87, 0x27, 0x52, 0x8d, 0x97, 0xd9, 0x05, 0x94, 0x87, 0x8c, 0x28, 0x0b, 0x5b, 0x8a,
-	0x74, 0x74, 0xb0, 0xc7, 0x32, 0x28, 0x07, 0xe9, 0x06, 0x7f, 0x2c, 0xca, 0xb2, 0x58, 0x66, 0x13,
-	0x64, 0x25, 0x57, 0x24, 0x51, 0x54, 0xca, 0x02, 0x9b, 0x44, 0xcb, 0x90, 0xe4, 0x65, 0xc1, 0xab,
-	0xe8, 0x63, 0x42, 0xa5, 0x08, 0x55, 0x3e, 0x93, 0xb7, 0xd8, 0x25, 0x42, 0x35, 0x77, 0x64, 0x81,
-	0x5d, 0x26, 0x55, 0xde, 0x68, 0x49, 0xb2, 0xc0, 0xa6, 0x09, 0x93, 0x6f, 0xca, 0x02, 0x9b, 0x21,
-	0x94, 0x28, 0x0b, 0xdb, 0x2c, 0x70, 0xbf, 0x31, 0x90, 0xe4, 0xdb, 0x3a, 0xda, 0xa6, 0x93, 0x14,
-	0x35, 0x4c, 0x90, 0x3f, 0x2a, 0xf4, 0xaf, 0x19, 0x48, 0x07, 0x3c, 0x74, 0x0b, 0x6e, 0x84, 0xa3,
-	0x14, 0x1a, 0xa7, 0x0a, 0x2f, 0x09, 0x95, 0xaa, 0x2c, 0x0a, 0x24, 0x5c, 0x76, 0x81, 0x34, 0x16,
-	0x2f, 0xd5, 0x44, 0xfe, 0x80, 0x74, 0xe9, 0x2a, 0x64, 0xfd, 0x85, 0xd2, 0x7a, 0xc4, 0xb3, 0x09,
-	0x12, 0x39, 0x2f, 0xd5, 0x76, 0x1f, 0x2a, 0xad, 0x3d, 0xde, 0x8b, 0xee, 0x6c, 0x6f, 0x97, 0x5d,
-	0x74, 0x5b, 0x73, 0x6f, 0x57, 0xd9, 0x7d, 0xe8, 0xc5, 0x57, 0xab, 0x36, 0x9a, 0x5e, 0xc3, 0x12,
-	0x6a, 0xf7, 0x21, 0xbb, 0xcc, 0x6d, 0x41, 0x3a, 0xb8, 0xb3, 0xd0, 0x5d, 0x48, 0xaa, 0x6d, 0xdd,
-	0x3d, 0xed, 0xb2, 0xdb, 0x85, 0x88, 0x20, 0x24, 0xf2, 0x9d, 0xbb, 0x84, 0xcc, 0xe8, 0x4e, 0x42,
-	0xfb, 0x74, 0xe8, 0x9b, 0xb1, 0xb7, 0xd7, 0x98, 0xa2, 0xd2, 0x70, 0x1f, 0x56, 0xe8, 0x0f, 0xc4,
-	0xcf, 0xfa, 0x49, 0x5d, 0xf4, 0xf6, 0xb3, 0xf2, 0x84, 0x2f, 0x97, 0xc9, 0x46, 0xb3, 0x0c, 0xf7,
-	0x11, 0xe4, 0xa9, 0x4b, 0x04, 0x6d, 0x40, 0x2e, 0xb8, 0x7e, 0x0c, 0xb5, 0xef, 0x9d, 0x23, 0x19,
-	0x29, 0xeb, 0xf3, 0xea, 0x6a, 0x1f, 0xa3, 0x37, 0x60, 0x35, 0x10, 0x09, 0xb7, 0x6b, 0x4a, 0x5a,
-	0xf1, 0xd9, 0x7e, 0xc3, 0x70, 0xbf, 0x27, 0xa0, 0xc8, 0xdb, 0x36, 0x76, 0xec, 0xb2, 0x6e, 0xe1,
-	0x8e, 0x63, 0x5a, 0xa1, 0x09, 0xa7, 0x14, 0x24, 0xe6, 0xea, 0x51, 0x91, 0x08, 0xa2, 0x23, 0xc8,
-	0x85, 0x27, 0xc4, 0x97, 0x9a, 0x0b, 0xb3, 0xa1, 0xb9, 0x10, 0x99, 0x70, 0x3d, 0x7e, 0x00, 0xf2,
-	0xef, 0xc1, 0xbf, 0x31, 0xf6, 0x14, 0xe3, 0xc6, 0x1e, 0xf4, 0x3e, 0xa4, 0x83, 0x11, 0x32, 0x6e,
-	0xf8, 0x9f, 0x9e, 0x3a, 0x47, 0x2a, 0xdc, 0x0f, 0x09, 0x28, 0xd6, 0x55, 0x47, 0xbf, 0xc4, 0x11,
-	0x59, 0xbc, 0x1b, 0xce, 0x62, 0x6c, 0x79, 0xa1, 0xfd, 0xc8, 0xe4, 0xbd, 0x3e, 0x23, 0x79, 0x74,
-	0xce, 0xce, 0xe7, 0xc8, 0xd9, 0xe6, 0xbc, 0x39, 0x9b, 0x91, 0xaa, 0x47, 0x90, 0x19, 0x8d, 0x61,
-	0x7e, 0xae, 0x5e, 0x8b, 0xad, 0x7e, 0x69, 0x2c, 0xcb, 0xc9, 0x80, 0xf8, 0x01, 0x7e, 0x5e, 0xed,
-	0x53, 0x73, 0xfa, 0x3e, 0x64, 0x46, 0x73, 0xa6, 0x9f, 0xa3, 0x39, 0xa6, 0xcb, 0x74, 0x30, 0x5d,
-	0x72, 0x16, 0xe4, 0xa8, 0x21, 0xf3, 0xfe, 0xf8, 0x76, 0x8d, 0x6d, 0x67, 0x4f, 0x02, 0x3d, 0x82,
-	0x9c, 0xda, 0x73, 0xb0, 0x65, 0xb8, 0x3b, 0x67, 0xfb, 0x13, 0x5c, 0xa4, 0x06, 0x25, 0xc8, 0x7d,
-	0xc9, 0xc0, 0xb5, 0x29, 0x9f, 0x50, 0x89, 0xb6, 0x5c, 0x8c, 0x8b, 0x22, 0x30, 0xff, 0x5e, 0xa4,
-	0xf9, 0x78, 0x35, 0xda, 0x87, 0xef, 0x18, 0x58, 0x8f, 0x7e, 0xb0, 0xa0, 0x1d, 0xda, 0x91, 0x9b,
-	0x33, 0xc7, 0xad, 0xc0, 0x1b, 0x3e, 0xd2, 0x9b, 0x2b, 0x74, 0x69, 0x97, 0x6a, 0x70, 0x6d, 0xaa,
-	0x49, 0xc2, 0xd3, 0x0e, 0x19, 0x26, 0x7d, 0x6b, 0x5c, 0x84, 0xb5, 0xcc, 0x04, 0xdc, 0xb7, 0x0c,
-	0xac, 0x45, 0x1d, 0x15, 0x68, 0x8b, 0x8e, 0x6f, 0x66, 0x8b, 0xf8, 0xf6, 0x3e, 0x88, 0x8c, 0x6e,
-	0xa6, 0x26, 0xed, 0xcc, 0x37, 0x0c, 0x14, 0x22, 0x9e, 0x09, 0xe8, 0x6d, 0xda, 0x97, 0xeb, 0xf1,
-	0x4f, 0x8b, 0xc0, 0x95, 0xfd, 0x48, 0x57, 0x66, 0x29, 0xd2, 0x9e, 0xfc, 0xc4, 0xc0, 0xc6, 0x95,
-	0x47, 0x1d, 0xb9, 0x9f, 0xc2, 0x7e, 0xcd, 0xdf, 0xf8, 0xbe, 0x97, 0xc7, 0x91, 0x5e, 0xce, 0x0f,
-	0x43, 0xfb, 0x2c, 0x02, 0x9a, 0x7e, 0xce, 0xa1, 0xb7, 0x68, 0x1f, 0x67, 0x9c, 0x22, 0xfe, 0x8c,
-	0xdc, 0x86, 0xf5, 0xe8, 0xe7, 0x14, 0xaa, 0x00, 0x6b, 0xe1, 0xcf, 0x86, 0xba, 0x85, 0xb5, 0xe0,
-	0x69, 0x16, 0x37, 0xee, 0x52, 0x08, 0xd2, 0x6a, 0xa0, 0xe6, 0x33, 0x0e, 0x1e, 0x00, 0xea, 0x98,
-	0xfd, 0x09, 0xa5, 0xa7, 0x6b, 0xfe, 0x5a, 0xf1, 0xd6, 0x8a, 0xfb, 0x0f, 0xb6, 0xf6, 0x92, 0xfb,
-	0xb3, 0xf3, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x47, 0xe0, 0x85, 0xa8, 0x7a, 0x13, 0x00, 0x00,
+var file_targeting_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
+var file_targeting_proto_msgTypes = make([]protoimpl.MessageInfo, 27)
+var file_targeting_proto_goTypes = []interface{}{
+	(ScreenDensity_DensityAlias)(0),                             // 0: android.bundle.ScreenDensity.DensityAlias
+	(TextureCompressionFormat_TextureCompressionFormatAlias)(0), // 1: android.bundle.TextureCompressionFormat.TextureCompressionFormatAlias
+	(Abi_AbiAlias)(0),                         // 2: android.bundle.Abi.AbiAlias
+	(Sanitizer_SanitizerAlias)(0),             // 3: android.bundle.Sanitizer.SanitizerAlias
+	(*VariantTargeting)(nil),                  // 4: android.bundle.VariantTargeting
+	(*ApkTargeting)(nil),                      // 5: android.bundle.ApkTargeting
+	(*ModuleTargeting)(nil),                   // 6: android.bundle.ModuleTargeting
+	(*UserCountriesTargeting)(nil),            // 7: android.bundle.UserCountriesTargeting
+	(*ScreenDensity)(nil),                     // 8: android.bundle.ScreenDensity
+	(*Int32Value)(nil),                        // 9: android.bundle.Int32Value
+	(*SdkVersion)(nil),                        // 10: android.bundle.SdkVersion
+	(*GraphicsApi)(nil),                       // 11: android.bundle.GraphicsApi
+	(*VulkanVersion)(nil),                     // 12: android.bundle.VulkanVersion
+	(*OpenGlVersion)(nil),                     // 13: android.bundle.OpenGlVersion
+	(*TextureCompressionFormat)(nil),          // 14: android.bundle.TextureCompressionFormat
+	(*Abi)(nil),                               // 15: android.bundle.Abi
+	(*MultiAbi)(nil),                          // 16: android.bundle.MultiAbi
+	(*Sanitizer)(nil),                         // 17: android.bundle.Sanitizer
+	(*DeviceFeature)(nil),                     // 18: android.bundle.DeviceFeature
+	(*AssetsDirectoryTargeting)(nil),          // 19: android.bundle.AssetsDirectoryTargeting
+	(*NativeDirectoryTargeting)(nil),          // 20: android.bundle.NativeDirectoryTargeting
+	(*ApexImageTargeting)(nil),                // 21: android.bundle.ApexImageTargeting
+	(*AbiTargeting)(nil),                      // 22: android.bundle.AbiTargeting
+	(*MultiAbiTargeting)(nil),                 // 23: android.bundle.MultiAbiTargeting
+	(*ScreenDensityTargeting)(nil),            // 24: android.bundle.ScreenDensityTargeting
+	(*LanguageTargeting)(nil),                 // 25: android.bundle.LanguageTargeting
+	(*GraphicsApiTargeting)(nil),              // 26: android.bundle.GraphicsApiTargeting
+	(*SdkVersionTargeting)(nil),               // 27: android.bundle.SdkVersionTargeting
+	(*TextureCompressionFormatTargeting)(nil), // 28: android.bundle.TextureCompressionFormatTargeting
+	(*SanitizerTargeting)(nil),                // 29: android.bundle.SanitizerTargeting
+	(*DeviceFeatureTargeting)(nil),            // 30: android.bundle.DeviceFeatureTargeting
+}
+var file_targeting_proto_depIdxs = []int32{
+	27, // 0: android.bundle.VariantTargeting.sdk_version_targeting:type_name -> android.bundle.SdkVersionTargeting
+	22, // 1: android.bundle.VariantTargeting.abi_targeting:type_name -> android.bundle.AbiTargeting
+	24, // 2: android.bundle.VariantTargeting.screen_density_targeting:type_name -> android.bundle.ScreenDensityTargeting
+	23, // 3: android.bundle.VariantTargeting.multi_abi_targeting:type_name -> android.bundle.MultiAbiTargeting
+	28, // 4: android.bundle.VariantTargeting.texture_compression_format_targeting:type_name -> android.bundle.TextureCompressionFormatTargeting
+	22, // 5: android.bundle.ApkTargeting.abi_targeting:type_name -> android.bundle.AbiTargeting
+	26, // 6: android.bundle.ApkTargeting.graphics_api_targeting:type_name -> android.bundle.GraphicsApiTargeting
+	25, // 7: android.bundle.ApkTargeting.language_targeting:type_name -> android.bundle.LanguageTargeting
+	24, // 8: android.bundle.ApkTargeting.screen_density_targeting:type_name -> android.bundle.ScreenDensityTargeting
+	27, // 9: android.bundle.ApkTargeting.sdk_version_targeting:type_name -> android.bundle.SdkVersionTargeting
+	28, // 10: android.bundle.ApkTargeting.texture_compression_format_targeting:type_name -> android.bundle.TextureCompressionFormatTargeting
+	23, // 11: android.bundle.ApkTargeting.multi_abi_targeting:type_name -> android.bundle.MultiAbiTargeting
+	29, // 12: android.bundle.ApkTargeting.sanitizer_targeting:type_name -> android.bundle.SanitizerTargeting
+	27, // 13: android.bundle.ModuleTargeting.sdk_version_targeting:type_name -> android.bundle.SdkVersionTargeting
+	30, // 14: android.bundle.ModuleTargeting.device_feature_targeting:type_name -> android.bundle.DeviceFeatureTargeting
+	7,  // 15: android.bundle.ModuleTargeting.user_countries_targeting:type_name -> android.bundle.UserCountriesTargeting
+	0,  // 16: android.bundle.ScreenDensity.density_alias:type_name -> android.bundle.ScreenDensity.DensityAlias
+	9,  // 17: android.bundle.SdkVersion.min:type_name -> android.bundle.Int32Value
+	13, // 18: android.bundle.GraphicsApi.min_open_gl_version:type_name -> android.bundle.OpenGlVersion
+	12, // 19: android.bundle.GraphicsApi.min_vulkan_version:type_name -> android.bundle.VulkanVersion
+	1,  // 20: android.bundle.TextureCompressionFormat.alias:type_name -> android.bundle.TextureCompressionFormat.TextureCompressionFormatAlias
+	2,  // 21: android.bundle.Abi.alias:type_name -> android.bundle.Abi.AbiAlias
+	15, // 22: android.bundle.MultiAbi.abi:type_name -> android.bundle.Abi
+	3,  // 23: android.bundle.Sanitizer.alias:type_name -> android.bundle.Sanitizer.SanitizerAlias
+	22, // 24: android.bundle.AssetsDirectoryTargeting.abi:type_name -> android.bundle.AbiTargeting
+	26, // 25: android.bundle.AssetsDirectoryTargeting.graphics_api:type_name -> android.bundle.GraphicsApiTargeting
+	28, // 26: android.bundle.AssetsDirectoryTargeting.texture_compression_format:type_name -> android.bundle.TextureCompressionFormatTargeting
+	25, // 27: android.bundle.AssetsDirectoryTargeting.language:type_name -> android.bundle.LanguageTargeting
+	15, // 28: android.bundle.NativeDirectoryTargeting.abi:type_name -> android.bundle.Abi
+	11, // 29: android.bundle.NativeDirectoryTargeting.graphics_api:type_name -> android.bundle.GraphicsApi
+	14, // 30: android.bundle.NativeDirectoryTargeting.texture_compression_format:type_name -> android.bundle.TextureCompressionFormat
+	17, // 31: android.bundle.NativeDirectoryTargeting.sanitizer:type_name -> android.bundle.Sanitizer
+	23, // 32: android.bundle.ApexImageTargeting.multi_abi:type_name -> android.bundle.MultiAbiTargeting
+	15, // 33: android.bundle.AbiTargeting.value:type_name -> android.bundle.Abi
+	15, // 34: android.bundle.AbiTargeting.alternatives:type_name -> android.bundle.Abi
+	16, // 35: android.bundle.MultiAbiTargeting.value:type_name -> android.bundle.MultiAbi
+	16, // 36: android.bundle.MultiAbiTargeting.alternatives:type_name -> android.bundle.MultiAbi
+	8,  // 37: android.bundle.ScreenDensityTargeting.value:type_name -> android.bundle.ScreenDensity
+	8,  // 38: android.bundle.ScreenDensityTargeting.alternatives:type_name -> android.bundle.ScreenDensity
+	11, // 39: android.bundle.GraphicsApiTargeting.value:type_name -> android.bundle.GraphicsApi
+	11, // 40: android.bundle.GraphicsApiTargeting.alternatives:type_name -> android.bundle.GraphicsApi
+	10, // 41: android.bundle.SdkVersionTargeting.value:type_name -> android.bundle.SdkVersion
+	10, // 42: android.bundle.SdkVersionTargeting.alternatives:type_name -> android.bundle.SdkVersion
+	14, // 43: android.bundle.TextureCompressionFormatTargeting.value:type_name -> android.bundle.TextureCompressionFormat
+	14, // 44: android.bundle.TextureCompressionFormatTargeting.alternatives:type_name -> android.bundle.TextureCompressionFormat
+	17, // 45: android.bundle.SanitizerTargeting.value:type_name -> android.bundle.Sanitizer
+	18, // 46: android.bundle.DeviceFeatureTargeting.required_feature:type_name -> android.bundle.DeviceFeature
+	47, // [47:47] is the sub-list for method output_type
+	47, // [47:47] is the sub-list for method input_type
+	47, // [47:47] is the sub-list for extension type_name
+	47, // [47:47] is the sub-list for extension extendee
+	0,  // [0:47] is the sub-list for field type_name
+}
+
+func init() { file_targeting_proto_init() }
+func file_targeting_proto_init() {
+	if File_targeting_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_targeting_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*VariantTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApkTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ModuleTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UserCountriesTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ScreenDensity); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Int32Value); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SdkVersion); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GraphicsApi); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*VulkanVersion); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OpenGlVersion); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TextureCompressionFormat); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Abi); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MultiAbi); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Sanitizer); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DeviceFeature); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AssetsDirectoryTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*NativeDirectoryTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ApexImageTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AbiTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MultiAbiTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ScreenDensityTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LanguageTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GraphicsApiTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SdkVersionTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TextureCompressionFormatTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SanitizerTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_targeting_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DeviceFeatureTargeting); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_targeting_proto_msgTypes[4].OneofWrappers = []interface{}{
+		(*ScreenDensity_DensityAlias_)(nil),
+		(*ScreenDensity_DensityDpi)(nil),
+	}
+	file_targeting_proto_msgTypes[7].OneofWrappers = []interface{}{
+		(*GraphicsApi_MinOpenGlVersion)(nil),
+		(*GraphicsApi_MinVulkanVersion)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_targeting_proto_rawDesc,
+			NumEnums:      4,
+			NumMessages:   27,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_targeting_proto_goTypes,
+		DependencyIndexes: file_targeting_proto_depIdxs,
+		EnumInfos:         file_targeting_proto_enumTypes,
+		MessageInfos:      file_targeting_proto_msgTypes,
+	}.Build()
+	File_targeting_proto = out.File
+	file_targeting_proto_rawDesc = nil
+	file_targeting_proto_goTypes = nil
+	file_targeting_proto_depIdxs = nil
 }
diff --git a/cmd/extract_apks/bundle_proto/targeting.proto b/cmd/extract_apks/bundle_proto/targeting.proto
index cdc910b..a33edc7 100644
--- a/cmd/extract_apks/bundle_proto/targeting.proto
+++ b/cmd/extract_apks/bundle_proto/targeting.proto
@@ -6,7 +6,7 @@
 
 package android.bundle;
 
-option go_package = "android_bundle_proto";
+option go_package = "android/soong/cmd/extract_apks/bundle_proto";
 option java_package = "com.android.bundle";
 
 // Targeting on the level of variants.
diff --git a/cmd/extract_apks/main.go b/cmd/extract_apks/main.go
index db54ffb..1cf64de 100644
--- a/cmd/extract_apks/main.go
+++ b/cmd/extract_apks/main.go
@@ -27,9 +27,9 @@
 	"sort"
 	"strings"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 
-	"android/soong/cmd/extract_apks/bundle_proto"
+	android_bundle_proto "android/soong/cmd/extract_apks/bundle_proto"
 	"android/soong/third_party/zip"
 )
 
@@ -356,7 +356,7 @@
 
 // Writes out selected entries, renaming them as needed
 func (apkSet *ApkSet) writeApks(selected SelectionResult, config TargetConfig,
-	writer Zip2ZipWriter, partition string) ([]string, error) {
+	outFile io.Writer, zipWriter Zip2ZipWriter, partition string) ([]string, error) {
 	// Renaming rules:
 	//  splits/MODULE-master.apk to STEM.apk
 	// else
@@ -406,8 +406,14 @@
 				origin, inName, outName)
 		}
 		entryOrigin[outName] = inName
-		if err := writer.CopyFrom(apkFile, outName); err != nil {
-			return nil, err
+		if outName == config.stem+".apk" {
+			if err := writeZipEntryToFile(outFile, apkFile); err != nil {
+				return nil, err
+			}
+		} else {
+			if err := zipWriter.CopyFrom(apkFile, outName); err != nil {
+				return nil, err
+			}
 		}
 		if partition != "" {
 			apkcerts = append(apkcerts, fmt.Sprintf(
@@ -426,14 +432,13 @@
 	if !ok {
 		return fmt.Errorf("Couldn't find apk path %s", selected.entries[0])
 	}
-	inputReader, _ := apk.Open()
-	_, err := io.Copy(outFile, inputReader)
-	return err
+	return writeZipEntryToFile(outFile, apk)
 }
 
 // Arguments parsing
 var (
-	outputFile   = flag.String("o", "", "output file containing extracted entries")
+	outputFile   = flag.String("o", "", "output file for primary entry")
+	zipFile      = flag.String("zip", "", "output file containing additional extracted entries")
 	targetConfig = TargetConfig{
 		screenDpi: map[android_bundle_proto.ScreenDensity_DensityAlias]bool{},
 		abis:      map[android_bundle_proto.Abi_AbiAlias]int{},
@@ -494,7 +499,8 @@
 
 func processArgs() {
 	flag.Usage = func() {
-		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> -sdk-version value -abis value `+
+		fmt.Fprintln(os.Stderr, `usage: extract_apks -o <output-file> [-zip <output-zip-file>] `+
+			`-sdk-version value -abis value `+
 			`-screen-densities value {-stem value | -extract-single} [-allow-prereleased] `+
 			`[-apkcerts <apkcerts output file> -partition <partition>] <APK set>`)
 		flag.PrintDefaults()
@@ -510,7 +516,8 @@
 	flag.StringVar(&targetConfig.stem, "stem", "", "output entries base name in the output zip file")
 	flag.Parse()
 	if (*outputFile == "") || len(flag.Args()) != 1 || *version == 0 ||
-		(targetConfig.stem == "" && !*extractSingle) || (*apkcertsOutput != "" && *partition == "") {
+		((targetConfig.stem == "" || *zipFile == "") && !*extractSingle) ||
+		(*apkcertsOutput != "" && *partition == "") {
 		flag.Usage()
 	}
 	targetConfig.sdkVersion = int32(*version)
@@ -542,13 +549,20 @@
 	if *extractSingle {
 		err = apkSet.extractAndCopySingle(sel, outFile)
 	} else {
-		writer := zip.NewWriter(outFile)
+		zipOutputFile, err := os.Create(*zipFile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer zipOutputFile.Close()
+
+		zipWriter := zip.NewWriter(zipOutputFile)
 		defer func() {
-			if err := writer.Close(); err != nil {
+			if err := zipWriter.Close(); err != nil {
 				log.Fatal(err)
 			}
 		}()
-		apkcerts, err := apkSet.writeApks(sel, targetConfig, writer, *partition)
+
+		apkcerts, err := apkSet.writeApks(sel, targetConfig, outFile, zipWriter, *partition)
 		if err == nil && *apkcertsOutput != "" {
 			apkcertsFile, err := os.Create(*apkcertsOutput)
 			if err != nil {
@@ -567,3 +581,13 @@
 		log.Fatal(err)
 	}
 }
+
+func writeZipEntryToFile(outFile io.Writer, zipEntry *zip.File) error {
+	reader, err := zipEntry.Open()
+	if err != nil {
+		return err
+	}
+	defer reader.Close()
+	_, err = io.Copy(outFile, reader)
+	return err
+}
diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go
index c3e6a2d..f5e4046 100644
--- a/cmd/extract_apks/main_test.go
+++ b/cmd/extract_apks/main_test.go
@@ -15,11 +15,12 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"reflect"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/encoding/prototext"
 
 	bp "android/soong/cmd/extract_apks/bundle_proto"
 	"android/soong/third_party/zip"
@@ -253,7 +254,7 @@
 	}
 	for _, testCase := range testCases {
 		var toc bp.BuildApksResult
-		if err := proto.UnmarshalText(testCase.protoText, &toc); err != nil {
+		if err := prototext.Unmarshal([]byte(testCase.protoText), &toc); err != nil {
 			t.Fatal(err)
 		}
 		for _, config := range testCase.configs {
@@ -407,7 +408,7 @@
 	}
 	for _, testCase := range testCases {
 		var toc bp.BuildApksResult
-		if err := proto.UnmarshalText(testCase.protoText, &toc); err != nil {
+		if err := prototext.Unmarshal([]byte(testCase.protoText), &toc); err != nil {
 			t.Fatal(err)
 		}
 		for _, config := range testCase.configs {
@@ -437,8 +438,8 @@
 	stem       string
 	partition  string
 	// what we write from what
-	expectedZipEntries map[string]string
-	expectedApkcerts   []string
+	zipEntries       map[string]string
+	expectedApkcerts []string
 }
 
 func TestWriteApks(t *testing.T) {
@@ -448,7 +449,7 @@
 			moduleName: "mybase",
 			stem:       "Foo",
 			partition:  "system",
-			expectedZipEntries: map[string]string{
+			zipEntries: map[string]string{
 				"Foo.apk":       "splits/mybase-master.apk",
 				"Foo-xhdpi.apk": "splits/mybase-xhdpi.apk",
 			},
@@ -462,7 +463,7 @@
 			moduleName: "base",
 			stem:       "Bar",
 			partition:  "product",
-			expectedZipEntries: map[string]string{
+			zipEntries: map[string]string{
 				"Bar.apk": "universal.apk",
 			},
 			expectedApkcerts: []string{
@@ -471,23 +472,46 @@
 		},
 	}
 	for _, testCase := range testCases {
-		apkSet := ApkSet{entries: make(map[string]*zip.File)}
-		sel := SelectionResult{moduleName: testCase.moduleName}
-		for _, in := range testCase.expectedZipEntries {
-			apkSet.entries[in] = &zip.File{FileHeader: zip.FileHeader{Name: in}}
-			sel.entries = append(sel.entries, in)
-		}
-		writer := testZip2ZipWriter{make(map[string]string)}
-		config := TargetConfig{stem: testCase.stem}
-		apkcerts, err := apkSet.writeApks(sel, config, writer, testCase.partition)
-		if err != nil {
-			t.Error(err)
-		}
-		if !reflect.DeepEqual(testCase.expectedZipEntries, writer.entries) {
-			t.Errorf("expected zip entries %v, got %v", testCase.expectedZipEntries, writer.entries)
-		}
-		if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) {
-			t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts)
-		}
+		t.Run(testCase.name, func(t *testing.T) {
+			testZipBuf := &bytes.Buffer{}
+			testZip := zip.NewWriter(testZipBuf)
+			for _, in := range testCase.zipEntries {
+				f, _ := testZip.Create(in)
+				f.Write([]byte(in))
+			}
+			testZip.Close()
+
+			zipReader, _ := zip.NewReader(bytes.NewReader(testZipBuf.Bytes()), int64(testZipBuf.Len()))
+
+			apkSet := ApkSet{entries: make(map[string]*zip.File)}
+			sel := SelectionResult{moduleName: testCase.moduleName}
+			for _, f := range zipReader.File {
+				apkSet.entries[f.Name] = f
+				sel.entries = append(sel.entries, f.Name)
+			}
+
+			zipWriter := testZip2ZipWriter{make(map[string]string)}
+			outWriter := &bytes.Buffer{}
+			config := TargetConfig{stem: testCase.stem}
+			apkcerts, err := apkSet.writeApks(sel, config, outWriter, zipWriter, testCase.partition)
+			if err != nil {
+				t.Error(err)
+			}
+			expectedZipEntries := make(map[string]string)
+			for k, v := range testCase.zipEntries {
+				if k != testCase.stem+".apk" {
+					expectedZipEntries[k] = v
+				}
+			}
+			if !reflect.DeepEqual(expectedZipEntries, zipWriter.entries) {
+				t.Errorf("expected zip entries %v, got %v", testCase.zipEntries, zipWriter.entries)
+			}
+			if !reflect.DeepEqual(testCase.expectedApkcerts, apkcerts) {
+				t.Errorf("expected apkcerts %v, got %v", testCase.expectedApkcerts, apkcerts)
+			}
+			if g, w := outWriter.String(), testCase.zipEntries[testCase.stem+".apk"]; !reflect.DeepEqual(g, w) {
+				t.Errorf("expected output file contents %q, got %q", testCase.stem+".apk", outWriter.String())
+			}
+		})
 	}
 }
diff --git a/cmd/extract_linker/main.go b/cmd/extract_linker/main.go
index ea0bf4e..aaca1dd 100644
--- a/cmd/extract_linker/main.go
+++ b/cmd/extract_linker/main.go
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 // This tool extracts ELF LOAD segments from our linker binary, and produces an
-// assembly file and linker flags which will embed those segments as sections
+// assembly file and linker script which will embed those segments as sections
 // in another binary.
 package main
 
@@ -26,15 +26,15 @@
 	"io/ioutil"
 	"log"
 	"os"
-	"strings"
+	"strconv"
 )
 
 func main() {
 	var asmPath string
-	var flagsPath string
+	var scriptPath string
 
 	flag.StringVar(&asmPath, "s", "", "Path to save the assembly file")
-	flag.StringVar(&flagsPath, "f", "", "Path to save the linker flags")
+	flag.StringVar(&scriptPath, "T", "", "Path to save the linker script")
 	flag.Parse()
 
 	f, err := os.Open(flag.Arg(0))
@@ -49,20 +49,26 @@
 	}
 
 	asm := &bytes.Buffer{}
+	script := &bytes.Buffer{}
 	baseLoadAddr := uint64(0x1000)
 	load := 0
-	linkFlags := []string{}
 
 	fmt.Fprintln(asm, ".globl __dlwrap_linker_offset")
 	fmt.Fprintf(asm, ".set __dlwrap_linker_offset, 0x%x\n", baseLoadAddr)
 
+	fmt.Fprintln(script, "ENTRY(__dlwrap__start)")
+	fmt.Fprintln(script, "SECTIONS {")
+
+	progsWithFlagsCount := make(map[string]int)
+
 	for _, prog := range ef.Progs {
 		if prog.Type != elf.PT_LOAD {
 			continue
 		}
 
-		sectionName := fmt.Sprintf(".linker.sect%d", load)
-		symName := fmt.Sprintf("__dlwrap_linker_sect%d", load)
+		progName := progNameFromFlags(prog.Flags, progsWithFlagsCount)
+		sectionName := ".linker_" + progName
+		symName := "__dlwrap_linker_" + progName
 
 		flags := ""
 		if prog.Flags&elf.PF_W != 0 {
@@ -73,12 +79,17 @@
 		}
 		fmt.Fprintf(asm, ".section %s, \"a%s\"\n", sectionName, flags)
 
+		if load == 0 {
+			fmt.Fprintln(asm, ".globl __dlwrap_linker")
+			fmt.Fprintln(asm, "__dlwrap_linker:")
+			fmt.Fprintln(asm)
+		}
+
 		fmt.Fprintf(asm, ".globl %s\n%s:\n\n", symName, symName)
 
-		linkFlags = append(linkFlags,
-			fmt.Sprintf("-Wl,--undefined=%s", symName),
-			fmt.Sprintf("-Wl,--section-start=%s=0x%x",
-				sectionName, baseLoadAddr+prog.Vaddr))
+		fmt.Fprintf(script, "  %s 0x%x : {\n", sectionName, baseLoadAddr+prog.Vaddr)
+		fmt.Fprintf(script, "    KEEP(*(%s));\n", sectionName)
+		fmt.Fprintln(script, "  }")
 
 		buffer, _ := ioutil.ReadAll(prog.Open())
 		bytesToAsm(asm, buffer)
@@ -97,16 +108,28 @@
 		load += 1
 	}
 
+	fmt.Fprintln(asm, ".globl __dlwrap_linker_end")
+	fmt.Fprintln(asm, "__dlwrap_linker_end:")
+	fmt.Fprintln(asm)
+
+	fmt.Fprintln(asm, `.section .note.android.embedded_linker,"a",%note`)
+
+	// Discard the PT_INTERP section so that the linker doesn't need to be passed the
+	// --no-dynamic-linker flag.
+	fmt.Fprintln(script, "  /DISCARD/ : { *(.interp) }")
+
+	fmt.Fprintln(script, "}")
+	fmt.Fprintln(script, "INSERT BEFORE .note.android.embedded_linker;")
+
 	if asmPath != "" {
 		if err := ioutil.WriteFile(asmPath, asm.Bytes(), 0777); err != nil {
 			log.Fatalf("Unable to write %q: %v", asmPath, err)
 		}
 	}
 
-	if flagsPath != "" {
-		flags := strings.Join(linkFlags, " ")
-		if err := ioutil.WriteFile(flagsPath, []byte(flags), 0777); err != nil {
-			log.Fatalf("Unable to write %q: %v", flagsPath, err)
+	if scriptPath != "" {
+		if err := ioutil.WriteFile(scriptPath, script.Bytes(), 0777); err != nil {
+			log.Fatalf("Unable to write %q: %v", scriptPath, err)
 		}
 	}
 }
@@ -125,3 +148,26 @@
 	}
 	fmt.Fprintln(asm)
 }
+
+func progNameFromFlags(flags elf.ProgFlag, progsWithFlagsCount map[string]int) string {
+	s := ""
+	if flags&elf.PF_R != 0 {
+		s += "r"
+	}
+	if flags&elf.PF_W != 0 {
+		s += "w"
+	}
+	if flags&elf.PF_X != 0 {
+		s += "x"
+	}
+
+	count := progsWithFlagsCount[s]
+	count++
+	progsWithFlagsCount[s] = count
+
+	if count > 1 {
+		s += strconv.Itoa(count)
+	}
+
+	return s
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/go2bp/Android.bp
similarity index 76%
copy from cmd/host_bionic_inject/Android.bp
copy to cmd/go2bp/Android.bp
index 16bc179..53d70b6 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/go2bp/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -17,8 +17,10 @@
 }
 
 blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+    name: "go2bp",
+    deps: [
+        "blueprint-proptools",
+        "bpfix-lib",
+    ],
+    srcs: ["go2bp.go"],
 }
diff --git a/cmd/go2bp/go2bp.go b/cmd/go2bp/go2bp.go
new file mode 100644
index 0000000..fb5a746
--- /dev/null
+++ b/cmd/go2bp/go2bp.go
@@ -0,0 +1,413 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"strings"
+	"text/template"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/bpfix/bpfix"
+)
+
+type RewriteNames []RewriteName
+type RewriteName struct {
+	prefix string
+	repl   string
+}
+
+func (r *RewriteNames) String() string {
+	return ""
+}
+
+func (r *RewriteNames) Set(v string) error {
+	split := strings.SplitN(v, "=", 2)
+	if len(split) != 2 {
+		return fmt.Errorf("Must be in the form of <prefix>=<replace>")
+	}
+	*r = append(*r, RewriteName{
+		prefix: split[0],
+		repl:   split[1],
+	})
+	return nil
+}
+
+func (r *RewriteNames) GoToBp(name string) string {
+	ret := name
+	for _, r := range *r {
+		prefix := r.prefix
+		if name == prefix {
+			ret = r.repl
+			break
+		}
+		prefix += "/"
+		if strings.HasPrefix(name, prefix) {
+			ret = r.repl + "-" + strings.TrimPrefix(name, prefix)
+		}
+	}
+	return strings.ReplaceAll(ret, "/", "-")
+}
+
+var rewriteNames = RewriteNames{}
+
+type Exclude map[string]bool
+
+func (e Exclude) String() string {
+	return ""
+}
+
+func (e Exclude) Set(v string) error {
+	e[v] = true
+	return nil
+}
+
+var excludes = make(Exclude)
+var excludeDeps = make(Exclude)
+var excludeSrcs = make(Exclude)
+
+type StringList []string
+
+func (l *StringList) String() string {
+	return strings.Join(*l, " ")
+}
+
+func (l *StringList) Set(v string) error {
+	*l = append(*l, strings.Fields(v)...)
+	return nil
+}
+
+type GoModule struct {
+	Dir string
+}
+
+type GoPackage struct {
+	ExportToAndroid bool
+
+	Dir         string
+	ImportPath  string
+	Name        string
+	Imports     []string
+	GoFiles     []string
+	TestGoFiles []string
+	TestImports []string
+
+	Module *GoModule
+}
+
+func (g GoPackage) IsCommand() bool {
+	return g.Name == "main"
+}
+
+func (g GoPackage) BpModuleType() string {
+	if g.IsCommand() {
+		return "blueprint_go_binary"
+	}
+	return "bootstrap_go_package"
+}
+
+func (g GoPackage) BpName() string {
+	if g.IsCommand() {
+		return rewriteNames.GoToBp(filepath.Base(g.ImportPath))
+	}
+	return rewriteNames.GoToBp(g.ImportPath)
+}
+
+func (g GoPackage) BpDeps(deps []string) []string {
+	var ret []string
+	for _, d := range deps {
+		// Ignore stdlib dependencies
+		if !strings.Contains(d, ".") {
+			continue
+		}
+		if _, ok := excludeDeps[d]; ok {
+			continue
+		}
+		name := rewriteNames.GoToBp(d)
+		ret = append(ret, name)
+	}
+	return ret
+}
+
+func (g GoPackage) BpSrcs(srcs []string) []string {
+	var ret []string
+	prefix, err := filepath.Rel(g.Module.Dir, g.Dir)
+	if err != nil {
+		panic(err)
+	}
+	for _, f := range srcs {
+		f = filepath.Join(prefix, f)
+		if _, ok := excludeSrcs[f]; ok {
+			continue
+		}
+		ret = append(ret, f)
+	}
+	return ret
+}
+
+// AllImports combines Imports and TestImports, as blueprint does not differentiate these.
+func (g GoPackage) AllImports() []string {
+	imports := append([]string(nil), g.Imports...)
+	imports = append(imports, g.TestImports...)
+
+	if len(imports) == 0 {
+		return nil
+	}
+
+	// Sort and de-duplicate
+	sort.Strings(imports)
+	j := 0
+	for i := 1; i < len(imports); i++ {
+		if imports[i] == imports[j] {
+			continue
+		}
+		j++
+		imports[j] = imports[i]
+	}
+	return imports[:j+1]
+}
+
+var bpTemplate = template.Must(template.New("bp").Parse(`
+{{.BpModuleType}} {
+    name: "{{.BpName}}",
+    {{- if not .IsCommand}}
+    pkgPath: "{{.ImportPath}}",
+    {{- end}}
+    {{- if .BpDeps .AllImports}}
+    deps: [
+        {{- range .BpDeps .AllImports}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+    {{- if .BpSrcs .GoFiles}}
+    srcs: [
+        {{- range .BpSrcs .GoFiles}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+    {{- if .BpSrcs .TestGoFiles}}
+    testSrcs: [
+    	{{- range .BpSrcs .TestGoFiles}}
+        "{{.}}",
+       {{- end}}
+    ],
+    {{- end}}
+}
+`))
+
+func rerunForRegen(filename string) error {
+	buf, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+
+	scanner := bufio.NewScanner(bytes.NewBuffer(buf))
+
+	// Skip the first line in the file
+	for i := 0; i < 2; i++ {
+		if !scanner.Scan() {
+			if scanner.Err() != nil {
+				return scanner.Err()
+			} else {
+				return fmt.Errorf("unexpected EOF")
+			}
+		}
+	}
+
+	// Extract the old args from the file
+	line := scanner.Text()
+	if strings.HasPrefix(line, "// go2bp ") {
+		line = strings.TrimPrefix(line, "// go2bp ")
+	} else {
+		return fmt.Errorf("unexpected second line: %q", line)
+	}
+	args := strings.Split(line, " ")
+	lastArg := args[len(args)-1]
+	args = args[:len(args)-1]
+
+	// Append all current command line args except -regen <file> to the ones from the file
+	for i := 1; i < len(os.Args); i++ {
+		if os.Args[i] == "-regen" || os.Args[i] == "--regen" {
+			i++
+		} else {
+			args = append(args, os.Args[i])
+		}
+	}
+	args = append(args, lastArg)
+
+	cmd := os.Args[0] + " " + strings.Join(args, " ")
+	// Re-exec pom2bp with the new arguments
+	output, err := exec.Command("/bin/sh", "-c", cmd).Output()
+	if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+		return fmt.Errorf("failed to run %s\n%s", cmd, string(exitErr.Stderr))
+	} else if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(filename, output, 0666)
+}
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `go2bp, a tool to create Android.bp files from go modules
+
+The tool will extract the necessary information from Go files to create an Android.bp that can
+compile them. This needs to be run from the same directory as the go.mod file.
+
+Usage: %s [--rewrite <pkg-prefix>=<replace>] [-exclude <package>] [-regen <file>]
+
+  -rewrite <pkg-prefix>=<replace>
+     rewrite can be used to specify mappings between go package paths and Android.bp modules. The -rewrite
+     option can be specified multiple times. When determining the Android.bp module for a given Go
+     package, mappings are searched in the order they were specified. The first <pkg-prefix> matching
+     either the package directly, or as the prefix '<pkg-prefix>/' will be replaced with <replace>.
+     After all replacements are finished, all '/' characters are replaced with '-'.
+  -exclude <package>
+     Don't put the specified go package in the Android.bp file.
+  -exclude-deps <package>
+     Don't put the specified go package in the dependency lists.
+  -exclude-srcs <module>
+     Don't put the specified source files in srcs or testSrcs lists.
+  -limit <package>
+     If set, limit the output to the specified packages and their dependencies.
+  -skip-tests
+     If passed, don't write out any test srcs or dependencies to the Android.bp output.
+  -regen <file>
+     Read arguments from <file> and overwrite it.
+
+`, os.Args[0])
+	}
+
+	var regen string
+	var skipTests bool
+	limit := StringList{}
+
+	flag.Var(&excludes, "exclude", "Exclude go package")
+	flag.Var(&excludeDeps, "exclude-dep", "Exclude go package from deps")
+	flag.Var(&excludeSrcs, "exclude-src", "Exclude go file from source lists")
+	flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
+	flag.Var(&limit, "limit", "If set, only includes the dependencies of the listed packages")
+	flag.BoolVar(&skipTests, "skip-tests", false, "Whether to skip test sources")
+	flag.StringVar(&regen, "regen", "", "Rewrite specified file")
+	flag.Parse()
+
+	if regen != "" {
+		err := rerunForRegen(regen)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			os.Exit(1)
+		}
+		os.Exit(0)
+	}
+
+	if flag.NArg() != 0 {
+		fmt.Fprintf(os.Stderr, "Unused argument detected: %v\n", flag.Args())
+		os.Exit(1)
+	}
+
+	if _, err := os.Stat("go.mod"); err != nil {
+		fmt.Fprintln(os.Stderr, "go.mod file not found")
+		os.Exit(1)
+	}
+
+	cmd := exec.Command("go", "list", "-json", "./...")
+	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(stdoutb.Bytes()))
+
+	pkgs := []*GoPackage{}
+	pkgMap := map[string]*GoPackage{}
+	for decoder.More() {
+		pkg := GoPackage{}
+		err := decoder.Decode(&pkg)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to parse json: %v\n", err)
+			os.Exit(1)
+		}
+		if len(limit) == 0 {
+			pkg.ExportToAndroid = true
+		}
+		if skipTests {
+			pkg.TestGoFiles = nil
+			pkg.TestImports = nil
+		}
+		pkgs = append(pkgs, &pkg)
+		pkgMap[pkg.ImportPath] = &pkg
+	}
+
+	buf := &bytes.Buffer{}
+
+	fmt.Fprintln(buf, "// Automatically generated with:")
+	fmt.Fprintln(buf, "// go2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
+
+	var mark func(string)
+	mark = func(pkgName string) {
+		if excludes[pkgName] {
+			return
+		}
+		if pkg, ok := pkgMap[pkgName]; ok && !pkg.ExportToAndroid {
+			pkg.ExportToAndroid = true
+			for _, dep := range pkg.AllImports() {
+				if !excludeDeps[dep] {
+					mark(dep)
+				}
+			}
+		}
+	}
+
+	for _, pkgName := range limit {
+		mark(pkgName)
+	}
+
+	for _, pkg := range pkgs {
+		if !pkg.ExportToAndroid || excludes[pkg.ImportPath] {
+			continue
+		}
+		if len(pkg.BpSrcs(pkg.GoFiles)) == 0 && len(pkg.BpSrcs(pkg.TestGoFiles)) == 0 {
+			continue
+		}
+		err := bpTemplate.Execute(buf, pkg)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Error writing", pkg.Name, err)
+			os.Exit(1)
+		}
+	}
+
+	out, err := bpfix.Reformat(buf.String())
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Error formatting output", err)
+		os.Exit(1)
+	}
+
+	os.Stdout.WriteString(out)
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/host_bionic_verify/Android.bp
similarity index 82%
rename from cmd/host_bionic_inject/Android.bp
rename to cmd/host_bionic_verify/Android.bp
index 16bc179..4e7c379 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/host_bionic_verify/Android.bp
@@ -17,8 +17,7 @@
 }
 
 blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+    name: "host_bionic_verify",
+    srcs: ["host_bionic_verify.go"],
+    testSrcs: ["host_bionic_verify_test.go"],
 }
diff --git a/cmd/host_bionic_inject/host_bionic_inject.go b/cmd/host_bionic_verify/host_bionic_verify.go
similarity index 68%
rename from cmd/host_bionic_inject/host_bionic_inject.go
rename to cmd/host_bionic_verify/host_bionic_verify.go
index ce8b062..52400a3 100644
--- a/cmd/host_bionic_inject/host_bionic_inject.go
+++ b/cmd/host_bionic_verify/host_bionic_verify.go
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Verifies a host bionic executable with an embedded linker, then injects
-// the address of the _start function for the linker_wrapper to use.
+// Verifies a host bionic executable with an embedded linker.
 package main
 
 import (
@@ -22,19 +21,16 @@
 	"fmt"
 	"io"
 	"os"
-
-	"android/soong/symbol_inject"
 )
 
 func main() {
-	var inputFile, linkerFile, outputFile string
+	var inputFile, linkerFile string
 
 	flag.StringVar(&inputFile, "i", "", "Input file")
 	flag.StringVar(&linkerFile, "l", "", "Linker file")
-	flag.StringVar(&outputFile, "o", "", "Output file")
 	flag.Parse()
 
-	if inputFile == "" || linkerFile == "" || outputFile == "" || flag.NArg() != 0 {
+	if inputFile == "" || linkerFile == "" || flag.NArg() != 0 {
 		flag.Usage()
 		os.Exit(1)
 	}
@@ -46,75 +42,52 @@
 	}
 	defer r.Close()
 
-	file, err := symbol_inject.OpenFile(r)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(3)
-	}
-
 	linker, err := elf.Open(linkerFile)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		os.Exit(4)
 	}
 
-	startAddr, err := parseElf(r, linker)
+	err = checkElf(r, linker)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		os.Exit(5)
 	}
-
-	w, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(6)
-	}
-	defer w.Close()
-
-	err = symbol_inject.InjectUint64Symbol(file, w, "__dlwrap_original_start", startAddr)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(7)
-	}
 }
 
 // Check the ELF file, and return the address to the _start function
-func parseElf(r io.ReaderAt, linker *elf.File) (uint64, error) {
+func checkElf(r io.ReaderAt, linker *elf.File) error {
 	file, err := elf.NewFile(r)
 	if err != nil {
-		return 0, err
+		return err
 	}
 
 	symbols, err := file.Symbols()
 	if err != nil {
-		return 0, err
+		return err
 	}
 
 	for _, prog := range file.Progs {
 		if prog.Type == elf.PT_INTERP {
-			return 0, fmt.Errorf("File should not have a PT_INTERP header")
+			return fmt.Errorf("File should not have a PT_INTERP header")
 		}
 	}
 
 	if dlwrap_start, err := findSymbol(symbols, "__dlwrap__start"); err != nil {
-		return 0, err
+		return err
 	} else if dlwrap_start.Value != file.Entry {
-		return 0, fmt.Errorf("Expected file entry(0x%x) to point to __dlwrap_start(0x%x)",
+		return fmt.Errorf("Expected file entry(0x%x) to point to __dlwrap_start(0x%x)",
 			file.Entry, dlwrap_start.Value)
 	}
 
 	err = checkLinker(file, linker, symbols)
 	if err != nil {
-		return 0, fmt.Errorf("Linker executable failed verification against app embedded linker: %s\n"+
+		return fmt.Errorf("Linker executable failed verification against app embedded linker: %s\n"+
 			"linker might not be in sync with crtbegin_dynamic.o.",
 			err)
 	}
 
-	start, err := findSymbol(symbols, "_start")
-	if err != nil {
-		return 0, fmt.Errorf("Failed to find _start symbol")
-	}
-	return start.Value, nil
+	return nil
 }
 
 func findSymbol(symbols []elf.Symbol, name string) (elf.Symbol, error) {
diff --git a/cmd/host_bionic_inject/host_bionic_inject_test.go b/cmd/host_bionic_verify/host_bionic_verify_test.go
similarity index 100%
rename from cmd/host_bionic_inject/host_bionic_inject_test.go
rename to cmd/host_bionic_verify/host_bionic_verify_test.go
diff --git a/cmd/multiproduct_kati/Android.bp b/cmd/multiproduct_kati/Android.bp
index 21d8e21..20ca2a3 100644
--- a/cmd/multiproduct_kati/Android.bp
+++ b/cmd/multiproduct_kati/Android.bp
@@ -19,8 +19,8 @@
 blueprint_go_binary {
     name: "multiproduct_kati",
     deps: [
-        "soong-ui-build",
         "soong-ui-logger",
+        "soong-ui-signal",
         "soong-ui-terminal",
         "soong-ui-tracer",
         "soong-zip",
@@ -31,4 +31,14 @@
     testSrcs: [
         "main_test.go",
     ],
+    linux: {
+        srcs: [
+            "main_linux.go",
+        ],
+    },
+    darwin: {
+        srcs: [
+            "main_darwin.go",
+        ],
+    },
 }
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index c7adbbd..7cb8ab7 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -15,22 +15,25 @@
 package main
 
 import (
+	"bufio"
 	"context"
 	"flag"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"log"
 	"os"
+	"os/exec"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"strings"
 	"sync"
 	"syscall"
 	"time"
 
-	"android/soong/finder"
-	"android/soong/ui/build"
 	"android/soong/ui/logger"
+	"android/soong/ui/signal"
 	"android/soong/ui/status"
 	"android/soong/ui/terminal"
 	"android/soong/ui/tracer"
@@ -155,28 +158,79 @@
 }
 
 type mpContext struct {
-	Context context.Context
-	Logger  logger.Logger
-	Status  status.ToolStatus
-	Tracer  tracer.Tracer
-	Finder  *finder.Finder
-	Config  build.Config
+	Logger logger.Logger
+	Status status.ToolStatus
 
-	LogsDir string
+	SoongUi     string
+	MainOutDir  string
+	MainLogsDir string
+}
+
+func findNamedProducts(soongUi string, log logger.Logger) []string {
+	cmd := exec.Command(soongUi, "--dumpvars-mode", "--vars=all_named_products")
+	output, err := cmd.Output()
+	if err != nil {
+		log.Fatalf("Cannot determine named products: %v", err)
+	}
+
+	rx := regexp.MustCompile(`^all_named_products='(.*)'$`)
+	match := rx.FindStringSubmatch(strings.TrimSpace(string(output)))
+	return strings.Fields(match[1])
+}
+
+// ensureEmptyFileExists ensures that the containing directory exists, and the
+// specified file exists. If it doesn't exist, it will write an empty file.
+func ensureEmptyFileExists(file string, log logger.Logger) {
+	if _, err := os.Stat(file); os.IsNotExist(err) {
+		f, err := os.Create(file)
+		if err != nil {
+			log.Fatalf("Error creating %s: %q\n", file, err)
+		}
+		f.Close()
+	} else if err != nil {
+		log.Fatalf("Error checking %s: %q\n", file, err)
+	}
+}
+
+func outDirBase() string {
+	outDirBase := os.Getenv("OUT_DIR")
+	if outDirBase == "" {
+		return "out"
+	} else {
+		return outDirBase
+	}
+}
+
+func distDir(outDir string) string {
+	if distDir := os.Getenv("DIST_DIR"); distDir != "" {
+		return filepath.Clean(distDir)
+	} else {
+		return filepath.Join(outDir, "dist")
+	}
+}
+
+func forceAnsiOutput() bool {
+	value := os.Getenv("SOONG_UI_ANSI_OUTPUT")
+	return value == "1" || value == "y" || value == "yes" || value == "on" || value == "true"
 }
 
 func main() {
 	stdio := terminal.StdioImpl{}
 
-	output := terminal.NewStatusOutput(stdio.Stdout(), "", false,
-		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
-
+	output := terminal.NewStatusOutput(stdio.Stdout(), "", false, false,
+		forceAnsiOutput())
 	log := logger.New(output)
 	defer log.Cleanup()
 
+	for _, v := range os.Environ() {
+		log.Println("Environment: " + v)
+	}
+
+	log.Printf("Argv: %v\n", os.Args)
+
 	flag.Parse()
 
-	ctx, cancel := context.WithCancel(context.Background())
+	_, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
 	trace := tracer.New(log)
@@ -189,61 +243,55 @@
 	var failures failureCount
 	stat.AddOutput(&failures)
 
-	build.SetupSignals(log, cancel, func() {
+	signal.SetupSignals(log, cancel, func() {
 		trace.Close()
 		log.Cleanup()
 		stat.Finish()
 	})
 
-	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
-		Context: ctx,
-		Logger:  log,
-		Tracer:  trace,
-		Writer:  output,
-		Status:  stat,
-	}}
+	soongUi := "build/soong/soong_ui.bash"
 
-	args := ""
-	if *alternateResultDir {
-		args = "dist"
-	}
-	config := build.NewConfig(buildCtx, args)
-	if *outDir == "" {
+	var outputDir string
+	if *outDir != "" {
+		outputDir = *outDir
+	} else {
 		name := "multiproduct"
 		if !*incremental {
 			name += "-" + time.Now().Format("20060102150405")
 		}
-
-		*outDir = filepath.Join(config.OutDir(), name)
-
-		// Ensure the empty files exist in the output directory
-		// containing our output directory too. This is mostly for
-		// safety, but also triggers the ninja_build file so that our
-		// build servers know that they can parse the output as if it
-		// was ninja output.
-		build.SetupOutDir(buildCtx, config)
-
-		if err := os.MkdirAll(*outDir, 0777); err != nil {
-			log.Fatalf("Failed to create tempdir: %v", err)
-		}
+		outputDir = filepath.Join(outDirBase(), name)
 	}
-	config.Environment().Set("OUT_DIR", *outDir)
-	log.Println("Output directory:", *outDir)
 
-	logsDir := filepath.Join(config.OutDir(), "logs")
+	log.Println("Output directory:", outputDir)
+
+	// The ninja_build file is used by our buildbots to understand that the output
+	// can be parsed as ninja output.
+	if err := os.MkdirAll(outputDir, 0777); err != nil {
+		log.Fatalf("Failed to create output directory: %v", err)
+	}
+	ensureEmptyFileExists(filepath.Join(outputDir, "ninja_build"), log)
+
+	logsDir := filepath.Join(outputDir, "logs")
 	os.MkdirAll(logsDir, 0777)
 
-	build.SetupOutDir(buildCtx, config)
+	var configLogsDir string
+	if *alternateResultDir {
+		configLogsDir = filepath.Join(distDir(outDirBase()), "logs")
+	} else {
+		configLogsDir = outputDir
+	}
 
-	os.MkdirAll(config.LogsDir(), 0777)
-	log.SetOutput(filepath.Join(config.LogsDir(), "soong.log"))
-	trace.SetOutput(filepath.Join(config.LogsDir(), "build.trace"))
+	log.Println("Logs dir: " + configLogsDir)
+
+	os.MkdirAll(configLogsDir, 0777)
+	log.SetOutput(filepath.Join(configLogsDir, "soong.log"))
+	trace.SetOutput(filepath.Join(configLogsDir, "build.trace"))
 
 	var jobs = *numJobs
 	if jobs < 1 {
 		jobs = runtime.NumCPU() / 4
 
-		ramGb := int(config.TotalRAM() / 1024 / 1024 / 1024)
+		ramGb := int(detectTotalRAM() / (1024 * 1024 * 1024))
 		if ramJobs := ramGb / 30; ramGb > 0 && jobs > ramJobs {
 			jobs = ramJobs
 		}
@@ -256,17 +304,8 @@
 
 	setMaxFiles(log)
 
-	finder := build.NewSourceFinder(buildCtx, config)
-	defer finder.Shutdown()
-
-	build.FindSources(buildCtx, config, finder)
-
-	vars, err := build.DumpMakeVars(buildCtx, config, nil, []string{"all_named_products"})
-	if err != nil {
-		log.Fatal(err)
-	}
+	allProducts := findNamedProducts(soongUi, log)
 	var productsList []string
-	allProducts := strings.Fields(vars["all_named_products"])
 
 	if len(includeProducts) > 0 {
 		var missingProducts []string
@@ -314,19 +353,15 @@
 
 	log.Verbose("Got product list: ", finalProductsList)
 
-	s := buildCtx.Status.StartTool()
+	s := stat.StartTool()
 	s.SetTotalActions(len(finalProductsList))
 
 	mpCtx := &mpContext{
-		Context: ctx,
-		Logger:  log,
-		Status:  s,
-		Tracer:  trace,
-
-		Finder: finder,
-		Config: config,
-
-		LogsDir: logsDir,
+		Logger:      log,
+		Status:      s,
+		SoongUi:     soongUi,
+		MainOutDir:  outputDir,
+		MainLogsDir: logsDir,
 	}
 
 	products := make(chan string, len(productsList))
@@ -348,7 +383,7 @@
 					if product == "" {
 						return
 					}
-					buildProduct(mpCtx, product)
+					runSoongUiForProduct(mpCtx, product)
 				}
 			}
 		}()
@@ -360,10 +395,11 @@
 			FileArgs: []zip.FileArg{
 				{GlobDir: logsDir, SourcePrefixToStrip: logsDir},
 			},
-			OutputFilePath:   filepath.Join(config.RealDistDir(), "logs.zip"),
+			OutputFilePath:   filepath.Join(distDir(outDirBase()), "logs.zip"),
 			NumParallelJobs:  runtime.NumCPU(),
 			CompressionLevel: 5,
 		}
+		log.Printf("Logs zip: %v\n", args.OutputFilePath)
 		if err := zip.Zip(args); err != nil {
 			log.Fatalf("Error zipping logs: %v", err)
 		}
@@ -371,20 +407,42 @@
 
 	s.Finish()
 
-	if failures == 1 {
+	if failures.count == 1 {
 		log.Fatal("1 failure")
-	} else if failures > 1 {
-		log.Fatalf("%d failures", failures)
+	} else if failures.count > 1 {
+		log.Fatalf("%d failures %q", failures.count, failures.fails)
 	} else {
 		fmt.Fprintln(output, "Success")
 	}
 }
 
-func buildProduct(mpctx *mpContext, product string) {
-	var stdLog string
+func cleanupAfterProduct(outDir, productZip string) {
+	if *keepArtifacts {
+		args := zip.ZipArgs{
+			FileArgs: []zip.FileArg{
+				{
+					GlobDir:             outDir,
+					SourcePrefixToStrip: outDir,
+				},
+			},
+			OutputFilePath:   productZip,
+			NumParallelJobs:  runtime.NumCPU(),
+			CompressionLevel: 5,
+		}
+		if err := zip.Zip(args); err != nil {
+			log.Fatalf("Error zipping artifacts: %v", err)
+		}
+	}
+	if !*incremental {
+		os.RemoveAll(outDir)
+	}
+}
 
-	outDir := filepath.Join(mpctx.Config.OutDir(), product)
-	logsDir := filepath.Join(mpctx.LogsDir, product)
+func runSoongUiForProduct(mpctx *mpContext, product string) {
+	outDir := filepath.Join(mpctx.MainOutDir, product)
+	logsDir := filepath.Join(mpctx.MainLogsDir, product)
+	productZip := filepath.Join(mpctx.MainOutDir, product+".zip")
+	consoleLogPath := filepath.Join(logsDir, "std.log")
 
 	if err := os.MkdirAll(outDir, 0777); err != nil {
 		mpctx.Logger.Fatalf("Error creating out directory: %v", err)
@@ -393,114 +451,94 @@
 		mpctx.Logger.Fatalf("Error creating log directory: %v", err)
 	}
 
-	stdLog = filepath.Join(logsDir, "std.log")
-	f, err := os.Create(stdLog)
+	consoleLogFile, err := os.Create(consoleLogPath)
 	if err != nil {
-		mpctx.Logger.Fatalf("Error creating std.log: %v", err)
+		mpctx.Logger.Fatalf("Error creating console log file: %v", err)
 	}
-	defer f.Close()
+	defer consoleLogFile.Close()
 
-	log := logger.New(f)
-	defer log.Cleanup()
-	log.SetOutput(filepath.Join(logsDir, "soong.log"))
+	consoleLogWriter := bufio.NewWriter(consoleLogFile)
+	defer consoleLogWriter.Flush()
+
+	args := []string{"--make-mode", "--skip-soong-tests", "--skip-ninja"}
+
+	if !*keepArtifacts {
+		args = append(args, "--empty-ninja-file")
+	}
+
+	if *onlyConfig {
+		args = append(args, "--config-only")
+	} else if *onlySoong {
+		args = append(args, "--soong-only")
+	}
+
+	cmd := exec.Command(mpctx.SoongUi, args...)
+	cmd.Stdout = consoleLogWriter
+	cmd.Stderr = consoleLogWriter
+	cmd.Env = append(os.Environ(),
+		"OUT_DIR="+outDir,
+		"TARGET_PRODUCT="+product,
+		"TARGET_BUILD_VARIANT="+*buildVariant,
+		"TARGET_BUILD_TYPE=release",
+		"TARGET_BUILD_APPS=",
+		"TARGET_BUILD_UNBUNDLED=")
+
+	if *alternateResultDir {
+		cmd.Env = append(cmd.Env,
+			"DIST_DIR="+filepath.Join(distDir(outDirBase()), "products/"+product))
+	}
 
 	action := &status.Action{
 		Description: product,
 		Outputs:     []string{product},
 	}
+
 	mpctx.Status.StartAction(action)
-	defer logger.Recover(func(err error) {
-		mpctx.Status.FinishAction(status.ActionResult{
-			Action: action,
-			Error:  err,
-			Output: errMsgFromLog(stdLog),
-		})
-	})
-
-	ctx := build.Context{ContextImpl: &build.ContextImpl{
-		Context: mpctx.Context,
-		Logger:  log,
-		Tracer:  mpctx.Tracer,
-		Writer:  f,
-		Thread:  mpctx.Tracer.NewThread(product),
-		Status:  &status.Status{},
-	}}
-	ctx.Status.AddOutput(terminal.NewStatusOutput(ctx.Writer, "", false,
-		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
-
-	args := append([]string(nil), flag.Args()...)
-	args = append(args, "--skip-soong-tests")
-	config := build.NewConfig(ctx, args...)
-	config.Environment().Set("OUT_DIR", outDir)
-	if !*keepArtifacts {
-		config.SetEmptyNinjaFile(true)
-	}
-	build.FindSources(ctx, config, mpctx.Finder)
-	config.Lunch(ctx, product, *buildVariant)
-
-	defer func() {
-		if *keepArtifacts {
-			args := zip.ZipArgs{
-				FileArgs: []zip.FileArg{
-					{
-						GlobDir:             outDir,
-						SourcePrefixToStrip: outDir,
-					},
-				},
-				OutputFilePath:   filepath.Join(mpctx.Config.OutDir(), product+".zip"),
-				NumParallelJobs:  runtime.NumCPU(),
-				CompressionLevel: 5,
-			}
-			if err := zip.Zip(args); err != nil {
-				log.Fatalf("Error zipping artifacts: %v", err)
-			}
-		}
-		if !*incremental {
-			os.RemoveAll(outDir)
-		}
-	}()
-
-	config.SetSkipNinja(true)
-
-	buildWhat := build.RunProductConfig
-	if !*onlyConfig {
-		buildWhat |= build.RunSoong
-		if !*onlySoong {
-			buildWhat |= build.RunKati
-		}
-	}
+	defer cleanupAfterProduct(outDir, productZip)
 
 	before := time.Now()
-	build.Build(ctx, config)
+	err = cmd.Run()
 
-	// Save std_full.log if Kati re-read the makefiles
-	if buildWhat&build.RunKati != 0 {
-		if after, err := os.Stat(config.KatiBuildNinjaFile()); err == nil && after.ModTime().After(before) {
-			err := copyFile(stdLog, filepath.Join(filepath.Dir(stdLog), "std_full.log"))
+	if !*onlyConfig && !*onlySoong {
+		katiBuildNinjaFile := filepath.Join(outDir, "build-"+product+".ninja")
+		if after, err := os.Stat(katiBuildNinjaFile); err == nil && after.ModTime().After(before) {
+			err := copyFile(consoleLogPath, filepath.Join(filepath.Dir(consoleLogPath), "std_full.log"))
 			if err != nil {
 				log.Fatalf("Error copying log file: %s", err)
 			}
 		}
 	}
+	var errOutput string
+	if err == nil {
+		errOutput = ""
+	} else {
+		errOutput = errMsgFromLog(consoleLogPath)
+	}
 
 	mpctx.Status.FinishAction(status.ActionResult{
 		Action: action,
+		Error:  err,
+		Output: errOutput,
 	})
 }
 
-type failureCount int
+type failureCount struct {
+	count int
+	fails []string
+}
 
 func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
 
 func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
 	if result.Error != nil {
-		*f += 1
+		f.count += 1
+		f.fails = append(f.fails, result.Action.Description)
 	}
 }
 
 func (f *failureCount) Message(level status.MsgLevel, message string) {
 	if level >= status.ErrorLvl {
-		*f += 1
+		f.count += 1
 	}
 }
 
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/multiproduct_kati/main_darwin.go
similarity index 65%
copy from cmd/host_bionic_inject/Android.bp
copy to cmd/multiproduct_kati/main_darwin.go
index 16bc179..3d1b12a 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/multiproduct_kati/main_darwin.go
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// Copyright 2017 Google Inc. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,13 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+package main
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+func detectTotalRAM() uint64 {
+	// unimplemented stub on darwin
+	return 0
 }
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/multiproduct_kati/main_linux.go
similarity index 65%
copy from cmd/host_bionic_inject/Android.bp
copy to cmd/multiproduct_kati/main_linux.go
index 16bc179..db74496 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/multiproduct_kati/main_linux.go
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// Copyright 2017 Google Inc. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,13 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+package main
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+import (
+	"syscall"
+)
+
+func detectTotalRAM() uint64 {
+	var info syscall.Sysinfo_t
+	err := syscall.Sysinfo(&info)
+	if err != nil {
+		panic(err)
+	}
+	return info.Totalram * uint64(info.Unit)
 }
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index d31489e..f8844fc 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -24,6 +24,7 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -93,6 +94,8 @@
 
 var extraLibs = make(ExtraDeps)
 
+var optionalUsesLibs = make(ExtraDeps)
+
 type Exclude map[string]bool
 
 func (e Exclude) String() string {
@@ -162,7 +165,8 @@
 type Dependency struct {
 	XMLName xml.Name `xml:"dependency"`
 
-	BpTarget string `xml:"-"`
+	BpTarget    string `xml:"-"`
+	BazelTarget string `xml:"-"`
 
 	GroupId    string `xml:"groupId"`
 	ArtifactId string `xml:"artifactId"`
@@ -228,6 +232,14 @@
 	}
 }
 
+func (p Pom) BazelTargetType() string {
+	if p.IsAar() {
+		return "android_library"
+	} else {
+		return "java_library"
+	}
+}
+
 func (p Pom) ImportModuleType() string {
 	if p.IsAar() {
 		return "android_library_import"
@@ -238,6 +250,14 @@
 	}
 }
 
+func (p Pom) BazelImportTargetType() string {
+	if p.IsAar() {
+		return "aar_import"
+	} else {
+		return "java_import"
+	}
+}
+
 func (p Pom) ImportProperty() string {
 	if p.IsAar() {
 		return "aars"
@@ -246,6 +266,14 @@
 	}
 }
 
+func (p Pom) BazelImportProperty() string {
+	if p.IsAar() {
+		return "aar"
+	} else {
+		return "jars"
+	}
+}
+
 func (p Pom) BpName() string {
 	if p.BpTarget == "" {
 		p.BpTarget = rewriteNames.MavenToBp(p.GroupId, p.ArtifactId)
@@ -261,6 +289,14 @@
 	return p.BpDeps("aar", []string{"compile", "runtime"})
 }
 
+func (p Pom) BazelJarDeps() []string {
+	return p.BazelDeps("jar", []string{"compile", "runtime"})
+}
+
+func (p Pom) BazelAarDeps() []string {
+	return p.BazelDeps("aar", []string{"compile", "runtime"})
+}
+
 func (p Pom) BpExtraStaticLibs() []string {
 	return extraStaticLibs[p.BpName()]
 }
@@ -269,6 +305,10 @@
 	return extraLibs[p.BpName()]
 }
 
+func (p Pom) BpOptionalUsesLibs() []string {
+	return optionalUsesLibs[p.BpName()]
+}
+
 // BpDeps obtains dependencies filtered by type and scope. The results of this
 // method are formatted as Android.bp targets, e.g. run through MavenToBp rules.
 func (p Pom) BpDeps(typeExt string, scopes []string) []string {
@@ -283,6 +323,91 @@
 	return ret
 }
 
+// BazelDeps obtains dependencies filtered by type and scope. The results of this
+// method are formatted as Bazel BUILD targets.
+func (p Pom) BazelDeps(typeExt string, scopes []string) []string {
+	var ret []string
+	for _, d := range p.Dependencies {
+		if d.Type != typeExt || !InList(d.Scope, scopes) {
+			continue
+		}
+		ret = append(ret, d.BazelTarget)
+	}
+	return ret
+}
+
+func PathModVars() (string, string, string) {
+	cmd := "/bin/bash"
+	androidTop := os.Getenv("ANDROID_BUILD_TOP")
+	envSetupSh := path.Join(androidTop, "build/envsetup.sh")
+	return cmd, androidTop, envSetupSh
+}
+
+func InitRefreshMod(poms []*Pom) error {
+	cmd, _, envSetupSh := PathModVars()
+	// refreshmod is expensive, so if pathmod is already working we can skip it.
+	_, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && pathmod "+poms[0].BpName()).Output()
+	if exitErr, _ := err.(*exec.ExitError); exitErr != nil || err != nil {
+		_, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && refreshmod").Output()
+		if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+			return fmt.Errorf("failed to run %s\n%s\ntry running lunch.", cmd, string(exitErr.Stderr))
+		} else if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func BazelifyExtraDeps(extraDeps ExtraDeps, modules map[string]*Pom) error {
+	for _, deps := range extraDeps {
+		for _, dep := range deps {
+			bazelName, err := BpNameToBazelTarget(dep, modules)
+			if err != nil {
+				return err
+			}
+			dep = bazelName
+		}
+
+	}
+	return nil
+}
+
+func (p *Pom) GetBazelDepNames(modules map[string]*Pom) error {
+	for _, d := range p.Dependencies {
+		bazelName, err := BpNameToBazelTarget(d.BpName(), modules)
+		if err != nil {
+			return err
+		}
+		d.BazelTarget = bazelName
+	}
+	return nil
+}
+
+func BpNameToBazelTarget(bpName string, modules map[string]*Pom) (string, error) {
+	cmd, androidTop, envSetupSh := PathModVars()
+
+	if _, ok := modules[bpName]; ok {
+		// We've seen the POM for this dependency, it will be local to the output BUILD file
+		return ":" + bpName, nil
+	} else {
+		// we don't have the POM for this artifact, find and use the fully qualified target name.
+		output, err := exec.Command(cmd, "-c", ". "+envSetupSh+" && pathmod "+bpName).Output()
+		if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+			return "", fmt.Errorf("failed to run %s %s\n%s", cmd, bpName, string(exitErr.Stderr))
+		} else if err != nil {
+			return "", err
+		}
+		relPath := ""
+		for _, line := range strings.Fields(string(output)) {
+			if strings.Contains(line, androidTop) {
+				relPath = strings.TrimPrefix(line, androidTop)
+				relPath = strings.TrimLeft(relPath, "/")
+			}
+		}
+		return "//" + relPath + ":" + bpName, nil
+	}
+}
+
 func (p Pom) SdkVersion() string {
 	return sdkVersion
 }
@@ -401,6 +526,13 @@
         {{- end}}
     ],
     {{- end}}
+    {{- if .BpOptionalUsesLibs}}
+    optional_uses_libs: [
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
     {{- else if not .IsHostOnly}}
     min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
@@ -488,10 +620,69 @@
         {{- end}}
     ],
     {{- end}}
+    {{- if .BpOptionalUsesLibs}}
+    optional_uses_libs: [
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
     java_version: "1.7",
 }
 `))
 
+var bazelTemplate = template.Must(template.New("bp").Parse(`
+{{.BazelImportTargetType}} (
+    name = "{{.BpName}}",
+    {{.BazelImportProperty}}: {{- if not .IsAar}}[{{- end}}"{{.ArtifactFile}}"{{- if not .IsAar}}]{{- end}},
+    visibility = ["//visibility:public"],
+    {{- if .IsAar}}
+    deps = [
+        {{- range .BazelJarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BazelAarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraStaticLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+)
+`))
+
+var bazelDepsTemplate = template.Must(template.New("bp").Parse(`
+{{.BazelImportTargetType}} (
+    name = "{{.BpName}}",
+    {{.BazelImportProperty}} = {{- if not .IsAar}}[{{- end}}"{{.ArtifactFile}}"{{- if not .IsAar}}]{{- end}},
+    visibility = ["//visibility:public"],
+    exports = [
+        {{- range .BazelJarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BazelAarDeps}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraStaticLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpExtraLibs}}
+        "{{.}}",
+        {{- end}}
+        {{- range .BpOptionalUsesLibs}}
+        "{{.}}",
+        {{- end}}
+    ],
+)
+`))
+
 func parse(filename string) (*Pom, error) {
 	data, err := ioutil.ReadFile(filename)
 	if err != nil {
@@ -539,12 +730,14 @@
 
 	// Extract the old args from the file
 	line := scanner.Text()
-	if strings.HasPrefix(line, "// pom2bp ") {
+	if strings.HasPrefix(line, "// pom2bp ") { // .bp file
 		line = strings.TrimPrefix(line, "// pom2bp ")
-	} else if strings.HasPrefix(line, "// pom2mk ") {
+	} else if strings.HasPrefix(line, "// pom2mk ") { // .bp file converted from .mk file
 		line = strings.TrimPrefix(line, "// pom2mk ")
-	} else if strings.HasPrefix(line, "# pom2mk ") {
+	} else if strings.HasPrefix(line, "# pom2mk ") { // .mk file
 		line = strings.TrimPrefix(line, "# pom2mk ")
+	} else if strings.HasPrefix(line, "# pom2bp ") { // Bazel BUILD file
+		line = strings.TrimPrefix(line, "# pom2bp ")
 	} else {
 		return fmt.Errorf("unexpected second line: %q", line)
 	}
@@ -587,7 +780,7 @@
 The tool will extract the necessary information from *.pom files to create an Android.bp whose
 aar libraries can be linked against when using AAPT2.
 
-Usage: %s [--rewrite <regex>=<replace>] [-exclude <module>] [--extra-static-libs <module>=<module>[,<module>]] [--extra-libs <module>=<module>[,<module>]] [<dir>] [-regen <file>]
+Usage: %s [--rewrite <regex>=<replace>] [--exclude <module>] [--extra-static-libs <module>=<module>[,<module>]] [--extra-libs <module>=<module>[,<module>]] [--optional-uses-libs <module>=<module>[,<module>]] [<dir>] [-regen <file>]
 
   -rewrite <regex>=<replace>
      rewrite can be used to specify mappings between Maven projects and Android.bp modules. The -rewrite
@@ -605,6 +798,11 @@
      Some Android.bp modules have transitive runtime dependencies that must be specified when they
      are depended upon (like androidx.test.rules requires android.test.base).
      This may be specified multiple times to declare these dependencies.
+  -optional-uses-libs <module>=<module>[,<module>]
+     Some Android.bp modules have optional dependencies (typically specified with <uses-library> in
+     the module's AndroidManifest.xml) that must be specified when they are depended upon (like
+     androidx.window:window optionally requires androidx.window:window-extensions).
+     This may be specified multiple times to declare these dependencies.
   -sdk-version <version>
      Sets sdk_version: "<version>" for all modules.
   -default-min-sdk-version
@@ -625,10 +823,12 @@
 	}
 
 	var regen string
+	var pom2build bool
 
 	flag.Var(&excludes, "exclude", "Exclude module")
 	flag.Var(&extraStaticLibs, "extra-static-libs", "Extra static dependencies needed when depending on a module")
 	flag.Var(&extraLibs, "extra-libs", "Extra runtime dependencies needed when depending on a module")
+	flag.Var(&optionalUsesLibs, "optional-uses-libs", "Extra optional dependencies needed when depending on a module")
 	flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
 	flag.Var(&hostModuleNames, "host", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is a host module")
 	flag.Var(&hostAndDeviceModuleNames, "host-and-device", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is both a host and device module.")
@@ -638,6 +838,7 @@
 	flag.BoolVar(&staticDeps, "static-deps", false, "Statically include direct dependencies")
 	flag.BoolVar(&jetifier, "jetifier", false, "Sets jetifier: true on all modules")
 	flag.StringVar(&regen, "regen", "", "Rewrite specified file")
+	flag.BoolVar(&pom2build, "pom2build", false, "If true, will generate a Bazel BUILD file *instead* of a .bp file")
 	flag.Parse()
 
 	if regen != "" {
@@ -732,6 +933,16 @@
 		os.Exit(1)
 	}
 
+	if pom2build {
+		if err := InitRefreshMod(poms); err != nil {
+			fmt.Fprintf(os.Stderr, "Error in refreshmod: %s", err)
+			os.Exit(1)
+		}
+		BazelifyExtraDeps(extraStaticLibs, modules)
+		BazelifyExtraDeps(extraLibs, modules)
+		BazelifyExtraDeps(optionalUsesLibs, modules)
+	}
+
 	for _, pom := range poms {
 		if pom.IsAar() {
 			err := pom.ExtractMinSdkVersion()
@@ -741,19 +952,32 @@
 			}
 		}
 		pom.FixDeps(modules)
+		if pom2build {
+			pom.GetBazelDepNames(modules)
+		}
 	}
 
 	buf := &bytes.Buffer{}
+	commentString := "//"
+	if pom2build {
+		commentString = "#"
+	}
+	fmt.Fprintln(buf, commentString, "Automatically generated with:")
+	fmt.Fprintln(buf, commentString, "pom2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
 
-	fmt.Fprintln(buf, "// Automatically generated with:")
-	fmt.Fprintln(buf, "// pom2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
+	depsTemplate := bpDepsTemplate
+	template := bpTemplate
+	if pom2build {
+		depsTemplate = bazelDepsTemplate
+		template = bazelTemplate
+	}
 
 	for _, pom := range poms {
 		var err error
 		if staticDeps {
-			err = bpDepsTemplate.Execute(buf, pom)
+			err = depsTemplate.Execute(buf, pom)
 		} else {
-			err = bpTemplate.Execute(buf, pom)
+			err = template.Execute(buf, pom)
 		}
 		if err != nil {
 			fmt.Fprintln(os.Stderr, "Error writing", pom.PomFile, pom.BpName(), err)
@@ -761,11 +985,15 @@
 		}
 	}
 
-	out, err := bpfix.Reformat(buf.String())
-	if err != nil {
-		fmt.Fprintln(os.Stderr, "Error formatting output", err)
-		os.Exit(1)
+	if pom2build {
+		os.Stdout.WriteString(buf.String())
+	} else {
+		out, err := bpfix.Reformat(buf.String())
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Error formatting output", err)
+			os.Exit(1)
+		}
+		os.Stdout.WriteString(out)
 	}
 
-	os.Stdout.WriteString(out)
 }
diff --git a/cmd/run_with_timeout/run_with_timeout.go b/cmd/run_with_timeout/run_with_timeout.go
index f2caaab..deaa842 100644
--- a/cmd/run_with_timeout/run_with_timeout.go
+++ b/cmd/run_with_timeout/run_with_timeout.go
@@ -47,7 +47,7 @@
 	flag.Parse()
 
 	if flag.NArg() < 1 {
-		fmt.Fprintln(os.Stderr, "command is required")
+		fmt.Fprintf(os.Stderr, "%s: error: command is required\n", os.Args[0])
 		usage()
 	}
 
@@ -55,9 +55,9 @@
 		os.Stdin, os.Stdout, os.Stderr)
 	if err != nil {
 		if exitErr, ok := err.(*exec.ExitError); ok {
-			fmt.Fprintln(os.Stderr, "process exited with error:", exitErr.Error())
+			fmt.Fprintf(os.Stderr, "%s: process exited with error: %s\n", os.Args[0], exitErr.Error())
 		} else {
-			fmt.Fprintln(os.Stderr, "error:", err.Error())
+			fmt.Fprintf(os.Stderr, "%s: error: %s\n", os.Args[0], err.Error())
 		}
 		os.Exit(1)
 	}
@@ -115,6 +115,7 @@
 	if timeout > 0 {
 		timeoutCh = time.After(timeout)
 	}
+	startTime := time.Now()
 
 	select {
 	case err := <-waitCh:
@@ -126,10 +127,12 @@
 		// Continue below.
 	}
 
+	fmt.Fprintf(concurrentStderr, "%s: process timed out after %s\n", os.Args[0], time.Since(startTime))
 	// Process timed out before exiting.
 	defer cmd.Process.Signal(syscall.SIGKILL)
 
 	if onTimeoutCmdStr != "" {
+		fmt.Fprintf(concurrentStderr, "%s: running on_timeout command `%s`\n", os.Args[0], onTimeoutCmdStr)
 		onTimeoutCmd := exec.Command("sh", "-c", onTimeoutCmdStr)
 		onTimeoutCmd.Stdin, onTimeoutCmd.Stdout, onTimeoutCmd.Stderr = stdin, concurrentStdout, concurrentStderr
 		onTimeoutCmd.Env = append(os.Environ(), fmt.Sprintf("PID=%d", cmd.Process.Pid))
diff --git a/cmd/run_with_timeout/run_with_timeout_test.go b/cmd/run_with_timeout/run_with_timeout_test.go
index aebd336..6729e61 100644
--- a/cmd/run_with_timeout/run_with_timeout_test.go
+++ b/cmd/run_with_timeout/run_with_timeout_test.go
@@ -17,6 +17,7 @@
 import (
 	"bytes"
 	"io"
+	"regexp"
 	"testing"
 	"time"
 )
@@ -49,7 +50,7 @@
 			args: args{
 				command: "echo",
 				args:    []string{"foo"},
-				timeout: 1 * time.Second,
+				timeout: 10 * time.Second,
 			},
 			wantStdout: "foo\n",
 		},
@@ -57,20 +58,22 @@
 			name: "timed out",
 			args: args{
 				command: "sh",
-				args:    []string{"-c", "sleep 1 && echo foo"},
+				args:    []string{"-c", "sleep 10 && echo foo"},
 				timeout: 1 * time.Millisecond,
 			},
-			wantErr: true,
+			wantStderr: ".*: process timed out after .*\n",
+			wantErr:    true,
 		},
 		{
 			name: "on_timeout command",
 			args: args{
 				command:      "sh",
-				args:         []string{"-c", "sleep 1 && echo foo"},
+				args:         []string{"-c", "sleep 10 && echo foo"},
 				timeout:      1 * time.Millisecond,
 				onTimeoutCmd: "echo bar",
 			},
 			wantStdout: "bar\n",
+			wantStderr: ".*: process timed out after .*\n.*: running on_timeout command `echo bar`\n",
 			wantErr:    true,
 		},
 	}
@@ -86,7 +89,7 @@
 			if gotStdout := stdout.String(); gotStdout != tt.wantStdout {
 				t.Errorf("runWithTimeout() gotStdout = %v, want %v", gotStdout, tt.wantStdout)
 			}
-			if gotStderr := stderr.String(); gotStderr != tt.wantStderr {
+			if gotStderr := stderr.String(); !regexp.MustCompile(tt.wantStderr).MatchString(gotStderr) {
 				t.Errorf("runWithTimeout() gotStderr = %v, want %v", gotStderr, tt.wantStderr)
 			}
 		})
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index b8d75ed..454cfd8 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -19,6 +19,7 @@
 blueprint_go_binary {
     name: "sbox",
     deps: [
+        "golang-protobuf-encoding-prototext",
         "sbox_proto",
         "soong-makedeps",
         "soong-response",
@@ -31,7 +32,10 @@
 bootstrap_go_package {
     name: "sbox_proto",
     pkgPath: "android/soong/cmd/sbox/sbox_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "sbox_proto/sbox.pb.go",
     ],
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index f124e40..4f7451d 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -34,13 +34,15 @@
 	"android/soong/makedeps"
 	"android/soong/response"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/encoding/prototext"
 )
 
 var (
-	sandboxesRoot string
-	manifestFile  string
-	keepOutDir    bool
+	sandboxesRoot  string
+	outputDir      string
+	manifestFile   string
+	keepOutDir     bool
+	writeIfChanged bool
 )
 
 const (
@@ -51,10 +53,14 @@
 func init() {
 	flag.StringVar(&sandboxesRoot, "sandbox-path", "",
 		"root of temp directory to put the sandbox into")
+	flag.StringVar(&outputDir, "output-dir", "",
+		"directory which will contain all output files and only output files")
 	flag.StringVar(&manifestFile, "manifest", "",
 		"textproto manifest describing the sandboxed command(s)")
 	flag.BoolVar(&keepOutDir, "keep-out-dir", false,
 		"whether to keep the sandbox directory when done")
+	flag.BoolVar(&writeIfChanged, "write-if-changed", false,
+		"only write the output files if they have changed")
 }
 
 func usageViolation(violation string) {
@@ -164,7 +170,7 @@
 		if useSubDir {
 			localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
 		}
-		depFile, err := runCommand(command, localTempDir)
+		depFile, err := runCommand(command, localTempDir, i)
 		if err != nil {
 			// Running the command failed, keep the temporary output directory around in
 			// case a user wants to inspect it for debugging purposes.  Soong will delete
@@ -194,6 +200,24 @@
 	return nil
 }
 
+// createCommandScript will create and return an exec.Cmd that runs rawCommand.
+//
+// rawCommand is executed via a script in the sandbox.
+// scriptPath is the temporary where the script is created.
+// scriptPathInSandbox is the path to the script in the sbox environment.
+//
+// returns an exec.Cmd that can be ran from within sbox context if no error, or nil if error.
+// caller must ensure script is cleaned up if function succeeds.
+//
+func createCommandScript(rawCommand, scriptPath, scriptPathInSandbox string) (*exec.Cmd, error) {
+	err := os.WriteFile(scriptPath, []byte(rawCommand), 0644)
+	if err != nil {
+		return nil, fmt.Errorf("failed to write command %s... to %s",
+			rawCommand[0:40], scriptPath)
+	}
+	return exec.Command("bash", scriptPathInSandbox), nil
+}
+
 // readManifest reads an sbox manifest from a textproto file.
 func readManifest(file string) (*sbox_proto.Manifest, error) {
 	manifestData, err := ioutil.ReadFile(file)
@@ -203,7 +227,7 @@
 
 	manifest := sbox_proto.Manifest{}
 
-	err = proto.UnmarshalText(string(manifestData), &manifest)
+	err = prototext.Unmarshal(manifestData, &manifest)
 	if err != nil {
 		return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
 	}
@@ -213,12 +237,18 @@
 
 // runCommand runs a single command from a manifest.  If the command references the
 // __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
-func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
+func runCommand(command *sbox_proto.Command, tempDir string, commandIndex int) (depFile string, err error) {
 	rawCommand := command.GetCommand()
 	if rawCommand == "" {
 		return "", fmt.Errorf("command is required")
 	}
 
+	// Remove files from the output directory
+	err = clearOutputDirectory(command.CopyAfter, outputDir, writeType(writeIfChanged))
+	if err != nil {
+		return "", err
+	}
+
 	pathToTempDirInSbox := tempDir
 	if command.GetChdir() {
 		pathToTempDirInSbox = "."
@@ -230,7 +260,7 @@
 	}
 
 	// Copy in any files specified by the manifest.
-	err = copyFiles(command.CopyBefore, "", tempDir, false)
+	err = copyFiles(command.CopyBefore, "", tempDir, requireFromExists, alwaysWrite)
 	if err != nil {
 		return "", err
 	}
@@ -255,7 +285,14 @@
 		return "", err
 	}
 
-	cmd := exec.Command("bash", "-c", rawCommand)
+	scriptName := fmt.Sprintf("sbox_command.%d.bash", commandIndex)
+	scriptPath := joinPath(tempDir, scriptName)
+	scriptPathInSandbox := joinPath(pathToTempDirInSbox, scriptName)
+	cmd, err := createCommandScript(rawCommand, scriptPath, scriptPathInSandbox)
+	if err != nil {
+		return "", err
+	}
+
 	buf := &bytes.Buffer{}
 	cmd.Stdin = os.Stdin
 	cmd.Stdout = buf
@@ -280,7 +317,7 @@
 		// especially useful for linters with baselines that print an error message on failure
 		// with a command to copy the output lint errors to the new baseline.  Use a copy instead of
 		// a move to leave the sandbox intact for manual inspection
-		copyFiles(command.CopyAfter, tempDir, "", true)
+		copyFiles(command.CopyAfter, tempDir, "", allowFromNotExists, writeType(writeIfChanged))
 	}
 
 	// If the command  was executed but failed with an error, print a debugging message before
@@ -289,9 +326,9 @@
 		fmt.Fprintf(os.Stderr,
 			"The failing command was run inside an sbox sandbox in temporary directory\n"+
 				"%s\n"+
-				"The failing command line was:\n"+
+				"The failing command line can be found in\n"+
 				"%s\n",
-			tempDir, rawCommand)
+			tempDir, scriptPath)
 	}
 
 	// Write the command's combined stdout/stderr.
@@ -301,39 +338,16 @@
 		return "", err
 	}
 
-	missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
-
-	if len(missingOutputErrors) > 0 {
-		// find all created files for making a more informative error message
-		createdFiles := findAllFilesUnder(tempDir)
-
-		// build error message
-		errorMessage := "mismatch between declared and actual outputs\n"
-		errorMessage += "in sbox command(" + rawCommand + ")\n\n"
-		errorMessage += "in sandbox " + tempDir + ",\n"
-		errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
-		for _, missingOutputError := range missingOutputErrors {
-			errorMessage += "  " + missingOutputError.Error() + "\n"
-		}
-		if len(createdFiles) < 1 {
-			errorMessage += "created 0 files."
-		} else {
-			errorMessage += fmt.Sprintf("did create %v files:\n", len(createdFiles))
-			creationMessages := createdFiles
-			maxNumCreationLines := 10
-			if len(creationMessages) > maxNumCreationLines {
-				creationMessages = creationMessages[:maxNumCreationLines]
-				creationMessages = append(creationMessages, fmt.Sprintf("...%v more", len(createdFiles)-maxNumCreationLines))
-			}
-			for _, creationMessage := range creationMessages {
-				errorMessage += "  " + creationMessage + "\n"
-			}
-		}
-
-		return "", errors.New(errorMessage)
+	err = validateOutputFiles(command.CopyAfter, tempDir, outputDir, rawCommand)
+	if err != nil {
+		return "", err
 	}
+
 	// the created files match the declared files; now move them
-	err = moveFiles(command.CopyAfter, tempDir, "")
+	err = moveFiles(command.CopyAfter, tempDir, "", writeType(writeIfChanged))
+	if err != nil {
+		return "", err
+	}
 
 	return depFile, nil
 }
@@ -354,8 +368,9 @@
 
 // validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
 // were created by the command.
-func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
+func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir, outputDir, rawCommand string) error {
 	var missingOutputErrors []error
+	var incorrectOutputDirectoryErrors []error
 	for _, copyPair := range copies {
 		fromPath := joinPath(sandboxDir, copyPair.GetFrom())
 		fileInfo, err := os.Stat(fromPath)
@@ -366,17 +381,91 @@
 		if fileInfo.IsDir() {
 			missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
 		}
+
+		toPath := copyPair.GetTo()
+		if rel, err := filepath.Rel(outputDir, toPath); err != nil {
+			return err
+		} else if strings.HasPrefix(rel, "../") {
+			incorrectOutputDirectoryErrors = append(incorrectOutputDirectoryErrors,
+				fmt.Errorf("%s is not under %s", toPath, outputDir))
+		}
 	}
-	return missingOutputErrors
+
+	const maxErrors = 10
+
+	if len(incorrectOutputDirectoryErrors) > 0 {
+		errorMessage := ""
+		more := 0
+		if len(incorrectOutputDirectoryErrors) > maxErrors {
+			more = len(incorrectOutputDirectoryErrors) - maxErrors
+			incorrectOutputDirectoryErrors = incorrectOutputDirectoryErrors[:maxErrors]
+		}
+
+		for _, err := range incorrectOutputDirectoryErrors {
+			errorMessage += err.Error() + "\n"
+		}
+		if more > 0 {
+			errorMessage += fmt.Sprintf("...%v more", more)
+		}
+
+		return errors.New(errorMessage)
+	}
+
+	if len(missingOutputErrors) > 0 {
+		// find all created files for making a more informative error message
+		createdFiles := findAllFilesUnder(sandboxDir)
+
+		// build error message
+		errorMessage := "mismatch between declared and actual outputs\n"
+		errorMessage += "in sbox command(" + rawCommand + ")\n\n"
+		errorMessage += "in sandbox " + sandboxDir + ",\n"
+		errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
+		for _, missingOutputError := range missingOutputErrors {
+			errorMessage += "  " + missingOutputError.Error() + "\n"
+		}
+		if len(createdFiles) < 1 {
+			errorMessage += "created 0 files."
+		} else {
+			errorMessage += fmt.Sprintf("did create %v files:\n", len(createdFiles))
+			creationMessages := createdFiles
+			if len(creationMessages) > maxErrors {
+				creationMessages = creationMessages[:maxErrors]
+				creationMessages = append(creationMessages, fmt.Sprintf("...%v more", len(createdFiles)-maxErrors))
+			}
+			for _, creationMessage := range creationMessages {
+				errorMessage += "  " + creationMessage + "\n"
+			}
+		}
+
+		return errors.New(errorMessage)
+	}
+
+	return nil
 }
 
-// copyFiles copies files in or out of the sandbox.  If allowFromNotExists is true then errors
-// caused by a from path not existing are ignored.
-func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, allowFromNotExists bool) error {
+type existsType bool
+
+const (
+	requireFromExists  existsType = false
+	allowFromNotExists            = true
+)
+
+type writeType bool
+
+const (
+	alwaysWrite        writeType = false
+	onlyWriteIfChanged           = true
+)
+
+// copyFiles copies files in or out of the sandbox.  If exists is allowFromNotExists then errors
+// caused by a from path not existing are ignored.  If write is onlyWriteIfChanged then the output
+// file is compared to the input file and not written to if it is the same, avoiding updating
+// the timestamp.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, exists existsType, write writeType) error {
 	for _, copyPair := range copies {
 		fromPath := joinPath(fromDir, copyPair.GetFrom())
 		toPath := joinPath(toDir, copyPair.GetTo())
-		err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), allowFromNotExists)
+		err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), exists, write)
 		if err != nil {
 			return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
 		}
@@ -385,8 +474,11 @@
 }
 
 // copyOneFile copies a file and its permissions.  If forceExecutable is true it adds u+x to the
-// permissions.  If allowFromNotExists is true it returns nil if the from path doesn't exist.
-func copyOneFile(from string, to string, forceExecutable, allowFromNotExists bool) error {
+// permissions.  If exists is allowFromNotExists it returns nil if the from path doesn't exist.
+// If write is onlyWriteIfChanged then the output file is compared to the input file and not written to
+// if it is the same, avoiding updating the timestamp.
+func copyOneFile(from string, to string, forceExecutable bool, exists existsType,
+	write writeType) error {
 	err := os.MkdirAll(filepath.Dir(to), 0777)
 	if err != nil {
 		return err
@@ -394,7 +486,7 @@
 
 	stat, err := os.Stat(from)
 	if err != nil {
-		if os.IsNotExist(err) && allowFromNotExists {
+		if os.IsNotExist(err) && exists == allowFromNotExists {
 			return nil
 		}
 		return err
@@ -405,6 +497,10 @@
 		perm = perm | 0100 // u+x
 	}
 
+	if write == onlyWriteIfChanged && filesHaveSameContents(from, to) {
+		return nil
+	}
+
 	in, err := os.Open(from)
 	if err != nil {
 		return err
@@ -478,7 +574,7 @@
 		to := applyPathMappings(rspFile.PathMappings, from)
 
 		// Copy the file into the sandbox.
-		err := copyOneFile(from, joinPath(toDir, to), false, false)
+		err := copyOneFile(from, joinPath(toDir, to), false, requireFromExists, alwaysWrite)
 		if err != nil {
 			return err
 		}
@@ -525,9 +621,10 @@
 
 // moveFiles moves files specified by a set of copy rules.  It uses os.Rename, so it is restricted
 // to moving files where the source and destination are in the same filesystem.  This is OK for
-// sbox because the temporary directory is inside the out directory.  It updates the timestamp
-// of the new file.
-func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// sbox because the temporary directory is inside the out directory.  If write is onlyWriteIfChanged
+// then the output file is compared to the input file and not written to if it is the same, avoiding
+// updating the timestamp.  Otherwise it always updates the timestamp of the new file.
+func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string, write writeType) error {
 	for _, copyPair := range copies {
 		fromPath := joinPath(fromDir, copyPair.GetFrom())
 		toPath := joinPath(toDir, copyPair.GetTo())
@@ -536,6 +633,10 @@
 			return err
 		}
 
+		if write == onlyWriteIfChanged && filesHaveSameContents(fromPath, toPath) {
+			continue
+		}
+
 		err = os.Rename(fromPath, toPath)
 		if err != nil {
 			return err
@@ -552,6 +653,37 @@
 	return nil
 }
 
+// clearOutputDirectory removes all files in the output directory if write is alwaysWrite, or
+// any files not listed in copies if write is onlyWriteIfChanged
+func clearOutputDirectory(copies []*sbox_proto.Copy, outputDir string, write writeType) error {
+	if outputDir == "" {
+		return fmt.Errorf("output directory must be set")
+	}
+
+	if write == alwaysWrite {
+		// When writing all the output files remove the whole output directory
+		return os.RemoveAll(outputDir)
+	}
+
+	outputFiles := make(map[string]bool, len(copies))
+	for _, copyPair := range copies {
+		outputFiles[copyPair.GetTo()] = true
+	}
+
+	existingFiles := findAllFilesUnder(outputDir)
+	for _, existingFile := range existingFiles {
+		fullExistingFile := filepath.Join(outputDir, existingFile)
+		if !outputFiles[fullExistingFile] {
+			err := os.Remove(fullExistingFile)
+			if err != nil {
+				return fmt.Errorf("failed to remove obsolete output file %s: %w", fullExistingFile, err)
+			}
+		}
+	}
+
+	return nil
+}
+
 // Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
 // to an output file.
 func rewriteDepFiles(ins []string, out string) error {
@@ -595,6 +727,66 @@
 	return filepath.Join(dir, file)
 }
 
+// filesHaveSameContents compares the contents if two files, returning true if they are the same
+// and returning false if they are different or any errors occur.
+func filesHaveSameContents(a, b string) bool {
+	// Compare the sizes of the two files
+	statA, err := os.Stat(a)
+	if err != nil {
+		return false
+	}
+	statB, err := os.Stat(b)
+	if err != nil {
+		return false
+	}
+
+	if statA.Size() != statB.Size() {
+		return false
+	}
+
+	// Open the two files
+	fileA, err := os.Open(a)
+	if err != nil {
+		return false
+	}
+	defer fileA.Close()
+	fileB, err := os.Open(b)
+	if err != nil {
+		return false
+	}
+	defer fileB.Close()
+
+	// Compare the files 1MB at a time
+	const bufSize = 1 * 1024 * 1024
+	bufA := make([]byte, bufSize)
+	bufB := make([]byte, bufSize)
+
+	remain := statA.Size()
+	for remain > 0 {
+		toRead := int64(bufSize)
+		if toRead > remain {
+			toRead = remain
+		}
+
+		_, err = io.ReadFull(fileA, bufA[:toRead])
+		if err != nil {
+			return false
+		}
+		_, err = io.ReadFull(fileB, bufB[:toRead])
+		if err != nil {
+			return false
+		}
+
+		if bytes.Compare(bufA[:toRead], bufB[:toRead]) != 0 {
+			return false
+		}
+
+		remain -= toRead
+	}
+
+	return true
+}
+
 func makeAbsPathEnv(pathEnv string) (string, error) {
 	pathEnvElements := filepath.SplitList(pathEnv)
 	for i, p := range pathEnvElements {
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
index b996481..7c84f2c 100644
--- a/cmd/sbox/sbox_proto/sbox.pb.go
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -1,78 +1,104 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: sbox.proto
 
 package sbox_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 // A set of commands to run in a sandbox.
 type Manifest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// A list of commands to run in the sandbox.
 	Commands []*Command `protobuf:"bytes,1,rep,name=commands" json:"commands,omitempty"`
 	// If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
 	// merged into the given output file relative to the $PWD when sbox was started.
-	OutputDepfile        *string  `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	OutputDepfile *string `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
 }
 
-func (m *Manifest) Reset()         { *m = Manifest{} }
-func (m *Manifest) String() string { return proto.CompactTextString(m) }
-func (*Manifest) ProtoMessage()    {}
+func (x *Manifest) Reset() {
+	*x = Manifest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_sbox_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Manifest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Manifest) ProtoMessage() {}
+
+func (x *Manifest) ProtoReflect() protoreflect.Message {
+	mi := &file_sbox_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Manifest.ProtoReflect.Descriptor instead.
 func (*Manifest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0425bf0de86ed1, []int{0}
+	return file_sbox_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *Manifest) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Manifest.Unmarshal(m, b)
-}
-func (m *Manifest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Manifest.Marshal(b, m, deterministic)
-}
-func (m *Manifest) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Manifest.Merge(m, src)
-}
-func (m *Manifest) XXX_Size() int {
-	return xxx_messageInfo_Manifest.Size(m)
-}
-func (m *Manifest) XXX_DiscardUnknown() {
-	xxx_messageInfo_Manifest.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Manifest proto.InternalMessageInfo
-
-func (m *Manifest) GetCommands() []*Command {
-	if m != nil {
-		return m.Commands
+func (x *Manifest) GetCommands() []*Command {
+	if x != nil {
+		return x.Commands
 	}
 	return nil
 }
 
-func (m *Manifest) GetOutputDepfile() string {
-	if m != nil && m.OutputDepfile != nil {
-		return *m.OutputDepfile
+func (x *Manifest) GetOutputDepfile() string {
+	if x != nil && x.OutputDepfile != nil {
+		return *x.OutputDepfile
 	}
 	return ""
 }
 
 // SandboxManifest describes a command to run in the sandbox.
 type Command struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// A list of copy rules to run before the sandboxed command.  The from field is relative to the
 	// $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
 	CopyBefore []*Copy `protobuf:"bytes,1,rep,name=copy_before,json=copyBefore" json:"copy_before,omitempty"`
@@ -89,75 +115,79 @@
 	InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
 	// A list of files that will be copied before the sandboxed command, and whose contents should be
 	// copied as if they were listed in copy_before.
-	RspFiles             []*RspFile `protobuf:"bytes,6,rep,name=rsp_files,json=rspFiles" json:"rsp_files,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
-	XXX_unrecognized     []byte     `json:"-"`
-	XXX_sizecache        int32      `json:"-"`
+	RspFiles []*RspFile `protobuf:"bytes,6,rep,name=rsp_files,json=rspFiles" json:"rsp_files,omitempty"`
 }
 
-func (m *Command) Reset()         { *m = Command{} }
-func (m *Command) String() string { return proto.CompactTextString(m) }
-func (*Command) ProtoMessage()    {}
+func (x *Command) Reset() {
+	*x = Command{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_sbox_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Command) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Command) ProtoMessage() {}
+
+func (x *Command) ProtoReflect() protoreflect.Message {
+	mi := &file_sbox_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Command.ProtoReflect.Descriptor instead.
 func (*Command) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0425bf0de86ed1, []int{1}
+	return file_sbox_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *Command) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Command.Unmarshal(m, b)
-}
-func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Command.Marshal(b, m, deterministic)
-}
-func (m *Command) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Command.Merge(m, src)
-}
-func (m *Command) XXX_Size() int {
-	return xxx_messageInfo_Command.Size(m)
-}
-func (m *Command) XXX_DiscardUnknown() {
-	xxx_messageInfo_Command.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Command proto.InternalMessageInfo
-
-func (m *Command) GetCopyBefore() []*Copy {
-	if m != nil {
-		return m.CopyBefore
+func (x *Command) GetCopyBefore() []*Copy {
+	if x != nil {
+		return x.CopyBefore
 	}
 	return nil
 }
 
-func (m *Command) GetChdir() bool {
-	if m != nil && m.Chdir != nil {
-		return *m.Chdir
+func (x *Command) GetChdir() bool {
+	if x != nil && x.Chdir != nil {
+		return *x.Chdir
 	}
 	return false
 }
 
-func (m *Command) GetCommand() string {
-	if m != nil && m.Command != nil {
-		return *m.Command
+func (x *Command) GetCommand() string {
+	if x != nil && x.Command != nil {
+		return *x.Command
 	}
 	return ""
 }
 
-func (m *Command) GetCopyAfter() []*Copy {
-	if m != nil {
-		return m.CopyAfter
+func (x *Command) GetCopyAfter() []*Copy {
+	if x != nil {
+		return x.CopyAfter
 	}
 	return nil
 }
 
-func (m *Command) GetInputHash() string {
-	if m != nil && m.InputHash != nil {
-		return *m.InputHash
+func (x *Command) GetInputHash() string {
+	if x != nil && x.InputHash != nil {
+		return *x.InputHash
 	}
 	return ""
 }
 
-func (m *Command) GetRspFiles() []*RspFile {
-	if m != nil {
-		return m.RspFiles
+func (x *Command) GetRspFiles() []*RspFile {
+	if x != nil {
+		return x.RspFiles
 	}
 	return nil
 }
@@ -166,193 +196,341 @@
 // are relative to is specific to the context the Copy is used in and will be different for
 // from and to.
 type Copy struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	From *string `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
 	To   *string `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
 	// If true, make the file executable after copying it.
-	Executable           *bool    `protobuf:"varint,3,opt,name=executable" json:"executable,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Executable *bool `protobuf:"varint,3,opt,name=executable" json:"executable,omitempty"`
 }
 
-func (m *Copy) Reset()         { *m = Copy{} }
-func (m *Copy) String() string { return proto.CompactTextString(m) }
-func (*Copy) ProtoMessage()    {}
+func (x *Copy) Reset() {
+	*x = Copy{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_sbox_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Copy) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Copy) ProtoMessage() {}
+
+func (x *Copy) ProtoReflect() protoreflect.Message {
+	mi := &file_sbox_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Copy.ProtoReflect.Descriptor instead.
 func (*Copy) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0425bf0de86ed1, []int{2}
+	return file_sbox_proto_rawDescGZIP(), []int{2}
 }
 
-func (m *Copy) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Copy.Unmarshal(m, b)
-}
-func (m *Copy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Copy.Marshal(b, m, deterministic)
-}
-func (m *Copy) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Copy.Merge(m, src)
-}
-func (m *Copy) XXX_Size() int {
-	return xxx_messageInfo_Copy.Size(m)
-}
-func (m *Copy) XXX_DiscardUnknown() {
-	xxx_messageInfo_Copy.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Copy proto.InternalMessageInfo
-
-func (m *Copy) GetFrom() string {
-	if m != nil && m.From != nil {
-		return *m.From
+func (x *Copy) GetFrom() string {
+	if x != nil && x.From != nil {
+		return *x.From
 	}
 	return ""
 }
 
-func (m *Copy) GetTo() string {
-	if m != nil && m.To != nil {
-		return *m.To
+func (x *Copy) GetTo() string {
+	if x != nil && x.To != nil {
+		return *x.To
 	}
 	return ""
 }
 
-func (m *Copy) GetExecutable() bool {
-	if m != nil && m.Executable != nil {
-		return *m.Executable
+func (x *Copy) GetExecutable() bool {
+	if x != nil && x.Executable != nil {
+		return *x.Executable
 	}
 	return false
 }
 
 // RspFile describes an rspfile that should be copied into the sandbox directory.
 type RspFile struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The path to the rsp file.
 	File *string `protobuf:"bytes,1,req,name=file" json:"file,omitempty"`
 	// A list of path mappings that should be applied to each file listed in the rsp file.
-	PathMappings         []*PathMapping `protobuf:"bytes,2,rep,name=path_mappings,json=pathMappings" json:"path_mappings,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
-	XXX_unrecognized     []byte         `json:"-"`
-	XXX_sizecache        int32          `json:"-"`
+	PathMappings []*PathMapping `protobuf:"bytes,2,rep,name=path_mappings,json=pathMappings" json:"path_mappings,omitempty"`
 }
 
-func (m *RspFile) Reset()         { *m = RspFile{} }
-func (m *RspFile) String() string { return proto.CompactTextString(m) }
-func (*RspFile) ProtoMessage()    {}
+func (x *RspFile) Reset() {
+	*x = RspFile{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_sbox_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RspFile) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RspFile) ProtoMessage() {}
+
+func (x *RspFile) ProtoReflect() protoreflect.Message {
+	mi := &file_sbox_proto_msgTypes[3]
+	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 RspFile.ProtoReflect.Descriptor instead.
 func (*RspFile) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0425bf0de86ed1, []int{3}
+	return file_sbox_proto_rawDescGZIP(), []int{3}
 }
 
-func (m *RspFile) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_RspFile.Unmarshal(m, b)
-}
-func (m *RspFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_RspFile.Marshal(b, m, deterministic)
-}
-func (m *RspFile) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_RspFile.Merge(m, src)
-}
-func (m *RspFile) XXX_Size() int {
-	return xxx_messageInfo_RspFile.Size(m)
-}
-func (m *RspFile) XXX_DiscardUnknown() {
-	xxx_messageInfo_RspFile.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_RspFile proto.InternalMessageInfo
-
-func (m *RspFile) GetFile() string {
-	if m != nil && m.File != nil {
-		return *m.File
+func (x *RspFile) GetFile() string {
+	if x != nil && x.File != nil {
+		return *x.File
 	}
 	return ""
 }
 
-func (m *RspFile) GetPathMappings() []*PathMapping {
-	if m != nil {
-		return m.PathMappings
+func (x *RspFile) GetPathMappings() []*PathMapping {
+	if x != nil {
+		return x.PathMappings
 	}
 	return nil
 }
 
 // PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox.
 type PathMapping struct {
-	From                 *string  `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
-	To                   *string  `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	From *string `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+	To   *string `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
 }
 
-func (m *PathMapping) Reset()         { *m = PathMapping{} }
-func (m *PathMapping) String() string { return proto.CompactTextString(m) }
-func (*PathMapping) ProtoMessage()    {}
+func (x *PathMapping) Reset() {
+	*x = PathMapping{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_sbox_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PathMapping) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PathMapping) ProtoMessage() {}
+
+func (x *PathMapping) ProtoReflect() protoreflect.Message {
+	mi := &file_sbox_proto_msgTypes[4]
+	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 PathMapping.ProtoReflect.Descriptor instead.
 func (*PathMapping) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0425bf0de86ed1, []int{4}
+	return file_sbox_proto_rawDescGZIP(), []int{4}
 }
 
-func (m *PathMapping) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_PathMapping.Unmarshal(m, b)
-}
-func (m *PathMapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_PathMapping.Marshal(b, m, deterministic)
-}
-func (m *PathMapping) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PathMapping.Merge(m, src)
-}
-func (m *PathMapping) XXX_Size() int {
-	return xxx_messageInfo_PathMapping.Size(m)
-}
-func (m *PathMapping) XXX_DiscardUnknown() {
-	xxx_messageInfo_PathMapping.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PathMapping proto.InternalMessageInfo
-
-func (m *PathMapping) GetFrom() string {
-	if m != nil && m.From != nil {
-		return *m.From
+func (x *PathMapping) GetFrom() string {
+	if x != nil && x.From != nil {
+		return *x.From
 	}
 	return ""
 }
 
-func (m *PathMapping) GetTo() string {
-	if m != nil && m.To != nil {
-		return *m.To
+func (x *PathMapping) GetTo() string {
+	if x != nil && x.To != nil {
+		return *x.To
 	}
 	return ""
 }
 
-func init() {
-	proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
-	proto.RegisterType((*Command)(nil), "sbox.Command")
-	proto.RegisterType((*Copy)(nil), "sbox.Copy")
-	proto.RegisterType((*RspFile)(nil), "sbox.RspFile")
-	proto.RegisterType((*PathMapping)(nil), "sbox.PathMapping")
+var File_sbox_proto protoreflect.FileDescriptor
+
+var file_sbox_proto_rawDesc = []byte{
+	0x0a, 0x0a, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x73, 0x62,
+	0x6f, 0x78, 0x22, 0x5c, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x29,
+	0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x0d, 0x2e, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52,
+	0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x70, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x70, 0x66, 0x69, 0x6c, 0x65,
+	0x22, 0xdc, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2b, 0x0a, 0x0b,
+	0x63, 0x6f, 0x70, 0x79, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x0a, 0x2e, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x0a, 0x63,
+	0x6f, 0x70, 0x79, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x68, 0x64,
+	0x69, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x63, 0x68, 0x64, 0x69, 0x72, 0x12,
+	0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x02, 0x28, 0x09,
+	0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x29, 0x0a, 0x0a, 0x63, 0x6f, 0x70,
+	0x79, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
+	0x73, 0x62, 0x6f, 0x78, 0x2e, 0x43, 0x6f, 0x70, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x70, 0x79, 0x41,
+	0x66, 0x74, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x68, 0x61,
+	0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x48,
+	0x61, 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x09, 0x72, 0x73, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73,
+	0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x52, 0x73,
+	0x70, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x08, 0x72, 0x73, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22,
+	0x4a, 0x0a, 0x04, 0x43, 0x6f, 0x70, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18,
+	0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74,
+	0x6f, 0x18, 0x02, 0x20, 0x02, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x65,
+	0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x55, 0x0a, 0x07, 0x52,
+	0x73, 0x70, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01,
+	0x20, 0x02, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x36, 0x0a, 0x0d, 0x70, 0x61,
+	0x74, 0x68, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x11, 0x2e, 0x73, 0x62, 0x6f, 0x78, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x70,
+	0x70, 0x69, 0x6e, 0x67, 0x52, 0x0c, 0x70, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
+	0x67, 0x73, 0x22, 0x31, 0x0a, 0x0b, 0x50, 0x61, 0x74, 0x68, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
+	0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52,
+	0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x02, 0x28,
+	0x09, 0x52, 0x02, 0x74, 0x6f, 0x42, 0x23, 0x5a, 0x21, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x73, 0x62, 0x6f, 0x78, 0x2f,
+	0x73, 0x62, 0x6f, 0x78, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
-func init() {
-	proto.RegisterFile("sbox.proto", fileDescriptor_9d0425bf0de86ed1)
+var (
+	file_sbox_proto_rawDescOnce sync.Once
+	file_sbox_proto_rawDescData = file_sbox_proto_rawDesc
+)
+
+func file_sbox_proto_rawDescGZIP() []byte {
+	file_sbox_proto_rawDescOnce.Do(func() {
+		file_sbox_proto_rawDescData = protoimpl.X.CompressGZIP(file_sbox_proto_rawDescData)
+	})
+	return file_sbox_proto_rawDescData
 }
 
-var fileDescriptor_9d0425bf0de86ed1 = []byte{
-	// 342 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x5f, 0x4b, 0xc3, 0x30,
-	0x14, 0xc5, 0x69, 0xd7, 0xb9, 0xf6, 0xee, 0x0f, 0x18, 0x7c, 0xc8, 0x8b, 0x32, 0x0a, 0xc2, 0xa6,
-	0x30, 0xd0, 0x07, 0xdf, 0x9d, 0x22, 0x22, 0x0c, 0x24, 0xe0, 0x8b, 0x08, 0x25, 0xeb, 0x52, 0x5b,
-	0x58, 0x9b, 0x90, 0x64, 0xb0, 0x7d, 0x57, 0x3f, 0x8c, 0xe4, 0xa6, 0xd3, 0x82, 0x2f, 0xbe, 0xdd,
-	0x7b, 0x0e, 0xf7, 0xdc, 0x5f, 0xc2, 0x05, 0x30, 0x6b, 0xb9, 0x5f, 0x28, 0x2d, 0xad, 0x24, 0x91,
-	0xab, 0xd3, 0x0f, 0x88, 0x57, 0xbc, 0xa9, 0x0a, 0x61, 0x2c, 0x99, 0x43, 0x9c, 0xcb, 0xba, 0xe6,
-	0xcd, 0xc6, 0xd0, 0x60, 0xda, 0x9b, 0x0d, 0x6f, 0xc7, 0x0b, 0x1c, 0x78, 0xf0, 0x2a, 0xfb, 0xb1,
-	0xc9, 0x25, 0x4c, 0xe4, 0xce, 0xaa, 0x9d, 0xcd, 0x36, 0x42, 0x15, 0xd5, 0x56, 0xd0, 0x70, 0x1a,
-	0xcc, 0x12, 0x36, 0xf6, 0xea, 0xa3, 0x17, 0xd3, 0xaf, 0x00, 0x06, 0xed, 0x30, 0xb9, 0x86, 0x61,
-	0x2e, 0xd5, 0x21, 0x5b, 0x8b, 0x42, 0x6a, 0xd1, 0x2e, 0x80, 0xe3, 0x02, 0x75, 0x60, 0xe0, 0xec,
-	0x25, 0xba, 0xe4, 0x0c, 0xfa, 0x79, 0xb9, 0xa9, 0x34, 0xc6, 0xc6, 0xcc, 0x37, 0x84, 0xc2, 0xa0,
-	0x25, 0xa0, 0xbd, 0x69, 0x38, 0x4b, 0xd8, 0xb1, 0x25, 0x73, 0xc0, 0xe9, 0x8c, 0x17, 0x56, 0x68,
-	0x1a, 0xfd, 0xc9, 0x4e, 0x9c, 0x7b, 0xef, 0x4c, 0x72, 0x0e, 0x50, 0x35, 0x8e, 0xbc, 0xe4, 0xa6,
-	0xa4, 0x7d, 0xc4, 0x4e, 0x50, 0x79, 0xe6, 0xa6, 0x24, 0x57, 0x90, 0x68, 0xa3, 0x32, 0x87, 0x6f,
-	0xe8, 0x49, 0xf7, 0x17, 0x98, 0x51, 0x4f, 0xd5, 0x56, 0xb0, 0x58, 0xfb, 0xc2, 0xa4, 0x2f, 0x10,
-	0xb9, 0x74, 0x42, 0x20, 0x2a, 0xb4, 0xac, 0x69, 0x80, 0x50, 0x58, 0x93, 0x09, 0x84, 0x56, 0xd2,
-	0x10, 0x95, 0xd0, 0x4a, 0x72, 0x01, 0x20, 0xf6, 0x22, 0xdf, 0x59, 0xbe, 0xde, 0x0a, 0xda, 0xc3,
-	0x67, 0x75, 0x94, 0xf4, 0x0d, 0x06, 0xed, 0x02, 0x8c, 0x73, 0x5f, 0x7a, 0x8c, 0x73, 0xda, 0x1d,
-	0x8c, 0x15, 0xb7, 0x65, 0x56, 0x73, 0xa5, 0xaa, 0xe6, 0xd3, 0xd0, 0x10, 0xd1, 0x4e, 0x3d, 0xda,
-	0x2b, 0xb7, 0xe5, 0xca, 0x3b, 0x6c, 0xa4, 0x7e, 0x1b, 0x93, 0xde, 0xc0, 0xb0, 0x63, 0xfe, 0x87,
-	0x74, 0x39, 0x7a, 0xc7, 0x33, 0xc9, 0xf0, 0x4c, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x83, 0x82,
-	0xb0, 0xc3, 0x33, 0x02, 0x00, 0x00,
+var file_sbox_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_sbox_proto_goTypes = []interface{}{
+	(*Manifest)(nil),    // 0: sbox.Manifest
+	(*Command)(nil),     // 1: sbox.Command
+	(*Copy)(nil),        // 2: sbox.Copy
+	(*RspFile)(nil),     // 3: sbox.RspFile
+	(*PathMapping)(nil), // 4: sbox.PathMapping
+}
+var file_sbox_proto_depIdxs = []int32{
+	1, // 0: sbox.Manifest.commands:type_name -> sbox.Command
+	2, // 1: sbox.Command.copy_before:type_name -> sbox.Copy
+	2, // 2: sbox.Command.copy_after:type_name -> sbox.Copy
+	3, // 3: sbox.Command.rsp_files:type_name -> sbox.RspFile
+	4, // 4: sbox.RspFile.path_mappings:type_name -> sbox.PathMapping
+	5, // [5:5] is the sub-list for method output_type
+	5, // [5:5] is the sub-list for method input_type
+	5, // [5:5] is the sub-list for extension type_name
+	5, // [5:5] is the sub-list for extension extendee
+	0, // [0:5] is the sub-list for field type_name
+}
+
+func init() { file_sbox_proto_init() }
+func file_sbox_proto_init() {
+	if File_sbox_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_sbox_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Manifest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_sbox_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Command); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_sbox_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Copy); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_sbox_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RspFile); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_sbox_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PathMapping); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_sbox_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_sbox_proto_goTypes,
+		DependencyIndexes: file_sbox_proto_depIdxs,
+		MessageInfos:      file_sbox_proto_msgTypes,
+	}.Build()
+	File_sbox_proto = out.File
+	file_sbox_proto_rawDesc = nil
+	file_sbox_proto_goTypes = nil
+	file_sbox_proto_depIdxs = nil
 }
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
index bdf92c6..2f0dcf0 100644
--- a/cmd/sbox/sbox_proto/sbox.proto
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -15,7 +15,7 @@
 syntax = "proto2";
 
 package sbox;
-option go_package = "sbox_proto";
+option go_package = "android/soong/cmd/sbox/sbox_proto";
 
 // A set of commands to run in a sandbox.
 message Manifest {
diff --git a/cmd/sbox/sbox_test.go b/cmd/sbox/sbox_test.go
new file mode 100644
index 0000000..3f13d2f
--- /dev/null
+++ b/cmd/sbox/sbox_test.go
@@ -0,0 +1,127 @@
+// 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 main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+func Test_filesHaveSameContents(t *testing.T) {
+
+	tests := []struct {
+		name     string
+		a        string
+		b        string
+		missingA bool
+		missingB bool
+
+		equal bool
+	}{
+		{
+			name:  "empty",
+			a:     "",
+			b:     "",
+			equal: true,
+		},
+		{
+			name:  "equal",
+			a:     "foo",
+			b:     "foo",
+			equal: true,
+		},
+		{
+			name:  "unequal",
+			a:     "foo",
+			b:     "bar",
+			equal: false,
+		},
+		{
+			name:  "unequal different sizes",
+			a:     "foo",
+			b:     "foobar",
+			equal: false,
+		},
+		{
+			name:  "equal large",
+			a:     strings.Repeat("a", 2*1024*1024),
+			b:     strings.Repeat("a", 2*1024*1024),
+			equal: true,
+		},
+		{
+			name:  "equal large unaligned",
+			a:     strings.Repeat("a", 2*1024*1024+10),
+			b:     strings.Repeat("a", 2*1024*1024+10),
+			equal: true,
+		},
+		{
+			name:  "unequal large",
+			a:     strings.Repeat("a", 2*1024*1024),
+			b:     strings.Repeat("a", 2*1024*1024-1) + "b",
+			equal: false,
+		},
+		{
+			name:  "unequal large unaligned",
+			a:     strings.Repeat("a", 2*1024*1024+10),
+			b:     strings.Repeat("a", 2*1024*1024+9) + "b",
+			equal: false,
+		},
+		{
+			name:     "missing a",
+			missingA: true,
+			b:        "foo",
+			equal:    false,
+		},
+		{
+			name:     "missing b",
+			a:        "foo",
+			missingB: true,
+			equal:    false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tempDir, err := os.MkdirTemp("", "testFilesHaveSameContents")
+			if err != nil {
+				t.Fatalf("failed to create temp dir: %s", err)
+			}
+			defer os.RemoveAll(tempDir)
+
+			fileA := filepath.Join(tempDir, "a")
+			fileB := filepath.Join(tempDir, "b")
+
+			if !tt.missingA {
+				err := ioutil.WriteFile(fileA, []byte(tt.a), 0666)
+				if err != nil {
+					t.Fatalf("failed to write %s: %s", fileA, err)
+				}
+			}
+
+			if !tt.missingB {
+				err := ioutil.WriteFile(fileB, []byte(tt.b), 0666)
+				if err != nil {
+					t.Fatalf("failed to write %s: %s", fileB, err)
+				}
+			}
+
+			if got := filesHaveSameContents(fileA, fileB); got != tt.equal {
+				t.Errorf("filesHaveSameContents() = %v, want %v", got, tt.equal)
+			}
+		})
+	}
+}
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 9f09224..72af3e0 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -16,14 +16,16 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-bootstrap_go_binary {
+blueprint_go_binary {
     name: "soong_build",
     deps: [
         "blueprint",
         "blueprint-bootstrap",
         "golang-protobuf-proto",
+        "golang-protobuf-android",
         "soong",
         "soong-android",
+        "soong-provenance",
         "soong-bp2build",
         "soong-ui-metrics_proto",
     ],
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 044689e..4b3161b 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -23,44 +23,77 @@
 	"strings"
 	"time"
 
+	"android/soong/android"
 	"android/soong/bp2build"
 	"android/soong/shared"
+	"android/soong/ui/metrics/bp2build_metrics_proto"
 
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/deptools"
-
-	"android/soong/android"
+	"github.com/google/blueprint/metrics"
+	androidProtobuf "google.golang.org/protobuf/android"
 )
 
 var (
 	topDir           string
 	outDir           string
+	soongOutDir      string
 	availableEnvFile string
 	usedEnvFile      string
 
+	runGoTests bool
+
+	globFile    string
+	globListDir string
 	delveListen string
 	delvePath   string
 
+	moduleGraphFile   string
+	moduleActionsFile string
 	docFile           string
 	bazelQueryViewDir string
 	bp2buildMarker    string
+
+	cmdlineArgs bootstrap.Args
 )
 
 func init() {
 	// Flags that make sense in every mode
 	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
-	flag.StringVar(&outDir, "out", "", "Soong output directory (usually $TOP/out/soong)")
+	flag.StringVar(&soongOutDir, "soong_out", "", "Soong output directory (usually $TOP/out/soong)")
 	flag.StringVar(&availableEnvFile, "available_env", "", "File containing available environment variables")
 	flag.StringVar(&usedEnvFile, "used_env", "", "File containing used environment variables")
+	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
+	flag.StringVar(&globListDir, "globListDir", "", "the directory containing the glob list files")
+	flag.StringVar(&outDir, "out", "", "the ninja builddir directory")
+	flag.StringVar(&cmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
 
 	// Debug flags
 	flag.StringVar(&delveListen, "delve_listen", "", "Delve port to listen on for debugging")
 	flag.StringVar(&delvePath, "delve_path", "", "Path to Delve. Only used if --delve_listen is set")
+	flag.StringVar(&cmdlineArgs.Cpuprofile, "cpuprofile", "", "write cpu profile to file")
+	flag.StringVar(&cmdlineArgs.TraceFile, "trace", "", "write trace to file")
+	flag.StringVar(&cmdlineArgs.Memprofile, "memprofile", "", "write memory profile to file")
+	flag.BoolVar(&cmdlineArgs.NoGC, "nogc", false, "turn off GC for debugging")
 
 	// Flags representing various modes soong_build can run in
+	flag.StringVar(&moduleGraphFile, "module_graph_file", "", "JSON module graph file to output")
+	flag.StringVar(&moduleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
 	flag.StringVar(&docFile, "soong_docs", "", "build documentation file to output")
 	flag.StringVar(&bazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
 	flag.StringVar(&bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
+	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
+	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
+
+	// Flags that probably shouldn't be flags of soong_build but we haven't found
+	// the time to remove them yet
+	flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
+
+	// Disable deterministic randomization in the protobuf package, so incremental
+	// builds with unrelated Soong changes don't trigger large rebuilds (since we
+	// write out text protos in command lines, and command line changes trigger
+	// rebuilds).
+	androidProtobuf.DisableRand()
 }
 
 func newNameResolver(config android.Config) *android.NameResolver {
@@ -79,19 +112,16 @@
 	return android.NewNameResolver(exportFilter)
 }
 
-func newContext(configuration android.Config, prepareBuildActions bool) *android.Context {
+func newContext(configuration android.Config) *android.Context {
 	ctx := android.NewContext(configuration)
 	ctx.Register()
-	if !prepareBuildActions {
-		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
-	}
 	ctx.SetNameInterface(newNameResolver(configuration))
 	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
 	return ctx
 }
 
-func newConfig(srcDir, outDir string, availableEnv map[string]string) android.Config {
-	configuration, err := android.NewConfig(srcDir, outDir, bootstrap.CmdlineArgs.ModuleListFile, availableEnv)
+func newConfig(availableEnv map[string]string) android.Config {
+	configuration, err := android.NewConfig(cmdlineArgs.ModuleListFile, runGoTests, outDir, soongOutDir, availableEnv)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -105,12 +135,14 @@
 // TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
 // the incorrect results from the first pass, and file I/O is expensive.
 func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
-	var firstArgs, secondArgs bootstrap.Args
+	firstCtx.EventHandler.Begin("mixed_build")
+	defer firstCtx.EventHandler.End("mixed_build")
 
-	firstArgs = bootstrap.CmdlineArgs
-	configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
-	bootstrap.RunBlueprint(firstArgs, firstCtx.Context, configuration)
+	firstCtx.EventHandler.Begin("prepare")
+	bootstrap.RunBlueprint(cmdlineArgs, bootstrap.StopBeforeWriteNinja, firstCtx.Context, configuration)
+	firstCtx.EventHandler.End("prepare")
 
+	firstCtx.EventHandler.Begin("bazel")
 	// Invoke bazel commands and save results for second pass.
 	if err := configuration.BazelContext.InvokeBazel(); err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
@@ -122,103 +154,156 @@
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
-	secondCtx := newContext(secondConfig, true)
-	secondArgs = bootstrap.CmdlineArgs
-	ninjaDeps := bootstrap.RunBlueprint(secondArgs, secondCtx.Context, secondConfig)
+	firstCtx.EventHandler.End("bazel")
+
+	secondCtx := newContext(secondConfig)
+	secondCtx.EventHandler = firstCtx.EventHandler
+	secondCtx.EventHandler.Begin("analyze")
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, secondCtx.Context, secondConfig)
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-	err = deptools.WriteDepFile(shared.JoinPath(topDir, secondArgs.DepFile), secondArgs.OutFile, ninjaDeps)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", secondArgs.DepFile, err)
-		os.Exit(1)
-	}
+	secondCtx.EventHandler.End("analyze")
+
+	globListFiles := writeBuildGlobsNinjaFile(secondCtx, configuration.SoongOutDir(), configuration)
+	ninjaDeps = append(ninjaDeps, globListFiles...)
+
+	writeDepFile(cmdlineArgs.OutFile, *secondCtx.EventHandler, ninjaDeps)
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
-func runQueryView(configuration android.Config, ctx *android.Context) {
+func runQueryView(queryviewDir, queryviewMarker string, configuration android.Config, ctx *android.Context) {
+	ctx.EventHandler.Begin("queryview")
+	defer ctx.EventHandler.End("queryview")
 	codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
-	absoluteQueryViewDir := shared.JoinPath(topDir, bazelQueryViewDir)
+	absoluteQueryViewDir := shared.JoinPath(topDir, queryviewDir)
 	if err := createBazelQueryView(codegenContext, absoluteQueryViewDir); err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
 	}
+
+	touch(shared.JoinPath(topDir, queryviewMarker))
 }
 
-func runSoongDocs(configuration android.Config) {
-	ctx := newContext(configuration, false)
-	soongDocsArgs := bootstrap.CmdlineArgs
-	bootstrap.RunBlueprint(soongDocsArgs, ctx.Context, configuration)
-	if err := writeDocs(ctx, configuration, docFile); err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
+func writeMetrics(configuration android.Config, eventHandler metrics.EventHandler) {
+	metricsDir := configuration.Getenv("LOG_DIR")
+	if len(metricsDir) < 1 {
+		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating soong metrics: LOG_DIR\n")
 		os.Exit(1)
 	}
-}
-
-func writeMetrics(configuration android.Config) {
-	metricsFile := filepath.Join(configuration.BuildDir(), "soong_build_metrics.pb")
-	err := android.WriteMetrics(configuration, metricsFile)
+	metricsFile := filepath.Join(metricsDir, "soong_build_metrics.pb")
+	err := android.WriteMetrics(configuration, eventHandler, metricsFile)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err)
 		os.Exit(1)
 	}
 }
 
-func writeJsonModuleGraph(configuration android.Config, ctx *android.Context, path string, extraNinjaDeps []string) {
-	f, err := os.Create(path)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
+func writeJsonModuleGraphAndActions(ctx *android.Context, graphPath string, actionsPath string) {
+	graphFile, graphErr := os.Create(shared.JoinPath(topDir, graphPath))
+	actionsFile, actionsErr := os.Create(shared.JoinPath(topDir, actionsPath))
+	if graphErr != nil || actionsErr != nil {
+		fmt.Fprintf(os.Stderr, "Graph err: %s, actions err: %s", graphErr, actionsErr)
 		os.Exit(1)
 	}
 
-	defer f.Close()
-	ctx.Context.PrintJSONGraph(f)
-	writeFakeNinjaFile(extraNinjaDeps, configuration.BuildDir())
+	defer graphFile.Close()
+	defer actionsFile.Close()
+	ctx.Context.PrintJSONGraphAndActions(graphFile, actionsFile)
 }
 
-func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
-	bazelConversionRequested := bp2buildMarker != ""
-	mixedModeBuild := configuration.BazelContext.BazelEnabled()
-	generateQueryView := bazelQueryViewDir != ""
-	jsonModuleFile := configuration.Getenv("SOONG_DUMP_JSON_MODULE_GRAPH")
+func writeBuildGlobsNinjaFile(ctx *android.Context, buildDir string, config interface{}) []string {
+	ctx.EventHandler.Begin("globs_ninja_file")
+	defer ctx.EventHandler.End("globs_ninja_file")
 
-	blueprintArgs := bootstrap.CmdlineArgs
-	prepareBuildActions := !generateQueryView && jsonModuleFile == ""
-	if bazelConversionRequested {
+	globDir := bootstrap.GlobDirectory(buildDir, globListDir)
+	bootstrap.WriteBuildGlobsNinjaFile(&bootstrap.GlobSingleton{
+		GlobLister: ctx.Globs,
+		GlobFile:   globFile,
+		GlobDir:    globDir,
+		SrcDir:     ctx.SrcDir(),
+	}, config)
+	return bootstrap.GlobFileListFiles(globDir)
+}
+
+func writeDepFile(outputFile string, eventHandler metrics.EventHandler, ninjaDeps []string) {
+	eventHandler.Begin("ninja_deps")
+	defer eventHandler.End("ninja_deps")
+	depFile := shared.JoinPath(topDir, outputFile+".d")
+	err := deptools.WriteDepFile(depFile, outputFile, ninjaDeps)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", depFile, err)
+		os.Exit(1)
+	}
+}
+
+// doChosenActivity runs Soong for a specific activity, like bp2build, queryview
+// or the actual Soong build for the build.ninja file. Returns the top level
+// output file of the specific activity.
+func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
+	mixedModeBuild := configuration.BazelContext.BazelEnabled()
+	generateBazelWorkspace := bp2buildMarker != ""
+	generateQueryView := bazelQueryViewDir != ""
+	generateModuleGraphFile := moduleGraphFile != ""
+	generateDocFile := docFile != ""
+
+	if generateBazelWorkspace {
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
 		runBp2Build(configuration, extraNinjaDeps)
-		if bp2buildMarker != "" {
-			return bp2buildMarker
-		} else {
-			return bootstrap.CmdlineArgs.OutFile
-		}
+		return bp2buildMarker
 	}
 
-	ctx := newContext(configuration, prepareBuildActions)
+	blueprintArgs := cmdlineArgs
+
+	ctx := newContext(configuration)
 	if mixedModeBuild {
 		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
 	} else {
-		ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, ctx.Context, configuration)
+		var stopBefore bootstrap.StopBefore
+		if generateModuleGraphFile {
+			stopBefore = bootstrap.StopBeforeWriteNinja
+		} else if generateQueryView {
+			stopBefore = bootstrap.StopBeforePrepareBuildActions
+		} else if generateDocFile {
+			stopBefore = bootstrap.StopBeforePrepareBuildActions
+		} else {
+			stopBefore = bootstrap.DoEverything
+		}
+
+		ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, stopBefore, ctx.Context, configuration)
 		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-		err := deptools.WriteDepFile(shared.JoinPath(topDir, blueprintArgs.DepFile), blueprintArgs.OutFile, ninjaDeps)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", blueprintArgs.DepFile, err)
-			os.Exit(1)
+
+		globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
+		ninjaDeps = append(ninjaDeps, globListFiles...)
+
+		// Convert the Soong module graph into Bazel BUILD files.
+		if generateQueryView {
+			queryviewMarkerFile := bazelQueryViewDir + ".marker"
+			runQueryView(bazelQueryViewDir, queryviewMarkerFile, configuration, ctx)
+			writeDepFile(queryviewMarkerFile, *ctx.EventHandler, ninjaDeps)
+			return queryviewMarkerFile
+		} else if generateModuleGraphFile {
+			writeJsonModuleGraphAndActions(ctx, moduleGraphFile, moduleActionsFile)
+			writeDepFile(moduleGraphFile, *ctx.EventHandler, ninjaDeps)
+			return moduleGraphFile
+		} else if generateDocFile {
+			// TODO: we could make writeDocs() return the list of documentation files
+			// written and add them to the .d file. Then soong_docs would be re-run
+			// whenever one is deleted.
+			if err := writeDocs(ctx, shared.JoinPath(topDir, docFile)); err != nil {
+				fmt.Fprintf(os.Stderr, "error building Soong documentation: %s\n", err)
+				os.Exit(1)
+			}
+			writeDepFile(docFile, *ctx.EventHandler, ninjaDeps)
+			return docFile
+		} else {
+			// The actual output (build.ninja) was written in the RunBlueprint() call
+			// above
+			writeDepFile(cmdlineArgs.OutFile, *ctx.EventHandler, ninjaDeps)
 		}
 	}
 
-	// Convert the Soong module graph into Bazel BUILD files.
-	if generateQueryView {
-		runQueryView(configuration, ctx)
-		return bootstrap.CmdlineArgs.OutFile // TODO: This is a lie
-	}
-
-	if jsonModuleFile != "" {
-		writeJsonModuleGraph(configuration, ctx, jsonModuleFile, extraNinjaDeps)
-		return bootstrap.CmdlineArgs.OutFile // TODO: This is a lie
-	}
-
-	writeMetrics(configuration)
-	return bootstrap.CmdlineArgs.OutFile
+	writeMetrics(configuration, *ctx.EventHandler)
+	return cmdlineArgs.OutFile
 }
 
 // soong_ui dumps the available environment variables to
@@ -257,9 +342,7 @@
 
 	availableEnv := parseAvailableEnv()
 
-	// The top-level Blueprints file is passed as the first argument.
-	srcDir := filepath.Dir(flag.Arg(0))
-	configuration := newConfig(srcDir, outDir, availableEnv)
+	configuration := newConfig(availableEnv)
 	extraNinjaDeps := []string{
 		configuration.ProductVariablesFileName,
 		usedEnvFile,
@@ -272,20 +355,11 @@
 	if shared.IsDebugging() {
 		// Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
 		// enabled even if it completed successfully.
-		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
-	}
-
-	if docFile != "" {
-		// We don't write an used variables file when generating documentation
-		// because that is done from within the actual builds as a Ninja action and
-		// thus it would overwrite the actual used variables file so this is
-		// special-cased.
-		// TODO: Fix this by not passing --used_env to the soong_docs invocation
-		runSoongDocs(configuration)
-		return
+		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.SoongOutDir(), "always_rerun_for_delve"))
 	}
 
 	finalOutputFile := doChosenActivity(configuration, extraNinjaDeps)
+
 	writeUsedEnvironmentFile(configuration, finalOutputFile)
 }
 
@@ -313,29 +387,6 @@
 	touch(shared.JoinPath(topDir, finalOutputFile))
 }
 
-// Workarounds to support running bp2build in a clean AOSP checkout with no
-// prior builds, and exiting early as soon as the BUILD files get generated,
-// therefore not creating build.ninja files that soong_ui and callers of
-// soong_build expects.
-//
-// These files are: build.ninja and build.ninja.d. Since Kati hasn't been
-// ran as well, and `nothing` is defined in a .mk file, there isn't a ninja
-// target called `nothing`, so we manually create it here.
-func writeFakeNinjaFile(extraNinjaDeps []string, buildDir string) {
-	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
-
-	ninjaFileName := "build.ninja"
-	ninjaFile := shared.JoinPath(topDir, buildDir, ninjaFileName)
-	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName)
-	// A workaround to create the 'nothing' ninja target so `m nothing` works,
-	// since bp2build runs without Kati, and the 'nothing' target is declared in
-	// a Makefile.
-	ioutil.WriteFile(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
-	ioutil.WriteFile(ninjaFileD,
-		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
-		0666)
-}
-
 func touch(path string) {
 	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
 	if err != nil {
@@ -357,14 +408,101 @@
 	}
 }
 
+// Find BUILD files in the srcDir which...
+//
+// - are not on the allow list (android/bazel.go#ShouldKeepExistingBuildFileForDir())
+//
+// - won't be overwritten by corresponding bp2build generated files
+//
+// And return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
+func getPathsToIgnoredBuildFiles(topDir string, generatedRoot string, srcDirBazelFiles []string, verbose bool) []string {
+	paths := make([]string, 0)
+
+	for _, srcDirBazelFileRelativePath := range srcDirBazelFiles {
+		srcDirBazelFileFullPath := shared.JoinPath(topDir, srcDirBazelFileRelativePath)
+		fileInfo, err := os.Stat(srcDirBazelFileFullPath)
+		if err != nil {
+			// Warn about error, but continue trying to check files
+			fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", srcDirBazelFileFullPath, err)
+			continue
+		}
+		if fileInfo.IsDir() {
+			// Don't ignore entire directories
+			continue
+		}
+		if !(fileInfo.Name() == "BUILD" || fileInfo.Name() == "BUILD.bazel") {
+			// Don't ignore this file - it is not a build file
+			continue
+		}
+		srcDirBazelFileDir := filepath.Dir(srcDirBazelFileRelativePath)
+		if android.ShouldKeepExistingBuildFileForDir(srcDirBazelFileDir) {
+			// Don't ignore this existing build file
+			continue
+		}
+		correspondingBp2BuildFile := shared.JoinPath(topDir, generatedRoot, srcDirBazelFileRelativePath)
+		if _, err := os.Stat(correspondingBp2BuildFile); err == nil {
+			// If bp2build generated an alternate BUILD file, don't exclude this workspace path
+			// BUILD file clash resolution happens later in the symlink forest creation
+			continue
+		}
+		if verbose {
+			fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", srcDirBazelFileRelativePath)
+		}
+		paths = append(paths, srcDirBazelFileRelativePath)
+	}
+
+	return paths
+}
+
+// Returns temporary symlink forest excludes necessary for bazel build //external/... (and bazel build //frameworks/...) to work
+func getTemporaryExcludes() []string {
+	excludes := make([]string, 0)
+
+	// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite symlink expansion error for Bazel
+	excludes = append(excludes, "external/autotest/venv/autotest_lib")
+
+	// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
+	// It contains several symlinks back to real source dirs, and those source dirs contain BUILD files we want to ignore
+	excludes = append(excludes, "external/google-fruit/extras/bazel_root/third_party/fruit")
+
+	// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
+	excludes = append(excludes, "frameworks/compile/slang")
+
+	return excludes
+}
+
+// Read the bazel.list file that the Soong Finder already dumped earlier (hopefully)
+// It contains the locations of BUILD files, BUILD.bazel files, etc. in the source dir
+func getExistingBazelRelatedFiles(topDir string) ([]string, error) {
+	bazelFinderFile := filepath.Join(filepath.Dir(cmdlineArgs.ModuleListFile), "bazel.list")
+	if !filepath.IsAbs(bazelFinderFile) {
+		// Assume this was a relative path under topDir
+		bazelFinderFile = filepath.Join(topDir, bazelFinderFile)
+	}
+	data, err := ioutil.ReadFile(bazelFinderFile)
+	if err != nil {
+		return nil, err
+	}
+	files := strings.Split(strings.TrimSpace(string(data)), "\n")
+	return files, nil
+}
+
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
 func runBp2Build(configuration android.Config, extraNinjaDeps []string) {
+	eventHandler := metrics.EventHandler{}
+	eventHandler.Begin("bp2build")
+
 	// Register an alternate set of singletons and mutators for bazel
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
 
+	// Soong internals like LoadHooks behave differently when running as
+	// bp2build. This is the bit to differentiate between Soong-as-Soong and
+	// Soong-as-bp2build.
+	bp2buildCtx.SetRunningAsBp2build()
+
 	// Propagate "allow misssing dependencies" bit. This is normally set in
 	// newContext(), but we create bp2buildCtx without calling that method.
 	bp2buildCtx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
@@ -375,33 +513,31 @@
 	// Android.bp files. It must not depend on the values of per-build product
 	// configurations or variables, since those will generate different BUILD
 	// files based on how the user has configured their tree.
-	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineArgs.ModuleListFile)
-	modulePaths, err := bp2buildCtx.ListModulePaths(configuration.SrcDir())
+	bp2buildCtx.SetModuleListFile(cmdlineArgs.ModuleListFile)
+	modulePaths, err := bp2buildCtx.ListModulePaths(".")
 	if err != nil {
 		panic(err)
 	}
 
 	extraNinjaDeps = append(extraNinjaDeps, modulePaths...)
 
-	// No need to generate Ninja build rules/statements from Modules and Singletons.
-	configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
-
 	// Run the loading and analysis pipeline to prepare the graph of regular
 	// Modules parsed from Android.bp files, and the BazelTargetModules mapped
 	// from the regular Modules.
-	blueprintArgs := bootstrap.CmdlineArgs
-	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bp2buildCtx.Context, configuration)
+	blueprintArgs := cmdlineArgs
+	ninjaDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.StopBeforePrepareBuildActions, bp2buildCtx.Context, configuration)
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
-	ninjaDeps = append(ninjaDeps, bootstrap.GlobFileListFiles(configuration)...)
+	globListFiles := writeBuildGlobsNinjaFile(bp2buildCtx, configuration.SoongOutDir(), configuration)
+	ninjaDeps = append(ninjaDeps, globListFiles...)
 
 	// Run the code-generation phase to convert BazelTargetModules to BUILD files
 	// and print conversion metrics to the user.
 	codegenContext := bp2build.NewCodegenContext(configuration, *bp2buildCtx, bp2build.Bp2Build)
 	metrics := bp2build.Codegen(codegenContext)
 
-	generatedRoot := shared.JoinPath(configuration.BuildDir(), "bp2build")
-	workspaceRoot := shared.JoinPath(configuration.BuildDir(), "workspace")
+	generatedRoot := shared.JoinPath(configuration.SoongOutDir(), "bp2build")
+	workspaceRoot := shared.JoinPath(configuration.SoongOutDir(), "workspace")
 
 	excludes := []string{
 		"bazel-bin",
@@ -411,31 +547,56 @@
 		"bazel-" + filepath.Base(topDir),
 	}
 
-	if bootstrap.CmdlineArgs.NinjaBuildDir[0] != '/' {
-		excludes = append(excludes, bootstrap.CmdlineArgs.NinjaBuildDir)
+	if outDir[0] != '/' {
+		excludes = append(excludes, outDir)
 	}
 
+	existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
+		os.Exit(1)
+	}
+
+	pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(topDir, generatedRoot, existingBazelRelatedFiles, configuration.IsEnvTrue("BP2BUILD_VERBOSE"))
+	excludes = append(excludes, pathsToIgnoredBuildFiles...)
+
+	excludes = append(excludes, getTemporaryExcludes()...)
+
 	symlinkForestDeps := bp2build.PlantSymlinkForest(
-		topDir, workspaceRoot, generatedRoot, configuration.SrcDir(), excludes)
+		topDir, workspaceRoot, generatedRoot, ".", excludes)
+
+	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
+	ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
+
+	writeDepFile(bp2buildMarker, eventHandler, ninjaDeps)
+
+	// Create an empty bp2build marker file.
+	touch(shared.JoinPath(topDir, bp2buildMarker))
+
+	eventHandler.End("bp2build")
 
 	// Only report metrics when in bp2build mode. The metrics aren't relevant
 	// for queryview, since that's a total repo-wide conversion and there's a
 	// 1:1 mapping for each module.
 	metrics.Print()
+	writeBp2BuildMetrics(&metrics, configuration, eventHandler)
+}
 
-	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
-	ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
-
-	depFile := bp2buildMarker + ".d"
-	err = deptools.WriteDepFile(shared.JoinPath(topDir, depFile), bp2buildMarker, ninjaDeps)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Cannot write depfile '%s': %s\n", depFile, err)
+// Write Bp2Build metrics into $LOG_DIR
+func writeBp2BuildMetrics(codegenMetrics *bp2build.CodegenMetrics,
+	configuration android.Config, eventHandler metrics.EventHandler) {
+	for _, event := range eventHandler.CompletedEvents() {
+		codegenMetrics.Events = append(codegenMetrics.Events,
+			&bp2build_metrics_proto.Event{
+				Name:      event.Id,
+				StartTime: uint64(event.Start.UnixNano()),
+				RealTime:  event.RuntimeNanoseconds(),
+			})
+	}
+	metricsDir := configuration.Getenv("LOG_DIR")
+	if len(metricsDir) < 1 {
+		fmt.Fprintf(os.Stderr, "\nMissing required env var for generating bp2build metrics: LOG_DIR\n")
 		os.Exit(1)
 	}
-
-	if bp2buildMarker != "" {
-		touch(shared.JoinPath(topDir, bp2buildMarker))
-	} else {
-		writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
-	}
+	codegenMetrics.Write(metricsDir)
 }
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index e2ce772..d63ded5 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -15,21 +15,24 @@
 package main
 
 import (
-	"android/soong/android"
-	"android/soong/bp2build"
 	"io/ioutil"
 	"os"
 	"path/filepath"
+
+	"android/soong/android"
+	"android/soong/bp2build"
 )
 
 func createBazelQueryView(ctx *bp2build.CodegenContext, bazelQueryViewDir string) error {
+	os.RemoveAll(bazelQueryViewDir)
 	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
 
-	// Ignore metrics reporting for queryview, since queryview is already a full-repo
-	// conversion and can use data from bazel query directly.
-	buildToTargets, _ := bp2build.GenerateBazelTargets(ctx, true)
+	res, err := bp2build.GenerateBazelTargets(ctx, true)
+	if err != nil {
+		panic(err)
+	}
 
-	filesToWrite := bp2build.CreateBazelFiles(ruleShims, buildToTargets, bp2build.QueryView)
+	filesToWrite := bp2build.CreateBazelFiles(ruleShims, res.BuildDirToTargets(), bp2build.QueryView)
 	for _, f := range filesToWrite {
 		if err := writeReadOnlyFile(bazelQueryViewDir, f); err != nil {
 			return err
diff --git a/cmd/soong_build/writedocs.go b/cmd/soong_build/writedocs.go
index b7c260c..d2fbed4 100644
--- a/cmd/soong_build/writedocs.go
+++ b/cmd/soong_build/writedocs.go
@@ -15,13 +15,14 @@
 package main
 
 import (
-	"android/soong/android"
 	"bytes"
 	"html/template"
 	"io/ioutil"
 	"path/filepath"
 	"sort"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/bootstrap/bpdoc"
 )
@@ -95,13 +96,13 @@
 	return result
 }
 
-func getPackages(ctx *android.Context, config interface{}) ([]*bpdoc.Package, error) {
+func getPackages(ctx *android.Context) ([]*bpdoc.Package, error) {
 	moduleTypeFactories := android.ModuleTypeFactoriesForDocs()
-	return bootstrap.ModuleTypeDocs(ctx.Context, config, moduleTypeFactories)
+	return bootstrap.ModuleTypeDocs(ctx.Context, moduleTypeFactories)
 }
 
-func writeDocs(ctx *android.Context, config interface{}, filename string) error {
-	packages, err := getPackages(ctx, config)
+func writeDocs(ctx *android.Context, filename string) error {
+	packages, err := getPackages(ctx)
 	if err != nil {
 		return err
 	}
@@ -371,6 +372,7 @@
     {{if .Properties -}}
       <div class="accordion"  id="{{getModule}}.{{.Name}}">
         <span class="fixed">&#x2295</span><b>{{.Name}}</b>
+        <i>{{.Type}}</i>
         {{- range .OtherNames -}}, {{.}}{{- end -}}
       </div>
       <div class="collapsible">
diff --git a/cmd/soong_ui/Android.bp b/cmd/soong_ui/Android.bp
index 4f5eea9..9f10b82 100644
--- a/cmd/soong_ui/Android.bp
+++ b/cmd/soong_ui/Android.bp
@@ -20,6 +20,7 @@
     name: "soong_ui",
     deps: [
         "soong-ui-build",
+        "soong-ui-signal",
         "soong-ui-logger",
         "soong-ui-terminal",
         "soong-ui-tracer",
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index d47b14c..a03a86a 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -16,7 +16,6 @@
 
 import (
 	"context"
-	"encoding/json"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -24,22 +23,19 @@
 	"path/filepath"
 	"strconv"
 	"strings"
+	"syscall"
 	"time"
 
 	"android/soong/shared"
 	"android/soong/ui/build"
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
+	"android/soong/ui/signal"
 	"android/soong/ui/status"
 	"android/soong/ui/terminal"
 	"android/soong/ui/tracer"
 )
 
-const (
-	configDir  = "vendor/google/tools/soong_config"
-	jsonSuffix = "json"
-)
-
 // A command represents an operation to be executed in the soong build
 // system.
 type command struct {
@@ -66,7 +62,7 @@
 }
 
 // list of supported commands (flags) supported by soong ui
-var commands []command = []command{
+var commands = []command{
 	{
 		flag:        "--make-mode",
 		description: "build the modules by the target name (i.e. soong_docs)",
@@ -116,43 +112,6 @@
 	return indexList(s, list) != -1
 }
 
-func loadEnvConfig(config build.Config) error {
-	bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
-	if bc == "" {
-		return nil
-	}
-	configDirs := []string{
-		os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR"),
-		config.OutDir(),
-		configDir,
-	}
-	var cfgFile string
-	for _, dir := range configDirs {
-		cfgFile = filepath.Join(os.Getenv("TOP"), dir, fmt.Sprintf("%s.%s", bc, jsonSuffix))
-		if _, err := os.Stat(cfgFile); err == nil {
-			break
-		}
-	}
-
-	envVarsJSON, err := ioutil.ReadFile(cfgFile)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "\033[33mWARNING:\033[0m failed to open config file %s: %s\n", cfgFile, err.Error())
-		return nil
-	}
-
-	var envVars map[string]map[string]string
-	if err := json.Unmarshal(envVarsJSON, &envVars); err != nil {
-		return fmt.Errorf("env vars config file: %s did not parse correctly: %s", cfgFile, err.Error())
-	}
-	for k, v := range envVars["env"] {
-		if os.Getenv(k) != "" {
-			continue
-		}
-		config.Environment().Set(k, v)
-	}
-	return nil
-}
-
 // Main execution of soong_ui. The command format is as follows:
 //
 //    soong_ui <command> [<arg 1> <arg 2> ... <arg n>]
@@ -172,7 +131,8 @@
 
 	// Create a terminal output that mimics Ninja's.
 	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput,
-		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
+		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"),
+		build.OsEnvironment().IsEnvTrue("SOONG_UI_ANSI_OUTPUT"))
 
 	// Attach a new logger instance to the terminal output.
 	log := logger.New(output)
@@ -199,7 +159,7 @@
 	stat.AddOutput(trace.StatusTracer())
 
 	// Set up a cleanup procedure in case the normal termination process doesn't work.
-	build.SetupSignals(log, cancel, func() {
+	signal.SetupSignals(log, cancel, func() {
 		trace.Close()
 		log.Cleanup()
 		stat.Finish()
@@ -216,11 +176,6 @@
 
 	config := c.config(buildCtx, args...)
 
-	if err := loadEnvConfig(config); err != nil {
-		fmt.Fprintf(os.Stderr, "failed to parse env config files: %v", err)
-		os.Exit(1)
-	}
-
 	build.SetupOutDir(buildCtx, config)
 
 	if config.UseBazel() && config.Dist() {
@@ -250,6 +205,8 @@
 	buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v",
 		config.Parallel(), config.RemoteParallel(), config.HighmemParallel())
 
+	setMaxFiles(buildCtx)
+
 	{
 		// The order of the function calls is important. The last defer function call
 		// is the first one that is executed to save the rbe metrics to a protobuf
@@ -264,7 +221,6 @@
 		}
 		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, files...)
 		defer met.Dump(soongMetricsFile)
-		defer build.DumpRBEMetrics(buildCtx, config, rbeMetricsFile)
 	}
 
 	// Read the time at the starting point.
@@ -569,7 +525,11 @@
 	}
 
 	// command not found
-	return nil, nil, fmt.Errorf("Command not found: %q", args)
+	flags := make([]string, len(commands))
+	for i, c := range commands {
+		flags[i] = c.flag
+	}
+	return nil, nil, fmt.Errorf("Command not found: %q\nDid you mean one of these: %q", args, flags)
 }
 
 // For Bazel support, this moves files and directories from e.g. out/dist/$f to DIST_DIR/$f if necessary.
@@ -646,3 +606,24 @@
 		}
 	}
 }
+
+func setMaxFiles(ctx build.Context) {
+	var limits syscall.Rlimit
+
+	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
+	if err != nil {
+		ctx.Println("Failed to get file limit:", err)
+		return
+	}
+
+	ctx.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
+	if limits.Cur == limits.Max {
+		return
+	}
+
+	limits.Cur = limits.Max
+	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
+	if err != nil {
+		ctx.Println("Failed to increase file limit:", err)
+	}
+}
diff --git a/cmd/symbols_map/Android.bp b/cmd/symbols_map/Android.bp
new file mode 100644
index 0000000..0ba3b07
--- /dev/null
+++ b/cmd/symbols_map/Android.bp
@@ -0,0 +1,34 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "symbols_map",
+    srcs: [
+        "elf.go",
+        "r8.go",
+        "symbols_map.go",
+    ],
+    testSrcs: [
+        "elf_test.go",
+        "r8_test.go",
+    ],
+    deps: [
+        "blueprint-pathtools",
+        "golang-protobuf-encoding-prototext",
+        "soong-response",
+        "symbols_map_proto",
+    ],
+}
+
+bootstrap_go_package {
+    name: "symbols_map_proto",
+    pkgPath: "android/soong/cmd/symbols_map/symbols_map_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "symbols_map_proto/symbols_map.pb.go",
+    ],
+}
diff --git a/cmd/symbols_map/elf.go b/cmd/symbols_map/elf.go
new file mode 100644
index 0000000..950e3b2
--- /dev/null
+++ b/cmd/symbols_map/elf.go
@@ -0,0 +1,118 @@
+// 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 main
+
+import (
+	"debug/elf"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+)
+
+const gnuBuildID = "GNU\x00"
+
+// elfIdentifier extracts the elf build ID from an elf file.  If allowMissing is true it returns
+// an empty identifier if the file exists but the build ID note does not.
+func elfIdentifier(filename string, allowMissing bool) (string, error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return "", fmt.Errorf("failed to open %s: %w", filename, err)
+	}
+	defer f.Close()
+
+	return elfIdentifierFromReaderAt(f, filename, allowMissing)
+}
+
+// elfIdentifierFromReaderAt extracts the elf build ID from a ReaderAt.  If allowMissing is true it
+// returns an empty identifier if the file exists but the build ID note does not.
+func elfIdentifierFromReaderAt(r io.ReaderAt, filename string, allowMissing bool) (string, error) {
+	f, err := elf.NewFile(r)
+	if err != nil {
+		if allowMissing {
+			if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
+				return "", nil
+			}
+			if _, ok := err.(*elf.FormatError); ok {
+				// The file was not an elf file.
+				return "", nil
+			}
+		}
+		return "", fmt.Errorf("failed to parse elf file %s: %w", filename, err)
+	}
+	defer f.Close()
+
+	buildIDNote := f.Section(".note.gnu.build-id")
+	if buildIDNote == nil {
+		if allowMissing {
+			return "", nil
+		}
+		return "", fmt.Errorf("failed to find .note.gnu.build-id in  %s", filename)
+	}
+
+	buildIDs, err := readNote(buildIDNote.Open(), f.ByteOrder)
+	if err != nil {
+		return "", fmt.Errorf("failed to read .note.gnu.build-id: %w", err)
+	}
+
+	for name, desc := range buildIDs {
+		if name == gnuBuildID {
+			return hex.EncodeToString(desc), nil
+		}
+	}
+
+	return "", nil
+}
+
+// readNote reads the contents of a note section, returning it as a map from name to descriptor.
+func readNote(note io.Reader, byteOrder binary.ByteOrder) (map[string][]byte, error) {
+	var noteHeader struct {
+		Namesz uint32
+		Descsz uint32
+		Type   uint32
+	}
+
+	notes := make(map[string][]byte)
+	for {
+		err := binary.Read(note, byteOrder, &noteHeader)
+		if err != nil {
+			if err == io.EOF {
+				return notes, nil
+			}
+			return nil, fmt.Errorf("failed to read note header: %w", err)
+		}
+
+		nameBuf := make([]byte, align4(noteHeader.Namesz))
+		err = binary.Read(note, byteOrder, &nameBuf)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read note name: %w", err)
+		}
+		name := string(nameBuf[:noteHeader.Namesz])
+
+		descBuf := make([]byte, align4(noteHeader.Descsz))
+		err = binary.Read(note, byteOrder, &descBuf)
+		if err != nil {
+			return nil, fmt.Errorf("failed to read note desc: %w", err)
+		}
+		notes[name] = descBuf[:noteHeader.Descsz]
+	}
+}
+
+// align4 rounds the input up to the next multiple of 4.
+func align4(i uint32) uint32 {
+	return (i + 3) &^ 3
+}
diff --git a/cmd/symbols_map/elf_test.go b/cmd/symbols_map/elf_test.go
new file mode 100644
index 0000000..a94c87f
--- /dev/null
+++ b/cmd/symbols_map/elf_test.go
@@ -0,0 +1,152 @@
+// 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 main
+
+import (
+	"bytes"
+	"debug/elf"
+	"encoding/binary"
+	"reflect"
+	"testing"
+)
+
+func Test_elfIdentifierFromReaderAt_BadElfFile(t *testing.T) {
+	tests := []struct {
+		name     string
+		contents string
+	}{
+		{
+			name:     "empty",
+			contents: "",
+		},
+		{
+			name:     "text",
+			contents: "#!/bin/bash\necho foobar",
+		},
+		{
+			name:     "empty elf",
+			contents: emptyElfFile(),
+		},
+		{
+			name:     "short section header",
+			contents: shortSectionHeaderElfFile(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			buf := bytes.NewReader([]byte(tt.contents))
+			_, err := elfIdentifierFromReaderAt(buf, "<>", false)
+			if err == nil {
+				t.Errorf("expected error reading bad elf file without allowMissing")
+			}
+			_, err = elfIdentifierFromReaderAt(buf, "<>", true)
+			if err != nil {
+				t.Errorf("expected no error reading bad elf file with allowMissing, got %q", err.Error())
+			}
+		})
+	}
+}
+
+func Test_readNote(t *testing.T) {
+	note := []byte{
+		0x04, 0x00, 0x00, 0x00,
+		0x10, 0x00, 0x00, 0x00,
+		0x03, 0x00, 0x00, 0x00,
+		0x47, 0x4e, 0x55, 0x00,
+		0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7,
+	}
+
+	descs, err := readNote(bytes.NewBuffer(note), binary.LittleEndian)
+	if err != nil {
+		t.Fatalf("unexpected error in readNote: %s", err)
+	}
+
+	expectedDescs := map[string][]byte{
+		"GNU\x00": []byte{0xca, 0xaf, 0x44, 0xd2, 0x82, 0x78, 0x68, 0xfe, 0xc0, 0x90, 0xa3, 0x43, 0x85, 0x36, 0x6c, 0xc7},
+	}
+
+	if !reflect.DeepEqual(descs, expectedDescs) {
+		t.Errorf("incorrect return, want %#v got %#v", expectedDescs, descs)
+	}
+}
+
+// emptyElfFile returns an elf file header with no program headers or sections.
+func emptyElfFile() string {
+	ident := [elf.EI_NIDENT]byte{}
+	identBuf := bytes.NewBuffer(ident[0:0:elf.EI_NIDENT])
+	binary.Write(identBuf, binary.LittleEndian, []byte("\x7fELF"))
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFCLASS64)
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFDATA2LSB)
+	binary.Write(identBuf, binary.LittleEndian, elf.EV_CURRENT)
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFOSABI_LINUX)
+	binary.Write(identBuf, binary.LittleEndian, make([]byte, 8))
+
+	header := elf.Header64{
+		Ident:     ident,
+		Type:      uint16(elf.ET_EXEC),
+		Machine:   uint16(elf.EM_X86_64),
+		Version:   uint32(elf.EV_CURRENT),
+		Entry:     0,
+		Phoff:     uint64(binary.Size(elf.Header64{})),
+		Shoff:     uint64(binary.Size(elf.Header64{})),
+		Flags:     0,
+		Ehsize:    uint16(binary.Size(elf.Header64{})),
+		Phentsize: 0x38,
+		Phnum:     0,
+		Shentsize: 0x40,
+		Shnum:     0,
+		Shstrndx:  0,
+	}
+
+	buf := &bytes.Buffer{}
+	binary.Write(buf, binary.LittleEndian, header)
+	return buf.String()
+}
+
+// shortSectionHeader returns an elf file header with a section header that extends past the end of
+// the file.
+func shortSectionHeaderElfFile() string {
+	ident := [elf.EI_NIDENT]byte{}
+	identBuf := bytes.NewBuffer(ident[0:0:elf.EI_NIDENT])
+	binary.Write(identBuf, binary.LittleEndian, []byte("\x7fELF"))
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFCLASS64)
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFDATA2LSB)
+	binary.Write(identBuf, binary.LittleEndian, elf.EV_CURRENT)
+	binary.Write(identBuf, binary.LittleEndian, elf.ELFOSABI_LINUX)
+	binary.Write(identBuf, binary.LittleEndian, make([]byte, 8))
+
+	header := elf.Header64{
+		Ident:     ident,
+		Type:      uint16(elf.ET_EXEC),
+		Machine:   uint16(elf.EM_X86_64),
+		Version:   uint32(elf.EV_CURRENT),
+		Entry:     0,
+		Phoff:     uint64(binary.Size(elf.Header64{})),
+		Shoff:     uint64(binary.Size(elf.Header64{})),
+		Flags:     0,
+		Ehsize:    uint16(binary.Size(elf.Header64{})),
+		Phentsize: 0x38,
+		Phnum:     0,
+		Shentsize: 0x40,
+		Shnum:     1,
+		Shstrndx:  0,
+	}
+
+	buf := &bytes.Buffer{}
+	binary.Write(buf, binary.LittleEndian, header)
+	binary.Write(buf, binary.LittleEndian, []byte{0})
+	return buf.String()
+}
diff --git a/cmd/symbols_map/r8.go b/cmd/symbols_map/r8.go
new file mode 100644
index 0000000..6f73e09
--- /dev/null
+++ b/cmd/symbols_map/r8.go
@@ -0,0 +1,56 @@
+// 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 main
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"strings"
+)
+
+const hashPrefix = "# pg_map_hash: "
+const hashTypePrefix = "SHA-256 "
+const commentPrefix = "#"
+
+// r8Identifier extracts the hash from the comments of a dictionary produced by R8. It returns
+// an empty identifier if no matching comment was found before the first non-comment line.
+func r8Identifier(filename string) (string, error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return "", fmt.Errorf("failed to open %s: %w", filename, err)
+	}
+	defer f.Close()
+
+	return extractR8CompilerHash(f)
+}
+
+func extractR8CompilerHash(r io.Reader) (string, error) {
+	s := bufio.NewScanner(r)
+	for s.Scan() {
+		line := s.Text()
+		if strings.HasPrefix(line, hashPrefix) {
+			hash := strings.TrimPrefix(line, hashPrefix)
+			if !strings.HasPrefix(hash, hashTypePrefix) {
+				return "", fmt.Errorf("invalid hash type found in %q", line)
+			}
+			return strings.TrimPrefix(hash, hashTypePrefix), nil
+		} else if !strings.HasPrefix(line, commentPrefix) {
+			break
+		}
+	}
+	return "", nil
+}
diff --git a/cmd/symbols_map/r8_test.go b/cmd/symbols_map/r8_test.go
new file mode 100644
index 0000000..5712da9
--- /dev/null
+++ b/cmd/symbols_map/r8_test.go
@@ -0,0 +1,91 @@
+// 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 main
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func Test_extractR8CompilerHash(t *testing.T) {
+	testCases := []struct {
+		name string
+		data string
+
+		hash string
+		err  string
+	}{
+		{
+			name: "simple",
+			data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+`,
+			hash: "7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da",
+		},
+		{
+			name: "empty",
+			data: ``,
+			hash: "",
+		},
+		{
+			name: "non comment line",
+			data: `# compiler: R8
+# compiler_version: 3.3.18-dev
+# min_api: 10000
+# compiler_hash: bab44c1a04a2201b55fe10394f477994205c34e0
+# common_typos_disable
+# {"id":"com.android.tools.r8.mapping","version":"2.0"}
+# pg_map_id: 7fe8b95
+android.car.userlib.UserHelper -> android.car.userlib.UserHelper:
+# pg_map_hash: SHA-256 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da
+`,
+			hash: "",
+		},
+		{
+			name: "invalid hash",
+			data: `# pg_map_hash: foobar 7fe8b95ae71f179f63d2a585356fb9cf2c8fb94df9c9dd50621ffa6d9e9e88da`,
+			err:  "invalid hash type",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			hash, err := extractR8CompilerHash(bytes.NewBufferString(tt.data))
+			if err != nil {
+				if tt.err != "" {
+					if !strings.Contains(err.Error(), tt.err) {
+						t.Fatalf("incorrect error in extractR8CompilerHash, want %s got %s", tt.err, err)
+					}
+				} else {
+					t.Fatalf("unexpected error in extractR8CompilerHash: %s", err)
+				}
+			} else if tt.err != "" {
+				t.Fatalf("missing error in extractR8CompilerHash, want %s", tt.err)
+			}
+
+			if g, w := hash, tt.hash; g != w {
+				t.Errorf("incorrect hash, want %q got %q", w, g)
+			}
+		})
+	}
+}
diff --git a/cmd/symbols_map/symbols_map.go b/cmd/symbols_map/symbols_map.go
new file mode 100644
index 0000000..938446d
--- /dev/null
+++ b/cmd/symbols_map/symbols_map.go
@@ -0,0 +1,202 @@
+// 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 main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+
+	"android/soong/cmd/symbols_map/symbols_map_proto"
+	"android/soong/response"
+
+	"github.com/google/blueprint/pathtools"
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+)
+
+// This tool is used to extract a hash from an elf file or an r8 dictionary and store it as a
+// textproto, or to merge multiple textprotos together.
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	// Hide the flag package to prevent accidental references to flag instead of flags.
+	flag := struct{}{}
+	_ = flag
+
+	flags.Usage = func() {
+		fmt.Fprintf(flags.Output(), "Usage of %s:\n", os.Args[0])
+		fmt.Fprintf(flags.Output(), "  %s -elf|-r8 <input file> [-write_if_changed] <output file>\n", os.Args[0])
+		fmt.Fprintf(flags.Output(), "  %s -merge <output file> [-write_if_changed] [-ignore_missing_files] [-strip_prefix <prefix>] [<input file>...]\n", os.Args[0])
+		fmt.Fprintln(flags.Output())
+
+		flags.PrintDefaults()
+	}
+
+	elfFile := flags.String("elf", "", "extract identifier from an elf file")
+	r8File := flags.String("r8", "", "extract identifier from an r8 dictionary")
+	merge := flags.String("merge", "", "merge multiple identifier protos")
+
+	writeIfChanged := flags.Bool("write_if_changed", false, "only write output file if it is modified")
+	ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "ignore missing input files in merge mode")
+	stripPrefix := flags.String("strip_prefix", "", "prefix to strip off of the location field in merge mode")
+
+	flags.Parse(expandedArgs)
+
+	if *merge != "" {
+		// If merge mode was requested perform the merge and exit early.
+		err := mergeProtos(*merge, flags.Args(), *stripPrefix, *writeIfChanged, *ignoreMissingFiles)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "failed to merge protos: %s", err)
+			os.Exit(1)
+		}
+		os.Exit(0)
+	}
+
+	if *elfFile == "" && *r8File == "" {
+		fmt.Fprintf(os.Stderr, "-elf or -r8 argument is required\n")
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	if *elfFile != "" && *r8File != "" {
+		fmt.Fprintf(os.Stderr, "only one of -elf or -r8 argument is allowed\n")
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	if flags.NArg() != 1 {
+		flags.Usage()
+		os.Exit(1)
+	}
+
+	output := flags.Arg(0)
+
+	var identifier string
+	var location string
+	var typ symbols_map_proto.Mapping_Type
+	var err error
+
+	if *elfFile != "" {
+		typ = symbols_map_proto.Mapping_ELF
+		location = *elfFile
+		identifier, err = elfIdentifier(*elfFile, true)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "error reading elf identifier: %s\n", err)
+			os.Exit(1)
+		}
+	} else if *r8File != "" {
+		typ = symbols_map_proto.Mapping_R8
+		identifier, err = r8Identifier(*r8File)
+		location = *r8File
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "error reading r8 identifier: %s\n", err)
+			os.Exit(1)
+		}
+	} else {
+		panic("shouldn't get here")
+	}
+
+	mapping := symbols_map_proto.Mapping{
+		Identifier: proto.String(identifier),
+		Location:   proto.String(location),
+		Type:       typ.Enum(),
+	}
+
+	err = writeTextProto(output, &mapping, *writeIfChanged)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error writing output: %s\n", err)
+		os.Exit(1)
+	}
+}
+
+// writeTextProto writes a proto to an output file as a textproto, optionally leaving the file
+// unmodified if it was already up to date.
+func writeTextProto(output string, message proto.Message, writeIfChanged bool) error {
+	marshaller := prototext.MarshalOptions{Multiline: true}
+	data, err := marshaller.Marshal(message)
+	if err != nil {
+		return fmt.Errorf("error marshalling textproto: %w", err)
+	}
+
+	if writeIfChanged {
+		err = pathtools.WriteFileIfChanged(output, data, 0666)
+	} else {
+		err = ioutil.WriteFile(output, data, 0666)
+	}
+
+	if err != nil {
+		return fmt.Errorf("error writing to %s: %w\n", output, err)
+	}
+
+	return nil
+}
+
+// mergeProtos merges a list of textproto files containing Mapping messages into a single textproto
+// containing a Mappings message.
+func mergeProtos(output string, inputs []string, stripPrefix string, writeIfChanged bool, ignoreMissingFiles bool) error {
+	mappings := symbols_map_proto.Mappings{}
+	for _, input := range inputs {
+		mapping := symbols_map_proto.Mapping{}
+		data, err := ioutil.ReadFile(input)
+		if err != nil {
+			if ignoreMissingFiles && os.IsNotExist(err) {
+				// Merge mode is used on a list of files in the packaging directory.  If multiple
+				// goals are included on the build command line, for example `dist` and `tests`,
+				// then the symbols packaging rule for `dist` can run while a dependency of `tests`
+				// is modifying the symbols packaging directory.  That can result in a file that
+				// existed when the file list was generated being deleted as part of updating it,
+				// resulting in sporadic ENOENT errors.  Ignore them if -ignore_missing_files
+				// was passed on the command line.
+				continue
+			}
+			return fmt.Errorf("failed to read %s: %w", input, err)
+		}
+		err = prototext.Unmarshal(data, &mapping)
+		if err != nil {
+			return fmt.Errorf("failed to parse textproto %s: %w", input, err)
+		}
+		if stripPrefix != "" && mapping.Location != nil {
+			mapping.Location = proto.String(strings.TrimPrefix(*mapping.Location, stripPrefix))
+		}
+		mappings.Mappings = append(mappings.Mappings, &mapping)
+	}
+
+	return writeTextProto(output, &mappings, writeIfChanged)
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
new file mode 100644
index 0000000..f9c0ce5
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.pb.go
@@ -0,0 +1,315 @@
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.9.1
+// source: symbols_map.proto
+
+package symbols_map_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Type is the valid types of a mapping.
+type Mapping_Type int32
+
+const (
+	// ELF denotes a mapping from an elf build ID to an unstripped elf file.
+	Mapping_ELF Mapping_Type = 0
+	// R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+	Mapping_R8 Mapping_Type = 1
+)
+
+// Enum value maps for Mapping_Type.
+var (
+	Mapping_Type_name = map[int32]string{
+		0: "ELF",
+		1: "R8",
+	}
+	Mapping_Type_value = map[string]int32{
+		"ELF": 0,
+		"R8":  1,
+	}
+)
+
+func (x Mapping_Type) Enum() *Mapping_Type {
+	p := new(Mapping_Type)
+	*p = x
+	return p
+}
+
+func (x Mapping_Type) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Mapping_Type) Descriptor() protoreflect.EnumDescriptor {
+	return file_symbols_map_proto_enumTypes[0].Descriptor()
+}
+
+func (Mapping_Type) Type() protoreflect.EnumType {
+	return &file_symbols_map_proto_enumTypes[0]
+}
+
+func (x Mapping_Type) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *Mapping_Type) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = Mapping_Type(num)
+	return nil
+}
+
+// Deprecated: Use Mapping_Type.Descriptor instead.
+func (Mapping_Type) EnumDescriptor() ([]byte, []int) {
+	return file_symbols_map_proto_rawDescGZIP(), []int{0, 0}
+}
+
+type Mapping struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// identifier is a unique identifier of a location, generally the hash of the file.  For an
+	// elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+	// top of the file.  It may be empty if no hash could be extracted from the file.
+	Identifier *string `protobuf:"bytes,1,opt,name=identifier" json:"identifier,omitempty"`
+	// location is the path to the file with the given identifier.  The location should be valid
+	// both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+	Location *string `protobuf:"bytes,2,opt,name=location" json:"location,omitempty"`
+	// type is the type of the mapping, either ELF or R8.
+	Type *Mapping_Type `protobuf:"varint,3,opt,name=type,enum=symbols_map.Mapping_Type" json:"type,omitempty"`
+}
+
+func (x *Mapping) Reset() {
+	*x = Mapping{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_symbols_map_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Mapping) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mapping) ProtoMessage() {}
+
+func (x *Mapping) ProtoReflect() protoreflect.Message {
+	mi := &file_symbols_map_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mapping.ProtoReflect.Descriptor instead.
+func (*Mapping) Descriptor() ([]byte, []int) {
+	return file_symbols_map_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Mapping) GetIdentifier() string {
+	if x != nil && x.Identifier != nil {
+		return *x.Identifier
+	}
+	return ""
+}
+
+func (x *Mapping) GetLocation() string {
+	if x != nil && x.Location != nil {
+		return *x.Location
+	}
+	return ""
+}
+
+func (x *Mapping) GetType() Mapping_Type {
+	if x != nil && x.Type != nil {
+		return *x.Type
+	}
+	return Mapping_ELF
+}
+
+type Mappings struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Mappings []*Mapping `protobuf:"bytes,4,rep,name=mappings" json:"mappings,omitempty"`
+}
+
+func (x *Mappings) Reset() {
+	*x = Mappings{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_symbols_map_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Mappings) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Mappings) ProtoMessage() {}
+
+func (x *Mappings) ProtoReflect() protoreflect.Message {
+	mi := &file_symbols_map_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Mappings.ProtoReflect.Descriptor instead.
+func (*Mappings) Descriptor() ([]byte, []int) {
+	return file_symbols_map_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Mappings) GetMappings() []*Mapping {
+	if x != nil {
+		return x.Mappings
+	}
+	return nil
+}
+
+var File_symbols_map_proto protoreflect.FileDescriptor
+
+var file_symbols_map_proto_rawDesc = []byte{
+	0x0a, 0x11, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70,
+	0x22, 0x8d, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x1e, 0x0a, 0x0a,
+	0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08,
+	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73,
+	0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x79, 0x70,
+	0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x17, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
+	0x07, 0x0a, 0x03, 0x45, 0x4c, 0x46, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x52, 0x38, 0x10, 0x01,
+	0x22, 0x3c, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x08,
+	0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
+	0x2e, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2e, 0x4d, 0x61, 0x70,
+	0x70, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x31,
+	0x5a, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
+	0x63, 0x6d, 0x64, 0x2f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x2f,
+	0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f,
+}
+
+var (
+	file_symbols_map_proto_rawDescOnce sync.Once
+	file_symbols_map_proto_rawDescData = file_symbols_map_proto_rawDesc
+)
+
+func file_symbols_map_proto_rawDescGZIP() []byte {
+	file_symbols_map_proto_rawDescOnce.Do(func() {
+		file_symbols_map_proto_rawDescData = protoimpl.X.CompressGZIP(file_symbols_map_proto_rawDescData)
+	})
+	return file_symbols_map_proto_rawDescData
+}
+
+var file_symbols_map_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_symbols_map_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_symbols_map_proto_goTypes = []interface{}{
+	(Mapping_Type)(0), // 0: symbols_map.Mapping.Type
+	(*Mapping)(nil),   // 1: symbols_map.Mapping
+	(*Mappings)(nil),  // 2: symbols_map.Mappings
+}
+var file_symbols_map_proto_depIdxs = []int32{
+	0, // 0: symbols_map.Mapping.type:type_name -> symbols_map.Mapping.Type
+	1, // 1: symbols_map.Mappings.mappings:type_name -> symbols_map.Mapping
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_symbols_map_proto_init() }
+func file_symbols_map_proto_init() {
+	if File_symbols_map_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_symbols_map_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Mapping); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_symbols_map_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Mappings); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_symbols_map_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_symbols_map_proto_goTypes,
+		DependencyIndexes: file_symbols_map_proto_depIdxs,
+		EnumInfos:         file_symbols_map_proto_enumTypes,
+		MessageInfos:      file_symbols_map_proto_msgTypes,
+	}.Build()
+	File_symbols_map_proto = out.File
+	file_symbols_map_proto_rawDesc = nil
+	file_symbols_map_proto_goTypes = nil
+	file_symbols_map_proto_depIdxs = nil
+}
diff --git a/cmd/symbols_map/symbols_map_proto/symbols_map.proto b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
new file mode 100644
index 0000000..693fe3e
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
@@ -0,0 +1,44 @@
+// 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.
+
+syntax = "proto2";
+
+package symbols_map;
+option go_package = "android/soong/cmd/symbols_map/symbols_map_proto";
+
+message Mapping {
+  // identifier is a unique identifier of a location, generally the hash of the file.  For an
+  // elf file it is the elf build ID, for an R8 dictionary it is the hash from the comments in the
+  // top of the file.  It may be empty if no hash could be extracted from the file.
+  optional string identifier = 1;
+
+  // location is the path to the file with the given identifier.  The location should be valid
+  // both on the local disk and in the distributed symbols.zip or proguard_dict.zip files.
+  optional string location = 2;
+
+  // Type is the valid types of a mapping.
+  enum Type {
+    // ELF denotes a mapping from an elf build ID to an unstripped elf file.
+    ELF = 0;
+    // R8 denotes a mapping from an R8 dictionary hash to an R8 dictionary.
+    R8 = 1;
+  }
+
+  // type is the type of the mapping, either ELF or R8.
+  optional Type type = 3;
+}
+
+message Mappings {
+  repeated Mapping mappings = 4;
+}
\ No newline at end of file
diff --git a/cmd/symbols_map/symbols_map_test.go b/cmd/symbols_map/symbols_map_test.go
new file mode 100644
index 0000000..754b7ef
--- /dev/null
+++ b/cmd/symbols_map/symbols_map_test.go
@@ -0,0 +1,217 @@
+// 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 main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"android/soong/cmd/symbols_map/symbols_map_proto"
+
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+)
+
+func Test_mergeProtos(t *testing.T) {
+	type testFile struct {
+		filename string
+		contents *symbols_map_proto.Mapping
+		missing  bool
+	}
+
+	tests := []struct {
+		name               string
+		inputs             []testFile
+		stripPrefix        string
+		writeIfChanged     bool
+		ignoreMissingFiles bool
+
+		error  string
+		output *symbols_map_proto.Mappings
+	}{
+		{
+			name:   "empty",
+			output: &symbols_map_proto.Mappings{},
+		},
+		{
+			name: "merge",
+			inputs: []testFile{
+				{
+					filename: "foo",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+				},
+				{
+					filename: "bar",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("bar"),
+						Location:   proto.String("symbols/bar"),
+						Type:       symbols_map_proto.Mapping_R8.Enum(),
+					},
+				},
+			},
+			output: &symbols_map_proto.Mappings{
+				Mappings: []*symbols_map_proto.Mapping{
+					{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+					{
+						Identifier: proto.String("bar"),
+						Location:   proto.String("symbols/bar"),
+						Type:       symbols_map_proto.Mapping_R8.Enum(),
+					},
+				},
+			},
+		},
+		{
+			name: "strip prefix",
+			inputs: []testFile{
+				{
+					filename: "foo",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+				},
+				{
+					filename: "bar",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("bar"),
+						Location:   proto.String("symbols/bar"),
+						Type:       symbols_map_proto.Mapping_R8.Enum(),
+					},
+				},
+			},
+			stripPrefix: "symbols/",
+			output: &symbols_map_proto.Mappings{
+				Mappings: []*symbols_map_proto.Mapping{
+					{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+					{
+						Identifier: proto.String("bar"),
+						Location:   proto.String("bar"),
+						Type:       symbols_map_proto.Mapping_R8.Enum(),
+					},
+				},
+			},
+		},
+		{
+			name: "missing",
+			inputs: []testFile{
+				{
+					filename: "foo",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+				},
+				{
+					filename: "bar",
+					missing:  true,
+				},
+			},
+			error: "no such file or directory",
+		},
+		{
+			name: "ignore missing",
+			inputs: []testFile{
+				{
+					filename: "foo",
+					contents: &symbols_map_proto.Mapping{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+				},
+				{
+					filename: "bar",
+					missing:  true,
+				},
+			},
+			ignoreMissingFiles: true,
+			output: &symbols_map_proto.Mappings{
+				Mappings: []*symbols_map_proto.Mapping{
+					{
+						Identifier: proto.String("foo"),
+						Location:   proto.String("symbols/foo"),
+						Type:       symbols_map_proto.Mapping_ELF.Enum(),
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			dir, err := os.MkdirTemp("", "test_mergeProtos")
+			if err != nil {
+				t.Fatalf("failed to create temporary directory: %s", err)
+			}
+			defer os.RemoveAll(dir)
+
+			var inputs []string
+			for _, in := range tt.inputs {
+				path := filepath.Join(dir, in.filename)
+				inputs = append(inputs, path)
+				if !in.missing {
+					err := writeTextProto(path, in.contents, false)
+					if err != nil {
+						t.Fatalf("failed to create input file %s: %s", path, err)
+					}
+				}
+			}
+			output := filepath.Join(dir, "out")
+
+			err = mergeProtos(output, inputs, tt.stripPrefix, tt.writeIfChanged, tt.ignoreMissingFiles)
+			if err != nil {
+				if tt.error != "" {
+					if !strings.Contains(err.Error(), tt.error) {
+						t.Fatalf("expected error %q, got %s", tt.error, err.Error())
+					}
+				} else {
+					t.Fatalf("unexpected error %q", err)
+				}
+			} else if tt.error != "" {
+				t.Fatalf("missing error %q", tt.error)
+			} else {
+				data, err := ioutil.ReadFile(output)
+				if err != nil {
+					t.Fatalf("failed to read output file %s: %s", output, err)
+				}
+				var got symbols_map_proto.Mappings
+				err = prototext.Unmarshal(data, &got)
+				if err != nil {
+					t.Fatalf("failed to unmarshal textproto %s: %s", output, err)
+				}
+
+				if !proto.Equal(tt.output, &got) {
+					t.Fatalf("expected output %q, got %q", tt.output.String(), got.String())
+				}
+			}
+		})
+	}
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/compliance/build_license_metadata/Android.bp
similarity index 68%
copy from cmd/host_bionic_inject/Android.bp
copy to compliance/build_license_metadata/Android.bp
index 16bc179..4826526 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/compliance/build_license_metadata/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -17,8 +17,14 @@
 }
 
 blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+    name: "build_license_metadata",
+    srcs: [
+        "build_license_metadata.go",
+    ],
+    deps: [
+        "license_metadata_proto",
+        "golang-protobuf-proto",
+        "golang-protobuf-encoding-prototext",
+        "soong-response",
+    ],
 }
diff --git a/compliance/build_license_metadata/build_license_metadata.go b/compliance/build_license_metadata/build_license_metadata.go
new file mode 100644
index 0000000..53d2407
--- /dev/null
+++ b/compliance/build_license_metadata/build_license_metadata.go
@@ -0,0 +1,196 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
+
+	"android/soong/compliance/license_metadata_proto"
+	"android/soong/response"
+)
+
+func newMultiString(flags *flag.FlagSet, name, usage string) *multiString {
+	var f multiString
+	flags.Var(&f, name, usage)
+	return &f
+}
+
+type multiString []string
+
+func (ms *multiString) String() string     { return strings.Join(*ms, ", ") }
+func (ms *multiString) Set(s string) error { *ms = append(*ms, s); return nil }
+
+func main() {
+	var expandedArgs []string
+	for _, arg := range os.Args[1:] {
+		if strings.HasPrefix(arg, "@") {
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
+			expandedArgs = append(expandedArgs, respArgs...)
+		} else {
+			expandedArgs = append(expandedArgs, arg)
+		}
+	}
+
+	flags := flag.NewFlagSet("flags", flag.ExitOnError)
+
+	packageName := flags.String("p", "", "license package name")
+	moduleType := newMultiString(flags, "mt", "module type")
+	kinds := newMultiString(flags, "k", "license kinds")
+	moduleClass := newMultiString(flags, "mc", "module class")
+	conditions := newMultiString(flags, "c", "license conditions")
+	notices := newMultiString(flags, "n", "license notice file")
+	deps := newMultiString(flags, "d", "license metadata file dependency")
+	sources := newMultiString(flags, "s", "source (input) dependency")
+	built := newMultiString(flags, "t", "built targets")
+	installed := newMultiString(flags, "i", "installed targets")
+	roots := newMultiString(flags, "r", "root directory of project")
+	installedMap := newMultiString(flags, "m", "map dependent targets to their installed names")
+	isContainer := flags.Bool("is_container", false, "preserved dependent target name when given")
+	outFile := flags.String("o", "", "output file")
+
+	flags.Parse(expandedArgs)
+
+	metadata := license_metadata_proto.LicenseMetadata{}
+	metadata.PackageName = proto.String(*packageName)
+	metadata.ModuleTypes = *moduleType
+	metadata.ModuleClasses = *moduleClass
+	metadata.IsContainer = proto.Bool(*isContainer)
+	metadata.Projects = findGitRoots(*roots)
+	metadata.LicenseKinds = *kinds
+	metadata.LicenseConditions = *conditions
+	metadata.LicenseTexts = *notices
+	metadata.Built = *built
+	metadata.Installed = *installed
+	metadata.InstallMap = convertInstalledMap(*installedMap)
+	metadata.Sources = *sources
+	metadata.Deps = convertDependencies(*deps)
+
+	err := writeMetadata(*outFile, &metadata)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
+		os.Exit(2)
+	}
+}
+
+func findGitRoots(dirs []string) []string {
+	ret := make([]string, len(dirs))
+	for i, dir := range dirs {
+		ret[i] = findGitRoot(dir)
+	}
+	return ret
+}
+
+// findGitRoot finds the directory at or above dir that contains a ".git" directory.  This isn't
+// guaranteed to exist, for example during remote execution, when sandboxed, when building from
+// infrastructure that doesn't use git, or when the .git directory has been removed to save space,
+// but it should be good enough for local builds.  If no .git directory is found the original value
+// is returned.
+func findGitRoot(dir string) string {
+	orig := dir
+	for dir != "" && dir != "." && dir != "/" {
+		_, err := os.Stat(filepath.Join(dir, ".git"))
+		if err == nil {
+			// Found dir/.git, return dir.
+			return dir
+		} else if !os.IsNotExist(err) {
+			// Error finding .git, return original input.
+			return orig
+		}
+		dir, _ = filepath.Split(dir)
+		dir = strings.TrimSuffix(dir, "/")
+	}
+	return orig
+}
+
+// convertInstalledMap converts a list of colon-separated from:to pairs into InstallMap proto
+// messages.
+func convertInstalledMap(installMaps []string) []*license_metadata_proto.InstallMap {
+	var ret []*license_metadata_proto.InstallMap
+
+	for _, installMap := range installMaps {
+		components := strings.Split(installMap, ":")
+		if len(components) != 2 {
+			panic(fmt.Errorf("install map entry %q contains %d colons, expected 1", installMap, len(components)-1))
+		}
+		ret = append(ret, &license_metadata_proto.InstallMap{
+			FromPath:      proto.String(components[0]),
+			ContainerPath: proto.String(components[1]),
+		})
+	}
+
+	return ret
+}
+
+// convertDependencies converts a colon-separated tuple of dependency:annotation:annotation...
+// into AnnotatedDependency proto messages.
+func convertDependencies(deps []string) []*license_metadata_proto.AnnotatedDependency {
+	var ret []*license_metadata_proto.AnnotatedDependency
+
+	for _, d := range deps {
+		components := strings.Split(d, ":")
+		dep := components[0]
+		components = components[1:]
+		ad := &license_metadata_proto.AnnotatedDependency{
+			File:        proto.String(dep),
+			Annotations: make([]string, 0, len(components)),
+		}
+		for _, ann := range components {
+			if len(ann) == 0 {
+				continue
+			}
+			ad.Annotations = append(ad.Annotations, ann)
+		}
+		ret = append(ret, ad)
+	}
+
+	return ret
+}
+
+func writeMetadata(file string, metadata *license_metadata_proto.LicenseMetadata) error {
+	buf, err := prototext.MarshalOptions{Multiline: true}.Marshal(metadata)
+	if err != nil {
+		return fmt.Errorf("error marshalling textproto: %w", err)
+	}
+
+	if file != "" {
+		err = ioutil.WriteFile(file, buf, 0666)
+		if err != nil {
+			return fmt.Errorf("error writing textproto %q: %w", file, err)
+		}
+	} else {
+		_, _ = os.Stdout.Write(buf)
+	}
+
+	return nil
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/compliance/license_metadata_proto/Android.bp
similarity index 65%
copy from cmd/host_bionic_inject/Android.bp
copy to compliance/license_metadata_proto/Android.bp
index 16bc179..3c041e4 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/compliance/license_metadata_proto/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -16,9 +16,12 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+bootstrap_go_package {
+    name: "license_metadata_proto",
+    pkgPath: "android/soong/compliance/license_metadata_proto",
+    srcs: ["license_metadata.pb.go"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
 }
diff --git a/compliance/license_metadata_proto/license_metadata.pb.go b/compliance/license_metadata_proto/license_metadata.pb.go
new file mode 100644
index 0000000..44dbc78
--- /dev/null
+++ b/compliance/license_metadata_proto/license_metadata.pb.go
@@ -0,0 +1,451 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.19.0
+// source: license_metadata.proto
+
+package license_metadata_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type LicenseMetadata struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// package_name identifies the source package. License texts are named relative to the package name.
+	PackageName   *string  `protobuf:"bytes,1,opt,name=package_name,json=packageName" json:"package_name,omitempty"`
+	ModuleTypes   []string `protobuf:"bytes,2,rep,name=module_types,json=moduleTypes" json:"module_types,omitempty"`
+	ModuleClasses []string `protobuf:"bytes,3,rep,name=module_classes,json=moduleClasses" json:"module_classes,omitempty"`
+	// projects identifies the git project(s) containing the associated source code.
+	Projects []string `protobuf:"bytes,4,rep,name=projects" json:"projects,omitempty"`
+	// license_kinds lists the kinds of licenses. e.g. SPDX-license-identifier-Apache-2.0 or legacy_notice.
+	LicenseKinds []string `protobuf:"bytes,5,rep,name=license_kinds,json=licenseKinds" json:"license_kinds,omitempty"`
+	// license_conditions lists the conditions that apply to the license kinds. e.g. notice or restricted.
+	LicenseConditions []string `protobuf:"bytes,6,rep,name=license_conditions,json=licenseConditions" json:"license_conditions,omitempty"`
+	// license_texts lists the filenames of the associated license text(s).
+	LicenseTexts []string `protobuf:"bytes,7,rep,name=license_texts,json=licenseTexts" json:"license_texts,omitempty"`
+	// is_container is true for target types that merely aggregate. e.g. .img or .zip files.
+	IsContainer *bool `protobuf:"varint,8,opt,name=is_container,json=isContainer" json:"is_container,omitempty"`
+	// built lists the built targets.
+	Built []string `protobuf:"bytes,9,rep,name=built" json:"built,omitempty"`
+	// installed lists the installed targets.
+	Installed []string `protobuf:"bytes,10,rep,name=installed" json:"installed,omitempty"`
+	// installMap identifies the substitutions to make to path names when moving into installed location.
+	InstallMap []*InstallMap `protobuf:"bytes,11,rep,name=install_map,json=installMap" json:"install_map,omitempty"`
+	// sources lists the targets depended on.
+	Sources []string `protobuf:"bytes,12,rep,name=sources" json:"sources,omitempty"`
+	// deps lists the license metadata files depended on.
+	Deps []*AnnotatedDependency `protobuf:"bytes,13,rep,name=deps" json:"deps,omitempty"`
+}
+
+func (x *LicenseMetadata) Reset() {
+	*x = LicenseMetadata{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *LicenseMetadata) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*LicenseMetadata) ProtoMessage() {}
+
+func (x *LicenseMetadata) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use LicenseMetadata.ProtoReflect.Descriptor instead.
+func (*LicenseMetadata) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *LicenseMetadata) GetPackageName() string {
+	if x != nil && x.PackageName != nil {
+		return *x.PackageName
+	}
+	return ""
+}
+
+func (x *LicenseMetadata) GetModuleTypes() []string {
+	if x != nil {
+		return x.ModuleTypes
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetModuleClasses() []string {
+	if x != nil {
+		return x.ModuleClasses
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetProjects() []string {
+	if x != nil {
+		return x.Projects
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseKinds() []string {
+	if x != nil {
+		return x.LicenseKinds
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseConditions() []string {
+	if x != nil {
+		return x.LicenseConditions
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetLicenseTexts() []string {
+	if x != nil {
+		return x.LicenseTexts
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetIsContainer() bool {
+	if x != nil && x.IsContainer != nil {
+		return *x.IsContainer
+	}
+	return false
+}
+
+func (x *LicenseMetadata) GetBuilt() []string {
+	if x != nil {
+		return x.Built
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetInstalled() []string {
+	if x != nil {
+		return x.Installed
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetInstallMap() []*InstallMap {
+	if x != nil {
+		return x.InstallMap
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetSources() []string {
+	if x != nil {
+		return x.Sources
+	}
+	return nil
+}
+
+func (x *LicenseMetadata) GetDeps() []*AnnotatedDependency {
+	if x != nil {
+		return x.Deps
+	}
+	return nil
+}
+
+// InstallMap messages describe the mapping from an input filesystem file to the path to the file
+// in a container.
+type InstallMap struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The input path on the filesystem.
+	FromPath *string `protobuf:"bytes,1,opt,name=from_path,json=fromPath" json:"from_path,omitempty"`
+	// The path to the file inside the container or installed location.
+	ContainerPath *string `protobuf:"bytes,2,opt,name=container_path,json=containerPath" json:"container_path,omitempty"`
+}
+
+func (x *InstallMap) Reset() {
+	*x = InstallMap{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *InstallMap) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*InstallMap) ProtoMessage() {}
+
+func (x *InstallMap) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use InstallMap.ProtoReflect.Descriptor instead.
+func (*InstallMap) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *InstallMap) GetFromPath() string {
+	if x != nil && x.FromPath != nil {
+		return *x.FromPath
+	}
+	return ""
+}
+
+func (x *InstallMap) GetContainerPath() string {
+	if x != nil && x.ContainerPath != nil {
+		return *x.ContainerPath
+	}
+	return ""
+}
+
+// AnnotateDepencency messages describe edges in the build graph.
+type AnnotatedDependency struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The path to the dependency license metadata file.
+	File *string `protobuf:"bytes,1,opt,name=file" json:"file,omitempty"`
+	// The annotations attached to the dependency.
+	Annotations []string `protobuf:"bytes,2,rep,name=annotations" json:"annotations,omitempty"`
+}
+
+func (x *AnnotatedDependency) Reset() {
+	*x = AnnotatedDependency{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_license_metadata_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AnnotatedDependency) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AnnotatedDependency) ProtoMessage() {}
+
+func (x *AnnotatedDependency) ProtoReflect() protoreflect.Message {
+	mi := &file_license_metadata_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AnnotatedDependency.ProtoReflect.Descriptor instead.
+func (*AnnotatedDependency) Descriptor() ([]byte, []int) {
+	return file_license_metadata_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *AnnotatedDependency) GetFile() string {
+	if x != nil && x.File != nil {
+		return *x.File
+	}
+	return ""
+}
+
+func (x *AnnotatedDependency) GetAnnotations() []string {
+	if x != nil {
+		return x.Annotations
+	}
+	return nil
+}
+
+var File_license_metadata_proto protoreflect.FileDescriptor
+
+var file_license_metadata_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x16, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x22, 0x8a, 0x04, 0x0a, 0x0f, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f,
+	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x63, 0x6b,
+	0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x6d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65,
+	0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x0a,
+	0x0d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x73, 0x18, 0x05,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x4b, 0x69, 0x6e,
+	0x64, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x63, 0x6f,
+	0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11,
+	0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x65, 0x78,
+	0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73,
+	0x65, 0x54, 0x65, 0x78, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e,
+	0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73,
+	0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69,
+	0x6c, 0x74, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a,
+	0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x0b, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e,
+	0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x49, 0x6e, 0x73, 0x74,
+	0x61, 0x6c, 0x6c, 0x4d, 0x61, 0x70, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x4d,
+	0x61, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x0c, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x04,
+	0x64, 0x65, 0x70, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x44, 0x65, 0x70,
+	0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x04, 0x64, 0x65, 0x70, 0x73, 0x22, 0x50, 0x0a,
+	0x0a, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x4d, 0x61, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x66,
+	0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+	0x66, 0x72, 0x6f, 0x6d, 0x50, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
+	0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x61, 0x74, 0x68, 0x22,
+	0x4b, 0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x44, 0x65, 0x70, 0x65,
+	0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x6e,
+	0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x31, 0x5a, 0x2f,
+	0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6f,
+	0x6d, 0x70, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x2f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65,
+	0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	file_license_metadata_proto_rawDescOnce sync.Once
+	file_license_metadata_proto_rawDescData = file_license_metadata_proto_rawDesc
+)
+
+func file_license_metadata_proto_rawDescGZIP() []byte {
+	file_license_metadata_proto_rawDescOnce.Do(func() {
+		file_license_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_license_metadata_proto_rawDescData)
+	})
+	return file_license_metadata_proto_rawDescData
+}
+
+var file_license_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_license_metadata_proto_goTypes = []interface{}{
+	(*LicenseMetadata)(nil),     // 0: build_license_metadata.LicenseMetadata
+	(*InstallMap)(nil),          // 1: build_license_metadata.InstallMap
+	(*AnnotatedDependency)(nil), // 2: build_license_metadata.AnnotatedDependency
+}
+var file_license_metadata_proto_depIdxs = []int32{
+	1, // 0: build_license_metadata.LicenseMetadata.install_map:type_name -> build_license_metadata.InstallMap
+	2, // 1: build_license_metadata.LicenseMetadata.deps:type_name -> build_license_metadata.AnnotatedDependency
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_license_metadata_proto_init() }
+func file_license_metadata_proto_init() {
+	if File_license_metadata_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_license_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LicenseMetadata); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_license_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*InstallMap); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_license_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AnnotatedDependency); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_license_metadata_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_license_metadata_proto_goTypes,
+		DependencyIndexes: file_license_metadata_proto_depIdxs,
+		MessageInfos:      file_license_metadata_proto_msgTypes,
+	}.Build()
+	File_license_metadata_proto = out.File
+	file_license_metadata_proto_rawDesc = nil
+	file_license_metadata_proto_goTypes = nil
+	file_license_metadata_proto_depIdxs = nil
+}
diff --git a/compliance/license_metadata_proto/license_metadata.proto b/compliance/license_metadata_proto/license_metadata.proto
new file mode 100644
index 0000000..1b4f34f
--- /dev/null
+++ b/compliance/license_metadata_proto/license_metadata.proto
@@ -0,0 +1,76 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package build_license_metadata;
+option go_package = "android/soong/compliance/license_metadata_proto";
+
+message LicenseMetadata {
+  // package_name identifies the source package. License texts are named relative to the package name.
+  optional string package_name = 1;
+
+  repeated string module_types = 2;
+
+  repeated string module_classes = 3;
+
+  // projects identifies the git project(s) containing the associated source code.
+  repeated string projects = 4;
+
+  // license_kinds lists the kinds of licenses. e.g. SPDX-license-identifier-Apache-2.0 or legacy_notice.
+  repeated string license_kinds = 5;
+
+  // license_conditions lists the conditions that apply to the license kinds. e.g. notice or restricted.
+  repeated string license_conditions = 6;
+
+  // license_texts lists the filenames of the associated license text(s).
+  repeated string license_texts = 7;
+
+  // is_container is true for target types that merely aggregate. e.g. .img or .zip files.
+  optional bool is_container = 8;
+
+  // built lists the built targets.
+  repeated string built = 9;
+
+  // installed lists the installed targets.
+  repeated string installed = 10;
+
+  // installMap identifies the substitutions to make to path names when moving into installed location.
+  repeated InstallMap install_map = 11;
+
+  // sources lists the targets depended on.
+  repeated string sources = 12;
+
+  // deps lists the license metadata files depended on.
+  repeated AnnotatedDependency deps = 13;
+}
+
+// InstallMap messages describe the mapping from an input filesystem file to the path to the file
+// in a container.
+message InstallMap {
+  // The input path on the filesystem.
+  optional string from_path = 1;
+
+  // The path to the file inside the container or installed location.
+  optional string container_path = 2;
+}
+
+// AnnotateDepencency messages describe edges in the build graph.
+message AnnotatedDependency {
+  // The path to the dependency license metadata file.
+  optional string file = 1;
+
+  // The annotations attached to the dependency.
+  repeated string annotations = 2;
+}
diff --git a/cuj/Android.bp b/cuj/Android.bp
index a2da6e6..f9cfdc1 100644
--- a/cuj/Android.bp
+++ b/cuj/Android.bp
@@ -7,6 +7,7 @@
     deps: [
         "soong-ui-build",
         "soong-ui-logger",
+        "soong-ui-signal",
         "soong-ui-terminal",
         "soong-ui-tracer",
     ],
diff --git a/cuj/cuj.go b/cuj/cuj.go
index b4ae9a2..869e0f7 100644
--- a/cuj/cuj.go
+++ b/cuj/cuj.go
@@ -27,6 +27,7 @@
 	"android/soong/ui/build"
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
+	"android/soong/ui/signal"
 	"android/soong/ui/status"
 	"android/soong/ui/terminal"
 	"android/soong/ui/tracer"
@@ -47,7 +48,7 @@
 
 // Run runs a single build command.  It emulates the "m" command line by calling into Soong UI directly.
 func (t *Test) Run(logsDir string) {
-	output := terminal.NewStatusOutput(os.Stdout, "", false, false)
+	output := terminal.NewStatusOutput(os.Stdout, "", false, false, false)
 
 	log := logger.New(output)
 	defer log.Cleanup()
@@ -65,7 +66,7 @@
 	stat.AddOutput(output)
 	stat.AddOutput(trace.StatusTracer())
 
-	build.SetupSignals(log, cancel, func() {
+	signal.SetupSignals(log, cancel, func() {
 		trace.Close()
 		log.Cleanup()
 		stat.Finish()
@@ -137,6 +138,8 @@
 
 	cujDir := filepath.Join(outDir, "cuj_tests")
 
+	wd, _ := os.Getwd()
+	os.Setenv("TOP", wd)
 	// Use a subdirectory for the out directory for the tests to keep them isolated.
 	os.Setenv("OUT_DIR", filepath.Join(cujDir, "out"))
 
diff --git a/cuj/run_cuj_tests.sh b/cuj/run_cuj_tests.sh
index b4f9f88..a746bd5 100755
--- a/cuj/run_cuj_tests.sh
+++ b/cuj/run_cuj_tests.sh
@@ -18,11 +18,10 @@
 cd "$ANDROID_TOP"
 
 export OUT_DIR="${OUT_DIR:-out}"
-readonly SOONG_OUT="${OUT_DIR}/soong"
 
-build/soong/soong_ui.bash --make-mode "${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests"
+build/soong/soong_ui.bash --make-mode "${OUT_DIR}/host/${OS}-x86/bin/cuj_tests"
 
-"${SOONG_OUT}/host/${OS}-x86/bin/cuj_tests" || true
+"${OUT_DIR}/host/${OS}-x86/bin/cuj_tests" || true
 
 if [ -n "${DIST_DIR}" ]; then
   cp -r "${OUT_DIR}/cuj_tests/logs" "${DIST_DIR}"
diff --git a/dexpreopt/DEXPREOPT_IMPLEMENTATION.md b/dexpreopt/DEXPREOPT_IMPLEMENTATION.md
new file mode 100644
index 0000000..c3a1730
--- /dev/null
+++ b/dexpreopt/DEXPREOPT_IMPLEMENTATION.md
@@ -0,0 +1,258 @@
+## Dexpreopt implementation
+
+### Introduction
+
+All dexpreopted Java code falls into three categories:
+
+- bootclasspath
+- system server
+- apps and libraries
+
+Dexpreopt implementation for bootclasspath libraries (boot images) is located in
+[soong/java] (see e.g. [soong/java/dexpreopt_bootjars.go]), and install rules
+are in [make/core/dex_preopt.mk].
+
+Dexpreopt implementation for system server, libraries and apps is located in
+[soong/dexpreopt]. For the rest of this section we focus primarily on it (and
+not boot images).
+
+Dexpeopt implementation is split across the Soong part and the Make part. The
+core logic is in Soong, and Make only generates configs and scripts to pass
+information to Soong.
+
+### Global and module dexpreopt.config
+
+The build system generates a global JSON dexpreopt config that is populated from
+product variables. This is static configuration that is passed to both Soong and
+Make. The `$OUT/soong/dexpreopt.config` file is generated in
+[make/core/dex_preopt_config.mk]. Soong reads it in [soong/dexpreopt/config.go]
+and makes a device-specific copy (this is needed to ensure incremental build
+correctness). The global config contains lists of bootclasspath jars, system
+server jars, dex2oat options, global switches that enable and disable parts of
+dexpreopt and so on.
+
+The build system also generates a module config for each dexpreopted package. It
+contains package-specific configuration that is derived from the global
+configuration and Android.bp or Android.mk module for the package.
+
+Module configs for Make packages are generated in
+[make/core/dex_preopt_odex_install.mk]; they are materialized as per-package
+JSON dexpreopt.config files.
+
+Module configs in Soong are not materialized as dexpreopt.config files and exist
+as Go structures in memory, unless it is necessary to materialize them as a file
+for dependent Make packages or for post-dexpreopting. Module configs are defined
+in [soong/dexpreopt/config.go].
+
+### Dexpreopt in Soong
+
+The Soong implementation of dexpreopt consists roughly of the following steps:
+
+- Read global dexpreopt config passed from Make ([soong/dexpreopt/config.go]).
+
+- Construct a static boot image config ([soong/java/dexpreopt_config.go]).
+
+- During dependency mutator pass, for each suitable module:
+    - add uses-library dependencies (e.g. for apps: [soong/java/app.go:deps])
+
+- During rule generation pass, for each suitable module:
+    - compute transitive uses-library dependency closure
+      ([soong/java/java.go:addCLCFromDep])
+
+    - construct CLC from the dependency closure
+      ([soong/dexpreopt/class_loader_context.go])
+
+    - construct module config with CLC, boot image locations, etc.
+      ([soong/java/dexpreopt.go])
+
+    - generate build rules to verify build-time CLC against the manifest (e.g.
+      for apps: [soong/java/app.go:verifyUsesLibraries])
+
+    - generate dexpreopt build rule ([soong/dexpreopt/dexpreopt.go])
+
+- At the end of rule generation pass:
+    - generate build rules for boot images ([soong/java/dexpreopt_bootjars.go],
+      [soong/java/bootclasspath_fragment.go] and
+      [soong/java/platform_bootclasspath.go])
+
+### Dexpreopt in Make - dexpreopt_gen
+
+In order to reuse the same dexpreopt implementation for both Soong and Make
+packages, part of Soong is compiled into a standalone binary dexpreopt_gen. It
+runs during the Ninja stage of the build and generates shell scripts with
+dexpreopt build rules for Make packages, and then executes them.
+
+This setup causes many inconveniences. To name a few:
+
+- Errors in the build rules are only revealed at the late stage of the build.
+
+- These rules are not tested by the presubmit builds that run `m nothing` on
+  many build targets/products.
+
+- It is impossible to find dexpreopt build rules in the generated Ninja files.
+
+However all these issues are a lesser evil compared to having a duplicate
+dexpreopt implementation in Make. Also note that it would be problematic to
+reimplement the logic in Make anyway, because Android.mk modules are not
+processed in the order of uses-library dependencies and propagating dependency
+information from one module to another would require a similar workaround with
+a script.
+
+Dexpreopt for Make packages involves a few steps:
+
+- At Soong phase (during `m nothing`), see dexpreopt_gen:
+    - generate build rules for dexpreopt_gen binary
+
+- At Make/Kati phase (during `m nothing`), see
+  [make/core/dex_preopt_odex_install.mk]:
+    - generate build rules for module dexpreopt.config
+
+    - generate build rules for merging dependency dexpreopt.config files (see
+      [make/core/dex_preopt_config_merger.py])
+
+    - generate build rules for dexpreopt_gen invocation
+
+    - generate build rules for executing dexpreopt.sh scripts
+
+- At Ninja phase (during `m`):
+    - generate dexpreopt.config files
+
+    - execute dexpreopt_gen rules (generate dexpreopt.sh scripts)
+
+    - execute dexpreopt.sh scripts (this runs the actual dexpreopt)
+
+The Make/Kati phase adds all the necessary dependencies that trigger
+dexpreopt_gen and dexpreopt.sh rules. The real dexpreopt command (dex2oat
+invocation that will be executed to AOT-compile a package) is in the
+dexpreopt.sh script, which is generated close to the end of the build.
+
+### Indirect build rules
+
+The process described above for Make packages involves "indirect build rules",
+i.e. build rules that are generated not at the time when the build system is
+created (which is a small step at the very beginning of the build triggered with
+`m nothing`), but at the time when the actual build is done (`m` phase).
+
+Some build systems, such as Make, allow modifications of the build graph during
+the build. Other build systems, such as Soong, have a clear separation into the
+first "generation phase" (this is when build rules are created) and the second
+"build phase" (this is when the build rules are executed), and they do not allow
+modifications of the dependency graph during the second phase. The Soong
+approach is better from performance standpoint, because with the Make approach
+there are no guarantees regarding the time of the build --- recursive build
+graph modfications continue until fixpoint. However the Soong approach is also
+more restictive, as it can only generate build rules from the information that
+is passed to the build system via global configuration, Android.bp files or
+encoded in the Go code. Any other information (such as the contents of the Java
+manifest files) are not accessible and cannot be used to generate build rules.
+
+Hence the need for the "indirect build rules": during the generation phase only
+stubs of the build rules are generated, and the real rules are generated by the
+stub rules during the build phase (and executed immediately). Note that the
+build system still has to add all the necessary dependencies during the
+generation phase, because it will not be possible to change build order during
+the build phase.
+
+Indirect buils rules are used in a couple of places in dexpreopt:
+
+- [soong/scripts/manifest_check.py]: first to extract targetSdkVersion from the
+  manifest, and later to extract `<uses-library/>` tags from the manifest and
+  compare them to the uses-library list known to the build system
+
+- [soong/scripts/construct_context.py]: to trim compatibility libraries in CLC
+
+- [make/core/dex_preopt_config_merger.py]: to merge information from
+  dexpreopt.config files for uses-library dependencies into the dependent's
+  dexpreopt.config file (mostly the CLC)
+
+- autogenerated dexpreopt.sh scripts: to call dexpreopt_gen
+
+### Consistency check - manifest_check.py
+
+Because the information from the manifests has to be duplicated in the
+Android.bp/Android.mk files, there is a danger that it may get out of sync. To
+guard against that, the build system generates a rule that verifies
+uses-libraries: checks the metadata in the build files against the contents of a
+manifest. The manifest can be available as a source file, or as part of a
+prebuilt APK.
+
+The check is implemented in [soong/scripts/manifest_check.py].
+
+It is possible to turn off the check globally for a product by setting
+`PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true` in a product makefile, or for a
+particular build by setting `RELAX_USES_LIBRARY_CHECK=true`.
+
+### Compatibility libraries - construct_context.py
+
+Compatibility libraries are libraries that didn’t exist prior to a certain SDK
+version (say, `N`), but classes in them were in the bootclasspath jars, etc.,
+and in version `N` they have been separated into a standalone uses-library.
+Compatibility libraries should only be in the CLC of an app if its
+`targetSdkVersion` in the manifest is less than `N`.
+
+Currently compatibility libraries only affect apps (but not other libraries).
+
+The build system cannot see `targetSdkVersion` of an app at the time it
+generates dexpreopt build rules, so it doesn't know whether to add compatibility
+libaries to CLC or not. As a workaround, the build system includes all
+compatibility libraries regardless of the app version, and appends some extra
+logic to the dexpreopt rule that will extract `targetSdkVersion` from the
+manifest and filter CLC based on that version during Ninja stage of the build,
+immediately before executing the dexpreopt command (see the
+soong/scripts/construct_context.py script).
+
+As of the time of writing (January 2022), there are the following compatibility
+libraries:
+
+- org.apache.http.legacy (SDK 28)
+- android.hidl.base-V1.0-java (SDK 29)
+- android.hidl.manager-V1.0-java (SDK 29)
+- android.test.base (SDK 30)
+- android.test.mock (SDK 30)
+
+### Manifest fixer
+
+Sometimes uses-library tags are missing from the source manifest of a
+library/app. This may happen for example if one of the transitive dependencies
+of the library/app starts using another uses-library, and the library/app's
+manifest isn't updated to include it.
+
+Soong can compute some of the missing uses-library tags for a given library/app
+automatically as SDK libraries in the transitive dependency closure of the
+library/app. The closure is needed because a library/app may depend on a static
+library that may in turn depend on an SDK library (possibly transitively via
+another library).
+
+Not all uses-library tags can be computed in this way, because some of the
+uses-library dependencies are not SDK libraries, or they are not reachable via
+transitive dependency closure. But when possible, allowing Soong to calculate
+the manifest entries is less prone to errors and simplifies maintenance. For
+example, consider a situation when many apps use some static library that adds a
+new uses-library dependency -- all the apps will have to be updated. That is
+difficult to maintain.
+
+There is also a manifest merger, because sometimes the final manifest of an app
+is merged from a few dependency manifests, so the final manifest installed on
+devices contains a superset of uses-library tags of the source manifest of the
+app.
+
+
+[make/core/dex_preopt.mk]: https://cs.android.com/android/platform/superproject/+/master:build/make/core/dex_preopt.mk
+[make/core/dex_preopt_config.mk]: https://cs.android.com/android/platform/superproject/+/master:build/make/core/dex_preopt_config.mk
+[make/core/dex_preopt_config_merger.py]: https://cs.android.com/android/platform/superproject/+/master:build/make/core/dex_preopt_config_merger.py
+[make/core/dex_preopt_odex_install.mk]: https://cs.android.com/android/platform/superproject/+/master:build/make/core/dex_preopt_odex_install.mk
+[soong/dexpreopt]: https://cs.android.com/android/platform/superproject/+/master:build/soong/dexpreopt
+[soong/dexpreopt/class_loader_context.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/dexpreopt/class_loader_context.go
+[soong/dexpreopt/config.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/dexpreopt/config.go
+[soong/dexpreopt/dexpreopt.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/dexpreopt/dexpreopt.go
+[soong/java]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java
+[soong/java/app.go:deps]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/app.go?q=%22func%20\(u%20*usesLibrary\)%20deps%22
+[soong/java/app.go:verifyUsesLibraries]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/app.go?q=%22func%20\(u%20*usesLibrary\)%20verifyUsesLibraries%22
+[soong/java/bootclasspath_fragment.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/bootclasspath_fragment.go
+[soong/java/dexpreopt.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/dexpreopt.go
+[soong/java/dexpreopt_bootjars.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/dexpreopt_bootjars.go
+[soong/java/dexpreopt_config.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/dexpreopt_config.go
+[soong/java/java.go:addCLCFromDep]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/java.go?q=%22func%20addCLCfromDep%22
+[soong/java/platform_bootclasspath.go]: https://cs.android.com/android/platform/superproject/+/master:build/soong/java/platform_bootclasspath.go
+[soong/scripts/construct_context.py]: https://cs.android.com/android/platform/superproject/+/master:build/soong/scripts/construct_context.py
+[soong/scripts/manifest_check.py]: https://cs.android.com/android/platform/superproject/+/master:build/soong/scripts/manifest_check.py
diff --git a/dexpreopt/OWNERS b/dexpreopt/OWNERS
index 166472f..5a2a198 100644
--- a/dexpreopt/OWNERS
+++ b/dexpreopt/OWNERS
@@ -1 +1 @@
-per-file * = ngeoffray@google.com,calin@google.com,mathieuc@google.com
+per-file * = ngeoffray@google.com,calin@google.com,skvadrik@google.com
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index 57b4c4f..36513b6 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -15,6 +15,7 @@
 package dexpreopt
 
 import (
+	"encoding/json"
 	"fmt"
 	"sort"
 	"strconv"
@@ -192,6 +193,13 @@
 	// The name of the library.
 	Name string
 
+	// 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
 
@@ -202,6 +210,34 @@
 	Subcontexts []*ClassLoaderContext
 }
 
+// excludeLibs excludes the libraries from this ClassLoaderContext.
+//
+// This treats the supplied context as being immutable (as it may come from a dependency). So, it
+// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within this context then this will return a deep copy of this without those libraries.
+//
+// If this ClassLoaderContext matches one of the libraries to exclude then this returns (nil, true)
+// to indicate that this context should be excluded from the containing list.
+//
+// If any of this ClassLoaderContext's Subcontexts reference the excluded libraries then this
+// returns a pointer to a copy of this without the excluded libraries and true to indicate that this
+// was copied.
+//
+// Otherwise, this returns a pointer to this and false to indicate that this was not copied.
+func (c *ClassLoaderContext) excludeLibs(excludedLibs []string) (*ClassLoaderContext, bool) {
+	if android.InList(c.Name, excludedLibs) {
+		return nil, true
+	}
+
+	if excludedList, modified := excludeLibsFromCLCList(c.Subcontexts, excludedLibs); modified {
+		clcCopy := *c
+		clcCopy.Subcontexts = excludedList
+		return &clcCopy, true
+	}
+
+	return c, false
+}
+
 // ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
 // AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
 //
@@ -254,8 +290,9 @@
 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,
-	hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) error {
+func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int,
+	lib string, optional, implicit bool, hostPath, installPath android.Path,
+	nestedClcMap ClassLoaderContextMap) error {
 
 	// For prebuilts, library should have the same name as the source module.
 	lib = android.RemoveOptionalPrebuiltPrefix(lib)
@@ -285,16 +322,26 @@
 	}
 	subcontexts := nestedClcMap[AnySdkVersion]
 
-	// If the library with this name is already present as one of the unconditional top-level
-	// components, do not re-add it.
+	// Check if the library with this name is already present in unconditional top-level CLC.
 	for _, clc := range clcMap[sdkVer] {
-		if clc.Name == lib {
+		if clc.Name != lib {
+			// Ok, a different library.
+		} else if clc.Host == hostPath && clc.Device == devicePath {
+			// Ok, the same library with the same paths. Don't re-add it, but don't raise an error
+			// either, as the same library may be reachable via different transitional dependencies.
 			return nil
+		} else {
+			// Fail, as someone is trying to add the same library with different paths. This likely
+			// indicates an error somewhere else, like trying to add a stub library.
+			return fmt.Errorf("a <uses-library> named %q is already in class loader context,"+
+				"but the library paths are different:\t\n", lib)
 		}
 	}
 
 	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
 		Name:        lib,
+		Optional:    optional,
+		Implicit:    implicit,
 		Host:        hostPath,
 		Device:      devicePath,
 		Subcontexts: subcontexts,
@@ -307,9 +354,10 @@
 // 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, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
+	lib string, optional, implicit bool, hostPath, installPath android.Path,
+	nestedClcMap ClassLoaderContextMap) {
 
-	err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, nestedClcMap)
+	err := clcMap.addContext(ctx, sdkVer, lib, optional, implicit, hostPath, installPath, nestedClcMap)
 	if err != nil {
 		ctx.ModuleErrorf(err.Error())
 	}
@@ -352,15 +400,101 @@
 // Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
 // 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.
-func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
+// Required and optional libraries are in separate lists.
+func (clcMap ClassLoaderContextMap) usesLibs(implicit bool) (required []string, optional []string) {
 	if clcMap != nil {
 		clcs := clcMap[AnySdkVersion]
-		ulibs = make([]string, 0, len(clcs))
+		required = make([]string, 0, len(clcs))
+		optional = make([]string, 0, len(clcs))
 		for _, clc := range clcs {
-			ulibs = append(ulibs, clc.Name)
+			if implicit && !clc.Implicit {
+				// Skip, this is an explicit library and we need only the implicit ones.
+			} else if clc.Optional {
+				optional = append(optional, clc.Name)
+			} else {
+				required = append(required, clc.Name)
+			}
 		}
 	}
-	return ulibs
+	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, "", "  ")
+	if err != nil {
+		panic(err)
+	}
+	return string(bytes)
+}
+
+// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list.
+//
+// This treats the supplied list as being immutable (as it may come from a dependency). So, it
+// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within the contexts in the list then this will return a deep copy of the list without those
+// libraries.
+//
+// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a
+// copy of this list without the excluded libraries and true to indicate that this was copied.
+//
+// Otherwise, this returns the list and false to indicate that this was not copied.
+func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) {
+	modifiedList := false
+	copiedList := make([]*ClassLoaderContext, 0, len(clcList))
+	for _, clc := range clcList {
+		resultClc, modifiedClc := clc.excludeLibs(excludedLibs)
+		if resultClc != nil {
+			copiedList = append(copiedList, resultClc)
+		}
+		modifiedList = modifiedList || modifiedClc
+	}
+
+	if modifiedList {
+		return copiedList, true
+	} else {
+		return clcList, false
+	}
+}
+
+// ExcludeLibs excludes the libraries from the ClassLoaderContextMap.
+//
+// If the list o libraries is empty then this returns the ClassLoaderContextMap.
+//
+// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So,
+// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
+// within the contexts in the map then this will return a deep copy of the map without those
+// libraries.
+//
+// Otherwise, this returns the map unchanged.
+func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap {
+	if len(excludedLibs) == 0 {
+		return clcMap
+	}
+
+	excludedClcMap := make(ClassLoaderContextMap)
+	modifiedMap := false
+	for sdkVersion, clcList := range clcMap {
+		excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs)
+		if len(excludedList) != 0 {
+			excludedClcMap[sdkVersion] = excludedList
+		}
+		modifiedMap = modifiedMap || modifiedList
+	}
+
+	if modifiedMap {
+		return excludedClcMap
+	} else {
+		return clcMap
+	}
 }
 
 // Now that the full unconditional context is known, reconstruct conditional context.
@@ -370,7 +504,8 @@
 // TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps.
 //
 func fixClassLoaderContext(clcMap ClassLoaderContextMap) {
-	usesLibs := clcMap.UsesLibs()
+	required, optional := clcMap.UsesLibs()
+	usesLibs := append(required, optional...)
 
 	for sdkVer, clcs := range clcMap {
 		if sdkVer == AnySdkVersion {
@@ -495,6 +630,8 @@
 // the same as Soong representation except that SDK versions and paths are represented with strings.
 type jsonClassLoaderContext struct {
 	Name        string
+	Optional    bool
+	Implicit    bool
 	Host        string
 	Device      string
 	Subcontexts []*jsonClassLoaderContext
@@ -526,6 +663,8 @@
 	for _, clc := range jClcs {
 		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),
@@ -548,9 +687,18 @@
 func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) []*jsonClassLoaderContext {
 	jClcs := make([]*jsonClassLoaderContext, len(clcs))
 	for i, clc := range clcs {
+		var host string
+		if clc.Host == nil {
+			// Defer build failure to when this CLC is actually used.
+			host = fmt.Sprintf("implementation-jar-for-%s-is-not-available.jar", clc.Name)
+		} else {
+			host = clc.Host.String()
+		}
 		jClcs[i] = &jsonClassLoaderContext{
 			Name:        clc.Name,
-			Host:        clc.Host.String(),
+			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 610a4c9..5d3a9d9 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -49,32 +49,35 @@
 	//
 	ctx := testContext()
 
+	optional := false
+	implicit := true
+
 	m := make(ClassLoaderContextMap)
 
-	m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, AnySdkVersion, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
+	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)
 
 	// Add some libraries with nested subcontexts.
 
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, AnySdkVersion, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"), nil)
-	m1.AddContext(ctx, AnySdkVersion, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"), nil)
+	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)
 
 	m2 := make(ClassLoaderContextMap)
-	m2.AddContext(ctx, AnySdkVersion, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"), nil)
-	m2.AddContext(ctx, AnySdkVersion, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"), nil)
-	m2.AddContext(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
+	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)
 
 	m3 := make(ClassLoaderContextMap)
-	m3.AddContext(ctx, AnySdkVersion, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"), nil)
-	m3.AddContext(ctx, AnySdkVersion, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"), nil)
+	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)
 
-	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+	m.AddContext(ctx, AnySdkVersion, "d", optional, implicit, 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", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
-	m.AddContext(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+	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)
 
 	// Merge map with implicit root library that is among toplevel contexts => does nothing.
 	m.AddContextMap(m1, "c")
@@ -83,12 +86,12 @@
 	m.AddContextMap(m3, "m_g")
 
 	// Compatibility libraries with unknown install paths get default paths.
-	m.AddContext(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
-	m.AddContext(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlManager, optional, implicit, buildPath(ctx, AndroidHidlManager), nil, nil)
+	m.AddContext(ctx, 29, AndroidHidlBase, optional, implicit, 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, buildPath(ctx, AndroidTestMock), nil, nil)
+	m.AddContext(ctx, 30, AndroidTestMock, optional, implicit, buildPath(ctx, AndroidTestMock), nil, nil)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -96,10 +99,10 @@
 
 	var haveStr string
 	var havePaths android.Paths
-	var haveUsesLibs []string
+	var haveUsesLibsReq, haveUsesLibsOpt []string
 	if valid && validationError == nil {
 		haveStr, havePaths = ComputeClassLoaderContext(m)
-		haveUsesLibs = m.UsesLibs()
+		haveUsesLibsReq, haveUsesLibsOpt = m.UsesLibs()
 	}
 
 	// Test that validation is successful (all paths are known).
@@ -112,16 +115,16 @@
 	// Test that class loader context structure is correct.
 	t.Run("string", func(t *testing.T) {
 		wantStr := " --host-context-for-sdk 29 " +
-			"PCL[out/" + AndroidHidlManager + ".jar]#" +
-			"PCL[out/" + AndroidHidlBase + ".jar]" +
+			"PCL[out/soong/" + AndroidHidlManager + ".jar]#" +
+			"PCL[out/soong/" + AndroidHidlBase + ".jar]" +
 			" --target-context-for-sdk 29 " +
 			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
 			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
 			" --host-context-for-sdk any " +
-			"PCL[out/a.jar]#PCL[out/b.jar]#PCL[out/c.jar]#PCL[out/d.jar]" +
-			"{PCL[out/a2.jar]#PCL[out/b2.jar]#PCL[out/c2.jar]" +
-			"{PCL[out/a1.jar]#PCL[out/b1.jar]}}#" +
-			"PCL[out/f.jar]#PCL[out/a3.jar]#PCL[out/b3.jar]" +
+			"PCL[out/soong/a.jar]#PCL[out/soong/b.jar]#PCL[out/soong/c.jar]#PCL[out/soong/d.jar]" +
+			"{PCL[out/soong/a2.jar]#PCL[out/soong/b2.jar]#PCL[out/soong/c2.jar]" +
+			"{PCL[out/soong/a1.jar]#PCL[out/soong/b1.jar]}}#" +
+			"PCL[out/soong/f.jar]#PCL[out/soong/a3.jar]#PCL[out/soong/b3.jar]" +
 			" --target-context-for-sdk any " +
 			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
 			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
@@ -135,11 +138,11 @@
 	// Test that all expected build paths are gathered.
 	t.Run("paths", func(t *testing.T) {
 		wantPaths := []string{
-			"out/android.hidl.manager-V1.0-java.jar", "out/android.hidl.base-V1.0-java.jar",
-			"out/a.jar", "out/b.jar", "out/c.jar", "out/d.jar",
-			"out/a2.jar", "out/b2.jar", "out/c2.jar",
-			"out/a1.jar", "out/b1.jar",
-			"out/f.jar", "out/a3.jar", "out/b3.jar",
+			"out/soong/android.hidl.manager-V1.0-java.jar", "out/soong/android.hidl.base-V1.0-java.jar",
+			"out/soong/a.jar", "out/soong/b.jar", "out/soong/c.jar", "out/soong/d.jar",
+			"out/soong/a2.jar", "out/soong/b2.jar", "out/soong/c2.jar",
+			"out/soong/a1.jar", "out/soong/b1.jar",
+			"out/soong/f.jar", "out/soong/a3.jar", "out/soong/b3.jar",
 		}
 		if !reflect.DeepEqual(wantPaths, havePaths.Strings()) {
 			t.Errorf("\nwant paths: %s\nhave paths: %s", wantPaths, havePaths)
@@ -148,20 +151,26 @@
 
 	// Test for libraries that are added by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		wantUsesLibs := []string{"a", "b", "c", "d", "f", "a3", "b3"}
-		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
-			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
+		wantUsesLibsReq := []string{"a", "b", "c", "d", "f", "a3", "b3"}
+		wantUsesLibsOpt := []string{}
+		if !reflect.DeepEqual(wantUsesLibsReq, haveUsesLibsReq) {
+			t.Errorf("\nwant required uses libs: %s\nhave required uses libs: %s", wantUsesLibsReq, haveUsesLibsReq)
+		}
+		if !reflect.DeepEqual(wantUsesLibsOpt, haveUsesLibsOpt) {
+			t.Errorf("\nwant optional uses libs: %s\nhave optional uses libs: %s", wantUsesLibsOpt, haveUsesLibsOpt)
 		}
 	})
 }
 
 func TestCLCJson(t *testing.T) {
 	ctx := testContext()
+	optional := false
+	implicit := true
 	m := make(ClassLoaderContextMap)
-	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	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)
 	jsonCLC := toJsonClassLoaderContext(m)
 	restored := fromJsonClassLoaderContext(ctx, jsonCLC)
 	android.AssertIntEquals(t, "The size of the maps should be the same.", len(m), len(restored))
@@ -181,20 +190,26 @@
 // Test that unknown library paths cause a validation error.
 func testCLCUnknownPath(t *testing.T, whichPath string) {
 	ctx := testContext()
+	optional := false
+	implicit := true
 
 	m := make(ClassLoaderContextMap)
 	if whichPath == "build" {
-		m.AddContext(ctx, AnySdkVersion, "a", nil, nil, nil)
+		m.AddContext(ctx, AnySdkVersion, "a", optional, implicit, nil, nil, nil)
 	} else {
-		m.AddContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, nil)
+		m.AddContext(ctx, AnySdkVersion, "a", optional, implicit, buildPath(ctx, "a"), nil, nil)
 	}
 
 	// The library should be added to <uses-library> tags by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		haveUsesLibs := m.UsesLibs()
-		wantUsesLibs := []string{"a"}
-		if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
-			t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
+		haveUsesLibsReq, haveUsesLibsOpt := m.UsesLibs()
+		wantUsesLibsReq := []string{"a"}
+		wantUsesLibsOpt := []string{}
+		if !reflect.DeepEqual(wantUsesLibsReq, haveUsesLibsReq) {
+			t.Errorf("\nwant required uses libs: %s\nhave required uses libs: %s", wantUsesLibsReq, haveUsesLibsReq)
+		}
+		if !reflect.DeepEqual(wantUsesLibsOpt, haveUsesLibsOpt) {
+			t.Errorf("\nwant optional uses libs: %s\nhave optional uses libs: %s", wantUsesLibsOpt, haveUsesLibsOpt)
 		}
 	})
 
@@ -216,10 +231,12 @@
 // An attempt to add conditional nested subcontext should fail.
 func TestCLCNestedConditional(t *testing.T) {
 	ctx := testContext()
+	optional := false
+	implicit := true
 	m1 := make(ClassLoaderContextMap)
-	m1.AddContext(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m1.AddContext(ctx, 42, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 	m := make(ClassLoaderContextMap)
-	err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), m1)
+	err := m.addContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), m1)
 	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
@@ -227,11 +244,13 @@
 // they end up in the order that agrees with PackageManager.
 func TestCLCSdkVersionOrder(t *testing.T) {
 	ctx := testContext()
+	optional := false
+	implicit := true
 	m := make(ClassLoaderContextMap)
-	m.AddContext(ctx, 28, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, 29, "b", buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, 30, "c", buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContext(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), nil)
+	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)
 
 	valid, validationError := validateClassLoaderContext(m)
 
@@ -251,13 +270,13 @@
 
 	// Test that class loader context structure is correct.
 	t.Run("string", func(t *testing.T) {
-		wantStr := " --host-context-for-sdk 30 PCL[out/c.jar]" +
+		wantStr := " --host-context-for-sdk 30 PCL[out/soong/c.jar]" +
 			" --target-context-for-sdk 30 PCL[/system/c.jar]" +
-			" --host-context-for-sdk 29 PCL[out/b.jar]" +
+			" --host-context-for-sdk 29 PCL[out/soong/b.jar]" +
 			" --target-context-for-sdk 29 PCL[/system/b.jar]" +
-			" --host-context-for-sdk 28 PCL[out/a.jar]" +
+			" --host-context-for-sdk 28 PCL[out/soong/a.jar]" +
 			" --target-context-for-sdk 28 PCL[/system/a.jar]" +
-			" --host-context-for-sdk any PCL[out/d.jar]" +
+			" --host-context-for-sdk any PCL[out/soong/d.jar]" +
 			" --target-context-for-sdk any PCL[/system/d.jar]"
 		if wantStr != haveStr {
 			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
@@ -265,6 +284,111 @@
 	})
 }
 
+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.
+		before := m.Dump()
+
+		// Create a new CLCM that excludes some libraries.
+		c := m.ExcludeLibs(excluded_libs)
+
+		// Make sure that the original CLCM was not changed.
+		after := m.Dump()
+		android.AssertStringEquals(t, "input CLCM modified", before, after)
+
+		return c
+	}
+
+	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)
+
+		a := excludeLibs(t, m)
+
+		android.AssertStringEquals(t, "output CLCM ", `{
+  "28": [
+    {
+      "Name": "a",
+      "Optional": false,
+      "Implicit": true,
+      "Host": "out/soong/a.jar",
+      "Device": "/system/a.jar",
+      "Subcontexts": []
+    }
+  ]
+}`, a.Dump())
+	})
+
+	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)
+
+		a := excludeLibs(t, m, "a")
+
+		expected := `{
+  "28": [
+    {
+      "Name": "b",
+      "Optional": false,
+      "Implicit": true,
+      "Host": "out/soong/b.jar",
+      "Device": "/system/b.jar",
+      "Subcontexts": []
+    }
+  ]
+}`
+		android.AssertStringEquals(t, "output CLCM ", expected, a.Dump())
+	})
+
+	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)
+
+		a := excludeLibs(t, m, "a", "b")
+
+		android.AssertStringEquals(t, "output CLCM ", `{}`, a.Dump())
+	})
+
+	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)
+
+		m := make(ClassLoaderContextMap)
+		m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), s)
+
+		a := excludeLibs(t, m, "b")
+
+		android.AssertStringEquals(t, "output CLCM ", `{
+  "28": [
+    {
+      "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": []
+        }
+      ]
+    }
+  ]
+}`, a.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 0bcec17..153b025 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -44,15 +44,17 @@
 	DisableGenerateProfile bool   // don't generate profiles
 	ProfileDir             string // directory to find profiles in
 
-	BootJars          android.ConfiguredJarList // modules for jars that form the boot class path
-	UpdatableBootJars android.ConfiguredJarList // jars within apex that form the boot class path
+	BootJars     android.ConfiguredJarList // modules for jars that form the boot class path
+	ApexBootJars android.ConfiguredJarList // jars within apex that form the boot class path
 
 	ArtApexJars android.ConfiguredJarList // modules for jars that are in the ART APEX
 
-	SystemServerJars          android.ConfiguredJarList // jars that form the system server
-	SystemServerApps          []string                  // apps that are loaded into system server
-	UpdatableSystemServerJars android.ConfiguredJarList // jars within apex that are loaded into system server
-	SpeedApps                 []string                  // apps that should be speed optimized
+	SystemServerJars               android.ConfiguredJarList // system_server classpath jars on the platform
+	SystemServerApps               []string                  // apps that are loaded into system server
+	ApexSystemServerJars           android.ConfiguredJarList // system_server classpath jars delivered via apex
+	StandaloneSystemServerJars     android.ConfiguredJarList // jars on the platform that system_server loads dynamically using separate classloaders
+	ApexStandaloneSystemServerJars android.ConfiguredJarList // jars delivered via apex that system_server loads dynamically using separate classloaders
+	SpeedApps                      []string                  // apps that should be speed optimized
 
 	BrokenSuboptimalOrderOfSystemServerJars bool // if true, sub-optimal order does not cause a build error
 
@@ -98,6 +100,48 @@
 	RelaxUsesLibraryCheck bool
 }
 
+var allPlatformSystemServerJarsKey = android.NewOnceKey("allPlatformSystemServerJars")
+
+// Returns all jars on the platform that system_server loads, including those on classpath and those
+// loaded dynamically.
+func (g *GlobalConfig) AllPlatformSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allPlatformSystemServerJarsKey, func() interface{} {
+		res := g.SystemServerJars.AppendList(&g.StandaloneSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allApexSystemServerJarsKey = android.NewOnceKey("allApexSystemServerJars")
+
+// Returns all jars delivered via apex that system_server loads, including those on classpath and
+// those loaded dynamically.
+func (g *GlobalConfig) AllApexSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allApexSystemServerJarsKey, func() interface{} {
+		res := g.ApexSystemServerJars.AppendList(&g.ApexStandaloneSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allSystemServerClasspathJarsKey = android.NewOnceKey("allSystemServerClasspathJars")
+
+// Returns all system_server classpath jars.
+func (g *GlobalConfig) AllSystemServerClasspathJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allSystemServerClasspathJarsKey, func() interface{} {
+		res := g.SystemServerJars.AppendList(&g.ApexSystemServerJars)
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
+var allSystemServerJarsKey = android.NewOnceKey("allSystemServerJars")
+
+// Returns all jars that system_server loads.
+func (g *GlobalConfig) AllSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+	return ctx.Config().Once(allSystemServerJarsKey, func() interface{} {
+		res := g.AllPlatformSystemServerJars(ctx).AppendList(g.AllApexSystemServerJars(ctx))
+		return &res
+	}).(*android.ConfiguredJarList)
+}
+
 // GlobalSoongConfig contains the global config that is generated from Soong,
 // stored in dexpreopt_soong.config.
 type GlobalSoongConfig struct {
@@ -159,7 +203,7 @@
 }
 
 func constructPath(ctx android.PathContext, path string) android.Path {
-	buildDirPrefix := ctx.Config().BuildDir() + "/"
+	buildDirPrefix := ctx.Config().SoongOutDir() + "/"
 	if path == "" {
 		return nil
 	} else if strings.HasPrefix(path, buildDirPrefix) {
@@ -364,6 +408,7 @@
 
 type dex2oatDependencyTag struct {
 	blueprint.BaseDependencyTag
+	android.LicenseAnnotationToolchainDependencyTag
 }
 
 func (d dex2oatDependencyTag) ExcludeFromVisibilityEnforcement() {
@@ -531,7 +576,7 @@
 	return config, nil
 }
 
-// checkBootJarsConfigConsistency checks the consistency of BootJars and UpdatableBootJars fields in
+// checkBootJarsConfigConsistency checks the consistency of BootJars and ApexBootJars fields in
 // DexpreoptGlobalConfig and Config.productVariables.
 func checkBootJarsConfigConsistency(ctx android.SingletonContext, dexpreoptConfig *GlobalConfig, config android.Config) {
 	compareBootJars := func(property string, dexpreoptJars, variableJars android.ConfiguredJarList) {
@@ -545,8 +590,8 @@
 		}
 	}
 
-	compareBootJars("BootJars", dexpreoptConfig.BootJars, config.NonUpdatableBootJars())
-	compareBootJars("UpdatableBootJars", dexpreoptConfig.UpdatableBootJars, config.UpdatableBootJars())
+	compareBootJars("BootJars", dexpreoptConfig.BootJars, config.NonApexBootJars())
+	compareBootJars("ApexBootJars", dexpreoptConfig.ApexBootJars, config.ApexBootJars())
 }
 
 func (s *globalSoongConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
@@ -614,11 +659,13 @@
 		DisableGenerateProfile:             false,
 		ProfileDir:                         "",
 		BootJars:                           android.EmptyConfiguredJarList(),
-		UpdatableBootJars:                  android.EmptyConfiguredJarList(),
+		ApexBootJars:                       android.EmptyConfiguredJarList(),
 		ArtApexJars:                        android.EmptyConfiguredJarList(),
 		SystemServerJars:                   android.EmptyConfiguredJarList(),
 		SystemServerApps:                   nil,
-		UpdatableSystemServerJars:          android.EmptyConfiguredJarList(),
+		ApexSystemServerJars:               android.EmptyConfiguredJarList(),
+		StandaloneSystemServerJars:         android.EmptyConfiguredJarList(),
+		ApexStandaloneSystemServerJars:     android.EmptyConfiguredJarList(),
 		SpeedApps:                          nil,
 		PreoptFlags:                        nil,
 		DefaultCompilerFilter:              "",
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index da015a3..de139c4 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -110,17 +110,12 @@
 		return true
 	}
 
-	// Don't preopt system server jars that are updatable.
-	if global.UpdatableSystemServerJars.ContainsJar(module.Name) {
-		return true
-	}
-
 	// If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip
 	// Also preopt system server jars since selinux prevents system server from loading anything from
 	// /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
 	// or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options.
 	if global.OnlyPreoptBootImageAndSystemServer && !global.BootJars.ContainsJar(module.Name) &&
-		!global.SystemServerJars.ContainsJar(module.Name) && !module.PreoptExtractedApk {
+		!global.AllSystemServerJars(ctx).ContainsJar(module.Name) && !module.PreoptExtractedApk {
 		return true
 	}
 
@@ -201,6 +196,25 @@
 	return profilePath
 }
 
+// Returns the dex location of a system server java library.
+func GetSystemServerDexLocation(ctx android.PathContext, global *GlobalConfig, lib string) string {
+	if apex := global.AllApexSystemServerJars(ctx).ApexOfJar(lib); apex != "" {
+		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apex, lib)
+	}
+	return fmt.Sprintf("/system/framework/%s.jar", lib)
+}
+
+// Returns the location to the odex file for the dex file at `path`.
+func ToOdexPath(path string, arch android.ArchType) string {
+	if strings.HasPrefix(path, "/apex/") {
+		return filepath.Join("/system/framework/oat", arch.String(),
+			strings.ReplaceAll(path[1:], "/", "@")+"@classes.odex")
+	}
+
+	return filepath.Join(filepath.Dir(path), "oat", arch.String(),
+		pathtools.ReplaceExtension(filepath.Base(path), "odex"))
+}
+
 func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
 	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
 	appImage bool, generateDM bool) {
@@ -215,16 +229,8 @@
 		base = "package.apk"
 	}
 
-	toOdexPath := func(path string) string {
-		return filepath.Join(
-			filepath.Dir(path),
-			"oat",
-			arch.String(),
-			pathtools.ReplaceExtension(filepath.Base(path), "odex"))
-	}
-
 	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
-	odexInstallPath := toOdexPath(module.DexLocation)
+	odexInstallPath := ToOdexPath(module.DexLocation, arch)
 	if odexOnSystemOther(module, global) {
 		odexInstallPath = filepath.Join(SystemOtherPartition, odexInstallPath)
 	}
@@ -234,33 +240,58 @@
 
 	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
 
-	systemServerJars := NonUpdatableSystemServerJars(ctx, global)
+	systemServerJars := global.AllSystemServerJars(ctx)
+	systemServerClasspathJars := global.AllSystemServerClasspathJars(ctx)
 
 	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 
-	if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
+	if jarIndex := systemServerJars.IndexOfJar(module.Name); jarIndex >= 0 {
 		// System server jars should be dexpreopted together: class loader context of each jar
 		// should include all preceding jars on the system server classpath.
 
 		var clcHost android.Paths
 		var clcTarget []string
-		for _, lib := range systemServerJars[:jarIndex] {
+		endIndex := systemServerClasspathJars.IndexOfJar(module.Name)
+		if endIndex < 0 {
+			// The jar is a standalone one. Use the full classpath as the class loader context.
+			endIndex = systemServerClasspathJars.Len()
+		}
+		for i := 0; i < endIndex; i++ {
+			lib := systemServerClasspathJars.Jar(i)
 			clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib))
-			clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar"))
+			clcTarget = append(clcTarget, GetSystemServerDexLocation(ctx, global, lib))
 		}
 
-		// Copy the system server jar to a predefined location where dex2oat will find it.
-		dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
-		rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
-		rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost)
+		if DexpreoptRunningInSoong {
+			// Copy the system server jar to a predefined location where dex2oat will find it.
+			dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
+			rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
+			rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost)
+		} else {
+			// For Make modules the copy rule is generated in the makefiles, not in dexpreopt.sh.
+			// This is necessary to expose the rule to Ninja, otherwise it has rules that depend on
+			// the jar (namely, dexpreopt commands for all subsequent system server jars that have
+			// this one in their class loader context), but no rule that creates it (because Ninja
+			// cannot see the rule in the generated dexpreopt.sh script).
+		}
 
-		checkSystemServerOrder(ctx, jarIndex)
+		clcHostString := "PCL[" + strings.Join(clcHost.Strings(), ":") + "]"
+		clcTargetString := "PCL[" + strings.Join(clcTarget, ":") + "]"
+
+		if systemServerClasspathJars.ContainsJar(module.Name) {
+			checkSystemServerOrder(ctx, jarIndex)
+		} else {
+			// Standalone jars are loaded by separate class loaders with SYSTEMSERVERCLASSPATH as the
+			// parent.
+			clcHostString = "PCL[];" + clcHostString
+			clcTargetString = "PCL[];" + clcTargetString
+		}
 
 		rule.Command().
-			Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clcHost.Strings(), ":") + "]").
+			Text(`class_loader_context_arg=--class-loader-context="` + clcHostString + `"`).
 			Implicits(clcHost).
-			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]")
+			Text(`stored_class_loader_context_arg=--stored-class-loader-context="` + clcTargetString + `"`)
 
 	} else {
 		// There are three categories of Java modules handled here:
@@ -362,7 +393,7 @@
 
 	if !android.PrefixInList(preoptFlags, "--compiler-filter=") {
 		var compilerFilter string
-		if global.SystemServerJars.ContainsJar(module.Name) {
+		if systemServerJars.ContainsJar(module.Name) {
 			// Jars of system server, use the product option if it is set, speed otherwise.
 			if global.SystemServerCompilerFilter != "" {
 				compilerFilter = global.SystemServerCompilerFilter
@@ -416,7 +447,7 @@
 
 	// PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
 	// PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
-	if global.SystemServerJars.ContainsJar(module.Name) {
+	if systemServerJars.ContainsJar(module.Name) {
 		if global.AlwaysSystemServerDebugInfo {
 			debugInfo = true
 		} else if global.NeverSystemServerDebugInfo {
@@ -430,11 +461,6 @@
 		}
 	}
 
-	// Never enable on eng.
-	if global.IsEng {
-		debugInfo = false
-	}
-
 	if debugInfo {
 		cmd.Flag("--generate-mini-debug-info")
 	} else {
@@ -523,16 +549,6 @@
 	}
 }
 
-var nonUpdatableSystemServerJarsKey = android.NewOnceKey("nonUpdatableSystemServerJars")
-
-// TODO: eliminate the superficial global config parameter by moving global config definition
-// from java subpackage to dexpreopt.
-func NonUpdatableSystemServerJars(ctx android.PathContext, global *GlobalConfig) []string {
-	return ctx.Config().Once(nonUpdatableSystemServerJarsKey, func() interface{} {
-		return android.RemoveListFromList(global.SystemServerJars.CopyOfJars(), global.UpdatableSystemServerJars.CopyOfJars())
-	}).([]string)
-}
-
 // A predefined location for the system server dex jars. This is needed in order to generate
 // class loader context for dex2oat, as the path to the jar in the Soong module may be unknown
 // at that time (Soong processes the jars in dependency order, which may be different from the
@@ -556,12 +572,12 @@
 	mctx, isModule := ctx.(android.ModuleContext)
 	if isModule {
 		config := GetGlobalConfig(ctx)
-		jars := NonUpdatableSystemServerJars(ctx, config)
+		jars := config.AllSystemServerClasspathJars(ctx)
 		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			depIndex := android.IndexList(dep.Name(), jars)
+			depIndex := jars.IndexOfJar(dep.Name())
 			if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars {
-				jar := jars[jarIndex]
-				dep := jars[depIndex]
+				jar := jars.Jar(jarIndex)
+				dep := jars.Jar(depIndex)
 				mctx.ModuleErrorf("non-optimal order of jars on the system server classpath:"+
 					" '%s' precedes its dependency '%s', so dexpreopt is unable to resolve any"+
 					" references from '%s' to '%s'.\n", jar, dep, jar, dep)
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index 7dbe74c..ba05d94 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -87,7 +87,9 @@
 		usage("--module configuration file is required")
 	}
 
-	ctx := &builderContext{android.NullConfig(*outDir)}
+	// NOTE: duplicating --out_dir here is incorrect (one should be the another
+	// plus "/soong" but doing so apparently breaks dexpreopt
+	ctx := &builderContext{android.NullConfig(*outDir, *outDir)}
 
 	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
 	if err != nil {
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 4ee61b6..07e4fad 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -33,17 +33,44 @@
 }
 
 func testModuleConfig(ctx android.PathContext, name, partition string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func testApexModuleConfig(ctx android.PathContext, name, apexName string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/apex/%s/javalib/%s.jar", apexName, name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func testPlatformSystemServerModuleConfig(ctx android.PathContext, name string) *ModuleConfig {
+	return createTestModuleConfig(
+		name,
+		fmt.Sprintf("/system/framework/%s.jar", name),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/dexpreopt/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/aligned/%s.jar", name, name)),
+		android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)))
+}
+
+func createTestModuleConfig(name, dexLocation string, buildPath, dexPath, enforceUsesLibrariesStatusFile android.OutputPath) *ModuleConfig {
 	return &ModuleConfig{
 		Name:                            name,
-		DexLocation:                     fmt.Sprintf("/%s/app/test/%s.apk", partition, name),
-		BuildPath:                       android.PathForOutput(ctx, fmt.Sprintf("%s/%s.apk", name, name)),
-		DexPath:                         android.PathForOutput(ctx, fmt.Sprintf("%s/dex/%s.jar", name, name)),
+		DexLocation:                     dexLocation,
+		BuildPath:                       buildPath,
+		DexPath:                         dexPath,
 		UncompressedDex:                 false,
 		HasApkLibraries:                 false,
 		PreoptFlags:                     nil,
 		ProfileClassListing:             android.OptionalPath{},
 		ProfileIsTextListing:            false,
-		EnforceUsesLibrariesStatusFile:  android.PathForOutput(ctx, fmt.Sprintf("%s/enforce_uses_libraries.status", name)),
+		EnforceUsesLibrariesStatusFile:  enforceUsesLibrariesStatusFile,
 		EnforceUsesLibraries:            false,
 		ClassLoaderContexts:             nil,
 		Archs:                           []android.ArchType{android.Arm},
@@ -140,6 +167,75 @@
 
 }
 
+func TestDexPreoptApexSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+
+	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
+func TestDexPreoptStandaloneSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testPlatformSystemServerModuleConfig(ctx, "service-A")
+
+	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"platform:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/service-A.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/service-A.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
+func TestDexPreoptApexStandaloneSystemServerJars(t *testing.T) {
+	config := android.TestConfig("out", nil, "", nil)
+	ctx := android.BuilderContextForTesting(config)
+	globalSoong := globalSoongConfigForTests()
+	global := GlobalConfigForTests(ctx)
+	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+
+	global.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(
+		[]string{"com.android.apex1:service-A"})
+
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wantInstalls := android.RuleBuilderInstalls{
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.odex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.odex"},
+		{android.PathForOutput(ctx, "service-A/dexpreopt/oat/arm/javalib.vdex"), "/system/framework/oat/arm/apex@com.android.apex1@javalib@service-A.jar@classes.vdex"},
+	}
+
+	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
+}
+
 func TestDexPreoptProfile(t *testing.T) {
 	config := android.TestConfig("out", nil, "", nil)
 	ctx := android.BuilderContextForTesting(config)
diff --git a/dexpreopt/testing.go b/dexpreopt/testing.go
index c0ba5ca..47ae494 100644
--- a/dexpreopt/testing.go
+++ b/dexpreopt/testing.go
@@ -85,12 +85,12 @@
 // Prepares a test fixture by enabling dexpreopt, registering the fake_tool_binary module type and
 // using that to define the `dex2oatd` module.
 var PrepareForTestByEnablingDexpreopt = android.GroupFixturePreparers(
-	FixtureModifyGlobalConfig(func(*GlobalConfig) {}),
+	FixtureModifyGlobalConfig(func(android.PathContext, *GlobalConfig) {}),
 )
 
 // FixtureModifyGlobalConfig enables dexpreopt (unless modified by the mutator) and modifies the
 // configuration.
-func FixtureModifyGlobalConfig(configModifier func(dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
+func FixtureModifyGlobalConfig(configModifier func(ctx android.PathContext, dexpreoptConfig *GlobalConfig)) android.FixturePreparer {
 	return android.FixtureModifyConfig(func(config android.Config) {
 		// Initialize the dexpreopt GlobalConfig to an empty structure. This has no effect if it has
 		// already been set.
@@ -100,34 +100,77 @@
 
 		// Retrieve the existing configuration and modify it.
 		dexpreoptConfig = GetGlobalConfig(pathCtx)
-		configModifier(dexpreoptConfig)
+		configModifier(pathCtx, dexpreoptConfig)
 	})
 }
 
 // FixtureSetArtBootJars enables dexpreopt and sets the ArtApexJars property.
 func FixtureSetArtBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.ArtApexJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
 
 // FixtureSetBootJars enables dexpreopt and sets the BootJars property.
 func FixtureSetBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
 
-// FixtureSetUpdatableBootJars sets the UpdatableBootJars property in the global config.
-func FixtureSetUpdatableBootJars(bootJars ...string) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
-		dexpreoptConfig.UpdatableBootJars = android.CreateTestConfiguredJarList(bootJars)
+// FixtureSetApexBootJars sets the ApexBootJars property in the global config.
+func FixtureSetApexBootJars(bootJars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.ApexBootJars = android.CreateTestConfiguredJarList(bootJars)
+	})
+}
+
+// FixtureSetStandaloneSystemServerJars sets the StandaloneSystemServerJars property.
+func FixtureSetStandaloneSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(jars)
+	})
+}
+
+// FixtureSetSystemServerJars sets the SystemServerJars property.
+func FixtureSetSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.SystemServerJars = android.CreateTestConfiguredJarList(jars)
+	})
+}
+
+// FixtureSetApexSystemServerJars sets the ApexSystemServerJars property in the global config.
+func FixtureSetApexSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.ApexSystemServerJars = android.CreateTestConfiguredJarList(jars)
+	})
+}
+
+// FixtureSetApexStandaloneSystemServerJars sets the ApexStandaloneSystemServerJars property in the
+// global config.
+func FixtureSetApexStandaloneSystemServerJars(jars ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(jars)
 	})
 }
 
 // FixtureSetPreoptWithUpdatableBcp sets the PreoptWithUpdatableBcp property in the global config.
 func FixtureSetPreoptWithUpdatableBcp(value bool) android.FixturePreparer {
-	return FixtureModifyGlobalConfig(func(dexpreoptConfig *GlobalConfig) {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
 		dexpreoptConfig.PreoptWithUpdatableBcp = value
 	})
 }
+
+// FixtureSetBootImageProfiles sets the BootImageProfiles property in the global config.
+func FixtureSetBootImageProfiles(profiles ...string) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(ctx android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.BootImageProfiles = android.PathsForSource(ctx, profiles)
+	})
+}
+
+// FixtureDisableGenerateProfile sets the DisableGenerateProfile property in the global config.
+func FixtureDisableGenerateProfile(disable bool) android.FixturePreparer {
+	return FixtureModifyGlobalConfig(func(_ android.PathContext, dexpreoptConfig *GlobalConfig) {
+		dexpreoptConfig.DisableGenerateProfile = disable
+	})
+}
diff --git a/docs/OWNERS b/docs/OWNERS
index d143317..2d71271 100644
--- a/docs/OWNERS
+++ b/docs/OWNERS
@@ -1 +1,3 @@
 per-file map_files.md = danalbert@google.com, enh@google.com, jiyong@google.com
+per-file native_code_coverage.md = pirama@google.com, srhines@google.com, allenhair@google.com
+per-file tidy.md = chh@google.com, srhines@google.com
diff --git a/docs/map_files.md b/docs/map_files.md
index 192530f..1388059 100644
--- a/docs/map_files.md
+++ b/docs/map_files.md
@@ -70,9 +70,11 @@
 
 ### apex
 
-Indicates that the version or symbol is to be exposed in the APEX stubs rather
-than the NDK. May be used in combination with `llndk` if the symbol is exposed
-to both APEX and the LL-NDK.
+Indicates that the version or symbol is to be exposed by an APEX rather than the
+NDK. For APIs exposed by the platform *for* APEX, use `systemapi`.
+
+May be used in combination with `llndk` if the symbol is exposed to both APEX
+and the LL-NDK.
 
 ### future
 
@@ -144,6 +146,12 @@
 preferable to keep such APIs in an entirely separate library to protect them
 from access via `dlsym`, but this is not always possible.
 
+### systemapi
+
+This is a synonym of the `apex` tag. It should be used to clarify that the API
+is an API exposed by the system for an APEX, whereas `apex` should be used for
+APIs exposed by an APEX to the platform or another APEX.
+
 ### var
 
 Used to define a public global variable. By default all symbols are exposed as
diff --git a/docs/native_code_coverage.md b/docs/native_code_coverage.md
new file mode 100644
index 0000000..fabbfa0
--- /dev/null
+++ b/docs/native_code_coverage.md
@@ -0,0 +1,241 @@
+## Native Code Coverage for Android
+
+## Scope
+
+These instructions are for Android developers to collect and inspect code
+coverage for C++ and Rust code on the Android platform.
+
+## Building with Native Code Coverage Instrumentation
+
+Identify the paths where native code-coverage instrumentation should be enabled
+and set up the following environment variables.
+
+```
+    export CLANG_COVERAGE=true
+    export NATIVE_COVERAGE_PATHS="<paths-to-instrument-for-coverage>"
+```
+
+`NATIVE_COVERAGE_PATHS` should be a list of paths. Any Android.bp module defined
+under these paths is instrumented for code-coverage. E.g:
+
+```
+export NATIVE_COVERAGE_PATHS="external/libcxx system/core/adb"
+```
+
+### Additional Notes
+
+-   Native Code coverage is not supported for host modules or `Android.mk`
+    modules.
+-   `NATIVE_COVERAGE_PATHS="*"` enables coverage instrumentation for all paths.
+-   Set `native_coverage: false` blueprint property to always disable code
+    coverage instrumentation for a module. This is useful if this module has
+    issues when building or running with coverage.
+-   `NATIVE_COVERAGE_EXCLUDE_PATHS` can be set to exclude subdirs under
+    `NATIVE_COVERAGE_PATHS` from coverage instrumentation. E.g.
+    `NATIVE_COVERAGE_PATHS=frameworks/native
+    NATIVE_COVERAGE_PATHS=frameworks/native/vulkan` will instrument all native
+    code under `frameworks/native` except`frameworks/native/vulkan`.
+
+## Running Tests
+
+### Collecting Profiles
+
+When an instrumented program is run, the profiles are stored to the path and
+name specified in the `LLVM_PROFILE_FILE` environment variable. On Android
+coverage builds it is set to `/data/misc/trace/clang-%p-%20m.profraw`.
+
+*   `%`p is replaced by the pid of the process
+*   `%m` by the hash of the library/binary
+*   The `20` in`%20m` creates a pool of 20 profraw files and "online" profile
+    merging is used to merge coverage to profiles onto this pool.
+
+Reference:`LLVM_PROFILE_FILE` can include additional specifiers as described
+[here](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program).
+
+For this and following steps, use the `acov-llvm.py` script:
+`$ANDROID_BUILD_TOP/development/scripts/acov-llvm.py`.
+
+There may be profiles in `/data/misc/trace` collected before the test is run.
+Clear this data before running the test.
+
+```
+    # Clear any coverage that's already written to /data/misc/trace
+    # and reset coverage for all daemons.
+    <host>$ acov-llvm.py clean-device
+
+    # Run the test.  The exact command depends on the nature of the test.
+    <device>$ /data/local/tmp/$MY_TEST
+```
+
+For tests that exercise a daemon/service running in another process, write out
+the coverage for those processes as well.
+
+```
+    # Flush coverage of all daemons/processes running on the device.
+    <host>$ acov-llvm.py flush
+
+    # Flush coverage for a particular daemon, say adbd.
+    <host>$ acov-llvm.py flush adbd
+```
+
+## Viewing Coverage Data (acov-llvm.py)
+
+To post-process and view coverage information we use the `acov-llvm.py report`
+command. It invokes two LLVM utilities `llvm-profdata` and `llvm-cov`. An
+advanced user can manually invoke these utilities for fine-grained control. This
+is discussed [below](#viewing-coverage-data-manual).
+
+To generate coverage report need the following parameters. These are dependent
+on the test/module:
+
+1.  One or more binaries and shared libraries from which coverage was collected.
+    E.g.:
+
+    1.  ART mainline module contains a few libraries such as `libart.so`,
+        `libart-compiler.so`.
+    2.  Bionic tests exercise code in `libc.so` and `libm.so`.
+
+    We need the *unstripped* copies of these binaries. Source information
+    included in the debuginfo is used to process the coverage data.
+
+2.  One or more source directories under `$ANDROID_BUILD_TOP` for which coverage
+    needs to be reported.
+
+Invoke the report subcommand of acov-llvm.py to produce a html coverage summary:
+
+```
+    $ acov-llvm.py report \
+        -s <one-or-more-source-paths-in-$ANDROID_BUILD_TOP \
+        -b <one-or-more-(unstripped)-binaries-in-$OUT>
+```
+
+E.g.:
+
+```
+    $ acov-llvm.py report \
+        -s bionic \
+        -b \
+        $OUT/symbols/apex/com.android.runtime/lib/bionic/libc.so \
+        $OUT/symbols/apex/com.android.runtime/lib/bionic/libm.so
+```
+
+The script will produce a report in a temporary directory under
+`$ANDROID_BUILD_TOP`. It'll produce a log as below:
+
+```
+    generating coverage report in covreport-xxxxxx
+```
+
+A html report would be generated under `covreport-xxxxxx/html`.
+
+## Viewing Coverage Data (manual)
+
+`acov-llvm.py report` does a few operations under the hood which we can also
+manually invoke for flexibility.
+
+### Post-processing Coverage Files
+
+Fetch coverage files from the device and post-process them to a `.profdata` file
+as follows:
+
+```
+    # Fetch the coverage data from the device.
+    <host>$ cd coverage_data
+    <host>$ adb pull /data/misc/trace/ $TRACE_DIR_HOST
+
+    # Convert from .profraw format to the .profdata format.
+    <host>$ llvm-profdata merge --output=$MY_TEST.profdata \
+    $TRACE_DIR_HOST/clang-*.profraw
+```
+
+For added specificity, restrict the above command to just the <PID>s of the
+daemon or test processes of interest.
+
+```
+    <host>$ llvm-profdata merge --output=$MY_TEST.profdata \
+    $MY_TEST.profraw \
+    trace/clang-<pid1>.profraw trace/clang-<pid2>.profraw ...
+```
+
+### Generating Coverage report
+
+Documentation on Clang source-instrumentation-based coverage is available
+[here](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#creating-coverage-reports).
+The `llvm-cov` utility is used to show coverage from a `.profdata` file. The
+documentation for commonly used `llvm-cov` command-line arguments is available
+[here](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report). (Try
+`llvm-cov show --help` for a complete list).
+
+#### `show` subcommand
+
+The `show` command displays the function and line coverage for each source file
+in the binary.
+
+```
+    <host>$ llvm-cov show \
+        --show-region-summary=false
+        --format=html --output-dir=coverage-html \
+        --instr-profile=$MY_TEST.profdata \
+        $MY_BIN \
+```
+
+*   In the above command, `$MY_BIN` should be the unstripped binary (i.e. with
+    debuginfo) since `llvm-cov` reads some debuginfo to process the coverage
+    data.
+
+    E.g.:
+
+    ~~~
+    ```
+    <host>$ llvm-cov report \
+        --instr-profile=adbd.profdata \
+        $LOCATION_OF_UNSTRIPPED_ADBD/adbd \
+        --show-region-summary=false
+    ```
+    ~~~
+
+*   The `-ignore-filename-regex=<regex>` option can be used to ignore files that
+    are not of interest. E.g: `-ignore-filename-regex="external/*"`
+
+*   Use the `--object=<BIN>` argument to specify additional binaries and shared
+    libraries whose coverage is included in this profdata. See the earlier
+    [section](#viewing-coverage-data-acov-llvm-py) for examples where more than
+    one binary may need to be used.
+
+    E.g., the following command is used for `bionic-unit-tests`, which tests
+    both `libc.so` and `libm.so`:
+
+    ~~~
+    ```
+    <host>$ llvm-cov report \
+        --instr-profile=bionic.profdata \
+        $OUT/.../libc.so \
+        --object=$OUT/.../libm.so
+    ```
+    ~~~
+
+*   `llvm-cov` also takes positional SOURCES argument to consider/display only
+    particular paths of interest. E.g:
+
+    ~~~
+    ```
+    <host>$ llvm-cov report \
+        --instr-profile=adbd.profdata \
+        $LOCATION_OF_ADBD/adbd \
+        --show-region-summary=false \
+        /proc/self/cwd/system/core/adb
+    ```
+    ~~~
+
+Note that the paths for the sources need to be prepended with
+'`/proc/self/cwd/`'. This is because Android C/C++ compilations run with
+`PWD=/proc/self/cwd` and consequently the source names are recorded with that
+prefix. Alternatively, the
+[`--path-equivalence`](https://llvm.org/docs/CommandGuide/llvm-cov.html#cmdoption-llvm-cov-show-path-equivalence)
+option to `llvm-cov` can be used.
+
+#### `report` subcommand
+
+The [`report`](https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report)
+subcommand summarizes the percentage of covered lines to the console. It takes
+options similar to the `show` subcommand.
diff --git a/docs/rbe.json b/docs/rbe.json
new file mode 100644
index 0000000..f6ff107
--- /dev/null
+++ b/docs/rbe.json
@@ -0,0 +1,24 @@
+{
+    "env": {
+        "USE_RBE": "1",
+
+        "RBE_R8_EXEC_STRATEGY": "remote_local_fallback",
+        "RBE_CXX_EXEC_STRATEGY": "remote_local_fallback",
+        "RBE_D8_EXEC_STRATEGY": "remote_local_fallback",
+        "RBE_JAVAC_EXEC_STRATEGY": "remote_local_fallback",
+        "RBE_JAVAC": "1",
+        "RBE_R8": "1",
+        "RBE_D8": "1",
+
+        "RBE_instance": "[replace with your RBE instance]",
+        "RBE_service": "[replace with your RBE service endpoint]",
+
+        "RBE_DIR": "prebuilts/remoteexecution-client/live",
+
+        "RBE_use_application_default_credentials": "true",
+
+        "RBE_log_dir": "/tmp",
+        "RBE_output_dir": "/tmp",
+        "RBE_proxy_log_dir": "/tmp"
+    }
+}
diff --git a/docs/rbe.md b/docs/rbe.md
new file mode 100644
index 0000000..cfe86d7
--- /dev/null
+++ b/docs/rbe.md
@@ -0,0 +1,70 @@
+# Build Android Platform on Remote Build Execution
+
+Soong is integrated with Google's Remote Build Execution(RBE) service, which
+implements the
+[Remote Executaion API](https://github.com/bazelbuild/remote-apis).
+
+With RBE enabled, it can speed up the Android Platform builds by distributing
+build actions through a worker pool sharing a central cache of build results.
+
+## Configuration
+
+To enable RBE, you need to set several environment variables before triggering
+the build. You can set them through a
+[environment variables config file](https://android.googlesource.com/platform/build/soong/+/master/README.md#environment-variables-config-file).
+As an example, [build/soong/docs/rbe.json](rbe.json) is a config that enables
+RBE in the build. Once the config file is created, you need to let Soong load
+the config file by specifying `ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR` environment
+variable and `ANDROID_BUILD_ENVIRONMENT_CONFIG` environment variable. The
+following command starts Soong with [build/soong/docs/rbe.json](rbe.json)
+loaded:
+
+```shell
+ANDROID_BUILD_ENVIRONMENT_CONFIG=rbe \
+ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR=build/soong/doc \
+  build/soong/soong_ui.bash
+```
+
+### Configuration Explanation
+
+Below a brief explanation of each field in
+[build/soong/docs/rbe.json](rbe.json):
+
+##### USE\_RBE:
+If set to 1, enable RBE for the build.
+
+##### RBE\_CXX\_EXEC\_STRATEGY / RBE\_JAVAC\_EXEC\_STRATEGY / RBE\_R8\_EXEC\_STRATEGY / RBE\_D8\_EXEC\_STRATEGY:
+
+Sets strategies for C++/javac/r8/d8 action types. Available options are
+(**Note**: all options will update the remote cache if the right permissions to
+update cache are given to the user.):
+
+*   **local**: Only execute locally.
+*   **remote**: Only execute remotely.
+*   **remote_local_fallback**: Try executing remotely and fall back to local
+    execution if failed.
+*   **racing**: Race remote execution and local execution and use the earlier
+    result.
+
+##### RBE\_JAVAC / RBE\_R8 / RBE\_D8
+
+If set to 1, enable javac/r8/d8 support. C++ compilation is enabled by default.
+
+##### RBE\_service / RBE\_instance
+
+The remote execution service endpoint and instance ID to target when calling
+remote execution via gRPC to execute actions.
+
+##### RBE\_DIR
+
+Where to find remote client binaries (rewrapper, reproxy)
+
+##### RBE\_use\_application\_default\_credentials
+
+reclient uses
+[application default credentials](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login)
+for autentication, as generated by `gcloud auth application-default login`
+
+##### RBE\_log\_dir/RBE\_proxy\_log\_dir/RBE\_output\_dir
+
+Logs generated by rewrapper and reproxy will go here.
diff --git a/docs/tidy.md b/docs/tidy.md
new file mode 100644
index 0000000..890c3a0
--- /dev/null
+++ b/docs/tidy.md
@@ -0,0 +1,254 @@
+# Clang-Tidy Rules and Checks
+
+Android C/C++ source files can be checked by clang-tidy for issues like
+coding style, error-prone/performance patterns, and static flow analysis.
+See the official
+[clang-tidy document](https://clang.llvm.org/extra/clang-tidy)
+and list of
+[clang-tidy checks](https://clang.llvm.org/extra/clang-tidy/checks/list.html).
+
+## Global defaults for Android builds
+
+The simplest way to enable clang-tidy checks is
+to set environment variable `WITH_TIDY`.
+```
+$ WITH_TIDY=1 make
+```
+
+This will turn on the global default to run clang-tidy for every required
+C/C++ source file compilation. The global default clang-tidy checks
+do not include time-consuming static analyzer checks. To enable those
+checks, set the `CLANG_ANALYZER_CHECKS` variable.
+```
+$ WITH_TIDY=1 CLANG_ANALYZER_CHECKS=1 make
+```
+
+The default global clang-tidy checks and flags are defined in
+[build/soong/cc/config/tidy.go](https://android.googlesource.com/platform/build/soong/+/refs/heads/master/cc/config/tidy.go).
+
+
+## Module clang-tidy properties
+
+The global default can be overwritten by module properties in Android.bp.
+
+### `tidy` and `tidy_checks`
+
+For example, in
+[system/bpf/Android.bp](https://android.googlesource.com/platform/system/bpf/+/refs/heads/master/Android.bp),
+clang-tidy is enabled explicitly and with a different check list:
+```
+cc_defaults {
+    name: "bpf_defaults",
+    // snipped
+    tidy: true,
+    tidy_checks: [
+        "android-*",
+        "cert-*",
+        "-cert-err34-c",
+        "clang-analyzer-security*",
+        // Disabling due to many unavoidable warnings from POSIX API usage.
+        "-google-runtime-int",
+    ],
+}
+```
+That means in normal builds, even without `WITH_TIDY=1`,
+the modules that use `bpf_defaults` will run clang-tidy
+over C/C++ source files with the given `tidy_checks`.
+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.
+
+Some modules might want to disable clang-tidy even when
+environment variable `WITH_TIDY=1` is set.
+Examples can be found in
+[system/netd/tests/Android.bp](https://android.googlesource.com/platform/system/netd/+/refs/heads/master/tests/Android.bp)
+```
+cc_test {
+    name: "netd_integration_test",
+    // snipped
+    defaults: ["netd_defaults"],
+    tidy: false,  // cuts test build time by almost 1 minute
+```
+and in
+[bionic/tests/Android.bp](https://android.googlesource.com/platform/bionic/+/refs/heads/master/tests/Android.bp).
+```
+cc_test_library {
+    name: "fortify_disabled_for_tidy",
+    // snipped
+    srcs: ["clang_fortify_tests.cpp"],
+    tidy: false,
+}
+```
+
+### `tidy_checks_as_errors`
+
+The global tidy checks are enabled as warnings.
+If a C/C++ module wants to be free of certain clang-tidy warnings,
+it can chose those checks to be treated as errors.
+For example
+[system/core/libsysutils/Android.bp](https://android.googlesource.com/platform/system/core/+/refs/heads/master/libsysutils/Android.bp)
+has enabled clang-tidy explicitly, selected its own tidy checks,
+and set three groups of tidy checks as errors:
+```
+cc_library {
+    name: "libsysutils",
+    // snipped
+    tidy: true,
+    tidy_checks: [
+        "-*",
+        "cert-*",
+        "clang-analyzer-security*",
+        "android-*",
+    ],
+    tidy_checks_as_errors: [
+        "cert-*",
+        "clang-analyzer-security*",
+        "android-*",
+    ],
+    // snipped
+}
+```
+
+### `tidy_flags` and `tidy_disabled_srcs`
+
+Extra clang-tidy flags can be passed with the `tidy_flags` property.
+
+Some Android modules use the `tidy_flags` to pass "-warnings-as-errors="
+to clang-tidy. This usage should now be replaced with the
+`tidy_checks_as_errors` property.
+
+Some other tidy flags examples are `-format-style=` and `-header-filter=`
+For example, in
+[art/odrefresh/Android.bp](https://android.googlesource.com/platform/art/+/refs/heads/master/odrefresh/Android.bp),
+we found
+```
+cc_defaults {
+    name: "odrefresh-defaults",
+    srcs: [
+        "odrefresh.cc",
+        "odr_common.cc",
+        "odr_compilation_log.cc",
+        "odr_fs_utils.cc",
+        "odr_metrics.cc",
+        "odr_metrics_record.cc",
+    ],
+    // snipped
+    generated_sources: [
+        "apex-info-list-tinyxml",
+        "art-apex-cache-info",
+        "art-odrefresh-operator-srcs",
+    ],
+    // snipped
+    tidy: true,
+    tidy_disabled_srcs: [":art-apex-cache-info"],
+    tidy_flags: [
+        "-format-style=file",
+        "-header-filter=(art/odrefresh/|system/apex/)",
+    ],
+}
+```
+That means all modules with the `odrefresh-defaults` will
+have clang-tidy enabled, but not for generated source
+files in `art-apex-cache-info`.
+The clang-tidy is called with extra flags to specify the
+format-style and header-filter.
+
+Note that the globally set default for header-filter is to
+include only the module directory. So, the default clang-tidy
+warnings for `art/odrefresh` modules will include source files
+under that directory. Now `odrefresh-defaults` is interested
+in seeing warnings from both `art/odrefresh/` and `system/apex/`
+and it redefines `-header-filter` in its `tidy_flags`.
+
+
+## Phony tidy-* targets
+
+### The tidy-*directory* targets
+
+Setting `WITH_TIDY=1` is easy to enable clang-tidy globally for any build.
+However, it adds extra compilation time.
+
+For developers focusing on just one directory, they only want to compile
+their files with clang-tidy and wish to build other Android components as
+fast as possible. Changing the `WITH_TIDY=1` variable setting is also expensive
+since the build.ninja file will be regenerated due to any such variable change.
+
+To manually select only some directories or modules to compile with clang-tidy,
+do not set the `WITH_TIDY=1` variable, but use the special `tidy-<directory>`
+phony target. For example, a person working on `system/libbase` can build
+Android quickly with
+```
+unset WITH_TIDY # Optional, not if you haven't set WITH_TIDY
+make droid tidy-system-libbase
+```
+
+For any directory `d1/d2/d3`, a phony target tidy-d1-d2-d3 is generated
+if there is any C/C++ source file under `d1/d2/d3`.
+
+Note that with `make droid tidy-system-libbase`, some C/C++ files
+that are not needed by the `droid` target will be passed to clang-tidy
+if they are under `system/libbase`. This is like a `checkbuild`
+under `system/libbase` to include all modules, but only C/C++
+files of those modules are compiled with clang-tidy.
+
+### The tidy-soong target
+
+A special `tidy-soong` target is defined to include all C/C++
+source files in *all* directories. This phony target is sometimes
+used to test if all source files compile with a new clang-tidy release.
+
+### The tidy-*_subset targets
+
+A *subset* of each tidy-* phony target is defined to reduce test time.
+Since any Android module, a C/C++ library or binary, can be built
+for many different *variants*, one C/C++ source file is usually
+compiled multiple times with different compilation flags.
+Many of such *variant* flags have little or no effect on clang-tidy
+checks. To reduce clang-tidy check time, a *subset* target like
+`tidy-soong_subset` or `tidy-system-libbase_subset` is generated
+to include only a subset, the first variant, of each module in
+the directory.
+
+Hence, for C/C++ source code quality, instead of a long
+"make checkbuild", we can use "make tidy-soong_subset".
+
+
+## Limit clang-tidy runtime
+
+Some Android modules have large files that take a long time to compile
+with clang-tidy, with or without the clang-analyzer checks.
+To limit clang-tidy time, an environment variable can be set as
+```base
+WITH_TIDY=1 TIDY_TIMEOUT=90 make
+```
+This 90-second limit is actually the default time limit
+in several Android continuous builds where `WITH_TIDY=1` and
+`CLANG_ANALYZER_CHECKS=1` are set.
+
+Similar to `tidy_disabled_srcs` a `tidy_timeout_srcs` list
+can be used to include all source files that took too much time to compile
+with clang-tidy. Files listed in `tidy_timeout_srcs` will not
+be compiled by clang-tidy when `TIDY_TIMEOUT` is defined.
+This can save global build time, when it is necessary to set some
+time limit globally to finish in an acceptable time.
+For developers who want to find all clang-tidy warnings and
+are willing to spend more time on all files in a project,
+they should not define `TIDY_TIMEOUT` and build only the wanted project directories.
+
+## Capabilities for Android.bp and Android.mk
+
+Some of the previously mentioned features are defined only
+for modules in Android.bp files, not for Android.mk modules yet.
+
+* The global `WITH_TIDY=1` variable will enable clang-tidy for all C/C++
+  modules in Android.bp or Android.mk files.
+
+* The global `TIDY_TIMEOUT` variable is recognized by Android prebuilt
+  clang-tidy, so it should work for any clang-tidy invocation.
+
+* The clang-tidy module level properties are defined for Android.bp modules.
+  For Android.mk modules, old `LOCAL_TIDY`, `LOCAL_TIDY_CHECKS`,
+  `LOCAL_TIDY_FLAGS` work similarly, but it would be better to convert
+  those modules to use Android.bp files.
+
+* The `tidy-*` phony targets are only generated for Android.bp modules.
diff --git a/etc/Android.bp b/etc/Android.bp
index cab7389..c670236 100644
--- a/etc/Android.bp
+++ b/etc/Android.bp
@@ -9,12 +9,15 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-snapshot",
     ],
     srcs: [
         "prebuilt_etc.go",
+        "snapshot_etc.go",
     ],
     testSrcs: [
         "prebuilt_etc_test.go",
+        "snapshot_etc_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 4dd383d..719771f 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -28,12 +28,16 @@
 // various `prebuilt_*` mutators.
 
 import (
+	"encoding/json"
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
+	"android/soong/snapshot"
 )
 
 var pctx = android.NewPackageContext("android/soong/etc")
@@ -43,12 +47,14 @@
 func init() {
 	pctx.Import("android/soong/android")
 	RegisterPrebuiltEtcBuildComponents(android.InitRegistrationContext)
+	snapshot.RegisterSnapshotAction(generatePrebuiltSnapshot)
 }
 
 func RegisterPrebuiltEtcBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("prebuilt_etc", PrebuiltEtcFactory)
 	ctx.RegisterModuleType("prebuilt_etc_host", PrebuiltEtcHostFactory)
 	ctx.RegisterModuleType("prebuilt_root", PrebuiltRootFactory)
+	ctx.RegisterModuleType("prebuilt_root_host", PrebuiltRootHostFactory)
 	ctx.RegisterModuleType("prebuilt_usr_share", PrebuiltUserShareFactory)
 	ctx.RegisterModuleType("prebuilt_usr_share_host", PrebuiltUserShareHostFactory)
 	ctx.RegisterModuleType("prebuilt_font", PrebuiltFontFactory)
@@ -57,6 +63,7 @@
 	ctx.RegisterModuleType("prebuilt_rfsa", PrebuiltRFSAFactory)
 
 	ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
+
 }
 
 var PrepareForTestWithPrebuiltEtc = android.FixtureRegisterWithContext(RegisterPrebuiltEtcBuildComponents)
@@ -127,6 +134,10 @@
 type PrebuiltEtc struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.BazelModuleBase
+
+	snapshot.VendorSnapshotModuleInterface
+	snapshot.RecoverySnapshotModuleInterface
 
 	properties       prebuiltEtcProperties
 	subdirProperties prebuiltSubdirProperties
@@ -183,7 +194,7 @@
 	return p.inDebugRamdisk()
 }
 
-func (p *PrebuiltEtc) inRecovery() bool {
+func (p *PrebuiltEtc) InRecovery() bool {
 	return p.ModuleBase.InRecovery() || p.ModuleBase.InstallInRecovery()
 }
 
@@ -192,7 +203,7 @@
 }
 
 func (p *PrebuiltEtc) InstallInRecovery() bool {
-	return p.inRecovery()
+	return p.InRecovery()
 }
 
 var _ android.ImageInterface = (*PrebuiltEtc)(nil)
@@ -271,6 +282,18 @@
 	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
 }
 
+func (p *PrebuiltEtc) InVendor() bool {
+	return p.ModuleBase.InstallInVendor()
+}
+
+func (p *PrebuiltEtc) ExcludeFromVendorSnapshot() bool {
+	return false
+}
+
+func (p *PrebuiltEtc) ExcludeFromRecoverySnapshot() bool {
+	return false
+}
+
 func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if p.properties.Src == nil {
 		ctx.PropertyErrorf("src", "missing prebuilt source file")
@@ -344,7 +367,7 @@
 	if p.inDebugRamdisk() && !p.onlyInDebugRamdisk() {
 		nameSuffix = ".debug_ramdisk"
 	}
-	if p.inRecovery() && !p.onlyInRecovery() {
+	if p.InRecovery() && !p.onlyInRecovery() {
 		nameSuffix = ".recovery"
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
@@ -354,7 +377,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
@@ -387,6 +410,7 @@
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -415,6 +439,8 @@
 	InitPrebuiltEtcModule(module, "etc")
 	// This module is host-only
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -425,6 +451,18 @@
 	InitPrebuiltRootModule(module)
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_root_host is for a host prebuilt artifact that is installed in $(HOST_OUT)/<sub_dir>
+// directory.
+func PrebuiltRootHostFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, ".")
+	// This module is host-only
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -435,6 +473,8 @@
 	InitPrebuiltEtcModule(module, "usr/share")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -445,6 +485,7 @@
 	InitPrebuiltEtcModule(module, "usr/share")
 	// This module is host-only
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -454,6 +495,7 @@
 	InitPrebuiltEtcModule(module, "fonts")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -467,6 +509,7 @@
 	InitPrebuiltEtcModule(module, "etc/firmware")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -479,6 +522,7 @@
 	InitPrebuiltEtcModule(module, "etc/dsp")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -492,5 +536,193 @@
 	InitPrebuiltEtcModule(module, "lib/rfsa")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
+
+// Copy file into the snapshot
+func copyFile(ctx android.SingletonContext, path android.Path, out string, fake bool) android.OutputPath {
+	if fake {
+		// Create empty file instead for the fake snapshot
+		return snapshot.WriteStringToFileRule(ctx, "", out)
+	} else {
+		return snapshot.CopyFileRule(pctx, ctx, path, out)
+	}
+}
+
+// Check if the module is target of the snapshot
+func isSnapshotAware(ctx android.SingletonContext, m *PrebuiltEtc, image snapshot.SnapshotImage) bool {
+	if !m.Enabled() {
+		return false
+	}
+
+	// Skip if the module is not included in the image
+	if !image.InImage(m)() {
+		return false
+	}
+
+	// When android/prebuilt.go selects between source and prebuilt, it sets
+	// HideFromMake on the other one to avoid duplicate install rules in make.
+	if m.IsHideFromMake() {
+		return false
+	}
+
+	// There are some prebuilt_etc module with multiple definition of same name.
+	// Check if the target would be included from the build
+	if !m.ExportedToMake() {
+		return false
+	}
+
+	// Skip if the module is in the predefined path list to skip
+	if image.IsProprietaryPath(ctx.ModuleDir(m), ctx.DeviceConfig()) {
+		return false
+	}
+
+	// Skip if the module should be excluded
+	if image.ExcludeFromSnapshot(m) || image.ExcludeFromDirectedSnapshot(ctx.DeviceConfig(), m.BaseModuleName()) {
+		return false
+	}
+
+	// Skip from other exceptional cases
+	if m.Target().Os.Class != android.Device {
+		return false
+	}
+	if m.Target().NativeBridge == android.NativeBridgeEnabled {
+		return false
+	}
+
+	return true
+}
+
+func generatePrebuiltSnapshot(s snapshot.SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths {
+	/*
+		Snapshot zipped artifacts directory structure for etc modules:
+		{SNAPSHOT_ARCH}/
+			arch-{TARGET_ARCH}-{TARGET_ARCH_VARIANT}/
+				etc/
+					(prebuilt etc files)
+			arch-{TARGET_2ND_ARCH}-{TARGET_2ND_ARCH_VARIANT}/
+				etc/
+					(prebuilt etc files)
+			NOTICE_FILES/
+				(notice files)
+	*/
+	var snapshotOutputs android.Paths
+	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
+	installedNotices := make(map[string]bool)
+
+	ctx.VisitAllModules(func(module android.Module) {
+		m, ok := module.(*PrebuiltEtc)
+		if !ok {
+			return
+		}
+
+		if !isSnapshotAware(ctx, m, s.Image) {
+			return
+		}
+
+		targetArch := "arch-" + m.Target().Arch.ArchType.String()
+
+		snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "etc", m.BaseModuleName())
+		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, m.OutputFile(), snapshotLibOut, s.Fake))
+
+		prop := snapshot.SnapshotJsonFlags{}
+		propOut := snapshotLibOut + ".json"
+		prop.ModuleName = m.BaseModuleName()
+		if m.subdirProperties.Relative_install_path != nil {
+			prop.RelativeInstallPath = *m.subdirProperties.Relative_install_path
+		}
+
+		if m.properties.Filename != nil {
+			prop.Filename = *m.properties.Filename
+		}
+
+		j, err := json.Marshal(prop)
+		if err != nil {
+			ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
+			return
+		}
+		snapshotOutputs = append(snapshotOutputs, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
+
+		if len(m.EffectiveLicenseFiles()) > 0 {
+			noticeName := ctx.ModuleName(m) + ".txt"
+			noticeOut := filepath.Join(noticeDir, noticeName)
+			// skip already copied notice file
+			if !installedNotices[noticeOut] {
+				installedNotices[noticeOut] = true
+
+				noticeOutPath := android.PathForOutput(ctx, noticeOut)
+				ctx.Build(pctx, android.BuildParams{
+					Rule:        android.Cat,
+					Inputs:      m.EffectiveLicenseFiles(),
+					Output:      noticeOutPath,
+					Description: "combine notices for " + noticeOut,
+				})
+				snapshotOutputs = append(snapshotOutputs, noticeOutPath)
+			}
+		}
+
+	})
+
+	return snapshotOutputs
+}
+
+// For Bazel / bp2build
+
+type bazelPrebuiltFileAttributes struct {
+	Src         bazel.LabelAttribute
+	Filename    string
+	Dir         string
+	Installable bazel.BoolAttribute
+}
+
+// ConvertWithBp2build performs bp2build conversion of PrebuiltEtc
+// 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)
+			if !ok {
+				continue
+			}
+			if props.Src != nil {
+				label := android.BazelLabelForModuleSrcSingle(ctx, *props.Src)
+				src.SetSelectValue(axis, config, label)
+			}
+		}
+	}
+
+	var filename string
+	if module.properties.Filename != nil {
+		filename = *module.properties.Filename
+	}
+
+	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 installable bazel.BoolAttribute
+	if install := module.properties.Installable; install != nil {
+		installable.Value = install
+	}
+
+	attrs := &bazelPrebuiltFileAttributes{
+		Src:         src,
+		Filename:    filename,
+		Dir:         dir,
+		Installable: installable,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		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/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 354f6bb..cf1f6d7 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -15,11 +15,15 @@
 package etc
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"testing"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
+	"android/soong/snapshot"
 )
 
 func TestMain(m *testing.M) {
@@ -36,6 +40,18 @@
 	}),
 )
 
+var prepareForPrebuiltEtcSnapshotTest = android.GroupFixturePreparers(
+	prepareForPrebuiltEtcTest,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		snapshot.VendorSnapshotImageSingleton.Init(ctx)
+		snapshot.RecoverySnapshotImageSingleton.Init(ctx)
+	}),
+	android.FixtureModifyConfig(func(config android.Config) {
+		config.TestProductVariables.DeviceVndkVersion = proptools.StringPtr("current")
+		config.TestProductVariables.RecoverySnapshotVersion = proptools.StringPtr("current")
+	}),
+)
+
 func TestPrebuiltEtcVariants(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
 		prebuilt_etc {
@@ -172,7 +188,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := result.Config.BuildOS.String()
 	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
 	if !p.Host() {
 		t.Errorf("host bit is not set for a prebuilt_etc_host module.")
@@ -226,7 +242,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := result.Config.BuildOS.String()
 	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
 	expected := filepath.Join("out/soong/host", result.Config.PrebuiltOS(), "usr", "share", "bar")
 	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
@@ -346,3 +362,110 @@
 		})
 	}
 }
+
+func checkIfSnapshotTaken(t *testing.T, result *android.TestResult, image string, moduleName string) {
+	checkIfSnapshotExistAsExpected(t, result, image, moduleName, true)
+}
+
+func checkIfSnapshotNotTaken(t *testing.T, result *android.TestResult, image string, moduleName string) {
+	checkIfSnapshotExistAsExpected(t, result, image, moduleName, false)
+}
+
+func checkIfSnapshotExistAsExpected(t *testing.T, result *android.TestResult, image string, moduleName string, expectToExist bool) {
+	snapshotSingleton := result.SingletonForTests(image + "-snapshot")
+	archType := "arm64"
+	archVariant := "armv8-a"
+	archDir := fmt.Sprintf("arch-%s", archType)
+
+	snapshotDir := fmt.Sprintf("%s-snapshot", image)
+	snapshotVariantPath := filepath.Join(snapshotDir, archType)
+	outputDir := filepath.Join(snapshotVariantPath, archDir, "etc")
+	imageVariant := ""
+	if image == "recovery" {
+		imageVariant = "recovery_"
+	}
+	mod := result.ModuleForTests(moduleName, fmt.Sprintf("android_%s%s_%s", imageVariant, archType, archVariant))
+	outputFiles := mod.OutputFiles(t, "")
+	if len(outputFiles) != 1 {
+		t.Errorf("%q must have single output\n", moduleName)
+		return
+	}
+	snapshotPath := filepath.Join(outputDir, moduleName)
+
+	if expectToExist {
+		out := snapshotSingleton.Output(snapshotPath)
+
+		if out.Input.String() != outputFiles[0].String() {
+			t.Errorf("The input of snapshot %q must be %q, but %q", "prebuilt_vendor", out.Input.String(), outputFiles[0])
+		}
+
+		snapshotJsonPath := snapshotPath + ".json"
+
+		if snapshotSingleton.MaybeOutput(snapshotJsonPath).Rule == nil {
+			t.Errorf("%q expected but not found", snapshotJsonPath)
+		}
+	} else {
+		out := snapshotSingleton.MaybeOutput(snapshotPath)
+		if out.Rule != nil {
+			t.Errorf("There must be no rule for module %q output file %q", moduleName, outputFiles[0])
+		}
+	}
+}
+
+func TestPrebuiltTakeSnapshot(t *testing.T) {
+	var testBp = `
+	prebuilt_etc {
+		name: "prebuilt_vendor",
+		src: "foo.conf",
+		vendor: true,
+	}
+
+	prebuilt_etc {
+		name: "prebuilt_vendor_indirect",
+		src: "foo.conf",
+		vendor: true,
+	}
+
+	prebuilt_etc {
+		name: "prebuilt_recovery",
+		src: "bar.conf",
+		recovery: true,
+	}
+
+	prebuilt_etc {
+		name: "prebuilt_recovery_indirect",
+		src: "bar.conf",
+		recovery: true,
+	}
+	`
+
+	t.Run("prebuilt: vendor and recovery snapshot", func(t *testing.T) {
+		result := prepareForPrebuiltEtcSnapshotTest.RunTestWithBp(t, testBp)
+
+		checkIfSnapshotTaken(t, result, "vendor", "prebuilt_vendor")
+		checkIfSnapshotTaken(t, result, "vendor", "prebuilt_vendor_indirect")
+		checkIfSnapshotTaken(t, result, "recovery", "prebuilt_recovery")
+		checkIfSnapshotTaken(t, result, "recovery", "prebuilt_recovery_indirect")
+	})
+
+	t.Run("prebuilt: directed snapshot", func(t *testing.T) {
+		prepareForPrebuiltEtcDirectedSnapshotTest := android.GroupFixturePreparers(
+			prepareForPrebuiltEtcSnapshotTest,
+			android.FixtureModifyConfig(func(config android.Config) {
+				config.TestProductVariables.DirectedVendorSnapshot = true
+				config.TestProductVariables.VendorSnapshotModules = make(map[string]bool)
+				config.TestProductVariables.VendorSnapshotModules["prebuilt_vendor"] = true
+				config.TestProductVariables.DirectedRecoverySnapshot = true
+				config.TestProductVariables.RecoverySnapshotModules = make(map[string]bool)
+				config.TestProductVariables.RecoverySnapshotModules["prebuilt_recovery"] = true
+			}),
+		)
+
+		result := prepareForPrebuiltEtcDirectedSnapshotTest.RunTestWithBp(t, testBp)
+
+		checkIfSnapshotTaken(t, result, "vendor", "prebuilt_vendor")
+		checkIfSnapshotNotTaken(t, result, "vendor", "prebuilt_vendor_indirect")
+		checkIfSnapshotTaken(t, result, "recovery", "prebuilt_recovery")
+		checkIfSnapshotNotTaken(t, result, "recovery", "prebuilt_recovery_indirect")
+	})
+}
diff --git a/etc/snapshot_etc.go b/etc/snapshot_etc.go
new file mode 100644
index 0000000..b54a8a6
--- /dev/null
+++ b/etc/snapshot_etc.go
@@ -0,0 +1,186 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package etc
+
+// This file implements snapshot module of 'prebuilt_etc' type
+// 'snapshot_etc' module defines android.PrebuiltInterface so it can be handled
+// as prebuilt of 'prebuilt_etc' type.
+// Properties of 'snapshot_etc' follows properties from snapshotJsonFlags type
+
+import (
+	"android/soong/android"
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func RegisterSnapshotEtcModule(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("snapshot_etc", SnapshotEtcFactory)
+}
+
+func init() {
+	RegisterSnapshotEtcModule(android.InitRegistrationContext)
+}
+
+// snapshot_etc is a prebuilt module type to be installed under etc which is auto-generated by
+// development/vendor_snapshot/update.py. This module will override prebuilt_etc module with same
+// name when 'prefer' property is true.
+func SnapshotEtcFactory() android.Module {
+	module := &SnapshotEtc{}
+	module.AddProperties(&module.properties)
+
+	var srcsSupplier = func(_ android.BaseModuleContext, prebuilt android.Module) []string {
+		s, ok := prebuilt.(*SnapshotEtc)
+		if !ok || s.properties.Src == nil {
+			return []string{}
+		}
+
+		return []string{*s.properties.Src}
+	}
+
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "src")
+	return module
+}
+
+type snapshotEtcProperties struct {
+	Src                   *string `android:"path,arch_variant"` // Source of snapshot_etc file
+	Filename              *string `android:"arch_variant"`      // Target file name when it differs from module name
+	Relative_install_path *string `android:"arch_variant"`      // Relative install path when it should be installed subdirectory of etc
+}
+
+type SnapshotEtc struct {
+	android.ModuleBase
+	prebuilt   android.Prebuilt
+	properties snapshotEtcProperties
+
+	outputFilePath android.OutputPath
+	installDirPath android.InstallPath
+}
+
+func (s *SnapshotEtc) Prebuilt() *android.Prebuilt {
+	return &s.prebuilt
+}
+
+func (s *SnapshotEtc) Name() string {
+	return s.prebuilt.Name(s.BaseModuleName())
+}
+
+func (s *SnapshotEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if s.properties.Src == nil {
+		ctx.PropertyErrorf("src", "missing prebuilt source file")
+		return
+	}
+
+	sourceFilePath := s.prebuilt.SingleSourcePath(ctx)
+
+	// Determine the output file basename.
+	// If Filename is set, use the name specified by the property.
+	// Otherwise use the module name.
+	filename := proptools.String(s.properties.Filename)
+	if filename == "" {
+		filename = ctx.ModuleName()
+	}
+
+	s.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
+
+	if strings.Contains(filename, "/") {
+		ctx.PropertyErrorf("filename", "filename cannot contain separator '/'")
+		return
+	}
+
+	subDir := ""
+	if s.properties.Relative_install_path != nil {
+		subDir = *s.properties.Relative_install_path
+	}
+
+	s.installDirPath = android.PathForModuleInstall(ctx, "etc", subDir)
+
+	// This ensures that outputFilePath has the correct name for others to
+	// use, as the source file may have a different name.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Cp,
+		Input:       sourceFilePath,
+		Output:      s.outputFilePath,
+		Description: "Install snapshot etc module " + s.BaseModuleName(),
+	})
+
+	ctx.InstallFile(s.installDirPath, s.outputFilePath.Base(), sourceFilePath)
+}
+
+func (p *SnapshotEtc) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(p.outputFilePath),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_TAGS", "optional")
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
+			},
+		},
+	}}
+}
+
+type snapshotEtcDependencyTag struct {
+	blueprint.DependencyTag
+}
+
+var tag = snapshotEtcDependencyTag{}
+
+func (s *SnapshotEtc) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+	return !s.ModuleBase.InstallInRecovery() && !s.ModuleBase.InstallInRamdisk() &&
+		!s.ModuleBase.InstallInVendorRamdisk() && !s.ModuleBase.InstallInDebugRamdisk()
+}
+
+func (p *SnapshotEtc) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return p.ModuleBase.InstallInRamdisk()
+}
+
+func (p *SnapshotEtc) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return p.ModuleBase.InstallInVendorRamdisk()
+}
+
+func (p *SnapshotEtc) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return p.ModuleBase.InstallInDebugRamdisk()
+}
+
+func (p *SnapshotEtc) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+	return p.ModuleBase.InstallInRecovery()
+}
+
+func (p *SnapshotEtc) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+	return nil
+}
+
+func (p *SnapshotEtc) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+}
+
+func (p *SnapshotEtc) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+
+func (p *SnapshotEtc) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{p.outputFilePath}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+
+}
+
+var _ android.PrebuiltInterface = (*SnapshotEtc)(nil)
+var _ android.ImageInterface = (*SnapshotEtc)(nil)
+var _ android.OutputFileProducer = (*SnapshotEtc)(nil)
diff --git a/etc/snapshot_etc_test.go b/etc/snapshot_etc_test.go
new file mode 100644
index 0000000..b9d5504
--- /dev/null
+++ b/etc/snapshot_etc_test.go
@@ -0,0 +1,185 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package etc
+
+import (
+	"android/soong/android"
+	"testing"
+
+	"github.com/google/blueprint"
+)
+
+var registerSourceModule = func(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("source", newSourceModule)
+}
+
+type sourceModuleProperties struct {
+	Deps []string `android:"path,arch_variant"`
+}
+
+type sourceModule struct {
+	android.ModuleBase
+	android.OverridableModuleBase
+
+	properties                                     sourceModuleProperties
+	dependsOnSourceModule, dependsOnPrebuiltModule bool
+	deps                                           android.Paths
+	src                                            android.Path
+}
+
+func newSourceModule() android.Module {
+	m := &sourceModule{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibFirst)
+	android.InitOverridableModule(m, nil)
+	return m
+}
+
+func (s *sourceModule) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
+	// s.properties.Deps are annotated with android:path, so they are
+	// automatically added to the dependency by pathDeps mutator
+}
+
+func (s *sourceModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	s.deps = android.PathsForModuleSrc(ctx, s.properties.Deps)
+	s.src = android.PathForModuleSrc(ctx, "source_file")
+}
+
+func (s *sourceModule) Srcs() android.Paths {
+	return android.Paths{s.src}
+}
+
+var prepareForSnapshotEtcTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithArchMutator,
+	android.PrepareForTestWithPrebuilts,
+	PrepareForTestWithPrebuiltEtc,
+	android.FixtureRegisterWithContext(RegisterSnapshotEtcModule),
+	android.FixtureRegisterWithContext(registerSourceModule),
+	android.FixtureMergeMockFs(android.MockFS{
+		"foo.conf": nil,
+		"bar.conf": nil,
+	}),
+)
+
+func TestSnapshotWithFilename(t *testing.T) {
+	var androidBp = `
+	snapshot_etc {
+		name: "etc_module",
+		src: "foo.conf",
+		filename: "bar.conf",
+	}
+	`
+
+	result := prepareForSnapshotEtcTest.RunTestWithBp(t, androidBp)
+	for _, variant := range result.ModuleVariantsForTests("etc_module") {
+		module := result.ModuleForTests("etc_module", variant)
+		s, ok := module.Module().(*SnapshotEtc)
+		if !ok {
+			t.Errorf("Expected snapshot_etc module type")
+		}
+		if s.outputFilePath.Base() != "bar.conf" {
+			t.Errorf("Output file path does not match with specified filename")
+		}
+	}
+}
+
+func TestSnapshotEtcWithOrigin(t *testing.T) {
+	var androidBp = `
+	prebuilt_etc {
+		name: "etc_module",
+		src: "foo.conf",
+	}
+
+	snapshot_etc {
+		name: "etc_module",
+		src: "bar.conf",
+	}
+
+	source {
+		name: "source",
+		deps: [":etc_module"],
+	}
+	`
+
+	result := prepareForSnapshotEtcTest.RunTestWithBp(t, androidBp)
+
+	for _, variant := range result.ModuleVariantsForTests("source") {
+		source := result.ModuleForTests("source", variant)
+
+		result.VisitDirectDeps(source.Module(), func(m blueprint.Module) {
+			if _, ok := m.(*PrebuiltEtc); !ok {
+				t.Errorf("Original prebuilt_etc module expected.")
+			}
+		})
+	}
+}
+
+func TestSnapshotEtcWithOriginAndPrefer(t *testing.T) {
+	var androidBp = `
+	prebuilt_etc {
+		name: "etc_module",
+		src: "foo.conf",
+	}
+
+	snapshot_etc {
+		name: "etc_module",
+		src: "bar.conf",
+		prefer: true,
+	}
+
+	source {
+		name: "source",
+		deps: [":etc_module"],
+	}
+	`
+
+	result := prepareForSnapshotEtcTest.RunTestWithBp(t, androidBp)
+
+	for _, variant := range result.ModuleVariantsForTests("source") {
+		source := result.ModuleForTests("source", variant)
+
+		result.VisitDirectDeps(source.Module(), func(m blueprint.Module) {
+			if _, ok := m.(*SnapshotEtc); !ok {
+				t.Errorf("Preferred snapshot_etc module expected.")
+			}
+		})
+	}
+}
+
+func TestSnapshotEtcWithoutOrigin(t *testing.T) {
+	var androidBp = `
+	snapshot_etc {
+		name: "etc_module",
+		src: "bar.conf",
+	}
+
+	source {
+		name: "source",
+		deps: [":etc_module"],
+	}
+	`
+
+	result := prepareForSnapshotEtcTest.RunTestWithBp(t, androidBp)
+
+	for _, variant := range result.ModuleVariantsForTests("source") {
+		source := result.ModuleForTests("source", variant)
+
+		result.VisitDirectDeps(source.Module(), func(m blueprint.Module) {
+			if _, ok := m.(*SnapshotEtc); !ok {
+				t.Errorf("Only source snapshot_etc module expected.")
+			}
+		})
+	}
+}
diff --git a/example_config.json b/example_config.json
new file mode 100644
index 0000000..7489840
--- /dev/null
+++ b/example_config.json
@@ -0,0 +1,6 @@
+{
+    "env": {
+	"ENV_VAR_1": "Value1",
+	"ENV_VAR_2": "Value2"
+    }
+}
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 29a8a39..33beb37 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -60,8 +60,8 @@
 	// https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
 	Vendor_boot *bool
 
-	// Optional kernel commandline
-	Cmdline *string `android:"arch_variant"`
+	// Optional kernel commandline arguments
+	Cmdline []string `android:"arch_variant"`
 
 	// File that contains bootconfig parameters. This can be set only when `vendor_boot` is true
 	// and `header_version` is greater than or equal to 4.
@@ -145,14 +145,12 @@
 	}
 
 	dtbName := proptools.String(b.properties.Dtb_prebuilt)
-	if dtbName == "" {
-		ctx.PropertyErrorf("dtb_prebuilt", "must be set")
-		return output
+	if dtbName != "" {
+		dtb := android.PathForModuleSrc(ctx, dtbName)
+		cmd.FlagWithInput("--dtb ", dtb)
 	}
-	dtb := android.PathForModuleSrc(ctx, dtbName)
-	cmd.FlagWithInput("--dtb ", dtb)
 
-	cmdline := proptools.String(b.properties.Cmdline)
+	cmdline := strings.Join(b.properties.Cmdline, " ")
 	if cmdline != "" {
 		flag := "--cmdline "
 		if vendor {
@@ -178,20 +176,18 @@
 	cmd.FlagWithArg("--header_version ", headerVersion)
 
 	ramdiskName := proptools.String(b.properties.Ramdisk_module)
-	if ramdiskName == "" {
-		ctx.PropertyErrorf("ramdisk_module", "must be set")
-		return output
-	}
-	ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
-	if filesystem, ok := ramdisk.(*filesystem); ok {
-		flag := "--ramdisk "
-		if vendor {
-			flag = "--vendor_ramdisk "
+	if ramdiskName != "" {
+		ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
+		if filesystem, ok := ramdisk.(*filesystem); ok {
+			flag := "--ramdisk "
+			if vendor {
+				flag = "--vendor_ramdisk "
+			}
+			cmd.FlagWithInput(flag, filesystem.OutputPath())
+		} else {
+			ctx.PropertyErrorf("ramdisk", "%q is not android_filesystem module", ramdisk.Name())
+			return output
 		}
-		cmd.FlagWithInput(flag, filesystem.OutputPath())
-	} else {
-		ctx.PropertyErrorf("ramdisk", "%q is not android_filesystem module", ramdisk.Name())
-		return output
 	}
 
 	bootconfig := proptools.String(b.properties.Bootconfig)
@@ -267,7 +263,7 @@
 		OutputFile: android.OptionalPathForPath(b.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", b.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", b.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
 			},
 		},
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index b2caa51..ccf9e9d 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -43,8 +43,14 @@
 	// Function that builds extra files under the root directory and returns the files
 	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
 
+	// Function that filters PackagingSpecs returned by PackagingBase.GatherPackagingSpecs()
+	filterPackagingSpecs func(specs map[string]android.PackagingSpec)
+
 	output     android.OutputPath
 	installDir android.InstallPath
+
+	// For testing. Keeps the result of CopyDepsToZip()
+	entries []string
 }
 
 type symlinkDefinition struct {
@@ -226,7 +232,7 @@
 
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
 	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.CopyDepsToZip(ctx, depsZipFile)
+	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -345,7 +351,7 @@
 	}
 
 	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.CopyDepsToZip(ctx, depsZipFile)
+	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
@@ -394,7 +400,7 @@
 		OutputFile: android.OptionalPathForPath(f.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
 			},
 		},
@@ -434,3 +440,14 @@
 	}
 	return nil
 }
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
+	specs := f.PackagingBase.GatherPackagingSpecs(ctx)
+	if f.filterPackagingSpecs != nil {
+		f.filterPackagingSpecs(specs)
+	}
+	return specs
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index e78fdff..cda06d9 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -45,11 +45,11 @@
 
 func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
 	result := fixture.RunTestWithBp(t, `
-	        android_system_image {
+		android_system_image {
 			name: "myfilesystem",
 			deps: [
 				"libfoo",
-                                "libbar",
+				"libbar",
 			],
 			linker_config_src: "linker.config.json",
 		}
@@ -74,3 +74,54 @@
 	android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
 		output.RuleParams.Command, "libbar.so")
 }
+
+func registerComponent(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("component", componentFactory)
+}
+
+func componentFactory() android.Module {
+	m := &component{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+type component struct {
+	android.ModuleBase
+	properties struct {
+		Install_copy_in_data []string
+	}
+}
+
+func (c *component) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	output := android.PathForModuleOut(ctx, c.Name())
+	dir := android.PathForModuleInstall(ctx, "components")
+	ctx.InstallFile(dir, c.Name(), output)
+
+	dataDir := android.PathForModuleInPartitionInstall(ctx, "data", "components")
+	for _, d := range c.properties.Install_copy_in_data {
+		ctx.InstallFile(dataDir, d, output)
+	}
+}
+
+func TestFileSystemGathersItemsOnlyInSystemPartition(t *testing.T) {
+	f := android.GroupFixturePreparers(fixture, android.FixtureRegisterWithContext(registerComponent))
+	result := f.RunTestWithBp(t, `
+		android_system_image {
+			name: "myfilesystem",
+			multilib: {
+				common: {
+					deps: ["foo"],
+				},
+			},
+			linker_config_src: "linker.config.json",
+		}
+		component {
+			name: "foo",
+			install_copy_in_data: ["bar"],
+		}
+	`)
+
+	module := result.ModuleForTests("myfilesystem", "android_common").Module().(*systemImage)
+	android.AssertDeepEquals(t, "entries should have foo only", []string{"components/foo"}, module.entries)
+}
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 739e609..e2f7d7b 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -215,7 +215,7 @@
 		OutputFile: android.OptionalPathForPath(l.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.installFileName())
 			},
 		},
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 1d24d6d..75abf70 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -37,6 +37,7 @@
 	module := &systemImage{}
 	module.AddProperties(&module.properties)
 	module.filesystem.buildExtraFiles = module.buildExtraFiles
+	module.filesystem.filterPackagingSpecs = module.filterPackagingSpecs
 	initFilesystemModule(&module.filesystem)
 	return module
 }
@@ -53,7 +54,7 @@
 
 	// we need "Module"s for packaging items
 	var otherModules []android.Module
-	deps := s.GatherPackagingSpecs(ctx)
+	deps := s.gatherFilteredPackagingSpecs(ctx)
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		for _, ps := range child.PackagingSpecs() {
 			if _, ok := deps[ps.RelPathInPackage()]; ok {
@@ -68,3 +69,14 @@
 	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
 	return output
 }
+
+// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
+// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// for symbol lookup by imitating "activated" paths.
+func (s *systemImage) filterPackagingSpecs(specs map[string]android.PackagingSpec) {
+	for k, ps := range specs {
+		if ps.Partition() != "system" {
+			delete(specs, k)
+		}
+	}
+}
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 3f16c0d..63e0aba 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -247,7 +247,7 @@
 		OutputFile: android.OptionalPathForPath(v.output),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", v.installDir.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", v.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName())
 			},
 		},
diff --git a/finder/finder.go b/finder/finder.go
index 5413fa6..b4834b1 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -847,6 +847,7 @@
 	if err != nil {
 		return errors.New("No data to load from database\n")
 	}
+	defer reader.Close()
 	bufferedReader := bufio.NewReader(reader)
 	if !f.validateCacheHeader(bufferedReader) {
 		return errors.New("Cache header does not match")
diff --git a/finder/fs/test.go b/finder/fs/test.go
index cb2140e..ed981fd 100644
--- a/finder/fs/test.go
+++ b/finder/fs/test.go
@@ -74,6 +74,7 @@
 	if err != nil {
 		t.Fatalf(err.Error())
 	}
+	defer reader.Close()
 	bytes, err := ioutil.ReadAll(reader)
 	if err != nil {
 		t.Fatal(err.Error())
diff --git a/fuzz/Android.bp b/fuzz/Android.bp
new file mode 100644
index 0000000..9d96e48
--- /dev/null
+++ b/fuzz/Android.bp
@@ -0,0 +1,15 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-fuzz",
+    pkgPath: "android/soong/fuzz",
+    deps: [
+        "soong-android",
+    ],
+    srcs: [
+        "fuzz_common.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
new file mode 100644
index 0000000..89f8187
--- /dev/null
+++ b/fuzz/fuzz_common.go
@@ -0,0 +1,297 @@
+// 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 fuzz
+
+// This file contains the common code for compiling C/C++ and Rust fuzzers for Android.
+
+import (
+	"encoding/json"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+type Lang string
+
+const (
+	Cc   Lang = ""
+	Rust Lang = "rust"
+	Java Lang = "java"
+)
+
+var BoolDefault = proptools.BoolDefault
+
+type FuzzModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	android.ApexModuleBase
+}
+
+type FuzzPackager struct {
+	Packages                android.Paths
+	FuzzTargets             map[string]bool
+	SharedLibInstallStrings []string
+}
+
+type FileToZip struct {
+	SourceFilePath        android.Path
+	DestinationPathPrefix string
+}
+
+type ArchOs struct {
+	HostOrTarget string
+	Arch         string
+	Dir          string
+}
+
+type FuzzConfig struct {
+	// Email address of people to CC on bugs or contact about this fuzz target.
+	Cc []string `json:"cc,omitempty"`
+	// Specify whether to enable continuous fuzzing on devices. Defaults to true.
+	Fuzz_on_haiku_device *bool `json:"fuzz_on_haiku_device,omitempty"`
+	// Specify whether to enable continuous fuzzing on host. Defaults to true.
+	Fuzz_on_haiku_host *bool `json:"fuzz_on_haiku_host,omitempty"`
+	// Component in Google's bug tracking system that bugs should be filed to.
+	Componentid *int64 `json:"componentid,omitempty"`
+	// Hotlists in Google's bug tracking system that bugs should be marked with.
+	Hotlists []string `json:"hotlists,omitempty"`
+	// Specify whether this fuzz target was submitted by a researcher. Defaults
+	// to false.
+	Researcher_submitted *bool `json:"researcher_submitted,omitempty"`
+	// Specify who should be acknowledged for CVEs in the Android Security
+	// Bulletin.
+	Acknowledgement []string `json:"acknowledgement,omitempty"`
+	// Additional options to be passed to libfuzzer when run in Haiku.
+	Libfuzzer_options []string `json:"libfuzzer_options,omitempty"`
+	// Additional options to be passed to HWASAN when running on-device in Haiku.
+	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"`
+}
+
+type FuzzProperties struct {
+	// Optional list of seed files to be installed to the fuzz target's output
+	// directory.
+	Corpus []string `android:"path"`
+	// Optional list of data files to be installed to the fuzz target's output
+	// directory. Directory structure relative to the module is preserved.
+	Data []string `android:"path"`
+	// Optional dictionary to be installed to the fuzz target's output directory.
+	Dictionary *string `android:"path"`
+	// Config for running the target on fuzzing infrastructure.
+	Fuzz_config *FuzzConfig
+}
+
+type FuzzPackagedModule struct {
+	FuzzProperties        FuzzProperties
+	Dictionary            android.Path
+	Corpus                android.Paths
+	CorpusIntermediateDir android.Path
+	Config                android.Path
+	Data                  android.Paths
+	DataIntermediateDir   android.Path
+}
+
+func IsValid(fuzzModule FuzzModule) bool {
+	// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
+	// fuzz targets we're going to package anyway.
+	if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() {
+		return false
+	}
+
+	// Discard modules that are in an unavailable namespace.
+	if !fuzzModule.ExportedToMake() {
+		return false
+	}
+
+	return true
+}
+
+func (s *FuzzPackager) PackageArtifacts(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, archDir android.OutputPath, builder *android.RuleBuilder) []FileToZip {
+	// Package the corpora into a zipfile.
+	var files []FileToZip
+	if fuzzModule.Corpus != nil {
+		corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
+		command := builder.Command().BuiltTool("soong_zip").
+			Flag("-j").
+			FlagWithOutput("-o ", corpusZip)
+		rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
+		command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.Corpus)
+		files = append(files, FileToZip{corpusZip, ""})
+	}
+
+	// Package the data into a zipfile.
+	if fuzzModule.Data != nil {
+		dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
+		command := builder.Command().BuiltTool("soong_zip").
+			FlagWithOutput("-o ", dataZip)
+		for _, f := range fuzzModule.Data {
+			intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
+			command.FlagWithArg("-C ", intermediateDir)
+			command.FlagWithInput("-f ", f)
+		}
+		files = append(files, FileToZip{dataZip, ""})
+	}
+
+	// The dictionary.
+	if fuzzModule.Dictionary != nil {
+		files = append(files, FileToZip{fuzzModule.Dictionary, ""})
+	}
+
+	// Additional fuzz config.
+	if fuzzModule.Config != nil {
+		files = append(files, FileToZip{fuzzModule.Config, ""})
+	}
+
+	return files
+}
+
+func (s *FuzzPackager) BuildZipFile(ctx android.SingletonContext, module android.Module, fuzzModule FuzzPackagedModule, files []FileToZip, builder *android.RuleBuilder, archDir android.OutputPath, archString string, hostOrTargetString string, archOs ArchOs, archDirs map[ArchOs][]FileToZip) ([]FileToZip, bool) {
+	fuzzZip := archDir.Join(ctx, module.Name()+".zip")
+
+	command := builder.Command().BuiltTool("soong_zip").
+		Flag("-j").
+		FlagWithOutput("-o ", fuzzZip)
+
+	for _, file := range files {
+		if file.DestinationPathPrefix != "" {
+			command.FlagWithArg("-P ", file.DestinationPathPrefix)
+		} else {
+			command.Flag("-P ''")
+		}
+		command.FlagWithInput("-f ", file.SourceFilePath)
+	}
+
+	builder.Build("create-"+fuzzZip.String(),
+		"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
+
+	// Don't add modules to 'make haiku-rust' that are set to not be
+	// exported to the fuzzing infrastructure.
+	if config := fuzzModule.FuzzProperties.Fuzz_config; config != nil {
+		if strings.Contains(hostOrTargetString, "host") && !BoolDefault(config.Fuzz_on_haiku_host, true) {
+			return archDirs[archOs], false
+		} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
+			return archDirs[archOs], false
+		}
+	}
+
+	s.FuzzTargets[module.Name()] = true
+	archDirs[archOs] = append(archDirs[archOs], FileToZip{fuzzZip, ""})
+
+	return archDirs[archOs], true
+}
+
+func (f *FuzzConfig) String() string {
+	b, err := json.Marshal(f)
+	if err != nil {
+		panic(err)
+	}
+
+	return string(b)
+}
+
+func (s *FuzzPackager) CreateFuzzPackage(ctx android.SingletonContext, archDirs map[ArchOs][]FileToZip, lang Lang, pctx android.PackageContext) {
+	var archOsList []ArchOs
+	for archOs := range archDirs {
+		archOsList = append(archOsList, archOs)
+	}
+	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].Dir < archOsList[j].Dir })
+
+	for _, archOs := range archOsList {
+		filesToZip := archDirs[archOs]
+		arch := archOs.Arch
+		hostOrTarget := archOs.HostOrTarget
+		builder := android.NewRuleBuilder(pctx, ctx)
+		zipFileName := "fuzz-" + hostOrTarget + "-" + arch + ".zip"
+		if lang == Rust {
+			zipFileName = "fuzz-rust-" + hostOrTarget + "-" + arch + ".zip"
+		}
+		if lang == Java {
+			zipFileName = "fuzz-java-" + hostOrTarget + "-" + arch + ".zip"
+		}
+		outputFile := android.PathForOutput(ctx, zipFileName)
+
+		s.Packages = append(s.Packages, outputFile)
+
+		command := builder.Command().BuiltTool("soong_zip").
+			Flag("-j").
+			FlagWithOutput("-o ", outputFile).
+			Flag("-L 0") // No need to try and re-compress the zipfiles.
+
+		for _, fileToZip := range filesToZip {
+
+			if fileToZip.DestinationPathPrefix != "" {
+				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
+			} else {
+				command.Flag("-P ''")
+			}
+			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
+
+		}
+		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
+			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
+	}
+}
+
+func (s *FuzzPackager) PreallocateSlice(ctx android.MakeVarsContext, targets string) {
+	fuzzTargets := make([]string, 0, len(s.FuzzTargets))
+	for target, _ := range s.FuzzTargets {
+		fuzzTargets = append(fuzzTargets, target)
+	}
+	sort.Strings(fuzzTargets)
+	ctx.Strict(targets, strings.Join(fuzzTargets, " "))
+}
+
+// CollectAllSharedDependencies performs a breadth-first search over the provided module's
+// dependencies using `visitDirectDeps` to enumerate all shared library
+// dependencies. We require breadth-first expansion, as otherwise we may
+// incorrectly use the core libraries (sanitizer runtimes, libc, libdl, etc.)
+// from a dependency. This may cause issues when dependencies have explicit
+// sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
+func CollectAllSharedDependencies(ctx android.SingletonContext, module android.Module, unstrippedOutputFile func(module android.Module) android.Path, isValidSharedDependency func(dependency android.Module) bool) android.Paths {
+	var fringe []android.Module
+
+	seen := make(map[string]bool)
+
+	// Enumerate the first level of dependencies, as we discard all non-library
+	// modules in the BFS loop below.
+	ctx.VisitDirectDeps(module, func(dep android.Module) {
+		if isValidSharedDependency(dep) {
+			fringe = append(fringe, dep)
+		}
+	})
+
+	var sharedLibraries android.Paths
+
+	for i := 0; i < len(fringe); i++ {
+		module := fringe[i]
+		if seen[module.Name()] {
+			continue
+		}
+		seen[module.Name()] = true
+
+		sharedLibraries = append(sharedLibraries, unstrippedOutputFile(module))
+		ctx.VisitDirectDeps(module, func(dep android.Module) {
+			if isValidSharedDependency(dep) && !seen[dep.Name()] {
+				fringe = append(fringe, dep)
+			}
+		})
+	}
+
+	return sharedLibraries
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 77dae75..c52ddee 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -67,13 +67,6 @@
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
 	})
-
-	android.DepsBp2BuildMutators(RegisterGenruleBp2BuildDeps)
-	android.RegisterBp2BuildMutator("genrule", GenruleBp2Build)
-}
-
-func RegisterGenruleBp2BuildDeps(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("genrule_tool_deps", toolDepsMutator)
 }
 
 var (
@@ -110,19 +103,31 @@
 
 type hostToolDependencyTag struct {
 	blueprint.BaseDependencyTag
+	android.LicenseAnnotationToolchainDependencyTag
 	label string
 }
+
+func (t hostToolDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
+	// Allow depending on a disabled module if it's replaced by a prebuilt
+	// counterpart. We get the prebuilt through android.PrebuiltGetPreferred in
+	// GenerateAndroidBuildActions.
+	return target.IsReplacedByPrebuilt()
+}
+
+var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
+
 type generatorProperties struct {
-	// The command to run on one or more input files. Cmd supports substitution of a few variables
+	// The command to run on one or more input files. Cmd supports substitution of a few variables.
 	//
 	// Available variables for substitution:
 	//
-	//  $(location): the path to the first entry in tools or tool_files
-	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>
-	//  $(in): one or more input files
-	//  $(out): a single output file
-	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true
-	//  $(genDir): the sandbox directory for this tool; contains $(out)
+	//  $(location): the path to the first entry in tools or tool_files.
+	//  $(location <label>): the path to the tool, tool_file, input or output with name <label>. Use $(location) if <label> refers to a rule that outputs exactly one file.
+	//  $(locations <label>): the paths to the tools, tool_files, inputs or outputs with name <label>. Use $(locations) if <label> refers to a rule that outputs two or more files.
+	//  $(in): one or more input files.
+	//  $(out): a single output file.
+	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true.
+	//  $(genDir): the sandbox directory for this tool; contains $(out).
 	//  $$: a literal $
 	Cmd *string
 
@@ -155,6 +160,11 @@
 	// For other packages to make their own genrules with extra
 	// properties
 	Extra interface{}
+
+	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
+	// prefix environment variables to it.
+	CmdModifier func(ctx android.ModuleContext, cmd string) string
+
 	android.ImageInterface
 
 	properties generatorProperties
@@ -211,6 +221,22 @@
 	return g.outputDeps
 }
 
+func (g *Module) OutputFiles(tag string) (android.Paths, error) {
+	if tag == "" {
+		return append(android.Paths{}, g.outputFiles...), nil
+	}
+	// otherwise, tag should match one of outputs
+	for _, outputFile := range g.outputFiles {
+		if outputFile.Rel() == tag {
+			return android.Paths{outputFile}, nil
+		}
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+}
+
+var _ android.SourceFileProducer = (*Module)(nil)
+var _ android.OutputFileProducer = (*Module)(nil)
+
 func toolDepsMutator(ctx android.BottomUpMutatorContext) {
 	if g, ok := ctx.Module().(*Module); ok {
 		for _, tool := range g.properties.Tools {
@@ -224,9 +250,9 @@
 }
 
 // Returns true if information was available from Bazel, false if bazel invocation still needs to occur.
-func (c *Module) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+func (c *Module) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
 	bazelCtx := ctx.Config().BazelContext
-	filePaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
+	filePaths, ok := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
 	if ok {
 		var bazelOutputFiles android.Paths
 		exportIncludeDirs := map[string]bool{}
@@ -268,7 +294,7 @@
 		if _, exists := locationLabels[label]; !exists {
 			locationLabels[label] = loc
 		} else {
-			ctx.ModuleErrorf("multiple labels for %q, %q and %q",
+			ctx.ModuleErrorf("multiple locations for label %q: %q and %q (do you have duplicate srcs entries?)",
 				label, locationLabels[label], loc)
 		}
 	}
@@ -282,6 +308,12 @@
 			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
 			case hostToolDependencyTag:
 				tool := ctx.OtherModuleName(module)
+				if m, ok := module.(android.Module); ok {
+					// Necessary to retrieve any prebuilt replacement for the tool, since
+					// toolDepsMutator runs too late for the prebuilt mutators to have
+					// replaced the dependency.
+					module = android.PrebuiltGetPreferred(ctx, m)
+				}
 
 				switch t := module.(type) {
 				case android.HostToolProvider:
@@ -314,14 +346,9 @@
 					}
 				case bootstrap.GoBinaryTool:
 					// A GoBinaryTool provides the install path to a tool, which will be copied.
-					if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil {
-						toolPath := android.PathForOutput(ctx, s)
-						tools = append(tools, toolPath)
-						addLocationLabel(tag.label, toolLocation{android.Paths{toolPath}})
-					} else {
-						ctx.ModuleErrorf("cannot find path for %q: %v", tool, err)
-						return
-					}
+					p := android.PathForGoBinary(ctx, t)
+					tools = append(tools, p)
+					addLocationLabel(tag.label, toolLocation{android.Paths{p}})
 				default:
 					ctx.ModuleErrorf("%q is not a host tool provider", tool)
 					return
@@ -355,9 +382,12 @@
 		addLocationLabel(toolFile, toolLocation{paths})
 	}
 
+	includeDirInPaths := ctx.DeviceConfig().BuildBrokenInputDir(g.Name())
 	var srcFiles android.Paths
 	for _, in := range g.properties.Srcs {
-		paths, missingDeps := android.PathsAndMissingDepsForModuleSrcExcludes(ctx, []string{in}, g.properties.Exclude_srcs)
+		paths, missingDeps := android.PathsAndMissingDepsRelativeToModuleSourceDir(android.SourceInput{
+			Context: ctx, Paths: []string{in}, ExcludePaths: g.properties.Exclude_srcs, IncludeDirs: includeDirInPaths,
+		})
 		if len(missingDeps) > 0 {
 			if !ctx.Config().AllowMissingDependencies() {
 				panic(fmt.Errorf("should never get here, the missing dependencies %q should have been reported in DepsMutator",
@@ -381,8 +411,13 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	cmd := String(g.properties.Cmd)
+	if g.CmdModifier != nil {
+		cmd = g.CmdModifier(ctx, cmd)
+	}
+
 	// Generate tasks, either from genrule or gensrcs.
-	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
+	for _, task := range g.taskGenerator(ctx, cmd, srcFiles) {
 		if len(task.out) == 0 {
 			ctx.ModuleErrorf("must have at least one output file")
 			return
@@ -464,7 +499,7 @@
 						}
 						return paths[0], nil
 					} else {
-						return reportError("unknown location label %q", label)
+						return reportError("unknown location label %q is not in srcs, out, tools or tool_files.", label)
 					}
 				} else if strings.HasPrefix(name, "locations ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
@@ -475,7 +510,7 @@
 						}
 						return strings.Join(paths, " "), nil
 					} else {
-						return reportError("unknown locations label %q", label)
+						return reportError("unknown locations label %q is not in srcs, out, tools or tool_files.", label)
 					}
 				} else {
 					return reportError("unknown variable '$(%s)'", name)
@@ -544,7 +579,7 @@
 	bazelModuleLabel := g.GetBazelLabel(ctx, g)
 	bazelActionsUsed := false
 	if g.MixedBuildsEnabled(ctx) {
-		bazelActionsUsed = g.generateBazelBuildActions(ctx, bazelModuleLabel)
+		bazelActionsUsed = g.GenerateBazelBuildActions(ctx, bazelModuleLabel)
 	}
 	if !bazelActionsUsed {
 		// For <= 6 outputs, just embed those directly in the users. Right now, that covers >90% of
@@ -810,29 +845,8 @@
 	Cmd   string
 }
 
-type bazelGenrule struct {
-	android.BazelTargetModuleBase
-	bazelGenruleAttributes
-}
-
-func BazelGenruleFactory() android.Module {
-	module := &bazelGenrule{}
-	module.AddProperties(&module.bazelGenruleAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func GenruleBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	if ctx.ModuleType() != "genrule" {
-		// Not a regular genrule. Could be a cc_genrule or java_genrule.
-		return
-	}
-
+// ConvertWithBp2build converts a Soong module -> Bazel target.
+func (m *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	// Bazel only has the "tools" attribute.
 	tools_prop := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
 	tool_files_prop := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
@@ -850,7 +864,11 @@
 	if m.properties.Cmd != nil {
 		cmd = strings.Replace(*m.properties.Cmd, "$(in)", "$(SRCS)", -1)
 		cmd = strings.Replace(cmd, "$(out)", "$(OUTS)", -1)
-		cmd = strings.Replace(cmd, "$(genDir)", "$(GENDIR)", -1)
+		genDir := "$(GENDIR)"
+		if t := ctx.ModuleType(); t == "cc_genrule" || t == "java_genrule" || t == "java_genrule_host" {
+			genDir = "$(RULEDIR)"
+		}
+		cmd = strings.Replace(cmd, "$(genDir)", genDir, -1)
 		if len(tools.Value.Includes) > 0 {
 			cmd = strings.Replace(cmd, "$(location)", fmt.Sprintf("$(location %s)", tools.Value.Includes[0].Label), -1)
 			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
@@ -888,15 +906,9 @@
 	}
 
 	// Create the BazelTargetModule.
-	ctx.CreateBazelTargetModule(BazelGenruleFactory, m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
-func (m *bazelGenrule) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelGenrule) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
 var Bool = proptools.Bool
 var String = proptools.String
 
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 3ce4f85..1b5cef2 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -31,12 +31,14 @@
 var prepareForGenRuleTest = android.GroupFixturePreparers(
 	android.PrepareForTestWithArchMutator,
 	android.PrepareForTestWithDefaults,
-
 	android.PrepareForTestWithFilegroup,
 	PrepareForTestWithGenRuleBuildComponents,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		android.RegisterPrebuiltMutators(ctx)
 		ctx.RegisterModuleType("tool", toolFactory)
+		ctx.RegisterModuleType("prebuilt_tool", prebuiltToolFactory)
 		ctx.RegisterModuleType("output", outputProducerFactory)
+		ctx.RegisterModuleType("use_source", useSourceFactory)
 	}),
 	android.FixtureMergeMockFs(android.MockFS{
 		"tool":       nil,
@@ -339,7 +341,7 @@
 				out: ["out"],
 				cmd: "echo foo > $(location missing)",
 			`,
-			err: `unknown location label "missing"`,
+			err: `unknown location label "missing" is not in srcs, out, tools or tool_files.`,
 		},
 		{
 			name: "error locations",
@@ -347,7 +349,7 @@
 					out: ["out"],
 					cmd: "echo foo > $(locations missing)",
 			`,
-			err: `unknown locations label "missing"`,
+			err: `unknown locations label "missing" is not in srcs, out, tools or tool_files`,
 		},
 		{
 			name: "error location no files",
@@ -684,6 +686,105 @@
 	}
 }
 
+func TestGenruleOutputFiles(t *testing.T) {
+	bp := `
+				genrule {
+					name: "gen",
+					out: ["foo", "sub/bar"],
+					cmd: "echo foo > $(location foo) && echo bar > $(location sub/bar)",
+				}
+				use_source {
+					name: "gen_foo",
+					srcs: [":gen{foo}"],
+				}
+				use_source {
+					name: "gen_bar",
+					srcs: [":gen{sub/bar}"],
+				}
+				use_source {
+					name: "gen_all",
+					srcs: [":gen"],
+				}
+			`
+
+	result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp)
+	android.AssertPathsRelativeToTopEquals(t,
+		"genrule.tag with output",
+		[]string{"out/soong/.intermediates/gen/gen/foo"},
+		result.ModuleForTests("gen_foo", "").Module().(*useSource).srcs)
+	android.AssertPathsRelativeToTopEquals(t,
+		"genrule.tag with output in subdir",
+		[]string{"out/soong/.intermediates/gen/gen/sub/bar"},
+		result.ModuleForTests("gen_bar", "").Module().(*useSource).srcs)
+	android.AssertPathsRelativeToTopEquals(t,
+		"genrule.tag with all",
+		[]string{"out/soong/.intermediates/gen/gen/foo", "out/soong/.intermediates/gen/gen/sub/bar"},
+		result.ModuleForTests("gen_all", "").Module().(*useSource).srcs)
+}
+
+func TestPrebuiltTool(t *testing.T) {
+	testcases := []struct {
+		name             string
+		bp               string
+		expectedToolName string
+	}{
+		{
+			name: "source only",
+			bp: `
+				tool { name: "tool" }
+			`,
+			expectedToolName: "bin/tool",
+		},
+		{
+			name: "prebuilt only",
+			bp: `
+				prebuilt_tool { name: "tool" }
+			`,
+			expectedToolName: "prebuilt_bin/tool",
+		},
+		{
+			name: "source preferred",
+			bp: `
+				tool { name: "tool" }
+				prebuilt_tool { name: "tool" }
+			`,
+			expectedToolName: "bin/tool",
+		},
+		{
+			name: "prebuilt preferred",
+			bp: `
+				tool { name: "tool" }
+				prebuilt_tool { name: "tool", prefer: true }
+			`,
+			expectedToolName: "prebuilt_bin/prebuilt_tool",
+		},
+		{
+			name: "source disabled",
+			bp: `
+				tool { name: "tool", enabled: false }
+				prebuilt_tool { name: "tool" }
+      `,
+			expectedToolName: "prebuilt_bin/prebuilt_tool",
+		},
+	}
+
+	for _, test := range testcases {
+		t.Run(test.name, func(t *testing.T) {
+			result := prepareForGenRuleTest.RunTestWithBp(t, test.bp+`
+				genrule {
+					name: "gen",
+					tools: ["tool"],
+					out: ["foo"],
+					cmd: "$(location tool)",
+				}
+			`)
+			gen := result.Module("gen", "").(*Module)
+			expectedCmd := "__SBOX_SANDBOX_DIR__/tools/out/" + test.expectedToolName
+			android.AssertStringEquals(t, "command", expectedCmd, gen.rawCommands[0])
+		})
+	}
+}
+
 func TestGenruleWithBazel(t *testing.T) {
 	bp := `
 		genrule {
@@ -728,7 +829,33 @@
 	return android.OptionalPathForPath(t.outputFile)
 }
 
+type prebuiltTestTool struct {
+	android.ModuleBase
+	prebuilt android.Prebuilt
+	testTool
+}
+
+func (p *prebuiltTestTool) Name() string {
+	return p.prebuilt.Name(p.ModuleBase.Name())
+}
+
+func (p *prebuiltTestTool) Prebuilt() *android.Prebuilt {
+	return &p.prebuilt
+}
+
+func (t *prebuiltTestTool) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "prebuilt_bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
+}
+
+func prebuiltToolFactory() android.Module {
+	module := &prebuiltTestTool{}
+	android.InitPrebuiltModuleWithoutSrcs(module)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	return module
+}
+
 var _ android.HostToolProvider = (*testTool)(nil)
+var _ android.HostToolProvider = (*prebuiltTestTool)(nil)
 
 type testOutputProducer struct {
 	android.ModuleBase
@@ -750,3 +877,22 @@
 }
 
 var _ android.OutputFileProducer = (*testOutputProducer)(nil)
+
+type useSource struct {
+	android.ModuleBase
+	props struct {
+		Srcs []string `android:"path"`
+	}
+	srcs android.Paths
+}
+
+func (s *useSource) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	s.srcs = android.PathsForModuleSrc(ctx, s.props.Srcs)
+}
+
+func useSourceFactory() android.Module {
+	module := &useSource{}
+	module.AddProperties(&module.props)
+	android.InitAndroidModule(module)
+	return module
+}
diff --git a/go.mod b/go.mod
index 7297dea..14444b3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,19 @@
 module android/soong
 
-require github.com/golang/protobuf v0.0.0
+require google.golang.org/protobuf v0.0.0
 
 require github.com/google/blueprint v0.0.0
 
-replace github.com/golang/protobuf v0.0.0 => ../../external/golang-protobuf
+replace google.golang.org/protobuf v0.0.0 => ../../external/golang-protobuf
 
 replace github.com/google/blueprint v0.0.0 => ../blueprint
 
+// Indirect deps from golang-protobuf
+exclude github.com/golang/protobuf v1.5.0
+
+replace github.com/google/go-cmp v0.5.5 => ../../external/go-cmp
+
+// Indirect dep from go-cmp
+exclude golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
+
 go 1.15
diff --git a/java/Android.bp b/java/Android.bp
index 5952602..df0d1eb 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -10,10 +10,12 @@
         "blueprint-pathtools",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-cc",
         "soong-dexpreopt",
         "soong-genrule",
         "soong-java-config",
+        "soong-provenance",
         "soong-python",
         "soong-remoteexec",
         "soong-tradefed",
@@ -39,9 +41,11 @@
         "dex.go",
         "dexpreopt.go",
         "dexpreopt_bootjars.go",
+        "dexpreopt_check.go",
         "dexpreopt_config.go",
         "droiddoc.go",
         "droidstubs.go",
+        "fuzz.go",
         "gen.go",
         "genrule.go",
         "hiddenapi.go",
@@ -78,6 +82,7 @@
         "app_test.go",
         "bootclasspath_fragment_test.go",
         "device_host_converter_test.go",
+        "dex_test.go",
         "dexpreopt_test.go",
         "dexpreopt_bootjars_test.go",
         "droiddoc_test.go",
@@ -91,8 +96,10 @@
         "platform_bootclasspath_test.go",
         "platform_compat_config_test.go",
         "plugin_test.go",
+        "prebuilt_apis_test.go",
         "rro_test.go",
         "sdk_test.go",
+        "sdk_library_test.go",
         "system_modules_test.go",
         "systemserver_classpath_fragment_test.go",
     ],
diff --git a/java/OWNERS b/java/OWNERS
index 16ef4d8..5b71b1e 100644
--- a/java/OWNERS
+++ b/java/OWNERS
@@ -1 +1,4 @@
-per-file dexpreopt*.go = ngeoffray@google.com,calin@google.com,mathieuc@google.com
+per-file dexpreopt*.go = ngeoffray@google.com,calin@google.com,skvadrik@google.com
+
+# For metalava team to disable lint checks in platform
+per-file droidstubs.go = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
\ No newline at end of file
diff --git a/java/aar.go b/java/aar.go
index a5cf0fd..00ff7e7 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -267,18 +267,29 @@
 	})
 
 func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkContext,
-	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
+	classLoaderContexts dexpreopt.ClassLoaderContextMap, excludedLibs []string,
+	extraLinkFlags ...string) {
 
 	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
 		aaptLibs(ctx, sdkContext, classLoaderContexts)
 
+	// Exclude any libraries from the supplied list.
+	classLoaderContexts = classLoaderContexts.ExcludeLibs(excludedLibs)
+
 	// App manifest file
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
 
-	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, classLoaderContexts,
-		a.isLibrary, a.useEmbeddedNativeLibs, a.usesNonSdkApis, a.useEmbeddedDex, a.hasNoCode,
-		a.LoggingParent)
+	manifestPath := ManifestFixer(ctx, manifestSrcPath, ManifestFixerParams{
+		SdkContext:            sdkContext,
+		ClassLoaderContexts:   classLoaderContexts,
+		IsLibrary:             a.isLibrary,
+		UseEmbeddedNativeLibs: a.useEmbeddedNativeLibs,
+		UsesNonSdkApis:        a.usesNonSdkApis,
+		UseEmbeddedDex:        a.useEmbeddedDex,
+		HasNoCode:             a.hasNoCode,
+		LoggingParent:         a.LoggingParent,
+	})
 
 	// Add additional manifest files to transitive manifests.
 	additionalManifests := android.PathsForModuleSrc(ctx, a.aaptProperties.Additional_manifests)
@@ -514,12 +525,13 @@
 	if sdkDep.hasFrameworkLibs() {
 		a.aapt.deps(ctx, sdkDep)
 	}
+	a.usesLibrary.deps(ctx, sdkDep.hasFrameworkLibs())
 }
 
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
-	a.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
-	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts)
+	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, nil)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -586,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
 }
 
@@ -615,6 +637,7 @@
 	exportPackage         android.WritablePath
 	extraAaptPackagesFile android.WritablePath
 	manifest              android.WritablePath
+	assetsPackage         android.WritablePath
 
 	exportedStaticPackages android.Paths
 
@@ -685,9 +708,8 @@
 	return android.Paths{a.manifest}
 }
 
-// TODO(jungjw): Decide whether we want to implement this.
 func (a *AARImport) ExportedAssets() android.OptionalPath {
-	return android.OptionalPath{}
+	return android.OptionalPathForPath(a.assetsPackage)
 }
 
 // RRO enforcement is not available on aar_import since its RRO dirs are not
@@ -731,10 +753,11 @@
 	blueprint.RuleParams{
 		Command: `rm -rf $outDir && mkdir -p $outDir && ` +
 			`unzip -qoDD -d $outDir $in && rm -rf $outDir/res && touch $out && ` +
+			`${config.Zip2ZipCmd} -i $in -o $assetsPackage 'assets/**/*' && ` +
 			`${config.MergeZipsCmd} $combinedClassesJar $$(ls $outDir/classes.jar 2> /dev/null) $$(ls $outDir/libs/*.jar 2> /dev/null)`,
-		CommandDeps: []string{"${config.MergeZipsCmd}"},
+		CommandDeps: []string{"${config.MergeZipsCmd}", "${config.Zip2ZipCmd}"},
 	},
-	"outDir", "combinedClassesJar")
+	"outDir", "combinedClassesJar", "assetsPackage")
 
 func (a *AARImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if len(a.properties.Aars) != 1 {
@@ -760,15 +783,17 @@
 	a.classpathFile = extractedAARDir.Join(ctx, "classes-combined.jar")
 	a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
 	a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
+	a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        unzipAAR,
 		Input:       a.aarPath,
-		Outputs:     android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest},
+		Outputs:     android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest, a.assetsPackage},
 		Description: "unzip AAR",
 		Args: map[string]string{
 			"outDir":             extractedAARDir.String(),
 			"combinedClassesJar": a.classpathFile.String(),
+			"assetsPackage":      a.assetsPackage.String(),
 		},
 	})
 
@@ -811,6 +836,19 @@
 	aapt2Link(ctx, a.exportPackage, srcJar, proguardOptionsFile, rTxt, a.extraAaptPackagesFile,
 		linkFlags, linkDeps, nil, overlayRes, transitiveAssets, nil)
 
+	// Merge this import's assets with its dependencies' assets (if there are any).
+	if len(transitiveAssets) > 0 {
+		mergedAssets := android.PathForModuleOut(ctx, "merged-assets.zip")
+		inputZips := append(android.Paths{a.assetsPackage}, transitiveAssets...)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        mergeAssetsRule,
+			Inputs:      inputZips,
+			Output:      mergedAssets,
+			Description: "merge assets from dependencies and self",
+		})
+		a.assetsPackage = mergedAssets
+	}
+
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(a.classpathFile),
 		ImplementationAndResourcesJars: android.PathsIfNonNil(a.classpathFile),
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 331f941..7772b70 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -27,13 +28,10 @@
 var manifestFixerRule = pctx.AndroidStaticRule("manifestFixer",
 	blueprint.RuleParams{
 		Command: `${config.ManifestFixerCmd} ` +
-			`--minSdkVersion ${minSdkVersion} ` +
-			`--targetSdkVersion ${targetSdkVersion} ` +
-			`--raise-min-sdk-version ` +
 			`$args $in $out`,
 		CommandDeps: []string{"${config.ManifestFixerCmd}"},
 	},
-	"minSdkVersion", "targetSdkVersion", "args")
+	"args")
 
 var manifestMergerRule = pctx.AndroidStaticRule("manifestMerger",
 	blueprint.RuleParams{
@@ -42,84 +40,124 @@
 	},
 	"args", "libs")
 
-// Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
-func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext android.SdkContext,
-	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
-	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
+// targetSdkVersion for manifest_fixer
+// When TARGET_BUILD_APPS is not empty, this method returns 10000 for modules targeting an unreleased SDK
+// This enables release builds (that run with TARGET_BUILD_APPS=[val...]) to target APIs that have not yet been finalized as part of an SDK
+func targetSdkVersionForManifestFixer(ctx android.ModuleContext, sdkContext android.SdkContext) string {
+	targetSdkVersionSpec := sdkContext.TargetSdkVersion(ctx)
+	if ctx.Config().UnbundledBuildApps() && targetSdkVersionSpec.ApiLevel.IsPreview() {
+		return strconv.Itoa(android.FutureApiLevel.FinalOrFutureInt())
+	}
+	targetSdkVersion, err := targetSdkVersionSpec.EffectiveVersionString(ctx)
+	if err != nil {
+		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
+	}
+	return targetSdkVersion
+}
 
+type ManifestFixerParams struct {
+	SdkContext            android.SdkContext
+	ClassLoaderContexts   dexpreopt.ClassLoaderContextMap
+	IsLibrary             bool
+	UseEmbeddedNativeLibs bool
+	UsesNonSdkApis        bool
+	UseEmbeddedDex        bool
+	HasNoCode             bool
+	TestOnly              bool
+	LoggingParent         string
+}
+
+// Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
+func ManifestFixer(ctx android.ModuleContext, manifest android.Path,
+	params ManifestFixerParams) android.Path {
 	var args []string
-	if isLibrary {
+
+	if params.IsLibrary {
 		args = append(args, "--library")
-	} else {
-		minSdkVersion, err := sdkContext.MinSdkVersion(ctx).EffectiveVersion(ctx)
+	} else if params.SdkContext != nil {
+		minSdkVersion, err := params.SdkContext.MinSdkVersion(ctx).EffectiveVersion(ctx)
 		if err != nil {
 			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 		}
 		if minSdkVersion.FinalOrFutureInt() >= 23 {
-			args = append(args, fmt.Sprintf("--extract-native-libs=%v", !useEmbeddedNativeLibs))
-		} else if useEmbeddedNativeLibs {
+			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)
 		}
 	}
 
-	if usesNonSdkApis {
+	if params.UsesNonSdkApis {
 		args = append(args, "--uses-non-sdk-api")
 	}
 
-	if useEmbeddedDex {
+	if params.UseEmbeddedDex {
 		args = append(args, "--use-embedded-dex")
 	}
 
-	for _, usesLib := range classLoaderContexts.UsesLibs() {
-		if inList(usesLib, dexpreopt.OptionalCompatUsesLibs) {
-			args = append(args, "--optional-uses-library", usesLib)
-		} else {
+	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()
+
+		for _, usesLib := range requiredUsesLibs {
 			args = append(args, "--uses-library", usesLib)
 		}
+		for _, usesLib := range optionalUsesLibs {
+			args = append(args, "--optional-uses-library", usesLib)
+		}
 	}
 
-	if hasNoCode {
+	if params.HasNoCode {
 		args = append(args, "--has-no-code")
 	}
 
-	if loggingParent != "" {
-		args = append(args, "--logging-parent", loggingParent)
-	}
-	var deps android.Paths
-	targetSdkVersion, err := sdkContext.TargetSdkVersion(ctx).EffectiveVersionString(ctx)
-	if err != nil {
-		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
-	}
-	if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-		targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-		deps = append(deps, ApiFingerprintPath(ctx))
+	if params.TestOnly {
+		args = append(args, "--test-only")
 	}
 
-	minSdkVersion, err := sdkContext.MinSdkVersion(ctx).EffectiveVersionString(ctx)
-	if err != nil {
-		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+	if params.LoggingParent != "" {
+		args = append(args, "--logging-parent", params.LoggingParent)
 	}
-	if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-		minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-		deps = append(deps, ApiFingerprintPath(ctx))
+	var deps android.Paths
+	var argsMapper = make(map[string]string)
+
+	if params.SdkContext != nil {
+		targetSdkVersion := targetSdkVersionForManifestFixer(ctx, params.SdkContext)
+		args = append(args, "--targetSdkVersion ", targetSdkVersion)
+
+		if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
+			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+			deps = append(deps, ApiFingerprintPath(ctx))
+		}
+
+		minSdkVersion, err := params.SdkContext.MinSdkVersion(ctx).EffectiveVersionString(ctx)
+		if err != nil {
+			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+		}
+
+		if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
+			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+			deps = append(deps, ApiFingerprintPath(ctx))
+		}
+
+		if err != nil {
+			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+		}
+		args = append(args, "--minSdkVersion ", minSdkVersion)
+		args = append(args, "--raise-min-sdk-version")
 	}
 
 	fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml")
-	if err != nil {
-		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
-	}
+	argsMapper["args"] = strings.Join(args, " ")
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        manifestFixerRule,
 		Description: "fix manifest",
 		Input:       manifest,
 		Implicits:   deps,
 		Output:      fixedManifest,
-		Args: map[string]string{
-			"minSdkVersion":    minSdkVersion,
-			"targetSdkVersion": targetSdkVersion,
-			"args":             strings.Join(args, " "),
-		},
+		Args:        argsMapper,
 	})
 
 	return fixedManifest.WithoutRel()
diff --git a/java/android_resources.go b/java/android_resources.go
index 6864ebb..8c5908f 100644
--- a/java/android_resources.go
+++ b/java/android_resources.go
@@ -43,7 +43,7 @@
 
 // androidResourceGlob returns the list of files in the given directory, using the standard
 // exclusion patterns for Android resources.
-func androidResourceGlob(ctx android.ModuleContext, dir android.Path) android.Paths {
+func androidResourceGlob(ctx android.EarlyModuleContext, dir android.Path) android.Paths {
 	return ctx.GlobFiles(filepath.Join(dir.String(), "**/*"), androidResourceIgnoreFilenames)
 }
 
diff --git a/java/androidmk.go b/java/androidmk.go
index 04357e0..7322637 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -29,8 +29,8 @@
 
 	if hostDexNeeded {
 		var output android.Path
-		if library.dexJarFile != nil {
-			output = library.dexJarFile
+		if library.dexJarFile.IsSet() {
+			output = library.dexJarFile.Path()
 		} else {
 			output = library.implementationAndResourcesJar
 		}
@@ -44,9 +44,10 @@
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetBool("LOCAL_IS_HOST_MODULE", true)
 					entries.SetPath("LOCAL_PREBUILT_MODULE_FILE", output)
-					if library.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile)
+					if library.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile.Path())
 					}
+					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", library.hostdexInstallFile)
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem()+"-hostdex")
@@ -60,28 +61,23 @@
 func (library *Library) AndroidMkEntries() []android.AndroidMkEntries {
 	var entriesList []android.AndroidMkEntries
 
+	if library.Os() == android.Windows {
+		// Make does not support Windows Java modules
+		return nil
+	}
+
 	if library.hideApexVariantFromMake {
-		// For a java library built for an APEX we don't need Make module
+		// For a java library built for an APEX, we don't need a Make module for itself. Otherwise, it
+		// will conflict with the platform variant because they have the same module name in the
+		// makefile. However, we need to add its dexpreopt outputs as sub-modules, if it is preopted.
+		dexpreoptEntries := library.dexpreopter.AndroidMkEntriesForApex()
+		if len(dexpreoptEntries) > 0 {
+			entriesList = append(entriesList, dexpreoptEntries...)
+		}
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
-		// May still need to add some additional dependencies.
-		checkedModulePaths := library.additionalCheckedModules
-		if len(checkedModulePaths) != 0 {
-			entriesList = append(entriesList, android.AndroidMkEntries{
-				Class: "FAKE",
-				// Need at least one output file in order for this to take effect.
-				OutputFile: android.OptionalPathForPath(checkedModulePaths[0]),
-				Include:    "$(BUILD_PHONY_PACKAGE)",
-				ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-					func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", checkedModulePaths.Strings()...)
-					},
-				},
-			})
-		} else {
-			entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
-		}
+		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
@@ -100,8 +96,8 @@
 					if library.installFile == nil {
 						entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
 					}
-					if library.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile)
+					if library.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", library.dexJarFile.Path())
 					}
 					if len(library.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
@@ -114,11 +110,8 @@
 						entries.SetPath("LOCAL_SOONG_JACOCO_REPORT_CLASSES_JAR", library.jacocoReportClassesFile)
 					}
 
-					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", library.classLoaderContexts.UsesLibs()...)
-
-					if len(library.additionalCheckedModules) != 0 {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
-					}
+					requiredUsesLibs, optionalUsesLibs := library.classLoaderContexts.UsesLibs()
+					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", append(requiredUsesLibs, optionalUsesLibs...)...)
 
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_DICT", library.dexer.proguardDictionary)
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_USAGE_ZIP", library.dexer.proguardUsageZip)
@@ -140,20 +133,21 @@
 }
 
 // Called for modules that are a component of a test suite.
-func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
+func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string, perTestcaseDirectory bool) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
 		entries.AddCompatibilityTestSuites(test_suites...)
 	} else {
 		entries.AddCompatibilityTestSuites("null-suite")
 	}
+	entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", perTestcaseDirectory)
 }
 
 func (j *Test) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testProperties.Test_suites)
+		testSuiteComponent(entries, j.testProperties.Test_suites, Bool(j.testProperties.Per_testcase_directory))
 		if j.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", j.testConfig)
 		}
@@ -181,14 +175,21 @@
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites)
+		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites, Bool(j.testHelperLibraryProperties.Per_testcase_directory))
 	})
 
 	return entriesList
 }
 
 func (prebuilt *Import) AndroidMkEntries() []android.AndroidMkEntries {
-	if prebuilt.hideApexVariantFromMake || !prebuilt.ContainingSdk().Unversioned() {
+	if prebuilt.hideApexVariantFromMake {
+		// For a library imported from a prebuilt APEX, we don't need a Make module for itself, as we
+		// don't need to install it. However, we need to add its dexpreopt outputs as sub-modules, if it
+		// is preopted.
+		dexpreoptEntries := prebuilt.dexpreopter.AndroidMkEntriesForApex()
+		return append(dexpreoptEntries, android.AndroidMkEntries{Disabled: true})
+	}
+	if !prebuilt.ContainingSdk().Unversioned() {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Disabled: true,
 		}}
@@ -200,8 +201,8 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", !Bool(prebuilt.properties.Installable))
-				if prebuilt.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile)
+				if prebuilt.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile.Path())
 				}
 				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.combinedClasspathFile)
 				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.combinedClasspathFile)
@@ -220,12 +221,12 @@
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "JAVA_LIBRARIES",
-		OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile),
+		OutputFile: android.OptionalPathForPath(prebuilt.dexJarFile.Path()),
 		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				if prebuilt.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile)
+				if prebuilt.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", prebuilt.dexJarFile.Path())
 				}
 				if len(prebuilt.dexpreopter.builtInstalled) > 0 {
 					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", prebuilt.dexpreopter.builtInstalled)
@@ -262,6 +263,10 @@
 }
 
 func (binary *Binary) AndroidMkEntries() []android.AndroidMkEntries {
+	if binary.Os() == android.Windows {
+		// Make does not support Windows Java modules
+		return nil
+	}
 
 	if !binary.isWrapperVariant {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
@@ -272,8 +277,8 @@
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar)
-					if binary.dexJarFile != nil {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile)
+					if binary.dexJarFile.IsSet() {
+						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile.Path())
 					}
 					if len(binary.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
@@ -288,11 +293,6 @@
 		}}
 	} else {
 		outputFile := binary.wrapperFile
-		// Have Make installation trigger Soong installation by using Soong's install path as
-		// the output file.
-		if binary.Host() {
-			outputFile = binary.binaryFile
-		}
 
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Class:      "EXECUTABLES",
@@ -314,7 +314,7 @@
 }
 
 func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries {
-	if app.hideApexVariantFromMake || app.appProperties.HideFromMake {
+	if app.hideApexVariantFromMake || app.IsHideFromMake() {
 		return []android.AndroidMkEntries{android.AndroidMkEntries{
 			Disabled: true,
 		}}
@@ -329,8 +329,8 @@
 				entries.SetString("LOCAL_MODULE", app.installApkName)
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", app.appProperties.PreventInstall)
 				entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", app.exportPackage)
-				if app.dexJarFile != nil {
-					entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile)
+				if app.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", app.dexJarFile.Path())
 				}
 				if app.implementationAndResourcesJar != nil {
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", app.implementationAndResourcesJar)
@@ -411,30 +411,24 @@
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string) {
-				if app.noticeOutputs.Merged.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.Merged.String(), app.installApkName+"_NOTICE")
-				}
-				if app.noticeOutputs.TxtOutput.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.TxtOutput.String(), app.installApkName+"_NOTICE.txt")
-				}
-				if app.noticeOutputs.HtmlOutput.Valid() {
-					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n",
-						app.installApkName, app.noticeOutputs.HtmlOutput.String(), app.installApkName+"_NOTICE.html")
+				if app.javaApiUsedByOutputFile.String() != "" {
+					fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s/$(notdir %s))\n",
+						app.installApkName, app.javaApiUsedByOutputFile.String(), "java_apis_used_by_apex", app.javaApiUsedByOutputFile.String())
 				}
 			},
-		},
-	}}
+		}},
+	}
 }
 
 func (a *AndroidApp) getOverriddenPackages() []string {
 	var overridden []string
-	if len(a.appProperties.Overrides) > 0 {
-		overridden = append(overridden, a.appProperties.Overrides...)
+	if len(a.overridableAppProperties.Overrides) > 0 {
+		overridden = append(overridden, a.overridableAppProperties.Overrides...)
 	}
-	if a.Name() != a.installApkName {
-		overridden = append(overridden, a.Name())
+	// When APK name is overridden via PRODUCT_PACKAGE_NAME_OVERRIDES
+	// ensure that the original name is overridden.
+	if a.Stem() != a.installApkName {
+		overridden = append(overridden, a.Stem())
 	}
 	return overridden
 }
@@ -443,7 +437,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		if a.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", a.testConfig)
 		}
@@ -459,7 +453,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites, Bool(a.appTestHelperAppProperties.Per_testcase_directory))
 		// introduce a flag variable to control the generation of the .config file
 		entries.SetString("LOCAL_DISABLE_TEST_CONFIG", "true")
 	})
@@ -670,7 +664,7 @@
 	entriesList := a.AndroidAppImport.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		androidMkWriteTestData(a.data, entries)
 	})
 	return entriesList
@@ -692,7 +686,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_CERTIFICATE", r.certificate.AndroidMkString())
-				entries.SetPath("LOCAL_MODULE_PATH", r.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", r.installDir)
 				entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", r.properties.Overrides...)
 			},
 		},
@@ -703,12 +697,12 @@
 	return []android.AndroidMkEntries{
 		android.AndroidMkEntries{
 			Class:      "APPS",
-			OutputFile: android.OptionalPathForPath(apkSet.packedOutput),
+			OutputFile: android.OptionalPathForPath(apkSet.primaryOutput),
 			Include:    "$(BUILD_SYSTEM)/soong_android_app_set.mk",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", apkSet.Privileged())
-					entries.SetString("LOCAL_APK_SET_INSTALL_FILE", apkSet.InstallFile())
+					entries.SetPath("LOCAL_APK_SET_INSTALL_FILE", apkSet.PackedAdditionalOutputs())
 					entries.SetPath("LOCAL_APKCERTS_FILE", apkSet.apkcertsFile)
 					entries.AddStrings("LOCAL_OVERRIDES_PACKAGES", apkSet.properties.Overrides...)
 				},
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 246c0eb..197da4f 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -206,3 +206,49 @@
 		t.Errorf("Unexpected flag value - expected: %q, actual: %q", expected, actual)
 	}
 }
+
+func TestGetOverriddenPackages(t *testing.T) {
+	ctx, _ := testJava(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			overrides: ["qux"]
+		}
+
+		override_android_app {
+			name: "foo_override",
+			base: "foo",
+			overrides: ["bar"]
+		}
+		`)
+
+	expectedVariants := []struct {
+		name        string
+		moduleName  string
+		variantName string
+		overrides   []string
+	}{
+		{
+			name:        "foo",
+			moduleName:  "foo",
+			variantName: "android_common",
+			overrides:   []string{"qux"},
+		},
+		{
+			name:        "foo",
+			moduleName:  "foo_override",
+			variantName: "android_common_foo_override",
+			overrides:   []string{"bar", "foo"},
+		},
+	}
+
+	for _, expected := range expectedVariants {
+		mod := ctx.ModuleForTests(expected.name, expected.variantName).Module()
+		entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+		actual := entries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
+
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, actual)
+	}
+}
diff --git a/java/app.go b/java/app.go
index fc1ace0..94e6fb9 100755
--- a/java/app.go
+++ b/java/app.go
@@ -19,13 +19,13 @@
 
 import (
 	"path/filepath"
-	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
 	"android/soong/tradefed"
@@ -63,13 +63,6 @@
 	// list of resource labels to generate individual resource packages
 	Package_splits []string
 
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-
 	// list of native libraries that will be provided in or alongside the resulting jar
 	Jni_libs []string `android:"arch_variant"`
 
@@ -106,7 +99,6 @@
 
 	// cc.Coverage related properties
 	PreventInstall    bool `blueprint:"mutated"`
-	HideFromMake      bool `blueprint:"mutated"`
 	IsCoverageVariant bool `blueprint:"mutated"`
 
 	// Whether this app is considered mainline updatable or not. When set to true, this will enforce
@@ -125,6 +117,9 @@
 	// Name of the signing certificate lineage file or filegroup module.
 	Lineage *string `android:"path"`
 
+	// For overriding the --rotation-min-sdk-version property of apksig
+	RotationMinSdkVersion *string
+
 	// the package name of this app. The package name in the manifest file is used if one was not given.
 	Package_name *string
 
@@ -133,9 +128,17 @@
 
 	// Whether to rename the package in resources to the override name rather than the base name. Defaults to true.
 	Rename_resources_package *bool
+
+	// Names of modules to be overridden. Listed modules can only be other binaries
+	// (in Make or Soong).
+	// This does not completely prevent installation of the overridden binaries, but if both
+	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
+	// from PRODUCT_PACKAGES.
+	Overrides []string
 }
 
 type AndroidApp struct {
+	android.BazelModuleBase
 	Library
 	aapt
 	android.OverridableModuleBase
@@ -162,11 +165,11 @@
 
 	additionalAaptFlags []string
 
-	noticeOutputs android.NoticeOutputs
-
 	overriddenManifestPackageName string
 
 	android.ApexBundleDepsInfo
+
+	javaApiUsedByOutputFile android.ModuleOutPath
 }
 
 func (a *AndroidApp) IsInstallable() bool {
@@ -275,6 +278,7 @@
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.checkAppSdkVersions(ctx)
 	a.generateAndroidBuildActions(ctx)
+	a.generateJavaUsedByApex(ctx)
 }
 
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
@@ -288,7 +292,7 @@
 
 		if minSdkVersion, err := a.MinSdkVersion(ctx).EffectiveVersion(ctx); err == nil {
 			a.checkJniLibsSdkVersion(ctx, minSdkVersion)
-			android.CheckMinSdkVersion(a, ctx, minSdkVersion)
+			android.CheckMinSdkVersion(ctx, minSdkVersion, a.WalkPayloadDeps)
 		} else {
 			ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		}
@@ -300,10 +304,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) {
@@ -314,10 +314,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
 		}
 
@@ -423,7 +423,8 @@
 
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts,
+		a.usesLibraryProperties.Exclude_uses_libs, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -468,12 +469,13 @@
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
 	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
+	a.dexpreopter.preventInstall = a.appProperties.PreventInstall
 
 	if ctx.ModuleName() != "framework-res" {
 		a.Module.compile(ctx, a.aaptSrcJar)
 	}
 
-	return a.dexJarFile
+	return a.dexJarFile.PathOrNil()
 }
 
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext) android.WritablePath {
@@ -482,7 +484,7 @@
 		a.jniLibs = jniLibs
 		if a.shouldEmbedJnis(ctx) {
 			jniJarFile = android.PathForModuleOut(ctx, "jnilibs.zip")
-			a.installPathForJNISymbols = a.installPath(ctx).ToMakePath()
+			a.installPathForJNISymbols = a.installPath(ctx)
 			TransformJniLibsToJar(ctx, jniJarFile, jniLibs, a.useEmbeddedNativeLibs(ctx))
 			for _, jni := range jniLibs {
 				if jni.coverageFile.Valid() {
@@ -519,53 +521,6 @@
 	return jniSymbols
 }
 
-func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext) {
-	// Collect NOTICE files from all dependencies.
-	seenModules := make(map[android.Module]bool)
-	noticePathSet := make(map[android.Path]bool)
-
-	ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
-		// Have we already seen this?
-		if _, ok := seenModules[child]; ok {
-			return false
-		}
-		seenModules[child] = true
-
-		// Skip host modules.
-		if child.Target().Os.Class == android.Host {
-			return false
-		}
-
-		paths := child.(android.Module).NoticeFiles()
-		if len(paths) > 0 {
-			for _, path := range paths {
-				noticePathSet[path] = true
-			}
-		}
-		return true
-	})
-
-	// If the app has one, add it too.
-	if len(a.NoticeFiles()) > 0 {
-		for _, path := range a.NoticeFiles() {
-			noticePathSet[path] = true
-		}
-	}
-
-	if len(noticePathSet) == 0 {
-		return
-	}
-	var noticePaths []android.Path
-	for path := range noticePathSet {
-		noticePaths = append(noticePaths, path)
-	}
-	sort.Slice(noticePaths, func(i, j int) bool {
-		return noticePaths[i].String() < noticePaths[j].String()
-	})
-
-	a.noticeOutputs = android.BuildNoticeOutput(ctx, a.installDir, a.installApkName+".apk", noticePaths)
-}
-
 // Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it
 // isn't a cert module reference. Also checks and enforces system cert restriction if applicable.
 func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate {
@@ -618,7 +573,7 @@
 	a.aapt.useEmbeddedDex = Bool(a.appProperties.Use_embedded_dex)
 
 	// Check if the install APK name needs to be overridden.
-	a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Name())
+	a.installApkName = ctx.DeviceConfig().OverridePackageNameFor(a.Stem())
 
 	if ctx.ModuleName() == "framework-res" {
 		// framework-res.apk is installed as system/framework/framework-res.apk
@@ -632,11 +587,6 @@
 	}
 	a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
 
-	a.noticeBuildActions(ctx)
-	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
-		a.aapt.noticeFile = a.noticeOutputs.HtmlGzOutput
-	}
-
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 
 	// Process all building blocks, from AAPT to certificates.
@@ -646,8 +596,12 @@
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
 	// Add implicit SDK libraries to <uses-library> list.
-	for _, usesLib := range a.classLoaderContexts.UsesLibs() {
-		a.usesLibrary.addLib(usesLib, inList(usesLib, dexpreopt.OptionalCompatUsesLibs))
+	requiredUsesLibs, optionalUsesLibs := a.classLoaderContexts.UsesLibs()
+	for _, usesLib := range requiredUsesLibs {
+		a.usesLibrary.addLib(usesLib, false)
+	}
+	for _, usesLib := range optionalUsesLibs {
+		a.usesLibrary.addLib(usesLib, true)
 	}
 
 	// Check that the <uses-library> list is coherent with the manifest.
@@ -673,7 +627,21 @@
 	}
 
 	certificates := processMainCert(a.ModuleBase, a.getCertString(ctx), certificateDeps, ctx)
-	a.certificate = certificates[0]
+
+	// This can be reached with an empty certificate list if AllowMissingDependencies is set
+	// and the certificate property for this module is a module reference to a missing module.
+	if len(certificates) > 0 {
+		a.certificate = certificates[0]
+	} else {
+		if !ctx.Config().AllowMissingDependencies() && len(ctx.GetMissingDependencies()) > 0 {
+			panic("Should only get here if AllowMissingDependencies set and there are missing dependencies")
+		}
+		// Set a certificate to avoid panics later when accessing it.
+		a.certificate = Certificate{
+			Key: android.PathForModuleOut(ctx, "missing.pk8"),
+			Pem: android.PathForModuleOut(ctx, "missing.pem"),
+		}
+	}
 
 	// Build a final signed app package.
 	packageFile := android.PathForModuleOut(ctx, a.installApkName+".apk")
@@ -686,19 +654,40 @@
 	if lineage := String(a.overridableAppProperties.Lineage); lineage != "" {
 		lineageFile = android.PathForModuleSrc(ctx, lineage)
 	}
-	CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile)
+
+	rotationMinSdkVersion := String(a.overridableAppProperties.RotationMinSdkVersion)
+
+	CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 	a.outputFile = packageFile
 	if v4SigningRequested {
 		a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile)
 	}
 
+	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
+		noticeFile := android.PathForModuleOut(ctx, "NOTICE.html.gz")
+		android.BuildNoticeHtmlOutputFromLicenseMetadata(
+			ctx, noticeFile, "", "",
+			[]string{
+				a.installDir.String() + "/",
+				android.PathForModuleInstall(ctx).String() + "/",
+				a.outputFile.String(),
+			})
+		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
+		builder := android.NewRuleBuilder(pctx, ctx)
+		builder.Command().Text("cp").
+			Input(noticeFile).
+			Output(noticeAssetPath)
+		builder.Build("notice_dir", "Building notice dir")
+		a.aapt.noticeFile = android.OptionalPathForPath(noticeAssetPath)
+	}
+
 	for _, split := range a.aapt.splits {
 		// Sign the split APKs
 		packageFile := android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk")
 		if v4SigningRequested {
 			v4SignatureFile = android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk.idsig")
 		}
-		CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile)
+		CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 		a.extraOutputFiles = append(a.extraOutputFiles, packageFile)
 		if v4SigningRequested {
 			a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile)
@@ -713,11 +702,15 @@
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 
 	// Install the app package.
-	if (Bool(a.Module.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() {
-		ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile)
+	if (Bool(a.Module.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() &&
+		!a.appProperties.PreventInstall {
+
+		var extraInstalledPaths android.Paths
 		for _, extra := range a.extraOutputFiles {
-			ctx.InstallFile(a.installDir, extra.Base(), extra)
+			installed := ctx.InstallFile(a.installDir, extra.Base(), extra)
+			extraInstalledPaths = append(extraInstalledPaths, installed)
 		}
+		ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile, extraInstalledPaths...)
 	}
 
 	a.buildAppDependencyInfo(ctx)
@@ -753,18 +746,18 @@
 				}
 
 				lib := dep.OutputFile()
-				path := lib.Path()
-				if seenModulePaths[path.String()] {
-					return false
-				}
-				seenModulePaths[path.String()] = true
-
-				if checkNativeSdkVersion && dep.SdkVersion() == "" {
-					ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
-						otherName)
-				}
-
 				if lib.Valid() {
+					path := lib.Path()
+					if seenModulePaths[path.String()] {
+						return false
+					}
+					seenModulePaths[path.String()] = true
+
+					if checkNativeSdkVersion && dep.SdkVersion() == "" {
+						ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
+							otherName)
+					}
+
 					jniLibs = append(jniLibs, jniLib{
 						name:           ctx.OtherModuleName(module),
 						path:           path,
@@ -859,6 +852,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 {
@@ -897,10 +894,6 @@
 	a.appProperties.PreventInstall = true
 }
 
-func (a *AndroidApp) HideFromMake() {
-	a.appProperties.HideFromMake = true
-}
-
 func (a *AndroidApp) MarkAsCoverageVariant(coverage bool) {
 	a.appProperties.IsCoverageVariant = coverage
 }
@@ -917,6 +910,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()
@@ -929,8 +923,9 @@
 
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitOverridableModule(module, &module.appProperties.Overrides)
+	android.InitOverridableModule(module, &module.overridableAppProperties.Overrides)
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
@@ -994,6 +989,7 @@
 	command := rule.Command().BuiltTool("test_config_fixer").Input(testConfig).Output(fixedConfig)
 	fixNeeded := false
 
+	// Auto-generated test config uses `ModuleName` as the APK name. So fix it if it is not the case.
 	if ctx.ModuleName() != a.installApkName {
 		fixNeeded = true
 		command.FlagWithArg("--test-file-name ", a.installApkName+".apk")
@@ -1034,6 +1030,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
@@ -1050,7 +1047,7 @@
 
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitOverridableModule(module, &module.appProperties.Overrides)
+	android.InitOverridableModule(module, &module.overridableAppProperties.Overrides)
 	return module
 }
 
@@ -1063,6 +1060,9 @@
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
 	Auto_gen_config *bool
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type AndroidTestHelperApp struct {
@@ -1104,6 +1104,8 @@
 
 type AndroidAppCertificate struct {
 	android.ModuleBase
+	android.BazelModuleBase
+
 	properties  AndroidAppCertificateProperties
 	Certificate Certificate
 }
@@ -1119,6 +1121,7 @@
 	module := &AndroidAppCertificate{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -1144,7 +1147,10 @@
 // some of its properties.
 func OverrideAndroidAppModuleFactory() android.Module {
 	m := &OverrideAndroidApp{}
-	m.AddProperties(&overridableAppProperties{})
+	m.AddProperties(
+		&OverridableDeviceProperties{},
+		&overridableAppProperties{},
+	)
 
 	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	android.InitOverrideModule(m)
@@ -1189,6 +1195,23 @@
 	// libraries, because SDK ones are automatically picked up by Soong. The <uses-library> name
 	// normally is the same as the module name, but there are exceptions.
 	Provides_uses_lib *string
+
+	// A list of shared library names to exclude from the classpath of the APK. Adding a library here
+	// will prevent it from being used when precompiling the APK and prevent it from being implicitly
+	// added to the APK's manifest's <uses-library> elements.
+	//
+	// Care must be taken when using this as it could result in runtime errors if the APK actually
+	// uses classes provided by the library and which are not provided in any other way.
+	//
+	// This is primarily intended for use by various CTS tests that check the runtime handling of the
+	// android.test.base shared library (and related libraries) but which depend on some common
+	// libraries that depend on the android.test.base library. Without this those tests will end up
+	// with a <uses-library android:name="android.test.base"/> in their manifest which would either
+	// render the tests worthless (as they would be testing the wrong behavior), or would break the
+	// test altogether by providing access to classes that the tests were not expecting. Those tests
+	// provide the android.test.base statically and use jarjar to rename them so they do not collide
+	// with the classes provided by the android.test.base library.
+	Exclude_uses_libs []string
 }
 
 // usesLibrary provides properties and helper functions for AndroidApp and AndroidAppImport to verify that the
@@ -1213,18 +1236,29 @@
 }
 
 func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, hasFrameworkLibs bool) {
-	if !ctx.Config().UnbundledBuild() {
-		ctx.AddVariationDependencies(nil, usesLibTag, u.usesLibraryProperties.Uses_libs...)
-		ctx.AddVariationDependencies(nil, usesLibTag, u.presentOptionalUsesLibs(ctx)...)
+	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)...)
+
 		// 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 {
-			// 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, usesLibCompat29Tag, dexpreopt.CompatUsesLibs29...)
-			ctx.AddVariationDependencies(nil, usesLibCompat28Tag, dexpreopt.OptionalCompatUsesLibs28...)
-			ctx.AddVariationDependencies(nil, usesLibCompat30Tag, dexpreopt.OptionalCompatUsesLibs30...)
+			// 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...)
 		}
 	}
 }
@@ -1245,36 +1279,53 @@
 	}
 }
 
-// Returns a map of module names of shared library dependencies to the paths
-// to their dex jars on host and on device.
+// Returns a map of module names of shared library dependencies to the paths to their dex jars on
+// host and on device.
 func (u *usesLibrary) classLoaderContextForUsesLibDeps(ctx android.ModuleContext) dexpreopt.ClassLoaderContextMap {
 	clcMap := make(dexpreopt.ClassLoaderContextMap)
 
-	if !ctx.Config().UnbundledBuild() {
-		ctx.VisitDirectDeps(func(m android.Module) {
-			if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
-				dep := ctx.OtherModuleName(m)
-				if lib, ok := m.(UsesLibraryDependency); ok {
-					libName := dep
-					if ulib, ok := m.(ProvidesUsesLib); ok && ulib.ProvidesUsesLib() != nil {
-						libName = *ulib.ProvidesUsesLib()
-						// Replace module name with library name in `uses_libs`/`optional_uses_libs`
-						// in order to pass verify_uses_libraries check (which compares these
-						// properties against library names written in the manifest).
-						replaceInList(u.usesLibraryProperties.Uses_libs, dep, libName)
-						replaceInList(u.usesLibraryProperties.Optional_uses_libs, dep, libName)
-					}
-					clcMap.AddContext(ctx, tag.sdkVersion, libName,
-						lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
-				} else if ctx.Config().AllowMissingDependencies() {
-					ctx.AddMissingDependencies([]string{dep})
-				} else {
-					ctx.ModuleErrorf("module %q in uses_libs or optional_uses_libs must be a java library", dep)
-				}
-			}
-		})
+	// Skip when UnbundledBuild() is true, but UnbundledBuildImage() is false. With
+	// UnbundledBuildImage() it is necessary to generate dexpreopt.config for post-dexpreopting.
+	if ctx.Config().UnbundledBuild() && !ctx.Config().UnbundledBuildImage() {
+		return clcMap
 	}
 
+	ctx.VisitDirectDeps(func(m android.Module) {
+		tag, isUsesLibTag := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag)
+		if !isUsesLibTag {
+			return
+		}
+
+		dep := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(m))
+
+		// Skip stub libraries. A dependency on the implementation library has been added earlier,
+		// so it will be added to CLC, but the stub shouldn't be. Stub libraries can be distingushed
+		// from implementation libraries by their name, which is different as it has a suffix.
+		if comp, ok := m.(SdkLibraryComponentDependency); ok {
+			if impl := comp.OptionalSdkLibraryImplementation(); impl != nil && *impl != dep {
+				return
+			}
+		}
+
+		if lib, ok := m.(UsesLibraryDependency); ok {
+			libName := dep
+			if ulib, ok := m.(ProvidesUsesLib); ok && ulib.ProvidesUsesLib() != nil {
+				libName = *ulib.ProvidesUsesLib()
+				// Replace module name with library name in `uses_libs`/`optional_uses_libs` in
+				// order to pass verify_uses_libraries check (which compares these properties
+				// against library names written in the manifest).
+				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,
+				lib.DexJarBuildPath().PathOrNil(), lib.DexJarInstallPath(),
+				lib.ClassLoaderContexts())
+		} else if ctx.Config().AllowMissingDependencies() {
+			ctx.AddMissingDependencies([]string{dep})
+		} else {
+			ctx.ModuleErrorf("module %q in uses_libs or optional_uses_libs must be a java library", dep)
+		}
+	})
 	return clcMap
 }
 
@@ -1352,3 +1403,94 @@
 	outputFile := android.PathForModuleOut(ctx, "verify_uses_libraries", apk.Base())
 	return outputFile
 }
+
+// For Bazel / bp2build
+
+type bazelAndroidAppCertificateAttributes struct {
+	Certificate string
+}
+
+func (m *AndroidAppCertificate) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	androidAppCertificateBp2Build(ctx, m)
+}
+
+func androidAppCertificateBp2Build(ctx android.TopDownMutatorContext, module *AndroidAppCertificate) {
+	var certificate string
+	if module.properties.Certificate != nil {
+		certificate = *module.properties.Certificate
+	}
+
+	attrs := &bazelAndroidAppCertificateAttributes{
+		Certificate: certificate,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "android_app_certificate",
+		Bzl_load_location: "//build/bazel/rules/android:android_app_certificate.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
+}
+
+type bazelAndroidAppAttributes struct {
+	*javaCommonAttributes
+	Deps             bazel.LabelListAttribute
+	Manifest         bazel.Label
+	Custom_package   *string
+	Resource_files   bazel.LabelListAttribute
+	Certificate      *bazel.Label
+	Certificate_name *string
+}
+
+// ConvertWithBp2build is used to convert android_app to Bazel.
+func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	commonAttrs, depLabels := a.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	if !commonAttrs.Srcs.IsEmpty() {
+		deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+	} else if !deps.IsEmpty() || !depLabels.StaticDeps.IsEmpty() {
+		ctx.ModuleErrorf("android_app has dynamic or static dependencies but no sources." +
+			" Bazel does not allow direct dependencies without sources nor exported" +
+			" dependencies on android_binary rule.")
+	}
+
+	manifest := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
+
+	resourceFiles := bazel.LabelList{
+		Includes: []bazel.Label{},
+	}
+	for _, dir := range android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res") {
+		files := android.RootToModuleRelativePaths(ctx, androidResourceGlob(ctx, dir))
+		resourceFiles.Includes = append(resourceFiles.Includes, files...)
+	}
+
+	var certificate *bazel.Label
+	certificateNamePtr := a.overridableAppProperties.Certificate
+	certificateName := proptools.StringDefault(certificateNamePtr, "")
+	certModule := android.SrcIsModule(certificateName)
+	if certModule != "" {
+		c := android.BazelLabelForModuleDepSingle(ctx, certificateName)
+		certificate = &c
+		certificateNamePtr = nil
+	}
+
+	attrs := &bazelAndroidAppAttributes{
+		commonAttrs,
+		deps,
+		android.BazelLabelForModuleSrcSingle(ctx, manifest),
+		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
+		a.overridableAppProperties.Package_name,
+		bazel.MakeLabelListAttribute(resourceFiles),
+		certificate,
+		certificateNamePtr,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "android_binary",
+		Bzl_load_location: "//build/bazel/rules/android:android_binary.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+
+}
diff --git a/java/app_builder.go b/java/app_builder.go
index 4a18dca..31023cb 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -52,7 +52,7 @@
 	})
 
 func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.WritablePath,
-	packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path) {
+	packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string) {
 
 	unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk"
 	unsignedApk := android.PathForModuleOut(ctx, unsignedApkName)
@@ -73,10 +73,10 @@
 		Implicits: deps,
 	})
 
-	SignAppPackage(ctx, outputFile, unsignedApk, certificates, v4SignatureFile, lineageFile)
+	SignAppPackage(ctx, outputFile, unsignedApk, certificates, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 }
 
-func SignAppPackage(ctx android.ModuleContext, signedApk android.WritablePath, unsignedApk android.Path, certificates []Certificate, v4SignatureFile android.WritablePath, lineageFile android.Path) {
+func SignAppPackage(ctx android.ModuleContext, signedApk android.WritablePath, unsignedApk android.Path, certificates []Certificate, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string) {
 
 	var certificateArgs []string
 	var deps android.Paths
@@ -97,6 +97,10 @@
 		deps = append(deps, lineageFile)
 	}
 
+	if rotationMinSdkVersion != "" {
+		flags = append(flags, "--rotation-min-sdk-version", rotationMinSdkVersion)
+	}
+
 	rule := Signapk
 	args := map[string]string{
 		"certificates": strings.Join(certificateArgs, " "),
@@ -254,6 +258,18 @@
 	})
 }
 
+func (a *AndroidApp) generateJavaUsedByApex(ctx android.ModuleContext) {
+	javaApiUsedByOutputFile := android.PathForModuleOut(ctx, a.installApkName+"_using.xml")
+	javaUsedByRule := android.NewRuleBuilder(pctx, ctx)
+	javaUsedByRule.Command().
+		Tool(android.PathForSource(ctx, "build/soong/scripts/gen_java_usedby_apex.sh")).
+		BuiltTool("dexdeps").
+		Output(javaApiUsedByOutputFile).
+		Input(a.Library.Module.outputFile)
+	javaUsedByRule.Build("java_usedby_list", "Generate Java APIs used by Apex")
+	a.javaApiUsedByOutputFile = javaApiUsedByOutputFile
+}
+
 func targetToJniDir(target android.Target) string {
 	return filepath.Join("lib", target.Arch.Abi[0])
 }
diff --git a/java/app_import.go b/java/app_import.go
index 3371e8e..58c01a4 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -22,6 +22,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/provenance"
 )
 
 func init() {
@@ -57,11 +58,13 @@
 	installPath android.InstallPath
 
 	hideApexVariantFromMake bool
+
+	provenanceMetaDataFile android.OutputPath
 }
 
 type AndroidAppImportProperties struct {
 	// A prebuilt apk to import
-	Apk *string
+	Apk *string `android:"path"`
 
 	// The name of a certificate in the default certificate directory or an android_app_certificate
 	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
@@ -77,6 +80,9 @@
 	// Name of the signing certificate lineage file or filegroup module.
 	Lineage *string `android:"path"`
 
+	// For overriding the --rotation-min-sdk-version property of apksig
+	RotationMinSdkVersion *string
+
 	// Sign with the default system dev certificate. Must be used judiciously. Most imported apps
 	// need to either specify a specific certificate or be presigned.
 	Default_dev_cert *bool
@@ -99,6 +105,9 @@
 	// If set, create package-export.apk, which other packages can
 	// use to get PRODUCT-agnostic resource data like IDs and type definitions.
 	Export_package_resources *bool
+
+	// Optional. Install to a subdirectory of the default install path for the module
+	Relative_install_path *string
 }
 
 func (a *AndroidAppImport) IsInstallable() bool {
@@ -201,9 +210,9 @@
 		return false
 	}
 
-	// Uncompress dex in APKs of privileged apps
-	if ctx.Config().UncompressPrivAppDex() && a.Privileged() {
-		return true
+	// Uncompress dex in APKs of priv-apps if and only if DONT_UNCOMPRESS_PRIV_APPS_DEXS is false.
+	if a.Privileged() {
+		return ctx.Config().UncompressPrivAppDex()
 	}
 
 	return shouldUncompressDex(ctx, &a.dexpreopter)
@@ -263,20 +272,25 @@
 	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
 	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
 
-	var installDir android.InstallPath
+	var pathFragments []string
+	relInstallPath := String(a.properties.Relative_install_path)
 
 	if a.isPrebuiltFrameworkRes() {
 		// framework-res.apk is installed as system/framework/framework-res.apk
-		installDir = android.PathForModuleInstall(ctx, "framework")
+		if relInstallPath != "" {
+			ctx.PropertyErrorf("relative_install_path", "Relative_install_path cannot be set for framework-res")
+		}
+		pathFragments = []string{"framework"}
 		a.preprocessed = true
 	} else if Bool(a.properties.Privileged) {
-		installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName())
+		pathFragments = []string{"priv-app", relInstallPath, a.BaseModuleName()}
 	} else if ctx.InstallInTestcases() {
-		installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch())
+		pathFragments = []string{relInstallPath, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch()}
 	} else {
-		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
+		pathFragments = []string{"app", relInstallPath, a.BaseModuleName()}
 	}
 
+	installDir := android.PathForModuleInstall(ctx, pathFragments...)
 	a.dexpreopter.isApp = true
 	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
 	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
@@ -322,7 +336,10 @@
 		if lineage := String(a.properties.Lineage); lineage != "" {
 			lineageFile = android.PathForModuleSrc(ctx, lineage)
 		}
-		SignAppPackage(ctx, signed, jnisUncompressed, certificates, nil, lineageFile)
+
+		rotationMinSdkVersion := String(a.properties.RotationMinSdkVersion)
+
+		SignAppPackage(ctx, signed, jnisUncompressed, certificates, nil, lineageFile, rotationMinSdkVersion)
 		a.outputFile = signed
 	} else {
 		alignedApk := android.PathForModuleOut(ctx, "zip-aligned", apkFilename)
@@ -335,6 +352,8 @@
 
 	if apexInfo.IsForPlatform() {
 		a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
+		artifactPath := android.PathForModuleSrc(ctx, *a.properties.Apk)
+		a.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, artifactPath, a.installPath)
 	}
 
 	// TODO: androidmk converter jni libs
@@ -360,6 +379,10 @@
 	return a.certificate
 }
 
+func (a *AndroidAppImport) ProvenanceMetaDataFile() android.OutputPath {
+	return a.provenanceMetaDataFile
+}
+
 var dpiVariantGroupType reflect.Type
 var archVariantGroupType reflect.Type
 var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
@@ -449,7 +472,7 @@
 //                 apk: "prebuilts/example_xhdpi.apk",
 //             },
 //         },
-//         certificate: "PRESIGNED",
+//         presigned: true,
 //     }
 func AndroidAppImportFactory() android.Module {
 	module := &AndroidAppImport{}
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 147ae45..41be092 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"reflect"
 	"regexp"
 	"strings"
@@ -52,6 +53,11 @@
 	if expected != signingFlag {
 		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
 	}
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_NoDexPreopt(t *testing.T) {
@@ -73,6 +79,12 @@
 		variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil {
 		t.Errorf("dexpreopt shouldn't have run.")
 	}
+
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_Presigned(t *testing.T) {
@@ -101,6 +113,12 @@
 	if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil {
 		t.Errorf("can't find aligning rule")
 	}
+
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_SigningLineage(t *testing.T) {
@@ -111,6 +129,7 @@
 			certificate: "platform",
 			additional_certificates: [":additional_certificate"],
 			lineage: "lineage.bin",
+			rotationMinSdkVersion: "32",
 		}
 
 		android_app_certificate {
@@ -130,12 +149,19 @@
 	if expected != certificatesFlag {
 		t.Errorf("Incorrect certificates flags, expected: %q, got: %q", expected, certificatesFlag)
 	}
-	// Check cert signing lineage flag.
-	signingFlag := signedApk.Args["flags"]
-	expected = "--lineage lineage.bin"
-	if expected != signingFlag {
-		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+
+	// Check cert signing flags.
+	actualCertSigningFlags := signedApk.Args["flags"]
+	expectedCertSigningFlags := "--lineage lineage.bin --rotation-min-sdk-version 32"
+	if expectedCertSigningFlags != actualCertSigningFlags {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expectedCertSigningFlags, actualCertSigningFlags)
 	}
+
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) {
@@ -162,6 +188,12 @@
 	if expected != signingFlag {
 		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
 	}
+
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
@@ -191,6 +223,12 @@
 	if expected != signingFlag {
 		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
 	}
+
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
 }
 
 func TestAndroidAppImport_DpiVariants(t *testing.T) {
@@ -213,40 +251,46 @@
 		}
 		`
 	testCases := []struct {
-		name                string
-		aaptPreferredConfig *string
-		aaptPrebuiltDPI     []string
-		expected            string
+		name                                   string
+		aaptPreferredConfig                    *string
+		aaptPrebuiltDPI                        []string
+		expected                               string
+		expectedProvenanceMetaDataArtifactPath string
 	}{
 		{
-			name:                "no preferred",
-			aaptPreferredConfig: nil,
-			aaptPrebuiltDPI:     []string{},
-			expected:            "verify_uses_libraries/apk/app.apk",
+			name:                                   "no preferred",
+			aaptPreferredConfig:                    nil,
+			aaptPrebuiltDPI:                        []string{},
+			expected:                               "verify_uses_libraries/apk/app.apk",
+			expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk",
 		},
 		{
-			name:                "AAPTPreferredConfig matches",
-			aaptPreferredConfig: proptools.StringPtr("xhdpi"),
-			aaptPrebuiltDPI:     []string{"xxhdpi", "ldpi"},
-			expected:            "verify_uses_libraries/apk/app_xhdpi.apk",
+			name:                                   "AAPTPreferredConfig matches",
+			aaptPreferredConfig:                    proptools.StringPtr("xhdpi"),
+			aaptPrebuiltDPI:                        []string{"xxhdpi", "ldpi"},
+			expected:                               "verify_uses_libraries/apk/app_xhdpi.apk",
+			expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk",
 		},
 		{
-			name:                "AAPTPrebuiltDPI matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"xxhdpi", "xhdpi"},
-			expected:            "verify_uses_libraries/apk/app_xxhdpi.apk",
+			name:                                   "AAPTPrebuiltDPI matches",
+			aaptPreferredConfig:                    proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:                        []string{"xxhdpi", "xhdpi"},
+			expected:                               "verify_uses_libraries/apk/app_xxhdpi.apk",
+			expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xxhdpi.apk",
 		},
 		{
-			name:                "non-first AAPTPrebuiltDPI matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"ldpi", "xhdpi"},
-			expected:            "verify_uses_libraries/apk/app_xhdpi.apk",
+			name:                                   "non-first AAPTPrebuiltDPI matches",
+			aaptPreferredConfig:                    proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:                        []string{"ldpi", "xhdpi"},
+			expected:                               "verify_uses_libraries/apk/app_xhdpi.apk",
+			expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app_xhdpi.apk",
 		},
 		{
-			name:                "no matches",
-			aaptPreferredConfig: proptools.StringPtr("mdpi"),
-			aaptPrebuiltDPI:     []string{"ldpi", "xxxhdpi"},
-			expected:            "verify_uses_libraries/apk/app.apk",
+			name:                                   "no matches",
+			aaptPreferredConfig:                    proptools.StringPtr("mdpi"),
+			aaptPrebuiltDPI:                        []string{"ldpi", "xxxhdpi"},
+			expected:                               "verify_uses_libraries/apk/app.apk",
+			expectedProvenanceMetaDataArtifactPath: "prebuilts/apk/app.apk",
 		},
 	}
 
@@ -269,6 +313,12 @@
 		if strings.HasSuffix(matches[1], test.expected) {
 			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
 		}
+
+		provenanceMetaDataRule := variant.Rule("genProvenanceMetaData")
+		android.AssertStringEquals(t, "Invalid input", test.expectedProvenanceMetaDataArtifactPath, provenanceMetaDataRule.Inputs[0].String())
+		android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", provenanceMetaDataRule.Output.String())
+		android.AssertStringEquals(t, "Invalid args", "foo", provenanceMetaDataRule.Args["module_name"])
+		android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", provenanceMetaDataRule.Args["install_path"])
 	}
 }
 
@@ -289,16 +339,25 @@
 		`)
 
 	testCases := []struct {
-		name     string
-		expected string
+		name                 string
+		expected             string
+		onDevice             string
+		expectedArtifactPath string
+		expectedMetaDataPath string
 	}{
 		{
-			name:     "foo",
-			expected: "foo.apk",
+			name:                 "foo",
+			expected:             "foo.apk",
+			onDevice:             "/system/app/foo/foo.apk",
+			expectedArtifactPath: "prebuilts/apk/app.apk",
+			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto",
 		},
 		{
-			name:     "bar",
-			expected: "bar_sample.apk",
+			name:                 "bar",
+			expected:             "bar_sample.apk",
+			onDevice:             "/system/app/bar/bar_sample.apk",
+			expectedArtifactPath: "prebuilts/apk/app.apk",
+			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/bar/provenance_metadata.textproto",
 		},
 	}
 
@@ -315,15 +374,23 @@
 			t.Errorf("Incorrect LOCAL_INSTALLED_MODULE_STEM value '%s', expected '%s'",
 				actualValues, expectedValues)
 		}
+		rule := variant.Rule("genProvenanceMetaData")
+		android.AssertStringEquals(t, "Invalid input", test.expectedArtifactPath, rule.Inputs[0].String())
+		android.AssertStringEquals(t, "Invalid output", test.expectedMetaDataPath, rule.Output.String())
+		android.AssertStringEquals(t, "Invalid args", test.name, rule.Args["module_name"])
+		android.AssertStringEquals(t, "Invalid args", test.onDevice, rule.Args["install_path"])
 	}
 }
 
 func TestAndroidAppImport_ArchVariants(t *testing.T) {
 	// The test config's target arch is ARM64.
 	testCases := []struct {
-		name     string
-		bp       string
-		expected string
+		name         string
+		bp           string
+		expected     string
+		artifactPath string
+		metaDataPath string
+		installPath  string
 	}{
 		{
 			name: "matching arch",
@@ -342,7 +409,9 @@
 					},
 				}
 			`,
-			expected: "verify_uses_libraries/apk/app_arm64.apk",
+			expected:     "verify_uses_libraries/apk/app_arm64.apk",
+			artifactPath: "prebuilts/apk/app_arm64.apk",
+			installPath:  "/system/app/foo/foo.apk",
 		},
 		{
 			name: "no matching arch",
@@ -361,7 +430,9 @@
 					},
 				}
 			`,
-			expected: "verify_uses_libraries/apk/app.apk",
+			expected:     "verify_uses_libraries/apk/app.apk",
+			artifactPath: "prebuilts/apk/app.apk",
+			installPath:  "/system/app/foo/foo.apk",
 		},
 		{
 			name: "no matching arch without default",
@@ -379,7 +450,9 @@
 					},
 				}
 			`,
-			expected: "",
+			expected:     "",
+			artifactPath: "prebuilts/apk/app_arm.apk",
+			installPath:  "/system/app/foo/foo.apk",
 		},
 	}
 
@@ -392,6 +465,8 @@
 			if variant.Module().Enabled() {
 				t.Error("module should have been disabled, but wasn't")
 			}
+			rule := variant.MaybeRule("genProvenanceMetaData")
+			android.AssertDeepEquals(t, "Provenance metadata is not empty", android.TestingBuildParams{}, rule)
 			continue
 		}
 		jniRuleCommand := variant.Output("jnis-uncompressed/foo.apk").RuleParams.Command
@@ -402,6 +477,11 @@
 		if strings.HasSuffix(matches[1], test.expected) {
 			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, matches[1])
 		}
+		rule := variant.Rule("genProvenanceMetaData")
+		android.AssertStringEquals(t, "Invalid input", test.artifactPath, rule.Inputs[0].String())
+		android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+		android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+		android.AssertStringEquals(t, "Invalid args", test.installPath, rule.Args["install_path"])
 	}
 }
 
@@ -493,6 +573,69 @@
 	}
 }
 
+func TestAndroidAppImport_relativeInstallPath(t *testing.T) {
+	bp := `
+		android_app_import {
+			name: "no_relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+		}
+
+		android_app_import {
+			name: "relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			relative_install_path: "my/path",
+		}
+
+		android_app_import {
+			name: "framework-res",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			prefer: true,
+		}
+
+		android_app_import {
+			name: "privileged_relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			privileged: true,
+			relative_install_path: "my/path"
+		}
+		`
+	testCases := []struct {
+		name                string
+		expectedInstallPath string
+		errorMessage        string
+	}{
+		{
+			name:                "no_relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/app/no_relative_install_path/no_relative_install_path.apk",
+			errorMessage:        "Install path is not correct when relative_install_path is missing",
+		},
+		{
+			name:                "relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/app/my/path/relative_install_path/relative_install_path.apk",
+			errorMessage:        "Install path is not correct for app when relative_install_path is present",
+		},
+		{
+			name:                "prebuilt_framework-res",
+			expectedInstallPath: "out/soong/target/product/test_device/system/framework/framework-res.apk",
+			errorMessage:        "Install path is not correct for framework-res",
+		},
+		{
+			name:                "privileged_relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/priv-app/my/path/privileged_relative_install_path/privileged_relative_install_path.apk",
+			errorMessage:        "Install path is not correct for privileged app when relative_install_path is present",
+		},
+	}
+	for _, testCase := range testCases {
+		ctx, _ := testJava(t, bp)
+		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*AndroidAppImport)
+		android.AssertPathRelativeToTopEquals(t, testCase.errorMessage, testCase.expectedInstallPath, mod.installPath)
+	}
+}
+
 func TestAndroidTestImport(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_test_import {
@@ -593,3 +736,74 @@
 		}
 	}
 }
+
+func TestAndroidTestImport_UncompressDex(t *testing.T) {
+	testCases := []struct {
+		name string
+		bp   string
+	}{
+		{
+			name: "normal",
+			bp: `
+				android_app_import {
+					name: "foo",
+					presigned: true,
+					apk: "prebuilts/apk/app.apk",
+				}
+			`,
+		},
+		{
+			name: "privileged",
+			bp: `
+				android_app_import {
+					name: "foo",
+					presigned: true,
+					privileged: true,
+					apk: "prebuilts/apk/app.apk",
+				}
+			`,
+		},
+	}
+
+	test := func(t *testing.T, bp string, unbundled bool, dontUncompressPrivAppDexs bool) {
+		t.Helper()
+
+		result := android.GroupFixturePreparers(
+			prepareForJavaTest,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				if unbundled {
+					variables.Unbundled_build = proptools.BoolPtr(true)
+				}
+				variables.UncompressPrivAppDex = proptools.BoolPtr(!dontUncompressPrivAppDexs)
+			}),
+		).RunTestWithBp(t, bp)
+
+		foo := result.ModuleForTests("foo", "android_common")
+		actual := foo.MaybeRule("uncompress-dex").Rule != nil
+
+		expect := !unbundled
+		if strings.Contains(bp, "privileged: true") {
+			if dontUncompressPrivAppDexs {
+				expect = false
+			} else {
+				// TODO(b/194504107): shouldn't priv-apps be always uncompressed unless
+				// DONT_UNCOMPRESS_PRIV_APPS_DEXS is true (regardless of unbundling)?
+				// expect = true
+			}
+		}
+
+		android.AssertBoolEquals(t, "uncompress dex", expect, actual)
+	}
+
+	for _, unbundled := range []bool{false, true} {
+		for _, dontUncompressPrivAppDexs := range []bool{false, true} {
+			for _, tt := range testCases {
+				name := fmt.Sprintf("%s,unbundled:%t,dontUncompressPrivAppDexs:%t",
+					tt.name, unbundled, dontUncompressPrivAppDexs)
+				t.Run(name, func(t *testing.T) {
+					test(t, tt.bp, unbundled, dontUncompressPrivAppDexs)
+				})
+			}
+		}
+	}
+}
diff --git a/java/app_set.go b/java/app_set.go
index 6b25638..694b167 100644
--- a/java/app_set.go
+++ b/java/app_set.go
@@ -55,10 +55,10 @@
 	android.DefaultableModuleBase
 	prebuilt android.Prebuilt
 
-	properties   AndroidAppSetProperties
-	packedOutput android.WritablePath
-	installFile  string
-	apkcertsFile android.ModuleOutPath
+	properties    AndroidAppSetProperties
+	packedOutput  android.WritablePath
+	primaryOutput android.WritablePath
+	apkcertsFile  android.ModuleOutPath
 }
 
 func (as *AndroidAppSet) Name() string {
@@ -78,11 +78,11 @@
 }
 
 func (as *AndroidAppSet) OutputFile() android.Path {
-	return as.packedOutput
+	return as.primaryOutput
 }
 
-func (as *AndroidAppSet) InstallFile() string {
-	return as.installFile
+func (as *AndroidAppSet) PackedAdditionalOutputs() android.Path {
+	return as.packedOutput
 }
 
 func (as *AndroidAppSet) APKCertsFile() android.Path {
@@ -114,11 +114,11 @@
 
 func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+	as.primaryOutput = android.PathForModuleOut(ctx, as.BaseModuleName()+".apk")
 	as.apkcertsFile = android.PathForModuleOut(ctx, "apkcerts.txt")
 	// We are assuming here that the install file in the APK
 	// set has `.apk` suffix. If it doesn't the build will fail.
 	// APK sets containing APEX files are handled elsewhere.
-	as.installFile = as.BaseModuleName() + ".apk"
 	screenDensities := "all"
 	if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 {
 		screenDensities = strings.ToUpper(strings.Join(dpis, ","))
@@ -127,11 +127,11 @@
 	// TODO(asmundak): do we support device features
 	ctx.Build(pctx,
 		android.BuildParams{
-			Rule:           extractMatchingApks,
-			Description:    "Extract APKs from APK set",
-			Output:         as.packedOutput,
-			ImplicitOutput: as.apkcertsFile,
-			Inputs:         android.Paths{as.prebuilt.SingleSourcePath(ctx)},
+			Rule:            extractMatchingApks,
+			Description:     "Extract APKs from APK set",
+			Output:          as.primaryOutput,
+			ImplicitOutputs: android.WritablePaths{as.packedOutput, as.apkcertsFile},
+			Inputs:          android.Paths{as.prebuilt.SingleSourcePath(ctx)},
 			Args: map[string]string{
 				"abis":              strings.Join(SupportedAbis(ctx), ","),
 				"allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)),
@@ -140,10 +140,21 @@
 				"stem":              as.BaseModuleName(),
 				"apkcerts":          as.apkcertsFile.String(),
 				"partition":         as.PartitionTag(ctx.DeviceConfig()),
+				"zip":               as.packedOutput.String(),
 			},
 		})
+
+	var installDir android.InstallPath
+	if as.Privileged() {
+		installDir = android.PathForModuleInstall(ctx, "priv-app", as.BaseModuleName())
+	} else {
+		installDir = android.PathForModuleInstall(ctx, "app", as.BaseModuleName())
+	}
+	ctx.InstallFileWithExtraFilesZip(installDir, as.BaseModuleName()+".apk", as.primaryOutput, as.packedOutput)
 }
 
+func (as *AndroidAppSet) InstallBypassMake() bool { return true }
+
 // android_app_set extracts a set of APKs based on the target device
 // configuration and installs this set as "split APKs".
 // The extracted set always contains an APK whose name is
diff --git a/java/app_set_test.go b/java/app_set_test.go
index adaf71b..03eb667 100644
--- a/java/app_set_test.go
+++ b/java/app_set_test.go
@@ -17,19 +17,20 @@
 import (
 	"fmt"
 	"reflect"
+	"strings"
 	"testing"
 
 	"android/soong/android"
 )
 
 func TestAndroidAppSet(t *testing.T) {
-	ctx, _ := testJava(t, `
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app_set {
 			name: "foo",
 			set: "prebuilts/apks/app.apks",
 			prerelease: true,
 		}`)
-	module := ctx.ModuleForTests("foo", "android_common")
+	module := result.ModuleForTests("foo", "android_common")
 	const packedSplitApks = "foo.zip"
 	params := module.Output(packedSplitApks)
 	if params.Rule == nil {
@@ -41,9 +42,22 @@
 	if s := params.Args["partition"]; s != "system" {
 		t.Errorf("wrong partition value: '%s', expected 'system'", s)
 	}
-	mkEntries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
+
+	android.AssertPathRelativeToTopEquals(t, "incorrect output path",
+		"out/soong/.intermediates/foo/android_common/foo.apk", params.Output)
+
+	android.AssertPathsRelativeToTopEquals(t, "incorrect implicit output paths",
+		[]string{
+			"out/soong/.intermediates/foo/android_common/foo.zip",
+			"out/soong/.intermediates/foo/android_common/apkcerts.txt",
+		},
+		params.ImplicitOutputs.Paths())
+
+	mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0]
 	actualInstallFile := mkEntries.EntryMap["LOCAL_APK_SET_INSTALL_FILE"]
-	expectedInstallFile := []string{"foo.apk"}
+	expectedInstallFile := []string{
+		strings.Replace(params.ImplicitOutputs[0].String(), android.OutSoongDir, result.Config.SoongOutDir(), 1),
+	}
 	if !reflect.DeepEqual(actualInstallFile, expectedInstallFile) {
 		t.Errorf("Unexpected LOCAL_APK_SET_INSTALL_FILE value: '%s', expected: '%s',",
 			actualInstallFile, expectedInstallFile)
diff --git a/java/app_test.go b/java/app_test.go
index a99ac62..8e331d4 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -27,7 +27,6 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
-	"android/soong/genrule"
 )
 
 // testApp runs tests using the prepareForJavaTest
@@ -144,14 +143,14 @@
 		}
 	`)
 
-	testJavaError(t, "platform_apis must be true when sdk_version is empty.", `
+	testJavaError(t, "This module has conflicting settings. sdk_version is empty, which means that this module is build against platform APIs. However platform_apis is not set to true", `
 		android_app {
 			name: "bar",
 			srcs: ["b.java"],
 		}
 	`)
 
-	testJavaError(t, "platform_apis must be false when sdk_version is not empty.", `
+	testJavaError(t, "This module has conflicting settings. sdk_version is not empty, which means this module cannot use platform APIs. However platform_apis is set to true.", `
 		android_app {
 			name: "bar",
 			srcs: ["b.java"],
@@ -428,7 +427,8 @@
 			name: "libjni",
 			stl: "none",
 			system_shared_libs: [],
-			sdk_version: "29",
+			sdk_version: "current",
+			min_sdk_version: "29",
 		}
 	`
 	fs := map[string][]byte{
@@ -482,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",
@@ -504,6 +505,7 @@
 			shared_libs: ["libbar"],
 			system_shared_libs: [],
 			sdk_version: "27",
+			min_sdk_version: "27",
 		}
 
 		cc_library {
@@ -511,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)
@@ -1485,11 +1488,11 @@
 
 func TestCertificates(t *testing.T) {
 	testCases := []struct {
-		name                string
-		bp                  string
-		certificateOverride string
-		expectedLineage     string
-		expectedCertificate string
+		name                     string
+		bp                       string
+		certificateOverride      string
+		expectedCertSigningFlags string
+		expectedCertificate      string
 	}{
 		{
 			name: "default",
@@ -1500,9 +1503,9 @@
 					sdk_version: "current",
 				}
 			`,
-			certificateOverride: "",
-			expectedLineage:     "",
-			expectedCertificate: "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8",
+			certificateOverride:      "",
+			expectedCertSigningFlags: "",
+			expectedCertificate:      "build/make/target/product/security/testkey.x509.pem build/make/target/product/security/testkey.pk8",
 		},
 		{
 			name: "module certificate property",
@@ -1519,9 +1522,9 @@
 					certificate: "cert/new_cert",
 				}
 			`,
-			certificateOverride: "",
-			expectedLineage:     "",
-			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
+			certificateOverride:      "",
+			expectedCertSigningFlags: "",
+			expectedCertificate:      "cert/new_cert.x509.pem cert/new_cert.pk8",
 		},
 		{
 			name: "path certificate property",
@@ -1533,9 +1536,9 @@
 					sdk_version: "current",
 				}
 			`,
-			certificateOverride: "",
-			expectedLineage:     "",
-			expectedCertificate: "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certificateOverride:      "",
+			expectedCertSigningFlags: "",
+			expectedCertificate:      "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 		},
 		{
 			name: "certificate overrides",
@@ -1552,18 +1555,19 @@
 					certificate: "cert/new_cert",
 				}
 			`,
-			certificateOverride: "foo:new_certificate",
-			expectedLineage:     "",
-			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
+			certificateOverride:      "foo:new_certificate",
+			expectedCertSigningFlags: "",
+			expectedCertificate:      "cert/new_cert.x509.pem cert/new_cert.pk8",
 		},
 		{
-			name: "certificate lineage",
+			name: "certificate signing flags",
 			bp: `
 				android_app {
 					name: "foo",
 					srcs: ["a.java"],
 					certificate: ":new_certificate",
 					lineage: "lineage.bin",
+					rotationMinSdkVersion: "32",
 					sdk_version: "current",
 				}
 
@@ -1572,18 +1576,19 @@
 					certificate: "cert/new_cert",
 				}
 			`,
-			certificateOverride: "",
-			expectedLineage:     "--lineage lineage.bin",
-			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
+			certificateOverride:      "",
+			expectedCertSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
+			expectedCertificate:      "cert/new_cert.x509.pem cert/new_cert.pk8",
 		},
 		{
-			name: "lineage from filegroup",
+			name: "cert signing flags from filegroup",
 			bp: `
 				android_app {
 					name: "foo",
 					srcs: ["a.java"],
 					certificate: ":new_certificate",
 					lineage: ":lineage_bin",
+					rotationMinSdkVersion: "32",
 					sdk_version: "current",
 				}
 
@@ -1597,9 +1602,9 @@
 					srcs: ["lineage.bin"],
 				}
 			`,
-			certificateOverride: "",
-			expectedLineage:     "--lineage lineage.bin",
-			expectedCertificate: "cert/new_cert.x509.pem cert/new_cert.pk8",
+			certificateOverride:      "",
+			expectedCertSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
+			expectedCertificate:      "cert/new_cert.x509.pem cert/new_cert.pk8",
 		},
 	}
 
@@ -1620,8 +1625,8 @@
 			signCertificateFlags := signapk.Args["certificates"]
 			android.AssertStringEquals(t, "certificates flags", test.expectedCertificate, signCertificateFlags)
 
-			signFlags := signapk.Args["flags"]
-			android.AssertStringEquals(t, "signing flags", test.expectedLineage, signFlags)
+			certSigningFlags := signapk.Args["flags"]
+			android.AssertStringEquals(t, "cert signing flags", test.expectedCertSigningFlags, certSigningFlags)
 		})
 	}
 }
@@ -1707,7 +1712,7 @@
 			},
 		},
 		{
-			name: "overridden",
+			name: "overridden via PRODUCT_PACKAGE_NAME_OVERRIDES",
 			bp: `
 				android_app {
 					name: "foo",
@@ -1722,6 +1727,22 @@
 				"out/soong/target/product/test_device/system/app/bar/bar.apk",
 			},
 		},
+		{
+			name: "overridden via stem",
+			bp: `
+				android_app {
+					name: "foo",
+					srcs: ["a.java"],
+					sdk_version: "current",
+					stem: "bar",
+				}
+			`,
+			packageNameOverride: "",
+			expected: []string{
+				"out/soong/.intermediates/foo/android_common/bar.apk",
+				"out/soong/target/product/test_device/system/app/bar/bar.apk",
+			},
+		},
 	}
 
 	for _, test := range testCases {
@@ -1737,7 +1758,7 @@
 
 			foo := result.ModuleForTests("foo", "android_common")
 
-			outSoongDir := result.Config.BuildDir()
+			outSoongDir := result.Config.SoongOutDir()
 
 			outputs := foo.AllOutputs()
 			outputMap := make(map[string]bool)
@@ -1800,6 +1821,7 @@
 			base: "foo",
 			certificate: ":new_certificate",
 			lineage: "lineage.bin",
+			rotationMinSdkVersion: "32",
 			logging_parent: "bah",
 		}
 
@@ -1845,89 +1867,89 @@
 		`)
 
 	expectedVariants := []struct {
-		name            string
-		moduleName      string
-		variantName     string
-		apkName         string
-		apkPath         string
-		certFlag        string
-		lineageFlag     string
-		overrides       []string
-		packageFlag     string
-		renameResources bool
-		logging_parent  string
+		name             string
+		moduleName       string
+		variantName      string
+		apkName          string
+		apkPath          string
+		certFlag         string
+		certSigningFlags string
+		overrides        []string
+		packageFlag      string
+		renameResources  bool
+		logging_parent   string
 	}{
 		{
-			name:            "foo",
-			moduleName:      "foo",
-			variantName:     "android_common",
-			apkPath:         "out/soong/target/product/test_device/system/app/foo/foo.apk",
-			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
-			lineageFlag:     "",
-			overrides:       []string{"qux"},
-			packageFlag:     "",
-			renameResources: false,
-			logging_parent:  "",
+			name:             "foo",
+			moduleName:       "foo",
+			variantName:      "android_common",
+			apkPath:          "out/soong/target/product/test_device/system/app/foo/foo.apk",
+			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certSigningFlags: "",
+			overrides:        []string{"qux"},
+			packageFlag:      "",
+			renameResources:  false,
+			logging_parent:   "",
 		},
 		{
-			name:            "foo",
-			moduleName:      "bar",
-			variantName:     "android_common_bar",
-			apkPath:         "out/soong/target/product/test_device/system/app/bar/bar.apk",
-			certFlag:        "cert/new_cert.x509.pem cert/new_cert.pk8",
-			lineageFlag:     "--lineage lineage.bin",
-			overrides:       []string{"qux", "foo"},
-			packageFlag:     "",
-			renameResources: false,
-			logging_parent:  "bah",
+			name:             "foo",
+			moduleName:       "bar",
+			variantName:      "android_common_bar",
+			apkPath:          "out/soong/target/product/test_device/system/app/bar/bar.apk",
+			certFlag:         "cert/new_cert.x509.pem cert/new_cert.pk8",
+			certSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
+			overrides:        []string{"qux", "foo"},
+			packageFlag:      "",
+			renameResources:  false,
+			logging_parent:   "bah",
 		},
 		{
-			name:            "foo",
-			moduleName:      "baz",
-			variantName:     "android_common_baz",
-			apkPath:         "out/soong/target/product/test_device/system/app/baz/baz.apk",
-			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
-			lineageFlag:     "",
-			overrides:       []string{"qux", "foo"},
-			packageFlag:     "org.dandroid.bp",
-			renameResources: true,
-			logging_parent:  "",
+			name:             "foo",
+			moduleName:       "baz",
+			variantName:      "android_common_baz",
+			apkPath:          "out/soong/target/product/test_device/system/app/baz/baz.apk",
+			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certSigningFlags: "",
+			overrides:        []string{"qux", "foo"},
+			packageFlag:      "org.dandroid.bp",
+			renameResources:  true,
+			logging_parent:   "",
 		},
 		{
-			name:            "foo",
-			moduleName:      "baz_no_rename_resources",
-			variantName:     "android_common_baz_no_rename_resources",
-			apkPath:         "out/soong/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
-			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
-			lineageFlag:     "",
-			overrides:       []string{"qux", "foo"},
-			packageFlag:     "org.dandroid.bp",
-			renameResources: false,
-			logging_parent:  "",
+			name:             "foo",
+			moduleName:       "baz_no_rename_resources",
+			variantName:      "android_common_baz_no_rename_resources",
+			apkPath:          "out/soong/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
+			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certSigningFlags: "",
+			overrides:        []string{"qux", "foo"},
+			packageFlag:      "org.dandroid.bp",
+			renameResources:  false,
+			logging_parent:   "",
 		},
 		{
-			name:            "foo_no_rename_resources",
-			moduleName:      "baz_base_no_rename_resources",
-			variantName:     "android_common_baz_base_no_rename_resources",
-			apkPath:         "out/soong/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
-			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
-			lineageFlag:     "",
-			overrides:       []string{"qux", "foo_no_rename_resources"},
-			packageFlag:     "org.dandroid.bp",
-			renameResources: false,
-			logging_parent:  "",
+			name:             "foo_no_rename_resources",
+			moduleName:       "baz_base_no_rename_resources",
+			variantName:      "android_common_baz_base_no_rename_resources",
+			apkPath:          "out/soong/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
+			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certSigningFlags: "",
+			overrides:        []string{"qux", "foo_no_rename_resources"},
+			packageFlag:      "org.dandroid.bp",
+			renameResources:  false,
+			logging_parent:   "",
 		},
 		{
-			name:            "foo_no_rename_resources",
-			moduleName:      "baz_override_base_rename_resources",
-			variantName:     "android_common_baz_override_base_rename_resources",
-			apkPath:         "out/soong/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
-			certFlag:        "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
-			lineageFlag:     "",
-			overrides:       []string{"qux", "foo_no_rename_resources"},
-			packageFlag:     "org.dandroid.bp",
-			renameResources: true,
-			logging_parent:  "",
+			name:             "foo_no_rename_resources",
+			moduleName:       "baz_override_base_rename_resources",
+			variantName:      "android_common_baz_override_base_rename_resources",
+			apkPath:          "out/soong/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
+			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
+			certSigningFlags: "",
+			overrides:        []string{"qux", "foo_no_rename_resources"},
+			packageFlag:      "org.dandroid.bp",
+			renameResources:  true,
+			logging_parent:   "",
 		},
 	}
 	for _, expected := range expectedVariants {
@@ -1941,13 +1963,13 @@
 		certFlag := signapk.Args["certificates"]
 		android.AssertStringEquals(t, "certificates flags", expected.certFlag, certFlag)
 
-		// Check the lineage flags
-		lineageFlag := signapk.Args["flags"]
-		android.AssertStringEquals(t, "signing flags", expected.lineageFlag, lineageFlag)
+		// Check the cert signing flags
+		certSigningFlags := signapk.Args["flags"]
+		android.AssertStringEquals(t, "cert signing flags", expected.certSigningFlags, certSigningFlags)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidApp)
-		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.appProperties.Overrides)
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)
 
 		// Test Overridable property: Logging_parent
 		logging_parent := mod.aapt.LoggingParent
@@ -1965,6 +1987,173 @@
 	}
 }
 
+func TestOverrideAndroidAppOverrides(t *testing.T) {
+	ctx, _ := testJava(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			overrides: ["qux"]
+		}
+
+		android_app {
+			name: "bar",
+			srcs: ["b.java"],
+			sdk_version: "current",
+			overrides: ["foo"]
+		}
+
+		override_android_app {
+			name: "foo_override",
+			base: "foo",
+			overrides: ["bar"]
+		}
+		`)
+
+	expectedVariants := []struct {
+		name        string
+		moduleName  string
+		variantName string
+		overrides   []string
+	}{
+		{
+			name:        "foo",
+			moduleName:  "foo",
+			variantName: "android_common",
+			overrides:   []string{"qux"},
+		},
+		{
+			name:        "bar",
+			moduleName:  "bar",
+			variantName: "android_common",
+			overrides:   []string{"foo"},
+		},
+		{
+			name:        "foo",
+			moduleName:  "foo_override",
+			variantName: "android_common_foo_override",
+			overrides:   []string{"bar", "foo"},
+		},
+	}
+	for _, expected := range expectedVariants {
+		variant := ctx.ModuleForTests(expected.name, expected.variantName)
+
+		// Check if the overrides field values are correctly aggregated.
+		mod := variant.Module().(*AndroidApp)
+		android.AssertDeepEquals(t, "overrides property", expected.overrides, mod.overridableAppProperties.Overrides)
+	}
+}
+
+func TestOverrideAndroidAppWithPrebuilt(t *testing.T) {
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
+		t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+
+		override_android_app {
+			name: "bar",
+			base: "foo",
+		}
+
+		android_app_import {
+			name: "bar",
+			prefer: true,
+			apk: "bar.apk",
+			presigned: true,
+		}
+		`)
+
+	// An app that has an override that also has a prebuilt should not be hidden.
+	foo := result.ModuleForTests("foo", "android_common")
+	if foo.Module().IsHideFromMake() {
+		t.Errorf("expected foo to have HideFromMake false")
+	}
+
+	// An override that also has a prebuilt should be hidden.
+	barOverride := result.ModuleForTests("foo", "android_common_bar")
+	if !barOverride.Module().IsHideFromMake() {
+		t.Errorf("expected bar override variant of foo to have HideFromMake true")
+	}
+}
+
+func TestOverrideAndroidAppStem(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+		override_android_app {
+			name: "bar",
+			base: "foo",
+		}
+		override_android_app {
+			name: "baz",
+			base: "foo",
+			stem: "baz_stem",
+		}
+		android_app {
+			name: "foo2",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			stem: "foo2_stem",
+		}
+		override_android_app {
+			name: "bar2",
+			base: "foo2",
+		}
+		override_android_app {
+			name: "baz2",
+			base: "foo2",
+			stem: "baz2_stem",
+		}
+	`)
+	for _, expected := range []struct {
+		moduleName  string
+		variantName string
+		apkPath     string
+	}{
+		{
+			moduleName:  "foo",
+			variantName: "android_common",
+			apkPath:     "out/soong/target/product/test_device/system/app/foo/foo.apk",
+		},
+		{
+			moduleName:  "foo",
+			variantName: "android_common_bar",
+			apkPath:     "out/soong/target/product/test_device/system/app/bar/bar.apk",
+		},
+		{
+			moduleName:  "foo",
+			variantName: "android_common_baz",
+			apkPath:     "out/soong/target/product/test_device/system/app/baz_stem/baz_stem.apk",
+		},
+		{
+			moduleName:  "foo2",
+			variantName: "android_common",
+			apkPath:     "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
+		},
+		{
+			moduleName:  "foo2",
+			variantName: "android_common_bar2",
+			// Note that this may cause the duplicate output error.
+			apkPath: "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
+		},
+		{
+			moduleName:  "foo2",
+			variantName: "android_common_baz2",
+			apkPath:     "out/soong/target/product/test_device/system/app/baz2_stem/baz2_stem.apk",
+		},
+	} {
+		variant := ctx.ModuleForTests(expected.moduleName, expected.variantName)
+		variant.Output(expected.apkPath)
+	}
+}
+
 func TestOverrideAndroidAppDependency(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_app {
@@ -2071,9 +2260,9 @@
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidTest)
-		if !reflect.DeepEqual(expected.overrides, mod.appProperties.Overrides) {
+		if !reflect.DeepEqual(expected.overrides, mod.overridableAppProperties.Overrides) {
 			t.Errorf("Incorrect overrides property value, expected: %q, got: %q",
-				expected.overrides, mod.appProperties.Overrides)
+				expected.overrides, mod.overridableAppProperties.Overrides)
 		}
 
 		// Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides.
@@ -2168,10 +2357,33 @@
 				t.Errorf("test_config_fixer was not expected to run, but did: %q", params.RuleParams.Command)
 			}
 		}
-
 	}
 }
 
+func TestInstrumentationTargetPrebuilt(t *testing.T) {
+	bp := `
+		android_app_import {
+			name: "foo",
+			apk: "foo.apk",
+			presigned: true,
+		}
+
+		android_test {
+			name: "bar",
+			srcs: ["a.java"],
+			instrumentation_for: "foo",
+			sdk_version: "current",
+		}
+		`
+
+	android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).ExtendWithErrorHandler(
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			"instrumentation_for: dependency \"foo\" of type \"android_app_import\" does not provide JavaInfo so is unsuitable for use with this property")).
+		RunTestWithBp(t, bp)
+}
+
 func TestStl(t *testing.T) {
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
@@ -2285,6 +2497,49 @@
 			sdk_version: "current",
 		}
 
+		java_library {
+			name: "runtime-required-x",
+			srcs: ["a.java"],
+			installable: true,
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "runtime-optional-x",
+			srcs: ["a.java"],
+			installable: true,
+			sdk_version: "current",
+		}
+
+		android_library {
+			name: "static-x",
+			uses_libs: ["runtime-required-x"],
+			optional_uses_libs: ["runtime-optional-x"],
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "runtime-required-y",
+			srcs: ["a.java"],
+			installable: true,
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "runtime-optional-y",
+			srcs: ["a.java"],
+			installable: true,
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "static-y",
+			srcs: ["a.java"],
+			uses_libs: ["runtime-required-y"],
+			optional_uses_libs: ["runtime-optional-y"],
+			sdk_version: "current",
+		}
+
 		// A library that has to use "provides_uses_lib", because:
 		//    - it is not an SDK library
 		//    - its library name is different from its module name
@@ -2307,6 +2562,8 @@
 				// statically linked component libraries should not pull their SDK libraries,
 				// so "fred" should not be added to class loader context
 				"fred.stubs",
+				"static-x",
+				"static-y",
 			],
 			uses_libs: [
 				"foo",
@@ -2353,11 +2610,8 @@
 	expectManifestFixerArgs := `--extract-native-libs=true ` +
 		`--uses-library qux ` +
 		`--uses-library quuz ` +
-		`--uses-library foo ` + // TODO(b/132357300): "foo" should not be passed to manifest_fixer
-		`--uses-library com.non.sdk.lib ` + // TODO(b/132357300): "com.non.sdk.lib" should not be passed to manifest_fixer
-		`--uses-library bar ` + // TODO(b/132357300): "bar" should not be passed to manifest_fixer
 		`--uses-library runtime-library`
-	android.AssertStringEquals(t, "manifest_fixer args", expectManifestFixerArgs, actualManifestFixerArgs)
+	android.AssertStringDoesContain(t, "manifest_fixer args", actualManifestFixerArgs, expectManifestFixerArgs)
 
 	// Test that all libraries are verified (library order matters).
 	verifyCmd := app.Rule("verify_uses_libraries").RuleParams.Command
@@ -2366,8 +2620,12 @@
 		`--uses-library qux ` +
 		`--uses-library quuz ` +
 		`--uses-library runtime-library ` +
+		`--uses-library runtime-required-x ` +
+		`--uses-library runtime-required-y ` +
 		`--optional-uses-library bar ` +
-		`--optional-uses-library baz `
+		`--optional-uses-library baz ` +
+		`--optional-uses-library runtime-optional-x ` +
+		`--optional-uses-library runtime-optional-y `
 	android.AssertStringDoesContain(t, "verify cmd args", verifyCmd, verifyArgs)
 
 	// Test that all libraries are verified for an APK (library order matters).
@@ -2387,7 +2645,11 @@
 		`PCL[/system/framework/foo.jar]#` +
 		`PCL[/system/framework/non-sdk-lib.jar]#` +
 		`PCL[/system/framework/bar.jar]#` +
-		`PCL[/system/framework/runtime-library.jar]`
+		`PCL[/system/framework/runtime-library.jar]#` +
+		`PCL[/system/framework/runtime-required-x.jar]#` +
+		`PCL[/system/framework/runtime-optional-x.jar]#` +
+		`PCL[/system/framework/runtime-required-y.jar]#` +
+		`PCL[/system/framework/runtime-optional-y.jar] `
 	android.AssertStringDoesContain(t, "dexpreopt app cmd args", cmd, w)
 
 	// Test conditional context for target SDK version 28.
@@ -2471,7 +2733,7 @@
 				PrepareForTestWithJavaSdkLibraryFiles,
 				FixtureWithLastReleaseApis("runtime-library", "foo", "bar"),
 				dexpreopt.FixtureSetBootJars("platform:foo"),
-				dexpreopt.FixtureSetUpdatableBootJars("platform:bar"),
+				dexpreopt.FixtureSetApexBootJars("platform:bar"),
 				dexpreopt.FixtureSetPreoptWithUpdatableBcp(test.with),
 			).RunTestWithBp(t, bp)
 
@@ -2559,116 +2821,6 @@
 	}
 }
 
-func TestEmbedNotice(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		PrepareForTestWithJavaDefaultModules,
-		cc.PrepareForTestWithCcDefaultModules,
-		genrule.PrepareForTestWithGenRuleBuildComponents,
-		android.MockFS{
-			"APP_NOTICE":     nil,
-			"GENRULE_NOTICE": nil,
-			"LIB_NOTICE":     nil,
-			"TOOL_NOTICE":    nil,
-		}.AddToFixture(),
-	).RunTestWithBp(t, `
-		android_app {
-			name: "foo",
-			srcs: ["a.java"],
-			static_libs: ["javalib"],
-			jni_libs: ["libjni"],
-			notice: "APP_NOTICE",
-			embed_notices: true,
-			sdk_version: "current",
-		}
-
-		// No embed_notice flag
-		android_app {
-			name: "bar",
-			srcs: ["a.java"],
-			jni_libs: ["libjni"],
-			notice: "APP_NOTICE",
-			sdk_version: "current",
-		}
-
-		// No NOTICE files
-		android_app {
-			name: "baz",
-			srcs: ["a.java"],
-			embed_notices: true,
-			sdk_version: "current",
-		}
-
-		cc_library {
-			name: "libjni",
-			system_shared_libs: [],
-			stl: "none",
-			notice: "LIB_NOTICE",
-			sdk_version: "current",
-		}
-
-		java_library {
-			name: "javalib",
-			srcs: [
-				":gen",
-			],
-			sdk_version: "current",
-		}
-
-		genrule {
-			name: "gen",
-			tools: ["gentool"],
-			out: ["gen.java"],
-			notice: "GENRULE_NOTICE",
-		}
-
-		java_binary_host {
-			name: "gentool",
-			srcs: ["b.java"],
-			notice: "TOOL_NOTICE",
-		}
-	`)
-
-	// foo has NOTICE files to process, and embed_notices is true.
-	foo := result.ModuleForTests("foo", "android_common")
-	// verify merge notices rule.
-	mergeNotices := foo.Rule("mergeNoticesRule")
-	noticeInputs := mergeNotices.Inputs.Strings()
-	// TOOL_NOTICE should be excluded as it's a host module.
-	if len(mergeNotices.Inputs) != 3 {
-		t.Errorf("number of input notice files: expected = 3, actual = %q", noticeInputs)
-	}
-	if !inList("APP_NOTICE", noticeInputs) {
-		t.Errorf("APP_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	if !inList("LIB_NOTICE", noticeInputs) {
-		t.Errorf("LIB_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	if !inList("GENRULE_NOTICE", noticeInputs) {
-		t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
-	}
-	// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
-	res := foo.Output("package-res.apk")
-	aapt2Flags := res.Args["flags"]
-	e := "-A out/soong/.intermediates/foo/android_common/NOTICE"
-	android.AssertStringDoesContain(t, "expected.apkPath", aapt2Flags, e)
-
-	// bar has NOTICE files to process, but embed_notices is not set.
-	bar := result.ModuleForTests("bar", "android_common")
-	res = bar.Output("package-res.apk")
-	aapt2Flags = res.Args["flags"]
-	e = "-A out/soong/.intermediates/bar/android_common/NOTICE"
-	android.AssertStringDoesNotContain(t, "bar shouldn't have the asset dir flag for NOTICE", aapt2Flags, e)
-
-	// baz's embed_notice is true, but it doesn't have any NOTICE files.
-	baz := result.ModuleForTests("baz", "android_common")
-	res = baz.Output("package-res.apk")
-	aapt2Flags = res.Args["flags"]
-	e = "-A out/soong/.intermediates/baz/android_common/NOTICE"
-	if strings.Contains(aapt2Flags, e) {
-		t.Errorf("baz shouldn't have the asset dir flag for NOTICE: %q", e)
-	}
-}
-
 func TestUncompressDex(t *testing.T) {
 	testCases := []struct {
 		name string
@@ -2823,3 +2975,97 @@
 		t.Errorf("App does not use library proguard config")
 	}
 }
+
+func TestTargetSdkVersionManifestFixer(t *testing.T) {
+	platform_sdk_codename := "Tiramisu"
+	testCases := []struct {
+		name                     string
+		targetSdkVersionInBp     string
+		targetSdkVersionExpected string
+		unbundledBuild           bool
+	}{
+		{
+			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion",
+			targetSdkVersionInBp:     "30",
+			targetSdkVersionExpected: "30",
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has targetSdkVersion",
+			targetSdkVersionInBp:     "30",
+			targetSdkVersionExpected: "30",
+			unbundledBuild:           true,
+		},
+		{
+			name:                     "Non-Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
+			targetSdkVersionInBp:     platform_sdk_codename,
+			targetSdkVersionExpected: platform_sdk_codename,
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has targetSdkVersion equal to platform_sdk_codename",
+			targetSdkVersionInBp:     platform_sdk_codename,
+			targetSdkVersionExpected: "10000",
+			unbundledBuild:           true,
+		},
+
+		{
+			name:                     "Non-Unbundled build: Android.bp has no targetSdkVersion",
+			targetSdkVersionExpected: platform_sdk_codename,
+			unbundledBuild:           false,
+		},
+		{
+			name:                     "Unbundled build: Android.bp has no targetSdkVersion",
+			targetSdkVersionExpected: "10000",
+			unbundledBuild:           true,
+		},
+	}
+	for _, testCase := range testCases {
+		bp := fmt.Sprintf(`
+			android_app {
+				name: "foo",
+				sdk_version: "current",
+				target_sdk_version: "%v",
+			}
+			`, testCase.targetSdkVersionInBp)
+		fixture := android.GroupFixturePreparers(
+			prepareForJavaTest,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				// explicitly set platform_sdk_codename to make the test deterministic
+				variables.Platform_sdk_codename = &platform_sdk_codename
+				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
+				// create a non-empty list if unbundledBuild==true
+				if testCase.unbundledBuild {
+					variables.Unbundled_build_apps = []string{"apex_a", "apex_b"}
+				}
+			}),
+		)
+
+		result := fixture.RunTestWithBp(t, bp)
+		foo := result.ModuleForTests("foo", "android_common")
+
+		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+	}
+}
+
+func TestAppMissingCertificateAllowMissingDependencies(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestWithAllowMissingDependencies,
+		android.PrepareForTestWithAndroidMk,
+	).RunTestWithBp(t, `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			certificate: ":missing_certificate",
+			sdk_version: "current",
+		}`)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	fooApk := foo.Output("foo.apk")
+	if fooApk.Rule != android.ErrorRule {
+		t.Fatalf("expected ErrorRule for foo.apk, got %s", fooApk.Rule.String())
+	}
+	android.AssertStringDoesContain(t, "expected error rule message", fooApk.Args["error"], "missing dependencies: missing_certificate\n")
+}
diff --git a/java/base.go b/java/base.go
index df70efb..7aa2814 100644
--- a/java/base.go
+++ b/java/base.go
@@ -155,6 +155,13 @@
 
 		// List of java_plugin modules that provide extra errorprone checks.
 		Extra_check_modules []string
+
+		// This property can be in 3 states. When set to true, errorprone will
+		// be run during the regular build. When set to false, errorprone will
+		// never be run. When unset, errorprone will be run when the RUN_ERROR_PRONE
+		// environment variable is true. Setting this to false will improve build
+		// performance more than adding -XepDisableAllChecks in javacflags.
+		Enabled *bool
 	}
 
 	Proto struct {
@@ -163,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"`
@@ -177,21 +187,30 @@
 // Properties that are specific to device modules. Host module factories should not add these when
 // constructing a new module.
 type DeviceProperties struct {
-	// if not blank, set to the version of the sdk to compile against.
-	// Defaults to compiling against the current platform.
+	// 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.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
 	Min_sdk_version *string
 
+	// if not blank, set the maximum version of the sdk that the compiled artifacts will run against.
+	// Defaults to empty string "". See sdk_version for possible values.
+	Max_sdk_version *string
+
 	// if not blank, set the targetSdkVersion in the AndroidManifest.xml.
-	// Defaults to sdk_version if not set.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
 	Target_sdk_version *string
 
 	// Whether to compile against the platform APIs instead of an SDK.
 	// 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 {
@@ -211,6 +230,12 @@
 		// whether to generate Binder#GetTransaction name method.
 		Generate_get_transaction_name *bool
 
+		// whether all interfaces should be annotated with required permissions.
+		Enforce_permissions *bool
+
+		// allowlist for interfaces that (temporarily) do not require annotation for permissions.
+		Enforce_permissions_exceptions []string `android:"path"`
+
 		// list of flags that will be passed to the AIDL compiler
 		Flags []string
 	}
@@ -229,9 +254,6 @@
 	// otherwise provides defaults libraries to add to the bootclasspath.
 	System_modules *string
 
-	// set the name of the output
-	Stem *string
-
 	IsSDKLibrary bool `blueprint:"mutated"`
 
 	// If true, generate the signature file of APK Signing Scheme V4, along side the signed APK file.
@@ -243,6 +265,15 @@
 	SyspropPublicStub string `blueprint:"mutated"`
 }
 
+// Device properties that can be overridden by overriding module (e.g. override_android_app)
+type OverridableDeviceProperties struct {
+	// set the name of the output. If not set, `name` is used.
+	// To override a module with this property set, overriding module might need to set this as well.
+	// Otherwise, both the overridden and the overriding modules will have the same output name, which
+	// can cause the duplicate output error.
+	Stem *string
+}
+
 // Functionality common to Module and Import
 //
 // It is embedded in Module so its functionality can be used by methods in Module
@@ -269,12 +300,94 @@
 	return false
 }
 
+// OptionalDexJarPath can be either unset, hold a valid path to a dex jar file,
+// or an invalid path describing the reason it is invalid.
+//
+// It is unset if a dex jar isn't applicable, i.e. no build rule has been
+// requested to create one.
+//
+// If a dex jar has been requested to be built then it is set, and it may be
+// either a valid android.Path, or invalid with a reason message. The latter
+// happens if the source that should produce the dex file isn't able to.
+//
+// E.g. it is invalid with a reason message if there is a prebuilt APEX that
+// could produce the dex jar through a deapexer module, but the APEX isn't
+// installable so doing so wouldn't be safe.
+type OptionalDexJarPath struct {
+	isSet bool
+	path  android.OptionalPath
+}
+
+// IsSet returns true if a path has been set, either invalid or valid.
+func (o OptionalDexJarPath) IsSet() bool {
+	return o.isSet
+}
+
+// Valid returns true if there is a path that is valid.
+func (o OptionalDexJarPath) Valid() bool {
+	return o.isSet && o.path.Valid()
+}
+
+// Path returns the valid path, or panics if it's either not set or is invalid.
+func (o OptionalDexJarPath) Path() android.Path {
+	if !o.isSet {
+		panic("path isn't set")
+	}
+	return o.path.Path()
+}
+
+// PathOrNil returns the path if it's set and valid, or else nil.
+func (o OptionalDexJarPath) PathOrNil() android.Path {
+	if o.Valid() {
+		return o.Path()
+	}
+	return nil
+}
+
+// InvalidReason returns the reason for an invalid path, which is never "". It
+// returns "" for an unset or valid path.
+func (o OptionalDexJarPath) InvalidReason() string {
+	if !o.isSet {
+		return ""
+	}
+	return o.path.InvalidReason()
+}
+
+func (o OptionalDexJarPath) String() string {
+	if !o.isSet {
+		return "<unset>"
+	}
+	return o.path.String()
+}
+
+// makeUnsetDexJarPath returns an unset OptionalDexJarPath.
+func makeUnsetDexJarPath() OptionalDexJarPath {
+	return OptionalDexJarPath{isSet: false}
+}
+
+// makeDexJarPathFromOptionalPath returns an OptionalDexJarPath that is set with
+// the given OptionalPath, which may be valid or invalid.
+func makeDexJarPathFromOptionalPath(path android.OptionalPath) OptionalDexJarPath {
+	return OptionalDexJarPath{isSet: true, path: path}
+}
+
+// makeDexJarPathFromPath returns an OptionalDexJarPath that is set with the
+// valid given path. It returns an unset OptionalDexJarPath if the given path is
+// nil.
+func makeDexJarPathFromPath(path android.Path) OptionalDexJarPath {
+	if path == nil {
+		return makeUnsetDexJarPath()
+	}
+	return makeDexJarPathFromOptionalPath(android.OptionalPathForPath(path))
+}
+
 // Module contains the properties and members used by all java module types
 type Module struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
 	android.ApexModuleBase
 	android.SdkBase
+	android.BazelModuleBase
 
 	// Functionality common to Module and Import.
 	embeddableInModuleAndImport
@@ -283,6 +396,8 @@
 	protoProperties  android.ProtoProperties
 	deviceProperties DeviceProperties
 
+	overridableDeviceProperties OverridableDeviceProperties
+
 	// jar file containing header classes including static library dependencies, suitable for
 	// inserting into the bootclasspath/classpath of another compile
 	headerJarFile android.Path
@@ -303,7 +418,7 @@
 	implementationAndResourcesJar android.Path
 
 	// output file containing classes.dex and resources
-	dexJarFile android.Path
+	dexJarFile OptionalDexJarPath
 
 	// output file containing uninstrumented classes that will be instrumented by jacoco
 	jacocoReportClassesFile android.Path
@@ -312,13 +427,17 @@
 	outputFile       android.Path
 	extraOutputFiles android.Paths
 
-	exportAidlIncludeDirs android.Paths
+	exportAidlIncludeDirs     android.Paths
+	ignoredAidlPermissionList android.Paths
 
 	logtagsSrcs android.Paths
 
 	// installed file for binary dependency
 	installFile android.Path
 
+	// installed file for hostdex copy
+	hostdexInstallFile android.InstallPath
+
 	// list of .java files and srcjars that was passed to javac
 	compiledJavaSrcs android.Paths
 	compiledSrcJars  android.Paths
@@ -345,9 +464,6 @@
 	// expanded Jarjar_rules
 	expandJarjarRules android.Path
 
-	// list of additional targets for checkbuild
-	additionalCheckedModules android.Paths
-
 	// Extra files generated by the module type to be added as java resources.
 	extraResources android.Paths
 
@@ -367,6 +483,9 @@
 
 	sdkVersion    android.SdkSpec
 	minSdkVersion android.SdkSpec
+	maxSdkVersion android.SdkSpec
+
+	sourceExtensions []string
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -375,7 +494,7 @@
 		return nil
 	}
 	if sdkVersion.Kind == android.SdkCorePlatform {
-		if useLegacyCorePlatformApiByName(j.BaseModuleName()) {
+		if useLegacyCorePlatformApi(ctx, j.BaseModuleName()) {
 			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
 		} else {
 			// Treat stable core platform as stable.
@@ -417,9 +536,9 @@
 		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
 		sdkVersionSpecified := sc.SdkVersion(ctx).Specified()
 		if usePlatformAPI && sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
+			ctx.PropertyErrorf("platform_apis", "This module has conflicting settings. sdk_version is not empty, which means this module cannot use platform APIs. However platform_apis is set to true.")
 		} else if !usePlatformAPI && !sdkVersionSpecified {
-			ctx.PropertyErrorf("platform_apis", "platform_apis must be true when sdk_version is empty.")
+			ctx.PropertyErrorf("platform_apis", "This module has conflicting settings. sdk_version is empty, which means that this module is build against platform APIs. However platform_apis is not set to true")
 		}
 
 	}
@@ -437,6 +556,7 @@
 	j.addHostProperties()
 	j.AddProperties(
 		&j.deviceProperties,
+		&j.overridableDeviceProperties,
 		&j.dexer.dexProperties,
 		&j.dexpreoptProperties,
 		&j.linter.properties,
@@ -488,7 +608,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())
 }
@@ -524,6 +645,13 @@
 	return j.SdkVersion(ctx)
 }
 
+func (j *Module) MaxSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	maxSdkVersion := proptools.StringDefault(j.deviceProperties.Max_sdk_version, "")
+	// SdkSpecFrom returns SdkSpecPrivate for this, which may be confusing.
+	// TODO(b/208456999): ideally MaxSdkVersion should be an ApiLevel and not SdkSpec.
+	return android.SdkSpecFrom(ctx, maxSdkVersion)
+}
+
 func (j *Module) MinSdkVersionString() string {
 	return j.minSdkVersion.Raw
 }
@@ -598,7 +726,10 @@
 		if dep != nil {
 			if component, ok := dep.(SdkLibraryComponentDependency); ok {
 				if lib := component.OptionalSdkLibraryImplementation(); lib != nil {
-					ctx.AddVariationDependencies(nil, usesLibTag, *lib)
+					// 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)
+					ctx.AddVariationDependencies(nil, tag, *lib)
 				}
 			}
 		}
@@ -633,6 +764,11 @@
 	} else if j.shouldInstrumentStatic(ctx) {
 		ctx.AddVariationDependencies(nil, staticLibTag, "jacocoagent")
 	}
+
+	if j.useCompose() {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), kotlinPluginTag,
+			"androidx.compose.compiler_compiler-hosted")
+	}
 }
 
 func hasSrcExt(srcs []string, ext string) bool {
@@ -649,6 +785,17 @@
 	return hasSrcExt(j.properties.Srcs, ext)
 }
 
+func (j *Module) individualAidlFlags(ctx android.ModuleContext, aidlFile android.Path) string {
+	var flags string
+
+	if Bool(j.deviceProperties.Aidl.Enforce_permissions) {
+		if !android.InList(aidlFile.String(), j.ignoredAidlPermissionList.Strings()) {
+			flags = "-Wmissing-permission-annotation -Werror"
+		}
+	}
+	return flags
+}
+
 func (j *Module) aidlFlags(ctx android.ModuleContext, aidlPreprocess android.OptionalPath,
 	aidlIncludeDirs android.Paths) (string, android.Paths) {
 
@@ -691,6 +838,14 @@
 		flags = append(flags, "--transaction_names")
 	}
 
+	if Bool(j.deviceProperties.Aidl.Enforce_permissions) {
+		exceptions := j.deviceProperties.Aidl.Enforce_permissions_exceptions
+		j.ignoredAidlPermissionList = android.PathsForModuleSrcExcludes(ctx, exceptions, nil)
+	}
+
+	aidlMinSdkVersion := j.MinSdkVersion(ctx).ApiLevel.String()
+	flags = append(flags, "--min_sdk_version="+aidlMinSdkVersion)
+
 	return strings.Join(flags, " "), deps
 }
 
@@ -701,7 +856,8 @@
 	// javaVersion flag.
 	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), android.SdkContext(j))
 
-	if ctx.Config().RunErrorProne() {
+	epEnabled := j.properties.Errorprone.Enabled
+	if (ctx.Config().RunErrorProne() && epEnabled == nil) || Bool(epEnabled) {
 		if config.ErrorProneClasspath == nil && ctx.Config().TestProductVariables == nil {
 			ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
 		}
@@ -712,7 +868,7 @@
 		}
 		errorProneFlags = append(errorProneFlags, j.properties.Errorprone.Javacflags...)
 
-		flags.errorProneExtraJavacFlags = "${config.ErrorProneFlags} " +
+		flags.errorProneExtraJavacFlags = "${config.ErrorProneHeapFlags} ${config.ErrorProneFlags} " +
 			"'" + strings.Join(errorProneFlags, " ") + "'"
 		flags.errorProneProcessorPath = classpath(android.PathsForSource(ctx, config.ErrorProneClasspath))
 	}
@@ -720,6 +876,7 @@
 	// classpath
 	flags.bootClasspath = append(flags.bootClasspath, deps.bootClasspath...)
 	flags.classpath = append(flags.classpath, deps.classpath...)
+	flags.dexClasspath = append(flags.dexClasspath, deps.dexClasspath...)
 	flags.java9Classpath = append(flags.java9Classpath, deps.java9Classpath...)
 	flags.processorPath = append(flags.processorPath, deps.processorPath...)
 	flags.errorProneProcessorPath = append(flags.errorProneProcessorPath, deps.errorProneProcessorPath...)
@@ -780,7 +937,7 @@
 			// Manually specify build directory in case it is not under the repo root.
 			// (javac doesn't seem to expand into symbolic links when searching for patch-module targets, so
 			// just adding a symlink under the root doesn't help.)
-			patchPaths := []string{".", ctx.Config().BuildDir()}
+			patchPaths := []string{".", ctx.Config().SoongOutDir()}
 
 			// b/150878007
 			//
@@ -832,6 +989,14 @@
 	return flags
 }
 
+func (j *Module) AddJSONData(d *map[string]interface{}) {
+	(&j.ModuleBase).AddJSONData(d)
+	(*d)["Java"] = map[string]interface{}{
+		"SourceExtensions": j.sourceExtensions,
+	}
+
+}
+
 func (j *Module) compile(ctx android.ModuleContext, aaptSrcJar android.Path) {
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.deviceProperties.Aidl.Export_include_dirs)
 
@@ -841,7 +1006,14 @@
 	if flags.javaVersion.usesJavaModules() {
 		j.properties.Srcs = append(j.properties.Srcs, j.properties.Openjdk9.Srcs...)
 	}
+
 	srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs)
+	j.sourceExtensions = []string{}
+	for _, ext := range []string{".kt", ".proto", ".aidl", ".java", ".logtags"} {
+		if hasSrcExt(srcFiles.Strings(), ext) {
+			j.sourceExtensions = append(j.sourceExtensions, ext)
+		}
+	}
 	if hasSrcExt(srcFiles.Strings(), ".proto") {
 		flags = protoFlags(ctx, &j.properties, &j.protoProperties, flags)
 	}
@@ -862,6 +1034,7 @@
 	if aaptSrcJar != nil {
 		srcJars = append(srcJars, aaptSrcJar)
 	}
+	srcFiles = srcFiles.FilterOutByExt(".srcjar")
 
 	if j.properties.Jarjar_rules != nil {
 		j.expandJarjarRules = android.PathForModuleSrc(ctx, *j.properties.Jarjar_rules)
@@ -879,18 +1052,30 @@
 		}
 	}
 
+	// We don't currently run annotation processors in turbine, which means we can't use turbine
+	// generated header jars when an annotation processor that generates API is enabled.  One
+	// exception (handled further below) is when kotlin sources are enabled, in which case turbine
+	//  is used to run all of the annotation processors.
+	disableTurbine := deps.disableTurbine
+
 	// Collect .java files for AIDEGen
 	j.expandIDEInfoCompiledSrcs = append(j.expandIDEInfoCompiledSrcs, uniqueSrcFiles.Strings()...)
 
 	var kotlinJars android.Paths
+	var kotlinHeaderJars android.Paths
 
 	if srcFiles.HasExt(".kt") {
+		// When using kotlin sources turbine is used to generate annotation processor sources,
+		// including for annotation processors that generate API, so we can use turbine for
+		// java sources too.
+		disableTurbine = false
+
 		// user defined kotlin flags.
 		kotlincFlags := j.properties.Kotlincflags
 		CheckKotlincFlags(ctx, kotlincFlags)
 
-		// Dogfood the JVM_IR backend.
-		kotlincFlags = append(kotlincFlags, "-Xuse-ir")
+		// Workaround for KT-46512
+		kotlincFlags = append(kotlincFlags, "-Xsam-conversions=class")
 
 		// If there are kotlin files, compile them first but pass all the kotlin and java files
 		// kotlinc will use the java files to resolve types referenced by the kotlin files, but
@@ -899,6 +1084,12 @@
 		if ctx.Device() {
 			kotlincFlags = append(kotlincFlags, "-no-jdk")
 		}
+
+		for _, plugin := range deps.kotlinPlugins {
+			kotlincFlags = append(kotlincFlags, "-Xplugin="+plugin.String())
+		}
+		flags.kotlincDeps = append(flags.kotlincDeps, deps.kotlinPlugins...)
+
 		if len(kotlincFlags) > 0 {
 			// optimization.
 			ctx.Variable(pctx, "kotlincFlags", strings.Join(kotlincFlags, " "))
@@ -916,6 +1107,8 @@
 		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...)
 
@@ -932,18 +1125,24 @@
 		}
 
 		kotlinJar := android.PathForModuleOut(ctx, "kotlin", jarName)
-		kotlinCompile(ctx, kotlinJar, kotlinSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
+		kotlinHeaderJar := android.PathForModuleOut(ctx, "kotlin_headers", jarName)
+		kotlinCompile(ctx, kotlinJar, kotlinHeaderJar, kotlinSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
 		if ctx.Failed() {
 			return
 		}
 
 		// Make javac rule depend on the kotlinc rule
-		flags.classpath = append(flags.classpath, kotlinJar)
+		flags.classpath = append(classpath{kotlinHeaderJar}, flags.classpath...)
 
 		kotlinJars = append(kotlinJars, kotlinJar)
+		kotlinHeaderJars = append(kotlinHeaderJars, kotlinHeaderJar)
+
 		// Jar kotlin classes into the final jar after javac
 		if BoolDefault(j.properties.Static_kotlin_stdlib, true) {
 			kotlinJars = append(kotlinJars, deps.kotlinStdlib...)
+			kotlinHeaderJars = append(kotlinHeaderJars, deps.kotlinStdlib...)
+		} else {
+			flags.dexClasspath = append(flags.dexClasspath, deps.kotlinStdlib...)
 		}
 	}
 
@@ -954,8 +1153,8 @@
 	j.compiledSrcJars = srcJars
 
 	enableSharding := false
-	var headerJarFileWithoutJarjar android.Path
-	if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !deps.disableTurbine {
+	var headerJarFileWithoutDepsOrJarjar android.Path
+	if ctx.Device() && !ctx.Config().IsEnvFalse("TURBINE_ENABLED") && !disableTurbine {
 		if j.properties.Javac_shard_size != nil && *(j.properties.Javac_shard_size) > 0 {
 			enableSharding = true
 			// Formerly, there was a check here that prevented annotation processors
@@ -964,27 +1163,38 @@
 			// allow for the use of annotation processors that do function correctly
 			// with sharding enabled. See: b/77284273.
 		}
-		headerJarFileWithoutJarjar, j.headerJarFile =
-			j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinJars)
+		headerJarFileWithoutDepsOrJarjar, j.headerJarFile =
+			j.compileJavaHeader(ctx, uniqueSrcFiles, srcJars, deps, flags, jarName, kotlinHeaderJars)
 		if ctx.Failed() {
 			return
 		}
 	}
 	if len(uniqueSrcFiles) > 0 || len(srcJars) > 0 {
 		var extraJarDeps android.Paths
-		if ctx.Config().RunErrorProne() {
-			// If error-prone is enabled, add an additional rule to compile the java files into
-			// a separate set of classes (so that they don't overwrite the normal ones and require
-			// a rebuild when error-prone is turned off).
-			// TODO(ccross): Once we always compile with javac9 we may be able to conditionally
-			//    enable error-prone without affecting the output class files.
+		if Bool(j.properties.Errorprone.Enabled) {
+			// If error-prone is enabled, enable errorprone flags on the regular
+			// build.
+			flags = enableErrorproneFlags(flags)
+		} else if ctx.Config().RunErrorProne() && j.properties.Errorprone.Enabled == nil {
+			// Otherwise, if the RUN_ERROR_PRONE environment variable is set, create
+			// a new jar file just for compiling with the errorprone compiler to.
+			// This is because we don't want to cause the java files to get completely
+			// rebuilt every time the state of the RUN_ERROR_PRONE variable changes.
+			// We also don't want to run this if errorprone is enabled by default for
+			// this module, or else we could have duplicated errorprone messages.
+			errorproneFlags := enableErrorproneFlags(flags)
 			errorprone := android.PathForModuleOut(ctx, "errorprone", jarName)
-			RunErrorProne(ctx, errorprone, uniqueSrcFiles, srcJars, flags)
+
+			transformJavaToClasses(ctx, errorprone, -1, uniqueSrcFiles, srcJars, errorproneFlags, nil,
+				"errorprone", "errorprone")
+
 			extraJarDeps = append(extraJarDeps, errorprone)
 		}
 
 		if enableSharding {
-			flags.classpath = append(flags.classpath, headerJarFileWithoutJarjar)
+			if headerJarFileWithoutDepsOrJarjar != nil {
+				flags.classpath = append(classpath{headerJarFileWithoutDepsOrJarjar}, flags.classpath...)
+			}
 			shardSize := int(*(j.properties.Javac_shard_size))
 			var shardSrcs []android.Paths
 			if len(uniqueSrcFiles) > 0 {
@@ -1152,10 +1362,25 @@
 
 	// Check package restrictions if necessary.
 	if len(j.properties.Permitted_packages) > 0 {
-		// Check packages and copy to package-checked file.
+		// Time stamp file created by the package check rule.
 		pkgckFile := android.PathForModuleOut(ctx, "package-check.stamp")
+
+		// Create a rule to copy the output jar to another path and add a validate dependency that
+		// will check that the jar only contains the permitted packages. The new location will become
+		// the output file of this module.
+		inputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "package-check", jarName).OutputPath
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  inputFile,
+			Output: outputFile,
+			// Make sure that any dependency on the output file will cause ninja to run the package check
+			// rule.
+			Validation: pkgckFile,
+		})
+
+		// Check packages and create a timestamp file when complete.
 		CheckJarPackages(ctx, pkgckFile, outputFile, j.properties.Permitted_packages)
-		j.additionalCheckedModules = append(j.additionalCheckedModules, pkgckFile)
 
 		if ctx.Failed() {
 			return
@@ -1212,7 +1437,7 @@
 			}
 			// Dex compilation
 			var dexOutputFile android.OutputPath
-			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(ctx), outputFile, jarName)
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(ctx), implementationAndResourcesJar, jarName)
 			if ctx.Failed() {
 				return
 			}
@@ -1233,12 +1458,13 @@
 			}
 
 			// Initialize the hiddenapi structure.
-			j.initHiddenAPI(ctx, dexOutputFile, j.implementationJarFile, j.dexProperties.Uncompress_dex)
+
+			j.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), j.implementationJarFile, j.dexProperties.Uncompress_dex)
 
 			// Encode hidden API flags in dex file, if needed.
 			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
 
-			j.dexJarFile = dexOutputFile
+			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 
 			// Dexpreopting
 			j.dexpreopt(ctx, dexOutputFile)
@@ -1248,7 +1474,7 @@
 			// There is no code to compile into a dex jar, make sure the resources are propagated
 			// to the APK if this is an app.
 			outputFile = implementationAndResourcesJar
-			j.dexJarFile = j.resourceJar
+			j.dexJarFile = makeDexJarPathFromPath(j.resourceJar)
 		}
 
 		if ctx.Failed() {
@@ -1259,11 +1485,11 @@
 	}
 
 	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)
 			}
 		}
 
@@ -1272,9 +1498,9 @@
 		j.linter.srcJars = srcJars
 		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"
@@ -1304,6 +1530,25 @@
 	j.outputFile = outputFile.WithoutRel()
 }
 
+func (j *Module) useCompose() bool {
+	return android.InList("androidx.compose.runtime_runtime", j.properties.Static_libs)
+}
+
+// Returns a copy of the supplied flags, but with all the errorprone-related
+// fields copied to the regular build's fields.
+func enableErrorproneFlags(flags javaBuilderFlags) javaBuilderFlags {
+	flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
+
+	if len(flags.errorProneExtraJavacFlags) > 0 {
+		if len(flags.javacFlags) > 0 {
+			flags.javacFlags += " " + flags.errorProneExtraJavacFlags
+		} else {
+			flags.javacFlags = flags.errorProneExtraJavacFlags
+		}
+	}
+	return flags
+}
+
 func (j *Module) compileJavaClasses(ctx android.ModuleContext, jarName string, idx int,
 	srcFiles, srcJars android.Paths, flags javaBuilderFlags, extraJarDeps android.Paths) android.WritablePath {
 
@@ -1352,7 +1597,7 @@
 
 func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars android.Paths,
 	deps deps, flags javaBuilderFlags, jarName string,
-	extraJars android.Paths) (headerJar, jarjarHeaderJar android.Path) {
+	extraJars android.Paths) (headerJar, jarjarAndDepsHeaderJar android.Path) {
 
 	var jars android.Paths
 	if len(srcFiles) > 0 || len(srcJars) > 0 {
@@ -1363,6 +1608,7 @@
 			return nil, nil
 		}
 		jars = append(jars, turbineJar)
+		headerJar = turbineJar
 	}
 
 	jars = append(jars, extraJars...)
@@ -1376,20 +1622,19 @@
 	combinedJar := android.PathForModuleOut(ctx, "turbine-combined", jarName)
 	TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{},
 		false, nil, []string{"META-INF/TRANSITIVE"})
-	headerJar = combinedJar
-	jarjarHeaderJar = combinedJar
+	jarjarAndDepsHeaderJar = combinedJar
 
 	if j.expandJarjarRules != nil {
 		// Transform classes.jar into classes-jarjar.jar
 		jarjarFile := android.PathForModuleOut(ctx, "turbine-jarjar", jarName)
-		TransformJarJar(ctx, jarjarFile, headerJar, j.expandJarjarRules)
-		jarjarHeaderJar = jarjarFile
+		TransformJarJar(ctx, jarjarFile, jarjarAndDepsHeaderJar, j.expandJarjarRules)
+		jarjarAndDepsHeaderJar = jarjarFile
 		if ctx.Failed() {
 			return nil, nil
 		}
 	}
 
-	return headerJar, jarjarHeaderJar
+	return headerJar, jarjarAndDepsHeaderJar
 }
 
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
@@ -1419,7 +1664,7 @@
 	return android.Paths{j.implementationJarFile}
 }
 
-func (j *Module) DexJarBuildPath() android.Path {
+func (j *Module) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
@@ -1453,6 +1698,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 {
@@ -1473,8 +1720,7 @@
 }
 
 // Implements android.ApexModule
-func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
-	sdkVersion android.ApiLevel) error {
+func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
 	sdkSpec := j.MinSdkVersion(ctx)
 	if !sdkSpec.Specified() {
 		return fmt.Errorf("min_sdk_version is not specified")
@@ -1482,18 +1728,14 @@
 	if sdkSpec.Kind == android.SdkCore {
 		return nil
 	}
-	ver, err := sdkSpec.EffectiveVersion(ctx)
-	if err != nil {
-		return err
-	}
-	if ver.GreaterThan(sdkVersion) {
-		return fmt.Errorf("newer SDK(%v)", ver)
+	if sdkSpec.ApiLevel.GreaterThan(sdkVersion) {
+		return fmt.Errorf("newer SDK(%v)", sdkSpec.ApiLevel)
 	}
 	return nil
 }
 
 func (j *Module) Stem() string {
-	return proptools.StringDefault(j.deviceProperties.Stem, j.Name())
+	return proptools.StringDefault(j.overridableDeviceProperties.Stem, j.Name())
 }
 
 func (j *Module) JacocoReportClassesFile() android.Path {
@@ -1627,6 +1869,7 @@
 		} else if sdkDep.useFiles {
 			// sdkDep.jar is actually equivalent to turbine header.jar.
 			deps.classpath = append(deps.classpath, sdkDep.jars...)
+			deps.dexClasspath = append(deps.dexClasspath, sdkDep.jars...)
 			deps.aidlPreprocess = sdkDep.aidl
 		} else {
 			deps.aidlPreprocess = sdkDep.aidl
@@ -1651,7 +1894,9 @@
 		if dep, ok := module.(SdkLibraryDependency); ok {
 			switch tag {
 			case libTag:
-				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))...)
+				depHeaderJars := dep.SdkHeaderJars(ctx, j.SdkVersion(ctx))
+				deps.classpath = append(deps.classpath, depHeaderJars...)
+				deps.dexClasspath = append(deps.dexClasspath, depHeaderJars...)
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
 			}
@@ -1670,6 +1915,7 @@
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
 			case libTag, instrumentationForTag:
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
+				deps.dexClasspath = append(deps.dexClasspath, dep.HeaderJars...)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
 				addPlugins(&deps, dep.ExportedPlugins, dep.ExportedPluginClasses...)
 				deps.disableTurbine = deps.disableTurbine || dep.ExportedPluginDisableTurbine
@@ -1723,6 +1969,8 @@
 				deps.kotlinStdlib = append(deps.kotlinStdlib, dep.HeaderJars...)
 			case kotlinAnnotationsTag:
 				deps.kotlinAnnotations = dep.HeaderJars
+			case kotlinPluginTag:
+				deps.kotlinPlugins = append(deps.kotlinPlugins, dep.ImplementationAndResourcesJars...)
 			case syspropPublicStubDepTag:
 				// This is a sysprop implementation library, forward the JavaInfoProvider from
 				// the corresponding sysprop public stub library as SyspropPublicStubInfoProvider.
@@ -1735,6 +1983,7 @@
 			case libTag:
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
+				deps.dexClasspath = append(deps.classpath, dep.Srcs()...)
 			case staticLibTag:
 				checkProducesJars(ctx, dep)
 				deps.classpath = append(deps.classpath, dep.Srcs()...)
@@ -1756,6 +2005,9 @@
 				sm := module.(SystemModulesProvider)
 				outputDir, outputDeps := sm.OutputDirAndDeps()
 				deps.systemModules = &systemModules{outputDir, outputDeps}
+
+			case instrumentationForTag:
+				ctx.PropertyErrorf("instrumentation_for", "dependency %q of type %q does not provide JavaInfo so is unsuitable for use with this property", ctx.OtherModuleName(module), ctx.OtherModuleType(module))
 			}
 		}
 
@@ -1785,3 +2037,16 @@
 }
 
 var _ ModuleWithStem = (*Module)(nil)
+
+func (j *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	switch ctx.ModuleType() {
+	case "java_library", "java_library_host", "java_library_static":
+		if lib, ok := ctx.Module().(*Library); ok {
+			javaLibraryBp2Build(ctx, lib)
+		}
+	case "java_binary_host":
+		if binary, ok := ctx.Module().(*Binary); ok {
+			javaBinaryHostBp2Build(ctx, binary)
+		}
+	}
+}
diff --git a/java/boot_jars.go b/java/boot_jars.go
index 86ebe36..5d40ec3 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -31,13 +31,18 @@
 // buildRuleForBootJarsPackageCheck generates the build rule to perform the boot jars package
 // check.
 func buildRuleForBootJarsPackageCheck(ctx android.ModuleContext, bootDexJarByModule bootDexJarByModule) {
+	bootDexJars := bootDexJarByModule.bootDexJarsWithoutCoverage()
+	if len(bootDexJars) == 0 {
+		return
+	}
+
 	timestamp := android.PathForOutput(ctx, "boot-jars-package-check/stamp")
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("check_boot_jars").
 		Input(ctx.Config().HostToolPath(ctx, "dexdump")).
 		Input(android.PathForSource(ctx, "build/soong/scripts/check_boot_jars/package_allowed_list.txt")).
-		Inputs(bootDexJarByModule.bootDexJarsWithoutCoverage()).
+		Inputs(bootDexJars).
 		Text("&& touch").Output(timestamp)
 	rule.Build("boot_jars_package_check", "check boot jar packages")
 
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index d754fe6..52ce77d 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -95,15 +95,6 @@
 	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
 		ctx.AddVariationDependencies(variations, tag, prebuiltName)
 		addedDep = true
-	} else if ctx.Config().AlwaysUsePrebuiltSdks() && len(variations) > 0 {
-		// TODO(b/179354495): Remove this code path once the Android build has been fully migrated to
-		//  use bootclasspath_fragment properly.
-		// Some prebuilt java_sdk_library modules do not yet have an APEX variations so try and add a
-		// dependency on the non-APEX variant.
-		if ctx.OtherModuleDependencyVariantExists(nil, prebuiltName) {
-			ctx.AddVariationDependencies(nil, tag, prebuiltName)
-			addedDep = true
-		}
 	}
 
 	// If no appropriate variant existing for this, so no dependency could be added, then it is an
@@ -144,6 +135,8 @@
 
 // ApexVariantReference specifies a particular apex variant of a module.
 type ApexVariantReference struct {
+	android.BpPrintableBase
+
 	// The name of the module apex variant, i.e. the apex containing the module variant.
 	//
 	// If this is not specified then it defaults to "platform" which will cause a dependency to be
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index a039964..9600935 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"io"
 	"path/filepath"
 	"reflect"
 	"strings"
@@ -89,7 +90,7 @@
 
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
 var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
-var _ android.SdkMemberTypeDependencyTag = bootclasspathFragmentContentDepTag
+var _ android.SdkMemberDependencyTag = bootclasspathFragmentContentDepTag
 var _ android.CopyDirectlyInAnyApexTag = bootclasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = bootclasspathFragmentContentDepTag
 
@@ -139,6 +140,87 @@
 	BootclasspathFragmentsDepsProperties
 }
 
+type HiddenApiPackageProperties struct {
+	Hidden_api struct {
+		// Contains prefixes of a package hierarchy that is provided solely by this
+		// bootclasspath_fragment.
+		//
+		// This affects the signature patterns file that is used to select the subset of monolithic
+		// hidden API flags. See split_packages property for more details.
+		Package_prefixes []string
+
+		// A list of individual packages that are provided solely by this
+		// bootclasspath_fragment but which cannot be listed in package_prefixes
+		// because there are sub-packages which are provided by other modules.
+		//
+		// This should only be used for legacy packages. New packages should be
+		// covered by a package prefix.
+		Single_packages []string
+
+		// The list of split packages provided by this bootclasspath_fragment.
+		//
+		// A split package is one that contains classes which are provided by multiple
+		// bootclasspath_fragment modules.
+		//
+		// This defaults to "*" - which treats all packages as being split. A module that has no split
+		// packages must specify an empty list.
+		//
+		// This affects the signature patterns file that is generated by a bootclasspath_fragment and
+		// used to select the subset of monolithic hidden API flags against which the flags generated
+		// by the bootclasspath_fragment are compared.
+		//
+		// The signature patterns file selects the subset of monolithic hidden API flags using a number
+		// of patterns, i.e.:
+		// * The qualified name (including package) of an outermost class, e.g. java/lang/Character.
+		//   This selects all the flags for all the members of this class and any nested classes.
+		// * A package wildcard, e.g. java/lang/*. This selects all the flags for all the members of all
+		//   the classes in this package (but not in sub-packages).
+		// * A recursive package wildcard, e.g. java/**. This selects all the flags for all the members
+		//   of all the classes in this package and sub-packages.
+		//
+		// The signature patterns file is constructed as follows:
+		// * All the signatures are retrieved from the all-flags.csv file.
+		// * The member and inner class names are removed.
+		// * If a class is in a split package then that is kept, otherwise the class part is removed
+		//   and replaced with a wildcard, i.e. *.
+		// * If a package matches a package prefix then the package is removed.
+		// * All the package prefixes are added with a recursive wildcard appended to each, i.e. **.
+		// * The resulting patterns are sorted.
+		//
+		// So, by default (i.e. without specifying any package_prefixes or split_packages) the signature
+		// patterns is a list of class names, because there are no package packages and all packages are
+		// assumed to be split.
+		//
+		// If any split packages are specified then only those packages are treated as split and all
+		// other packages are treated as belonging solely to the bootclasspath_fragment and so they use
+		// wildcard package patterns.
+		//
+		// So, if an empty list of split packages is specified then the signature patterns file just
+		// includes a wildcard package pattern for every package provided by the bootclasspath_fragment.
+		//
+		// If split_packages are specified and a package that is split is not listed then it could lead
+		// to build failures as it will select monolithic flags that are generated by another
+		// bootclasspath_fragment to compare against the flags provided by this fragment. The latter
+		// will obviously not contain those flags and that can cause the comparison and build to fail.
+		//
+		// If any package prefixes are specified then any matching packages are removed from the
+		// signature patterns and replaced with a single recursive package pattern.
+		//
+		// It is not strictly necessary to specify either package_prefixes or split_packages as the
+		// defaults will produce a valid set of signature patterns. However, those patterns may include
+		// implementation details, e.g. names of implementation classes or packages, which will be
+		// exported to the sdk snapshot in the signature patterns file. That is something that should be
+		// avoided where possible. Specifying package_prefixes and split_packages allows those
+		// implementation details to be excluded from the snapshot.
+		Split_packages []string
+	}
+}
+
+type SourceOnlyBootclasspathProperties struct {
+	HiddenApiPackageProperties
+	Coverage HiddenApiPackageProperties
+}
+
 type BootclasspathFragmentModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
@@ -146,6 +228,16 @@
 	ClasspathFragmentBase
 
 	properties bootclasspathFragmentProperties
+
+	sourceOnlyProperties SourceOnlyBootclasspathProperties
+
+	// Collect the module directory for IDE info in java/jdeps.go.
+	modulePaths []string
+
+	// Installs for on-device boot image files. This list has entries only if the installs should be
+	// handled by Make (e.g., the boot image should be installed on the system partition, rather than
+	// in the APEX).
+	bootImageDeviceInstalls []dexpreopterInstall
 }
 
 // commonBootclasspathFragment defines the methods that are implemented by both source and prebuilt
@@ -177,7 +269,7 @@
 
 func bootclasspathFragmentFactory() android.Module {
 	m := &BootclasspathFragmentModule{}
-	m.AddProperties(&m.properties)
+	m.AddProperties(&m.properties, &m.sourceOnlyProperties)
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, BOOTCLASSPATH)
@@ -192,6 +284,12 @@
 				ctx.PropertyErrorf("coverage", "error trying to append coverage specific properties: %s", err)
 				return
 			}
+
+			err = proptools.AppendProperties(&m.sourceOnlyProperties.HiddenApiPackageProperties, &m.sourceOnlyProperties.Coverage, nil)
+			if err != nil {
+				ctx.PropertyErrorf("coverage", "error trying to append hidden api coverage specific properties: %s", err)
+				return
+			}
 		}
 
 		// Initialize the contents property from the image_name.
@@ -314,9 +412,19 @@
 	// Map from arch type to the boot image files.
 	bootImageFilesByArch bootImageFilesByArch
 
+	// True if the boot image should be installed in the APEX.
+	shouldInstallBootImageInApex bool
+
 	// Map from the base module name (without prebuilt_ prefix) of a fragment's contents module to the
 	// hidden API encoded dex jar path.
 	contentModuleDexJarPaths bootDexJarByModule
+
+	// Path to the image profile file on host (or empty, if profile is not generated).
+	profilePathOnHost android.Path
+
+	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
+	// needed.
+	profileInstallPathInApex string
 }
 
 func (i BootclasspathFragmentApexContentInfo) Modules() android.ConfiguredJarList {
@@ -330,6 +438,11 @@
 	return i.bootImageFilesByArch
 }
 
+// Return true if the boot image should be installed in the APEX.
+func (i *BootclasspathFragmentApexContentInfo) ShouldInstallBootImageInApex() bool {
+	return i.shouldInstallBootImageInApex
+}
+
 // DexBootJarPathForContentModule returns the path to the dex boot jar for specified module.
 //
 // The dex boot jar is one which has had hidden API encoding performed on it.
@@ -345,6 +458,14 @@
 	}
 }
 
+func (i BootclasspathFragmentApexContentInfo) ProfilePathOnHost() android.Path {
+	return i.profilePathOnHost
+}
+
+func (i BootclasspathFragmentApexContentInfo) ProfileInstallPathInApex() string {
+	return i.profileInstallPathInApex
+}
+
 func (b *BootclasspathFragmentModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
 	if IsBootclasspathFragmentContentDepTag(tag) {
@@ -422,6 +543,9 @@
 	// Generate classpaths.proto config
 	b.generateClasspathProtoBuildActions(ctx)
 
+	// Collect the module directory for IDE info in java/jdeps.go.
+	b.modulePaths = append(b.modulePaths, ctx.ModuleDir())
+
 	// Gather the bootclasspath fragment's contents.
 	var contents []android.Module
 	ctx.VisitDirectDeps(func(module android.Module) {
@@ -459,6 +583,24 @@
 				// Copy the dex jars of this fragment's content modules to their predefined locations.
 				copyBootJarsToPredefinedLocations(ctx, hiddenAPIOutput.EncodedBootDexFilesByModule, imageConfig.dexPathsByModule)
 			}
+
+			for _, variant := range imageConfig.apexVariants() {
+				arch := variant.target.Arch.ArchType.String()
+				for _, install := range variant.deviceInstalls {
+					// 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)
+					installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+
+					b.bootImageDeviceInstalls = append(b.bootImageDeviceInstalls, dexpreopterInstall{
+						name:                arch + "-" + installBase,
+						moduleName:          b.Name(),
+						outputPathOnHost:    install.From,
+						installDirOnDevice:  installPath,
+						installFileOnDevice: installBase,
+					})
+				}
+			}
 		}
 
 		// A prebuilt fragment cannot contribute to an apex.
@@ -466,6 +608,19 @@
 			// Provide the apex content info.
 			b.provideApexContentInfo(ctx, imageConfig, hiddenAPIOutput, bootImageFilesByArch)
 		}
+	} else {
+		// Versioned fragments are not needed by make.
+		b.HideFromMake()
+	}
+
+	// In order for information about bootclasspath_fragment modules to be added to module-info.json
+	// it is necessary to output an entry to Make. As bootclasspath_fragment modules are part of an
+	// APEX there can be multiple variants, including the default/platform variant and only one can
+	// be output to Make but it does not really matter which variant is output. The default/platform
+	// variant is the first (ctx.PrimaryModule()) and is usually hidden from make so this just picks
+	// the last variant (ctx.FinalModule()).
+	if ctx.Module() != ctx.FinalModule() {
+		b.HideFromMake()
 	}
 }
 
@@ -503,6 +658,13 @@
 
 	if imageConfig != nil {
 		info.modules = imageConfig.modules
+		global := dexpreopt.GetGlobalConfig(ctx)
+		if !global.DisableGenerateProfile {
+			info.profilePathOnHost = imageConfig.profilePathOnHost
+			info.profileInstallPathInApex = imageConfig.profileInstallPathInApex
+		}
+
+		info.shouldInstallBootImageInApex = imageConfig.shouldInstallInApex()
 	}
 
 	info.bootImageFilesByArch = bootImageFilesByArch
@@ -514,35 +676,46 @@
 // generateClasspathProtoBuildActions generates all required build actions for classpath.proto config
 func (b *BootclasspathFragmentModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
 	var classpathJars []classpathJar
+	configuredJars := b.configuredJars(ctx)
 	if "art" == proptools.String(b.properties.Image_name) {
 		// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
-		classpathJars = configuredJarListToClasspathJars(ctx, b.ClasspathFragmentToConfiguredJarList(ctx), BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
+		classpathJars = configuredJarListToClasspathJars(ctx, configuredJars, BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
 	} else {
-		classpathJars = configuredJarListToClasspathJars(ctx, b.ClasspathFragmentToConfiguredJarList(ctx), b.classpathType)
+		classpathJars = configuredJarListToClasspathJars(ctx, configuredJars, b.classpathType)
 	}
-	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
-func (b *BootclasspathFragmentModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
+func (b *BootclasspathFragmentModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
 	if "art" == proptools.String(b.properties.Image_name) {
 		return b.getImageConfig(ctx).modules
 	}
 
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	possibleUpdatableModules := gatherPossibleUpdatableModuleNamesAndStems(ctx, b.properties.Contents, bootclasspathFragmentContentDepTag)
-
-	// Only create configs for updatable boot jars. Non-updatable boot jars must be part of the
-	// platform_bootclasspath's classpath proto config to guarantee that they come before any
-	// updatable jars at runtime.
-	jars := global.UpdatableBootJars.Filter(possibleUpdatableModules)
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, b.properties.Contents, bootclasspathFragmentContentDepTag)
+	jars, unknown := global.ApexBootJars.Filter(possibleUpdatableModules)
 
 	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
-	// config. However, any test specific jars would not be present in UpdatableBootJars. Instead,
+	// config. However, any test specific jars would not be present in ApexBootJars. Instead,
 	// we should check if we are creating a config for apex_test via ApexInfo and amend the values.
 	// This is an exception to support end-to-end test for SdkExtensions, until such support exists.
 	if android.InList("test_framework-sdkextensions", possibleUpdatableModules) {
 		jars = jars.Append("com.android.sdkext", "test_framework-sdkextensions")
+	} else if android.InList("AddNewActivity", possibleUpdatableModules) {
+		jars = jars.Append("test_com.android.cts.frameworkresapkplits", "AddNewActivity")
+	} else if android.InList("test_framework-apexd", possibleUpdatableModules) {
+		jars = jars.Append("com.android.apex.test_package", "test_framework-apexd")
+	} else if global.ApexBootJars.Len() != 0 && !android.IsModuleInVersionedSdk(ctx.Module()) {
+		unknown = android.RemoveListFromList(unknown, b.properties.Coverage.Contents)
+		_, unknown = android.RemoveFromList("core-icu4j", unknown)
+		// This module only exists in car products.
+		// So ignore it even if it is not in PRODUCT_APEX_BOOT_JARS.
+		// TODO(b/202896428): Add better way to handle this.
+		_, unknown = android.RemoveFromList("android.car-module", unknown)
+		if len(unknown) > 0 {
+			ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
+		}
 	}
 	return jars
 }
@@ -576,6 +749,15 @@
 	common := ctx.Module().(commonBootclasspathFragment)
 	output := common.produceHiddenAPIOutput(ctx, contents, input)
 
+	// If the source or prebuilts module does not provide a signature patterns file then generate one
+	// from the flags.
+	// TODO(b/192868581): Remove once the source and prebuilts provide a signature patterns file of
+	//  their own.
+	if output.SignaturePatternsPath == nil {
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(
+			ctx, output.AllFlagsPath, []string{"*"}, nil, nil)
+	}
+
 	// Initialize a HiddenAPIInfo structure.
 	hiddenAPIInfo := HiddenAPIInfo{
 		// The monolithic hidden API processing needs access to the flag files that override the default
@@ -592,7 +774,7 @@
 
 	// The monolithic hidden API processing also needs access to all the output files produced by
 	// hidden API processing of this fragment.
-	hiddenAPIInfo.HiddenAPIFlagOutput = (*output).HiddenAPIFlagOutput
+	hiddenAPIInfo.HiddenAPIFlagOutput = output.HiddenAPIFlagOutput
 
 	//  Provide it for use by other modules.
 	ctx.SetProvider(HiddenAPIInfoProvider, hiddenAPIInfo)
@@ -642,7 +824,22 @@
 func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
 	// Generate the rules to create the hidden API flags and update the supplied hiddenAPIInfo with the
 	// paths to the created files.
-	return hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+	output := hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+
+	// If the module specifies split_packages or package_prefixes then use those to generate the
+	// signature patterns.
+	splitPackages := b.sourceOnlyProperties.Hidden_api.Split_packages
+	packagePrefixes := b.sourceOnlyProperties.Hidden_api.Package_prefixes
+	singlePackages := b.sourceOnlyProperties.Hidden_api.Single_packages
+	if splitPackages != nil || packagePrefixes != nil || singlePackages != nil {
+		if splitPackages == nil {
+			splitPackages = []string{"*"}
+		}
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(
+			ctx, output.AllFlagsPath, splitPackages, packagePrefixes, singlePackages)
+	}
+
+	return output
 }
 
 // produceBootImageFiles builds the boot image files from the source if it is required.
@@ -691,12 +888,50 @@
 	return androidBootImageFilesByArch
 }
 
+func (b *BootclasspathFragmentModule) AndroidMkEntries() []android.AndroidMkEntries {
+	// Use the generated classpath proto as the output.
+	outputFile := b.outputFilepath
+	// Create a fake entry that will cause this to be added to the module-info.json file.
+	entriesList := []android.AndroidMkEntries{{
+		Class:      "FAKE",
+		OutputFile: android.OptionalPathForPath(outputFile),
+		Include:    "$(BUILD_PHONY_PACKAGE)",
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string) {
+				// Allow the bootclasspath_fragment to be built by simply passing its name on the command
+				// line.
+				fmt.Fprintln(w, ".PHONY:", b.Name())
+				fmt.Fprintln(w, b.Name()+":", outputFile.String())
+			},
+		},
+	}}
+	for _, install := range b.bootImageDeviceInstalls {
+		entriesList = append(entriesList, install.ToMakeEntries())
+	}
+	return entriesList
+}
+
+// Returns the names of all Make modules that handle the installation of the boot image.
+func (b *BootclasspathFragmentModule) BootImageDeviceInstallMakeModules() []string {
+	var makeModules []string
+	for _, install := range b.bootImageDeviceInstalls {
+		makeModules = append(makeModules, install.FullModuleName())
+	}
+	return makeModules
+}
+
+// Collect information for opening IDE project files in java/jdeps.go.
+func (b *BootclasspathFragmentModule) IDEInfo(dpInfo *android.IdeInfo) {
+	dpInfo.Deps = append(dpInfo.Deps, b.properties.Contents...)
+	dpInfo.Paths = append(dpInfo.Paths, b.modulePaths...)
+}
+
 type bootclasspathFragmentMemberType struct {
 	android.SdkMemberTypeBase
 }
 
-func (b *bootclasspathFragmentMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (b *bootclasspathFragmentMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (b *bootclasspathFragmentMemberType) IsInstance(module android.Module) bool {
@@ -729,12 +964,12 @@
 	Stub_libs               []string
 	Core_platform_stub_libs []string
 
+	// Fragment properties
+	Fragments []ApexVariantReference
+
 	// Flag files by *hiddenAPIFlagFileCategory
 	Flag_files_by_category FlagFilesByCategory
 
-	// The path to the generated stub-flags.csv file.
-	Stub_flags_path android.OptionalPath
-
 	// The path to the generated annotation-flags.csv file.
 	Annotation_flags_path android.OptionalPath
 
@@ -744,8 +979,20 @@
 	// The path to the generated index.csv file.
 	Index_path android.OptionalPath
 
+	// The path to the generated stub-flags.csv file.
+	Stub_flags_path android.OptionalPath `supported_build_releases:"S"`
+
 	// The path to the generated all-flags.csv file.
-	All_flags_path android.OptionalPath
+	All_flags_path android.OptionalPath `supported_build_releases:"S"`
+
+	// The path to the generated signature-patterns.csv file.
+	Signature_patterns_path android.OptionalPath `supported_build_releases:"Tiramisu+"`
+
+	// The path to the generated filtered-stub-flags.csv file.
+	Filtered_stub_flags_path android.OptionalPath `supported_build_releases:"Tiramisu+"`
+
+	// The path to the generated filtered-flags.csv file.
+	Filtered_flags_path android.OptionalPath `supported_build_releases:"Tiramisu+"`
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -760,15 +1007,23 @@
 	b.Flag_files_by_category = hiddenAPIInfo.FlagFilesByCategory
 
 	// Copy all the generated file paths.
-	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
 	b.Annotation_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AnnotationFlagsPath)
 	b.Metadata_path = android.OptionalPathForPath(hiddenAPIInfo.MetadataPath)
 	b.Index_path = android.OptionalPathForPath(hiddenAPIInfo.IndexPath)
+
+	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
 	b.All_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AllFlagsPath)
 
+	b.Signature_patterns_path = android.OptionalPathForPath(hiddenAPIInfo.SignaturePatternsPath)
+	b.Filtered_stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredStubFlagsPath)
+	b.Filtered_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredFlagsPath)
+
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
 	b.Core_platform_stub_libs = module.properties.Core_platform_api.Stub_libs
+
+	// Copy fragment properties.
+	b.Fragments = module.properties.Fragments
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
@@ -791,6 +1046,9 @@
 		corePlatformApiPropertySet := propertySet.AddPropertySet("core_platform_api")
 		corePlatformApiPropertySet.AddPropertyWithTag("stub_libs", b.Core_platform_stub_libs, requiredMemberDependency)
 	}
+	if len(b.Fragments) > 0 {
+		propertySet.AddProperty("fragments", b.Fragments)
+	}
 
 	hiddenAPISet := propertySet.AddPropertySet("hidden_api")
 	hiddenAPIDir := "hiddenapi"
@@ -821,11 +1079,16 @@
 	}
 
 	// Copy all the generated files, if available.
-	copyOptionalPath(b.Stub_flags_path, "stub_flags")
 	copyOptionalPath(b.Annotation_flags_path, "annotation_flags")
 	copyOptionalPath(b.Metadata_path, "metadata")
 	copyOptionalPath(b.Index_path, "index")
+
+	copyOptionalPath(b.Stub_flags_path, "stub_flags")
 	copyOptionalPath(b.All_flags_path, "all_flags")
+
+	copyOptionalPath(b.Signature_patterns_path, "signature_patterns")
+	copyOptionalPath(b.Filtered_stub_flags_path, "filtered_stub_flags")
+	copyOptionalPath(b.Filtered_flags_path, "filtered_flags")
 }
 
 var _ android.SdkMemberType = (*bootclasspathFragmentMemberType)(nil)
@@ -834,9 +1097,6 @@
 // specific properties.
 type prebuiltBootclasspathFragmentProperties struct {
 	Hidden_api struct {
-		// The path to the stub-flags.csv file created by the bootclasspath_fragment.
-		Stub_flags *string `android:"path"`
-
 		// The path to the annotation-flags.csv file created by the bootclasspath_fragment.
 		Annotation_flags *string `android:"path"`
 
@@ -846,8 +1106,20 @@
 		// The path to the index.csv file created by the bootclasspath_fragment.
 		Index *string `android:"path"`
 
+		// The path to the signature-patterns.csv file created by the bootclasspath_fragment.
+		Signature_patterns *string `android:"path"`
+
+		// The path to the stub-flags.csv file created by the bootclasspath_fragment.
+		Stub_flags *string `android:"path"`
+
 		// The path to the all-flags.csv file created by the bootclasspath_fragment.
 		All_flags *string `android:"path"`
+
+		// The path to the filtered-stub-flags.csv file created by the bootclasspath_fragment.
+		Filtered_stub_flags *string `android:"path"`
+
+		// The path to the filtered-flags.csv file created by the bootclasspath_fragment.
+		Filtered_flags *string `android:"path"`
 	}
 }
 
@@ -856,7 +1128,7 @@
 // At the moment this is basically just a bootclasspath_fragment module that can be used as a
 // prebuilt. Eventually as more functionality is migrated into the bootclasspath_fragment module
 // type from the various singletons then this will diverge.
-type prebuiltBootclasspathFragmentModule struct {
+type PrebuiltBootclasspathFragmentModule struct {
 	BootclasspathFragmentModule
 	prebuilt android.Prebuilt
 
@@ -864,20 +1136,26 @@
 	prebuiltProperties prebuiltBootclasspathFragmentProperties
 }
 
-func (module *prebuiltBootclasspathFragmentModule) Prebuilt() *android.Prebuilt {
+func (module *PrebuiltBootclasspathFragmentModule) Prebuilt() *android.Prebuilt {
 	return &module.prebuilt
 }
 
-func (module *prebuiltBootclasspathFragmentModule) Name() string {
+func (module *PrebuiltBootclasspathFragmentModule) Name() string {
 	return module.prebuilt.Name(module.ModuleBase.Name())
 }
 
 // produceHiddenAPIOutput returns a path to the prebuilt all-flags.csv or nil if none is specified.
-func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
-	pathForOptionalSrc := func(src *string) android.Path {
+func (module *PrebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
+	pathForOptionalSrc := func(src *string, defaultPath android.Path) android.Path {
 		if src == nil {
-			// TODO(b/179354495): Fail if this is not provided once prebuilts have been updated.
-			return nil
+			return defaultPath
+		}
+		return android.PathForModuleSrc(ctx, *src)
+	}
+	pathForSrc := func(property string, src *string) android.Path {
+		if src == nil {
+			ctx.PropertyErrorf(property, "is required but was not specified")
+			return android.PathForModuleSrc(ctx, "missing", property)
 		}
 		return android.PathForModuleSrc(ctx, *src)
 	}
@@ -888,76 +1166,83 @@
 
 	output := HiddenAPIOutput{
 		HiddenAPIFlagOutput: HiddenAPIFlagOutput{
-			StubFlagsPath:       pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags),
-			AnnotationFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Annotation_flags),
-			MetadataPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Metadata),
-			IndexPath:           pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Index),
-			AllFlagsPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags),
+			AnnotationFlagsPath:   pathForSrc("hidden_api.annotation_flags", module.prebuiltProperties.Hidden_api.Annotation_flags),
+			MetadataPath:          pathForSrc("hidden_api.metadata", module.prebuiltProperties.Hidden_api.Metadata),
+			IndexPath:             pathForSrc("hidden_api.index", module.prebuiltProperties.Hidden_api.Index),
+			SignaturePatternsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Signature_patterns, nil),
+			// TODO: Temporarily handle stub_flags/all_flags properties until prebuilts have been updated.
+			StubFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags, nil),
+			AllFlagsPath:  pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags, nil),
 		},
+
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
 
+	// TODO: Temporarily fallback to stub_flags/all_flags properties until prebuilts have been updated.
+	output.FilteredStubFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_stub_flags, output.StubFlagsPath)
+	output.FilteredFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_flags, output.AllFlagsPath)
+
 	return &output
 }
 
 // produceBootImageFiles extracts the boot image files from the APEX if available.
-func (module *prebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
+func (module *PrebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
 	if !shouldCopyBootFilesToPredefinedLocations(ctx, imageConfig) {
 		return nil
 	}
 
-	var deapexerModule android.Module
-	ctx.VisitDirectDeps(func(to android.Module) {
-		tag := ctx.OtherModuleDependencyTag(to)
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			if deapexerModule != nil {
-				ctx.ModuleErrorf("Ambiguous duplicate deapexer module dependencies %q and %q",
-					deapexerModule.Name(), to.Name())
+	di := android.FindDeapexerProviderForModule(ctx)
+	if di == nil {
+		return nil // An error has been reported by FindDeapexerProviderForModule.
+	}
+
+	profile := (android.WritablePath)(nil)
+	if imageConfig.profileInstallPathInApex != "" {
+		profile = di.PrebuiltExportPath(imageConfig.profileInstallPathInApex)
+	}
+
+	// Build the boot image files for the host variants. These are always built from the dex files
+	// provided by the contents of this module as prebuilt versions of the host boot image files are
+	// not available, i.e. there is no host specific prebuilt apex containing them. This has to be
+	// built without a profile as the prebuilt modules do not provide a profile.
+	buildBootImageVariantsForBuildOs(ctx, imageConfig, profile)
+
+	if imageConfig.shouldInstallInApex() {
+		// If the boot image files for the android variants are in the prebuilt apex, we must use those
+		// rather than building new ones because those boot image files are going to be used on device.
+		files := bootImageFilesByArch{}
+		for _, variant := range imageConfig.apexVariants() {
+			arch := variant.target.Arch.ArchType
+			for _, toPath := range variant.imagesDeps {
+				apexRelativePath := apexRootRelativePathToBootImageFile(arch, toPath.Base())
+				// Get the path to the file that the deapexer extracted from the prebuilt apex file.
+				fromPath := di.PrebuiltExportPath(apexRelativePath)
+
+				// Return the toPath as the calling code expects the paths in the returned map to be the
+				// paths predefined in the bootImageConfig.
+				files[arch] = append(files[arch], toPath)
+
+				// Copy the file to the predefined location.
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   android.Cp,
+					Input:  fromPath,
+					Output: toPath,
+				})
 			}
-			deapexerModule = to
 		}
-	})
-
-	if deapexerModule == nil {
-		// This should never happen as a variant for a prebuilt_apex is only created if the
-		// deapexer module has been configured to export the dex implementation jar for this module.
-		ctx.ModuleErrorf("internal error: module does not depend on a `deapexer` module")
-		return nil
-	}
-
-	di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
-	files := bootImageFilesByArch{}
-	for _, variant := range imageConfig.apexVariants() {
-		arch := variant.target.Arch.ArchType
-		for _, toPath := range variant.imagesDeps {
-			apexRelativePath := apexRootRelativePathToBootImageFile(arch, toPath.Base())
-			// Get the path to the file that the deapexer extracted from the prebuilt apex file.
-			fromPath := di.PrebuiltExportPath(apexRelativePath)
-
-			// Return the toPath as the calling code expects the paths in the returned map to be the
-			// paths predefined in the bootImageConfig.
-			files[arch] = append(files[arch], toPath)
-
-			// Copy the file to the predefined location.
-			ctx.Build(pctx, android.BuildParams{
-				Rule:   android.Cp,
-				Input:  fromPath,
-				Output: toPath,
-			})
+		return files
+	} else {
+		if profile == nil {
+			ctx.ModuleErrorf("Unable to produce boot image files: neither boot image files nor profiles exists in the prebuilt apex")
+			return nil
 		}
+		// Build boot image files for the android variants from the dex files provided by the contents
+		// of this module.
+		return buildBootImageVariantsForAndroidOs(ctx, imageConfig, profile)
 	}
-
-	// Build the boot image files for the host variants. These are built from the dex files provided
-	// by the contents of this module as prebuilt versions of the host boot image files are not
-	// available, i.e. there is no host specific prebuilt apex containing them. This has to be built
-	// without a profile as the prebuilt modules do not provide a profile.
-	buildBootImageVariantsForBuildOs(ctx, imageConfig, nil)
-
-	return files
 }
 
-var _ commonBootclasspathFragment = (*prebuiltBootclasspathFragmentModule)(nil)
+var _ commonBootclasspathFragment = (*PrebuiltBootclasspathFragmentModule)(nil)
 
 // createBootImageTag creates the tag to uniquely identify the boot image file among all of the
 // files that a module requires from the prebuilt .apex file.
@@ -971,16 +1256,22 @@
 //
 // If there is no image config associated with this fragment then it returns nil. Otherwise, it
 // returns the files that are listed in the image config.
-func (module *prebuiltBootclasspathFragmentModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
+func (module *PrebuiltBootclasspathFragmentModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
 	imageConfig := module.getImageConfig(ctx)
 	if imageConfig != nil {
-		// Add the boot image files, e.g. .art, .oat and .vdex files.
 		files := []string{}
-		for _, variant := range imageConfig.apexVariants() {
-			arch := variant.target.Arch.ArchType
-			for _, path := range variant.imagesDeps.Paths() {
-				base := path.Base()
-				files = append(files, apexRootRelativePathToBootImageFile(arch, base))
+		if imageConfig.profileInstallPathInApex != "" {
+			// Add the boot image profile.
+			files = append(files, imageConfig.profileInstallPathInApex)
+		}
+		if imageConfig.shouldInstallInApex() {
+			// Add the boot image files, e.g. .art, .oat and .vdex files.
+			for _, variant := range imageConfig.apexVariants() {
+				arch := variant.target.Arch.ArchType
+				for _, path := range variant.imagesDeps.Paths() {
+					base := path.Base()
+					files = append(files, apexRootRelativePathToBootImageFile(arch, base))
+				}
 			}
 		}
 		return files
@@ -992,10 +1283,10 @@
 	return filepath.Join("javalib", arch.String(), base)
 }
 
-var _ android.RequiredFilesFromPrebuiltApex = (*prebuiltBootclasspathFragmentModule)(nil)
+var _ android.RequiredFilesFromPrebuiltApex = (*PrebuiltBootclasspathFragmentModule)(nil)
 
 func prebuiltBootclasspathFragmentFactory() android.Module {
-	m := &prebuiltBootclasspathFragmentModule{}
+	m := &PrebuiltBootclasspathFragmentModule{}
 	m.AddProperties(&m.properties, &m.prebuiltProperties)
 	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
 	// array.
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index 3d0e155..d3de675 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -164,6 +164,7 @@
 		prepareForTestWithBootclasspathFragment,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("mysdklibrary", "mycoveragestubs"),
+		FixtureConfigureApexBootJars("someapex:mybootlib"),
 		prepareWithBp,
 	)
 
@@ -186,6 +187,7 @@
 		prepareForTestWithBootclasspathFragment,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary", "mycoreplatform"),
+		FixtureConfigureApexBootJars("someapex:mysdklibrary"),
 	).RunTestWithBp(t, `
 		bootclasspath_fragment {
 			name: "myfragment",
diff --git a/java/builder.go b/java/builder.go
index cde8731..c0fadd4 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -120,42 +120,39 @@
 		"extractMatchingApks",
 		blueprint.RuleParams{
 			Command: `rm -rf "$out" && ` +
-				`${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` +
+				`${config.ExtractApksCmd} -o "${out}" -zip "${zip}" -allow-prereleased=${allow-prereleased} ` +
 				`-sdk-version=${sdk-version} -abis=${abis} ` +
 				`--screen-densities=${screen-densities} --stem=${stem} ` +
 				`-apkcerts=${apkcerts} -partition=${partition} ` +
 				`${in}`,
 			CommandDeps: []string{"${config.ExtractApksCmd}"},
 		},
-		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition")
+		"abis", "allow-prereleased", "screen-densities", "sdk-version", "stem", "apkcerts", "partition", "zip")
 
 	turbine, turbineRE = pctx.RemoteStaticRules("turbine",
 		blueprint.RuleParams{
-			Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
-				`$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} --output $out.tmp ` +
-				`--temp_dir "$outDir" --sources @$out.rsp  --source_jars $srcJars ` +
+			Command: `$reTemplate${config.JavaCmd} ${config.JavaVmFlags} -jar ${config.TurbineJar} $outputFlags ` +
+				`--sources @$out.rsp  --source_jars $srcJars ` +
 				`--javacopts ${config.CommonJdkFlags} ` +
-				`$javacFlags -source $javaVersion -target $javaVersion -- $bootClasspath $classpath && ` +
-				`${config.Ziptime} $out.tmp && ` +
-				`(if cmp -s $out.tmp $out ; then rm $out.tmp ; else mv $out.tmp $out ; fi )`,
+				`$javacFlags -source $javaVersion -target $javaVersion -- $turbineFlags && ` +
+				`(for o in $outputs; do if cmp -s $${o}.tmp $${o} ; then rm $${o}.tmp ; else mv $${o}.tmp $${o} ; fi; done )`,
 			CommandDeps: []string{
 				"${config.TurbineJar}",
 				"${config.JavaCmd}",
-				"${config.Ziptime}",
 			},
 			Rspfile:        "$out.rsp",
 			RspfileContent: "$in",
 			Restat:         true,
 		},
 		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
-			ExecStrategy:      "${config.RETurbineExecStrategy}",
-			Inputs:            []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
-			RSPFiles:          []string{"${out}.rsp"},
-			OutputFiles:       []string{"$out.tmp"},
-			OutputDirectories: []string{"$outDir"},
-			ToolchainInputs:   []string{"${config.JavaCmd}"},
-			Platform:          map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
-		}, []string{"javacFlags", "bootClasspath", "classpath", "srcJars", "outDir", "javaVersion"}, []string{"implicits"})
+			ExecStrategy:    "${config.RETurbineExecStrategy}",
+			Inputs:          []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
+			RSPFiles:        []string{"${out}.rsp"},
+			OutputFiles:     []string{"$rbeOutputs"},
+			ToolchainInputs: []string{"${config.JavaCmd}"},
+			Platform:        map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
+		},
+		[]string{"javacFlags", "turbineFlags", "outputFlags", "javaVersion", "outputs", "rbeOutputs", "srcJars"}, []string{"implicits"})
 
 	jar, jarRE = pctx.RemoteStaticRules("jar",
 		blueprint.RuleParams{
@@ -247,22 +244,40 @@
 }
 
 type javaBuilderFlags struct {
-	javacFlags     string
-	bootClasspath  classpath
-	classpath      classpath
+	javacFlags string
+
+	// bootClasspath is the list of jars that form the boot classpath (generally the java.* and
+	// android.* classes) for tools that still use it.  javac targeting 1.9 or higher uses
+	// systemModules and java9Classpath instead.
+	bootClasspath classpath
+
+	// classpath is the list of jars that form the classpath for javac and kotlinc rules.  It
+	// contains header jars for all static and non-static dependencies.
+	classpath classpath
+
+	// dexClasspath is the list of jars that form the classpath for d8 and r8 rules.  It contains
+	// header jars for all non-static dependencies.  Static dependencies have already been
+	// combined into the program jar.
+	dexClasspath classpath
+
+	// java9Classpath is the list of jars that will be added to the classpath when targeting
+	// 1.9 or higher.  It generally contains the android.* classes, while the java.* classes
+	// are provided by systemModules.
 	java9Classpath classpath
-	processorPath  classpath
-	processors     []string
-	systemModules  *systemModules
-	aidlFlags      string
-	aidlDeps       android.Paths
-	javaVersion    javaVersion
+
+	processorPath classpath
+	processors    []string
+	systemModules *systemModules
+	aidlFlags     string
+	aidlDeps      android.Paths
+	javaVersion   javaVersion
 
 	errorProneExtraJavacFlags string
 	errorProneProcessorPath   classpath
 
 	kotlincFlags     string
 	kotlincClasspath classpath
+	kotlincDeps      android.Paths
 
 	proto android.ProtoFlags
 }
@@ -279,23 +294,6 @@
 	transformJavaToClasses(ctx, outputFile, shardIdx, srcFiles, srcJars, flags, deps, "javac", desc)
 }
 
-func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath,
-	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
-
-	flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
-
-	if len(flags.errorProneExtraJavacFlags) > 0 {
-		if len(flags.javacFlags) > 0 {
-			flags.javacFlags += " " + flags.errorProneExtraJavacFlags
-		} else {
-			flags.javacFlags = flags.errorProneExtraJavacFlags
-		}
-	}
-
-	transformJavaToClasses(ctx, outputFile, -1, srcFiles, srcJars, flags, nil,
-		"errorprone", "errorprone")
-}
-
 // Emits the rule to generate Xref input file (.kzip file) for the given set of source files and source jars
 // to compile with given set of builder flags, etc.
 func emitXrefRule(ctx android.ModuleContext, xrefFile android.WritablePath, idx int,
@@ -357,11 +355,8 @@
 		})
 }
 
-func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
-	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
-
+func turbineFlags(ctx android.ModuleContext, flags javaBuilderFlags) (string, android.Paths) {
 	var deps android.Paths
-	deps = append(deps, srcJars...)
 
 	classpath := flags.classpath
 
@@ -383,20 +378,31 @@
 	}
 
 	deps = append(deps, classpath...)
-	deps = append(deps, flags.processorPath...)
+	turbineFlags := bootClasspath + " " + classpath.FormTurbineClassPath("--classpath ")
+
+	return turbineFlags, deps
+}
+
+func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
+	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
+
+	turbineFlags, deps := turbineFlags(ctx, flags)
+
+	deps = append(deps, srcJars...)
 
 	rule := turbine
 	args := map[string]string{
-		"javacFlags":    flags.javacFlags,
-		"bootClasspath": bootClasspath,
-		"srcJars":       strings.Join(srcJars.Strings(), " "),
-		"classpath":     classpath.FormTurbineClassPath("--classpath "),
-		"outDir":        android.PathForModuleOut(ctx, "turbine", "classes").String(),
-		"javaVersion":   flags.javaVersion.String(),
+		"javacFlags":   flags.javacFlags,
+		"srcJars":      strings.Join(srcJars.Strings(), " "),
+		"javaVersion":  flags.javaVersion.String(),
+		"turbineFlags": turbineFlags,
+		"outputFlags":  "--output " + outputFile.String() + ".tmp",
+		"outputs":      outputFile.String(),
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_TURBINE") {
 		rule = turbineRE
 		args["implicits"] = strings.Join(deps.Strings(), ",")
+		args["rbeOutputs"] = outputFile.String() + ".tmp"
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
@@ -408,6 +414,47 @@
 	})
 }
 
+// TurbineApt produces a rule to run annotation processors using turbine.
+func TurbineApt(ctx android.ModuleContext, outputSrcJar, outputResJar android.WritablePath,
+	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
+
+	turbineFlags, deps := turbineFlags(ctx, flags)
+
+	deps = append(deps, srcJars...)
+
+	deps = append(deps, flags.processorPath...)
+	turbineFlags += " " + flags.processorPath.FormTurbineClassPath("--processorpath ")
+	turbineFlags += " --processors " + strings.Join(flags.processors, " ")
+
+	outputs := android.WritablePaths{outputSrcJar, outputResJar}
+	outputFlags := "--gensrc_output " + outputSrcJar.String() + ".tmp " +
+		"--resource_output " + outputResJar.String() + ".tmp"
+
+	rule := turbine
+	args := map[string]string{
+		"javacFlags":   flags.javacFlags,
+		"srcJars":      strings.Join(srcJars.Strings(), " "),
+		"javaVersion":  flags.javaVersion.String(),
+		"turbineFlags": turbineFlags,
+		"outputFlags":  outputFlags,
+		"outputs":      strings.Join(outputs.Strings(), " "),
+	}
+	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_TURBINE") {
+		rule = turbineRE
+		args["implicits"] = strings.Join(deps.Strings(), ",")
+		args["rbeOutputs"] = outputSrcJar.String() + ".tmp," + outputResJar.String() + ".tmp"
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:            rule,
+		Description:     "turbine apt",
+		Output:          outputs[0],
+		ImplicitOutputs: outputs[1:],
+		Inputs:          srcFiles,
+		Implicits:       deps,
+		Args:            args,
+	})
+}
+
 // transformJavaToClasses takes source files and converts them to a jar containing .class files.
 // srcFiles is a list of paths to sources, srcJars is a list of paths to jar files that contain
 // sources.  flags contains various command line flags to be passed to the compiler.
@@ -669,6 +716,6 @@
 	} else if forceEmpty {
 		return `--bootclasspath ""`, nil
 	} else {
-		return "", nil
+		return "--system ${config.JavaHome}", nil
 	}
 }
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index ecfdfb7..259e977 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -19,12 +19,13 @@
 import (
 	"fmt"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 	"strings"
 
 	"android/soong/android"
 )
 
-// Build rules and utilities to generate individual packages/modules/SdkExtensions/proto/classpaths.proto
+// Build rules and utilities to generate individual packages/modules/common/proto/classpaths.proto
 // config files based on build configuration to embed into /system and /apex on a device.
 //
 // See `derive_classpath` service that reads the configs at runtime and defines *CLASSPATH variables
@@ -33,17 +34,23 @@
 type classpathType int
 
 const (
-	// Matches definition in packages/modules/SdkExtensions/proto/classpaths.proto
+	// Matches definition in packages/modules/common/proto/classpaths.proto
 	BOOTCLASSPATH classpathType = iota
 	DEX2OATBOOTCLASSPATH
 	SYSTEMSERVERCLASSPATH
+	STANDALONE_SYSTEMSERVER_JARS
 )
 
 func (c classpathType) String() string {
-	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH"}[c]
+	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"}[c]
 }
 
 type classpathFragmentProperties struct {
+	// Whether to generated classpaths.proto config instance for the fragment. If the config is not
+	// generated, then relevant boot jars are added to platform classpath, i.e. platform_bootclasspath
+	// or platform_systemserverclasspath. This is useful for non-updatable APEX boot jars, to keep
+	// them as part of dexopt on device. Defaults to true.
+	Generate_classpaths_proto *bool
 }
 
 // classpathFragment interface is implemented by a module that contributes jars to a *CLASSPATH
@@ -52,10 +59,6 @@
 	android.Module
 
 	classpathFragmentBase() *ClasspathFragmentBase
-
-	// ClasspathFragmentToConfiguredJarList returns android.ConfiguredJarList representation of all
-	// the jars in this classpath fragment.
-	ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList
 }
 
 // ClasspathFragmentBase is meant to be embedded in any module types that implement classpathFragment;
@@ -82,15 +85,14 @@
 
 // Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto
 type classpathJar struct {
-	path      string
-	classpath classpathType
-	// TODO(satayev): propagate min/max sdk versions for the jars
-	minSdkVersion int32
-	maxSdkVersion int32
+	path          string
+	classpath     classpathType
+	minSdkVersion string
+	maxSdkVersion string
 }
 
-// gatherPossibleUpdatableModuleNamesAndStems returns a set of module and stem names from the
-// supplied contents that may be in the updatable boot jars.
+// gatherPossibleApexModuleNamesAndStems returns a set of module and stem names from the
+// supplied contents that may be in the apex boot jars.
 //
 // The module names are included because sometimes the stem is set to just change the name of
 // the installed file and it expects the configuration to still use the actual module name.
@@ -98,7 +100,7 @@
 // The stem names are included because sometimes the stem is set to change the effective name of the
 // module that is used in the configuration as well,e .g. when a test library is overriding an
 // actual boot jar
-func gatherPossibleUpdatableModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
+func gatherPossibleApexModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
 	set := map[string]struct{}{}
 	for _, name := range contents {
 		dep := ctx.GetDirectDepWithTag(name, tag)
@@ -118,58 +120,79 @@
 	jars := make([]classpathJar, 0, len(paths)*len(classpaths))
 	for i := 0; i < len(paths); i++ {
 		for _, classpathType := range classpaths {
-			jars = append(jars, classpathJar{
+			jar := classpathJar{
 				classpath: classpathType,
 				path:      paths[i],
+			}
+			ctx.VisitDirectDepsIf(func(m android.Module) bool {
+				return m.Name() == configuredJars.Jar(i)
+			}, func(m android.Module) {
+				if s, ok := m.(*SdkLibrary); ok {
+					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
+					if s.minSdkVersion.Specified() {
+						if s.minSdkVersion.ApiLevel.IsCurrent() {
+							jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
+						} else {
+							jar.minSdkVersion = s.minSdkVersion.ApiLevel.String()
+						}
+					}
+					if s.maxSdkVersion.Specified() {
+						if s.maxSdkVersion.ApiLevel.IsCurrent() {
+							jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
+						} else {
+							jar.maxSdkVersion = s.maxSdkVersion.ApiLevel.String()
+						}
+					}
+				}
 			})
+			jars = append(jars, jar)
 		}
 	}
 	return jars
 }
 
-func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, jars []classpathJar) {
-	outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
-	c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
-	c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
+func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) {
+	generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true)
+	if generateProto {
+		outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
+		c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
+		c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
 
-	generatedJson := android.PathForModuleOut(ctx, outputFilename+".json")
-	writeClasspathsJson(ctx, generatedJson, jars)
+		generatedTextproto := android.PathForModuleOut(ctx, outputFilename+".textproto")
+		writeClasspathsTextproto(ctx, generatedTextproto, jars)
 
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("conv_classpaths_proto").
-		Flag("encode").
-		Flag("--format=json").
-		FlagWithInput("--input=", generatedJson).
-		FlagWithOutput("--output=", c.outputFilepath)
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			BuiltTool("conv_classpaths_proto").
+			Flag("encode").
+			Flag("--format=textproto").
+			FlagWithInput("--input=", generatedTextproto).
+			FlagWithOutput("--output=", c.outputFilepath)
 
-	rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
+		rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
+	}
 
 	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
+		ClasspathFragmentProtoGenerated:  generateProto,
+		ClasspathFragmentProtoContents:   configuredJars,
 		ClasspathFragmentProtoInstallDir: c.installDirPath,
 		ClasspathFragmentProtoOutput:     c.outputFilepath,
 	}
 	ctx.SetProvider(ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
 }
 
-func writeClasspathsJson(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
+func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
 	var content strings.Builder
-	fmt.Fprintf(&content, "{\n")
-	fmt.Fprintf(&content, "\"jars\": [\n")
-	for idx, jar := range jars {
-		fmt.Fprintf(&content, "{\n")
 
-		fmt.Fprintf(&content, "\"path\": \"%s\",\n", jar.path)
-		fmt.Fprintf(&content, "\"classpath\": \"%s\"\n", jar.classpath)
-
-		if idx < len(jars)-1 {
-			fmt.Fprintf(&content, "},\n")
-		} else {
-			fmt.Fprintf(&content, "}\n")
-		}
+	for _, jar := range jars {
+		fmt.Fprintf(&content, "jars {\n")
+		fmt.Fprintf(&content, "path: \"%s\"\n", jar.path)
+		fmt.Fprintf(&content, "classpath: %s\n", jar.classpath)
+		fmt.Fprintf(&content, "min_sdk_version: \"%s\"\n", jar.minSdkVersion)
+		fmt.Fprintf(&content, "max_sdk_version: \"%s\"\n", jar.maxSdkVersion)
+		fmt.Fprintf(&content, "}\n")
 	}
-	fmt.Fprintf(&content, "]\n")
-	fmt.Fprintf(&content, "}\n")
+
 	android.WriteFileRule(ctx, output, content.String())
 }
 
@@ -181,7 +204,7 @@
 		OutputFile: android.OptionalPathForPath(c.outputFilepath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
 			},
 		},
@@ -191,6 +214,12 @@
 var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider(ClasspathFragmentProtoContentInfo{})
 
 type ClasspathFragmentProtoContentInfo struct {
+	// Whether the classpaths.proto config is generated for the fragment.
+	ClasspathFragmentProtoGenerated bool
+
+	// ClasspathFragmentProtoContents contains a list of jars that are part of this classpath fragment.
+	ClasspathFragmentProtoContents android.ConfiguredJarList
+
 	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
 	//
 	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
diff --git a/java/config/config.go b/java/config/config.go
index 30c6f91..95b841f 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -26,7 +26,8 @@
 )
 
 var (
-	pctx = android.NewPackageContext("android/soong/java/config")
+	pctx         = android.NewPackageContext("android/soong/java/config")
+	exportedVars = android.NewExportedVariables(pctx)
 
 	LegacyCorePlatformBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
 	LegacyCorePlatformSystemModules          = "legacy-core-platform-api-stubs-system-modules"
@@ -50,27 +51,43 @@
 		"core-icu4j",
 		"core-oj",
 		"core-libart",
-		// TODO: Could this be all updatable bootclasspath jars?
-		"updatable-media",
-		"framework-mediaprovider",
-		"framework-sdkextensions",
-		"android.net.ipsec.ike",
 	}
 )
 
-const (
-	JavaVmFlags  = `-XX:OnError="cat hs_err_pid%p.log" -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads`
-	JavacVmFlags = `-J-XX:OnError="cat hs_err_pid%p.log" -J-XX:CICompilerCount=6 -J-XX:+UseDynamicNumberOfGCThreads`
+var (
+	JavacVmFlags    = strings.Join(javacVmFlagsList, " ")
+	javaVmFlagsList = []string{
+		`-XX:OnError="cat hs_err_pid%p.log"`,
+		"-XX:CICompilerCount=6",
+		"-XX:+UseDynamicNumberOfGCThreads",
+	}
+	javacVmFlagsList = []string{
+		`-J-XX:OnError="cat hs_err_pid%p.log"`,
+		"-J-XX:CICompilerCount=6",
+		"-J-XX:+UseDynamicNumberOfGCThreads",
+		"-J-XX:+TieredCompilation",
+		"-J-XX:TieredStopAtLevel=1",
+	}
 )
 
 func init() {
 	pctx.Import("github.com/google/blueprint/bootstrap")
 
-	pctx.StaticVariable("JavacHeapSize", "2048M")
-	pctx.StaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
-	pctx.StaticVariable("DexFlags", "-JXX:OnError='cat hs_err_pid%p.log' -JXX:CICompilerCount=6 -JXX:+UseDynamicNumberOfGCThreads")
+	exportedVars.ExportStringStaticVariable("JavacHeapSize", "2048M")
+	exportedVars.ExportStringStaticVariable("JavacHeapFlags", "-J-Xmx${JavacHeapSize}")
 
-	pctx.StaticVariable("CommonJdkFlags", strings.Join([]string{
+	// ErrorProne can use significantly more memory than javac alone, give it a higher heap
+	// size (b/221480398).
+	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",
+	})
+
+	exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
 		`-Xmaxerrs 9999999`,
 		`-encoding UTF-8`,
 		`-sourcepath ""`,
@@ -84,10 +101,10 @@
 
 		// b/65004097: prevent using java.lang.invoke.StringConcatFactory when using -target 1.9
 		`-XDstringConcat=inline`,
-	}, " "))
+	})
 
-	pctx.StaticVariable("JavaVmFlags", JavaVmFlags)
-	pctx.StaticVariable("JavacVmFlags", JavacVmFlags)
+	exportedVars.ExportStringListStaticVariable("JavaVmFlags", javaVmFlagsList)
+	exportedVars.ExportStringListStaticVariable("JavacVmFlags", javacVmFlagsList)
 
 	pctx.VariableConfigMethod("hostPrebuiltTag", android.Config.PrebuiltOS)
 
@@ -99,7 +116,12 @@
 		if override := ctx.Config().Getenv("OVERRIDE_JLINK_VERSION_NUMBER"); override != "" {
 			return override
 		}
-		return "11"
+		switch ctx.Config().Getenv("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN") {
+		case "true":
+			return "17"
+		default:
+			return "11"
+		}
 	})
 
 	pctx.SourcePathVariable("JavaToolchain", "${JavaHome}/bin")
@@ -178,6 +200,10 @@
 	hostJNIToolVariableWithSdkToolsPrebuilt("SignapkJniLibrary", "libconscrypt_openjdk_jni")
 }
 
+func BazelJavaToolchainVars(config android.Config) string {
+	return android.BazelToolchainVars(config, exportedVars)
+}
+
 func hostBinToolVariableWithSdkToolsPrebuilt(name, tool string) {
 	pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
 		if ctx.Config().AlwaysUsePrebuiltSdks() {
diff --git a/java/config/error_prone.go b/java/config/error_prone.go
index 48681b5..5f853c8 100644
--- a/java/config/error_prone.go
+++ b/java/config/error_prone.go
@@ -16,8 +16,6 @@
 
 import (
 	"strings"
-
-	"android/soong/android"
 )
 
 var (
@@ -31,23 +29,23 @@
 )
 
 // Wrapper that grabs value of val late so it can be initialized by a later module's init function
-func errorProneVar(name string, val *[]string, sep string) {
-	pctx.VariableFunc(name, func(android.PackageVarContext) string {
+func errorProneVar(val *[]string, sep string) func() string {
+	return func() string {
 		return strings.Join(*val, sep)
-	})
+	}
 }
 
 func init() {
-	errorProneVar("ErrorProneClasspath", &ErrorProneClasspath, ":")
-	errorProneVar("ErrorProneChecksError", &ErrorProneChecksError, " ")
-	errorProneVar("ErrorProneChecksWarning", &ErrorProneChecksWarning, " ")
-	errorProneVar("ErrorProneChecksDefaultDisabled", &ErrorProneChecksDefaultDisabled, " ")
-	errorProneVar("ErrorProneChecksOff", &ErrorProneChecksOff, " ")
-	errorProneVar("ErrorProneFlags", &ErrorProneFlags, " ")
-	pctx.StaticVariable("ErrorProneChecks", strings.Join([]string{
+	exportedVars.ExportVariableFuncVariable("ErrorProneClasspath", errorProneVar(&ErrorProneClasspath, ":"))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksError", errorProneVar(&ErrorProneChecksError, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksWarning", errorProneVar(&ErrorProneChecksWarning, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksDefaultDisabled", errorProneVar(&ErrorProneChecksDefaultDisabled, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneChecksOff", errorProneVar(&ErrorProneChecksOff, " "))
+	exportedVars.ExportVariableFuncVariable("ErrorProneFlags", errorProneVar(&ErrorProneFlags, " "))
+	exportedVars.ExportStringListStaticVariable("ErrorProneChecks", []string{
 		"${ErrorProneChecksOff}",
 		"${ErrorProneChecksError}",
 		"${ErrorProneChecksWarning}",
 		"${ErrorProneChecksDefaultDisabled}",
-	}, " "))
+	})
 }
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index 6cb61f3..fc63f4d 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -34,6 +34,7 @@
 	pctx.SourcePathVariable("KotlinKaptJar", "external/kotlinc/lib/kotlin-annotation-processing.jar")
 	pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar")
 	pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
+	pctx.SourcePathVariable("KotlinAbiGenPluginJar", "external/kotlinc/lib/jvm-abi-gen.jar")
 
 	// These flags silence "Illegal reflective access" warnings when running kapt in OpenJDK9+
 	pctx.StaticVariable("KaptSuppressJDK9Warnings", strings.Join([]string{
@@ -47,4 +48,9 @@
 	pctx.StaticVariable("KotlincSuppressJDK9Warnings", strings.Join([]string{
 		"-J--add-opens=java.base/java.util=ALL-UNNAMED", // https://youtrack.jetbrains.com/issue/KT-43704
 	}, " "))
+
+	pctx.StaticVariable("KotlincGlobalFlags", strings.Join([]string{
+		// b/222162908: prevent kotlinc from reading /tmp/build.txt
+		"-Didea.plugins.compatible.build=999.SNAPSHOT",
+	}, " "))
 }
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
new file mode 100644
index 0000000..cf39746
--- /dev/null
+++ b/java/core-libraries/Android.bp
@@ -0,0 +1,225 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//
+// Definitions for building the Android core libraries, i.e. ART, I18n and
+// Conscrypt.
+//
+// These are here as the definitions are used by the build itself and include
+// parts from all three of those modules.
+//
+
+// A stubs target containing the parts of the public SDK API provided by the
+// core libraries.
+//
+// Don't use this directly, use "sdk_version: core_current".
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+dist_targets = [
+    "sdk",
+    "win_sdk",
+]
+
+java_library {
+    name: "core.current.stubs",
+    visibility: ["//visibility:public"],
+    static_libs: [
+        "art.module.public.api.stubs",
+        "conscrypt.module.public.api.stubs",
+        "i18n.module.public.api.stubs",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+
+    dist: {
+        targets: dist_targets,
+    },
+}
+
+// Distributed with the SDK for turning into system modules to compile apps
+// against.
+//
+// Also, produces dist files that are used by the
+// prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
+// directory.
+java_library {
+    name: "core-current-stubs-for-system-modules",
+    visibility: ["//development/sdk"],
+    static_libs: [
+        "core.current.stubs",
+        // This one is not on device but it's needed when javac compiles code
+        // containing lambdas.
+        "core-lambda-stubs-for-system-modules",
+        // This one is not on device but it's needed when javac compiles code
+        // containing @Generated annotations produced by some code generation
+        // tools.
+        // See http://b/123891440.
+        "core-generated-annotation-stubs",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    dists: [
+        {
+            // Legacy dist location for the public file.
+            dest: "core-for-system-modules.jar",
+            targets: dist_targets,
+        },
+        {
+            dest: "system-modules/public/core-for-system-modules.jar",
+            targets: dist_targets,
+        },
+    ],
+}
+
+// Used when compiling higher-level code against core.current.stubs.
+java_system_modules {
+    name: "core-public-stubs-system-modules",
+    visibility: ["//visibility:public"],
+    libs: [
+        "core-current-stubs-for-system-modules",
+    ],
+}
+
+// A stubs target containing the parts of the public SDK & @SystemApi(MODULE_LIBRARIES) API
+// provided by the core libraries.
+//
+// Don't use this directly, use "sdk_version: module_current".
+java_library {
+    name: "core.module_lib.stubs",
+    static_libs: [
+        "art.module.public.api.stubs.module_lib",
+
+        // Replace the following with the module-lib correspondence when Conscrypt or i18N module
+        // provides @SystemApi(MODULE_LIBRARIES). Currently, assume that only ART module provides
+        // @SystemApi(MODULE_LIBRARIES).
+        "conscrypt.module.public.api.stubs",
+        "i18n.module.public.api.stubs",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    visibility: ["//visibility:private"],
+}
+
+// Produces a dist file that is used by the
+// prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
+// directory.
+java_library {
+    name: "core-module-lib-stubs-for-system-modules",
+    visibility: ["//visibility:private"],
+    static_libs: [
+        "core.module_lib.stubs",
+        // This one is not on device but it's needed when javac compiles code
+        // containing lambdas.
+        "core-lambda-stubs-for-system-modules",
+        // This one is not on device but it's needed when javac compiles code
+        // containing @Generated annotations produced by some code generation
+        // tools.
+        // See http://b/123891440.
+        "core-generated-annotation-stubs",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    dist: {
+        dest: "system-modules/module-lib/core-for-system-modules.jar",
+        targets: dist_targets,
+    },
+}
+
+// Used when compiling higher-level code with sdk_version "module_current"
+java_system_modules {
+    name: "core-module-lib-stubs-system-modules",
+    libs: [
+        "core-module-lib-stubs-for-system-modules",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// Ideally this should be a restricted allowlist but there are hundreds of modules that depend on
+// this.
+// TODO(http://b/134561230) - limit the number of dependents on this.
+core_platform_visibility = ["//visibility:public"]
+
+// Libraries containing the core platform API stubs for the core libraries.
+//
+// Although this stubs library is primarily used by the Java compiler / build to indicate
+// the core platform API surface area, compile_dex: true is used so that the Core Platform
+// API annotations are available to the dex tools that enable enforcement of runtime
+// accessibility. b/119068555
+java_library {
+    name: "legacy.core.platform.api.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "art.module.public.api.stubs.module_lib",
+        "conscrypt.module.platform.api.stubs",
+        "legacy.i18n.module.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+}
+
+java_library {
+    name: "stable.core.platform.api.stubs",
+    visibility: core_platform_visibility,
+    hostdex: true,
+    compile_dex: true,
+
+    sdk_version: "none",
+    system_modules: "none",
+    static_libs: [
+        "art.module.public.api.stubs.module_lib",
+        // conscrypt only has a stable version, so it is okay to depend on it here:
+        "conscrypt.module.platform.api.stubs",
+        "stable.i18n.module.platform.api.stubs",
+    ],
+    patch_module: "java.base",
+}
+
+// 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",
+        // This one is not on device but it's needed when javac compiles code
+        // containing lambdas.
+        "core-lambda-stubs-for-system-modules",
+        // This one is not on device but it's needed when javac compiles code
+        // containing @Generated annotations produced by some code generation
+        // tools.
+        // See http://b/123891440.
+        "core-generated-annotation-stubs",
+    ],
+}
+
+java_system_modules {
+    name: "stable-core-platform-api-stubs-system-modules",
+    visibility: core_platform_visibility,
+    libs: [
+        "stable.core.platform.api.stubs",
+        // This one is not on device but it's needed when javac compiles code
+        // containing lambdas.
+        "core-lambda-stubs-for-system-modules",
+        // This one is not on device but it's needed when javac compiles code
+        // containing @Generated annotations produced by some code generation
+        // tools.
+        // See http://b/123891440.
+        "core-generated-annotation-stubs",
+    ],
+}
diff --git a/java/core-libraries/OWNERS b/java/core-libraries/OWNERS
new file mode 100644
index 0000000..bb3546a
--- /dev/null
+++ b/java/core-libraries/OWNERS
@@ -0,0 +1,3 @@
+include platform/external/icu:/OWNERS
+include platform/external/conscrypt:/OWNERS
+include platform/libcore:/OWNERS
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 39fb04a..4abdcc6 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -118,7 +118,7 @@
 		TransformJarsToJar(ctx, outputFile, "combine", d.implementationAndResourceJars,
 			android.OptionalPath{}, false, nil, nil)
 		d.combinedImplementationJar = outputFile
-	} else {
+	} else if len(d.implementationAndResourceJars) == 1 {
 		d.combinedImplementationJar = d.implementationAndResourceJars[0]
 	}
 
@@ -127,7 +127,7 @@
 		TransformJarsToJar(ctx, outputFile, "turbine combine", d.headerJars,
 			android.OptionalPath{}, false, nil, []string{"META-INF/TRANSITIVE"})
 		d.combinedHeaderJar = outputFile
-	} else {
+	} else if len(d.headerJars) == 1 {
 		d.combinedHeaderJar = d.headerJars[0]
 	}
 
@@ -174,7 +174,9 @@
 	return android.AndroidMkData{
 		Class:      "JAVA_LIBRARIES",
 		OutputFile: android.OptionalPathForPath(d.combinedImplementationJar),
-		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		// Make does not support Windows Java modules
+		Disabled: d.Os() == android.Windows,
+		Include:  "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
 		Extra: []android.AndroidMkExtraFunc{
 			func(w io.Writer, outputFile android.Path) {
 				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
diff --git a/java/dex.go b/java/dex.go
index 7898e9d..84665e7 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -32,6 +32,9 @@
 	// list of module-specific flags that will be used for dex compiles
 	Dxflags []string `android:"arch_variant"`
 
+	// A list of files containing rules that specify the classes to keep in the main dex file.
+	Main_dex_rules []string `android:"path"`
+
 	Optimize struct {
 		// If false, disable all optimization.  Defaults to true for android_app and android_test
 		// modules, false for java_library and java_test modules.
@@ -69,6 +72,9 @@
 	// This defaults to reasonable value based on module and should not be set.
 	// It exists only to support ART tests.
 	Uncompress_dex *bool
+
+	// Exclude kotlinc generate files: *.kotlin_module, *.kotlin_builtins. Defaults to false.
+	Exclude_kotlinc_generated_files *bool
 }
 
 type dexer struct {
@@ -87,11 +93,14 @@
 var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8",
 	blueprint.RuleParams{
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
-			`$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` +
+			`mkdir -p $$(dirname $tmpJar) && ` +
+			`${config.Zip2ZipCmd} -i $in -o $tmpJar -x '**/*.dex' && ` +
+			`$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $tmpJar && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
-			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`,
+			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in`,
 		CommandDeps: []string{
 			"${config.D8Cmd}",
+			"${config.Zip2ZipCmd}",
 			"${config.SoongZipCmd}",
 			"${config.MergeZipsCmd}",
 		},
@@ -110,25 +119,31 @@
 			ExecStrategy: "${config.RED8ExecStrategy}",
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
-	}, []string{"outDir", "d8Flags", "zipFlags"}, nil)
+	}, []string{"outDir", "d8Flags", "zipFlags", "tmpJar", "mergeZipsFlags"}, nil)
 
 var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8",
 	blueprint.RuleParams{
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
 			`rm -f "$outDict" && rm -rf "${outUsageDir}" && ` +
 			`mkdir -p $$(dirname ${outUsage}) && ` +
-			`$r8Template${config.R8Cmd} ${config.DexFlags} -injars $in --output $outDir ` +
+			`mkdir -p $$(dirname $tmpJar) && ` +
+			`${config.Zip2ZipCmd} -i $in -o $tmpJar -x '**/*.dex' && ` +
+			`$r8Template${config.R8Cmd} ${config.DexFlags} -injars $tmpJar --output $outDir ` +
 			`--no-data-resources ` +
 			`-printmapping ${outDict} ` +
 			`-printusage ${outUsage} ` +
+			`--deps-file ${out}.d ` +
 			`$r8Flags && ` +
 			`touch "${outDict}" "${outUsage}" && ` +
 			`${config.SoongZipCmd} -o ${outUsageZip} -C ${outUsageDir} -f ${outUsage} && ` +
 			`rm -rf ${outUsageDir} && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
-			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`,
+			`${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in`,
+		Depfile: "${out}.d",
+		Deps:    blueprint.DepsGCC,
 		CommandDeps: []string{
 			"${config.R8Cmd}",
+			"${config.Zip2ZipCmd}",
 			"${config.SoongZipCmd}",
 			"${config.MergeZipsCmd}",
 		},
@@ -156,15 +171,22 @@
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
 	}, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir",
-		"r8Flags", "zipFlags"}, []string{"implicits"})
+		"r8Flags", "zipFlags", "tmpJar", "mergeZipsFlags"}, []string{"implicits"})
 
-func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string {
-	flags := d.dexProperties.Dxflags
+func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
+	minSdkVersion android.SdkSpec) (flags []string, deps android.Paths) {
+
+	flags = d.dexProperties.Dxflags
 	// Translate all the DX flags to D8 ones until all the build files have been migrated
 	// to D8 flags. See: b/69377755
 	flags = android.RemoveListFromList(flags,
 		[]string{"--core-library", "--dex", "--multi-dex"})
 
+	for _, f := range android.PathsForModuleSrc(ctx, d.dexProperties.Main_dex_rules) {
+		flags = append(flags, "--main-dex-rules", f.String())
+		deps = append(deps, f)
+	}
+
 	if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" {
 		flags = append(flags, "--debug")
 	}
@@ -181,15 +203,15 @@
 	}
 
 	flags = append(flags, "--min-api "+strconv.Itoa(effectiveVersion.FinalOrFutureInt()))
-	return flags
+	return flags, deps
 }
 
 func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) {
 	d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...)
-	d8Flags = append(d8Flags, flags.classpath.FormRepeatedClassPath("--lib ")...)
+	d8Flags = append(d8Flags, flags.dexClasspath.FormRepeatedClassPath("--lib ")...)
 
 	d8Deps = append(d8Deps, flags.bootClasspath...)
-	d8Deps = append(d8Deps, flags.classpath...)
+	d8Deps = append(d8Deps, flags.dexClasspath...)
 
 	return d8Flags, d8Deps
 }
@@ -212,11 +234,11 @@
 
 	r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars"))
 	r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars"))
-	r8Flags = append(r8Flags, flags.classpath.FormJavaClassPath("-libraryjars"))
+	r8Flags = append(r8Flags, flags.dexClasspath.FormJavaClassPath("-libraryjars"))
 
 	r8Deps = append(r8Deps, proguardRaiseDeps...)
 	r8Deps = append(r8Deps, flags.bootClasspath...)
-	r8Deps = append(r8Deps, flags.classpath...)
+	r8Deps = append(r8Deps, flags.dexClasspath...)
 
 	flagFiles := android.Paths{
 		android.PathForSource(ctx, "build/make/core/proguard.flags"),
@@ -238,6 +260,15 @@
 
 	if BoolDefault(opt.Proguard_compatibility, true) {
 		r8Flags = append(r8Flags, "--force-proguard-compatibility")
+	} else {
+		// TODO(b/213833843): Allow configuration of the prefix via a build variable.
+		var sourceFilePrefix = "go/retraceme "
+		var sourceFileTemplate = "\"" + sourceFilePrefix + "%MAP_ID\""
+		// TODO(b/200967150): Also tag the source file in compat builds.
+		if Bool(opt.Optimize) || Bool(opt.Obfuscate) {
+			r8Flags = append(r8Flags, "--map-id-template", "%MAP_HASH")
+			r8Flags = append(r8Flags, "--source-file-template", sourceFileTemplate)
+		}
 	}
 
 	// TODO(ccross): Don't shrink app instrumentation tests by default.
@@ -273,13 +304,20 @@
 	// Compile classes.jar into classes.dex and then javalib.jar
 	javalibJar := android.PathForModuleOut(ctx, "dex", jarName).OutputPath
 	outDir := android.PathForModuleOut(ctx, "dex")
+	tmpJar := android.PathForModuleOut(ctx, "withres-withoutdex", jarName)
 
 	zipFlags := "--ignore_missing_files"
 	if proptools.Bool(d.dexProperties.Uncompress_dex) {
 		zipFlags += " -L 0"
 	}
 
-	commonFlags := d.dexCommonFlags(ctx, minSdkVersion)
+	commonFlags, commonDeps := d.dexCommonFlags(ctx, minSdkVersion)
+
+	// Exclude kotlinc generated files when "exclude_kotlinc_generated_files" is set to true.
+	mergeZipsFlags := ""
+	if proptools.BoolDefault(d.dexProperties.Exclude_kotlinc_generated_files, false) {
+		mergeZipsFlags = "-stripFile META-INF/*.kotlin_module -stripFile **/*.kotlin_builtins"
+	}
 
 	useR8 := d.effectiveOptimizeEnabled()
 	if useR8 {
@@ -291,15 +329,18 @@
 		proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip")
 		d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
 		r8Flags, r8Deps := d.r8Flags(ctx, flags)
+		r8Deps = append(r8Deps, commonDeps...)
 		rule := r8
 		args := map[string]string{
-			"r8Flags":     strings.Join(append(commonFlags, r8Flags...), " "),
-			"zipFlags":    zipFlags,
-			"outDict":     proguardDictionary.String(),
-			"outUsageDir": proguardUsageDir.String(),
-			"outUsage":    proguardUsage.String(),
-			"outUsageZip": proguardUsageZip.String(),
-			"outDir":      outDir.String(),
+			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
+			"zipFlags":       zipFlags,
+			"outDict":        proguardDictionary.String(),
+			"outUsageDir":    proguardUsageDir.String(),
+			"outUsage":       proguardUsage.String(),
+			"outUsageZip":    proguardUsageZip.String(),
+			"outDir":         outDir.String(),
+			"tmpJar":         tmpJar.String(),
+			"mergeZipsFlags": mergeZipsFlags,
 		}
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") {
 			rule = r8RE
@@ -316,6 +357,7 @@
 		})
 	} else {
 		d8Flags, d8Deps := d8Flags(flags)
+		d8Deps = append(d8Deps, commonDeps...)
 		rule := d8
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") {
 			rule = d8RE
@@ -327,9 +369,11 @@
 			Input:       classesJar,
 			Implicits:   d8Deps,
 			Args: map[string]string{
-				"d8Flags":  strings.Join(append(commonFlags, d8Flags...), " "),
-				"zipFlags": zipFlags,
-				"outDir":   outDir.String(),
+				"d8Flags":        strings.Join(append(commonFlags, d8Flags...), " "),
+				"zipFlags":       zipFlags,
+				"outDir":         outDir.String(),
+				"tmpJar":         tmpJar.String(),
+				"mergeZipsFlags": mergeZipsFlags,
 			},
 		})
 	}
diff --git a/java/dex_test.go b/java/dex_test.go
new file mode 100644
index 0000000..fbdccb6
--- /dev/null
+++ b/java/dex_test.go
@@ -0,0 +1,103 @@
+// 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 java
+
+import (
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestR8(t *testing.T) {
+	result := PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd.RunTestWithBp(t, `
+		android_app {
+			name: "app",
+			srcs: ["foo.java"],
+			libs: ["lib"],
+			static_libs: ["static_lib"],
+			platform_apis: true,
+		}
+
+		java_library {
+			name: "lib",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "static_lib",
+			srcs: ["foo.java"],
+		}
+	`)
+
+	app := result.ModuleForTests("app", "android_common")
+	lib := result.ModuleForTests("lib", "android_common")
+	staticLib := result.ModuleForTests("static_lib", "android_common")
+
+	appJavac := app.Rule("javac")
+	appR8 := app.Rule("r8")
+	libHeader := lib.Output("turbine-combined/lib.jar").Output
+	staticLibHeader := staticLib.Output("turbine-combined/static_lib.jar").Output
+
+	android.AssertStringDoesContain(t, "expected lib header jar in app javac classpath",
+		appJavac.Args["classpath"], libHeader.String())
+	android.AssertStringDoesContain(t, "expected static_lib header jar in app javac classpath",
+		appJavac.Args["classpath"], staticLibHeader.String())
+
+	android.AssertStringDoesContain(t, "expected lib header jar in app r8 classpath",
+		appR8.Args["r8Flags"], libHeader.String())
+	android.AssertStringDoesNotContain(t, "expected no  static_lib header jar in app javac classpath",
+		appR8.Args["r8Flags"], staticLibHeader.String())
+}
+
+func TestD8(t *testing.T) {
+	result := PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["foo.java"],
+			libs: ["lib"],
+			static_libs: ["static_lib"],
+			installable: true,
+		}
+
+		java_library {
+			name: "lib",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "static_lib",
+			srcs: ["foo.java"],
+		}
+	`)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	lib := result.ModuleForTests("lib", "android_common")
+	staticLib := result.ModuleForTests("static_lib", "android_common")
+
+	fooJavac := foo.Rule("javac")
+	fooD8 := foo.Rule("d8")
+	libHeader := lib.Output("turbine-combined/lib.jar").Output
+	staticLibHeader := staticLib.Output("turbine-combined/static_lib.jar").Output
+
+	android.AssertStringDoesContain(t, "expected lib header jar in foo javac classpath",
+		fooJavac.Args["classpath"], libHeader.String())
+	android.AssertStringDoesContain(t, "expected static_lib header jar in foo javac classpath",
+		fooJavac.Args["classpath"], staticLibHeader.String())
+
+	android.AssertStringDoesContain(t, "expected lib header jar in foo d8 classpath",
+		fooD8.Args["d8Flags"], libHeader.String())
+	android.AssertStringDoesNotContain(t, "expected no  static_lib header jar in foo javac classpath",
+		fooD8.Args["d8Flags"], staticLibHeader.String())
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 2e46d74..7c5f055 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -15,13 +15,65 @@
 package java
 
 import (
+	"path/filepath"
+	"strings"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
 
-type dexpreopterInterface interface {
+type DexpreopterInterface interface {
 	IsInstallable() bool // Structs that embed dexpreopter must implement this.
 	dexpreoptDisabled(ctx android.BaseModuleContext) bool
+	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
+	AndroidMkEntriesForApex() []android.AndroidMkEntries
+}
+
+type dexpreopterInstall struct {
+	// A unique name to distinguish an output from others for the same java library module. Usually in
+	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
+	name string
+
+	// The name of the input java module.
+	moduleName string
+
+	// The path to the dexpreopt output on host.
+	outputPathOnHost android.Path
+
+	// The directory on the device for the output to install to.
+	installDirOnDevice android.InstallPath
+
+	// The basename (the last segment of the path) for the output to install as.
+	installFileOnDevice string
+}
+
+// The full module name of the output in the makefile.
+func (install *dexpreopterInstall) FullModuleName() string {
+	return install.moduleName + install.SubModuleName()
+}
+
+// The sub-module name of the output in the makefile (the name excluding the java module name).
+func (install *dexpreopterInstall) SubModuleName() string {
+	return "-dexpreopt-" + install.name
+}
+
+// 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 {
@@ -33,13 +85,16 @@
 	isApp               bool
 	isTest              bool
 	isPresignedPrebuilt bool
+	preventInstall      bool
 
 	manifestFile        android.Path
 	statusFile          android.WritablePath
 	enforceUsesLibs     bool
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
-	builtInstalled string
+	// See the `dexpreopt` function for details.
+	builtInstalled        string
+	builtInstalledForApex []dexpreopterInstall
 
 	// The config is used for two purposes:
 	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
@@ -74,14 +129,24 @@
 	dexpreopt.DexpreoptRunningInSoong = true
 }
 
+func isApexVariant(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return !apexInfo.IsForPlatform()
+}
+
+func forPrebuiltApex(ctx android.BaseModuleContext) bool {
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	return apexInfo.ForPrebuiltApex
+}
+
+func moduleName(ctx android.BaseModuleContext) string {
+	// Remove the "prebuilt_" prefix if the module is from a prebuilt because the prefix is not
+	// expected by dexpreopter.
+	return android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
+}
+
 func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext) bool {
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	if global.DisablePreopt {
-		return true
-	}
-
-	if inList(ctx.ModuleName(), global.DisablePreoptModules) {
+	if !ctx.Device() {
 		return true
 	}
 
@@ -93,36 +158,80 @@
 		return true
 	}
 
-	if !ctx.Module().(dexpreopterInterface).IsInstallable() {
+	// If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be
+	// dexpreopted.
+	if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) {
 		return true
 	}
 
-	if ctx.Host() {
+	if !android.IsModulePreferred(ctx.Module()) {
 		return true
 	}
 
-	// Don't preopt APEX variant module
-	if apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo); !apexInfo.IsForPlatform() {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	if global.DisablePreopt {
 		return true
 	}
 
+	if inList(moduleName(ctx), global.DisablePreoptModules) {
+		return true
+	}
+
+	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+	if isApexVariant(ctx) {
+		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
+		// building the entire system image.
+		if !isApexSystemServerJar || ctx.Config().UnbundledBuild() {
+			return true
+		}
+	} else {
+		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
+		if isApexSystemServerJar {
+			return true
+		}
+	}
+
 	// TODO: contains no java code
 
 	return false
 }
 
 func dexpreoptToolDepsMutator(ctx android.BottomUpMutatorContext) {
-	if d, ok := ctx.Module().(dexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
+	if d, ok := ctx.Module().(DexpreopterInterface); !ok || d.dexpreoptDisabled(ctx) {
 		return
 	}
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
-func odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, installPath android.InstallPath) bool {
+	return dexpreopt.OdexOnSystemOtherByName(moduleName(ctx), android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
+}
+
+// Returns the install path of the dex jar of a module.
+//
+// Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
+// than the `name` in the path `/apex/<name>` as suggested in its comment.
+//
+// This function is on a best-effort basis. It cannot handle the case where an APEX jar is not a
+// system server jar, which is fine because we currently only preopt system server jars for APEXes.
+func (d *dexpreopter) getInstallPath(
+	ctx android.ModuleContext, defaultInstallPath android.InstallPath) android.InstallPath {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx)) {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, moduleName(ctx))
+		return android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexLocation, "/"))
+	}
+	if !d.dexpreoptDisabled(ctx) && isApexVariant(ctx) &&
+		filepath.Base(defaultInstallPath.PartitionDir()) != "apex" {
+		ctx.ModuleErrorf("unable to get the install path of the dex jar for dexpreopt")
+	}
+	return defaultInstallPath
 }
 
 func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.WritablePath) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
 	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
 	// the dexpreopter struct hasn't been fully initialized before we're called,
 	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
@@ -133,7 +242,7 @@
 
 	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
 
-	providesUsesLib := ctx.ModuleName()
+	providesUsesLib := moduleName(ctx)
 	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
 		name := ulib.ProvidesUsesLib()
 		if name != nil {
@@ -141,24 +250,20 @@
 		}
 	}
 
-	// If it is neither app nor test, make config files regardless of its dexpreopt setting.
+	// 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.
-	// TODO(b/158843648): The config for apps should be generated as well regardless of setting.
-	if (d.isApp || d.isTest) && d.dexpreoptDisabled(ctx) {
+	if d.isTest && d.dexpreoptDisabled(ctx) {
 		return
 	}
 
-	global := dexpreopt.GetGlobalConfig(ctx)
-
-	isSystemServerJar := global.SystemServerJars.ContainsJar(ctx.ModuleName())
+	isSystemServerJar := global.AllSystemServerJars(ctx).ContainsJar(moduleName(ctx))
 
 	bootImage := defaultBootImageConfig(ctx)
 	if global.UseArtImage {
 		bootImage = artBootImageConfig(ctx)
 	}
 
-	// System server jars are an exception: they are dexpreopted without updatable bootclasspath.
-	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp && !isSystemServerJar)
+	dexFiles, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp)
 
 	targets := ctx.MultiTargets()
 	if len(targets) == 0 {
@@ -200,15 +305,15 @@
 			profileIsTextListing = true
 		} else if global.ProfileDir != "" {
 			profileClassListing = android.ExistentPathForSource(ctx,
-				global.ProfileDir, ctx.ModuleName()+".prof")
+				global.ProfileDir, moduleName(ctx)+".prof")
 		}
 	}
 
 	// Full dexpreopt config, used to create dexpreopt build rules.
 	dexpreoptConfig := &dexpreopt.ModuleConfig{
-		Name:            ctx.ModuleName(),
+		Name:            moduleName(ctx),
 		DexLocation:     dexLocation,
-		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
+		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", moduleName(ctx)+".jar").OutputPath,
 		DexPath:         dexJarFile,
 		ManifestPath:    android.OptionalPathForPath(d.manifestFile),
 		UncompressedDex: d.uncompressedDex,
@@ -257,5 +362,47 @@
 
 	dexpreoptRule.Build("dexpreopt", "dexpreopt")
 
-	d.builtInstalled = dexpreoptRule.Installs().String()
+	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
+
+	for _, install := range dexpreoptRule.Installs() {
+		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
+		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+		installBase := filepath.Base(install.To)
+		arch := filepath.Base(installDir)
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+
+		if isApexSystemServerJar {
+			// APEX variants of java libraries are hidden from Make, so their dexpreopt
+			// outputs need special handling. Currently, for APEX variants of java
+			// libraries, only those in the system server classpath are handled here.
+			// Preopting of boot classpath jars in the ART APEX are handled in
+			// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
+			// The installs will be handled by Make as sub-modules of the java library.
+			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
+				name:                arch + "-" + installBase,
+				moduleName:          moduleName(ctx),
+				outputPathOnHost:    install.From,
+				installDirOnDevice:  installPath,
+				installFileOnDevice: installBase,
+			})
+		} else if !d.preventInstall {
+			ctx.InstallFile(installPath, installBase, install.From)
+		}
+	}
+
+	if !isApexSystemServerJar {
+		d.builtInstalled = dexpreoptRule.Installs().String()
+	}
+}
+
+func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
+	return d.builtInstalledForApex
+}
+
+func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
+	var entries []android.AndroidMkEntries
+	for _, install := range d.builtInstalledForApex {
+		entries = append(entries, install.ToMakeEntries())
+	}
+	return entries
 }
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 19c65ca..3d91aec 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -16,7 +16,6 @@
 
 import (
 	"path/filepath"
-	"sort"
 	"strings"
 
 	"android/soong/android"
@@ -154,12 +153,23 @@
 // 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 either a simple
-// name (if the library is a part of the Platform), or a colon-separated pair <apex, name> (if the
-// library is a part of a non-updatable APEX).
+// 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.
 //
-// A related variable PRODUCT_UPDATABLE_BOOT_JARS contains bootclasspath libraries that are in
-// updatable APEXes. They are not included in the boot image.
+// 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
@@ -246,6 +256,10 @@
 	// Subdirectory where the image files on device are installed.
 	installDirOnDevice string
 
+	// Install path of the boot image profile if it needs to be installed in the APEX, or empty if not
+	// needed.
+	profileInstallPathInApex string
+
 	// A list of (location, jar) pairs for the Java modules in this image.
 	modules android.ConfiguredJarList
 
@@ -262,8 +276,17 @@
 	// 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.
@@ -296,10 +319,16 @@
 	// 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.
+	// 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.
@@ -371,6 +400,11 @@
 	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
@@ -489,7 +523,21 @@
 		dst := dstBootJarsByModule[name]
 
 		if src == nil {
-			ctx.ModuleErrorf("module %s does not provide a dex boot jar", name)
+			// 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 {
@@ -512,14 +560,14 @@
 }
 
 // buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the
-// android.BuildOs OsType, i.e. the type of OS on which the build is being running.
+// 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, android.BuildOs)
+	buildBootImageForOsType(ctx, image, profile, ctx.Config().BuildOS)
 }
 
 // buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType
@@ -611,7 +659,6 @@
 		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx)
 
 	if profile != nil {
-		cmd.FlagWithArg("--compiler-filter=", "speed-profile")
 		cmd.FlagWithInput("--profile-file=", profile)
 	}
 
@@ -642,6 +689,13 @@
 		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).
@@ -680,6 +734,7 @@
 
 	var vdexInstalls android.RuleBuilderInstalls
 	var unstrippedInstalls android.RuleBuilderInstalls
+	var deviceInstalls android.RuleBuilderInstalls
 
 	for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") {
 		cmd.ImplicitOutput(artOrOat)
@@ -705,12 +760,22 @@
 			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.
@@ -756,11 +821,15 @@
 		FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps).
 		FlagWithOutput("--reference-profile-file=", profile)
 
-	rule.Install(profile, "/system/etc/boot-image.prof")
+	if image == defaultBootImageConfig(ctx) {
+		rule.Install(profile, "/system/etc/boot-image.prof")
+		image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+		image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
+	}
 
 	rule.Build("bootJarsProfile", "profile boot jars")
 
-	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
+	image.profilePathOnHost = profile
 
 	return profile
 }
@@ -793,44 +862,11 @@
 	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
 }
 
-// generateUpdatableBcpPackagesRule generates the rule to create the updatable-bcp-packages.txt file
-// and returns a path to the generated file.
-func generateUpdatableBcpPackagesRule(ctx android.ModuleContext, image *bootImageConfig, updatableModules []android.Module) android.WritablePath {
-	// Collect `permitted_packages` for updatable boot jars.
-	var updatablePackages []string
-	for _, module := range updatableModules {
-		if j, ok := module.(PermittedPackagesForUpdatableBootJars); ok {
-			pp := j.PermittedPackagesForUpdatableBootJars()
-			if len(pp) > 0 {
-				updatablePackages = append(updatablePackages, pp...)
-			} else {
-				ctx.OtherModuleErrorf(module, "Missing permitted_packages")
-			}
-		}
-	}
-
-	// Sort updatable packages to ensure deterministic ordering.
-	sort.Strings(updatablePackages)
-
-	updatableBcpPackagesName := "updatable-bcp-packages.txt"
-	updatableBcpPackages := image.dir.Join(ctx, updatableBcpPackagesName)
-
-	// WriteFileRule automatically adds the last end-of-line.
-	android.WriteFileRule(ctx, updatableBcpPackages, strings.Join(updatablePackages, "\n"))
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Install(updatableBcpPackages, "/system/etc/"+updatableBcpPackagesName)
-	// TODO: Rename `profileInstalls` to `extraInstalls`?
-	// Maybe even move the field out of the bootImageConfig into some higher level type?
-	image.profileInstalls = append(image.profileInstalls, rule.Installs()...)
-
-	return updatableBcpPackages
-}
-
 func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) {
 	var allPhonies android.Paths
 	for _, image := range image.variants {
@@ -892,6 +928,9 @@
 	image := d.defaultBootImage
 	if image != nil {
 		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)
@@ -917,6 +956,9 @@
 				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 := current.getAnyAndroidVariant().imageLocations()
 			ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_HOST"+current.name, strings.Join(imageLocationsOnHost, ":"))
diff --git a/java/dexpreopt_check.go b/java/dexpreopt_check.go
new file mode 100644
index 0000000..83c088c
--- /dev/null
+++ b/java/dexpreopt_check.go
@@ -0,0 +1,96 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"strings"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/pathtools"
+)
+
+func init() {
+	RegisterDexpreoptCheckBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterDexpreoptCheckBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonModuleType("dexpreopt_systemserver_check", dexpreoptSystemserverCheckFactory)
+}
+
+// A build-time check to verify if all compilation artifacts of system server jars are installed
+// into the system image. When the check fails, it means that dexpreopting is not working for some
+// system server jars and needs to be fixed.
+// This singleton module generates a list of the paths to the artifacts based on
+// PRODUCT_SYSTEM_SERVER_JARS and PRODUCT_APEX_SYSTEM_SERVER_JARS, and passes it to Make via a
+// variable. Make will then do the actual check.
+// Currently, it only checks artifacts of modules defined in Soong. Artifacts of modules defined in
+// Makefile are generated by a script generated by dexpreopt_gen, and their existence is unknown to
+// Make and Ninja.
+type dexpreoptSystemserverCheck struct {
+	android.SingletonModuleBase
+
+	// Mapping from the module name to the install paths to the compilation artifacts.
+	artifactsByModuleName map[string][]string
+
+	// The install paths to the compilation artifacts.
+	artifacts []string
+}
+
+func dexpreoptSystemserverCheckFactory() android.SingletonModule {
+	m := &dexpreoptSystemserverCheck{}
+	m.artifactsByModuleName = make(map[string][]string)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+func getInstallPath(ctx android.ModuleContext, location string) android.InstallPath {
+	return android.PathForModuleInPartitionInstall(
+		ctx, "", strings.TrimPrefix(location, "/"))
+}
+
+func (m *dexpreoptSystemserverCheck) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	targets := ctx.Config().Targets[android.Android]
+
+	// The check should be skipped on unbundled builds because system server jars are not preopted on
+	// unbundled builds since the artifacts are installed into the system image, not the APEXes.
+	if global.DisablePreopt || len(targets) == 0 || ctx.Config().UnbundledBuild() {
+		return
+	}
+
+	systemServerJars := global.AllSystemServerJars(ctx)
+	for _, jar := range systemServerJars.CopyOfJars() {
+		dexLocation := dexpreopt.GetSystemServerDexLocation(ctx, global, jar)
+		odexLocation := dexpreopt.ToOdexPath(dexLocation, targets[0].Arch.ArchType)
+		odexPath := getInstallPath(ctx, odexLocation)
+		vdexPath := getInstallPath(ctx, pathtools.ReplaceExtension(odexLocation, "vdex"))
+		m.artifactsByModuleName[jar] = []string{odexPath.String(), vdexPath.String()}
+	}
+}
+
+func (m *dexpreoptSystemserverCheck) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+	// Only keep modules defined in Soong.
+	ctx.VisitAllModules(func(module android.Module) {
+		if artifacts, ok := m.artifactsByModuleName[module.Name()]; ok {
+			m.artifacts = append(m.artifacts, artifacts...)
+		}
+	})
+}
+
+func (m *dexpreoptSystemserverCheck) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("DEXPREOPT_SYSTEMSERVER_ARTIFACTS", strings.Join(m.artifacts, " "))
+}
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index b13955f..4d0bd09 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -32,7 +32,7 @@
 		}
 	}
 	// We may also need the images on host in order to run host-based tests.
-	for _, target := range ctx.Config().Targets[android.BuildOs] {
+	for _, target := range ctx.Config().Targets[ctx.Config().BuildOS] {
 		targets = append(targets, target)
 	}
 
@@ -41,50 +41,59 @@
 
 var (
 	bootImageConfigKey     = android.NewOnceKey("bootImageConfig")
+	bootImageConfigRawKey  = android.NewOnceKey("bootImageConfigRaw")
 	artBootImageName       = "art"
 	frameworkBootImageName = "boot"
 )
 
-// Construct the global boot image configs.
-func genBootImageConfigs(ctx android.PathContext) map[string]*bootImageConfig {
-	return ctx.Config().Once(bootImageConfigKey, func() interface{} {
-
+func genBootImageConfigRaw(ctx android.PathContext) map[string]*bootImageConfig {
+	return ctx.Config().Once(bootImageConfigRawKey, func() interface{} {
 		global := dexpreopt.GetGlobalConfig(ctx)
-		targets := dexpreoptTargets(ctx)
-		deviceDir := android.PathForOutput(ctx, ctx.Config().DeviceName())
 
 		artModules := global.ArtApexJars
 		frameworkModules := global.BootJars.RemoveList(artModules)
 
-		artDirOnHost := "apex/art_boot_images/javalib"
-		artDirOnDevice := "apex/com.android.art/javalib"
-		frameworkSubdir := "system/framework"
-
 		// ART config for the primary boot image in the ART apex.
 		// It includes the Core Libraries.
 		artCfg := bootImageConfig{
-			name:               artBootImageName,
-			stem:               "boot",
-			installDirOnHost:   artDirOnHost,
-			installDirOnDevice: artDirOnDevice,
-			modules:            artModules,
+			name:                     artBootImageName,
+			stem:                     "boot",
+			installDirOnHost:         "apex/art_boot_images/javalib",
+			installDirOnDevice:       "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,
+			extends:              &artCfg,
+			name:                 frameworkBootImageName,
+			stem:                 "boot",
+			installDirOnHost:     frameworkSubdir,
+			installDirOnDevice:   frameworkSubdir,
+			modules:              frameworkModules,
+			preloadedClassesFile: "frameworks/base/config/preloaded-classes",
 		}
 
-		configs := map[string]*bootImageConfig{
+		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 {
@@ -142,14 +151,14 @@
 	return genBootImageConfigs(ctx)[frameworkBootImageName]
 }
 
-// Updatable boot config allows to access build/install paths of updatable boot jars without going
+// 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 updatableBootConfig struct {
-	// A list of updatable boot jars.
+type apexBootConfig struct {
+	// A list of apex boot jars.
 	modules android.ConfiguredJarList
 
-	// A list of predefined build paths to updatable boot jars. They are configured very early,
+	// 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
@@ -161,21 +170,21 @@
 	dexLocations []string
 }
 
-var updatableBootConfigKey = android.NewOnceKey("updatableBootConfig")
+var updatableBootConfigKey = android.NewOnceKey("apexBootConfig")
 
-// Returns updatable boot config.
-func GetUpdatableBootConfig(ctx android.PathContext) updatableBootConfig {
+// Returns apex boot config.
+func GetApexBootConfig(ctx android.PathContext) apexBootConfig {
 	return ctx.Config().Once(updatableBootConfigKey, func() interface{} {
-		updatableBootJars := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars
+		apexBootJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars
 
-		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars")
-		dexPaths := updatableBootJars.BuildPaths(ctx, dir)
-		dexPathsByModuleName := updatableBootJars.BuildPathsByModule(ctx, dir)
+		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "apex_bootjars")
+		dexPaths := apexBootJars.BuildPaths(ctx, dir)
+		dexPathsByModuleName := apexBootJars.BuildPathsByModule(ctx, dir)
 
-		dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android)
+		dexLocations := apexBootJars.DevicePaths(ctx.Config(), android.Android)
 
-		return updatableBootConfig{updatableBootJars, dexPaths, dexPathsByModuleName, dexLocations}
-	}).(updatableBootConfig)
+		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
@@ -188,10 +197,10 @@
 	dexLocations := bootImage.getAnyAndroidVariant().dexLocationsDeps
 
 	if withUpdatable {
-		// Updatable boot jars (they are used only in dexpreopt, but not in the boot image).
-		updBootConfig := GetUpdatableBootConfig(ctx)
-		dexPaths = append(dexPaths, updBootConfig.dexPaths...)
-		dexLocations = append(dexLocations, updBootConfig.dexLocations...)
+		// 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
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index b25dece..1c1070a 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"runtime"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -23,11 +25,17 @@
 	"android/soong/dexpreopt"
 )
 
+func init() {
+	RegisterFakeRuntimeApexMutator()
+}
+
 func TestDexpreoptEnabled(t *testing.T) {
 	tests := []struct {
-		name    string
-		bp      string
-		enabled bool
+		name        string
+		bp          string
+		moduleName  string
+		apexVariant bool
+		enabled     bool
 	}{
 		{
 			name: "app",
@@ -147,13 +155,81 @@
 				}`,
 			enabled: true,
 		},
+		{
+			name: "apex variant",
+			bp: `
+				java_library {
+					name: "foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			apexVariant: true,
+			enabled:     false,
+		},
+		{
+			name: "apex variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "apex variant of prebuilt apex system server jar",
+			bp: `
+				java_library {
+					name: "prebuilt_service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "prebuilt_service-foo",
+			apexVariant: true,
+			enabled:     true,
+		},
+		{
+			name: "platform variant of apex system server jar",
+			bp: `
+				java_library {
+					name: "service-foo",
+					installable: true,
+					srcs: ["a.java"],
+					apex_available: ["com.android.apex1"],
+				}`,
+			moduleName:  "service-foo",
+			apexVariant: false,
+			enabled:     false,
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			ctx, _ := testJava(t, test.bp)
+			preparers := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				PrepareForTestWithFakeApexMutator,
+				dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+			)
 
-			dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt")
+			result := preparers.RunTestWithBp(t, test.bp)
+			ctx := result.TestContext
+
+			moduleName := "foo"
+			if test.moduleName != "" {
+				moduleName = test.moduleName
+			}
+
+			variant := "android_common"
+			if test.apexVariant {
+				variant += "_apex1000"
+			}
+
+			dexpreopt := ctx.ModuleForTests(moduleName, variant).MaybeRule("dexpreopt")
 			enabled := dexpreopt.Rule != nil
 
 			if enabled != test.enabled {
@@ -173,9 +249,9 @@
 }
 
 func TestDex2oatToolDeps(t *testing.T) {
-	if android.BuildOs != android.Linux {
+	if runtime.GOOS != "linux" {
 		// The host binary paths checked below are build OS dependent.
-		t.Skipf("Unsupported build OS %s", android.BuildOs)
+		t.Skipf("Unsupported build OS %s", runtime.GOOS)
 	}
 
 	preparers := android.GroupFixturePreparers(
@@ -219,3 +295,145 @@
 	testDex2oatToolDep(true, true, true, prebuiltDex2oatPath)
 	testDex2oatToolDep(false, true, false, prebuiltDex2oatPath)
 }
+
+func TestDexpreoptBuiltInstalledForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+	library := module.Module().(*Library)
+
+	installs := library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 2, len(installs))
+
+	android.AssertStringEquals(t, "installs[0] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[0] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].SubModuleName())
+
+	android.AssertStringEquals(t, "installs[1] FullModuleName",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].FullModuleName())
+
+	android.AssertStringEquals(t, "installs[1] SubModuleName",
+		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].SubModuleName())
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+	library = module.Module().(*Library)
+
+	installs = library.dexpreopter.DexpreoptBuiltInstalledForApex()
+
+	android.AssertIntEquals(t, "install count", 0, len(installs))
+}
+
+func filterDexpreoptEntriesList(entriesList []android.AndroidMkEntries) []android.AndroidMkEntries {
+	var results []android.AndroidMkEntries
+	for _, entries := range entriesList {
+		if strings.Contains(entries.EntryMap["LOCAL_MODULE"][0], "-dexpreopt-") {
+			results = append(results, entries)
+		}
+	}
+	return results
+}
+
+func verifyEntries(t *testing.T, message string, expectedModule string,
+	expectedPrebuiltModuleFile string, expectedModulePath string, expectedInstalledModuleStem string,
+	entries android.AndroidMkEntries) {
+	android.AssertStringEquals(t, message+" LOCAL_MODULE", expectedModule,
+		entries.EntryMap["LOCAL_MODULE"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_MODULE_CLASS", "ETC",
+		entries.EntryMap["LOCAL_MODULE_CLASS"][0])
+
+	android.AssertStringDoesContain(t, message+" LOCAL_PREBUILT_MODULE_FILE",
+		entries.EntryMap["LOCAL_PREBUILT_MODULE_FILE"][0], expectedPrebuiltModuleFile)
+
+	android.AssertStringDoesContain(t, message+" LOCAL_MODULE_PATH",
+		entries.EntryMap["LOCAL_MODULE_PATH"][0], expectedModulePath)
+
+	android.AssertStringEquals(t, message+" LOCAL_INSTALLED_MODULE_STEM",
+		expectedInstalledModuleStem, entries.EntryMap["LOCAL_INSTALLED_MODULE_STEM"][0])
+
+	android.AssertStringEquals(t, message+" LOCAL_NOT_AVAILABLE_FOR_PLATFORM",
+		"false", entries.EntryMap["LOCAL_NOT_AVAILABLE_FOR_PLATFORM"][0])
+}
+
+func TestAndroidMkEntriesForApex(t *testing.T) {
+	preparers := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithFakeApexMutator,
+		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
+	)
+
+	// An APEX system server jar.
+	result := preparers.RunTestWithBp(t, `
+		java_library {
+			name: "service-foo",
+			installable: true,
+			srcs: ["a.java"],
+			apex_available: ["com.android.apex1"],
+		}`)
+	ctx := result.TestContext
+	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+
+	entriesList := android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 2, len(entriesList))
+
+	verifyEntries(t,
+		"entriesList[0]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		"/dexpreopt/oat/arm64/javalib.odex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		entriesList[0])
+
+	verifyEntries(t,
+		"entriesList[1]",
+		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		"/dexpreopt/oat/arm64/javalib.vdex",
+		"/system/framework/oat/arm64",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		entriesList[1])
+
+	// Not an APEX system server jar.
+	result = preparers.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			installable: true,
+			srcs: ["a.java"],
+		}`)
+	ctx = result.TestContext
+	module = ctx.ModuleForTests("foo", "android_common")
+
+	entriesList = android.AndroidMkEntriesForTest(t, ctx, module.Module())
+	entriesList = filterDexpreoptEntriesList(entriesList)
+
+	android.AssertIntEquals(t, "entries count", 0, len(entriesList))
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 869a598..023d619 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -330,7 +330,7 @@
 
 	// Process all aidl files together to support sharding them into one or more rules that produce srcjars.
 	if len(aidlSrcs) > 0 {
-		srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, flags.aidlDeps)
+		srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, nil, flags.aidlDeps)
 		outSrcFiles = append(outSrcFiles, srcJarFiles...)
 	}
 
@@ -769,8 +769,8 @@
 
 	d.Javadoc.docZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"docs.zip")
 
-	jsilver := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "jsilver.jar")
-	doclava := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "framework", "doclava.jar")
+	jsilver := ctx.Config().HostJavaToolPath(ctx, "jsilver.jar")
+	doclava := ctx.Config().HostJavaToolPath(ctx, "doclava.jar")
 
 	outDir := android.PathForModuleOut(ctx, "out")
 	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
diff --git a/java/droidstubs.go b/java/droidstubs.go
index c756815..3b1f7c0 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -25,6 +26,9 @@
 	"android/soong/remoteexec"
 )
 
+// The values allowed for Droidstubs' Api_levels_sdk_type
+var allowedApiLevelSdkTypes = []string{"public", "system", "module-lib"}
+
 func init() {
 	RegisterStubsBuildComponents(android.InitRegistrationContext)
 }
@@ -128,12 +132,15 @@
 	// whicih can be used for scheduling purposes
 	High_mem *bool
 
-	// is set to true, Metalava will allow framework SDK to contain API levels annotations.
+	// if set to true, Metalava will allow framework SDK to contain API levels annotations.
 	Api_levels_annotations_enabled *bool
 
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
+	// the sdk kind which Metalava extracts API levels annotations from. Supports 'public', 'system' and 'module-lib' for now; defaults to public.
+	Api_levels_sdk_type *string
+
 	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
 	Api_levels_jar_filename *string
 
@@ -153,6 +160,7 @@
 
 // Provider of information about API stubs, used by java_sdk_library.
 type ApiStubsProvider interface {
+	AnnotationsZip() android.Path
 	ApiFilePath
 	RemovedApiFilePath() android.Path
 
@@ -207,6 +215,10 @@
 	}
 }
 
+func (d *Droidstubs) AnnotationsZip() android.Path {
+	return d.annotationsZip
+}
+
 func (d *Droidstubs) ApiFilePath() android.Path {
 	return d.apiFilePath
 }
@@ -322,7 +334,11 @@
 		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
 		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
 			FlagWithArg("--hide ", "SuperfluousPrefix").
-			FlagWithArg("--hide ", "AnnotationExtraction")
+			FlagWithArg("--hide ", "AnnotationExtraction").
+			// b/222738070
+			FlagWithArg("--hide ", "BannedThrow").
+			// b/223382732
+			FlagWithArg("--hide ", "ChangedDefault")
 	}
 }
 
@@ -367,6 +383,7 @@
 
 	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
 
+	var dirs []string
 	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
 		if t, ok := m.(*ExportedDroiddocDir); ok {
 			for _, dep := range t.deps {
@@ -383,12 +400,41 @@
 					cmd.Implicit(dep)
 				}
 			}
-			cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename)
+
+			dirs = append(dirs, t.dir.String())
 		} else {
 			ctx.PropertyErrorf("api_levels_annotations_dirs",
 				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
 		}
 	})
+
+	// Add all relevant --android-jar-pattern patterns for Metalava.
+	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
+	// an actual file present on disk (in the order the patterns were passed). For system APIs for
+	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
+	// for older releases. Similarly, module-lib falls back to system API.
+	var sdkDirs []string
+	switch proptools.StringDefault(d.properties.Api_levels_sdk_type, "public") {
+	case "module-lib":
+		sdkDirs = []string{"module-lib", "system", "public"}
+	case "system":
+		sdkDirs = []string{"system", "public"}
+	case "public":
+		sdkDirs = []string{"public"}
+	default:
+		ctx.PropertyErrorf("api_levels_sdk_type", "needs to be one of %v", allowedApiLevelSdkTypes)
+		return
+	}
+
+	for _, sdkDir := range sdkDirs {
+		for _, dir := range dirs {
+			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, filename))
+		}
+	}
+}
+
+func metalavaUseRbe(ctx android.ModuleContext) bool {
+	return ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA")
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
@@ -399,7 +445,7 @@
 	cmd := rule.Command()
 	cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())
 
-	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
+	if metalavaUseRbe(ctx) {
 		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
 		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
 		labels := map[string]string{"type": "tool", "name": "metalava"}
@@ -434,7 +480,10 @@
 		Flag("--quiet").
 		Flag("--format=v2").
 		FlagWithArg("--repeat-errors-max ", "10").
-		FlagWithArg("--hide ", "UnresolvedImport")
+		FlagWithArg("--hide ", "UnresolvedImport").
+		FlagWithArg("--hide ", "InvalidNullabilityOverride").
+		// b/223382732
+		FlagWithArg("--hide ", "ChangedDefault")
 
 	return cmd
 }
@@ -530,7 +579,8 @@
 			`\n` +
 			`If it is not possible to do so, there are workarounds:\n` +
 			`\n` +
-			`1. You can suppress the errors with @SuppressLint("<id>")\n`
+			`1. You can suppress the errors with @SuppressLint("<id>")\n` +
+			`   where the <id> is given in brackets in the error message above.\n`
 
 		if baselineFile.Valid() {
 			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
@@ -619,7 +669,9 @@
 	}
 
 	// TODO(b/183630617): rewrapper doesn't support restat rules
-	// rule.Restat()
+	if !metalavaUseRbe(ctx) {
+		rule.Restat()
+	}
 
 	zipSyncCleanupCmd(rule, srcJarDir)
 
@@ -767,7 +819,7 @@
 
 	properties PrebuiltStubsSourcesProperties
 
-	stubsSrcJar android.ModuleOutPath
+	stubsSrcJar android.Path
 }
 
 func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
@@ -784,35 +836,39 @@
 }
 
 func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
-
 	if len(p.properties.Srcs) != 1 {
-		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
+		ctx.PropertyErrorf("srcs", "must only specify one directory path or srcjar, contains %d paths", len(p.properties.Srcs))
 		return
 	}
 
-	localSrcDir := p.properties.Srcs[0]
-	// Although PathForModuleSrc can return nil if either the path doesn't exist or
-	// the path components are invalid it won't in this case because no components
-	// are specified and the module directory must exist in order to get this far.
-	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
+	src := p.properties.Srcs[0]
+	if filepath.Ext(src) == ".srcjar" {
+		// This is a srcjar. We can use it directly.
+		p.stubsSrcJar = android.PathForModuleSrc(ctx, src)
+	} else {
+		outPath := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
 
-	// Glob the contents of the directory just in case the directory does not exist.
-	srcGlob := localSrcDir + "/**/*"
-	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
+		// This is a directory. Glob the contents just in case the directory does not exist.
+		srcGlob := src + "/**/*"
+		srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
 
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("soong_zip").
-		Flag("-write_if_changed").
-		Flag("-jar").
-		FlagWithOutput("-o ", p.stubsSrcJar).
-		FlagWithArg("-C ", srcDir.String()).
-		FlagWithRspFileInputList("-r ", p.stubsSrcJar.ReplaceExtension(ctx, "rsp"), srcPaths)
+		// Although PathForModuleSrc can return nil if either the path doesn't exist or
+		// the path components are invalid it won't in this case because no components
+		// are specified and the module directory must exist in order to get this far.
+		srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, src)
 
-	rule.Restat()
-
-	rule.Build("zip src", "Create srcjar from prebuilt source")
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			BuiltTool("soong_zip").
+			Flag("-write_if_changed").
+			Flag("-jar").
+			FlagWithOutput("-o ", outPath).
+			FlagWithArg("-C ", srcDir.String()).
+			FlagWithRspFileInputList("-r ", outPath.ReplaceExtension(ctx, "rsp"), srcPaths)
+		rule.Restat()
+		rule.Build("zip src", "Create srcjar from prebuilt source")
+		p.stubsSrcJar = outPath
+	}
 }
 
 func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index db664c1..10d99f3 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -15,7 +15,9 @@
 package java
 
 import (
+	"fmt"
 	"reflect"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -81,6 +83,75 @@
 	}
 }
 
+// runs a test for droidstubs with a customizable sdkType argument and returns
+// the list of jar patterns that is passed as `--android-jar-pattern`
+func getAndroidJarPatternsForDroidstubs(t *testing.T, sdkType string) []string {
+	ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
+		droiddoc_exported_dir {
+			name: "some-exported-dir",
+			path: "somedir",
+		}
+
+		droiddoc_exported_dir {
+			name: "some-other-exported-dir",
+			path: "someotherdir",
+		}
+
+		droidstubs {
+			name: "foo-stubs",
+			srcs: ["foo-doc/a.java"],
+			api_levels_annotations_dirs: [
+				"some-exported-dir",
+				"some-other-exported-dir",
+			],
+			api_levels_annotations_enabled: true,
+      api_levels_sdk_type: "%s",
+		}
+		`, sdkType),
+		map[string][]byte{
+			"foo-doc/a.java": nil,
+		})
+
+	m := ctx.ModuleForTests("foo-stubs", "android_common")
+	manifest := m.Output("metalava.sbox.textproto")
+	cmd := String(android.RuleBuilderSboxProtoForTests(t, manifest).Commands[0].Command)
+	r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
+	return r.FindAllString(cmd, -1)
+}
+
+func TestPublicDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "public")
+
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, patterns)
+}
+
+func TestSystemDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "system")
+
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/system/android.jar",
+		"--android-jar-pattern someotherdir/%/system/android.jar",
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, patterns)
+}
+
+func TestModuleLibDroidstubs(t *testing.T) {
+	patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib")
+
+	android.AssertArrayString(t, "order of patterns", []string{
+		"--android-jar-pattern somedir/%/module-lib/android.jar",
+		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
+		"--android-jar-pattern somedir/%/system/android.jar",
+		"--android-jar-pattern someotherdir/%/system/android.jar",
+		"--android-jar-pattern somedir/%/public/android.jar",
+		"--android-jar-pattern someotherdir/%/public/android.jar",
+	}, patterns)
+}
+
 func TestDroidstubsSandbox(t *testing.T) {
 	ctx, _ := testJavaWithFS(t, `
 		genrule {
diff --git a/java/fuzz.go b/java/fuzz.go
new file mode 100644
index 0000000..257f343
--- /dev/null
+++ b/java/fuzz.go
@@ -0,0 +1,159 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"github.com/google/blueprint/proptools"
+	"sort"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/fuzz"
+)
+
+func init() {
+	RegisterJavaFuzzBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterJavaFuzzBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("java_fuzz_host", FuzzFactory)
+	ctx.RegisterSingletonType("java_fuzz_packaging", javaFuzzPackagingFactory)
+}
+
+type JavaFuzzLibrary struct {
+	Library
+	fuzzPackagedModule fuzz.FuzzPackagedModule
+}
+
+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)
+	}
+	if j.fuzzPackagedModule.FuzzProperties.Data != nil {
+		j.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, j.fuzzPackagedModule.FuzzProperties.Data)
+	}
+	if j.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
+		j.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *j.fuzzPackagedModule.FuzzProperties.Dictionary)
+	}
+
+	if j.fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
+		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
+		android.WriteFileRule(ctx, configPath, j.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
+		j.fuzzPackagedModule.Config = configPath
+	}
+}
+
+// java_fuzz builds and links sources into a `.jar` file for the host.
+//
+// By default, a java_fuzz produces a `.jar` file containing `.class` files.
+// This jar is not suitable for installing on a device.
+func FuzzFactory() android.Module {
+	module := &JavaFuzzLibrary{}
+
+	module.addHostProperties()
+	module.Module.properties.Installable = proptools.BoolPtr(false)
+	module.AddProperties(&module.fuzzPackagedModule.FuzzProperties)
+
+	// java_fuzz packaging rules collide when both linux_glibc and linux_bionic are enabled, disable the linux_bionic variants.
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		disableLinuxBionic := struct {
+			Target struct {
+				Linux_bionic struct {
+					Enabled *bool
+				}
+			}
+		}{}
+		disableLinuxBionic.Target.Linux_bionic.Enabled = proptools.BoolPtr(false)
+		ctx.AppendProperties(&disableLinuxBionic)
+	})
+
+	module.initModuleAndImport(module)
+	android.InitSdkAwareModule(module)
+	InitJavaModule(module, android.HostSupported)
+	return module
+}
+
+// Responsible for generating rules that package fuzz targets into
+// their architecture & target/host specific zip file.
+type javaFuzzPackager struct {
+	fuzz.FuzzPackager
+}
+
+func javaFuzzPackagingFactory() android.Singleton {
+	return &javaFuzzPackager{}
+}
+
+func (s *javaFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
+	// Map between each architecture + host/device combination.
+	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
+
+	// List of individual fuzz targets.
+	s.FuzzTargets = make(map[string]bool)
+
+	ctx.VisitAllModules(func(module android.Module) {
+		// Discard non-fuzz targets.
+		javaModule, ok := module.(*JavaFuzzLibrary)
+		if !ok {
+			return
+		}
+
+		fuzzModuleValidator := fuzz.FuzzModule{
+			javaModule.ModuleBase,
+			javaModule.DefaultableModuleBase,
+			javaModule.ApexModuleBase,
+		}
+
+		if ok := fuzz.IsValid(fuzzModuleValidator); !ok || *javaModule.Module.properties.Installable {
+			return
+		}
+
+		hostOrTargetString := "target"
+		if javaModule.Host() {
+			hostOrTargetString = "host"
+		}
+		archString := javaModule.Arch().ArchType.String()
+
+		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
+		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
+
+		var files []fuzz.FileToZip
+		builder := android.NewRuleBuilder(pctx, ctx)
+
+		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
+		files = s.PackageArtifacts(ctx, module, javaModule.fuzzPackagedModule, archDir, builder)
+
+		// Add .jar
+		files = append(files, fuzz.FileToZip{javaModule.outputFile, ""})
+
+		archDirs[archOs], ok = s.BuildZipFile(ctx, module, javaModule.fuzzPackagedModule, files, builder, archDir, archString, "host", archOs, archDirs)
+		if !ok {
+			return
+		}
+
+	})
+	s.CreateFuzzPackage(ctx, archDirs, fuzz.Java, pctx)
+}
+
+func (s *javaFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+	packages := s.Packages.Strings()
+	sort.Strings(packages)
+
+	ctx.Strict("SOONG_JAVA_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
+
+	// Preallocate the slice of fuzz targets to minimize memory allocations.
+	s.PreallocateSlice(ctx, "ALL_JAVA_FUZZ_TARGETS")
+}
diff --git a/java/fuzz_test.go b/java/fuzz_test.go
new file mode 100644
index 0000000..cf063eb
--- /dev/null
+++ b/java/fuzz_test.go
@@ -0,0 +1,65 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+	"path/filepath"
+	"testing"
+)
+
+var prepForJavaFuzzTest = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModules,
+	android.FixtureRegisterWithContext(RegisterJavaFuzzBuildComponents),
+)
+
+func TestJavaFuzz(t *testing.T) {
+	result := prepForJavaFuzzTest.RunTestWithBp(t, `
+		java_fuzz_host {
+			name: "foo",
+			srcs: ["a.java"],
+			libs: ["bar"],
+			static_libs: ["baz"],
+		}
+
+		java_library_host {
+			name: "bar",
+			srcs: ["b.java"],
+		}
+
+		java_library_host {
+			name: "baz",
+			srcs: ["c.java"],
+		}`)
+
+	osCommonTarget := result.Config.BuildOSCommonTarget.String()
+	javac := result.ModuleForTests("foo", osCommonTarget).Rule("javac")
+	combineJar := result.ModuleForTests("foo", osCommonTarget).Description("for javac")
+
+	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
+		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
+	}
+
+	baz := result.ModuleForTests("baz", osCommonTarget).Rule("javac").Output.String()
+	barOut := filepath.Join("out", "soong", ".intermediates", "bar", osCommonTarget, "javac", "bar.jar")
+	bazOut := filepath.Join("out", "soong", ".intermediates", "baz", osCommonTarget, "javac", "baz.jar")
+
+	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], barOut)
+	android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], bazOut)
+
+	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
+		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
+	}
+}
diff --git a/java/gen.go b/java/gen.go
index 445a2d8..1572bf0 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -44,7 +44,7 @@
 		})
 )
 
-func genAidl(ctx android.ModuleContext, aidlFiles android.Paths, aidlFlags string, deps android.Paths) android.Paths {
+func genAidl(ctx android.ModuleContext, aidlFiles android.Paths, aidlGlobalFlags string, aidlIndividualFlags map[string]string, deps android.Paths) android.Paths {
 	// Shard aidl files into groups of 50 to avoid having to recompile all of them if one changes and to avoid
 	// hitting command line length limits.
 	shards := android.ShardPaths(aidlFiles, 50)
@@ -61,15 +61,17 @@
 
 		rule.Command().Text("rm -rf").Flag(outDir.String())
 		rule.Command().Text("mkdir -p").Flag(outDir.String())
-		rule.Command().Text("FLAGS=' " + aidlFlags + "'")
+		rule.Command().Text("FLAGS=' " + aidlGlobalFlags + "'")
 
 		for _, aidlFile := range shard {
+			localFlag := aidlIndividualFlags[aidlFile.String()]
 			depFile := srcJarFile.InSameDir(ctx, aidlFile.String()+".d")
 			javaFile := outDir.Join(ctx, pathtools.ReplaceExtension(aidlFile.String(), "java"))
 			rule.Command().
 				Tool(ctx.Config().HostToolPath(ctx, "aidl")).
 				FlagWithDepFile("-d", depFile).
 				Flag("$FLAGS").
+				Flag(localFlag).
 				Input(aidlFile).
 				Output(javaFile).
 				Implicits(deps)
@@ -159,7 +161,14 @@
 
 	// Process all aidl files together to support sharding them into one or more rules that produce srcjars.
 	if len(aidlSrcs) > 0 {
-		srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, flags.aidlDeps)
+		individualFlags := make(map[string]string)
+		for _, aidlSrc := range aidlSrcs {
+			flags := j.individualAidlFlags(ctx, aidlSrc)
+			if flags != "" {
+				individualFlags[aidlSrc.String()] = flags
+			}
+		}
+		srcJarFiles := genAidl(ctx, aidlSrcs, flags.aidlFlags+aidlIncludeFlags, individualFlags, flags.aidlDeps)
 		outSrcFiles = append(outSrcFiles, srcJarFiles...)
 	}
 
diff --git a/java/genrule.go b/java/genrule.go
index e0a9c8f..5047c41 100644
--- a/java/genrule.go
+++ b/java/genrule.go
@@ -24,8 +24,8 @@
 }
 
 func RegisterGenRuleBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("java_genrule", genRuleFactory)
-	ctx.RegisterModuleType("java_genrule_host", genRuleFactoryHost)
+	ctx.RegisterModuleType("java_genrule", GenRuleFactory)
+	ctx.RegisterModuleType("java_genrule_host", GenRuleFactoryHost)
 }
 
 // java_genrule is a genrule that can depend on other java_* objects.
@@ -33,7 +33,7 @@
 // By default a java_genrule has a single variant that will run against the device variant of its dependencies and
 // produce an output that can be used as an input to a device java rule.
 //
-// Specifying `host_supported: true` will produce two variants, one that uses device dependencie sand one that uses
+// Specifying `host_supported: true` will produce two variants, one that uses device dependencies and one that uses
 // host dependencies.  Each variant will run the command.
 //
 // Use a java_genrule instead of a genrule when it needs to depend on or be depended on by other java modules, unless
@@ -44,7 +44,7 @@
 // Use a java_genrule to package generated java resources:
 //
 //     java_genrule {
-//     name: "generated_resources",
+//         name: "generated_resources",
 //         tools: [
 //             "generator",
 //             "soong_zip",
@@ -60,10 +60,12 @@
 //         srcs: ["src/**/*.java"],
 //         static_libs: ["generated_resources"],
 //     }
-func genRuleFactory() android.Module {
+func GenRuleFactory() android.Module {
 	module := genrule.NewGenRule()
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
@@ -72,10 +74,12 @@
 //
 // A java_genrule_host has a single variant that will run against the host variant of its dependencies and
 // produce an output that can be used as an input to a host java rule.
-func genRuleFactoryHost() android.Module {
+func GenRuleFactoryHost() android.Module {
 	module := genrule.NewGenRule()
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 
 	return module
 }
diff --git a/java/genrule_test.go b/java/genrule_test.go
new file mode 100644
index 0000000..1c294b2
--- /dev/null
+++ b/java/genrule_test.go
@@ -0,0 +1,118 @@
+// 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 (
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func testGenruleContext(config android.Config) *android.TestContext {
+	ctx := android.NewTestArchContext(config)
+	ctx.RegisterModuleType("java_genrule", GenRuleFactory)
+	ctx.Register()
+
+	return ctx
+}
+
+func TestGenruleCmd(t *testing.T) {
+	fs := map[string][]byte{
+		"tool": nil,
+		"foo":  nil,
+	}
+	bp := `
+				java_genrule {
+					name: "gen",
+					tool_files: ["tool"],
+					cmd: "$(location tool) $(in) $(out)",
+					srcs: ["foo"],
+					out: ["out"],
+				}
+			`
+	config := android.TestArchConfig(t.TempDir(), nil, bp, fs)
+
+	ctx := testGenruleContext(config)
+
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	if errs == nil {
+		_, errs = ctx.PrepareBuildActions(config)
+	}
+	if errs != nil {
+		t.Fatal(errs)
+	}
+
+	gen := ctx.ModuleForTests("gen", "android_common").Output("out")
+	expected := []string{"foo"}
+	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
+		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
+	}
+}
+
+func TestJarGenrules(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+
+		java_genrule {
+			name: "jargen",
+			tool_files: ["b.java"],
+			cmd: "$(location b.java) $(in) $(out)",
+			out: ["jargen.jar"],
+			srcs: [":foo"],
+		}
+
+		java_library {
+			name: "bar",
+			static_libs: ["jargen"],
+			srcs: ["c.java"],
+		}
+
+		java_library {
+			name: "baz",
+			libs: ["jargen"],
+			srcs: ["c.java"],
+		}
+	`)
+
+	foo := ctx.ModuleForTests("foo", "android_common").Output("javac/foo.jar")
+	jargen := ctx.ModuleForTests("jargen", "android_common").Output("jargen.jar")
+	bar := ctx.ModuleForTests("bar", "android_common").Output("javac/bar.jar")
+	baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
+	barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
+
+	if g, w := jargen.Implicits.Strings(), foo.Output.String(); !android.InList(w, g) {
+		t.Errorf("expected jargen inputs [%q], got %q", w, g)
+	}
+
+	if !strings.Contains(bar.Args["classpath"], jargen.Output.String()) {
+		t.Errorf("bar classpath %v does not contain %q", bar.Args["classpath"], jargen.Output.String())
+	}
+
+	if !strings.Contains(baz.Args["classpath"], jargen.Output.String()) {
+		t.Errorf("baz classpath %v does not contain %q", baz.Args["classpath"], jargen.Output.String())
+	}
+
+	if len(barCombined.Inputs) != 2 ||
+		barCombined.Inputs[0].String() != bar.Output.String() ||
+		barCombined.Inputs[1].String() != jargen.Output.String() {
+		t.Errorf("bar combined jar inputs %v is not [%q, %q]",
+			barCombined.Inputs.Strings(), bar.Output.String(), jargen.Output.String())
+	}
+}
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index f901434..cf9c7ad 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -30,14 +30,14 @@
 	// that information encoded within it.
 	active bool
 
-	// The path to the dex jar that is in the boot class path. If this is nil then the associated
+	// The path to the dex jar that is in the boot class path. If this is unset then the associated
 	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
 	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
 	// themselves.
 	//
 	// This must be the path to the unencoded dex jar as the encoded dex jar indirectly depends on
 	// this file so using the encoded dex jar here would result in a cycle in the ninja rules.
-	bootDexJarPath android.Path
+	bootDexJarPath OptionalDexJarPath
 
 	// The paths to the classes jars that contain classes and class members annotated with
 	// the UnsupportedAppUsage annotation that need to be extracted as part of the hidden API
@@ -49,7 +49,7 @@
 	uncompressDexState *bool
 }
 
-func (h *hiddenAPI) bootDexJar() android.Path {
+func (h *hiddenAPI) bootDexJar() OptionalDexJarPath {
 	return h.bootDexJarPath
 }
 
@@ -65,10 +65,12 @@
 type hiddenAPIModule interface {
 	android.Module
 	hiddenAPIIntf
+
+	MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec
 }
 
 type hiddenAPIIntf interface {
-	bootDexJar() android.Path
+	bootDexJar() OptionalDexJarPath
 	classesJars() android.Paths
 	uncompressDex() *bool
 }
@@ -79,7 +81,7 @@
 //
 // uncompressedDexState should be nil when the module is a prebuilt and so does not require hidden
 // API encoding.
-func (h *hiddenAPI) initHiddenAPI(ctx android.ModuleContext, dexJar, classesJar android.Path, uncompressedDexState *bool) {
+func (h *hiddenAPI) initHiddenAPI(ctx android.ModuleContext, dexJar OptionalDexJarPath, classesJar android.Path, uncompressedDexState *bool) {
 
 	// Save the classes jars even if this is not active as they may be used by modular hidden API
 	// processing.
@@ -118,11 +120,11 @@
 }
 
 func isModuleInBootClassPath(ctx android.BaseModuleContext, module android.Module) bool {
-	// Get the configured non-updatable and updatable boot jars.
-	nonUpdatableBootJars := ctx.Config().NonUpdatableBootJars()
-	updatableBootJars := ctx.Config().UpdatableBootJars()
-	active := isModuleInConfiguredList(ctx, module, nonUpdatableBootJars) ||
-		isModuleInConfiguredList(ctx, module, updatableBootJars)
+	// Get the configured platform and apex boot jars.
+	nonApexBootJars := ctx.Config().NonApexBootJars()
+	apexBootJars := ctx.Config().ApexBootJars()
+	active := isModuleInConfiguredList(ctx, module, nonApexBootJars) ||
+		isModuleInConfiguredList(ctx, module, apexBootJars)
 	return active
 }
 
@@ -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",
@@ -297,6 +308,7 @@
 
 type hiddenApiAnnotationsDependencyTag struct {
 	blueprint.BaseDependencyTag
+	android.LicenseAnnotationSharedDependencyTag
 }
 
 // Tag used to mark dependencies on java_library instances that contains Java source files whose
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 654ebb7..c90b2ff 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -218,7 +219,7 @@
 var _ android.ExcludeFromVisibilityEnforcementTag = hiddenAPIStubsDependencyTag{}
 var _ android.ReplaceSourceWithPrebuilt = hiddenAPIStubsDependencyTag{}
 var _ android.ExcludeFromApexContentsTag = hiddenAPIStubsDependencyTag{}
-var _ android.SdkMemberTypeDependencyTag = hiddenAPIStubsDependencyTag{}
+var _ android.SdkMemberDependencyTag = hiddenAPIStubsDependencyTag{}
 
 // hiddenAPIComputeMonolithicStubLibModules computes the set of module names that provide stubs
 // needed to produce the hidden API monolithic stub flags file.
@@ -277,7 +278,7 @@
 // hiddenAPIRetrieveDexJarBuildPath retrieves the DexJarBuildPath from the specified module, if
 // available, or reports an error.
 func hiddenAPIRetrieveDexJarBuildPath(ctx android.ModuleContext, module android.Module, kind android.SdkKind) android.Path {
-	var dexJar android.Path
+	var dexJar OptionalDexJarPath
 	if sdkLibrary, ok := module.(SdkLibraryDependency); ok {
 		dexJar = sdkLibrary.SdkApiStubDexJar(ctx, kind)
 	} else if j, ok := module.(UsesLibraryDependency); ok {
@@ -287,17 +288,24 @@
 		return nil
 	}
 
-	if dexJar == nil {
-		ctx.ModuleErrorf("dependency %s does not provide a dex jar, consider setting compile_dex: true", module)
+	if !dexJar.Valid() {
+		ctx.ModuleErrorf("dependency %s does not provide a dex jar: %s", module, dexJar.InvalidReason())
+		return nil
 	}
-	return dexJar
+	return dexJar.Path()
 }
 
+// HIDDENAPI_STUB_FLAGS_IMPL_FLAGS is the set of flags that identify implementation only signatures,
+// i.e. those signatures that are not part of any API (including the hidden API).
+var HIDDENAPI_STUB_FLAGS_IMPL_FLAGS = []string{}
+
+var HIDDENAPI_FLAGS_CSV_IMPL_FLAGS = []string{"blocked"}
+
 // buildRuleToGenerateHiddenAPIStubFlagsFile creates a rule to create a hidden API stub flags file.
 //
 // The rule is initialized but not built so that the caller can modify it and select an appropriate
 // name.
-func buildRuleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, name, desc string, outputPath android.WritablePath, bootDexJars android.Paths, input HiddenAPIFlagInput, moduleStubFlagsPaths android.Paths) {
+func buildRuleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, name, desc string, outputPath android.WritablePath, bootDexJars android.Paths, input HiddenAPIFlagInput, stubFlagSubsets SignatureCsvSubsets) {
 	// Singleton rule which applies hiddenapi on all boot class path dex files.
 	rule := android.NewRuleBuilder(pctx, ctx)
 
@@ -317,7 +325,7 @@
 
 	// If no module stub flags paths are provided then this must be being called for a
 	// bootclasspath_fragment and not the whole platform_bootclasspath.
-	if moduleStubFlagsPaths == nil {
+	if stubFlagSubsets == nil {
 		// This is being run on a fragment of the bootclasspath.
 		command.Flag("--fragment")
 	}
@@ -342,8 +350,9 @@
 
 	// If there are stub flag files that have been generated by fragments on which this depends then
 	// use them to validate the stub flag file generated by the rules created by this method.
-	if len(moduleStubFlagsPaths) > 0 {
-		validFile := buildRuleValidateOverlappingCsvFiles(ctx, name, desc, outputPath, moduleStubFlagsPaths)
+	if len(stubFlagSubsets) > 0 {
+		validFile := buildRuleValidateOverlappingCsvFiles(ctx, name, desc, outputPath, stubFlagSubsets,
+			HIDDENAPI_STUB_FLAGS_IMPL_FLAGS)
 
 		// Add the file that indicates that the file generated by this is valid.
 		//
@@ -510,14 +519,6 @@
 	}
 }
 
-// dedup removes duplicates in the flag files, while maintaining the order in which they were
-// appended.
-func (s FlagFilesByCategory) dedup() {
-	for category, paths := range s {
-		s[category] = android.FirstUniquePaths(paths)
-	}
-}
-
 // HiddenAPIInfo contains information provided by the hidden API processing.
 //
 // That includes paths resolved from HiddenAPIFlagFileProperties and also generated by hidden API
@@ -554,6 +555,20 @@
 	}
 }
 
+// StubFlagSubset returns a SignatureCsvSubset that contains a path to a filtered-stub-flags.csv
+// file and a path to a signature-patterns.csv file that defines a subset of the monolithic stub
+// flags file, i.e. out/soong/hiddenapi/hiddenapi-stub-flags.txt, against which it will be compared.
+func (i *HiddenAPIInfo) StubFlagSubset() SignatureCsvSubset {
+	return SignatureCsvSubset{i.FilteredStubFlagsPath, i.SignaturePatternsPath}
+}
+
+// FlagSubset returns a SignatureCsvSubset that contains a path to a filtered-flags.csv file and a
+// path to a signature-patterns.csv file that defines a subset of the monolithic flags file, i.e.
+// out/soong/hiddenapi/hiddenapi-flags.csv, against which it will be compared.
+func (i *HiddenAPIInfo) FlagSubset() SignatureCsvSubset {
+	return SignatureCsvSubset{i.FilteredFlagsPath, i.SignaturePatternsPath}
+}
+
 var HiddenAPIInfoProvider = blueprint.NewProvider(HiddenAPIInfo{})
 
 // ModuleStubDexJars contains the stub dex jars provided by a single module.
@@ -776,9 +791,6 @@
 // HiddenAPIFlagOutput contains paths to output files from the hidden API flag generation for a
 // bootclasspath_fragment module.
 type HiddenAPIFlagOutput struct {
-	// The path to the generated stub-flags.csv file.
-	StubFlagsPath android.Path
-
 	// The path to the generated annotation-flags.csv file.
 	AnnotationFlagsPath android.Path
 
@@ -788,8 +800,21 @@
 	// The path to the generated index.csv file.
 	IndexPath android.Path
 
+	// The path to the generated stub-flags.csv file.
+	StubFlagsPath android.Path
+
 	// The path to the generated all-flags.csv file.
 	AllFlagsPath android.Path
+
+	// The path to the generated signature-patterns.txt file which defines the subset of the
+	// monolithic hidden API files provided in this.
+	SignaturePatternsPath android.Path
+
+	// The path to the generated filtered-stub-flags.csv file.
+	FilteredStubFlagsPath android.Path
+
+	// The path to the generated filtered-flags.csv file.
+	FilteredFlagsPath android.Path
 }
 
 // bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
@@ -856,7 +881,7 @@
 // the annotationFlags.
 func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc string,
 	outputPath android.WritablePath, baseFlagsPath android.Path, annotationFlagPaths android.Paths,
-	flagFilesByCategory FlagFilesByCategory, allFlagsPaths android.Paths, generatedRemovedDexSignatures android.OptionalPath) {
+	flagFilesByCategory FlagFilesByCategory, flagSubsets SignatureCsvSubsets, generatedRemovedDexSignatures android.OptionalPath) {
 
 	// Create the rule that will generate the flag files.
 	tempPath := tempPathForRestat(ctx, outputPath)
@@ -885,8 +910,9 @@
 
 	// If there are flag files that have been generated by fragments on which this depends then use
 	// them to validate the flag file generated by the rules created by this method.
-	if len(allFlagsPaths) > 0 {
-		validFile := buildRuleValidateOverlappingCsvFiles(ctx, name, desc, outputPath, allFlagsPaths)
+	if len(flagSubsets) > 0 {
+		validFile := buildRuleValidateOverlappingCsvFiles(ctx, name, desc, outputPath, flagSubsets,
+			HIDDENAPI_FLAGS_CSV_IMPL_FLAGS)
 
 		// Add the file that indicates that the file generated by this is valid.
 		//
@@ -898,20 +924,122 @@
 	rule.Build(name, desc)
 }
 
+// SignatureCsvSubset describes a subset of a monolithic flags file, i.e. either
+// out/soong/hiddenapi/hiddenapi-stub-flags.txt or out/soong/hiddenapi/hiddenapi-flags.csv
+type SignatureCsvSubset struct {
+	// The path to the CSV file containing hidden API flags.
+	//
+	// It has the dex member signature as the first column, with flags, one per column, in the
+	// subsequent columns.
+	CsvFile android.Path
+
+	// The path to the CSV file containing the signature patterns.
+	//
+	// It is a single column CSV file with the column containing a signature pattern.
+	SignaturePatternsFile android.Path
+}
+
+type SignatureCsvSubsets []SignatureCsvSubset
+
+func (s SignatureCsvSubsets) RelativeToTop() []string {
+	result := []string{}
+	for _, subset := range s {
+		result = append(result, fmt.Sprintf("%s:%s", subset.CsvFile.RelativeToTop(), subset.SignaturePatternsFile.RelativeToTop()))
+	}
+	return result
+}
+
+// buildRuleSignaturePatternsFile creates a rule to generate a file containing the set of signature
+// patterns that will select a subset of the monolithic flags.
+func buildRuleSignaturePatternsFile(
+	ctx android.ModuleContext, flagsPath android.Path,
+	splitPackages []string, packagePrefixes []string, singlePackages []string) android.Path {
+	patternsFile := android.PathForModuleOut(ctx, "modular-hiddenapi", "signature-patterns.csv")
+	// Create a rule to validate the output from the following rule.
+	rule := android.NewRuleBuilder(pctx, ctx)
+
+	// Quote any * in the packages to avoid them being expanded by the shell.
+	quotedSplitPackages := []string{}
+	for _, pkg := range splitPackages {
+		quotedSplitPackages = append(quotedSplitPackages, strings.ReplaceAll(pkg, "*", "\\*"))
+	}
+
+	rule.Command().
+		BuiltTool("signature_patterns").
+		FlagWithInput("--flags ", flagsPath).
+		FlagForEachArg("--split-package ", quotedSplitPackages).
+		FlagForEachArg("--package-prefix ", packagePrefixes).
+		FlagForEachArg("--single-package ", singlePackages).
+		FlagWithOutput("--output ", patternsFile)
+	rule.Build("hiddenAPISignaturePatterns", "hidden API signature patterns")
+
+	return patternsFile
+}
+
+// buildRuleRemoveSignaturesWithImplementationFlags creates a rule that will remove signatures from
+// the input flags file which have only the implementation flags, i.e. are not part of an API.
+//
+// The implementationFlags specifies the set of default flags that identifies the signature of a
+// private, implementation only, member. Signatures that match those flags are removed from the
+// flags as they are implementation only.
+//
+// This is used to remove implementation only signatures from the signature files that are persisted
+// in the sdk snapshot as the sdk snapshots should not include implementation details. The
+// signatures generated by this method will be compared by the buildRuleValidateOverlappingCsvFiles
+// method which treats any missing signatures as if they were implementation only signatures.
+func buildRuleRemoveSignaturesWithImplementationFlags(ctx android.BuilderContext,
+	name string, desc string, inputPath android.Path, filteredPath android.WritablePath,
+	implementationFlags []string) {
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	implementationFlagPattern := ""
+	for _, implementationFlag := range implementationFlags {
+		implementationFlagPattern = implementationFlagPattern + "," + implementationFlag
+	}
+	rule.Command().
+		Text(`grep -vE "^[^,]+` + implementationFlagPattern + `$"`).Input(inputPath).
+		Text(">").Output(filteredPath).
+		// Grep's exit code depends on whether it finds anything. It is 0 (build success) when it finds
+		// something and 1 (build failure) when it does not and 2 (when it encounters an error).
+		// However, while it is unlikely it is not an error if this does not find any matches. The
+		// following will only run if the grep does not find something and in that case it will treat
+		// an exit code of 1 as success and anything else as failure.
+		Text("|| test $? -eq 1")
+	rule.Build(name, desc)
+}
+
 // buildRuleValidateOverlappingCsvFiles checks that the modular CSV files, i.e. the files generated
 // by the individual bootclasspath_fragment modules are subsets of the monolithic CSV file.
-func buildRuleValidateOverlappingCsvFiles(ctx android.BuilderContext, name string, desc string, monolithicFilePath android.WritablePath, modularFilePaths android.Paths) android.WritablePath {
+//
+// The implementationFlags specifies the set of default flags that identifies the signature of a
+// private, implementation only, member. A signature which is present in a monolithic flags subset
+// defined by SignatureCsvSubset but which is not present in the flags file from the corresponding
+// module is assumed to be an implementation only member and so must have these flags.
+func buildRuleValidateOverlappingCsvFiles(ctx android.BuilderContext, name string, desc string,
+	monolithicFilePath android.WritablePath, csvSubsets SignatureCsvSubsets,
+	implementationFlags []string) android.WritablePath {
 	// The file which is used to record that the flags file is valid.
 	validFile := pathForValidation(ctx, monolithicFilePath)
 
 	// Create a rule to validate the output from the following rule.
 	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
+	command := rule.Command().
 		BuiltTool("verify_overlaps").
-		Input(monolithicFilePath).
-		Inputs(modularFilePaths).
-		// If validation passes then update the file that records that.
-		Text("&& touch").Output(validFile)
+		FlagWithInput("--monolithic-flags ", monolithicFilePath)
+
+	for _, subset := range csvSubsets {
+		command.
+			Flag("--module-flags ").
+			Textf("%s:%s", subset.CsvFile, subset.SignaturePatternsFile).
+			Implicit(subset.CsvFile).Implicit(subset.SignaturePatternsFile)
+	}
+
+	for _, implementationFlag := range implementationFlags {
+		command.FlagWithArg("--implementation-flag ", implementationFlag)
+	}
+
+	// If validation passes then update the file that records that.
+	command.Text("&& touch").Output(validFile)
 	rule.Build(name+"Validation", desc+" validation")
 
 	return validFile
@@ -976,18 +1104,34 @@
 	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
 	}
 
+	// Generate the filtered-stub-flags.csv file which contains the filtered stub flags that will be
+	// compared against the monolithic stub flags.
+	filteredStubFlagsCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "filtered-stub-flags.csv")
+	buildRuleRemoveSignaturesWithImplementationFlags(ctx, "modularHiddenApiFilteredStubFlags",
+		"modular hiddenapi filtered stub flags", stubFlagsCSV, filteredStubFlagsCSV,
+		HIDDENAPI_STUB_FLAGS_IMPL_FLAGS)
+
+	// Generate the filtered-flags.csv file which contains the filtered flags that will be compared
+	// against the monolithic flags.
+	filteredFlagsCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "filtered-flags.csv")
+	buildRuleRemoveSignaturesWithImplementationFlags(ctx, "modularHiddenApiFilteredFlags",
+		"modular hiddenapi filtered flags", allFlagsCSV, filteredFlagsCSV,
+		HIDDENAPI_FLAGS_CSV_IMPL_FLAGS)
+
 	// Store the paths in the info for use by other modules and sdk snapshot generation.
 	output := HiddenAPIOutput{
 		HiddenAPIFlagOutput: HiddenAPIFlagOutput{
-			StubFlagsPath:       stubFlagsCSV,
-			AnnotationFlagsPath: annotationFlagsCSV,
-			MetadataPath:        metadataCSV,
-			IndexPath:           indexCSV,
-			AllFlagsPath:        allFlagsCSV,
+			AnnotationFlagsPath:   annotationFlagsCSV,
+			MetadataPath:          metadataCSV,
+			IndexPath:             indexCSV,
+			StubFlagsPath:         stubFlagsCSV,
+			AllFlagsPath:          allFlagsCSV,
+			FilteredStubFlagsPath: filteredStubFlagsCSV,
+			FilteredFlagsPath:     filteredFlagsCSV,
 		},
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
@@ -1044,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
@@ -1069,6 +1216,7 @@
 		bootDexJarsByModule[module.Name()] = bootDexInfo{
 			path:          bootDexJar,
 			uncompressDex: *hiddenAPIModule.uncompressDex(),
+			minSdkVersion: hiddenAPIModule.MinSdkVersion(ctx),
 		}
 	}
 
@@ -1077,18 +1225,17 @@
 
 // retrieveBootDexJarFromHiddenAPIModule retrieves the boot dex jar from the hiddenAPIModule.
 //
-// If the module does not provide a boot dex jar, i.e. the returned boot dex jar is nil, then  that
-// create a fake path and either report an error immediately or defer reporting of the error until
-// the path is actually used.
+// If the module does not provide a boot dex jar, i.e. the returned boot dex jar is unset or
+// invalid, then create a fake path and either report an error immediately or defer reporting of the
+// error until the path is actually used.
 func retrieveBootDexJarFromHiddenAPIModule(ctx android.ModuleContext, module hiddenAPIModule) android.Path {
 	bootDexJar := module.bootDexJar()
-	if bootDexJar == nil {
+	if !bootDexJar.Valid() {
 		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/boot-dex/%s.jar", module.Name()))
-		bootDexJar = fake
-
-		handleMissingDexBootFile(ctx, module, fake)
+		handleMissingDexBootFile(ctx, module, fake, bootDexJar.InvalidReason())
+		return fake
 	}
-	return bootDexJar
+	return bootDexJar.Path()
 }
 
 // extractClassesJarsFromModules extracts the class jars from the supplied modules.
@@ -1112,13 +1259,6 @@
 // deferReportingMissingBootDexJar returns true if a missing boot dex jar should not be reported by
 // Soong but should instead only be reported in ninja if the file is actually built.
 func deferReportingMissingBootDexJar(ctx android.ModuleContext, module android.Module) bool {
-	// TODO(b/179354495): Remove this workaround when it is unnecessary.
-	// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
-	// create a fake one that will cause a build error only if it is used.
-	if ctx.Config().AlwaysUsePrebuiltSdks() {
-		return true
-	}
-
 	// Any missing dependency should be allowed.
 	if ctx.Config().AllowMissingDependencies() {
 		return true
@@ -1189,7 +1329,7 @@
 
 // handleMissingDexBootFile will either log a warning or create an error rule to create the fake
 // file depending on the value returned from deferReportingMissingBootDexJar.
-func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath) {
+func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath, reason string) {
 	if deferReportingMissingBootDexJar(ctx, module) {
 		// Create an error rule that pretends to create the output file but will actually fail if it
 		// is run.
@@ -1197,11 +1337,11 @@
 			Rule:   android.ErrorRule,
 			Output: fake,
 			Args: map[string]string{
-				"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
+				"error": fmt.Sprintf("missing boot dex jar dependency for %s: %s", module, reason),
 			},
 		})
 	} else {
-		ctx.ModuleErrorf("module %s does not provide a dex jar", module)
+		ctx.ModuleErrorf("module %s does not provide a dex jar: %s", module, reason)
 	}
 }
 
@@ -1212,14 +1352,13 @@
 // However, under certain conditions, e.g. errors, or special build configurations it will return
 // a path to a fake file.
 func retrieveEncodedBootDexJarFromModule(ctx android.ModuleContext, module android.Module) android.Path {
-	bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-	if bootDexJar == nil {
+	bootDexJar := module.(interface{ DexJarBuildPath() OptionalDexJarPath }).DexJarBuildPath()
+	if !bootDexJar.Valid() {
 		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/encoded-dex/%s.jar", module.Name()))
-		bootDexJar = fake
-
-		handleMissingDexBootFile(ctx, module, fake)
+		handleMissingDexBootFile(ctx, module, fake, bootDexJar.InvalidReason())
+		return fake
 	}
-	return bootDexJar
+	return bootDexJar.Path()
 }
 
 // extractEncodedDexJarsFromModules extracts the encoded dex jars from the supplied modules.
diff --git a/java/hiddenapi_monolithic.go b/java/hiddenapi_monolithic.go
index 52f0770..5956e3c 100644
--- a/java/hiddenapi_monolithic.go
+++ b/java/hiddenapi_monolithic.go
@@ -29,9 +29,6 @@
 	// that category.
 	FlagsFilesByCategory FlagFilesByCategory
 
-	// The paths to the generated stub-flags.csv files.
-	StubFlagsPaths android.Paths
-
 	// The paths to the generated annotation-flags.csv files.
 	AnnotationFlagsPaths android.Paths
 
@@ -41,8 +38,13 @@
 	// The paths to the generated index.csv files.
 	IndexPaths android.Paths
 
-	// The paths to the generated all-flags.csv files.
-	AllFlagsPaths android.Paths
+	// The subsets of the monolithic hiddenapi-stubs-flags.txt file that are provided by each
+	// bootclasspath_fragment modules.
+	StubFlagSubsets SignatureCsvSubsets
+
+	// The subsets of the monolithic hiddenapi-flags.csv file that are provided by each
+	// bootclasspath_fragment modules.
+	FlagSubsets SignatureCsvSubsets
 
 	// The classes jars from the libraries on the platform bootclasspath.
 	ClassesJars android.Paths
@@ -58,68 +60,34 @@
 	// Merge all the information from the classpathElements. The fragments form a DAG so it is possible that
 	// this will introduce duplicates so they will be resolved after processing all the classpathElements.
 	for _, element := range classpathElements {
-		var classesJars android.Paths
 		switch e := element.(type) {
 		case *ClasspathLibraryElement:
-			classesJars = retrieveClassesJarsFromModule(e.Module())
+			classesJars := retrieveClassesJarsFromModule(e.Module())
+			monolithicInfo.ClassesJars = append(monolithicInfo.ClassesJars, classesJars...)
 
 		case *ClasspathFragmentElement:
 			fragment := e.Module()
 			if ctx.OtherModuleHasProvider(fragment, HiddenAPIInfoProvider) {
 				info := ctx.OtherModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
 				monolithicInfo.append(&info)
-
-				// If the bootclasspath fragment actually perform hidden API processing itself then use the
-				// CSV files it provides and do not bother processing the classesJars files. This ensures
-				// consistent behavior between source and prebuilt as prebuilt modules do not provide
-				// classesJars.
-				if info.AllFlagsPath != nil {
-					continue
-				}
+			} else {
+				ctx.ModuleErrorf("%s does not provide hidden API information", fragment)
 			}
-
-			classesJars = extractClassesJarsFromModules(e.Contents)
 		}
-
-		monolithicInfo.ClassesJars = append(monolithicInfo.ClassesJars, classesJars...)
 	}
 
-	// Dedup paths.
-	monolithicInfo.dedup()
-
 	return monolithicInfo
 }
 
 // append appends all the files from the supplied info to the corresponding files in this struct.
 func (i *MonolithicHiddenAPIInfo) append(other *HiddenAPIInfo) {
 	i.FlagsFilesByCategory.append(other.FlagFilesByCategory)
+	i.AnnotationFlagsPaths = append(i.AnnotationFlagsPaths, other.AnnotationFlagsPath)
+	i.MetadataPaths = append(i.MetadataPaths, other.MetadataPath)
+	i.IndexPaths = append(i.IndexPaths, other.IndexPath)
 
-	// The output may not be set if the bootclasspath_fragment has not yet been updated to support
-	// hidden API processing.
-	// TODO(b/179354495): Switch back to append once all bootclasspath_fragment modules have been
-	//  updated to support hidden API processing properly.
-	appendIfNotNil := func(paths android.Paths, path android.Path) android.Paths {
-		if path == nil {
-			return paths
-		}
-		return append(paths, path)
-	}
-	i.StubFlagsPaths = appendIfNotNil(i.StubFlagsPaths, other.StubFlagsPath)
-	i.AnnotationFlagsPaths = appendIfNotNil(i.AnnotationFlagsPaths, other.AnnotationFlagsPath)
-	i.MetadataPaths = appendIfNotNil(i.MetadataPaths, other.MetadataPath)
-	i.IndexPaths = appendIfNotNil(i.IndexPaths, other.IndexPath)
-	i.AllFlagsPaths = appendIfNotNil(i.AllFlagsPaths, other.AllFlagsPath)
-}
-
-// dedup removes duplicates in all the paths, while maintaining the order in which they were
-// appended.
-func (i *MonolithicHiddenAPIInfo) dedup() {
-	i.FlagsFilesByCategory.dedup()
-	i.StubFlagsPaths = android.FirstUniquePaths(i.StubFlagsPaths)
-	i.AnnotationFlagsPaths = android.FirstUniquePaths(i.AnnotationFlagsPaths)
-	i.MetadataPaths = android.FirstUniquePaths(i.MetadataPaths)
-	i.IndexPaths = android.FirstUniquePaths(i.IndexPaths)
-	i.AllFlagsPaths = android.FirstUniquePaths(i.AllFlagsPaths)
+	i.StubFlagSubsets = append(i.StubFlagSubsets, other.StubFlagSubset())
+	i.FlagSubsets = append(i.FlagSubsets, other.FlagSubset())
 }
 
 var MonolithicHiddenAPIInfoProvider = blueprint.NewProvider(MonolithicHiddenAPIInfo{})
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index dcd363c..75b7bb7 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -20,6 +20,7 @@
 	"testing"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -306,7 +307,7 @@
 		android.AssertStringEquals(t, "encode embedded java_library", unencodedDexJar, actualUnencodedDexJar.String())
 
 		// Make sure that the encoded dex jar is the exported one.
-		exportedDexJar := moduleForTests.Module().(UsesLibraryDependency).DexJarBuildPath()
+		exportedDexJar := moduleForTests.Module().(UsesLibraryDependency).DexJarBuildPath().Path()
 		android.AssertPathRelativeToTopEquals(t, "encode embedded java_library", encodedDexJar, exportedDexJar)
 	}
 
diff --git a/java/jacoco.go b/java/jacoco.go
index 9162161..e11c2ce 100644
--- a/java/jacoco.go
+++ b/java/jacoco.go
@@ -94,7 +94,7 @@
 	if len(includes) > 0 {
 		specs += strings.Join(includes, " ")
 	} else {
-		specs += "**/*.class"
+		specs += "'**/*.class'"
 	}
 	return specs
 }
diff --git a/java/jacoco_test.go b/java/jacoco_test.go
index 91f0553..1882908 100644
--- a/java/jacoco_test.go
+++ b/java/jacoco_test.go
@@ -74,7 +74,7 @@
 		{
 			name:     "implicit wildcard",
 			includes: []string{},
-			out:      "**/*.class",
+			out:      "'**/*.class'",
 		},
 		{
 			name:     "only include",
diff --git a/java/java.go b/java/java.go
index bbed42d..c8fb93c 100644
--- a/java/java.go
+++ b/java/java.go
@@ -23,6 +23,8 @@
 	"path/filepath"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -74,6 +76,7 @@
 	android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaBootLibsSdkMemberType)
+	android.RegisterSdkMemberType(javaSystemserverLibsSdkMemberType)
 	android.RegisterSdkMemberType(javaTestSdkMemberType)
 }
 
@@ -131,11 +134,50 @@
 			PropertyName: "java_boot_libs",
 			SupportsSdk:  true,
 		},
-		// Temporarily export implementation classes jar for java_boot_libs as it is required for the
-		// hiddenapi processing.
-		// TODO(b/179354495): Revert once hiddenapi processing has been modularized.
-		exportImplementationClassesJar,
-		sdkSnapshotFilePathForJar,
+		func(ctx android.SdkMemberContext, j *Library) android.Path {
+			// Java boot libs are only provided in the SDK to provide access to their dex implementation
+			// jar for use by dexpreopting and boot jars package check. They do not need to provide an
+			// actual implementation jar but the java_import will need a file that exists so just copy an
+			// empty file. Any attempt to use that file as a jar will cause a build error.
+			return ctx.SnapshotBuilder().EmptyFile()
+		},
+		func(osPrefix, name string) string {
+			// Create a special name for the implementation jar to try and provide some useful information
+			// to a developer that attempts to compile against this.
+			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
+			return filepath.Join(osPrefix, "java_boot_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
+		},
+		onlyCopyJarToSnapshot,
+	}
+
+	// Supports adding java systemserver libraries to module_exports and sdk.
+	//
+	// The build has some implicit dependencies (via the systemserver jars configuration) on a number
+	// of modules that are part of the java systemserver classpath and which are provided by mainline
+	// modules but which are not otherwise used outside those mainline modules.
+	//
+	// As they are not needed outside the mainline modules adding them to the sdk/module-exports as
+	// either java_libs, or java_header_libs would end up exporting more information than was strictly
+	// necessary. The java_systemserver_libs property to allow those modules to be exported as part of
+	// the sdk/module_exports without exposing any unnecessary information.
+	javaSystemserverLibsSdkMemberType = &librarySdkMemberType{
+		android.SdkMemberTypeBase{
+			PropertyName: "java_systemserver_libs",
+			SupportsSdk:  true,
+		},
+		func(ctx android.SdkMemberContext, j *Library) android.Path {
+			// Java systemserver libs are only provided in the SDK to provide access to their dex
+			// implementation jar for use by dexpreopting. They do not need to provide an actual
+			// implementation jar but the java_import will need a file that exists so just copy an empty
+			// file. Any attempt to use that file as a jar will cause a build error.
+			return ctx.SnapshotBuilder().EmptyFile()
+		},
+		func(osPrefix, name string) string {
+			// Create a special name for the implementation jar to try and provide some useful information
+			// to a developer that attempts to compile against this.
+			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
+			return filepath.Join(osPrefix, "java_systemserver_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
+		},
 		onlyCopyJarToSnapshot,
 	}
 
@@ -212,7 +254,7 @@
 
 // Provides build path and install path to DEX jars.
 type UsesLibraryDependency interface {
-	DexJarBuildPath() android.Path
+	DexJarBuildPath() OptionalDexJarPath
 	DexJarInstallPath() android.Path
 	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
 }
@@ -229,6 +271,12 @@
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
+
+	// True if the dependency is relinked at runtime.
+	runtimeLinked bool
+
+	// True if the dependency is a toolchain, for example an annotation processor.
+	toolchain bool
 }
 
 // installDependencyTag is a dependency tag that is annotated to cause the installed files of the
@@ -239,15 +287,40 @@
 	name string
 }
 
-type usesLibraryDependencyTag struct {
-	dependencyTag
-	sdkVersion int // SDK version in which the library appared as a standalone library.
+func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.runtimeLinked {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	} else if d.toolchain {
+		return []android.LicenseAnnotation{android.LicenseAnnotationToolchain}
+	}
+	return nil
 }
 
-func makeUsesLibraryDependencyTag(sdkVersion int) usesLibraryDependencyTag {
+var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}
+
+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
+}
+
+func makeUsesLibraryDependencyTag(sdkVersion int, optional bool, implicit bool) usesLibraryDependencyTag {
 	return usesLibraryDependencyTag{
-		dependencyTag: dependencyTag{name: fmt.Sprintf("uses-library-%d", sdkVersion)},
-		sdkVersion:    sdkVersion,
+		dependencyTag: dependencyTag{
+			name:          fmt.Sprintf("uses-library-%d", sdkVersion),
+			runtimeLinked: true,
+		},
+		sdkVersion: sdkVersion,
+		optional:   optional,
+		implicit:   implicit,
 	}
 }
 
@@ -257,29 +330,27 @@
 
 var (
 	dataNativeBinsTag       = dependencyTag{name: "dataNativeBins"}
+	dataDeviceBinsTag       = dependencyTag{name: "dataDeviceBins"}
 	staticLibTag            = dependencyTag{name: "staticlib"}
-	libTag                  = dependencyTag{name: "javalib"}
-	java9LibTag             = dependencyTag{name: "java9lib"}
-	pluginTag               = dependencyTag{name: "plugin"}
-	errorpronePluginTag     = dependencyTag{name: "errorprone-plugin"}
-	exportedPluginTag       = dependencyTag{name: "exported-plugin"}
-	bootClasspathTag        = dependencyTag{name: "bootclasspath"}
-	systemModulesTag        = dependencyTag{name: "system modules"}
+	libTag                  = dependencyTag{name: "javalib", runtimeLinked: true}
+	java9LibTag             = dependencyTag{name: "java9lib", runtimeLinked: true}
+	pluginTag               = dependencyTag{name: "plugin", toolchain: true}
+	errorpronePluginTag     = dependencyTag{name: "errorprone-plugin", toolchain: true}
+	exportedPluginTag       = dependencyTag{name: "exported-plugin", toolchain: true}
+	bootClasspathTag        = dependencyTag{name: "bootclasspath", runtimeLinked: true}
+	systemModulesTag        = dependencyTag{name: "system modules", runtimeLinked: true}
 	frameworkResTag         = dependencyTag{name: "framework-res"}
-	kotlinStdlibTag         = dependencyTag{name: "kotlin-stdlib"}
-	kotlinAnnotationsTag    = dependencyTag{name: "kotlin-annotations"}
+	kotlinStdlibTag         = dependencyTag{name: "kotlin-stdlib", runtimeLinked: true}
+	kotlinAnnotationsTag    = dependencyTag{name: "kotlin-annotations", runtimeLinked: true}
+	kotlinPluginTag         = dependencyTag{name: "kotlin-plugin", toolchain: true}
 	proguardRaiseTag        = dependencyTag{name: "proguard-raise"}
 	certificateTag          = dependencyTag{name: "certificate"}
 	instrumentationForTag   = dependencyTag{name: "instrumentation_for"}
-	extraLintCheckTag       = dependencyTag{name: "extra-lint-check"}
-	jniLibTag               = dependencyTag{name: "jnilib"}
+	extraLintCheckTag       = dependencyTag{name: "extra-lint-check", toolchain: true}
+	jniLibTag               = dependencyTag{name: "jnilib", runtimeLinked: true}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	jniInstallTag           = installDependencyTag{name: "jni install"}
 	binaryInstallTag        = installDependencyTag{name: "binary install"}
-	usesLibTag              = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
-	usesLibCompat28Tag      = makeUsesLibraryDependencyTag(28)
-	usesLibCompat29Tag      = makeUsesLibraryDependencyTag(29)
-	usesLibCompat30Tag      = makeUsesLibraryDependencyTag(30)
 )
 
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
@@ -350,9 +421,25 @@
 }
 
 type deps struct {
-	classpath               classpath
-	java9Classpath          classpath
-	bootClasspath           classpath
+	// bootClasspath is the list of jars that form the boot classpath (generally the java.* and
+	// android.* classes) for tools that still use it.  javac targeting 1.9 or higher uses
+	// systemModules and java9Classpath instead.
+	bootClasspath classpath
+
+	// classpath is the list of jars that form the classpath for javac and kotlinc rules.  It
+	// contains header jars for all static and non-static dependencies.
+	classpath classpath
+
+	// dexClasspath is the list of jars that form the classpath for d8 and r8 rules.  It contains
+	// header jars for all non-static dependencies.  Static dependencies have already been
+	// combined into the program jar.
+	dexClasspath classpath
+
+	// java9Classpath is the list of jars that will be added to the classpath when targeting
+	// 1.9 or higher.  It generally contains the android.* classes, while the java.* classes
+	// are provided by systemModules.
+	java9Classpath classpath
+
 	processorPath           classpath
 	errorProneProcessorPath classpath
 	processorClasses        []string
@@ -366,6 +453,7 @@
 	aidlPreprocess          android.OptionalPath
 	kotlinStdlib            android.Paths
 	kotlinAnnotations       android.Paths
+	kotlinPlugins           android.Paths
 
 	disableTurbine bool
 }
@@ -385,7 +473,7 @@
 	} else if ctx.Device() {
 		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
 	} else {
-		return JAVA_VERSION_9
+		return JAVA_VERSION_11
 	}
 }
 
@@ -397,6 +485,7 @@
 	JAVA_VERSION_7           = 7
 	JAVA_VERSION_8           = 8
 	JAVA_VERSION_9           = 9
+	JAVA_VERSION_11          = 11
 )
 
 func (v javaVersion) String() string {
@@ -409,6 +498,8 @@
 		return "1.8"
 	case JAVA_VERSION_9:
 		return "1.9"
+	case JAVA_VERSION_11:
+		return "11"
 	default:
 		return "unsupported"
 	}
@@ -429,8 +520,10 @@
 		return JAVA_VERSION_8
 	case "1.9", "9":
 		return JAVA_VERSION_9
-	case "10", "11":
-		ctx.PropertyErrorf("java_version", "Java language levels above 9 are not supported")
+	case "11":
+		return JAVA_VERSION_11
+	case "10":
+		ctx.PropertyErrorf("java_version", "Java language levels 10 is not supported")
 		return JAVA_VERSION_UNSUPPORTED
 	default:
 		ctx.PropertyErrorf("java_version", "Unrecognized Java language level")
@@ -450,7 +543,7 @@
 
 var _ android.ApexModule = (*Library)(nil)
 
-// Provides access to the list of permitted packages from updatable boot jars.
+// Provides access to the list of permitted packages from apex boot jars.
 type PermittedPackagesForUpdatableBootJars interface {
 	PermittedPackagesForUpdatableBootJars() []string
 }
@@ -473,7 +566,7 @@
 	}
 
 	// Store uncompressed dex files that are preopted on /system.
-	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !odexOnSystemOther(ctx, dexpreopter.installPath)) {
+	if !dexpreopter.dexpreoptDisabled(ctx) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, dexpreopter.installPath)) {
 		return true
 	}
 	if ctx.Config().UncompressPrivAppDex() &&
@@ -484,9 +577,18 @@
 	return false
 }
 
+// Sets `dexer.dexProperties.Uncompress_dex` to the proper value.
+func setUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter, dexer *dexer) {
+	if dexer.dexProperties.Uncompress_dex == nil {
+		// If the value was not force-set by the user, use reasonable default based on the module.
+		dexer.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, dexpreopter))
+	}
+}
+
 func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
+	j.maxSdkVersion = j.MaxSdkVersion(ctx)
 
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if !apexInfo.IsForPlatform() {
@@ -494,14 +596,12 @@
 	}
 
 	j.checkSdkVersions(ctx)
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
-	if j.dexProperties.Uncompress_dex == nil {
-		// If the value was not force-set by the user, use reasonable default based on the module.
-		j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
-	}
+	setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 	j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
-	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
+	j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 	j.compile(ctx, nil)
 
 	// Collect the module directory for IDE info in java/jdeps.go.
@@ -513,13 +613,29 @@
 		if j.InstallMixin != nil {
 			extraInstallDeps = j.InstallMixin(ctx, j.outputFile)
 		}
-		j.installFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
-			j.Stem()+".jar", j.outputFile, extraInstallDeps...)
+		hostDexNeeded := Bool(j.deviceProperties.Hostdex) && !ctx.Host()
+		if hostDexNeeded {
+			j.hostdexInstallFile = ctx.InstallFile(
+				android.PathForHostDexInstall(ctx, "framework"),
+				j.Stem()+"-hostdex.jar", j.outputFile)
+		}
+		var installDir android.InstallPath
+		if ctx.InstallInTestcases() {
+			var archDir string
+			if !ctx.Host() {
+				archDir = ctx.DeviceConfig().DeviceArch()
+			}
+			installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
+		} else {
+			installDir = android.PathForModuleInstall(ctx, "framework")
+		}
+		j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
 	j.deps(ctx)
+	j.usesLibrary.deps(ctx, false)
 }
 
 const (
@@ -559,8 +675,8 @@
 	copyEverythingToSnapshot = false
 )
 
-func (mt *librarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (mt *librarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (mt *librarySdkMemberType) IsInstance(module android.Module) bool {
@@ -657,6 +773,7 @@
 
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostAndDeviceSupported)
 	return module
 }
@@ -679,6 +796,7 @@
 
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostSupported)
 	return module
 }
@@ -727,17 +845,44 @@
 
 	// Names of modules containing JNI libraries that should be installed alongside the test.
 	Jni_libs []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type hostTestProperties struct {
 	// list of native binary modules that should be installed alongside the test
 	Data_native_bins []string `android:"arch_variant"`
+
+	// list of device binary modules that should be installed alongside the test
+	// This property only adds the first variant of the dependency
+	Data_device_bins_first []string `android:"arch_variant"`
+
+	// list of device binary modules that should be installed alongside the test
+	// This property adds 64bit AND 32bit variants of the dependency
+	Data_device_bins_both []string `android:"arch_variant"`
+
+	// list of device binary modules that should be installed alongside the test
+	// This property only adds 64bit variants of the dependency
+	Data_device_bins_64 []string `android:"arch_variant"`
+
+	// list of device binary modules that should be installed alongside the test
+	// This property adds 32bit variants of the dependency if available, or else
+	// defaults to the 64bit variant
+	Data_device_bins_prefer32 []string `android:"arch_variant"`
+
+	// list of device binary modules that should be installed alongside the test
+	// This property only adds 32bit variants of the dependency
+	Data_device_bins_32 []string `android:"arch_variant"`
 }
 
 type testHelperLibraryProperties struct {
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type prebuiltTestProperties struct {
@@ -781,6 +926,97 @@
 	dexJarFile android.Path
 }
 
+func (j *Test) InstallInTestcases() bool {
+	// Host java tests install into $(HOST_OUT_JAVA_LIBRARIES), and then are copied into
+	// testcases by base_rules.mk.
+	return !j.Host()
+}
+
+func (j *TestHelperLibrary) InstallInTestcases() bool {
+	return true
+}
+
+func (j *JavaTestImport) InstallInTestcases() bool {
+	return true
+}
+
+func (j *TestHost) addDataDeviceBinsDeps(ctx android.BottomUpMutatorContext) {
+	if len(j.testHostProperties.Data_device_bins_first) > 0 {
+		deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
+		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_first...)
+	}
+
+	var maybeAndroid32Target *android.Target
+	var maybeAndroid64Target *android.Target
+	android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32")
+	android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64")
+	if len(android32TargetList) > 0 {
+		maybeAndroid32Target = &android32TargetList[0]
+	}
+	if len(android64TargetList) > 0 {
+		maybeAndroid64Target = &android64TargetList[0]
+	}
+
+	if len(j.testHostProperties.Data_device_bins_both) > 0 {
+		if maybeAndroid32Target == nil && maybeAndroid64Target == nil {
+			ctx.PropertyErrorf("data_device_bins_both", "no device targets available. Targets: %q", ctx.Config().Targets)
+			return
+		}
+		if maybeAndroid32Target != nil {
+			ctx.AddFarVariationDependencies(
+				maybeAndroid32Target.Variations(),
+				dataDeviceBinsTag,
+				j.testHostProperties.Data_device_bins_both...,
+			)
+		}
+		if maybeAndroid64Target != nil {
+			ctx.AddFarVariationDependencies(
+				maybeAndroid64Target.Variations(),
+				dataDeviceBinsTag,
+				j.testHostProperties.Data_device_bins_both...,
+			)
+		}
+	}
+
+	if len(j.testHostProperties.Data_device_bins_prefer32) > 0 {
+		if maybeAndroid32Target != nil {
+			ctx.AddFarVariationDependencies(
+				maybeAndroid32Target.Variations(),
+				dataDeviceBinsTag,
+				j.testHostProperties.Data_device_bins_prefer32...,
+			)
+		} else {
+			if maybeAndroid64Target == nil {
+				ctx.PropertyErrorf("data_device_bins_prefer32", "no device targets available. Targets: %q", ctx.Config().Targets)
+				return
+			}
+			ctx.AddFarVariationDependencies(
+				maybeAndroid64Target.Variations(),
+				dataDeviceBinsTag,
+				j.testHostProperties.Data_device_bins_prefer32...,
+			)
+		}
+	}
+
+	if len(j.testHostProperties.Data_device_bins_32) > 0 {
+		if maybeAndroid32Target == nil {
+			ctx.PropertyErrorf("data_device_bins_32", "cannot find 32bit device target. Targets: %q", ctx.Config().Targets)
+			return
+		}
+		deviceVariations := maybeAndroid32Target.Variations()
+		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_32...)
+	}
+
+	if len(j.testHostProperties.Data_device_bins_64) > 0 {
+		if maybeAndroid64Target == nil {
+			ctx.PropertyErrorf("data_device_bins_64", "cannot find 64bit device target. Targets: %q", ctx.Config().Targets)
+			return
+		}
+		deviceVariations := maybeAndroid64Target.Variations()
+		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_64...)
+	}
+}
+
 func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if len(j.testHostProperties.Data_native_bins) > 0 {
 		for _, target := range ctx.MultiTargets() {
@@ -795,6 +1031,8 @@
 		}
 	}
 
+	j.addDataDeviceBinsDeps(ctx)
+
 	j.deps(ctx)
 }
 
@@ -802,14 +1040,58 @@
 	j.extraResources = append(j.extraResources, p)
 }
 
+func (j *TestHost) dataDeviceBins() []string {
+	ret := make([]string, 0,
+		len(j.testHostProperties.Data_device_bins_first)+
+			len(j.testHostProperties.Data_device_bins_both)+
+			len(j.testHostProperties.Data_device_bins_prefer32)+
+			len(j.testHostProperties.Data_device_bins_32)+
+			len(j.testHostProperties.Data_device_bins_64),
+	)
+
+	ret = append(ret, j.testHostProperties.Data_device_bins_first...)
+	ret = append(ret, j.testHostProperties.Data_device_bins_both...)
+	ret = append(ret, j.testHostProperties.Data_device_bins_prefer32...)
+	ret = append(ret, j.testHostProperties.Data_device_bins_32...)
+	ret = append(ret, j.testHostProperties.Data_device_bins_64...)
+
+	return ret
+}
+
+func (j *TestHost) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var configs []tradefed.Config
+	dataDeviceBins := j.dataDeviceBins()
+	if len(dataDeviceBins) > 0 {
+		// add Tradefed configuration to push device bins to device for testing
+		remoteDir := filepath.Join("/data/local/tests/unrestricted/", j.Name())
+		options := []tradefed.Option{{Name: "cleanup", Value: "true"}}
+		for _, bin := range dataDeviceBins {
+			fullPath := filepath.Join(remoteDir, bin)
+			options = append(options, tradefed.Option{Name: "push-file", Key: bin, Value: fullPath})
+		}
+		configs = append(configs, tradefed.Object{
+			Type:    "target_preparer",
+			Class:   "com.android.tradefed.targetprep.PushFilePreparer",
+			Options: options,
+		})
+	}
+
+	j.Test.generateAndroidBuildActionsWithConfig(ctx, configs)
+}
+
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	j.generateAndroidBuildActionsWithConfig(ctx, nil)
+}
+
+func (j *Test) generateAndroidBuildActionsWithConfig(ctx android.ModuleContext, configs []tradefed.Config) {
 	if j.testProperties.Test_options.Unit_test == nil && ctx.Host() {
 		// TODO(b/): Clean temporary heuristic to avoid unexpected onboarding.
 		defaultUnitTest := !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites)
 		j.testProperties.Test_options.Unit_test = proptools.BoolPtr(defaultUnitTest)
 	}
+
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
-		j.testProperties.Test_suites, j.testProperties.Auto_gen_config, j.testProperties.Test_options.Unit_test)
+		j.testProperties.Test_suites, configs, j.testProperties.Auto_gen_config, j.testProperties.Test_options.Unit_test)
 
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
@@ -819,6 +1101,10 @@
 		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
 	})
 
+	ctx.VisitDirectDepsWithTag(dataDeviceBinsTag, func(dep android.Module) {
+		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
+	})
+
 	ctx.VisitDirectDepsWithTag(jniLibTag, func(dep android.Module) {
 		sharedLibInfo := ctx.OtherModuleProvider(dep, cc.SharedLibraryInfoProvider).(cc.SharedLibraryInfo)
 		if sharedLibInfo.SharedLibrary != nil {
@@ -851,7 +1137,7 @@
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.prebuiltTestProperties.Test_config, nil,
-		j.prebuiltTestProperties.Test_suites, nil, nil)
+		j.prebuiltTestProperties.Test_suites, nil, nil, nil)
 
 	j.Import.GenerateAndroidBuildActions(ctx)
 }
@@ -860,8 +1146,8 @@
 	android.SdkMemberTypeBase
 }
 
-func (mt *testSdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (mt *testSdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (mt *testSdkMemberType) IsInstance(module android.Module) bool {
@@ -1012,14 +1298,14 @@
 
 type binaryProperties struct {
 	// installable script to execute the resulting jar
-	Wrapper *string `android:"path"`
+	Wrapper *string `android:"path,arch_variant"`
 
 	// Name of the class containing main to be inserted into the manifest as Main-Class.
 	Main_class *string
 
 	// Names of modules containing JNI libraries that should be installed alongside the host
 	// variant of the binary.
-	Jni_libs []string
+	Jni_libs []string `android:"arch_variant"`
 }
 
 type Binary struct {
@@ -1057,14 +1343,23 @@
 		if j.binaryProperties.Wrapper != nil {
 			j.wrapperFile = android.PathForModuleSrc(ctx, *j.binaryProperties.Wrapper)
 		} else {
+			if ctx.Windows() {
+				ctx.PropertyErrorf("wrapper", "wrapper is required for Windows")
+			}
+
 			j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
 		}
 
+		ext := ""
+		if ctx.Windows() {
+			ext = ".bat"
+		}
+
 		// The host installation rules make the installed wrapper depend on all the dependencies
 		// of the wrapper variant, which will include the common variant's jar file and any JNI
 		// libraries.  This is verified by TestBinary.
 		j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
-			ctx.ModuleName(), j.wrapperFile)
+			ctx.ModuleName()+ext, j.wrapperFile)
 	}
 }
 
@@ -1100,6 +1395,8 @@
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommonFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
+
 	return module
 }
 
@@ -1117,6 +1414,7 @@
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommonFirst)
 	android.InitDefaultableModule(module)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -1138,7 +1436,6 @@
 	Installable *bool
 
 	// If not empty, classes are restricted to the specified packages and their sub-packages.
-	// This information is used to generate the updatable-bcp-packages.txt file.
 	Permitted_packages []string
 
 	// List of shared java libs that this module has dependencies to
@@ -1167,6 +1464,7 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 	android.ApexModuleBase
+	android.BazelModuleBase
 	prebuilt android.Prebuilt
 	android.SdkBase
 
@@ -1180,7 +1478,8 @@
 	properties ImportProperties
 
 	// output file containing classes.dex and resources
-	dexJarFile android.Path
+	dexJarFile        OptionalDexJarPath
+	dexJarInstallFile android.Path
 
 	combinedClasspathFile android.Path
 	classLoaderContexts   dexpreopt.ClassLoaderContextMap
@@ -1264,6 +1563,10 @@
 		j.hideApexVariantFromMake = true
 	}
 
+	if ctx.Windows() {
+		j.HideFromMake()
+	}
+
 	jars := android.PathsForModuleSrc(ctx, j.properties.Jars)
 
 	jarName := j.Stem() + ".jar"
@@ -1279,7 +1582,6 @@
 	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
 
 	var flags javaBuilderFlags
-	var deapexerModule android.Module
 
 	ctx.VisitDirectDeps(func(module android.Module) {
 		tag := ctx.OtherModuleDependencyTag(module)
@@ -1287,7 +1589,10 @@
 		if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
 			dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
 			switch tag {
-			case libTag, staticLibTag:
+			case libTag:
+				flags.classpath = append(flags.classpath, dep.HeaderJars...)
+				flags.dexClasspath = append(flags.dexClasspath, dep.HeaderJars...)
+			case staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars...)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars...)
@@ -1300,20 +1605,20 @@
 		}
 
 		addCLCFromDep(ctx, module, j.classLoaderContexts)
-
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			if deapexerModule != nil {
-				ctx.ModuleErrorf("Ambiguous duplicate deapexer module dependencies %q and %q",
-					deapexerModule.Name(), module.Name())
-			}
-			deapexerModule = module
-		}
 	})
 
 	if Bool(j.properties.Installable) {
-		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
-			jarName, outputFile)
+		var installDir android.InstallPath
+		if ctx.InstallInTestcases() {
+			var archDir string
+			if !ctx.Host() {
+				archDir = ctx.DeviceConfig().DeviceArch()
+			}
+			installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
+		} else {
+			installDir = android.PathForModuleInstall(ctx, "framework")
+		}
+		ctx.InstallFile(installDir, jarName, outputFile)
 	}
 
 	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
@@ -1323,25 +1628,28 @@
 		// obtained from the associated deapexer module.
 		ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if ai.ForPrebuiltApex {
-			if deapexerModule == nil {
-				// This should never happen as a variant for a prebuilt_apex is only created if the
-				// deapexer module has been configured to export the dex implementation jar for this module.
-				ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
-					j.Name(), ai.ApexVariationName)
-				return
-			}
-
 			// Get the path of the dex implementation jar from the `deapexer` module.
-			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+			di := android.FindDeapexerProviderForModule(ctx)
+			if di == nil {
+				return // An error has been reported by FindDeapexerProviderForModule.
+			}
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(j.BaseModuleName())); dexOutputPath != nil {
-				j.dexJarFile = dexOutputPath
+				dexJarFile := makeDexJarPathFromPath(dexOutputPath)
+				j.dexJarFile = dexJarFile
+				installPath := android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(j.BaseModuleName()))
+				j.dexJarInstallFile = installPath
+
+				j.dexpreopter.installPath = j.dexpreopter.getInstallPath(ctx, installPath)
+				setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
+				j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
+				j.dexpreopt(ctx, dexOutputPath)
 
 				// Initialize the hiddenapi structure.
-				j.initHiddenAPI(ctx, dexOutputPath, outputFile, nil)
+				j.initHiddenAPI(ctx, dexJarFile, outputFile, j.dexProperties.Uncompress_dex)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
-				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt APEX %s", di.ApexModuleName())
 			}
 		} else if Bool(j.dexProperties.Compile_dex) {
 			sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
@@ -1355,11 +1663,9 @@
 
 			// Dex compilation
 
-			j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName)
-			if j.dexProperties.Uncompress_dex == nil {
-				// If the value was not force-set by the user, use reasonable default based on the module.
-				j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
-			}
+			j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+				ctx, android.PathForModuleInstall(ctx, "framework", jarName))
+			setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 
 			var dexOutputFile android.OutputPath
@@ -1369,12 +1675,13 @@
 			}
 
 			// Initialize the hiddenapi structure.
-			j.initHiddenAPI(ctx, dexOutputFile, outputFile, j.dexProperties.Uncompress_dex)
+			j.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), outputFile, j.dexProperties.Uncompress_dex)
 
 			// Encode hidden API flags in dex file.
 			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
 
-			j.dexJarFile = dexOutputFile
+			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
+			j.dexJarInstallFile = android.PathForModuleInstall(ctx, "framework", jarName)
 		}
 	}
 
@@ -1411,12 +1718,12 @@
 	return android.Paths{j.combinedClasspathFile}
 }
 
-func (j *Import) DexJarBuildPath() android.Path {
+func (j *Import) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
 func (j *Import) DexJarInstallPath() android.Path {
-	return nil
+	return j.dexJarInstallFile
 }
 
 func (j *Import) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
@@ -1440,12 +1747,8 @@
 	if sdkSpec.Kind == android.SdkCore {
 		return nil
 	}
-	ver, err := sdkSpec.EffectiveVersion(ctx)
-	if err != nil {
-		return err
-	}
-	if ver.GreaterThan(sdkVersion) {
-		return fmt.Errorf("newer SDK(%v)", ver)
+	if sdkSpec.ApiLevel.GreaterThan(sdkVersion) {
+		return fmt.Errorf("newer SDK(%v)", sdkSpec.ApiLevel)
 	}
 	return nil
 }
@@ -1478,9 +1781,6 @@
 var _ android.IDECustomizedModuleName = (*Import)(nil)
 
 // Collect information for opening IDE project files in java/jdeps.go.
-const (
-	removedPrefix = "prebuilt_"
-)
 
 func (j *Import) IDEInfo(dpInfo *android.IdeInfo) {
 	dpInfo.Jars = append(dpInfo.Jars, j.PrebuiltSrcs()...)
@@ -1490,11 +1790,7 @@
 	// TODO(b/113562217): Extract the base module name from the Import name, often the Import name
 	// has a prefix "prebuilt_". Remove the prefix explicitly if needed until we find a better
 	// solution to get the Import name.
-	name := j.Name()
-	if strings.HasPrefix(name, removedPrefix) {
-		name = strings.TrimPrefix(name, removedPrefix)
-	}
-	return name
+	return android.RemoveOptionalPrebuiltPrefix(j.Name())
 }
 
 var _ android.PrebuiltInterface = (*Import)(nil)
@@ -1503,7 +1799,7 @@
 	return Bool(j.properties.Installable)
 }
 
-var _ dexpreopterInterface = (*Import)(nil)
+var _ DexpreopterInterface = (*Import)(nil)
 
 // java_import imports one or more `.jar` files into the build graph as if they were built by a java_library module.
 //
@@ -1527,6 +1823,7 @@
 	android.InitPrebuiltModule(module, &module.properties.Jars)
 	android.InitApexModule(module)
 	android.InitSdkAwareModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostAndDeviceSupported)
 	return module
 }
@@ -1543,6 +1840,7 @@
 
 	android.InitPrebuiltModule(module, &module.properties.Jars)
 	android.InitApexModule(module)
+	android.InitBazelModule(module)
 	InitJavaModule(module, android.HostSupported)
 	return module
 }
@@ -1564,7 +1862,7 @@
 
 	properties DexImportProperties
 
-	dexJarFile android.Path
+	dexJarFile OptionalDexJarPath
 
 	dexpreopter
 
@@ -1616,7 +1914,8 @@
 		j.hideApexVariantFromMake = true
 	}
 
-	j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar")
+	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
+		ctx, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 	j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &j.dexpreopter)
 
 	inputJar := ctx.ExpandSource(j.properties.Jars[0], "jars")
@@ -1654,7 +1953,7 @@
 		})
 	}
 
-	j.dexJarFile = dexOutputFile
+	j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 
 	j.dexpreopt(ctx, dexOutputFile)
 
@@ -1664,7 +1963,7 @@
 	}
 }
 
-func (j *DexImport) DexJarBuildPath() android.Path {
+func (j *DexImport) DexJarBuildPath() OptionalDexJarPath {
 	return j.dexJarFile
 }
 
@@ -1738,6 +2037,7 @@
 	module.AddProperties(
 		&CommonProperties{},
 		&DeviceProperties{},
+		&OverridableDeviceProperties{},
 		&DexProperties{},
 		&DexpreoptProperties{},
 		&android.ProtoProperties{},
@@ -1796,32 +2096,30 @@
 		return
 	}
 
-	// Find out if the dependency is either an SDK library or an ordinary library that is disguised
-	// as an SDK library by the means of `provides_uses_lib` property. If yes, the library is itself
-	// a <uses-library> and should be added as a node in the CLC tree, and its CLC should be added
-	// as subtree of that node. Otherwise the library is not a <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.
-	var implicitSdkLib *string
-	comp, isComp := depModule.(SdkLibraryComponentDependency)
-	if isComp {
-		implicitSdkLib = comp.OptionalImplicitSdkLibrary()
-		// OptionalImplicitSdkLibrary() may be nil so need to fall through to ProvidesUsesLib().
-	}
-	if implicitSdkLib == nil {
-		if ulib, ok := depModule.(ProvidesUsesLib); ok {
-			implicitSdkLib = ulib.ProvidesUsesLib()
-		}
+	depName := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(depModule))
+
+	var sdkLib *string
+	if lib, ok := depModule.(SdkLibraryDependency); ok && lib.sharedLibrary() {
+		// A shared SDK library. This should be added as a top-level CLC element.
+		sdkLib = &depName
+	} else if ulib, ok := depModule.(ProvidesUsesLib); ok {
+		// A non-SDK library disguised as an SDK library by the means of `provides_uses_lib`
+		// property. This should be handled in the same way as a shared SDK library.
+		sdkLib = ulib.ProvidesUsesLib()
 	}
 
 	depTag := ctx.OtherModuleDependencyTag(depModule)
-	if depTag == libTag || depTag == usesLibTag {
+	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 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,
 		// which should not be pulled just because of a static component library.
-		if implicitSdkLib != nil {
+		if sdkLib != nil {
 			return
 		}
 	} else {
@@ -1829,11 +2127,260 @@
 		return
 	}
 
-	if implicitSdkLib != nil {
-		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *implicitSdkLib,
-			dep.DexJarBuildPath(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
+	// If this is an SDK (or SDK-like) library, then it should be added as a node in the CLC tree,
+	// and its CLC should be added as subtree of that node. Otherwise the library is not a
+	// <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,
+			dep.DexJarBuildPath().PathOrNil(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
 	} else {
-		depName := ctx.OtherModuleName(depModule)
 		clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
 	}
 }
+
+type javaCommonAttributes struct {
+	Srcs      bazel.LabelListAttribute
+	Plugins   bazel.LabelListAttribute
+	Javacopts bazel.StringListAttribute
+}
+
+type javaDependencyLabels struct {
+	// Dependencies which DO NOT contribute to the API visible to upstream dependencies.
+	Deps bazel.LabelListAttribute
+	// Dependencies which DO contribute to the API visible to upstream dependencies.
+	StaticDeps bazel.LabelListAttribute
+}
+
+// convertLibraryAttrsBp2Build converts a few shared attributes from java_* modules
+// and also separates dependencies into dynamic dependencies and static dependencies.
+// Each corresponding Bazel target type, can have a different method for handling
+// dynamic vs. static dependencies, and so these are returned to the calling function.
+type eventLogTagsAttributes struct {
+	Srcs bazel.LabelListAttribute
+}
+
+func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) (*javaCommonAttributes, *javaDependencyLabels) {
+	var srcs bazel.LabelListAttribute
+	archVariantProps := m.GetArchVariantProperties(ctx, &CommonProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*CommonProperties); ok {
+				archSrcs := android.BazelLabelForModuleSrcExcludes(ctx, archProps.Srcs, archProps.Exclude_srcs)
+				srcs.SetSelectValue(axis, config, archSrcs)
+			}
+		}
+	}
+
+	javaSrcPartition := "java"
+	protoSrcPartition := "proto"
+	logtagSrcPartition := "logtag"
+	srcPartitions := bazel.PartitionLabelListAttribute(ctx, &srcs, bazel.LabelPartitions{
+		javaSrcPartition:   bazel.LabelPartition{Extensions: []string{".java"}, Keep_remainder: true},
+		logtagSrcPartition: bazel.LabelPartition{Extensions: []string{".logtags", ".logtag"}},
+		protoSrcPartition:  android.ProtoSrcLabelPartition,
+	})
+
+	javaSrcs := srcPartitions[javaSrcPartition]
+
+	var logtagsSrcs bazel.LabelList
+	if !srcPartitions[logtagSrcPartition].IsEmpty() {
+		logtagsLibName := m.Name() + "_logtags"
+		logtagsSrcs = bazel.MakeLabelList([]bazel.Label{{Label: ":" + logtagsLibName}})
+		ctx.CreateBazelTargetModule(
+			bazel.BazelTargetModuleProperties{
+				Rule_class:        "event_log_tags",
+				Bzl_load_location: "//build/make/tools:event_log_tags.bzl",
+			},
+			android.CommonAttributes{Name: logtagsLibName},
+			&eventLogTagsAttributes{
+				Srcs: srcPartitions[logtagSrcPartition],
+			},
+		)
+	}
+	javaSrcs.Append(bazel.MakeLabelListAttribute(logtagsSrcs))
+
+	var javacopts []string
+	if m.properties.Javacflags != nil {
+		javacopts = append(javacopts, m.properties.Javacflags...)
+	}
+	epEnabled := m.properties.Errorprone.Enabled
+	//TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
+	if Bool(epEnabled) {
+		javacopts = append(javacopts, m.properties.Errorprone.Javacflags...)
+	}
+
+	commonAttrs := &javaCommonAttributes{
+		Srcs: javaSrcs,
+		Plugins: bazel.MakeLabelListAttribute(
+			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
+		),
+		Javacopts: bazel.MakeStringListAttribute(javacopts),
+	}
+
+	depLabels := &javaDependencyLabels{}
+
+	var deps bazel.LabelList
+	if m.properties.Libs != nil {
+		deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
+	}
+
+	var staticDeps bazel.LabelList
+	if m.properties.Static_libs != nil {
+		staticDeps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
+	}
+
+	protoDepLabel := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
+	// Soong does not differentiate between a java_library and the Bazel equivalent of
+	// a java_proto_library + proto_library pair. Instead, in Soong proto sources are
+	// listed directly in the srcs of a java_library, and the classes produced
+	// by protoc are included directly in the resulting JAR. Thus upstream dependencies
+	// that depend on a java_library with proto sources can link directly to the protobuf API,
+	// and so this should be a static dependency.
+	staticDeps.Add(protoDepLabel)
+
+	depLabels.Deps = bazel.MakeLabelListAttribute(deps)
+	depLabels.StaticDeps = bazel.MakeLabelListAttribute(staticDeps)
+
+	return commonAttrs, depLabels
+}
+
+type javaLibraryAttributes struct {
+	*javaCommonAttributes
+	Deps    bazel.LabelListAttribute
+	Exports bazel.LabelListAttribute
+}
+
+func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
+	commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	if !commonAttrs.Srcs.IsEmpty() {
+		deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
+
+		sdkVersion := m.SdkVersion(ctx)
+		if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
+			// TODO(b/220869005) remove forced dependency on current public android.jar
+			deps.Add(bazel.MakeLabelAttribute("//prebuilts/sdk:public_current_android_sdk_java_import"))
+		}
+	} else if !depLabels.Deps.IsEmpty() {
+		ctx.ModuleErrorf("Module has direct dependencies but no sources. Bazel will not allow this.")
+	}
+
+	attrs := &javaLibraryAttributes{
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Exports:              depLabels.StaticDeps,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "java_library",
+		Bzl_load_location: "//build/bazel/rules/java:library.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+}
+
+type javaBinaryHostAttributes struct {
+	*javaCommonAttributes
+	Deps         bazel.LabelListAttribute
+	Runtime_deps bazel.LabelListAttribute
+	Main_class   string
+	Jvm_flags    bazel.StringListAttribute
+}
+
+// JavaBinaryHostBp2Build is for java_binary_host bp2build.
+func javaBinaryHostBp2Build(ctx android.TopDownMutatorContext, m *Binary) {
+	commonAttrs, depLabels := m.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	deps.Append(depLabels.StaticDeps)
+	if m.binaryProperties.Jni_libs != nil {
+		deps.Append(bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, m.binaryProperties.Jni_libs)))
+	}
+
+	var runtimeDeps bazel.LabelListAttribute
+	if commonAttrs.Srcs.IsEmpty() {
+		// if there are no sources, then the dependencies can only be used at runtime
+		runtimeDeps = deps
+		deps = bazel.LabelListAttribute{}
+	}
+
+	mainClass := ""
+	if m.binaryProperties.Main_class != nil {
+		mainClass = *m.binaryProperties.Main_class
+	}
+	if m.properties.Manifest != nil {
+		mainClassInManifest, err := android.GetMainClassInManifest(ctx.Config(), android.PathForModuleSrc(ctx, *m.properties.Manifest).String())
+		if err != nil {
+			return
+		}
+		mainClass = mainClassInManifest
+	}
+
+	attrs := &javaBinaryHostAttributes{
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Runtime_deps:         runtimeDeps,
+		Main_class:           mainClass,
+	}
+
+	// Attribute jvm_flags
+	if m.binaryProperties.Jni_libs != nil {
+		jniLibPackages := map[string]bool{}
+		for _, jniLibLabel := range android.BazelLabelForModuleDeps(ctx, m.binaryProperties.Jni_libs).Includes {
+			jniLibPackage := jniLibLabel.Label
+			indexOfColon := strings.Index(jniLibLabel.Label, ":")
+			if indexOfColon > 0 {
+				// JNI lib from other package
+				jniLibPackage = jniLibLabel.Label[2:indexOfColon]
+			} else if indexOfColon == 0 {
+				// JNI lib in the same package of java_binary
+				packageOfCurrentModule := m.GetBazelLabel(ctx, m)
+				jniLibPackage = packageOfCurrentModule[2:strings.Index(packageOfCurrentModule, ":")]
+			}
+			if _, inMap := jniLibPackages[jniLibPackage]; !inMap {
+				jniLibPackages[jniLibPackage] = true
+			}
+		}
+		jniLibPaths := []string{}
+		for jniLibPackage, _ := range jniLibPackages {
+			// See cs/f:.*/third_party/bazel/.*java_stub_template.txt for the use of RUNPATH
+			jniLibPaths = append(jniLibPaths, "$${RUNPATH}"+jniLibPackage)
+		}
+		attrs.Jvm_flags = bazel.MakeStringListAttribute([]string{"-Djava.library.path=" + strings.Join(jniLibPaths, ":")})
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "java_binary",
+	}
+
+	// Create the BazelTargetModule.
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+}
+
+type bazelJavaImportAttributes struct {
+	Jars bazel.LabelListAttribute
+}
+
+// java_import bp2Build converter.
+func (i *Import) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	var jars bazel.LabelListAttribute
+	archVariantProps := i.GetArchVariantProperties(ctx, &ImportProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*ImportProperties); ok {
+				archJars := android.BazelLabelForModuleSrcExcludes(ctx, archProps.Jars, []string(nil))
+				jars.SetSelectValue(axis, config, archJars)
+			}
+		}
+	}
+
+	attrs := &bazelJavaImportAttributes{
+		Jars: jars,
+	}
+	props := bazel.BazelTargetModuleProperties{Rule_class: "java_import"}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: android.RemoveOptionalPrebuiltPrefix(i.Name())}, attrs)
+
+}
diff --git a/java/java_test.go b/java/java_test.go
index bd373c1..af889cc 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -441,7 +441,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 
 	bar := ctx.ModuleForTests("bar", buildOS+"_common")
 	barJar := bar.Output("bar.jar").Output.String()
@@ -478,7 +478,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 
 	foo := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
 
@@ -523,7 +523,7 @@
 	}
 
 	// check that -g is not overridden for host modules
-	buildOS := android.BuildOs.String()
+	buildOS := result.Config.BuildOS.String()
 	hostBinary := result.ModuleForTests("host_binary", buildOS+"_common")
 	hostJavaFlags := hostBinary.Module().VariablesForTests()["javacFlags"]
 	if strings.Contains(hostJavaFlags, "-g:source,lines") {
@@ -600,8 +600,8 @@
 	}
 
 	barDexJar := barModule.Module().(*Import).DexJarBuildPath()
-	if barDexJar != nil {
-		t.Errorf("bar dex jar build path expected to be nil, got %q", barDexJar)
+	if barDexJar.IsSet() {
+		t.Errorf("bar dex jar build path expected to be set, got %s", barDexJar)
 	}
 
 	if !strings.Contains(javac.Args["classpath"], sdklibStubsJar.String()) {
@@ -612,7 +612,7 @@
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, bazJar.String())
 	}
 
-	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath()
+	bazDexJar := bazModule.Module().(*Import).DexJarBuildPath().Path()
 	expectedDexJar := "out/soong/.intermediates/baz/android_common/dex/baz.jar"
 	android.AssertPathRelativeToTopEquals(t, "baz dex jar build path", expectedDexJar, bazDexJar)
 
@@ -973,7 +973,7 @@
 
 	fooHeaderJar := filepath.Join("out", "soong", ".intermediates", "foo", "android_common", "turbine-combined", "foo.jar")
 	barTurbineJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
-	android.AssertStringDoesContain(t, "bar turbine classpath", barTurbine.Args["classpath"], fooHeaderJar)
+	android.AssertStringDoesContain(t, "bar turbine classpath", barTurbine.Args["turbineFlags"], fooHeaderJar)
 	android.AssertStringDoesContain(t, "bar javac classpath", barJavac.Args["classpath"], fooHeaderJar)
 	android.AssertPathsRelativeToTopEquals(t, "bar turbine combineJar", []string{barTurbineJar, fooHeaderJar}, barTurbineCombined.Inputs)
 	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "prebuilts/sdk/14/public/android.jar")
@@ -988,69 +988,15 @@
 		}
 		`)
 
-	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
+	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
 	for i := 0; i < 3; i++ {
 		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i))
-		if !strings.Contains(barJavac.Args["classpath"], barHeaderJar) {
-			t.Errorf("bar javac classpath %v does not contain %q", barJavac.Args["classpath"], barHeaderJar)
+		if !strings.HasPrefix(barJavac.Args["classpath"], "-classpath "+barHeaderJar+":") {
+			t.Errorf("bar javac classpath %v does start with %q", barJavac.Args["classpath"], barHeaderJar)
 		}
 	}
 }
 
-func TestJarGenrules(t *testing.T) {
-	ctx, _ := testJava(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-		}
-
-		java_genrule {
-			name: "jargen",
-			tool_files: ["b.java"],
-			cmd: "$(location b.java) $(in) $(out)",
-			out: ["jargen.jar"],
-			srcs: [":foo"],
-		}
-
-		java_library {
-			name: "bar",
-			static_libs: ["jargen"],
-			srcs: ["c.java"],
-		}
-
-		java_library {
-			name: "baz",
-			libs: ["jargen"],
-			srcs: ["c.java"],
-		}
-	`)
-
-	foo := ctx.ModuleForTests("foo", "android_common").Output("javac/foo.jar")
-	jargen := ctx.ModuleForTests("jargen", "android_common").Output("jargen.jar")
-	bar := ctx.ModuleForTests("bar", "android_common").Output("javac/bar.jar")
-	baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
-	barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
-
-	if g, w := jargen.Implicits.Strings(), foo.Output.String(); !android.InList(w, g) {
-		t.Errorf("expected jargen inputs [%q], got %q", w, g)
-	}
-
-	if !strings.Contains(bar.Args["classpath"], jargen.Output.String()) {
-		t.Errorf("bar classpath %v does not contain %q", bar.Args["classpath"], jargen.Output.String())
-	}
-
-	if !strings.Contains(baz.Args["classpath"], jargen.Output.String()) {
-		t.Errorf("baz classpath %v does not contain %q", baz.Args["classpath"], jargen.Output.String())
-	}
-
-	if len(barCombined.Inputs) != 2 ||
-		barCombined.Inputs[0].String() != bar.Output.String() ||
-		barCombined.Inputs[1].String() != jargen.Output.String() {
-		t.Errorf("bar combined jar inputs %v is not [%q, %q]",
-			barCombined.Inputs.Strings(), bar.Output.String(), jargen.Output.String())
-	}
-}
-
 func TestExcludeFileGroupInSrcs(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
@@ -1183,7 +1129,7 @@
 			break
 		}
 	}
-	if expected != android.StringPathRelativeToTop(ctx.Config().BuildDir(), got) {
+	if expected != android.StringPathRelativeToTop(ctx.Config().SoongOutDir(), got) {
 		t.Errorf("Unexpected patch-module flag for module %q - expected %q, but got %q", moduleName, expected, got)
 	}
 }
@@ -1357,6 +1303,72 @@
 	}
 }
 
+func TestAidlFlagsWithMinSdkVersion(t *testing.T) {
+	fixture := android.GroupFixturePreparers(
+		prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}}))
+
+	for _, tc := range []struct {
+		name       string
+		sdkVersion string
+		expected   string
+	}{
+		{"default is current", "", "current"},
+		{"use sdk_version", `sdk_version: "14"`, "14"},
+		{"system_current", `sdk_version: "system_current"`, "current"},
+	} {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := fixture.RunTestWithBp(t, `
+				java_library {
+					name: "foo",
+					srcs: ["aidl/foo/IFoo.aidl"],
+					`+tc.sdkVersion+`
+				}
+			`)
+			aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+			expectedAidlFlag := "--min_sdk_version=" + tc.expected
+			if !strings.Contains(aidlCommand, expectedAidlFlag) {
+				t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+			}
+		})
+	}
+}
+
+func TestAidlEnforcePermissions(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["aidl/foo/IFoo.aidl"],
+			aidl: { enforce_permissions: true },
+		}
+	`)
+
+	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	expectedAidlFlag := "-Wmissing-permission-annotation -Werror"
+	if !strings.Contains(aidlCommand, expectedAidlFlag) {
+		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+	}
+}
+
+func TestAidlEnforcePermissionsException(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["aidl/foo/IFoo.aidl", "aidl/foo/IFoo2.aidl"],
+			aidl: { enforce_permissions: true, enforce_permissions_exceptions: ["aidl/foo/IFoo2.aidl"] },
+		}
+	`)
+
+	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	expectedAidlFlag := "$$FLAGS -Wmissing-permission-annotation -Werror aidl/foo/IFoo.aidl"
+	if !strings.Contains(aidlCommand, expectedAidlFlag) {
+		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+	}
+	expectedAidlFlag = "$$FLAGS  aidl/foo/IFoo2.aidl"
+	if !strings.Contains(aidlCommand, expectedAidlFlag) {
+		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
+	}
+}
+
 func TestDataNativeBinaries(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_test_host {
@@ -1371,7 +1383,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 
 	test := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
 	entries := android.AndroidMkEntriesForTest(t, ctx, test)[0]
@@ -1387,8 +1399,271 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 	module := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
 	assertDeepEquals(t, "Default installable value should be true.", proptools.BoolPtr(true),
 		module.properties.Installable)
 }
+
+func TestErrorproneEnabled(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			errorprone: {
+				enabled: true,
+			},
+		}
+	`)
+
+	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+
+	// Test that the errorprone plugins are passed to javac
+	expectedSubstring := "-Xplugin:ErrorProne"
+	if !strings.Contains(javac.Args["javacFlags"], expectedSubstring) {
+		t.Errorf("expected javacFlags to contain %q, got %q", expectedSubstring, javac.Args["javacFlags"])
+	}
+
+	// Modules with errorprone { enabled: true } will include errorprone checks
+	// in the main javac build rule. Only when RUN_ERROR_PRONE is true will
+	// the explicit errorprone build rule be created.
+	errorprone := ctx.ModuleForTests("foo", "android_common").MaybeDescription("errorprone")
+	if errorprone.RuleParams.Description != "" {
+		t.Errorf("expected errorprone build rule to not exist, but it did")
+	}
+}
+
+func TestErrorproneDisabled(t *testing.T) {
+	bp := `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			errorprone: {
+				enabled: false,
+			},
+		}
+	`
+	ctx := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureMergeEnv(map[string]string{
+			"RUN_ERROR_PRONE": "true",
+		}),
+	).RunTestWithBp(t, bp)
+
+	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+
+	// Test that the errorprone plugins are not passed to javac, like they would
+	// be if enabled was true.
+	expectedSubstring := "-Xplugin:ErrorProne"
+	if strings.Contains(javac.Args["javacFlags"], expectedSubstring) {
+		t.Errorf("expected javacFlags to not contain %q, got %q", expectedSubstring, javac.Args["javacFlags"])
+	}
+
+	// Check that no errorprone build rule is created, like there would be
+	// if enabled was unset and RUN_ERROR_PRONE was true.
+	errorprone := ctx.ModuleForTests("foo", "android_common").MaybeDescription("errorprone")
+	if errorprone.RuleParams.Description != "" {
+		t.Errorf("expected errorprone build rule to not exist, but it did")
+	}
+}
+
+func TestErrorproneEnabledOnlyByEnvironmentVariable(t *testing.T) {
+	bp := `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+	`
+	ctx := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureMergeEnv(map[string]string{
+			"RUN_ERROR_PRONE": "true",
+		}),
+	).RunTestWithBp(t, bp)
+
+	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+	errorprone := ctx.ModuleForTests("foo", "android_common").Description("errorprone")
+
+	// Check that the errorprone plugins are not passed to javac, because they
+	// will instead be passed to the separate errorprone compilation
+	expectedSubstring := "-Xplugin:ErrorProne"
+	if strings.Contains(javac.Args["javacFlags"], expectedSubstring) {
+		t.Errorf("expected javacFlags to not contain %q, got %q", expectedSubstring, javac.Args["javacFlags"])
+	}
+
+	// Check that the errorprone plugin is enabled
+	if !strings.Contains(errorprone.Args["javacFlags"], expectedSubstring) {
+		t.Errorf("expected errorprone to contain %q, got %q", expectedSubstring, javac.Args["javacFlags"])
+	}
+}
+
+func TestDataDeviceBinsBuildsDeviceBinary(t *testing.T) {
+	testCases := []struct {
+		dataDeviceBinType  string
+		depCompileMultilib string
+		variants           []string
+		expectedError      string
+	}{
+		{
+			dataDeviceBinType:  "first",
+			depCompileMultilib: "first",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			dataDeviceBinType:  "first",
+			depCompileMultilib: "both",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			// this is true because our testing framework is set up with
+			// Targets ~ [<64bit target>, <32bit target>], where 64bit is "first"
+			dataDeviceBinType:  "first",
+			depCompileMultilib: "32",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "first",
+			depCompileMultilib: "64",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			dataDeviceBinType:  "both",
+			depCompileMultilib: "both",
+			variants: []string{
+				"android_arm_armv7-a-neon",
+				"android_arm64_armv8-a",
+			},
+		},
+		{
+			dataDeviceBinType:  "both",
+			depCompileMultilib: "32",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "both",
+			depCompileMultilib: "64",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "both",
+			depCompileMultilib: "first",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "32",
+			depCompileMultilib: "32",
+			variants:           []string{"android_arm_armv7-a-neon"},
+		},
+		{
+			dataDeviceBinType:  "32",
+			depCompileMultilib: "first",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "32",
+			depCompileMultilib: "both",
+			variants:           []string{"android_arm_armv7-a-neon"},
+		},
+		{
+			dataDeviceBinType:  "32",
+			depCompileMultilib: "64",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "64",
+			depCompileMultilib: "64",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			dataDeviceBinType:  "64",
+			depCompileMultilib: "both",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			dataDeviceBinType:  "64",
+			depCompileMultilib: "first",
+			variants:           []string{"android_arm64_armv8-a"},
+		},
+		{
+			dataDeviceBinType:  "64",
+			depCompileMultilib: "32",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "prefer32",
+			depCompileMultilib: "32",
+			variants:           []string{"android_arm_armv7-a-neon"},
+		},
+		{
+			dataDeviceBinType:  "prefer32",
+			depCompileMultilib: "both",
+			variants:           []string{"android_arm_armv7-a-neon"},
+		},
+		{
+			dataDeviceBinType:  "prefer32",
+			depCompileMultilib: "first",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+		{
+			dataDeviceBinType:  "prefer32",
+			depCompileMultilib: "64",
+			expectedError:      `Android.bp:2:3: dependency "bar" of "foo" missing variant`,
+		},
+	}
+
+	bpTemplate := `
+		java_test_host {
+			name: "foo",
+			srcs: ["test.java"],
+			data_device_bins_%s: ["bar"],
+		}
+
+		cc_binary {
+			name: "bar",
+			compile_multilib: "%s",
+		}
+	`
+
+	for _, tc := range testCases {
+		bp := fmt.Sprintf(bpTemplate, tc.dataDeviceBinType, tc.depCompileMultilib)
+
+		errorHandler := android.FixtureExpectsNoErrors
+		if tc.expectedError != "" {
+			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(tc.expectedError)
+		}
+
+		testName := fmt.Sprintf(`data_device_bins_%s with compile_multilib:"%s"`, tc.dataDeviceBinType, tc.depCompileMultilib)
+		t.Run(testName, func(t *testing.T) {
+			ctx := android.GroupFixturePreparers(PrepareForIntegrationTestWithJava).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, bp)
+			if tc.expectedError != "" {
+				return
+			}
+
+			buildOS := ctx.Config.BuildOS.String()
+			fooVariant := ctx.ModuleForTests("foo", buildOS+"_common")
+			fooMod := fooVariant.Module().(*TestHost)
+			entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, fooMod)[0]
+
+			expectedAutogenConfig := `<option name="push-file" key="bar" value="/data/local/tests/unrestricted/foo/bar" />`
+			autogen := fooVariant.Rule("autogen")
+			if !strings.Contains(autogen.Args["extraConfigs"], expectedAutogenConfig) {
+				t.Errorf("foo extraConfigs %v does not contain %q", autogen.Args["extraConfigs"], expectedAutogenConfig)
+			}
+
+			expectedData := []string{}
+			for _, variant := range tc.variants {
+				barVariant := ctx.ModuleForTests("bar", variant)
+				relocated := barVariant.Output("bar")
+				expectedInput := fmt.Sprintf("out/soong/.intermediates/bar/%s/unstripped/bar", variant)
+				android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input)
+
+				expectedData = append(expectedData, fmt.Sprintf("out/soong/.intermediates/bar/%s/bar:bar", variant))
+			}
+
+			actualData := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
+			android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", ctx.Config, expectedData, actualData)
+		})
+	}
+}
diff --git a/java/jdeps.go b/java/jdeps.go
index 0ab2e42..3734335 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -40,16 +40,11 @@
 var _ android.SingletonMakeVarsProvider = (*jdepsGeneratorSingleton)(nil)
 
 const (
-	// Environment variables used to modify behavior of this singleton.
-	envVariableCollectJavaDeps = "SOONG_COLLECT_JAVA_DEPS"
-	jdepsJsonFileName          = "module_bp_java_deps.json"
+	jdepsJsonFileName = "module_bp_java_deps.json"
 )
 
 func (j *jdepsGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	if !ctx.Config().IsEnvTrue(envVariableCollectJavaDeps) {
-		return
-	}
-
+	// (b/204397180) Generate module_bp_java_deps.json by default.
 	moduleInfos := make(map[string]android.IdeInfo)
 
 	ctx.VisitAllModules(func(module android.Module) {
@@ -76,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 3a6fc0f..903c624 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -28,16 +28,20 @@
 
 var kotlinc = pctx.AndroidRemoteStaticRule("kotlinc", android.RemoteRuleSupports{Goma: true},
 	blueprint.RuleParams{
-		Command: `rm -rf "$classesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
-			`mkdir -p "$classesDir" "$srcJarDir" "$emptyDir" && ` +
+		Command: `rm -rf "$classesDir" "$headerClassesDir" "$srcJarDir" "$kotlinBuildFile" "$emptyDir" && ` +
+			`mkdir -p "$classesDir" "$headerClassesDir" "$srcJarDir" "$emptyDir" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
 			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
 			` --out_dir "$classesDir" --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
 			` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
-			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} ` +
-			`$kotlincFlags -jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile ` +
-			`-kotlin-home $emptyDir && ` +
-			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` +
+			`${config.KotlincCmd} ${config.KotlincGlobalFlags} ` +
+			` ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} ` +
+			` $kotlincFlags -jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile ` +
+			` -kotlin-home $emptyDir ` +
+			` -Xplugin=${config.KotlinAbiGenPluginJar} ` +
+			` -P plugin:org.jetbrains.kotlin.jvm.abi:outputDir=$headerClassesDir && ` +
+			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir -write_if_changed && ` +
+			`${config.SoongZipCmd} -jar -o $headerJar -C $headerClassesDir -D $headerClassesDir -write_if_changed && ` +
 			`rm -rf "$srcJarDir"`,
 		CommandDeps: []string{
 			"${config.KotlincCmd}",
@@ -48,15 +52,17 @@
 			"${config.KotlinStdlibJar}",
 			"${config.KotlinTrove4jJar}",
 			"${config.KotlinAnnotationJar}",
+			"${config.KotlinAbiGenPluginJar}",
 			"${config.GenKotlinBuildFileCmd}",
 			"${config.SoongZipCmd}",
 			"${config.ZipSyncCmd}",
 		},
 		Rspfile:        "$out.rsp",
 		RspfileContent: `$in`,
+		Restat:         true,
 	},
 	"kotlincFlags", "classpath", "srcJars", "commonSrcFilesArg", "srcJarDir", "classesDir",
-	"kotlinJvmTarget", "kotlinBuildFile", "emptyDir", "name")
+	"headerClassesDir", "headerJar", "kotlinJvmTarget", "kotlinBuildFile", "emptyDir", "name")
 
 func kotlinCommonSrcsList(ctx android.ModuleContext, commonSrcFiles android.Paths) android.OptionalPath {
 	if len(commonSrcFiles) > 0 {
@@ -75,12 +81,13 @@
 }
 
 // kotlinCompile takes .java and .kt sources and srcJars, and compiles the .kt sources into a classes jar in outputFile.
-func kotlinCompile(ctx android.ModuleContext, outputFile android.WritablePath,
+func kotlinCompile(ctx android.ModuleContext, outputFile, headerOutputFile android.WritablePath,
 	srcFiles, commonSrcFiles, srcJars android.Paths,
 	flags javaBuilderFlags) {
 
 	var deps android.Paths
 	deps = append(deps, flags.kotlincClasspath...)
+	deps = append(deps, flags.kotlincDeps...)
 	deps = append(deps, srcJars...)
 	deps = append(deps, commonSrcFiles...)
 
@@ -95,17 +102,20 @@
 	}
 
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        kotlinc,
-		Description: "kotlinc",
-		Output:      outputFile,
-		Inputs:      srcFiles,
-		Implicits:   deps,
+		Rule:           kotlinc,
+		Description:    "kotlinc",
+		Output:         outputFile,
+		ImplicitOutput: headerOutputFile,
+		Inputs:         srcFiles,
+		Implicits:      deps,
 		Args: map[string]string{
 			"classpath":         flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":      flags.kotlincFlags,
 			"commonSrcFilesArg": commonSrcFilesArg,
 			"srcJars":           strings.Join(srcJars.Strings(), " "),
 			"classesDir":        android.PathForModuleOut(ctx, "kotlinc", "classes").String(),
+			"headerClassesDir":  android.PathForModuleOut(ctx, "kotlinc", "header_classes").String(),
+			"headerJar":         headerOutputFile.String(),
 			"srcJarDir":         android.PathForModuleOut(ctx, "kotlinc", "srcJars").String(),
 			"kotlinBuildFile":   android.PathForModuleOut(ctx, "kotlinc-build.xml").String(),
 			"emptyDir":          android.PathForModuleOut(ctx, "kotlinc", "empty").String(),
@@ -116,7 +126,7 @@
 	})
 }
 
-var kapt = pctx.AndroidRemoteStaticRule("kapt", android.RemoteRuleSupports{Goma: true},
+var kaptStubs = pctx.AndroidRemoteStaticRule("kaptStubs", android.RemoteRuleSupports{Goma: true},
 	blueprint.RuleParams{
 		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` +
 			`mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` +
@@ -124,19 +134,19 @@
 			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
 			` --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
 			` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
-			`${config.KotlincCmd} ${config.KaptSuppressJDK9Warnings} ${config.KotlincSuppressJDK9Warnings} ` +
+			`${config.KotlincCmd} ${config.KotlincGlobalFlags} ` +
+			`${config.KaptSuppressJDK9Warnings} ${config.KotlincSuppressJDK9Warnings} ` +
 			`${config.JavacHeapFlags} $kotlincFlags -Xplugin=${config.KotlinKaptJar} ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:classes=$kaptDir/classes ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptDir/stubs ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true ` +
-			`-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubsAndApt ` +
+			`-P plugin:org.jetbrains.kotlin.kapt3:aptMode=stubs ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:javacArguments=$encodedJavacFlags ` +
 			`$kaptProcessorPath ` +
 			`$kaptProcessor ` +
 			`-Xbuild-file=$kotlinBuildFile && ` +
-			`${config.SoongZipCmd} -jar -o $out -C $kaptDir/sources -D $kaptDir/sources && ` +
-			`${config.SoongZipCmd} -jar -o $classesJarOut -C $kaptDir/classes -D $kaptDir/classes && ` +
+			`${config.SoongZipCmd} -jar -o $out -C $kaptDir/stubs -D $kaptDir/stubs && ` +
 			`rm -rf "$srcJarDir"`,
 		CommandDeps: []string{
 			"${config.KotlincCmd}",
@@ -165,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...)
@@ -194,13 +205,14 @@
 	kotlinName := filepath.Join(ctx.ModuleDir(), ctx.ModuleSubDir(), ctx.ModuleName())
 	kotlinName = strings.ReplaceAll(kotlinName, "/", "__")
 
+	// First run kapt to generate .java stubs from .kt files
+	kaptStubsJar := android.PathForModuleOut(ctx, "kapt", "stubs.jar")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:           kapt,
-		Description:    "kapt",
-		Output:         srcJarOutputFile,
-		ImplicitOutput: resJarOutputFile,
-		Inputs:         srcFiles,
-		Implicits:      deps,
+		Rule:        kaptStubs,
+		Description: "kapt stubs",
+		Output:      kaptStubsJar,
+		Inputs:      srcFiles,
+		Implicits:   deps,
 		Args: map[string]string{
 			"classpath":         flags.kotlincClasspath.FormJavaClassPath(""),
 			"kotlincFlags":      flags.kotlincFlags,
@@ -216,6 +228,11 @@
 			"classesJarOut":     resJarOutputFile.String(),
 		},
 	})
+
+	// Then run turbine to perform annotation processing on the stubs and any .java srcFiles.
+	javaSrcFiles := srcFiles.FilterByExt(".java")
+	turbineSrcJars := append(android.Paths{kaptStubsJar}, srcJars...)
+	TurbineApt(ctx, srcJarOutputFile, resJarOutputFile, javaSrcFiles, turbineSrcJars, flags)
 }
 
 // kapt converts a list of key, value pairs into a base64 encoded Java serialization, which is what kapt expects.
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 1c146a1..435d782 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -15,10 +15,11 @@
 package java
 
 import (
-	"android/soong/android"
 	"strconv"
 	"strings"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestKotlin(t *testing.T) {
@@ -44,6 +45,10 @@
 	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")
+	fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
+
+	fooKotlincClasses := fooKotlinc.Output
+	fooKotlincHeaderClasses := fooKotlinc.ImplicitOutput
 
 	if len(fooKotlinc.Inputs) != 2 || fooKotlinc.Inputs[0].String() != "a.java" ||
 		fooKotlinc.Inputs[1].String() != "b.kt" {
@@ -54,17 +59,21 @@
 		t.Errorf(`foo inputs %v != ["a.java"]`, fooJavac.Inputs)
 	}
 
-	if !strings.Contains(fooJavac.Args["classpath"], fooKotlinc.Output.String()) {
+	if !strings.Contains(fooJavac.Args["classpath"], fooKotlincHeaderClasses.String()) {
 		t.Errorf("foo classpath %v does not contain %q",
-			fooJavac.Args["classpath"], fooKotlinc.Output.String())
+			fooJavac.Args["classpath"], fooKotlincHeaderClasses.String())
 	}
 
-	if !inList(fooKotlinc.Output.String(), fooJar.Inputs.Strings()) {
+	if !inList(fooKotlincClasses.String(), fooJar.Inputs.Strings()) {
 		t.Errorf("foo jar inputs %v does not contain %q",
-			fooJar.Inputs.Strings(), fooKotlinc.Output.String())
+			fooJar.Inputs.Strings(), fooKotlincClasses.String())
 	}
 
-	fooHeaderJar := ctx.ModuleForTests("foo", "android_common").Output("turbine-combined/foo.jar")
+	if !inList(fooKotlincHeaderClasses.String(), fooHeaderJar.Inputs.Strings()) {
+		t.Errorf("foo header jar inputs %v does not contain %q",
+			fooHeaderJar.Inputs.Strings(), fooKotlincHeaderClasses.String())
+	}
+
 	bazHeaderJar := ctx.ModuleForTests("baz", "android_common").Output("turbine-combined/baz.jar")
 	barKotlinc := ctx.ModuleForTests("bar", "android_common").Rule("kotlinc")
 
@@ -114,53 +123,73 @@
 	t.Run("", func(t *testing.T) {
 		ctx, _ := testJava(t, bp)
 
-		buildOS := android.BuildOs.String()
+		buildOS := ctx.Config().BuildOS.String()
 
-		kapt := ctx.ModuleForTests("foo", "android_common").Rule("kapt")
-		kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
-		javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+		foo := ctx.ModuleForTests("foo", "android_common")
+		kaptStubs := foo.Rule("kapt")
+		turbineApt := foo.Description("turbine apt")
+		kotlinc := foo.Rule("kotlinc")
+		javac := foo.Rule("javac")
 
 		bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
 		baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
 
 		// Test that the kotlin and java sources are passed to kapt and kotlinc
-		if len(kapt.Inputs) != 2 || kapt.Inputs[0].String() != "a.java" || kapt.Inputs[1].String() != "b.kt" {
-			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kapt.Inputs)
+		if len(kaptStubs.Inputs) != 2 || kaptStubs.Inputs[0].String() != "a.java" || kaptStubs.Inputs[1].String() != "b.kt" {
+			t.Errorf(`foo kapt inputs %v != ["a.java", "b.kt"]`, kaptStubs.Inputs)
 		}
 		if len(kotlinc.Inputs) != 2 || kotlinc.Inputs[0].String() != "a.java" || kotlinc.Inputs[1].String() != "b.kt" {
 			t.Errorf(`foo kotlinc inputs %v != ["a.java", "b.kt"]`, kotlinc.Inputs)
 		}
 
-		// Test that only the java sources are passed to javac
+		// Test that only the java sources are passed to turbine-apt and javac
+		if len(turbineApt.Inputs) != 1 || turbineApt.Inputs[0].String() != "a.java" {
+			t.Errorf(`foo turbine apt inputs %v != ["a.java"]`, turbineApt.Inputs)
+		}
 		if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 			t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
 		}
 
-		// Test that the kapt srcjar is a dependency of kotlinc and javac rules
-		if !inList(kapt.Output.String(), kotlinc.Implicits.Strings()) {
-			t.Errorf("expected %q in kotlinc implicits %v", kapt.Output.String(), kotlinc.Implicits.Strings())
-		}
-		if !inList(kapt.Output.String(), javac.Implicits.Strings()) {
-			t.Errorf("expected %q in javac implicits %v", kapt.Output.String(), javac.Implicits.Strings())
+		// Test that the kapt stubs jar is a dependency of turbine-apt
+		if !inList(kaptStubs.Output.String(), turbineApt.Implicits.Strings()) {
+			t.Errorf("expected %q in turbine-apt implicits %v", kaptStubs.Output.String(), kotlinc.Implicits.Strings())
 		}
 
-		// Test that the kapt srcjar is extracted by the kotlinc and javac rules
-		if kotlinc.Args["srcJars"] != kapt.Output.String() {
-			t.Errorf("expected %q in kotlinc srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		// Test that the turbine-apt srcjar is a dependency of kotlinc and javac rules
+		if !inList(turbineApt.Output.String(), kotlinc.Implicits.Strings()) {
+			t.Errorf("expected %q in kotlinc implicits %v", turbineApt.Output.String(), kotlinc.Implicits.Strings())
 		}
-		if javac.Args["srcJars"] != kapt.Output.String() {
-			t.Errorf("expected %q in javac srcjars %v", kapt.Output.String(), kotlinc.Args["srcJars"])
+		if !inList(turbineApt.Output.String(), javac.Implicits.Strings()) {
+			t.Errorf("expected %q in javac implicits %v", turbineApt.Output.String(), javac.Implicits.Strings())
+		}
+
+		// Test that the turbine-apt srcjar is extracted by the kotlinc and javac rules
+		if kotlinc.Args["srcJars"] != turbineApt.Output.String() {
+			t.Errorf("expected %q in kotlinc srcjars %v", turbineApt.Output.String(), kotlinc.Args["srcJars"])
+		}
+		if javac.Args["srcJars"] != turbineApt.Output.String() {
+			t.Errorf("expected %q in javac srcjars %v", turbineApt.Output.String(), kotlinc.Args["srcJars"])
 		}
 
 		// Test that the processors are passed to kapt
 		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
 			" -P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + baz
-		if kapt.Args["kaptProcessorPath"] != expectedProcessorPath {
-			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kapt.Args["kaptProcessorPath"])
+		if kaptStubs.Args["kaptProcessorPath"] != expectedProcessorPath {
+			t.Errorf("expected kaptProcessorPath %q, got %q", expectedProcessorPath, kaptStubs.Args["kaptProcessorPath"])
 		}
 		expectedProcessor := "-P plugin:org.jetbrains.kotlin.kapt3:processors=com.bar -P plugin:org.jetbrains.kotlin.kapt3:processors=com.baz"
-		if kapt.Args["kaptProcessor"] != expectedProcessor {
-			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kapt.Args["kaptProcessor"])
+		if kaptStubs.Args["kaptProcessor"] != expectedProcessor {
+			t.Errorf("expected kaptProcessor %q, got %q", expectedProcessor, kaptStubs.Args["kaptProcessor"])
+		}
+
+		// Test that the processors are passed to turbine-apt
+		expectedProcessorPath = "--processorpath " + bar + " " + baz
+		if !strings.Contains(turbineApt.Args["turbineFlags"], expectedProcessorPath) {
+			t.Errorf("expected turbine-apt processorpath %q, got %q", expectedProcessorPath, turbineApt.Args["turbineFlags"])
+		}
+		expectedProcessor = "--processors com.bar com.baz"
+		if !strings.Contains(turbineApt.Args["turbineFlags"], expectedProcessor) {
+			t.Errorf("expected turbine-apt processor %q, got %q", expectedProcessor, turbineApt.Args["turbineFlags"])
 		}
 
 		// Test that the processors are not passed to javac
@@ -182,10 +211,9 @@
 			android.FixtureMergeEnv(env),
 		).RunTestWithBp(t, bp)
 
-		buildOS := android.BuildOs.String()
+		buildOS := result.Config.BuildOS.String()
 
 		kapt := result.ModuleForTests("foo", "android_common").Rule("kapt")
-		//kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
 		javac := result.ModuleForTests("foo", "android_common").Description("javac")
 		errorprone := result.ModuleForTests("foo", "android_common").Description("errorprone")
 
@@ -281,3 +309,54 @@
 		})
 	}
 }
+
+func TestKotlinCompose(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "androidx.compose.runtime_runtime",
+		}
+
+		java_library_host {
+			name: "androidx.compose.compiler_compiler-hosted",
+		}
+
+		java_library {
+			name: "withcompose",
+			srcs: ["a.kt"],
+			plugins: ["plugin"],
+			static_libs: ["androidx.compose.runtime_runtime"],
+		}
+
+		java_library {
+			name: "nocompose",
+			srcs: ["a.kt"],
+		}
+
+		java_plugin {
+			name: "plugin",
+		}
+	`)
+
+	buildOS := result.Config.BuildOS.String()
+
+	composeCompiler := result.ModuleForTests("androidx.compose.compiler_compiler-hosted", buildOS+"_common").Rule("combineJar").Output
+	withCompose := result.ModuleForTests("withcompose", "android_common")
+	noCompose := result.ModuleForTests("nocompose", "android_common")
+
+	android.AssertStringListContains(t, "missing compose compiler dependency",
+		withCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String())
+
+	android.AssertStringDoesContain(t, "missing compose compiler plugin",
+		withCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
+
+	android.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())
+
+	android.AssertStringDoesNotContain(t, "unexpected compose compiler plugin",
+		noCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
+}
diff --git a/java/legacy_core_platform_api_usage.go b/java/legacy_core_platform_api_usage.go
index 628a100..8e22491 100644
--- a/java/legacy_core_platform_api_usage.go
+++ b/java/legacy_core_platform_api_usage.go
@@ -81,7 +81,6 @@
 	"ds-car-docs", // for AAOS API documentation only
 	"DynamicSystemInstallationService",
 	"EmergencyInfo-lib",
-	"ethernet-service",
 	"EthernetServiceTests",
 	"ExternalStorageProvider",
 	"face-V1-0-javalib",
@@ -228,17 +227,27 @@
 	}
 }
 
-func useLegacyCorePlatformApi(ctx android.EarlyModuleContext) bool {
-	return useLegacyCorePlatformApiByName(ctx.ModuleName())
+var legacyCorePlatformApiLookupKey = android.NewOnceKey("legacyCorePlatformApiLookup")
+
+func getLegacyCorePlatformApiLookup(config android.Config) map[string]struct{} {
+	return config.Once(legacyCorePlatformApiLookupKey, func() interface{} {
+		return legacyCorePlatformApiLookup
+	}).(map[string]struct{})
 }
 
-func useLegacyCorePlatformApiByName(name string) bool {
-	_, found := legacyCorePlatformApiLookup[name]
+// useLegacyCorePlatformApi checks to see whether the supplied module name is in the list of modules
+// that are able to use the legacy core platform API and returns true if it does, false otherwise.
+//
+// This method takes the module name separately from the context as this may be being called for a
+// module that is not the target of the supplied context.
+func useLegacyCorePlatformApi(ctx android.EarlyModuleContext, moduleName string) bool {
+	lookup := getLegacyCorePlatformApiLookup(ctx.Config())
+	_, found := lookup[moduleName]
 	return found
 }
 
 func corePlatformSystemModules(ctx android.EarlyModuleContext) string {
-	if useLegacyCorePlatformApi(ctx) {
+	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
 		return config.LegacyCorePlatformSystemModules
 	} else {
 		return config.StableCorePlatformSystemModules
@@ -246,7 +255,7 @@
 }
 
 func corePlatformBootclasspathLibraries(ctx android.EarlyModuleContext) []string {
-	if useLegacyCorePlatformApi(ctx) {
+	if useLegacyCorePlatformApi(ctx, ctx.ModuleName()) {
 		return config.LegacyCorePlatformBootclasspathLibraries
 	} else {
 		return config.StableCorePlatformBootclasspathLibraries
diff --git a/java/lint.go b/java/lint.go
index fe3218e..426a2af 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
@@ -102,12 +102,12 @@
 	lintOutputs() *lintOutputs
 }
 
-type lintDepSetsIntf interface {
+type LintDepSetsIntf interface {
 	LintDepSets() LintDepSets
 
 	// Methods used to propagate strict_updatability_linting values.
-	getStrictUpdatabilityLinting() bool
-	setStrictUpdatabilityLinting(bool)
+	GetStrictUpdatabilityLinting() bool
+	SetStrictUpdatabilityLinting(bool)
 }
 
 type LintDepSets struct {
@@ -158,15 +158,15 @@
 	return l.outputs.depSets
 }
 
-func (l *linter) getStrictUpdatabilityLinting() bool {
+func (l *linter) GetStrictUpdatabilityLinting() bool {
 	return BoolDefault(l.properties.Lint.Strict_updatability_linting, false)
 }
 
-func (l *linter) setStrictUpdatabilityLinting(strictLinting bool) {
+func (l *linter) SetStrictUpdatabilityLinting(strictLinting bool) {
 	l.properties.Lint.Strict_updatability_linting = &strictLinting
 }
 
-var _ lintDepSetsIntf = (*linter)(nil)
+var _ LintDepSetsIntf = (*linter)(nil)
 
 var _ lintOutputsIntf = (*linter)(nil)
 
@@ -273,7 +273,7 @@
 	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
 	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
 
-	if l.getStrictUpdatabilityLinting() {
+	if l.GetStrictUpdatabilityLinting() {
 		// Verify the module does not baseline issues that endanger safe updatability.
 		if baselinePath := l.getBaselineFilepath(ctx); baselinePath.Valid() {
 			cmd.FlagWithInput("--baseline ", baselinePath.Path())
@@ -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 {
@@ -377,11 +377,12 @@
 	html := android.PathForModuleOut(ctx, "lint", "lint-report.html")
 	text := android.PathForModuleOut(ctx, "lint", "lint-report.txt")
 	xml := android.PathForModuleOut(ctx, "lint", "lint-report.xml")
+	baseline := android.PathForModuleOut(ctx, "lint", "lint-baseline.xml")
 
 	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml)
 
 	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
-		if depLint, ok := dep.(lintDepSetsIntf); ok {
+		if depLint, ok := dep.(LintDepSetsIntf); ok {
 			depSetsBuilder.Transitive(depLint.LintDepSets())
 		}
 	})
@@ -426,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())).
@@ -447,6 +448,8 @@
 		cmd.FlagWithInput("--baseline ", lintBaseline.Path())
 	}
 
+	cmd.FlagWithOutput("--write-reference-baseline ", baseline)
+
 	cmd.Text("|| (").Text("if [ -e").Input(text).Text("]; then cat").Input(text).Text("; fi; exit 7)")
 
 	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
@@ -657,10 +660,10 @@
 // Enforce the strict updatability linting to all applicable transitive dependencies.
 func enforceStrictUpdatabilityLintingMutator(ctx android.TopDownMutatorContext) {
 	m := ctx.Module()
-	if d, ok := m.(lintDepSetsIntf); ok && d.getStrictUpdatabilityLinting() {
+	if d, ok := m.(LintDepSetsIntf); ok && d.GetStrictUpdatabilityLinting() {
 		ctx.VisitDirectDepsWithTag(staticLibTag, func(d android.Module) {
-			if a, ok := d.(lintDepSetsIntf); ok {
-				a.setStrictUpdatabilityLinting(true)
+			if a, ok := d.(LintDepSetsIntf); ok {
+				a.SetStrictUpdatabilityLinting(true)
 			}
 		})
 	}
diff --git a/java/lint_defaults.txt b/java/lint_defaults.txt
index 2de05b0..1eee354 100644
--- a/java/lint_defaults.txt
+++ b/java/lint_defaults.txt
@@ -1,10 +1,42 @@
 # Treat LintError as fatal to catch invocation errors
 --fatal_check LintError
 
+# Checks which do not apply to the platform (implementation
+# in lint assumes that it's running on app code)
+
+--disable_check AnimatorKeep
+--disable_check AppBundleLocaleChanges
+--disable_check BlockedPrivateApi
+--disable_check CustomSplashScreen
+--disable_check CustomX509TrustManager
+--disable_check Deprecated
+--disable_check ExifInterface
+--disable_check HardwareIds
+--disable_check InvalidWakeLockTag
+--disable_check LibraryCustomView
+--disable_check MissingPermission
+--disable_check NonConstantResourceId
+--disable_check OldTargetApi
+--disable_check Override
+--disable_check PackageManagerGetSignatures
+--disable_check PrivateApi
+--disable_check ProtectedPermissions
+--disable_check QueryPermissionsNeeded
+--disable_check ScopedStorage
+--disable_check ServiceCast
+--disable_check SoonBlockedPrivateApi
+--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
 --warning_check AppLinkUrlError                    # 111 occurences in 53 modules
---warning_check BlockedPrivateApi                  # 2 occurences in 2 modules
 --warning_check ByteOrderMark                      # 2 occurences in 2 modules
 --warning_check DuplicateActivity                  # 3 occurences in 3 modules
 --warning_check DuplicateDefinition                # 3623 occurences in 48 modules
@@ -22,9 +54,7 @@
 --warning_check Instantiatable                     # 145 occurences in 19 modules
 --warning_check InvalidPermission                  # 6 occurences in 4 modules
 --warning_check InvalidUsesTagAttribute            # 6 occurences in 2 modules
---warning_check InvalidWakeLockTag                 # 111 occurences in 37 modules
 --warning_check JavascriptInterface                # 3 occurences in 2 modules
---warning_check LibraryCustomView                  # 9 occurences in 4 modules
 --warning_check LogTagMismatch                     # 81 occurences in 13 modules
 --warning_check LongLogTag                         # 249 occurences in 12 modules
 --warning_check MenuTitle                          # 5 occurences in 4 modules
@@ -35,21 +65,17 @@
 --warning_check MissingLeanbackLauncher            # 3 occurences in 3 modules
 --warning_check MissingLeanbackSupport             # 2 occurences in 2 modules
 --warning_check MissingOnPlayFromSearch            # 1 occurences in 1 modules
---warning_check MissingPermission                  # 2071 occurences in 150 modules
 --warning_check MissingPrefix                      # 46 occurences in 41 modules
 --warning_check MissingQuantity                    # 100 occurences in 1 modules
 --warning_check MissingSuperCall                   # 121 occurences in 36 modules
 --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
 --warning_check Orientation                        # 77 occurences in 19 modules
---warning_check Override                           # 385 occurences in 36 modules
 --warning_check ParcelCreator                      # 23 occurences in 2 modules
---warning_check ProtectedPermissions               # 2413 occurences in 381 modules
 --warning_check Range                              # 80 occurences in 28 modules
 --warning_check RecyclerView                       # 1 occurences in 1 modules
 --warning_check ReferenceType                      # 4 occurences in 1 modules
@@ -60,8 +86,6 @@
 --warning_check ResourceType                       # 137 occurences in 36 modules
 --warning_check RestrictedApi                      # 28 occurences in 5 modules
 --warning_check RtlCompat                          # 9 occurences in 6 modules
---warning_check ServiceCast                        # 3 occurences in 1 modules
---warning_check SoonBlockedPrivateApi              # 5 occurences in 3 modules
 --warning_check StringFormatInvalid                # 148 occurences in 11 modules
 --warning_check StringFormatMatches                # 4800 occurences in 30 modules
 --warning_check UnknownId                          # 8 occurences in 7 modules
@@ -74,7 +98,9 @@
 --warning_check WrongThread                        # 14 occurences in 6 modules
 --warning_check WrongViewCast                      # 1 occurences in 1 modules
 
-# TODO(b/158390965): remove this when lint doesn't crash
---disable_check HardcodedDebugMode
-
+--warning_check CoarseFineLocation
+--warning_check IntentFilterExportedReceiver
 --warning_check QueryAllPackagesPermission
+--warning_check RemoteViewLayout
+--warning_check SupportAnnotationUsage
+--warning_check UniqueConstants
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 10739b0..1e27238 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -32,9 +32,9 @@
 // The tags used for the dependencies between the platform bootclasspath and any configured boot
 // jars.
 var (
-	platformBootclasspathArtBootJarDepTag          = bootclasspathDependencyTag{name: "art-boot-jar"}
-	platformBootclasspathNonUpdatableBootJarDepTag = bootclasspathDependencyTag{name: "non-updatable-boot-jar"}
-	platformBootclasspathUpdatableBootJarDepTag    = bootclasspathDependencyTag{name: "updatable-boot-jar"}
+	platformBootclasspathArtBootJarDepTag  = bootclasspathDependencyTag{name: "art-boot-jar"}
+	platformBootclasspathBootJarDepTag     = bootclasspathDependencyTag{name: "platform-boot-jar"}
+	platformBootclasspathApexBootJarDepTag = bootclasspathDependencyTag{name: "apex-boot-jar"}
 )
 
 type platformBootclasspathModule struct {
@@ -68,7 +68,6 @@
 func platformBootclasspathFactory() android.SingletonModule {
 	m := &platformBootclasspathModule{}
 	m.AddProperties(&m.properties)
-	// TODO(satayev): split apex jars into separate configs.
 	initClasspathFragment(m, BOOTCLASSPATH)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
@@ -131,11 +130,11 @@
 	// Add dependencies on all the non-updatable module configured in the "boot" boot image. That does
 	// not include modules configured in the "art" boot image.
 	bootImageConfig := b.getImageConfig(ctx)
-	addDependenciesOntoBootImageModules(ctx, bootImageConfig.modules, platformBootclasspathNonUpdatableBootJarDepTag)
+	addDependenciesOntoBootImageModules(ctx, bootImageConfig.modules, platformBootclasspathBootJarDepTag)
 
-	// Add dependencies on all the updatable modules.
-	updatableModules := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars
-	addDependenciesOntoBootImageModules(ctx, updatableModules, platformBootclasspathUpdatableBootJarDepTag)
+	// Add dependencies on all the apex jars.
+	apexJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars
+	addDependenciesOntoBootImageModules(ctx, apexJars, platformBootclasspathApexBootJarDepTag)
 
 	// Add dependencies on all the fragments.
 	b.properties.BootclasspathFragmentsDepsProperties.addDependenciesOntoFragments(ctx)
@@ -163,16 +162,16 @@
 }
 
 func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Gather all the dependencies from the art, updatable and non-updatable boot jars.
+	// Gather all the dependencies from the art, platform, and apex boot jars.
 	artModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathArtBootJarDepTag)
-	nonUpdatableModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathNonUpdatableBootJarDepTag)
-	updatableModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathUpdatableBootJarDepTag)
+	platformModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathBootJarDepTag)
+	apexModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathApexBootJarDepTag)
 
 	// Concatenate them all, in order as they would appear on the bootclasspath.
 	var allModules []android.Module
 	allModules = append(allModules, artModules...)
-	allModules = append(allModules, nonUpdatableModules...)
-	allModules = append(allModules, updatableModules...)
+	allModules = append(allModules, platformModules...)
+	allModules = append(allModules, apexModules...)
 	b.configuredModules = allModules
 
 	// Gather all the fragments dependencies.
@@ -180,8 +179,8 @@
 
 	// Check the configuration of the boot modules.
 	// ART modules are checked by the art-bootclasspath-fragment.
-	b.checkNonUpdatableModules(ctx, nonUpdatableModules)
-	b.checkUpdatableModules(ctx, updatableModules)
+	b.checkPlatformModules(ctx, platformModules)
+	b.checkApexModules(ctx, apexModules)
 
 	b.generateClasspathProtoBuildActions(ctx)
 
@@ -193,23 +192,40 @@
 		return
 	}
 
-	b.generateBootImageBuildActions(ctx, nonUpdatableModules, updatableModules)
+	b.generateBootImageBuildActions(ctx, platformModules, apexModules)
 }
 
 // Generate classpaths.proto config
 func (b *platformBootclasspathModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
+	configuredJars := b.configuredJars(ctx)
 	// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
-	classpathJars := configuredJarListToClasspathJars(ctx, b.ClasspathFragmentToConfiguredJarList(ctx), BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
-	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
+	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
-func (b *platformBootclasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
-	return b.getImageConfig(ctx).modules
+func (b *platformBootclasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	// Include all non APEX jars
+	jars := b.getImageConfig(ctx).modules
+
+	// Include jars from APEXes that don't populate their classpath proto config.
+	remainingJars := dexpreopt.GetGlobalConfig(ctx).ApexBootJars
+	for _, fragment := range b.fragments {
+		info := ctx.OtherModuleProvider(fragment, ClasspathFragmentProtoContentInfoProvider).(ClasspathFragmentProtoContentInfo)
+		if info.ClasspathFragmentProtoGenerated {
+			remainingJars = remainingJars.RemoveList(info.ClasspathFragmentProtoContents)
+		}
+	}
+	for i := 0; i < remainingJars.Len(); i++ {
+		jars = jars.Append(remainingJars.Apex(i), remainingJars.Jar(i))
+	}
+
+	return jars
 }
 
-// checkNonUpdatableModules ensures that the non-updatable modules supplied are not part of an
-// updatable module.
-func (b *platformBootclasspathModule) checkNonUpdatableModules(ctx android.ModuleContext, modules []android.Module) {
+// checkPlatformModules ensures that the non-updatable modules supplied are not part of an
+// apex module.
+func (b *platformBootclasspathModule) checkPlatformModules(ctx android.ModuleContext, modules []android.Module) {
+	// TODO(satayev): change this check to only allow core-icu4j, all apex jars should not be here.
 	for _, m := range modules {
 		apexInfo := ctx.OtherModuleProvider(m, android.ApexInfoProvider).(android.ApexInfo)
 		fromUpdatableApex := apexInfo.Updatable
@@ -222,8 +238,8 @@
 	}
 }
 
-// checkUpdatableModules ensures that the updatable modules supplied are not from the platform.
-func (b *platformBootclasspathModule) checkUpdatableModules(ctx android.ModuleContext, modules []android.Module) {
+// checkApexModules ensures that the apex modules supplied are not from the platform.
+func (b *platformBootclasspathModule) checkApexModules(ctx android.ModuleContext, modules []android.Module) {
 	for _, m := range modules {
 		apexInfo := ctx.OtherModuleProvider(m, android.ApexInfoProvider).(android.ApexInfo)
 		fromUpdatableApex := apexInfo.Updatable
@@ -239,12 +255,12 @@
 				//  modules is complete.
 				if !ctx.Config().AlwaysUsePrebuiltSdks() {
 					// error: this jar is part of the platform
-					ctx.ModuleErrorf("module %q from platform is not allowed in the updatable boot jars list", name)
+					ctx.ModuleErrorf("module %q from platform is not allowed in the apex boot jars list", name)
 				}
 			} else {
 				// TODO(b/177892522): Treat this as an error.
 				// Cannot do that at the moment because framework-wifi and framework-tethering are in the
-				// PRODUCT_UPDATABLE_BOOT_JARS but not marked as updatable in AOSP.
+				// PRODUCT_APEX_BOOT_JARS but not marked as updatable in AOSP.
 			}
 		}
 	}
@@ -299,7 +315,7 @@
 
 	// Generate the monolithic stub-flags.csv file.
 	stubFlags := hiddenAPISingletonPaths(ctx).stubFlags
-	buildRuleToGenerateHiddenAPIStubFlagsFile(ctx, "platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags", stubFlags, bootDexJarByModule.bootDexJars(), input, monolithicInfo.StubFlagsPaths)
+	buildRuleToGenerateHiddenAPIStubFlagsFile(ctx, "platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags", stubFlags, bootDexJarByModule.bootDexJars(), input, monolithicInfo.StubFlagSubsets)
 
 	// Generate the annotation-flags.csv file from all the module annotations.
 	annotationFlags := android.PathForModuleOut(ctx, "hiddenapi-monolithic", "annotation-flags-from-classes.csv")
@@ -312,7 +328,7 @@
 	allAnnotationFlagFiles := android.Paths{annotationFlags}
 	allAnnotationFlagFiles = append(allAnnotationFlagFiles, monolithicInfo.AnnotationFlagsPaths...)
 	allFlags := hiddenAPISingletonPaths(ctx).flags
-	buildRuleToGenerateHiddenApiFlags(ctx, "hiddenAPIFlagsFile", "monolithic hidden API flags", allFlags, stubFlags, allAnnotationFlagFiles, monolithicInfo.FlagsFilesByCategory, monolithicInfo.AllFlagsPaths, android.OptionalPath{})
+	buildRuleToGenerateHiddenApiFlags(ctx, "hiddenAPIFlagsFile", "monolithic hidden API flags", allFlags, stubFlags, allAnnotationFlagFiles, monolithicInfo.FlagsFilesByCategory, monolithicInfo.FlagSubsets, android.OptionalPath{})
 
 	// Generate an intermediate monolithic hiddenapi-metadata.csv file directly from the annotations
 	// in the source code.
@@ -389,7 +405,7 @@
 }
 
 // generateBootImageBuildActions generates ninja rules related to the boot image creation.
-func (b *platformBootclasspathModule) generateBootImageBuildActions(ctx android.ModuleContext, nonUpdatableModules, updatableModules []android.Module) {
+func (b *platformBootclasspathModule) generateBootImageBuildActions(ctx android.ModuleContext, platformModules, apexModules []android.Module) {
 	// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
 	// GenerateSingletonBuildActions method as it cannot create it for itself.
 	dexpreopt.GetGlobalSoongConfig(ctx)
@@ -407,17 +423,14 @@
 	// Generate the framework profile rule
 	bootFrameworkProfileRule(ctx, imageConfig)
 
-	// Generate the updatable bootclasspath packages rule.
-	generateUpdatableBcpPackagesRule(ctx, imageConfig, updatableModules)
+	// Copy platform module dex jars to their predefined locations.
+	platformBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, platformModules)
+	copyBootJarsToPredefinedLocations(ctx, platformBootDexJarsByModule, imageConfig.dexPathsByModule)
 
-	// Copy non-updatable module dex jars to their predefined locations.
-	nonUpdatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, nonUpdatableModules)
-	copyBootJarsToPredefinedLocations(ctx, nonUpdatableBootDexJarsByModule, imageConfig.dexPathsByModule)
-
-	// Copy updatable module dex jars to their predefined locations.
-	config := GetUpdatableBootConfig(ctx)
-	updatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, updatableModules)
-	copyBootJarsToPredefinedLocations(ctx, updatableBootDexJarsByModule, config.dexPathsByModule)
+	// Copy apex module dex jars to their predefined locations.
+	config := GetApexBootConfig(ctx)
+	apexBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, apexModules)
+	copyBootJarsToPredefinedLocations(ctx, apexBootDexJarsByModule, config.dexPathsByModule)
 
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 712c2a2..f442ddf 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -115,7 +115,7 @@
 		Include:    "$(BUILD_PREBUILT)",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base())
 			},
 		},
@@ -134,8 +134,8 @@
 	android.SdkMemberTypeBase
 }
 
-func (b *compatConfigMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (b *compatConfigMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (b *compatConfigMemberType) IsInstance(module android.Module) bool {
diff --git a/java/plugin.go b/java/plugin.go
index 297ac2c..123dbd4 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -14,7 +14,10 @@
 
 package java
 
-import "android/soong/android"
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+)
 
 func init() {
 	registerJavaPluginBuildComponents(android.InitRegistrationContext)
@@ -24,7 +27,6 @@
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 }
 
-// A java_plugin module describes a host java library that will be used by javac as an annotation processor.
 func PluginFactory() android.Module {
 	module := &Plugin{}
 
@@ -32,9 +34,13 @@
 	module.AddProperties(&module.pluginProperties)
 
 	InitJavaModule(module, android.HostSupported)
+
+	android.InitBazelModule(module)
+
 	return module
 }
 
+// Plugin describes a java_plugin module, a host java library that will be used by javac as an annotation processor.
 type Plugin struct {
 	Library
 
@@ -50,3 +56,34 @@
 	// parallelism and cause more recompilation for modules that depend on modules that use this plugin.
 	Generates_api *bool
 }
+
+type pluginAttributes struct {
+	*javaCommonAttributes
+	Deps            bazel.LabelListAttribute
+	Processor_class *string
+}
+
+// ConvertWithBp2build is used to convert android_app to Bazel.
+func (p *Plugin) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	pluginName := p.Name()
+	commonAttrs, depLabels := p.convertLibraryAttrsBp2Build(ctx)
+
+	deps := depLabels.Deps
+	deps.Append(depLabels.StaticDeps)
+
+	var processorClass *string
+	if p.pluginProperties.Processor_class != nil {
+		processorClass = p.pluginProperties.Processor_class
+	}
+
+	attrs := &pluginAttributes{
+		javaCommonAttributes: commonAttrs,
+		Deps:                 deps,
+		Processor_class:      processorClass,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "java_plugin",
+	}
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: pluginName}, attrs)
+}
diff --git a/java/plugin_test.go b/java/plugin_test.go
index c7913d3..dc29b1c 100644
--- a/java/plugin_test.go
+++ b/java/plugin_test.go
@@ -15,7 +15,6 @@
 package java
 
 import (
-	"android/soong/android"
 	"testing"
 )
 
@@ -58,7 +57,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 
 	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine")
@@ -98,7 +97,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 
 	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine")
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index c33e6c2..44650a6 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path"
 	"strconv"
 	"strings"
 
@@ -37,6 +38,11 @@
 	// list of api version directories
 	Api_dirs []string
 
+	// Directory containing finalized api txt files for extension versions.
+	// Extension versions higher than the base sdk extension version will
+	// be assumed to be finalized later than all Api_dirs.
+	Extensions_dir *string
+
 	// The next API directory can optionally point to a directory where
 	// files incompatibility-tracking files are stored for the current
 	// "in progress" API. Each module present in one of the api_dirs will have
@@ -60,36 +66,45 @@
 	// no need to implement
 }
 
-func parseJarPath(path string) (module string, apiver string, scope string) {
-	elements := strings.Split(path, "/")
+// parsePrebuiltPath parses the relevant variables out of a variety of paths, e.g.
+// <version>/<scope>/<module>.jar
+// <version>/<scope>/api/<module>.txt
+// extensions/<version>/<scope>/<module>.jar
+// extensions/<version>/<scope>/api/<module>.txt
+func parsePrebuiltPath(ctx android.LoadHookContext, p string) (module string, version string, scope string) {
+	elements := strings.Split(p, "/")
 
-	apiver = elements[0]
-	scope = elements[1]
-
-	module = strings.TrimSuffix(elements[2], ".jar")
-	return
-}
-
-func parseApiFilePath(ctx android.LoadHookContext, path string) (module string, apiver string, scope string) {
-	elements := strings.Split(path, "/")
-	apiver = elements[0]
-
-	scope = elements[1]
-	if scope != "public" && scope != "system" && scope != "test" && scope != "module-lib" && scope != "system-server" {
-		ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, path)
+	scopeIdx := len(elements) - 2
+	if elements[scopeIdx] == "api" {
+		scopeIdx--
+	}
+	scope = elements[scopeIdx]
+	if scope != "core" && scope != "public" && scope != "system" && scope != "test" && scope != "module-lib" && scope != "system-server" {
+		ctx.ModuleErrorf("invalid scope %q found in path: %q", scope, p)
 		return
 	}
+	version = elements[scopeIdx-1]
 
-	// elements[2] is string literal "api". skipping.
-	module = strings.TrimSuffix(elements[3], ".txt")
+	module = strings.TrimSuffix(path.Base(p), path.Ext(p))
 	return
 }
 
-func prebuiltApiModuleName(mctx android.LoadHookContext, module string, scope string, apiver string) string {
-	return mctx.ModuleName() + "_" + scope + "_" + apiver + "_" + module
+// parseFinalizedPrebuiltPath is like parsePrebuiltPath, but verifies the version is numeric (a finalized version).
+func parseFinalizedPrebuiltPath(ctx android.LoadHookContext, p string) (module string, version int, scope string) {
+	module, v, scope := parsePrebuiltPath(ctx, p)
+	version, err := strconv.Atoi(v)
+	if err != nil {
+		ctx.ModuleErrorf("Found finalized API files in non-numeric dir '%v'", v)
+		return
+	}
+	return
 }
 
-func createImport(mctx android.LoadHookContext, module, scope, apiver, path, sdkVersion string, compileDex bool) {
+func prebuiltApiModuleName(mctx android.LoadHookContext, module, scope, version string) string {
+	return fmt.Sprintf("%s_%s_%s_%s", mctx.ModuleName(), scope, version, module)
+}
+
+func createImport(mctx android.LoadHookContext, module, scope, version, path, sdkVersion string, compileDex bool) {
 	props := struct {
 		Name        *string
 		Jars        []string
@@ -97,7 +112,7 @@
 		Installable *bool
 		Compile_dex *bool
 	}{}
-	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, module, scope, apiver))
+	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, module, scope, version))
 	props.Jars = append(props.Jars, path)
 	props.Sdk_version = proptools.StringPtr(sdkVersion)
 	props.Installable = proptools.BoolPtr(false)
@@ -132,117 +147,125 @@
 	mctx.CreateModule(genrule.GenRuleFactory, &props)
 }
 
-func getPrebuiltFiles(mctx android.LoadHookContext, p *prebuiltApis, name string) []string {
+// globApiDirs collects all the files in all api_dirs and all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
+// <api-dir>/<scope>/<glob> for all api-dir and scope.
+func globApiDirs(mctx android.LoadHookContext, p *prebuiltApis, api_dir_glob string) []string {
 	var files []string
 	for _, apiver := range p.properties.Api_dirs {
-		files = append(files, getPrebuiltFilesInSubdir(mctx, apiver, name)...)
+		files = append(files, globScopeDir(mctx, apiver, api_dir_glob)...)
 	}
 	return files
 }
 
-func getPrebuiltFilesInSubdir(mctx android.LoadHookContext, subdir string, name string) []string {
+// globExtensionDirs collects all the files under the extension dir (for all versions and scopes) that match the given glob
+// <extension-dir>/<version>/<scope>/<glob> for all version and scope.
+func globExtensionDirs(mctx android.LoadHookContext, p *prebuiltApis, extension_dir_glob string) []string {
+	// <extensions-dir>/<num>/<extension-dir-glob>
+	return globScopeDir(mctx, *p.properties.Extensions_dir+"/*", extension_dir_glob)
+}
+
+// globScopeDir collects all the files in the given subdir across all scopes that match the given glob, e.g. '*.jar' or 'api/*.txt'.
+// <subdir>/<scope>/<glob> for all scope.
+func globScopeDir(mctx android.LoadHookContext, subdir string, subdir_glob string) []string {
 	var files []string
 	dir := mctx.ModuleDir() + "/" + subdir
 	for _, scope := range []string{"public", "system", "test", "core", "module-lib", "system-server"} {
-		glob := fmt.Sprintf("%s/%s/%s", dir, scope, name)
+		glob := fmt.Sprintf("%s/%s/%s", dir, scope, subdir_glob)
 		vfiles, err := mctx.GlobWithDeps(glob, nil)
 		if err != nil {
-			mctx.ModuleErrorf("failed to glob %s files under %q: %s", name, dir+"/"+scope, err)
+			mctx.ModuleErrorf("failed to glob %s files under %q: %s", subdir_glob, dir+"/"+scope, err)
 		}
 		files = append(files, vfiles...)
 	}
+	for i, f := range files {
+		files[i] = strings.TrimPrefix(f, mctx.ModuleDir()+"/")
+	}
 	return files
 }
 
 func prebuiltSdkStubs(mctx android.LoadHookContext, p *prebuiltApis) {
-	mydir := mctx.ModuleDir() + "/"
 	// <apiver>/<scope>/<module>.jar
-	files := getPrebuiltFiles(mctx, p, "*.jar")
+	files := globApiDirs(mctx, p, "*.jar")
 
 	sdkVersion := proptools.StringDefault(p.properties.Imports_sdk_version, "current")
 	compileDex := proptools.BoolDefault(p.properties.Imports_compile_dex, false)
 
 	for _, f := range files {
 		// create a Import module for each jar file
-		localPath := strings.TrimPrefix(f, mydir)
-		module, apiver, scope := parseJarPath(localPath)
-		createImport(mctx, module, scope, apiver, localPath, sdkVersion, compileDex)
+		module, version, scope := parsePrebuiltPath(mctx, f)
+		createImport(mctx, module, scope, version, f, sdkVersion, compileDex)
+
+		if module == "core-for-system-modules" {
+			createSystemModules(mctx, version, scope)
+		}
 	}
 }
 
-func createSystemModules(mctx android.LoadHookContext, apiver string) {
+func createSystemModules(mctx android.LoadHookContext, version, scope string) {
 	props := struct {
 		Name *string
 		Libs []string
 	}{}
-	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", "public", apiver))
-	props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", "public", apiver))
+	props.Name = proptools.StringPtr(prebuiltApiModuleName(mctx, "system_modules", scope, version))
+	props.Libs = append(props.Libs, prebuiltApiModuleName(mctx, "core-for-system-modules", scope, version))
 
 	mctx.CreateModule(systemModulesImportFactory, &props)
 }
 
-func prebuiltSdkSystemModules(mctx android.LoadHookContext, p *prebuiltApis) {
-	for _, apiver := range p.properties.Api_dirs {
-		jar := android.ExistentPathForSource(mctx,
-			mctx.ModuleDir(), apiver, "public", "core-for-system-modules.jar")
-		if jar.Valid() {
-			createSystemModules(mctx, apiver)
-		}
-	}
-}
-
 func prebuiltApiFiles(mctx android.LoadHookContext, p *prebuiltApis) {
-	mydir := mctx.ModuleDir() + "/"
 	// <apiver>/<scope>/api/<module>.txt
-	files := getPrebuiltFiles(mctx, p, "api/*.txt")
-
-	if len(files) == 0 {
-		mctx.ModuleErrorf("no api file found under %q", mydir)
-	}
-
-	// construct a map to find out the latest api file path
-	// for each (<module>, <scope>) pair.
-	type latestApiInfo struct {
-		module  string
-		scope   string
-		version int
-		path    string
+	apiLevelFiles := globApiDirs(mctx, p, "api/*.txt")
+	if len(apiLevelFiles) == 0 {
+		mctx.ModuleErrorf("no api file found under %q", mctx.ModuleDir())
 	}
 
 	// Create modules for all (<module>, <scope, <version>) triplets,
-	// and a "latest" module variant for each (<module>, <scope>) pair
 	apiModuleName := func(module, scope, version string) string {
 		return module + ".api." + scope + "." + version
 	}
-	m := make(map[string]latestApiInfo)
-	for _, f := range files {
-		localPath := strings.TrimPrefix(f, mydir)
-		module, apiver, scope := parseApiFilePath(mctx, localPath)
-		createApiModule(mctx, apiModuleName(module, scope, apiver), localPath)
+	for _, f := range apiLevelFiles {
+		module, version, scope := parseFinalizedPrebuiltPath(mctx, f)
+		createApiModule(mctx, apiModuleName(module, scope, strconv.Itoa(version)), f)
+	}
 
-		version, err := strconv.Atoi(apiver)
-		if err != nil {
-			mctx.ModuleErrorf("Found finalized API files in non-numeric dir %v", apiver)
-			return
-		}
+	// Figure out the latest version of each module/scope
+	type latestApiInfo struct {
+		module, scope, path string
+		version             int
+	}
 
-		// Track latest version of each module/scope, except for incompatibilities
-		if !strings.HasSuffix(module, "incompatibilities") {
+	getLatest := func(files []string) map[string]latestApiInfo {
+		m := make(map[string]latestApiInfo)
+		for _, f := range files {
+			module, version, scope := parseFinalizedPrebuiltPath(mctx, f)
+			if strings.HasSuffix(module, "incompatibilities") {
+				continue
+			}
 			key := module + "." + scope
-			info, ok := m[key]
-			if !ok {
-				m[key] = latestApiInfo{module, scope, version, localPath}
-			} else if version > info.version {
-				info.version = version
-				info.path = localPath
-				m[key] = info
+			info, exists := m[key]
+			if !exists || version > info.version {
+				m[key] = latestApiInfo{module, scope, f, version}
+			}
+		}
+		return m
+	}
+
+	latest := getLatest(apiLevelFiles)
+	if p.properties.Extensions_dir != nil {
+		extensionApiFiles := globExtensionDirs(mctx, p, "api/*.txt")
+		for k, v := range getLatest(extensionApiFiles) {
+			if v.version > mctx.Config().PlatformBaseSdkExtensionVersion() {
+				if _, exists := latest[k]; !exists {
+					mctx.ModuleErrorf("Module %v finalized for extension %d but never during an API level; likely error", v.module, v.version)
+				}
+				latest[k] = v
 			}
 		}
 	}
 
 	// Sort the keys in order to make build.ninja stable
-	for _, k := range android.SortedStringKeys(m) {
-		info := m[k]
+	for _, k := range android.SortedStringKeys(latest) {
+		info := latest[k]
 		name := apiModuleName(info.module, info.scope, "latest")
 		createApiModule(mctx, name, info.path)
 	}
@@ -250,21 +273,20 @@
 	// Create incompatibilities tracking files for all modules, if we have a "next" api.
 	incompatibilities := make(map[string]bool)
 	if nextApiDir := String(p.properties.Next_api_dir); nextApiDir != "" {
-		files := getPrebuiltFilesInSubdir(mctx, nextApiDir, "api/*incompatibilities.txt")
+		files := globScopeDir(mctx, nextApiDir, "api/*incompatibilities.txt")
 		for _, f := range files {
-			localPath := strings.TrimPrefix(f, mydir)
-			filename, _, scope := parseApiFilePath(mctx, localPath)
+			filename, _, scope := parsePrebuiltPath(mctx, f)
 			referencedModule := strings.TrimSuffix(filename, "-incompatibilities")
 
-			createApiModule(mctx, apiModuleName(referencedModule+"-incompatibilities", scope, "latest"), localPath)
+			createApiModule(mctx, apiModuleName(referencedModule+"-incompatibilities", scope, "latest"), f)
 
 			incompatibilities[referencedModule+"."+scope] = true
 		}
 	}
 	// Create empty incompatibilities files for remaining modules
-	for _, k := range android.SortedStringKeys(m) {
+	for _, k := range android.SortedStringKeys(latest) {
 		if _, ok := incompatibilities[k]; !ok {
-			createEmptyFile(mctx, apiModuleName(m[k].module+"-incompatibilities", m[k].scope, "latest"))
+			createEmptyFile(mctx, apiModuleName(latest[k].module+"-incompatibilities", latest[k].scope, "latest"))
 		}
 	}
 }
@@ -273,7 +295,6 @@
 	if p, ok := mctx.Module().(*prebuiltApis); ok {
 		prebuiltApiFiles(mctx, p)
 		prebuiltSdkStubs(mctx, p)
-		prebuiltSdkSystemModules(mctx, p)
 	}
 }
 
diff --git a/java/prebuilt_apis_test.go b/java/prebuilt_apis_test.go
new file mode 100644
index 0000000..75422ad
--- /dev/null
+++ b/java/prebuilt_apis_test.go
@@ -0,0 +1,92 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"sort"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+func intPtr(v int) *int {
+	return &v
+}
+
+func TestPrebuiltApis_SystemModulesCreation(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"31":      {},
+			"32":      {},
+			"current": {},
+		}),
+	).RunTest(t)
+
+	sdkSystemModules := []string{}
+	result.VisitAllModules(func(module blueprint.Module) {
+		name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+		if strings.HasPrefix(name, "sdk_") && strings.HasSuffix(name, "_system_modules") {
+			sdkSystemModules = append(sdkSystemModules, name)
+		}
+	})
+	sort.Strings(sdkSystemModules)
+	expected := []string{
+		// 31 only has public system modules.
+		"sdk_public_31_system_modules",
+
+		// 32 and current both have public and module-lib system modules.
+		"sdk_public_32_system_modules",
+		"sdk_module-lib_32_system_modules",
+		"sdk_public_current_system_modules",
+		"sdk_module-lib_current_system_modules",
+	}
+	sort.Strings(expected)
+	android.AssertArrayString(t, "sdk system modules", expected, sdkSystemModules)
+}
+
+func TestPrebuiltApis_WithExtensions(t *testing.T) {
+	runTestWithBaseExtensionLevel := func(v int) (foo_input string, bar_input string) {
+		result := android.GroupFixturePreparers(
+			prepareForJavaTest,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.Platform_base_sdk_extension_version = intPtr(v)
+			}),
+			FixtureWithPrebuiltApisAndExtensions(map[string][]string{
+				"31":      {"foo"},
+				"32":      {"foo", "bar"},
+				"current": {"foo", "bar"},
+			}, map[string][]string{
+				"1": {"foo"},
+				"2": {"foo", "bar"},
+			}),
+		).RunTest(t)
+		foo_input = result.ModuleForTests("foo.api.public.latest", "").Rule("generator").Implicits[0].String()
+		bar_input = result.ModuleForTests("bar.api.public.latest", "").Rule("generator").Implicits[0].String()
+		return
+	}
+	// Here, the base extension level is 1, so extension level 2 is the latest
+	foo_input, bar_input := runTestWithBaseExtensionLevel(1)
+	android.AssertStringEquals(t, "Expected latest = extension level 2", "prebuilts/sdk/extensions/2/public/api/foo.txt", foo_input)
+	android.AssertStringEquals(t, "Expected latest = extension level 2", "prebuilts/sdk/extensions/2/public/api/bar.txt", bar_input)
+
+	// Here, the base extension level is 2, so 2 is not later than 32.
+	foo_input, bar_input = runTestWithBaseExtensionLevel(2)
+	android.AssertStringEquals(t, "Expected latest = api level 32", "prebuilts/sdk/32/public/api/foo.txt", foo_input)
+	android.AssertStringEquals(t, "Expected latest = api level 32", "prebuilts/sdk/32/public/api/bar.txt", bar_input)
+}
diff --git a/java/proto.go b/java/proto.go
index 8731822..5ba486f 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -19,12 +19,19 @@
 	"strconv"
 
 	"android/soong/android"
+	"android/soong/bazel"
+
+	"github.com/google/blueprint/proptools"
+)
+
+const (
+	protoTypeDefault = "lite"
 )
 
 func genProto(ctx android.ModuleContext, protoFiles android.Paths, flags android.ProtoFlags) android.Paths {
 	// Shard proto files into groups of 100 to avoid having to recompile all of them if one changes and to avoid
 	// hitting command line length limits.
-	shards := android.ShardPaths(protoFiles, 100)
+	shards := android.ShardPaths(protoFiles, 50)
 
 	srcJarFiles := make(android.Paths, 0, len(shards))
 
@@ -73,13 +80,15 @@
 }
 
 func protoDeps(ctx android.BottomUpMutatorContext, p *android.ProtoProperties) {
+	const unspecifiedProtobufPluginType = ""
 	if String(p.Proto.Plugin) == "" {
 		switch String(p.Proto.Type) {
+		case "stream": // does not require additional dependencies
 		case "micro":
 			ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-micro")
 		case "nano":
 			ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-nano")
-		case "lite", "":
+		case "lite", unspecifiedProtobufPluginType:
 			ctx.AddVariationDependencies(nil, staticLibTag, "libprotobuf-java-lite")
 		case "full":
 			if ctx.Host() || ctx.BazelConversionMode() {
@@ -102,6 +111,9 @@
 	if String(p.Proto.Plugin) == "" {
 		var typeToPlugin string
 		switch String(p.Proto.Type) {
+		case "stream":
+			flags.proto.OutTypeFlag = "--javastream_out"
+			typeToPlugin = "javastream"
 		case "micro":
 			flags.proto.OutTypeFlag = "--javamicro_out"
 			typeToPlugin = "javamicro"
@@ -129,3 +141,52 @@
 
 	return flags
 }
+
+type protoAttributes struct {
+	Deps bazel.LabelListAttribute
+}
+
+func bp2buildProto(ctx android.Bp2buildMutatorContext, m *Module, protoSrcs bazel.LabelListAttribute) *bazel.Label {
+	protoInfo, ok := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, protoSrcs)
+	if !ok {
+		return nil
+	}
+
+	typ := proptools.StringDefault(protoInfo.Type, protoTypeDefault)
+	var rule_class string
+	suffix := "_java_proto"
+	switch typ {
+	case "nano":
+		suffix += "_nano"
+		rule_class = "java_nano_proto_library"
+	case "micro":
+		suffix += "_micro"
+		rule_class = "java_micro_proto_library"
+	case "lite":
+		suffix += "_lite"
+		rule_class = "java_lite_proto_library"
+	case "stream":
+		suffix += "_stream"
+		rule_class = "java_stream_proto_library"
+	case "full":
+		rule_class = "java_proto_library"
+	default:
+		ctx.PropertyErrorf("proto.type", "cannot handle conversion at this time: %q", typ)
+	}
+
+	protoLabel := bazel.Label{Label: ":" + m.Name() + "_proto"}
+	var protoAttrs protoAttributes
+	protoAttrs.Deps.SetValue(bazel.LabelList{Includes: []bazel.Label{protoLabel}})
+
+	name := m.Name() + suffix
+
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{
+			Rule_class:        rule_class,
+			Bzl_load_location: "//build/bazel/rules/java:proto.bzl",
+		},
+		android.CommonAttributes{Name: name},
+		&protoAttrs)
+
+	return &bazel.Label{Label: ":" + name}
+}
diff --git a/java/proto_test.go b/java/proto_test.go
new file mode 100644
index 0000000..d1cb714
--- /dev/null
+++ b/java/proto_test.go
@@ -0,0 +1,53 @@
+// 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 java
+
+import (
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+const protoModules = `
+java_library_static {
+    name: "libprotobuf-java-lite",
+}
+`
+
+func TestProtoStream(t *testing.T) {
+	bp := `
+		java_library {
+			name: "java-stream-protos",
+			proto: {
+				type: "stream",
+			},
+			srcs: [
+				"a.proto",
+				"b.proto",
+			],
+		}
+	`
+
+	ctx := android.GroupFixturePreparers(
+		PrepareForIntegrationTestWithJava,
+	).RunTestWithBp(t, protoModules+bp)
+
+	proto0 := ctx.ModuleForTests("java-stream-protos", "android_common").Output("proto/proto0.srcjar")
+
+	if cmd := proto0.RuleParams.Command; !strings.Contains(cmd, "--javastream_out=") {
+		t.Errorf("expected '--javastream_out' in %q", cmd)
+	}
+}
diff --git a/java/robolectric.go b/java/robolectric.go
index 9fe1f0e..f719521 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -83,6 +83,9 @@
 
 	testConfig android.Path
 	data       android.Paths
+
+	forceOSType   android.OsType
+	forceArchType android.ArchType
 }
 
 func (r *robolectricTest) TestSuites() []string {
@@ -115,6 +118,9 @@
 }
 
 func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	r.forceOSType = ctx.Config().BuildOS
+	r.forceArchType = ctx.Config().BuildArch
+
 	r.testConfig = tradefed.AutoGenRobolectricTestConfig(ctx, r.testProperties.Test_config,
 		r.testProperties.Test_config_template, r.testProperties.Test_suites,
 		r.testProperties.Auto_gen_config)
@@ -179,7 +185,7 @@
 			continue
 		} else if strings.HasSuffix(s, "/BaseRobolectricTest.java") {
 			continue
-		} else if strings.HasPrefix(s, "src/") {
+		} else {
 			s = strings.TrimPrefix(s, "src/")
 		}
 		r.tests = append(r.tests, s)
@@ -206,7 +212,7 @@
 		installDeps = append(installDeps, installedData)
 	}
 
-	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.combinedJar, installDeps...)
+	r.installFile = ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.combinedJar, installDeps...)
 }
 
 func generateRoboTestConfig(ctx android.ModuleContext, outputFile android.WritablePath,
@@ -270,6 +276,10 @@
 func (r *robolectricTest) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := r.Library.AndroidMkEntries()
 	entries := &entriesList[0]
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+		})
 
 	entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
 		func(w io.Writer, name, prefix, moduleDir string) {
@@ -342,10 +352,9 @@
 	return module
 }
 
-func (r *robolectricTest) InstallBypassMake() bool  { return true }
 func (r *robolectricTest) InstallInTestcases() bool { return true }
 func (r *robolectricTest) InstallForceOS() (*android.OsType, *android.ArchType) {
-	return &android.BuildOs, &android.BuildArch
+	return &r.forceOSType, &r.forceArchType
 }
 
 func robolectricRuntimesFactory() android.Module {
@@ -366,6 +375,9 @@
 	props robolectricRuntimesProperties
 
 	runtimes []android.InstallPath
+
+	forceOSType   android.OsType
+	forceArchType android.ArchType
 }
 
 func (r *robolectricRuntimes) TestSuites() []string {
@@ -385,6 +397,9 @@
 		return
 	}
 
+	r.forceOSType = ctx.Config().BuildOS
+	r.forceArchType = ctx.Config().BuildArch
+
 	files := android.PathsForModuleSrc(ctx, r.props.Jars)
 
 	androidAllDir := android.PathForModuleInstall(ctx, "android-all")
@@ -405,15 +420,16 @@
 		}
 		runtimeFromSourceJar := android.OutputFileForModule(ctx, runtimeFromSourceModule, "")
 
-		runtimeName := fmt.Sprintf("android-all-%s-robolectric-r0.jar",
-			    ctx.Config().PlatformSdkCodename())
+		// "TREE" name is essential here because it hooks into the "TREE" name in
+		// Robolectric's SdkConfig.java that will always correspond to the NEWEST_SDK
+		// in Robolectric configs.
+		runtimeName := "android-all-current-robolectric-r0.jar"
 		installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar)
 		r.runtimes = append(r.runtimes, installedRuntime)
 	}
 }
 
-func (r *robolectricRuntimes) InstallBypassMake() bool  { return true }
 func (r *robolectricRuntimes) InstallInTestcases() bool { return true }
 func (r *robolectricRuntimes) InstallForceOS() (*android.OsType, *android.ArchType) {
-	return &android.BuildOs, &android.BuildArch
+	return &r.forceOSType, &r.forceArchType
 }
diff --git a/java/rro.go b/java/rro.go
index 2e58c04..c98cbbd 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -51,6 +51,9 @@
 	// Name of the signing certificate lineage file.
 	Lineage *string
 
+	// For overriding the --rotation-min-sdk-version property of apksig
+	RotationMinSdkVersion *string
+
 	// optional theme name. If specified, the overlay package will be applied
 	// only when the ro.boot.vendor.overlay.theme system property is set to the same value.
 	Theme *string
@@ -90,6 +93,22 @@
 	Theme() string
 }
 
+// RRO's partition logic is different from the partition logic of other modules defined in soong/android/paths.go
+// The default partition for RRO is "/product" and not "/system"
+func rroPartition(ctx android.ModuleContext) string {
+	var partition string
+	if ctx.DeviceSpecific() {
+		partition = ctx.DeviceConfig().OdmPath()
+	} else if ctx.SocSpecific() {
+		partition = ctx.DeviceConfig().VendorPath()
+	} else if ctx.SystemExtSpecific() {
+		partition = ctx.DeviceConfig().SystemExtPath()
+	} else {
+		partition = ctx.DeviceConfig().ProductPath()
+	}
+	return partition
+}
+
 func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
 	sdkDep := decodeSdkDep(ctx, android.SdkContext(r))
 	if sdkDep.hasFrameworkLibs() {
@@ -123,7 +142,7 @@
 		aaptLinkFlags = append(aaptLinkFlags,
 			"--rename-overlay-target-package "+*r.overridableProperties.Target_package_name)
 	}
-	r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...)
+	r.aapt.buildActions(ctx, r, nil, nil, aaptLinkFlags...)
 
 	// Sign the built package
 	_, certificates := collectAppDeps(ctx, r, false, false)
@@ -133,11 +152,15 @@
 	if lineage := String(r.properties.Lineage); lineage != "" {
 		lineageFile = android.PathForModuleSrc(ctx, lineage)
 	}
-	SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates, nil, lineageFile)
+
+	rotationMinSdkVersion := String(r.properties.RotationMinSdkVersion)
+
+	SignAppPackage(ctx, signed, r.aapt.exportPackage, certificates, nil, lineageFile, rotationMinSdkVersion)
 	r.certificate = certificates[0]
 
 	r.outputFile = signed
-	r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme))
+	partition := rroPartition(ctx)
+	r.installDir = android.PathForModuleInPartitionInstall(ctx, partition, "overlay", String(r.properties.Theme))
 	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
 }
 
diff --git a/java/rro_test.go b/java/rro_test.go
index bad60bc..00ba5ba 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -33,6 +33,7 @@
 			name: "foo",
 			certificate: "platform",
 			lineage: "lineage.bin",
+			rotationMinSdkVersion: "32",
 			product_specific: true,
 			static_libs: ["bar"],
 			resource_libs: ["baz"],
@@ -62,6 +63,7 @@
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		PrepareForTestWithOverlayBuildComponents,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
 		fs.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
@@ -88,13 +90,14 @@
 		t.Errorf("Resource lib flag %q missing in aapt2 link flags: %q", resourceLibFlag, aapt2Flags)
 	}
 
-	// Check cert signing flag.
+	// Check cert signing flags.
 	signedApk := m.Output("signed/foo.apk")
-	lineageFlag := signedApk.Args["flags"]
-	expectedLineageFlag := "--lineage lineage.bin"
-	if expectedLineageFlag != lineageFlag {
-		t.Errorf("Incorrect signing lineage flags, expected: %q, got: %q", expectedLineageFlag, lineageFlag)
+	actualCertSigningFlags := signedApk.Args["flags"]
+	expectedCertSigningFlags := "--lineage lineage.bin --rotation-min-sdk-version 32"
+	if expectedCertSigningFlags != actualCertSigningFlags {
+		t.Errorf("Incorrect cert signing flags, expected: %q, got: %q", expectedCertSigningFlags, actualCertSigningFlags)
 	}
+
 	signingFlag := signedApk.Args["certificates"]
 	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
 	if expected != signingFlag {
@@ -127,7 +130,10 @@
 }
 
 func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
-	ctx, config := testJava(t, `
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		java_defaults {
 			name: "rro_defaults",
 			theme: "default_theme",
@@ -148,7 +154,7 @@
 	//
 	// RRO module with defaults
 	//
-	m := ctx.ModuleForTests("foo_with_defaults", "android_common")
+	m := result.ModuleForTests("foo_with_defaults", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -159,14 +165,14 @@
 	}
 
 	// Check device location.
-	path := android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	path := android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath := []string{shared.JoinPath("out/target/product/test_device/product/overlay/default_theme")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	//
 	// RRO module without defaults
 	//
-	m = ctx.ModuleForTests("foo_barebones", "android_common")
+	m = result.ModuleForTests("foo_barebones", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -176,9 +182,9 @@
 	}
 
 	// Check device location.
-	path = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{shared.JoinPath("out/target/product/test_device/system/overlay")}
-	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
+	path = android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
+	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay")}
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 }
 
 func TestOverrideRuntimeResourceOverlay(t *testing.T) {
@@ -343,3 +349,57 @@
 		})
 	}
 }
+
+func TestRuntimeResourceOverlayPartition(t *testing.T) {
+	bp := `
+		runtime_resource_overlay {
+			name: "device_specific",
+			device_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "soc_specific",
+			soc_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "system_ext_specific",
+			system_ext_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "product_specific",
+			product_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "default"
+		}
+	`
+	testCases := []struct {
+		name         string
+		expectedPath string
+	}{
+		{
+			name:         "device_specific",
+			expectedPath: "out/soong/target/product/test_device/odm/overlay",
+		},
+		{
+			name:         "soc_specific",
+			expectedPath: "out/soong/target/product/test_device/vendor/overlay",
+		},
+		{
+			name:         "system_ext_specific",
+			expectedPath: "out/soong/target/product/test_device/system_ext/overlay",
+		},
+		{
+			name:         "product_specific",
+			expectedPath: "out/soong/target/product/test_device/product/overlay",
+		},
+		{
+			name:         "default",
+			expectedPath: "out/soong/target/product/test_device/product/overlay",
+		},
+	}
+	for _, testCase := range testCases {
+		ctx, _ := testJava(t, bp)
+		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*RuntimeResourceOverlay)
+		android.AssertPathRelativeToTopEquals(t, "Install dir is not correct for "+testCase.name, testCase.expectedPath, mod.installDir)
+	}
+}
diff --git a/java/sdk.go b/java/sdk.go
index d1b899e..0dddd40 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -55,11 +55,37 @@
 		return JAVA_VERSION_7
 	} else if sdk.FinalOrFutureInt() <= 29 {
 		return JAVA_VERSION_8
-	} else {
+	} else if sdk.FinalOrFutureInt() <= 31 {
 		return JAVA_VERSION_9
+	} else {
+		return JAVA_VERSION_11
 	}
 }
 
+// systemModuleKind returns the kind of system modules to use for the supplied combination of sdk
+// kind and API level.
+func systemModuleKind(sdkKind android.SdkKind, apiLevel android.ApiLevel) android.SdkKind {
+	systemModuleKind := sdkKind
+	if apiLevel.LessThanOrEqualTo(android.LastWithoutModuleLibCoreSystemModules) {
+		// API levels less than or equal to 31 did not provide a core-for-system-modules.jar
+		// specifically for the module-lib API. So, always use the public system modules for them.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkCore {
+		// Core is by definition what is included in the system module for the public API so should
+		// just use its system modules.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkSystem || systemModuleKind == android.SdkTest {
+		// The core system and test APIs are currently the same as the public API so they should use
+		// its system modules.
+		systemModuleKind = android.SdkPublic
+	} else if systemModuleKind == android.SdkSystemServer {
+		// The core system server API is the same as the core module-lib API.
+		systemModuleKind = android.SdkModule
+	}
+
+	return systemModuleKind
+}
+
 func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext android.SdkContext) sdkDep {
 	sdkVersion := sdkContext.SdkVersion(ctx)
 	if !sdkVersion.Valid() {
@@ -105,7 +131,8 @@
 
 		var systemModules string
 		if defaultJavaLanguageVersion(ctx, sdkVersion).usesJavaModules() {
-			systemModules = "sdk_public_" + sdkVersion.ApiLevel.String() + "_system_modules"
+			systemModuleKind := systemModuleKind(sdkVersion.Kind, sdkVersion.ApiLevel)
+			systemModules = fmt.Sprintf("sdk_%s_%s_system_modules", systemModuleKind, sdkVersion.ApiLevel)
 		}
 
 		return sdkDep{
@@ -116,13 +143,15 @@
 		}
 	}
 
-	toModule := func(modules []string, res string, aidl android.Path) sdkDep {
+	toModule := func(module string, aidl android.Path) sdkDep {
+		// Select the kind of system modules needed for the sdk version.
+		systemModulesKind := systemModuleKind(sdkVersion.Kind, android.FutureApiLevel)
 		return sdkDep{
 			useModule:          true,
-			bootclasspath:      append(modules, config.DefaultLambdaStubsLibrary),
-			systemModules:      "core-current-stubs-system-modules",
-			java9Classpath:     modules,
-			frameworkResModule: res,
+			bootclasspath:      []string{module, config.DefaultLambdaStubsLibrary},
+			systemModules:      fmt.Sprintf("core-%s-stubs-system-modules", systemModulesKind),
+			java9Classpath:     []string{module},
+			frameworkResModule: "framework-res",
 			aidl:               android.OptionalPathForPath(aidl),
 		}
 	}
@@ -161,38 +190,24 @@
 			noFrameworksLibs: true,
 		}
 	case android.SdkPublic:
-		return toModule([]string{"android_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkSystem:
-		return toModule([]string{"android_system_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_system_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkTest:
-		return toModule([]string{"android_test_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
+		return toModule("android_test_stubs_current", sdkFrameworkAidlPath(ctx))
 	case android.SdkCore:
 		return sdkDep{
 			useModule:        true,
 			bootclasspath:    []string{"core.current.stubs", config.DefaultLambdaStubsLibrary},
-			systemModules:    "core-current-stubs-system-modules",
+			systemModules:    "core-public-stubs-system-modules",
 			noFrameworksLibs: true,
 		}
 	case android.SdkModule:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return sdkDep{
-			useModule:          true,
-			bootclasspath:      []string{"android_module_lib_stubs_current", config.DefaultLambdaStubsLibrary},
-			systemModules:      "core-module-lib-stubs-system-modules",
-			java9Classpath:     []string{"android_module_lib_stubs_current"},
-			frameworkResModule: "framework-res",
-			aidl:               android.OptionalPathForPath(nonUpdatableFrameworkAidlPath(ctx)),
-		}
+		return toModule("android_module_lib_stubs_current", nonUpdatableFrameworkAidlPath(ctx))
 	case android.SdkSystemServer:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
-		return sdkDep{
-			useModule:          true,
-			bootclasspath:      []string{"android_system_server_stubs_current", config.DefaultLambdaStubsLibrary},
-			systemModules:      "core-module-lib-stubs-system-modules",
-			java9Classpath:     []string{"android_system_server_stubs_current"},
-			frameworkResModule: "framework-res",
-			aidl:               android.OptionalPathForPath(sdkFrameworkAidlPath(ctx)),
-		}
+		return toModule("android_system_server_stubs_current", sdkFrameworkAidlPath(ctx))
 	default:
 		panic(fmt.Errorf("invalid sdk %q", sdkVersion.Raw))
 	}
@@ -370,7 +385,7 @@
 			"frameworks-base-api-current.txt",
 			"frameworks-base-api-system-current.txt",
 			"frameworks-base-api-module-lib-current.txt",
-			"services-system-server-current.txt",
+			"frameworks-base-api-system-server-current.txt",
 		}
 		count := 0
 		ctx.VisitAllModules(func(module android.Module) {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 8c66438..47ffc6a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -32,25 +32,7 @@
 )
 
 const (
-	sdkXmlFileSuffix    = ".xml"
-	permissionsTemplate = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n` +
-		`<!-- Copyright (C) 2018 The Android Open Source Project\n` +
-		`\n` +
-		`    Licensed under the Apache License, Version 2.0 (the \"License\");\n` +
-		`    you may not use this file except in compliance with the License.\n` +
-		`    You may obtain a copy of the License at\n` +
-		`\n` +
-		`        http://www.apache.org/licenses/LICENSE-2.0\n` +
-		`\n` +
-		`    Unless required by applicable law or agreed to in writing, software\n` +
-		`    distributed under the License is distributed on an \"AS IS\" BASIS,\n` +
-		`    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n` +
-		`    See the License for the specific language governing permissions and\n` +
-		`    limitations under the License.\n` +
-		`-->\n` +
-		`<permissions>\n` +
-		`    <library name=\"%s\" file=\"%s\"/>\n` +
-		`</permissions>\n`
+	sdkXmlFileSuffix = ".xml"
 )
 
 // A tag to associated a dependency with a specific api scope.
@@ -361,13 +343,14 @@
 	// The sdk_version to use for building the stubs.
 	//
 	// If not specified then it will use an sdk_version determined as follows:
+	//
 	// 1) If the sdk_version specified on the java_sdk_library is none then this
-	//    will be none. This is used for java_sdk_library instances that are used
-	//    to create stubs that contribute to the core_current sdk version.
-	// 2) Otherwise, it is assumed that this library extends but does not contribute
-	//    directly to a specific sdk_version and so this uses the sdk_version appropriate
-	//    for the api scope. e.g. public will use sdk_version: current, system will use
-	//    sdk_version: system_current, etc.
+	// will be none. This is used for java_sdk_library instances that are used
+	// to create stubs that contribute to the core_current sdk version.
+	// 2) Otherwise, it is assumed that this library extends but does not
+	// contribute directly to a specific sdk_version and so this uses the
+	// sdk_version appropriate for the api scope. e.g. public will use
+	// sdk_version: current, system will use sdk_version: system_current, etc.
 	//
 	// This does not affect the sdk_version used for either generating the stubs source
 	// or the API file. They both have to use the same sdk_version as is used for
@@ -376,6 +359,9 @@
 }
 
 type sdkLibraryProperties struct {
+	// List of source files that are needed to compile the API, but are not part of runtime library.
+	Api_srcs []string `android:"arch_variant"`
+
 	// Visibility for impl library module. If not specified then defaults to the
 	// visibility property.
 	Impl_library_visibility []string
@@ -391,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"`
 
@@ -419,7 +408,7 @@
 	// local files that are used within user customized droiddoc options.
 	Droiddoc_option_files []string
 
-	// additional droiddoc options
+	// additional droiddoc options.
 	// Available variables for substitution:
 	//
 	//  $(location <label>): the path to the droiddoc_option_files with name <label>
@@ -537,7 +526,7 @@
 	// The dex jar for the stubs.
 	//
 	// This is not the implementation jar, it still only contains stubs.
-	stubsDexJarPath android.Path
+	stubsDexJarPath OptionalDexJarPath
 
 	// The API specification file, e.g. system_current.txt.
 	currentApiFilePath android.OptionalPath
@@ -547,6 +536,9 @@
 
 	// The stubs source jar.
 	stubsSrcJar android.OptionalPath
+
+	// Extracted annotations.
+	annotationsZip android.OptionalPath
 }
 
 func (paths *scopePaths) extractStubsLibraryInfoFromDependency(ctx android.ModuleContext, dep android.Module) error {
@@ -582,6 +574,7 @@
 }
 
 func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider ApiStubsProvider) {
+	paths.annotationsZip = android.OptionalPathForPath(provider.AnnotationsZip())
 	paths.currentApiFilePath = android.OptionalPathForPath(provider.ApiFilePath())
 	paths.removedApiFilePath = android.OptionalPathForPath(provider.RemovedApiFilePath())
 }
@@ -629,6 +622,33 @@
 
 	// Files containing information about supported java doc tags.
 	Doctag_files []string `android:"path"`
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
 }
 
 // commonSdkLibraryAndImportModule defines the interface that must be provided by a module that
@@ -736,6 +756,8 @@
 	apiTxtComponentName = "api.txt"
 
 	removedApiTxtComponentName = "removed-api.txt"
+
+	annotationsComponentName = "annotations.zip"
 )
 
 // A regular expression to match tags that reference a specific stubs component.
@@ -754,7 +776,7 @@
 	scopesRegexp := choice(allScopeNames...)
 
 	// Regular expression to match one of the components.
-	componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName)
+	componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName, annotationsComponentName)
 
 	// Regular expression to match any combination of one scope and one component.
 	return regexp.MustCompile(fmt.Sprintf(`^\.(%s)\.(%s)$`, scopesRegexp, componentsRegexp))
@@ -762,9 +784,7 @@
 
 // For OutputFileProducer interface
 //
-// .<scope>.stubs.source
-// .<scope>.api.txt
-// .<scope>.removed-api.txt
+// .<scope>.<component name>, for all ComponentNames (for example: .public.removed-api.txt)
 func (c *commonToSdkLibraryAndImport) commonOutputFiles(tag string) (android.Paths, error) {
 	if groups := tagSplitter.FindStringSubmatch(tag); groups != nil {
 		scopeName := groups[1]
@@ -791,6 +811,11 @@
 				if paths.removedApiFilePath.Valid() {
 					return android.Paths{paths.removedApiFilePath.Path()}, nil
 				}
+
+			case annotationsComponentName:
+				if paths.annotationsZip.Valid() {
+					return android.Paths{paths.annotationsZip.Path()}, nil
+				}
 			}
 
 			return nil, fmt.Errorf("%s not available for api scope %s", component, scopeName)
@@ -903,10 +928,10 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path {
+func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) OptionalDexJarPath {
 	paths := c.selectScopePaths(ctx, kind)
 	if paths == nil {
-		return nil
+		return makeUnsetDexJarPath()
 	}
 
 	return paths.stubsDexJarPath
@@ -981,13 +1006,15 @@
 }
 
 // to satisfy SdkLibraryComponentDependency
-func (e *EmbeddableSdkLibraryComponent) OptionalImplicitSdkLibrary() *string {
-	return e.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack
-}
-
-// to satisfy SdkLibraryComponentDependency
 func (e *EmbeddableSdkLibraryComponent) OptionalSdkLibraryImplementation() *string {
-	// Currently implementation library name is the same as the SDK library name.
+	// For shared libraries, this is the same as the SDK library name. If a Java library or app
+	// depends on a component library (e.g. a stub library) it still needs to know the name of the
+	// run-time library and the corresponding module that provides the implementation. This name is
+	// passed to manifest_fixer (to be added to AndroidManifest.xml) and added to CLC (to be used
+	// in dexpreopt).
+	//
+	// For non-shared SDK (component or not) libraries this returns `nil`, as they are not
+	// <uses-library> and should not be added to the manifest or to CLC.
 	return e.sdkLibraryComponentProperties.SdkLibraryToImplicitlyTrack
 }
 
@@ -999,12 +1026,6 @@
 	// SdkLibraryName returns the name of the java_sdk_library/_import module.
 	SdkLibraryName() *string
 
-	// The optional name of the sdk library that should be implicitly added to the
-	// AndroidManifest of an app that contains code which references the sdk library.
-	//
-	// Returns the name of the optional implicit SDK library or nil, if there isn't one.
-	OptionalImplicitSdkLibrary() *string
-
 	// The name of the implementation library for the optional SDK library or nil, if there isn't one.
 	OptionalSdkLibraryImplementation() *string
 }
@@ -1036,7 +1057,7 @@
 
 	// SdkApiStubDexJar returns the dex jar for the stubs. It is needed by the hiddenapi processing
 	// tool which processes dex files.
-	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path
+	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) OptionalDexJarPath
 
 	// SdkRemovedTxtFile returns the optional path to the removed.txt file for the specified sdk kind.
 	SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath
@@ -1110,6 +1131,22 @@
 	return generatedScopes
 }
 
+var _ android.ModuleWithMinSdkVersionCheck = (*SdkLibrary)(nil)
+
+func (module *SdkLibrary) CheckMinSdkVersion(ctx android.ModuleContext) {
+	android.CheckMinSdkVersion(ctx, module.MinSdkVersion(ctx).ApiLevel, func(c android.ModuleContext, do android.PayloadDepsCallback) {
+		ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
+			isExternal := !module.depIsInSameApex(ctx, child)
+			if am, ok := child.(android.ApexModule); ok {
+				if !do(ctx, parent, am, isExternal) {
+					return false
+				}
+			}
+			return !isExternal
+		})
+	})
+}
+
 type sdkLibraryComponentTag struct {
 	blueprint.BaseDependencyTag
 	name string
@@ -1187,14 +1224,23 @@
 
 func (module *SdkLibrary) OutputFiles(tag string) (android.Paths, error) {
 	paths, err := module.commonOutputFiles(tag)
-	if paths == nil && err == nil {
-		return module.Library.OutputFiles(tag)
-	} else {
+	if paths != nil || err != nil {
 		return paths, err
 	}
+	if module.requiresRuntimeImplementationLibrary() {
+		return module.Library.OutputFiles(tag)
+	}
+	if tag == "" {
+		return nil, nil
+	}
+	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 }
 
 func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if proptools.String(module.deviceProperties.Min_sdk_version) != "" {
+		module.CheckMinSdkVersion(ctx)
+	}
+
 	module.generateCommonBuildActions(ctx)
 
 	// Only build an implementation library if required.
@@ -1303,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,
@@ -1315,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{}{
@@ -1442,6 +1496,7 @@
 	props.Name = proptools.StringPtr(name)
 	props.Visibility = childModuleVisibility(module.sdkLibraryProperties.Stubs_source_visibility)
 	props.Srcs = append(props.Srcs, module.properties.Srcs...)
+	props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...)
 	props.Sdk_version = module.deviceProperties.Sdk_version
 	props.System_modules = module.deviceProperties.System_modules
 	props.Installable = proptools.BoolPtr(false)
@@ -1467,15 +1522,15 @@
 	}
 	droidstubsArgs = append(droidstubsArgs, module.sdkLibraryProperties.Droiddoc_options...)
 	disabledWarnings := []string{
-		"MissingPermission",
 		"BroadcastBehavior",
-		"HiddenSuperclass",
 		"DeprecationMismatch",
-		"UnavailableSymbol",
-		"SdkConstant",
+		"HiddenSuperclass",
 		"HiddenTypeParameter",
+		"MissingPermission",
+		"SdkConstant",
 		"Todo",
 		"Typo",
+		"UnavailableSymbol",
 	}
 	droidstubsArgs = append(droidstubsArgs, android.JoinWithPrefix(disabledWarnings, "--hide "))
 
@@ -1570,14 +1625,29 @@
 
 // Creates the xml file that publicizes the runtime library
 func (module *SdkLibrary) createXmlFile(mctx android.DefaultableHookContext) {
+	moduleMinApiLevel := module.Library.MinSdkVersion(mctx).ApiLevel
+	var moduleMinApiLevelStr = moduleMinApiLevel.String()
+	if moduleMinApiLevel == android.NoneApiLevel {
+		moduleMinApiLevelStr = "current"
+	}
 	props := struct {
-		Name           *string
-		Lib_name       *string
-		Apex_available []string
+		Name                      *string
+		Lib_name                  *string
+		Apex_available            []string
+		On_bootclasspath_since    *string
+		On_bootclasspath_before   *string
+		Min_device_sdk            *string
+		Max_device_sdk            *string
+		Sdk_library_min_api_level *string
 	}{
-		Name:           proptools.StringPtr(module.xmlPermissionsModuleName()),
-		Lib_name:       proptools.StringPtr(module.BaseModuleName()),
-		Apex_available: module.ApexProperties.Apex_available,
+		Name:                      proptools.StringPtr(module.xmlPermissionsModuleName()),
+		Lib_name:                  proptools.StringPtr(module.BaseModuleName()),
+		Apex_available:            module.ApexProperties.Apex_available,
+		On_bootclasspath_since:    module.commonSdkLibraryProperties.On_bootclasspath_since,
+		On_bootclasspath_before:   module.commonSdkLibraryProperties.On_bootclasspath_before,
+		Min_device_sdk:            module.commonSdkLibraryProperties.Min_device_sdk,
+		Max_device_sdk:            module.commonSdkLibraryProperties.Max_device_sdk,
+		Sdk_library_min_api_level: &moduleMinApiLevelStr,
 	}
 
 	mctx.CreateModule(sdkLibraryXmlFactory, &props)
@@ -1700,8 +1770,12 @@
 			path := path.Join(mctx.ModuleDir(), apiDir, scope.apiFilePrefix+api)
 			p := android.ExistentPathForSource(mctx, path)
 			if !p.Valid() {
-				mctx.ModuleErrorf("Current api file %#v doesn't exist", path)
-				missingCurrentApi = true
+				if mctx.Config().AllowMissingDependencies() {
+					mctx.AddMissingDependencies([]string{path})
+				} else {
+					mctx.ModuleErrorf("Current api file %#v doesn't exist", path)
+					missingCurrentApi = true
+				}
 			}
 		}
 	}
@@ -1751,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() {
@@ -1884,6 +1959,9 @@
 
 	// The removed.txt
 	Removed_api *string `android:"path"`
+
+	// Annotation zip
+	Annotations *string `android:"path"`
 }
 
 type sdkLibraryImportProperties struct {
@@ -1895,7 +1973,6 @@
 	Compile_dex *bool
 
 	// If not empty, classes are restricted to the specified packages and their sub-packages.
-	// This information is used to generate the updatable-bcp-packages.txt file.
 	Permitted_packages []string
 }
 
@@ -1907,6 +1984,7 @@
 	android.SdkBase
 
 	hiddenAPI
+	dexpreopter
 
 	properties sdkLibraryImportProperties
 
@@ -1923,8 +2001,12 @@
 	// Is nil if the source module does not exist.
 	xmlPermissionsFileModule *sdkLibraryXml
 
-	// Path to the dex implementation jar obtained from the prebuilt_apex, if any.
-	dexJarFile android.Path
+	// Build path to the dex implementation jar obtained from the prebuilt_apex, if any.
+	dexJarFile OptionalDexJarPath
+
+	// Expected install file path of the source module(sdk_library)
+	// or dex implementation jar obtained from the prebuilt_apex, if any.
+	installFile android.Path
 }
 
 var _ SdkLibraryDependency = (*SdkLibraryImport)(nil)
@@ -2107,6 +2189,14 @@
 	}
 }
 
+func (module *SdkLibraryImport) AndroidMkEntries() []android.AndroidMkEntries {
+	// For an SDK library imported from a prebuilt APEX, we don't need a Make module for itself, as we
+	// don't need to install it. However, we need to add its dexpreopt outputs as sub-modules, if it
+	// is preopted.
+	dexpreoptEntries := module.dexpreopter.AndroidMkEntriesForApex()
+	return append(dexpreoptEntries, android.AndroidMkEntries{Disabled: true})
+}
+
 var _ android.ApexModule = (*SdkLibraryImport)(nil)
 
 // Implements android.ApexModule
@@ -2133,14 +2223,30 @@
 	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) {
 	module.generateCommonBuildActions(ctx)
 
-	var deapexerModule android.Module
+	// Assume that source module(sdk_library) is installed in /<sdk_library partition>/framework
+	module.installFile = android.PathForModuleInstall(ctx, "framework", module.Stem()+".jar")
 
 	// Record the paths to the prebuilt stubs library and stubs source.
 	ctx.VisitDirectDeps(func(to android.Module) {
@@ -2167,15 +2273,6 @@
 				ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to)
 			}
 		}
-
-		// Save away the `deapexer` module on which this depends, if any.
-		if tag == android.DeapexerTag {
-			if deapexerModule != nil {
-				ctx.ModuleErrorf("Ambiguous duplicate deapexer module dependencies %q and %q",
-					deapexerModule.Name(), to.Name())
-			}
-			deapexerModule = to
-		}
 	})
 
 	// Populate the scope paths with information from the properties.
@@ -2185,6 +2282,7 @@
 		}
 
 		paths := module.getScopePathsCreateIfNeeded(apiScope)
+		paths.annotationsZip = android.OptionalPathForModuleSrc(ctx, scopeProperties.Annotations)
 		paths.currentApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Current_api)
 		paths.removedApiFilePath = android.OptionalPathForModuleSrc(ctx, scopeProperties.Removed_api)
 	}
@@ -2194,22 +2292,28 @@
 		// obtained from the associated deapexer module.
 		ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if ai.ForPrebuiltApex {
-			if deapexerModule == nil {
-				// This should never happen as a variant for a prebuilt_apex is only created if the
-				// deapxer module has been configured to export the dex implementation jar for this module.
-				ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q",
-					module.Name(), ai.ApexVariationName)
-			}
-
 			// Get the path of the dex implementation jar from the `deapexer` module.
-			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
+			di := android.FindDeapexerProviderForModule(ctx)
+			if di == nil {
+				return // An error has been reported by FindDeapexerProviderForModule.
+			}
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(module.BaseModuleName())); dexOutputPath != nil {
-				module.dexJarFile = dexOutputPath
-				module.initHiddenAPI(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
+				dexJarFile := makeDexJarPathFromPath(dexOutputPath)
+				module.dexJarFile = dexJarFile
+				installPath := android.PathForModuleInPartitionInstall(
+					ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(module.BaseModuleName()))
+				module.installFile = installPath
+				module.initHiddenAPI(ctx, dexJarFile, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
+
+				// Dexpreopting.
+				module.dexpreopter.installPath = module.dexpreopter.getInstallPath(ctx, installPath)
+				module.dexpreopter.isSDKLibrary = true
+				module.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, &module.dexpreopter)
+				module.dexpreopt(ctx, dexOutputPath)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
-				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
+				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt APEX %s", di.ApexModuleName())
 			}
 		}
 	}
@@ -2244,14 +2348,14 @@
 }
 
 // to satisfy UsesLibraryDependency interface
-func (module *SdkLibraryImport) DexJarBuildPath() android.Path {
+func (module *SdkLibraryImport) DexJarBuildPath() OptionalDexJarPath {
 	// The dex implementation jar extracted from the .apex file should be used in preference to the
 	// source.
-	if module.dexJarFile != nil {
+	if module.dexJarFile.IsSet() {
 		return module.dexJarFile
 	}
 	if module.implLibraryModule == nil {
-		return nil
+		return makeUnsetDexJarPath()
 	} else {
 		return module.implLibraryModule.DexJarBuildPath()
 	}
@@ -2259,11 +2363,7 @@
 
 // to satisfy UsesLibraryDependency interface
 func (module *SdkLibraryImport) DexJarInstallPath() android.Path {
-	if module.implLibraryModule == nil {
-		return nil
-	} else {
-		return module.implLibraryModule.DexJarInstallPath()
-	}
+	return module.installFile
 }
 
 // to satisfy UsesLibraryDependency interface
@@ -2289,17 +2389,17 @@
 	}
 }
 
-func (module *SdkLibraryImport) getStrictUpdatabilityLinting() bool {
+func (module *SdkLibraryImport) GetStrictUpdatabilityLinting() bool {
 	if module.implLibraryModule == nil {
 		return false
 	} else {
-		return module.implLibraryModule.getStrictUpdatabilityLinting()
+		return module.implLibraryModule.GetStrictUpdatabilityLinting()
 	}
 }
 
-func (module *SdkLibraryImport) setStrictUpdatabilityLinting(strictLinting bool) {
+func (module *SdkLibraryImport) SetStrictUpdatabilityLinting(strictLinting bool) {
 	if module.implLibraryModule != nil {
-		module.implLibraryModule.setStrictUpdatabilityLinting(strictLinting)
+		module.implLibraryModule.SetStrictUpdatabilityLinting(strictLinting)
 	}
 }
 
@@ -2328,6 +2428,11 @@
 	}
 }
 
+// to satisfy java.DexpreopterInterface interface
+func (module *SdkLibraryImport) IsInstallable() bool {
+	return true
+}
+
 var _ android.RequiredFilesFromPrebuiltApex = (*SdkLibraryImport)(nil)
 
 func (module *SdkLibraryImport) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
@@ -2354,6 +2459,38 @@
 type sdkLibraryXmlProperties struct {
 	// canonical name of the lib
 	Lib_name *string
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
+
+	// The SdkLibrary's min api level as a string
+	//
+	// This value comes from the ApiLevel of the MinSdkVersion property.
+	Sdk_library_min_api_level *string
 }
 
 // java_sdk_library_xml builds the permission xml file for a java_sdk_library.
@@ -2430,11 +2567,90 @@
 	return "/" + partition + "/framework/" + implName + ".jar"
 }
 
+func formattedOptionalSdkLevelAttribute(ctx android.ModuleContext, attrName string, value *string) string {
+	if value == nil {
+		return ""
+	}
+	apiLevel, err := android.ApiLevelFromUser(ctx, *value)
+	if err != nil {
+		// attributes in bp files have underscores but in the xml have dashes.
+		ctx.PropertyErrorf(strings.ReplaceAll(attrName, "-", "_"), err.Error())
+		return ""
+	}
+	if apiLevel.IsCurrent() {
+		// passing "current" would always mean a future release, never the current (or the current in
+		// progress) which means some conditions would never be triggered.
+		ctx.PropertyErrorf(strings.ReplaceAll(attrName, "-", "_"),
+			`"current" is not an allowed value for this attribute`)
+		return ""
+	}
+	// "safeValue" is safe because it translates finalized codenames to a string
+	// with their SDK int.
+	safeValue := apiLevel.String()
+	return formattedOptionalAttribute(attrName, &safeValue)
+}
+
+// formats an attribute for the xml permissions file if the value is not null
+// returns empty string otherwise
+func formattedOptionalAttribute(attrName string, value *string) string {
+	if value == nil {
+		return ""
+	}
+	return fmt.Sprintf(`        %s=\"%s\"\n`, attrName, *value)
+}
+
+func (module *sdkLibraryXml) permissionsContents(ctx android.ModuleContext) string {
+	libName := proptools.String(module.properties.Lib_name)
+	libNameAttr := formattedOptionalAttribute("name", &libName)
+	filePath := module.implPath(ctx)
+	filePathAttr := formattedOptionalAttribute("file", &filePath)
+	implicitFromAttr := formattedOptionalSdkLevelAttribute(ctx, "on-bootclasspath-since", module.properties.On_bootclasspath_since)
+	implicitUntilAttr := formattedOptionalSdkLevelAttribute(ctx, "on-bootclasspath-before", module.properties.On_bootclasspath_before)
+	minSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "min-device-sdk", module.properties.Min_device_sdk)
+	maxSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "max-device-sdk", module.properties.Max_device_sdk)
+	// <library> is understood in all android versions whereas <apex-library> is only understood from API T (and ignored before that).
+	// similarly, min_device_sdk is only understood from T. So if a library is using that, we need to use the apex-library to make sure this library is not loaded before T
+	var libraryTag string
+	if module.properties.Min_device_sdk != nil {
+		libraryTag = `    <apex-library\n`
+	} else {
+		libraryTag = `    <library\n`
+	}
+
+	return strings.Join([]string{
+		`<?xml version=\"1.0\" encoding=\"utf-8\"?>\n`,
+		`<!-- Copyright (C) 2018 The Android Open Source Project\n`,
+		`\n`,
+		`    Licensed under the Apache License, Version 2.0 (the \"License\");\n`,
+		`    you may not use this file except in compliance with the License.\n`,
+		`    You may obtain a copy of the License at\n`,
+		`\n`,
+		`        http://www.apache.org/licenses/LICENSE-2.0\n`,
+		`\n`,
+		`    Unless required by applicable law or agreed to in writing, software\n`,
+		`    distributed under the License is distributed on an \"AS IS\" BASIS,\n`,
+		`    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n`,
+		`    See the License for the specific language governing permissions and\n`,
+		`    limitations under the License.\n`,
+		`-->\n`,
+		`<permissions>\n`,
+		libraryTag,
+		libNameAttr,
+		filePathAttr,
+		implicitFromAttr,
+		implicitUntilAttr,
+		minSdkAttr,
+		maxSdkAttr,
+		`    />\n`,
+		`</permissions>\n`}, "")
+}
+
 func (module *sdkLibraryXml) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	module.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
 	libName := proptools.String(module.properties.Lib_name)
-	xmlContent := fmt.Sprintf(permissionsTemplate, libName, module.implPath(ctx))
+	module.selfValidate(ctx)
+	xmlContent := module.permissionsContents(ctx)
 
 	module.outputFilePath = android.PathForModuleOut(ctx, libName+".xml").OutputPath
 	rule := android.NewRuleBuilder(pctx, ctx)
@@ -2449,30 +2665,105 @@
 
 func (module *sdkLibraryXml) AndroidMkEntries() []android.AndroidMkEntries {
 	if module.hideApexVariantFromMake {
-		return []android.AndroidMkEntries{android.AndroidMkEntries{
+		return []android.AndroidMkEntries{{
 			Disabled: true,
 		}}
 	}
 
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
+	return []android.AndroidMkEntries{{
 		Class:      "ETC",
 		OutputFile: android.OptionalPathForPath(module.outputFilePath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", module.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", module.outputFilePath.Base())
 			},
 		},
 	}}
 }
 
+func (module *sdkLibraryXml) selfValidate(ctx android.ModuleContext) {
+	module.validateAtLeastTAttributes(ctx)
+	module.validateMinAndMaxDeviceSdk(ctx)
+	module.validateMinMaxDeviceSdkAndModuleMinSdk(ctx)
+	module.validateOnBootclasspathBeforeRequirements(ctx)
+}
+
+func (module *sdkLibraryXml) validateAtLeastTAttributes(ctx android.ModuleContext) {
+	t := android.ApiLevelOrPanic(ctx, "Tiramisu")
+	module.attrAtLeastT(ctx, t, module.properties.Min_device_sdk, "min_device_sdk")
+	module.attrAtLeastT(ctx, t, module.properties.Max_device_sdk, "max_device_sdk")
+	module.attrAtLeastT(ctx, t, module.properties.On_bootclasspath_before, "on_bootclasspath_before")
+	module.attrAtLeastT(ctx, t, module.properties.On_bootclasspath_since, "on_bootclasspath_since")
+}
+
+func (module *sdkLibraryXml) attrAtLeastT(ctx android.ModuleContext, t android.ApiLevel, attr *string, attrName string) {
+	if attr != nil {
+		if level, err := android.ApiLevelFromUser(ctx, *attr); err == nil {
+			// we will inform the user of invalid inputs when we try to write the
+			// permissions xml file so we don't need to do it here
+			if t.GreaterThan(level) {
+				ctx.PropertyErrorf(attrName, "Attribute value needs to be at least T")
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateMinAndMaxDeviceSdk(ctx android.ModuleContext) {
+	if module.properties.Min_device_sdk != nil && module.properties.Max_device_sdk != nil {
+		min, minErr := android.ApiLevelFromUser(ctx, *module.properties.Min_device_sdk)
+		max, maxErr := android.ApiLevelFromUser(ctx, *module.properties.Max_device_sdk)
+		if minErr == nil && maxErr == nil {
+			// we will inform the user of invalid inputs when we try to write the
+			// permissions xml file so we don't need to do it here
+			if min.GreaterThan(max) {
+				ctx.ModuleErrorf("min_device_sdk can't be greater than max_device_sdk")
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateMinMaxDeviceSdkAndModuleMinSdk(ctx android.ModuleContext) {
+	moduleMinApi := android.ApiLevelOrPanic(ctx, *module.properties.Sdk_library_min_api_level)
+	if module.properties.Min_device_sdk != nil {
+		api, err := android.ApiLevelFromUser(ctx, *module.properties.Min_device_sdk)
+		if err == nil {
+			if moduleMinApi.GreaterThan(api) {
+				ctx.PropertyErrorf("min_device_sdk", "Can't be less than module's min sdk (%s)", moduleMinApi)
+			}
+		}
+	}
+	if module.properties.Max_device_sdk != nil {
+		api, err := android.ApiLevelFromUser(ctx, *module.properties.Max_device_sdk)
+		if err == nil {
+			if moduleMinApi.GreaterThan(api) {
+				ctx.PropertyErrorf("max_device_sdk", "Can't be less than module's min sdk (%s)", moduleMinApi)
+			}
+		}
+	}
+}
+
+func (module *sdkLibraryXml) validateOnBootclasspathBeforeRequirements(ctx android.ModuleContext) {
+	moduleMinApi := android.ApiLevelOrPanic(ctx, *module.properties.Sdk_library_min_api_level)
+	if module.properties.On_bootclasspath_before != nil {
+		t := android.ApiLevelOrPanic(ctx, "Tiramisu")
+		// if we use the attribute, then we need to do this validation
+		if moduleMinApi.LessThan(t) {
+			// if minAPi is < T, then we need to have min_device_sdk (which only accepts T+)
+			if module.properties.Min_device_sdk == nil {
+				ctx.PropertyErrorf("on_bootclasspath_before", "Using this property requires that the module's min_sdk_version or the shared library's min_device_sdk is at least T")
+			}
+		}
+	}
+}
+
 type sdkLibrarySdkMemberType struct {
 	android.SdkMemberTypeBase
 }
 
-func (s *sdkLibrarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (s *sdkLibrarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (s *sdkLibrarySdkMemberType) IsInstance(module android.Module) bool {
@@ -2499,7 +2790,7 @@
 	android.SdkMemberPropertiesBase
 
 	// Scope to per scope properties.
-	Scopes map[*apiScope]scopeProperties
+	Scopes map[*apiScope]*scopeProperties
 
 	// The Java stubs source files.
 	Stub_srcs []string
@@ -2518,6 +2809,33 @@
 	Doctag_paths android.Paths
 
 	Permitted_packages []string
+
+	// Signals that this shared library is part of the bootclasspath starting
+	// on the version indicated in this attribute.
+	//
+	// This will make platforms at this level and above to ignore
+	// <uses-library> tags with this library name because the library is already
+	// available
+	On_bootclasspath_since *string
+
+	// Signals that this shared library was part of the bootclasspath before
+	// (but not including) the version indicated in this attribute.
+	//
+	// The system will automatically add a <uses-library> tag with this library to
+	// apps that target any SDK less than the version indicated in this attribute.
+	On_bootclasspath_before *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is below the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Min_device_sdk *string
+
+	// Indicates that PackageManager should ignore this shared library if the
+	// platform is above the version indicated in this attribute.
+	//
+	// This means that the device won't recognise this library as installed.
+	Max_device_sdk *string
 }
 
 type scopeProperties struct {
@@ -2525,13 +2843,14 @@
 	StubsSrcJar    android.Path
 	CurrentApiFile android.Path
 	RemovedApiFile android.Path
+	AnnotationsZip android.Path `supported_build_releases:"Tiramisu+"`
 	SdkVersion     string
 }
 
 func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	sdk := variant.(*SdkLibrary)
 
-	s.Scopes = make(map[*apiScope]scopeProperties)
+	s.Scopes = make(map[*apiScope]*scopeProperties)
 	for _, apiScope := range allApiScopes {
 		paths := sdk.findScopePaths(apiScope)
 		if paths == nil {
@@ -2550,7 +2869,11 @@
 			if paths.removedApiFilePath.Valid() {
 				properties.RemovedApiFile = paths.removedApiFilePath.Path()
 			}
-			s.Scopes[apiScope] = properties
+			// The annotations zip is only available for modules that set annotations_enabled: true.
+			if paths.annotationsZip.Valid() {
+				properties.AnnotationsZip = paths.annotationsZip.Path()
+			}
+			s.Scopes[apiScope] = &properties
 		}
 	}
 
@@ -2559,6 +2882,10 @@
 	s.Compile_dex = sdk.dexProperties.Compile_dex
 	s.Doctag_paths = sdk.doctagPaths
 	s.Permitted_packages = sdk.PermittedPackagesForUpdatableBootJars()
+	s.On_bootclasspath_since = sdk.commonSdkLibraryProperties.On_bootclasspath_since
+	s.On_bootclasspath_before = sdk.commonSdkLibraryProperties.On_bootclasspath_before
+	s.Min_device_sdk = sdk.commonSdkLibraryProperties.Min_device_sdk
+	s.Max_device_sdk = sdk.commonSdkLibraryProperties.Max_device_sdk
 }
 
 func (s *sdkLibrarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
@@ -2614,6 +2941,12 @@
 				scopeSet.AddProperty("removed_api", removedApiSnapshotPath)
 			}
 
+			if properties.AnnotationsZip != nil {
+				annotationsSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"_annotations.zip")
+				ctx.SnapshotBuilder().CopyToSnapshot(properties.AnnotationsZip, annotationsSnapshotPath)
+				scopeSet.AddProperty("annotations", annotationsSnapshotPath)
+			}
+
 			if properties.SdkVersion != "" {
 				scopeSet.AddProperty("sdk_version", properties.SdkVersion)
 			}
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 65af953..3500c84 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -15,12 +15,13 @@
 package java
 
 import (
-	"android/soong/android"
 	"fmt"
 	"path/filepath"
 	"regexp"
 	"testing"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -47,6 +48,7 @@
 			name: "bar",
 			srcs: ["a.java", "b.java"],
 			api_packages: ["bar"],
+			exclude_kotlinc_generated_files: true,
 		}
 		java_library {
 			name: "baz",
@@ -107,7 +109,7 @@
 			libs: ["foo"],
 			sdk_version: "module_30",
 		}
-		`)
+	`)
 
 	// check the existence of the internal modules
 	foo := result.ModuleForTests("foo", "android_common")
@@ -122,7 +124,7 @@
 	result.ModuleForTests("foo.api.system.28", "")
 	result.ModuleForTests("foo.api.test.28", "")
 
-	exportedComponentsInfo := result.ModuleProvider(foo.Module(), ExportedComponentsInfoProvider).(ExportedComponentsInfo)
+	exportedComponentsInfo := result.ModuleProvider(foo.Module(), android.ExportedComponentsInfoProvider).(android.ExportedComponentsInfo)
 	expectedFooExportedComponents := []string{
 		"foo.stubs",
 		"foo.stubs.source",
@@ -156,9 +158,198 @@
 	// test if baz has exported SDK lib names foo and bar to qux
 	qux := result.ModuleForTests("qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
-		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
+		requiredSdkLibs, optionalSdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
+		android.AssertDeepEquals(t, "qux exports (required)", []string{"fred", "quuz", "foo", "bar"}, requiredSdkLibs)
+		android.AssertDeepEquals(t, "qux exports (optional)", []string{}, optionalSdkLibs)
 	}
+
+	fooDexJar := result.ModuleForTests("foo", "android_common").Rule("d8")
+	// tests if kotlinc generated files are NOT excluded from output of foo.
+	android.AssertStringDoesNotContain(t, "foo dex", fooDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
+
+	barDexJar := result.ModuleForTests("bar", "android_common").Rule("d8")
+	// tests if kotlinc generated files are excluded from output of bar.
+	android.AssertStringDoesContain(t, "bar dex", barDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+			"29": {"foo"},
+			"30": {"foo", "fooUpdatable", "fooUpdatableErr"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V", "W", "X"}
+		}),
+	).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "fooUpdatable",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "U",
+			on_bootclasspath_before: "V",
+			min_device_sdk: "W",
+			max_device_sdk: "X",
+			min_sdk_version: "S",
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+		}
+`)
+
+	// test that updatability attributes are passed on correctly
+	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-since=\"U\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `on-bootclasspath-before=\"V\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `min-device-sdk=\"W\"`)
+	android.AssertStringDoesContain(t, "fooUpdatable.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `max-device-sdk=\"X\"`)
+
+	// double check that updatability attributes are not written if they don't exist in the bp file
+	// the permissions file for the foo library defined above
+	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-since`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `on-bootclasspath-before`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `min-device-sdk`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooPermissions.RuleParams.Command, `max-device-sdk`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"30": {"fooUpdatable", "fooUpdatableErr"},
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			`on_bootclasspath_since: "aaa" could not be parsed as an integer and is not a recognized codename`,
+			`on_bootclasspath_before: "bbc" could not be parsed as an integer and is not a recognized codename`,
+			`min_device_sdk: "ccc" could not be parsed as an integer and is not a recognized codename`,
+			`max_device_sdk: "current" is not an allowed value for this attribute`,
+		})).RunTestWithBp(t,
+		`
+	java_sdk_library {
+			name: "fooUpdatableErr",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "aaa",
+			on_bootclasspath_before: "bbc",
+			min_device_sdk: "ccc",
+			max_device_sdk: "current",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_AtLeastTAttributes(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			"on_bootclasspath_since: Attribute value needs to be at least T",
+			"on_bootclasspath_before: Attribute value needs to be at least T",
+			"min_device_sdk: Attribute value needs to be at least T",
+			"max_device_sdk: Attribute value needs to be at least T",
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			on_bootclasspath_since: "S",
+			on_bootclasspath_before: "S",
+			min_device_sdk: "S",
+			max_device_sdk: "S",
+			min_sdk_version: "S",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdk(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			"min_device_sdk can't be greater than max_device_sdk",
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			min_device_sdk: "V",
+			max_device_sdk: "U",
+			min_sdk_version: "S",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdkAndModuleMinSdk(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Platform_version_active_codenames = []string{"Tiramisu", "U", "V"}
+		}),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(
+		[]string{
+			regexp.QuoteMeta("min_device_sdk: Can't be less than module's min sdk (V)"),
+			regexp.QuoteMeta("max_device_sdk: Can't be less than module's min sdk (V)"),
+		},
+	)).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			min_device_sdk: "U",
+			max_device_sdk: "U",
+			min_sdk_version: "V",
+		}
+`)
+}
+
+func TestJavaSdkLibrary_UpdatableLibrary_usesNewTag(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"30": {"foo"},
+		}),
+	).RunTestWithBp(t,
+		`
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			min_device_sdk: "Tiramisu",
+			min_sdk_version: "S",
+		}
+`)
+	// test that updatability attributes are passed on correctly
+	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Rule("java_sdk_xml")
+	android.AssertStringDoesContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<apex-library`)
+	android.AssertStringDoesNotContain(t, "foo.xml java_sdk_xml command", fooUpdatable.RuleParams.Command, `<library`)
 }
 
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
@@ -247,7 +438,7 @@
 	}
 }
 
-func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
+func TestJavaSdkLibrary_AccessOutputFiles(t *testing.T) {
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -257,6 +448,31 @@
 			name: "foo",
 			srcs: ["a.java"],
 			api_packages: ["foo"],
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.public.stubs.source}"],
+			java_resources: [":foo{.public.annotations.zip}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_AccessOutputFiles_NoAnnotations(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": path dependency ":foo{.public.annotations.zip}": annotations.zip not available for api scope public`)).
+		RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
 			public: {
 				enabled: true,
 			},
@@ -265,6 +481,7 @@
 		java_library {
 			name: "bar",
 			srcs: ["b.java", ":foo{.public.stubs.source}"],
+			java_resources: [":foo{.public.annotations.zip}"],
 		}
 		`)
 }
@@ -328,6 +545,7 @@
 				stub_srcs: ["a.java"],
 				current_api: "api/current.txt",
 				removed_api: "api/removed.txt",
+				annotations: "x/annotations.zip",
 			},
 		}
 
@@ -337,6 +555,7 @@
 			java_resources: [
 				":foo{.public.api.txt}",
 				":foo{.public.removed-api.txt}",
+				":foo{.public.annotations.zip}",
 			],
 		}
 		`)
@@ -597,6 +816,7 @@
 	}
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
 		`prebuilt_sdklib.stubs.system`,
@@ -673,7 +893,6 @@
 		`)
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
 		`prebuilt_sdklib`,
 		`sdklib.impl`,
 		`sdklib.stubs`,
@@ -682,6 +901,7 @@
 	})
 
 	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`sdklib.impl`,
 		`sdklib.xml`,
@@ -930,3 +1150,87 @@
 		})
 	}
 }
+
+func TestSdkLibrary_CheckMinSdkVersion(t *testing.T) {
+	preparer := android.GroupFixturePreparers(
+		PrepareForTestWithJavaBuildComponents,
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithJavaSdkLibraryFiles,
+	)
+
+	preparer.RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+            srcs: ["a.java"],
+            static_libs: ["util"],
+            min_sdk_version: "30",
+			unsafe_ignore_missing_latest_api: true,
+        }
+
+		java_library {
+			name: "util",
+			srcs: ["a.java"],
+			min_sdk_version: "30",
+		}
+	`)
+
+	preparer.
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				libs: ["util"],
+				impl_only_libs: ["util"],
+				stub_only_libs: ["util"],
+				stub_only_static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+			}
+		`)
+
+	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "util".*should support min_sdk_version\(30\)`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+				min_sdk_version: "31",
+			}
+		`)
+
+	preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "another_util".*should support min_sdk_version\(30\)`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "sdklib",
+				srcs: ["a.java"],
+				static_libs: ["util"],
+				min_sdk_version: "30",
+				unsafe_ignore_missing_latest_api: true,
+			}
+
+			java_library {
+				name: "util",
+				srcs: ["a.java"],
+				static_libs: ["another_util"],
+				min_sdk_version: "30",
+			}
+
+			java_library {
+				name: "another_util",
+				srcs: ["a.java"],
+				min_sdk_version: "31",
+			}
+		`)
+}
diff --git a/java/sdk_test.go b/java/sdk_test.go
index bb595a5..9e8ba6e 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -25,27 +25,36 @@
 	"android/soong/java/config"
 )
 
+type classpathTestCase struct {
+	name       string
+	unbundled  bool
+	moduleType string
+	host       android.OsClass
+	properties string
+
+	// for java 8
+	bootclasspath  []string
+	java8classpath []string
+
+	// for java 9
+	system         string
+	java9classpath []string
+
+	forces8 bool // if set, javac will always be called with java 8 arguments
+
+	aidl string
+
+	// Indicates how this test case is affected by the setting of Always_use_prebuilt_sdks.
+	//
+	// If this is nil then the test case is unaffected by the setting of Always_use_prebuilt_sdks.
+	// Otherwise, the test case can only be used when
+	// Always_use_prebuilt_sdks=*forAlwaysUsePrebuiltSdks.
+	forAlwaysUsePrebuiltSdks *bool
+}
+
 func TestClasspath(t *testing.T) {
 	const frameworkAidl = "-I" + defaultJavaDir + "/framework/aidl"
-	var classpathTestcases = []struct {
-		name       string
-		unbundled  bool
-		moduleType string
-		host       android.OsClass
-		properties string
-
-		// for java 8
-		bootclasspath  []string
-		java8classpath []string
-
-		// for java 9
-		system         string
-		java9classpath []string
-
-		forces8 bool // if set, javac will always be called with java 8 arguments
-
-		aidl string
-	}{
+	var classpathTestcases = []classpathTestCase{
 		{
 			name:           "default",
 			bootclasspath:  config.StableCorePlatformBootclasspathLibraries,
@@ -91,25 +100,52 @@
 			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "current",
 			properties:     `sdk_version: "current",`,
 			bootclasspath:  []string{"android_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "current",
+			properties:     `sdk_version: "current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "system_current",
 			properties:     `sdk_version: "system_current",`,
 			bootclasspath:  []string{"android_system_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_system_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
 
+			name:           "system_current",
+			properties:     `sdk_version: "system_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/system/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
 			name:           "system_29",
 			properties:     `sdk_version: "system_29",`,
 			bootclasspath:  []string{`""`},
@@ -118,7 +154,6 @@
 			aidl:           "-pprebuilts/sdk/29/public/framework.aidl",
 		},
 		{
-
 			name:           "system_30",
 			properties:     `sdk_version: "system_30",`,
 			bootclasspath:  []string{`""`},
@@ -128,20 +163,57 @@
 			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:           "test_current",
 			properties:     `sdk_version: "test_current",`,
 			bootclasspath:  []string{"android_test_stubs_current", "core-lambda-stubs"},
-			system:         "core-current-stubs-system-modules",
+			system:         "core-public-stubs-system-modules",
 			java9classpath: []string{"android_test_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "test_current",
+			properties:     `sdk_version: "test_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "test_30",
+			properties:     `sdk_version: "test_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/test/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
 
 			name:          "core_current",
 			properties:    `sdk_version: "core_current",`,
 			bootclasspath: []string{"core.current.stubs", "core-lambda-stubs"},
-			system:        "core-current-stubs-system-modules",
+			system:        "core-public-stubs-system-modules",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "core_current",
+			properties:     `sdk_version: "core_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/core/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/core/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
 		},
 		{
 
@@ -214,8 +286,10 @@
 			java9classpath: []string{"prebuilts/sdk/current/public/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
 			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
 		},
-
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
+
 			name:           "module_current",
 			properties:     `sdk_version: "module_current",`,
 			bootclasspath:  []string{"android_module_lib_stubs_current", "core-lambda-stubs"},
@@ -224,6 +298,48 @@
 			aidl:           "-pout/soong/framework_non_updatable.aidl",
 		},
 		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "module_current",
+			properties:     `sdk_version: "module_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "module_30",
+			properties:     `sdk_version: "module_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			name:           "module_31",
+			properties:     `sdk_version: "module_31",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_31_system_modules",
+			java8classpath: []string{"prebuilts/sdk/31/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/31/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/31/public/framework.aidl",
+		},
+		{
+			name:           "module_32",
+			properties:     `sdk_version: "module_32",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_32_system_modules",
+			java8classpath: []string{"prebuilts/sdk/32/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/32/module-lib/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/32/public/framework.aidl",
+		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=false (the default).
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(false),
+
 			name:           "system_server_current",
 			properties:     `sdk_version: "system_server_current",`,
 			bootclasspath:  []string{"android_system_server_stubs_current", "core-lambda-stubs"},
@@ -231,9 +347,62 @@
 			java9classpath: []string{"android_system_server_stubs_current"},
 			aidl:           "-pout/soong/framework.aidl",
 		},
+		{
+			// Test case only applies when Always_use_prebuilt_sdks=true.
+			forAlwaysUsePrebuiltSdks: proptools.BoolPtr(true),
+
+			name:           "system_server_current",
+			properties:     `sdk_version: "system_server_current",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_current_system_modules",
+			java8classpath: []string{"prebuilts/sdk/current/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/current/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/current/public/framework.aidl",
+		},
+		{
+			name:           "system_server_30",
+			properties:     `sdk_version: "system_server_30",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_30_system_modules",
+			java8classpath: []string{"prebuilts/sdk/30/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/30/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/30/public/framework.aidl",
+		},
+		{
+			name:           "system_server_31",
+			properties:     `sdk_version: "system_server_31",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_public_31_system_modules",
+			java8classpath: []string{"prebuilts/sdk/31/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/31/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/31/public/framework.aidl",
+		},
+		{
+			name:           "system_server_32",
+			properties:     `sdk_version: "system_server_32",`,
+			bootclasspath:  []string{`""`},
+			system:         "sdk_module-lib_32_system_modules",
+			java8classpath: []string{"prebuilts/sdk/32/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			java9classpath: []string{"prebuilts/sdk/32/system-server/android.jar", "prebuilts/sdk/tools/core-lambda-stubs.jar"},
+			aidl:           "-pprebuilts/sdk/32/public/framework.aidl",
+		},
 	}
 
+	t.Run("basic", func(t *testing.T) {
+		testClasspathTestCases(t, classpathTestcases, false)
+	})
+
+	t.Run("Always_use_prebuilt_sdks=true", func(t *testing.T) {
+		testClasspathTestCases(t, classpathTestcases, true)
+	})
+}
+
+func testClasspathTestCases(t *testing.T, classpathTestcases []classpathTestCase, alwaysUsePrebuiltSdks bool) {
 	for _, testcase := range classpathTestcases {
+		if testcase.forAlwaysUsePrebuiltSdks != nil && *testcase.forAlwaysUsePrebuiltSdks != alwaysUsePrebuiltSdks {
+			continue
+		}
+
 		t.Run(testcase.name, func(t *testing.T) {
 			moduleType := "java_library"
 			if testcase.moduleType != "" {
@@ -255,9 +424,11 @@
 				` + testcase.properties + `
 			}`
 
-			variant := "android_common"
-			if testcase.host == android.Host {
-				variant = android.BuildOs.String() + "_common"
+			variant := func(result *android.TestResult) string {
+				if testcase.host == android.Host {
+					return result.Config.BuildOS.String() + "_common"
+				}
+				return "android_common"
 			}
 
 			convertModulesToPaths := func(cp []string) []string {
@@ -297,7 +468,9 @@
 				system = "--system=none"
 			} else if testcase.system != "" {
 				dir := ""
-				if strings.HasPrefix(testcase.system, "sdk_public_") {
+				// If the system modules name starts with sdk_ then it is a prebuilt module and so comes
+				// from the prebuilt directory.
+				if strings.HasPrefix(testcase.system, "sdk_") {
 					dir = "prebuilts/sdk"
 				} else {
 					dir = defaultJavaDir
@@ -312,7 +485,7 @@
 			}
 
 			checkClasspath := func(t *testing.T, result *android.TestResult, isJava8 bool) {
-				foo := result.ModuleForTests("foo", variant)
+				foo := result.ModuleForTests("foo", variant(result))
 				javac := foo.Rule("javac")
 				var deps []string
 
@@ -349,11 +522,20 @@
 				android.AssertPathsRelativeToTopEquals(t, "implicits", deps, javac.Implicits)
 			}
 
+			preparer := android.NullFixturePreparer
+			if alwaysUsePrebuiltSdks {
+				preparer = android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
+				})
+			}
+
 			fixtureFactory := android.GroupFixturePreparers(
 				prepareForJavaTest,
 				FixtureWithPrebuiltApis(map[string][]string{
 					"29":      {},
 					"30":      {},
+					"31":      {},
+					"32":      {},
 					"current": {},
 				}),
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -367,6 +549,7 @@
 						env["ANDROID_JAVA8_HOME"] = "jdk8"
 					}
 				}),
+				preparer,
 			)
 
 			// Test with legacy javac -source 1.8 -target 1.8
@@ -376,7 +559,7 @@
 				checkClasspath(t, result, true /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
+					aidl := result.ModuleForTests("foo", variant(result)).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -389,7 +572,7 @@
 				checkClasspath(t, result, false /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
+					aidl := result.ModuleForTests("foo", variant(result)).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
diff --git a/java/system_modules.go b/java/system_modules.go
index d0dc74a..fec8eba 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -245,8 +245,8 @@
 	android.SdkMemberTypeBase
 }
 
-func (mt *systemModulesSdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
-	mctx.AddVariationDependencies(nil, dependencyTag, names...)
+func (mt *systemModulesSdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
 }
 
 func (mt *systemModulesSdkMemberType) IsInstance(module android.Module) bool {
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index a2006b7..fa61ea6 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -23,11 +23,19 @@
 
 func init() {
 	registerSystemserverClasspathBuildComponents(android.InitRegistrationContext)
+
+	android.RegisterSdkMemberType(&systemServerClasspathFragmentMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "systemserverclasspath_fragments",
+			SupportsSdk:  true,
+		},
+	})
 }
 
 func registerSystemserverClasspathBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("platform_systemserverclasspath", platformSystemServerClasspathFactory)
 	ctx.RegisterModuleType("systemserverclasspath_fragment", systemServerClasspathFactory)
+	ctx.RegisterModuleType("prebuilt_systemserverclasspath_fragment", prebuiltSystemServerClasspathModuleFactory)
 }
 
 type platformSystemServerClasspathModule struct {
@@ -48,22 +56,35 @@
 }
 
 func (p *platformSystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	classpathJars := configuredJarListToClasspathJars(ctx, p.ClasspathFragmentToConfiguredJarList(ctx), p.classpathType)
-	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	configuredJars := p.configuredJars(ctx)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, p.classpathType)
+	standaloneConfiguredJars := p.standaloneConfiguredJars(ctx)
+	standaloneClasspathJars := configuredJarListToClasspathJars(ctx, standaloneConfiguredJars, STANDALONE_SYSTEMSERVER_JARS)
+	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
+	classpathJars = append(classpathJars, standaloneClasspathJars...)
+	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
 }
 
-func (p *platformSystemServerClasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
-	global := dexpreopt.GetGlobalConfig(ctx)
-	return global.SystemServerJars
+func (p *platformSystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	// TODO(satayev): include any apex jars that don't populate their classpath proto config.
+	return dexpreopt.GetGlobalConfig(ctx).SystemServerJars
+}
+
+func (p *platformSystemServerClasspathModule) standaloneConfiguredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	return dexpreopt.GetGlobalConfig(ctx).StandaloneSystemServerJars
 }
 
 type SystemServerClasspathModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
+	android.SdkBase
 
 	ClasspathFragmentBase
 
 	properties systemServerClasspathFragmentProperties
+
+	// Collect the module directory for IDE info in java/jdeps.go.
+	modulePaths []string
 }
 
 func (s *SystemServerClasspathModule) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
@@ -71,39 +92,85 @@
 }
 
 type systemServerClasspathFragmentProperties struct {
-	// The contents of this systemserverclasspath_fragment, could be either java_library, or java_sdk_library.
+	// List of system_server classpath jars, could be either java_library, or java_sdk_library.
 	//
 	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
 	Contents []string
+
+	// List of jars that system_server loads dynamically using separate classloaders.
+	//
+	// The order does not matter.
+	Standalone_contents []string
 }
 
 func systemServerClasspathFactory() android.Module {
 	m := &SystemServerClasspathModule{}
 	m.AddProperties(&m.properties)
 	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, SYSTEMSERVERCLASSPATH)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
 
 func (s *SystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if len(s.properties.Contents) == 0 {
-		ctx.PropertyErrorf("contents", "empty contents are not allowed")
+	if len(s.properties.Contents) == 0 && len(s.properties.Standalone_contents) == 0 {
+		ctx.PropertyErrorf("contents", "Either contents or standalone_contents needs to be non-empty")
 	}
 
-	classpathJars := configuredJarListToClasspathJars(ctx, s.ClasspathFragmentToConfiguredJarList(ctx), s.classpathType)
-	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+	configuredJars := s.configuredJars(ctx)
+	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, s.classpathType)
+	standaloneConfiguredJars := s.standaloneConfiguredJars(ctx)
+	standaloneClasspathJars := configuredJarListToClasspathJars(ctx, standaloneConfiguredJars, STANDALONE_SYSTEMSERVER_JARS)
+	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
+	classpathJars = append(classpathJars, standaloneClasspathJars...)
+	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
+
+	// Collect the module directory for IDE info in java/jdeps.go.
+	s.modulePaths = append(s.modulePaths, ctx.ModuleDir())
 }
 
-func (s *SystemServerClasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
+func (s *SystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	possibleUpdatableModules := gatherPossibleUpdatableModuleNamesAndStems(ctx, s.properties.Contents, systemServerClasspathFragmentContentDepTag)
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Contents, systemServerClasspathFragmentContentDepTag)
+	jars, unknown := global.ApexSystemServerJars.Filter(possibleUpdatableModules)
+	// TODO(satayev): remove geotz ssc_fragment, since geotz is not part of SSCP anymore.
+	_, unknown = android.RemoveFromList("geotz", unknown)
+	// This module only exists in car products.
+	// So ignore it even if it is not in PRODUCT_APEX_SYSTEM_SERVER_JARS.
+	// TODO(b/203233647): Add better mechanism to make it optional.
+	_, unknown = android.RemoveFromList("car-frameworks-service-module", unknown)
 
-	// Only create configs for updatable boot jars. Non-updatable system server jars must be part of the
-	// platform_systemserverclasspath's classpath proto config to guarantee that they come before any
-	// updatable jars at runtime.
-	return global.UpdatableSystemServerJars.Filter(possibleUpdatableModules)
+	// This module is optional, so it is not present in all products.
+	// (See PRODUCT_ISOLATED_COMPILATION_ENABLED.)
+	// So ignore it even if it is not in PRODUCT_APEX_SYSTEM_SERVER_JARS.
+	// TODO(b/203233647): Add better mechanism to make it optional.
+	_, unknown = android.RemoveFromList("service-compos", unknown)
+
+	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
+	// config. However, any test specific jars would not be present in ApexSystemServerJars. Instead,
+	// we should check if we are creating a config for apex_test via ApexInfo and amend the values.
+	// This is an exception to support end-to-end test for ApexdUnitTests, until such support exists.
+	if android.InList("test_service-apexd", possibleUpdatableModules) {
+		jars = jars.Append("com.android.apex.test_package", "test_service-apexd")
+	} else if global.ApexSystemServerJars.Len() > 0 && len(unknown) > 0 && !android.IsModuleInVersionedSdk(ctx.Module()) {
+		// For non test apexes, make sure that all contents are actually declared in make.
+		ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS", unknown)
+	}
+
+	return jars
+}
+
+func (s *SystemServerClasspathModule) standaloneConfiguredJars(ctx android.ModuleContext) android.ConfiguredJarList {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Standalone_contents, systemServerClasspathFragmentContentDepTag)
+	jars, _ := global.ApexStandaloneSystemServerJars.Filter(possibleUpdatableModules)
+
+	// TODO(jiakaiz): add a check to ensure that the contents are declared in make.
+
+	return jars
 }
 
 type systemServerClasspathFragmentContentDependencyTag struct {
@@ -115,11 +182,33 @@
 	return false
 }
 
+// SdkMemberType causes dependencies added with this tag to be automatically added to the sdk as if
+// they were specified using java_systemserver_libs or java_sdk_libs.
+func (b systemServerClasspathFragmentContentDependencyTag) SdkMemberType(child android.Module) android.SdkMemberType {
+	// If the module is a java_sdk_library then treat it as if it was specified in the java_sdk_libs
+	// property, otherwise treat if it was specified in the java_systemserver_libs property.
+	if javaSdkLibrarySdkMemberType.IsInstance(child) {
+		return javaSdkLibrarySdkMemberType
+	}
+
+	return javaSystemserverLibsSdkMemberType
+}
+
+func (b systemServerClasspathFragmentContentDependencyTag) ExportMember() bool {
+	return true
+}
+
 // Contents of system server fragments in an apex are considered to be directly in the apex, as if
 // they were listed in java_libs.
 func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
 
+// Contents of system server fragments require files from prebuilt apex files.
+func (systemServerClasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
+
+var _ android.ReplaceSourceWithPrebuilt = systemServerClasspathFragmentContentDepTag
+var _ android.SdkMemberDependencyTag = systemServerClasspathFragmentContentDepTag
 var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
+var _ android.RequiresFilesFromPrebuiltApexTag = systemServerClasspathFragmentContentDepTag
 
 // The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
 var systemServerClasspathFragmentContentDepTag = systemServerClasspathFragmentContentDependencyTag{}
@@ -130,8 +219,113 @@
 
 func (s *SystemServerClasspathModule) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	module := ctx.Module()
+	_, isSourceModule := module.(*SystemServerClasspathModule)
+	var deps []string
+	deps = append(deps, s.properties.Contents...)
+	deps = append(deps, s.properties.Standalone_contents...)
 
-	for _, name := range s.properties.Contents {
+	for _, name := range deps {
+		// A systemserverclasspath_fragment must depend only on other source modules, while the
+		// prebuilt_systemserverclasspath_fragment_fragment must only depend on other prebuilt modules.
+		if !isSourceModule {
+			name = android.PrebuiltNameFromSource(name)
+		}
 		ctx.AddDependency(module, systemServerClasspathFragmentContentDepTag, name)
 	}
 }
+
+// Collect information for opening IDE project files in java/jdeps.go.
+func (s *SystemServerClasspathModule) IDEInfo(dpInfo *android.IdeInfo) {
+	dpInfo.Deps = append(dpInfo.Deps, s.properties.Contents...)
+	dpInfo.Deps = append(dpInfo.Deps, s.properties.Standalone_contents...)
+	dpInfo.Paths = append(dpInfo.Paths, s.modulePaths...)
+}
+
+type systemServerClasspathFragmentMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (s *systemServerClasspathFragmentMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	ctx.AddVariationDependencies(nil, dependencyTag, names...)
+}
+
+func (s *systemServerClasspathFragmentMemberType) IsInstance(module android.Module) bool {
+	_, ok := module.(*SystemServerClasspathModule)
+	return ok
+}
+
+func (s *systemServerClasspathFragmentMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_systemserverclasspath_fragment")
+}
+
+func (s *systemServerClasspathFragmentMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	return &systemServerClasspathFragmentSdkMemberProperties{}
+}
+
+type systemServerClasspathFragmentSdkMemberProperties struct {
+	android.SdkMemberPropertiesBase
+
+	// List of system_server classpath jars, could be either java_library, or java_sdk_library.
+	//
+	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
+	Contents []string
+
+	// List of jars that system_server loads dynamically using separate classloaders.
+	//
+	// The order does not matter.
+	Standalone_contents []string
+}
+
+func (s *systemServerClasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
+	module := variant.(*SystemServerClasspathModule)
+
+	s.Contents = module.properties.Contents
+	s.Standalone_contents = module.properties.Standalone_contents
+}
+
+func (s *systemServerClasspathFragmentSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
+	builder := ctx.SnapshotBuilder()
+	requiredMemberDependency := builder.SdkMemberReferencePropertyTag(true)
+
+	if len(s.Contents) > 0 {
+		propertySet.AddPropertyWithTag("contents", s.Contents, requiredMemberDependency)
+	}
+
+	if len(s.Standalone_contents) > 0 {
+		propertySet.AddPropertyWithTag("standalone_contents", s.Standalone_contents, requiredMemberDependency)
+	}
+}
+
+var _ android.SdkMemberType = (*systemServerClasspathFragmentMemberType)(nil)
+
+// A prebuilt version of the systemserverclasspath_fragment module.
+type prebuiltSystemServerClasspathModule struct {
+	SystemServerClasspathModule
+	prebuilt android.Prebuilt
+}
+
+func (module *prebuiltSystemServerClasspathModule) Prebuilt() *android.Prebuilt {
+	return &module.prebuilt
+}
+
+func (module *prebuiltSystemServerClasspathModule) Name() string {
+	return module.prebuilt.Name(module.ModuleBase.Name())
+}
+
+func (module *prebuiltSystemServerClasspathModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
+	return nil
+}
+
+var _ android.RequiredFilesFromPrebuiltApex = (*prebuiltSystemServerClasspathModule)(nil)
+
+func prebuiltSystemServerClasspathModuleFactory() android.Module {
+	m := &prebuiltSystemServerClasspathModule{}
+	m.AddProperties(&m.properties)
+	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
+	// array.
+	android.InitPrebuiltModule(m, &[]string{"placeholder"})
+	android.InitApexModule(m)
+	android.InitSdkAwareModule(m)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
diff --git a/java/systemserver_classpath_fragment_test.go b/java/systemserver_classpath_fragment_test.go
index 9ad50dd..ba328e7 100644
--- a/java/systemserver_classpath_fragment_test.go
+++ b/java/systemserver_classpath_fragment_test.go
@@ -99,7 +99,7 @@
 func TestSystemServerClasspathFragmentWithoutContents(t *testing.T) {
 	prepareForTestWithSystemServerClasspath.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qempty contents are not allowed\E`)).
+			`\QEither contents or standalone_contents needs to be non-empty\E`)).
 		RunTestWithBp(t, `
 			systemserverclasspath_fragment {
 				name: "systemserverclasspath-fragment",
diff --git a/java/testing.go b/java/testing.go
index 3ef51bd..511cc5d 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -54,6 +54,8 @@
 		"build/soong/java/lint_defaults.txt": nil,
 		// Needed for apps that do not provide their own.
 		"build/make/target/product/security": nil,
+		// Required to generate Java used-by API coverage
+		"build/soong/scripts/gen_java_usedby_apex.sh": nil,
 	}.AddToFixture(),
 )
 
@@ -70,6 +72,10 @@
 		defaultJavaDir + "/framework/aidl": nil,
 		// Needed for various deps defined in GatherRequiredDepsForTest()
 		defaultJavaDir + "/a.java": nil,
+
+		// Needed for R8 rules on apps
+		"build/make/core/proguard.flags":             nil,
+		"build/make/core/proguard_basic_keeps.flags": nil,
 	}.AddToFixture(),
 	// The java default module definitions.
 	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", gatherRequiredDepsForTest()),
@@ -146,6 +152,10 @@
 // This defines a file in the mock file system in a predefined location (prebuilts/sdk/Android.bp)
 // and so only one instance of this can be used in each fixture.
 func FixtureWithPrebuiltApis(release2Modules map[string][]string) android.FixturePreparer {
+	return FixtureWithPrebuiltApisAndExtensions(release2Modules, nil)
+}
+
+func FixtureWithPrebuiltApisAndExtensions(apiLevel2Modules map[string][]string, extensionLevel2Modules map[string][]string) android.FixturePreparer {
 	mockFS := android.MockFS{}
 	path := "prebuilts/sdk/Android.bp"
 
@@ -153,14 +163,19 @@
 			prebuilt_apis {
 				name: "sdk",
 				api_dirs: ["%s"],
+				extensions_dir: "extensions",
 				imports_sdk_version: "none",
 				imports_compile_dex: true,
 			}
-		`, strings.Join(android.SortedStringKeys(release2Modules), `", "`))
+		`, strings.Join(android.SortedStringKeys(apiLevel2Modules), `", "`))
 
-	for release, modules := range release2Modules {
-		libs := append([]string{"android", "core-for-system-modules"}, modules...)
-		mockFS.Merge(prebuiltApisFilesForLibs([]string{release}, libs))
+	for release, modules := range apiLevel2Modules {
+		mockFS.Merge(prebuiltApisFilesForModules([]string{release}, modules))
+	}
+	if extensionLevel2Modules != nil {
+		for release, modules := range extensionLevel2Modules {
+			mockFS.Merge(prebuiltExtensionApiFiles([]string{release}, modules))
+		}
 	}
 	return android.GroupFixturePreparers(
 		android.FixtureAddTextFile(path, bp),
@@ -168,24 +183,50 @@
 	)
 }
 
-func prebuiltApisFilesForLibs(apiLevels []string, sdkLibs []string) map[string][]byte {
+func prebuiltApisFilesForModules(apiLevels []string, modules []string) map[string][]byte {
+	libs := append([]string{"android"}, modules...)
+
 	fs := make(map[string][]byte)
 	for _, level := range apiLevels {
-		for _, lib := range sdkLibs {
-			for _, scope := range []string{"public", "system", "module-lib", "system-server", "test"} {
-				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/%s.jar", level, scope, lib)] = nil
+		apiLevel := android.ApiLevelForTest(level)
+		for _, sdkKind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkModule, android.SdkSystemServer, android.SdkTest} {
+			// A core-for-system-modules file must only be created for the sdk kind that supports it.
+			if sdkKind == systemModuleKind(sdkKind, apiLevel) {
+				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/core-for-system-modules.jar", level, sdkKind)] = nil
+			}
+
+			for _, lib := range libs {
+				// Create a jar file for every library.
+				fs[fmt.Sprintf("prebuilts/sdk/%s/%s/%s.jar", level, sdkKind, lib)] = nil
+
 				// No finalized API files for "current"
 				if level != "current" {
-					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s.txt", level, scope, lib)] = nil
-					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s-removed.txt", level, scope, lib)] = nil
+					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s.txt", level, sdkKind, lib)] = nil
+					fs[fmt.Sprintf("prebuilts/sdk/%s/%s/api/%s-removed.txt", level, sdkKind, lib)] = nil
 				}
 			}
 		}
+		if level == "current" {
+			fs["prebuilts/sdk/current/core/android.jar"] = nil
+		}
 		fs[fmt.Sprintf("prebuilts/sdk/%s/public/framework.aidl", level)] = nil
 	}
 	return fs
 }
 
+func prebuiltExtensionApiFiles(extensionLevels []string, modules []string) map[string][]byte {
+	fs := make(map[string][]byte)
+	for _, level := range extensionLevels {
+		for _, sdkKind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkModule, android.SdkSystemServer} {
+			for _, lib := range modules {
+				fs[fmt.Sprintf("prebuilts/sdk/extensions/%s/%s/api/%s.txt", level, sdkKind, lib)] = nil
+				fs[fmt.Sprintf("prebuilts/sdk/extensions/%s/%s/api/%s-removed.txt", level, sdkKind, lib)] = nil
+			}
+		}
+	}
+	return fs
+}
+
 // FixtureConfigureBootJars configures the boot jars in both the dexpreopt.GlobalConfig and
 // Config.productVariables structs. As a side effect that enables dexpreopt.
 func FixtureConfigureBootJars(bootJars ...string) android.FixturePreparer {
@@ -214,21 +255,41 @@
 	)
 }
 
-// FixtureConfigureUpdatableBootJars configures the updatable boot jars in both the
+// FixtureConfigureApexBootJars configures the apex boot jars in both the
 // dexpreopt.GlobalConfig and Config.productVariables structs. As a side effect that enables
 // dexpreopt.
-func FixtureConfigureUpdatableBootJars(bootJars ...string) android.FixturePreparer {
+func FixtureConfigureApexBootJars(bootJars ...string) android.FixturePreparer {
 	return android.GroupFixturePreparers(
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.UpdatableBootJars = android.CreateTestConfiguredJarList(bootJars)
+			variables.ApexBootJars = android.CreateTestConfiguredJarList(bootJars)
 		}),
-		dexpreopt.FixtureSetUpdatableBootJars(bootJars...),
+		dexpreopt.FixtureSetApexBootJars(bootJars...),
 
 		// Add a fake dex2oatd module.
 		dexpreopt.PrepareForTestWithFakeDex2oatd,
 	)
 }
 
+// FixtureUseLegacyCorePlatformApi prepares the fixture by setting the exception list of those
+// modules that are allowed to use the legacy core platform API to be the ones supplied.
+func FixtureUseLegacyCorePlatformApi(moduleNames ...string) android.FixturePreparer {
+	lookup := make(map[string]struct{})
+	for _, moduleName := range moduleNames {
+		lookup[moduleName] = struct{}{}
+	}
+	return android.FixtureModifyConfig(func(config android.Config) {
+		// Try and set the legacyCorePlatformApiLookup in the config, the returned value will be the
+		// actual value that is set.
+		cached := config.Once(legacyCorePlatformApiLookupKey, func() interface{} {
+			return lookup
+		})
+		// Make sure that the cached value is the one we need.
+		if !reflect.DeepEqual(cached, lookup) {
+			panic(fmt.Errorf("attempting to set legacyCorePlatformApiLookupKey to %q but it has already been set to %q", lookup, cached))
+		}
+	})
+}
+
 // registerRequiredBuildComponentsForTest registers the build components used by
 // PrepareForTestWithJavaDefaultModules.
 //
@@ -280,6 +341,7 @@
 		"kotlin-stdlib-jdk7",
 		"kotlin-stdlib-jdk8",
 		"kotlin-annotations",
+		"stub-annotations",
 	}
 
 	for _, extra := range extraModules {
@@ -311,7 +373,7 @@
 		}`
 
 	systemModules := []string{
-		"core-current-stubs-system-modules",
+		"core-public-stubs-system-modules",
 		"core-module-lib-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
 		"stable-core-platform-api-stubs-system-modules",
@@ -364,6 +426,17 @@
 	android.AssertDeepEquals(t, fmt.Sprintf("%s modules", "platform-bootclasspath"), expected, pairs)
 }
 
+func CheckClasspathFragmentProtoContentInfoProvider(t *testing.T, result *android.TestResult, generated bool, contents, outputFilename, installDir string) {
+	t.Helper()
+	p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
+	info := result.ModuleProvider(p, ClasspathFragmentProtoContentInfoProvider).(ClasspathFragmentProtoContentInfo)
+
+	android.AssertBoolEquals(t, "classpath proto generated", generated, info.ClasspathFragmentProtoGenerated)
+	android.AssertStringEquals(t, "classpath proto contents", contents, info.ClasspathFragmentProtoContents.String())
+	android.AssertStringEquals(t, "output filepath", outputFilename, info.ClasspathFragmentProtoOutput.Base())
+	android.AssertPathRelativeToTopEquals(t, "install filepath", installDir, info.ClasspathFragmentProtoInstallDir)
+}
+
 // ApexNamePairsFromModules returns the apex:module pair for the supplied modules.
 func ApexNamePairsFromModules(ctx *android.TestContext, modules []android.Module) []string {
 	pairs := []string{}
@@ -420,3 +493,61 @@
 	output := sourceGlobalCompatConfig.Output(allOutputs[0])
 	android.AssertPathsRelativeToTopEquals(t, message+": inputs", expectedPaths, output.Implicits)
 }
+
+// Register the fake APEX mutator to `android.InitRegistrationContext` as if the real mutator exists
+// at runtime. This must be called in `init()` of a test if the test is going to use the fake APEX
+// mutator. Otherwise, we will be missing the runtime mutator because "soong-apex" is not a
+// dependency, which will cause an inconsistency between testing and runtime mutators.
+func RegisterFakeRuntimeApexMutator() {
+	registerFakeApexMutator(android.InitRegistrationContext)
+}
+
+var PrepareForTestWithFakeApexMutator = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(registerFakeApexMutator),
+)
+
+func registerFakeApexMutator(ctx android.RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("apex", fakeApexMutator).Parallel()
+	})
+}
+
+type apexModuleBase interface {
+	ApexAvailable() []string
+}
+
+var _ apexModuleBase = (*Library)(nil)
+var _ apexModuleBase = (*SdkLibrary)(nil)
+
+// A fake APEX mutator that creates a platform variant and an APEX variant for modules with
+// `apex_available`. It helps us avoid a dependency on the real mutator defined in "soong-apex",
+// which will cause a cyclic dependency, and it provides an easy way to create an APEX variant for
+// testing without dealing with all the complexities in the real mutator.
+func fakeApexMutator(mctx android.BottomUpMutatorContext) {
+	switch mctx.Module().(type) {
+	case *Library, *SdkLibrary:
+		if len(mctx.Module().(apexModuleBase).ApexAvailable()) > 0 {
+			modules := mctx.CreateVariations("", "apex1000")
+			apexInfo := android.ApexInfo{
+				ApexVariationName: "apex1000",
+			}
+			mctx.SetVariationProvider(modules[1], android.ApexInfoProvider, apexInfo)
+		}
+	}
+}
+
+// Applies the given modifier on the boot image config with the given name.
+func FixtureModifyBootImageConfig(name string, configModifier func(*bootImageConfig)) android.FixturePreparer {
+	return android.FixtureModifyConfig(func(androidConfig android.Config) {
+		pathCtx := android.PathContextForTesting(androidConfig)
+		config := genBootImageConfigRaw(pathCtx)
+		configModifier(config[name])
+	})
+}
+
+// Sets the value of `installDirOnDevice` of the boot image config with the given name.
+func FixtureSetBootImageInstallDirOnDevice(name string, installDir string) android.FixturePreparer {
+	return FixtureModifyBootImageConfig(name, func(config *bootImageConfig) {
+		config.installDirOnDevice = installDir
+	})
+}
diff --git a/licenses/Android.bp b/licenses/Android.bp
index a983b5b..8db001f 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -18,6 +18,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+filegroup {
+    name: "Apache-2.0-License-Text",
+    srcs: ["LICENSE"],
+}
+
 license {
     name: "Android-Apache-2.0",
     package_name: "Android",
@@ -492,36 +497,36 @@
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-1.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-1.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-2.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-2.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-2.5",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-2.5.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-3.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-3.0.html",
 }
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-ND-4.0",
-    conditions: ["restricted"],
+    conditions: ["by_exception_only"],
     url: "https://spdx.org/licenses/CC-BY-ND-4.0.html",
 }
 
@@ -562,7 +567,10 @@
 
 license_kind {
     name: "SPDX-license-identifier-CC-BY-SA-ND",
-    conditions: ["restricted"],
+    conditions: [
+        "restricted",
+        "by_exception_only",
+    ],
 }
 
 license_kind {
diff --git a/licenses/LICENSE b/licenses/LICENSE
index dae0406..d645695 100644
--- a/licenses/LICENSE
+++ b/licenses/LICENSE
@@ -1,16 +1,4 @@
 
-   Copyright (c) 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.
-
-   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.
-
-
                                  Apache License
                            Version 2.0, January 2004
                         http://www.apache.org/licenses/
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 8d0ad7c..003b275 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -155,10 +155,9 @@
 		OutputFile: android.OptionalPathForPath(l.outputFilePath),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
+				entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
-				entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
 			},
 		},
 	}}
diff --git a/linkerconfig/proto/linker_config.proto b/linkerconfig/proto/linker_config.proto
index fec66c8..dccf311 100644
--- a/linkerconfig/proto/linker_config.proto
+++ b/linkerconfig/proto/linker_config.proto
@@ -34,4 +34,16 @@
 
   // Required libs from the module
   repeated string requireLibs = 4;
+
+  message Contribution {
+    // Target namespace where this module contributes the search paths.
+    string namespace = 1;
+    // Search paths (inc. permitted paths) that this module contributes.
+    // Paths should be related to the current module and can use "${LIB}" variable which is
+    // expanded to "lib" or "lib64".
+    // e.g. ${LIB}/subdir
+    repeated string paths = 2;
+  }
+  // APEX can contribute search paths to specified namespaces.
+  repeated Contribution contributions = 5;
 }
diff --git a/mk2rbc/Android.bp b/mk2rbc/Android.bp
new file mode 100644
index 0000000..4fa3eb6
--- /dev/null
+++ b/mk2rbc/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "mk2rbc",
+    srcs: ["cmd/mk2rbc.go"],
+    deps: [
+        "mk2rbc-lib",
+        "androidmk-parser",
+    ],
+}
+
+bootstrap_go_package {
+    name: "mk2rbc-lib",
+    pkgPath: "android/soong/mk2rbc",
+    srcs: [
+        "android_products.go",
+        "config_variables.go",
+        "expr.go",
+        "mk2rbc.go",
+        "node.go",
+        "soong_variables.go",
+        "types.go",
+        "variable.go",
+    ],
+    deps: ["androidmk-parser"],
+}
diff --git a/mk2rbc/TODO b/mk2rbc/TODO
new file mode 100644
index 0000000..731deb6
--- /dev/null
+++ b/mk2rbc/TODO
@@ -0,0 +1,14 @@
+* Checking filter/filter-out results is incorrect if pattern contains '%'
+* Need heuristics to recognize that a variable is local. Propose to use lowercase.
+* Need heuristics for the local variable type. Propose '_list' suffix
+* Internal source tree has variables in the inherit-product macro argument. Handle it
+* Enumerate all environment variables that configuration files use.
+* Break mk2rbc.go into multiple files.
+* If variable's type is not yet known, try to divine it from the value assigned to it
+  (it may be a variable of the known type, or a function result)
+* ifneq (,$(VAR)) should translate to
+    if getattr(<>, "VAR", <default>):
+* Launcher file needs to have same suffix as the rest of the generated files
+* Implement $(shell) function
+* Write execution tests
+* Review all TODOs in mk2rbc.go
\ No newline at end of file
diff --git a/mk2rbc/android_products.go b/mk2rbc/android_products.go
new file mode 100644
index 0000000..de38391
--- /dev/null
+++ b/mk2rbc/android_products.go
@@ -0,0 +1,110 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	mkparser "android/soong/androidmk/parser"
+)
+
+// Implements mkparser.Scope, to be used by mkparser.Value.Value()
+type localDirEval struct {
+	localDir  string
+	hasErrors bool
+}
+
+func (l *localDirEval) Get(name string) string {
+	if name == "LOCAL_DIR" {
+		return l.localDir
+	}
+	l.hasErrors = true
+	return fmt.Sprintf("$(%s)", name)
+}
+
+func (l *localDirEval) Set(_, _ string) {
+}
+
+func (l *localDirEval) Call(_ string, _ []string) []string {
+	l.hasErrors = true
+	return []string{"$(call ...)"}
+}
+
+func (l *localDirEval) SetFunc(_ string, _ func([]string) []string) {
+}
+
+// UpdateProductConfigMap builds product configuration map.
+// The product configuration map maps a product name (i.e., the value of the
+// TARGET_PRODUCT variable) to the top-level configuration file.
+// In the Android's Make-based build machinery, the equivalent of the
+// product configuration map is $(PRODUCT_MAKEFILES), which is the list
+// of <product>:<configuration makefile> pairs (if <product>: is missing,
+// <product> is the basename of the configuration makefile).
+// UpdateProductConfigMap emulates this build logic by processing the
+// assignments to PRODUCT_MAKEFILES in the file passed to it.
+func UpdateProductConfigMap(configMap map[string]string, configMakefile string) error {
+	contents, err := ioutil.ReadFile(configMakefile)
+	if err != nil {
+		return err
+	}
+	parser := mkparser.NewParser(configMakefile, bytes.NewBuffer(contents))
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		for _, e := range errs {
+			fmt.Fprintln(os.Stderr, "ERROR:", e)
+		}
+		return fmt.Errorf("cannot parse %s", configMakefile)
+	}
+
+	ldEval := &localDirEval{localDir: filepath.Dir(configMakefile)}
+
+	for _, node := range nodes {
+		// We are interested in assignments to 'PRODUCT_MAKEFILES'
+		asgn, ok := node.(*mkparser.Assignment)
+		if !ok {
+			continue
+		}
+		if !(asgn.Name.Const() && asgn.Name.Strings[0] == "PRODUCT_MAKEFILES") {
+			continue
+		}
+
+		// Resolve the references to $(LOCAL_DIR) in $(PRODUCT_MAKEFILES).
+		ldEval.hasErrors = false
+		value := asgn.Value.Value(ldEval)
+		if ldEval.hasErrors {
+			return fmt.Errorf("cannot evaluate %s", asgn.Value.Dump())
+		}
+		// Each item is either <product>:<configuration makefile>, or
+		// just <configuration makefile>
+		for _, token := range strings.Fields(value) {
+			var product, config_path string
+			if n := strings.Index(token, ":"); n >= 0 {
+				product = token[0:n]
+				config_path = token[n+1:]
+			} else {
+				config_path = token
+				product = filepath.Base(config_path)
+				product = strings.TrimSuffix(product, filepath.Ext(product))
+			}
+			configMap[product] = config_path
+		}
+	}
+	return nil
+}
diff --git a/mk2rbc/android_products_test.go b/mk2rbc/android_products_test.go
new file mode 100644
index 0000000..f8c930a
--- /dev/null
+++ b/mk2rbc/android_products_test.go
@@ -0,0 +1,38 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"path/filepath"
+	"reflect"
+	"testing"
+)
+
+func TestProductsMakefile(t *testing.T) {
+	testDir := getTestDirectory()
+	abspath := func(relPath string) string { return filepath.Join(testDir, relPath) }
+	actualProducts := make(map[string]string)
+	if err := UpdateProductConfigMap(actualProducts, abspath("android_products.mk.test")); err != nil {
+		t.Fatal(err)
+	}
+	expectedProducts := map[string]string{
+		"aosp_cf_x86_tv": abspath("vsoc_x86/tv/device.mk"),
+		"aosp_tv_arm":    abspath("aosp_tv_arm.mk"),
+		"aosp_tv_arm64":  abspath("aosp_tv_arm64.mk"),
+	}
+	if !reflect.DeepEqual(actualProducts, expectedProducts) {
+		t.Errorf("\nExpected: %v\n  Actual: %v", expectedProducts, actualProducts)
+	}
+}
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
new file mode 100644
index 0000000..e84eacd
--- /dev/null
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -0,0 +1,617 @@
+// Copyright 2021 Google LLC
+//
+// 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.
+
+// The application to convert product configuration makefiles to Starlark.
+// Converts either given list of files (and optionally the dependent files
+// of the same kind), or all all product configuration makefiles in the
+// given source tree.
+// Previous version of a converted file can be backed up.
+// Optionally prints detailed statistics at the end.
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"runtime/debug"
+	"runtime/pprof"
+	"sort"
+	"strings"
+	"time"
+
+	"android/soong/androidmk/parser"
+	"android/soong/mk2rbc"
+)
+
+var (
+	// TODO(asmundak): remove this option once there is a consensus on suffix
+	suffix   = flag.String("suffix", ".rbc", "generated files' suffix")
+	dryRun   = flag.Bool("dry_run", false, "dry run")
+	recurse  = flag.Bool("convert_dependents", false, "convert all dependent files")
+	mode     = flag.String("mode", "", `"backup" to back up existing files, "write" to overwrite them`)
+	errstat  = flag.Bool("error_stat", false, "print error statistics")
+	traceVar = flag.String("trace", "", "comma-separated list of variables to trace")
+	// TODO(asmundak): this option is for debugging
+	allInSource           = flag.Bool("all", false, "convert all product config makefiles in the tree under //")
+	outputTop             = flag.String("outdir", "", "write output files into this directory hierarchy")
+	launcher              = flag.String("launcher", "", "generated launcher path.")
+	boardlauncher         = flag.String("boardlauncher", "", "generated board configuration launcher path.")
+	printProductConfigMap = flag.Bool("print_product_config_map", false, "print product config map and exit")
+	cpuProfile            = flag.String("cpu_profile", "", "write cpu profile to file")
+	traceCalls            = flag.Bool("trace_calls", false, "trace function calls")
+	inputVariables        = flag.String("input_variables", "", "starlark file containing product config and global variables")
+	makefileList          = flag.String("makefile_list", "", "path to a list of all makefiles in the source tree, generated by soong's finder. If not provided, mk2rbc will find the makefiles itself (more slowly than if this flag was provided)")
+)
+
+func init() {
+	// Simplistic flag aliasing: works, but the usage string is ugly and
+	// both flag and its alias can be present on the command line
+	flagAlias := func(target string, alias string) {
+		if f := flag.Lookup(target); f != nil {
+			flag.Var(f.Value, alias, "alias for --"+f.Name)
+			return
+		}
+		quit("cannot alias unknown flag " + target)
+	}
+	flagAlias("suffix", "s")
+	flagAlias("dry_run", "n")
+	flagAlias("convert_dependents", "r")
+	flagAlias("error_stat", "e")
+}
+
+var backupSuffix string
+var tracedVariables []string
+var errorLogger = errorSink{data: make(map[string]datum)}
+var makefileFinder mk2rbc.MakefileFinder
+
+func main() {
+	flag.Usage = func() {
+		cmd := filepath.Base(os.Args[0])
+		fmt.Fprintf(flag.CommandLine.Output(),
+			"Usage: %[1]s flags file...\n", cmd)
+		flag.PrintDefaults()
+	}
+	flag.Parse()
+
+	if _, err := os.Stat("build/soong/mk2rbc"); err != nil {
+		quit("Must be run from the root of the android tree. (build/soong/mk2rbc does not exist)")
+	}
+
+	// Delouse
+	if *suffix == ".mk" {
+		quit("cannot use .mk as generated file suffix")
+	}
+	if *suffix == "" {
+		quit("suffix cannot be empty")
+	}
+	if *outputTop != "" {
+		if err := os.MkdirAll(*outputTop, os.ModeDir+os.ModePerm); err != nil {
+			quit(err)
+		}
+		s, err := filepath.Abs(*outputTop)
+		if err != nil {
+			quit(err)
+		}
+		*outputTop = s
+	}
+	if *allInSource && len(flag.Args()) > 0 {
+		quit("file list cannot be specified when -all is present")
+	}
+	if *allInSource && *launcher != "" {
+		quit("--all and --launcher are mutually exclusive")
+	}
+
+	// Flag-driven adjustments
+	if (*suffix)[0] != '.' {
+		*suffix = "." + *suffix
+	}
+	if *mode == "backup" {
+		backupSuffix = time.Now().Format("20060102150405")
+	}
+	if *traceVar != "" {
+		tracedVariables = strings.Split(*traceVar, ",")
+	}
+
+	if *cpuProfile != "" {
+		f, err := os.Create(*cpuProfile)
+		if err != nil {
+			quit(err)
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	if *makefileList != "" {
+		makefileFinder = &FileListMakefileFinder{
+			cachedMakefiles: nil,
+			filePath:        *makefileList,
+		}
+	} else {
+		makefileFinder = &FindCommandMakefileFinder{}
+	}
+
+	// Find out global variables
+	getConfigVariables()
+	getSoongVariables()
+
+	if *printProductConfigMap {
+		productConfigMap := buildProductConfigMap()
+		var products []string
+		for p := range productConfigMap {
+			products = append(products, p)
+		}
+		sort.Strings(products)
+		for _, p := range products {
+			fmt.Println(p, productConfigMap[p])
+		}
+		os.Exit(0)
+	}
+
+	// Convert!
+	files := flag.Args()
+	if *allInSource {
+		productConfigMap := buildProductConfigMap()
+		for _, path := range productConfigMap {
+			files = append(files, path)
+		}
+	}
+	ok := true
+	for _, mkFile := range files {
+		ok = convertOne(mkFile) && ok
+	}
+
+	if *launcher != "" {
+		if len(files) != 1 {
+			quit(fmt.Errorf("a launcher can be generated only for a single product"))
+		}
+		if *inputVariables == "" {
+			quit(fmt.Errorf("the product launcher requires an input variables file"))
+		}
+		if !convertOne(*inputVariables) {
+			quit(fmt.Errorf("the product launcher input variables file failed to convert"))
+		}
+
+		err := writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(files[0]), outputFilePath(*inputVariables),
+			mk2rbc.MakePath2ModuleName(files[0])))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
+			ok = false
+		}
+	}
+	if *boardlauncher != "" {
+		if len(files) != 1 {
+			quit(fmt.Errorf("a launcher can be generated only for a single product"))
+		}
+		if *inputVariables == "" {
+			quit(fmt.Errorf("the board launcher requires an input variables file"))
+		}
+		if !convertOne(*inputVariables) {
+			quit(fmt.Errorf("the board launcher input variables file failed to convert"))
+		}
+		err := writeGenerated(*boardlauncher, mk2rbc.BoardLauncher(
+			outputFilePath(files[0]), outputFilePath(*inputVariables)))
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "%s: %s", files[0], err)
+			ok = false
+		}
+	}
+
+	if *errstat {
+		errorLogger.printStatistics()
+		printStats()
+	}
+	if !ok {
+		os.Exit(1)
+	}
+}
+
+func quit(s interface{}) {
+	fmt.Fprintln(os.Stderr, s)
+	os.Exit(2)
+}
+
+func buildProductConfigMap() map[string]string {
+	const androidProductsMk = "AndroidProducts.mk"
+	// Build the list of AndroidProducts.mk files: it's
+	// build/make/target/product/AndroidProducts.mk + device/**/AndroidProducts.mk plus + vendor/**/AndroidProducts.mk
+	targetAndroidProductsFile := filepath.Join("build", "make", "target", "product", androidProductsMk)
+	if _, err := os.Stat(targetAndroidProductsFile); err != nil {
+		fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
+	}
+	productConfigMap := make(map[string]string)
+	if err := mk2rbc.UpdateProductConfigMap(productConfigMap, targetAndroidProductsFile); err != nil {
+		fmt.Fprintf(os.Stderr, "%s: %s\n", targetAndroidProductsFile, err)
+	}
+	for _, t := range []string{"device", "vendor"} {
+		_ = filepath.WalkDir(t,
+			func(path string, d os.DirEntry, err error) error {
+				if err != nil || d.IsDir() || filepath.Base(path) != androidProductsMk {
+					return nil
+				}
+				if err2 := mk2rbc.UpdateProductConfigMap(productConfigMap, path); err2 != nil {
+					fmt.Fprintf(os.Stderr, "%s: %s\n", path, err)
+					// Keep going, we want to find all such errors in a single run
+				}
+				return nil
+			})
+	}
+	return productConfigMap
+}
+
+func getConfigVariables() {
+	path := filepath.Join("build", "make", "core", "product.mk")
+	if err := mk2rbc.FindConfigVariables(path, mk2rbc.KnownVariables); err != nil {
+		quit(err)
+	}
+}
+
+// Implements mkparser.Scope, to be used by mkparser.Value.Value()
+type fileNameScope struct {
+	mk2rbc.ScopeBase
+}
+
+func (s fileNameScope) Get(name string) string {
+	if name != "BUILD_SYSTEM" {
+		return fmt.Sprintf("$(%s)", name)
+	}
+	return filepath.Join("build", "make", "core")
+}
+
+func getSoongVariables() {
+	path := filepath.Join("build", "make", "core", "soong_config.mk")
+	err := mk2rbc.FindSoongVariables(path, fileNameScope{}, mk2rbc.KnownVariables)
+	if err != nil {
+		quit(err)
+	}
+}
+
+var converted = make(map[string]*mk2rbc.StarlarkScript)
+
+//goland:noinspection RegExpRepeatedSpace
+var cpNormalizer = regexp.MustCompile(
+	"#  Copyright \\(C\\) 20.. The Android Open Source Project")
+
+const cpNormalizedCopyright = "#  Copyright (C) 20xx The Android Open Source Project"
+const copyright = `#
+#  Copyright (C) 20xx 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.
+#
+`
+
+// Convert a single file.
+// Write the result either to the same directory, to the same place in
+// 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) {
+	if v, ok := converted[mkFile]; ok {
+		return v != nil
+	}
+	converted[mkFile] = nil
+	defer func() {
+		if r := recover(); r != nil {
+			ok = false
+			fmt.Fprintf(os.Stderr, "%s: panic while converting: %s\n%s\n", mkFile, r, debug.Stack())
+		}
+	}()
+
+	mk2starRequest := mk2rbc.Request{
+		MkFile:          mkFile,
+		Reader:          nil,
+		OutputDir:       *outputTop,
+		OutputSuffix:    *suffix,
+		TracedVariables: tracedVariables,
+		TraceCalls:      *traceCalls,
+		SourceFS:        os.DirFS("."),
+		MakefileFinder:  makefileFinder,
+		ErrorLogger:     errorLogger,
+	}
+	ss, err := mk2rbc.Convert(mk2starRequest)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, mkFile, ": ", err)
+		return false
+	}
+	script := ss.String()
+	outputPath := outputFilePath(mkFile)
+
+	if *dryRun {
+		fmt.Printf("==== %s ====\n", outputPath)
+		// Print generated script after removing the copyright header
+		outText := cpNormalizer.ReplaceAllString(script, cpNormalizedCopyright)
+		fmt.Println(strings.TrimPrefix(outText, copyright))
+	} else {
+		if err := maybeBackup(outputPath); err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			return false
+		}
+		if err := writeGenerated(outputPath, script); err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			return false
+		}
+	}
+	ok = true
+	if *recurse {
+		for _, sub := range ss.SubConfigFiles() {
+			// File may be absent if it is a conditional load
+			if _, err := os.Stat(sub); os.IsNotExist(err) {
+				continue
+			}
+			ok = convertOne(sub) && ok
+		}
+	}
+	converted[mkFile] = ss
+	return ok
+}
+
+// Optionally saves the previous version of the generated file
+func maybeBackup(filename string) error {
+	stat, err := os.Stat(filename)
+	if os.IsNotExist(err) {
+		return nil
+	}
+	if !stat.Mode().IsRegular() {
+		return fmt.Errorf("%s exists and is not a regular file", filename)
+	}
+	switch *mode {
+	case "backup":
+		return os.Rename(filename, filename+backupSuffix)
+	case "write":
+		return os.Remove(filename)
+	default:
+		return fmt.Errorf("%s already exists, use --mode option", filename)
+	}
+}
+
+func outputFilePath(mkFile string) string {
+	path := strings.TrimSuffix(mkFile, filepath.Ext(mkFile)) + *suffix
+	if *outputTop != "" {
+		path = filepath.Join(*outputTop, path)
+	}
+	return path
+}
+
+func writeGenerated(path string, contents string) error {
+	if err := os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm); err != nil {
+		return err
+	}
+	if err := ioutil.WriteFile(path, []byte(contents), 0644); err != nil {
+		return err
+	}
+	return nil
+}
+
+func printStats() {
+	var sortedFiles []string
+	for p := range converted {
+		sortedFiles = append(sortedFiles, p)
+	}
+	sort.Strings(sortedFiles)
+
+	nOk, nPartial, nFailed := 0, 0, 0
+	for _, f := range sortedFiles {
+		if converted[f] == nil {
+			nFailed++
+		} else if converted[f].HasErrors() {
+			nPartial++
+		} else {
+			nOk++
+		}
+	}
+	if nPartial > 0 {
+		fmt.Fprintf(os.Stderr, "Conversion was partially successful for:\n")
+		for _, f := range sortedFiles {
+			if ss := converted[f]; ss != nil && ss.HasErrors() {
+				fmt.Fprintln(os.Stderr, "  ", f)
+			}
+		}
+	}
+
+	if nFailed > 0 {
+		fmt.Fprintf(os.Stderr, "Conversion failed for files:\n")
+		for _, f := range sortedFiles {
+			if converted[f] == nil {
+				fmt.Fprintln(os.Stderr, "  ", f)
+			}
+		}
+	}
+}
+
+type datum struct {
+	count          int
+	formattingArgs []string
+}
+
+type errorSink struct {
+	data map[string]datum
+}
+
+func (ebt errorSink) NewError(el mk2rbc.ErrorLocation, node parser.Node, message string, args ...interface{}) {
+	fmt.Fprint(os.Stderr, el, ": ")
+	fmt.Fprintf(os.Stderr, message, args...)
+	fmt.Fprintln(os.Stderr)
+	if !*errstat {
+		return
+	}
+
+	v, exists := ebt.data[message]
+	if exists {
+		v.count++
+	} else {
+		v = datum{1, nil}
+	}
+	if strings.Contains(message, "%s") {
+		var newArg1 string
+		if len(args) == 0 {
+			panic(fmt.Errorf(`%s has %%s but args are missing`, message))
+		}
+		newArg1 = fmt.Sprint(args[0])
+		if message == "unsupported line" {
+			newArg1 = node.Dump()
+		} else if message == "unsupported directive %s" {
+			if newArg1 == "include" || newArg1 == "-include" {
+				newArg1 = node.Dump()
+			}
+		}
+		v.formattingArgs = append(v.formattingArgs, newArg1)
+	}
+	ebt.data[message] = v
+}
+
+func (ebt errorSink) printStatistics() {
+	if len(ebt.data) > 0 {
+		fmt.Fprintln(os.Stderr, "Error counts:")
+	}
+	for message, data := range ebt.data {
+		if len(data.formattingArgs) == 0 {
+			fmt.Fprintf(os.Stderr, "%4d %s\n", data.count, message)
+			continue
+		}
+		itemsByFreq, count := stringsWithFreq(data.formattingArgs, 30)
+		fmt.Fprintf(os.Stderr, "%4d %s [%d unique items]:\n", data.count, message, count)
+		fmt.Fprintln(os.Stderr, "      ", itemsByFreq)
+	}
+}
+
+func stringsWithFreq(items []string, topN int) (string, int) {
+	freq := make(map[string]int)
+	for _, item := range items {
+		freq[strings.TrimPrefix(strings.TrimSuffix(item, "]"), "[")]++
+	}
+	var sorted []string
+	for item := range freq {
+		sorted = append(sorted, item)
+	}
+	sort.Slice(sorted, func(i int, j int) bool {
+		return freq[sorted[i]] > freq[sorted[j]]
+	})
+	sep := ""
+	res := ""
+	for i, item := range sorted {
+		if i >= topN {
+			res += " ..."
+			break
+		}
+		count := freq[item]
+		if count > 1 {
+			res += fmt.Sprintf("%s%s(%d)", sep, item, count)
+		} else {
+			res += fmt.Sprintf("%s%s", sep, item)
+		}
+		sep = ", "
+	}
+	return res, len(sorted)
+}
+
+// FindCommandMakefileFinder is an implementation of mk2rbc.MakefileFinder that
+// runs the unix find command to find all the makefiles in the source tree.
+type FindCommandMakefileFinder struct {
+	cachedRoot      string
+	cachedMakefiles []string
+}
+
+func (l *FindCommandMakefileFinder) Find(root string) []string {
+	if l.cachedMakefiles != nil && l.cachedRoot == root {
+		return l.cachedMakefiles
+	}
+
+	// Return all *.mk files but not in hidden directories.
+
+	// NOTE(asmundak): as it turns out, even the WalkDir (which is an _optimized_ directory tree walker)
+	// is about twice slower than running `find` command (14s vs 6s on the internal Android source tree).
+	common_args := []string{"!", "-type", "d", "-name", "*.mk", "!", "-path", "*/.*/*"}
+	if root != "" {
+		common_args = append([]string{root}, common_args...)
+	}
+	cmd := exec.Command("/usr/bin/find", common_args...)
+	stdout, err := cmd.StdoutPipe()
+	if err == nil {
+		err = cmd.Start()
+	}
+	if err != nil {
+		panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
+	}
+	scanner := bufio.NewScanner(stdout)
+	result := make([]string, 0)
+	for scanner.Scan() {
+		result = append(result, strings.TrimPrefix(scanner.Text(), "./"))
+	}
+	stdout.Close()
+	err = scanner.Err()
+	if err != nil {
+		panic(fmt.Errorf("cannot get the output from %s: %s", cmd, err))
+	}
+	l.cachedRoot = root
+	l.cachedMakefiles = result
+	return l.cachedMakefiles
+}
+
+// FileListMakefileFinder is an implementation of mk2rbc.MakefileFinder that
+// reads a file containing the list of makefiles in the android source tree.
+// This file is generated by soong's finder, so that it can be computed while
+// soong is already walking the source tree looking for other files. If the root
+// to find makefiles under is not the root of the android source tree, it will
+// fall back to using FindCommandMakefileFinder.
+type FileListMakefileFinder struct {
+	FindCommandMakefileFinder
+	cachedMakefiles []string
+	filePath        string
+}
+
+func (l *FileListMakefileFinder) Find(root string) []string {
+	root, err1 := filepath.Abs(root)
+	wd, err2 := os.Getwd()
+	if root != wd || err1 != nil || err2 != nil {
+		return l.FindCommandMakefileFinder.Find(root)
+	}
+	if l.cachedMakefiles != nil {
+		return l.cachedMakefiles
+	}
+
+	file, err := os.Open(l.filePath)
+	if err != nil {
+		panic(fmt.Errorf("Cannot read makefile list: %s\n", err))
+	}
+	defer file.Close()
+
+	result := make([]string, 0)
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if len(line) > 0 {
+			result = append(result, line)
+		}
+	}
+
+	if err = scanner.Err(); err != nil {
+		panic(fmt.Errorf("Cannot read makefile list: %s\n", err))
+	}
+	l.cachedMakefiles = result
+	return l.cachedMakefiles
+}
diff --git a/mk2rbc/config_variables.go b/mk2rbc/config_variables.go
new file mode 100644
index 0000000..dac509c
--- /dev/null
+++ b/mk2rbc/config_variables.go
@@ -0,0 +1,67 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+
+	mkparser "android/soong/androidmk/parser"
+)
+
+// Extracts the list of product config variables from a file, calling
+// given registrar for each variable.
+func FindConfigVariables(mkFile string, vr variableRegistrar) error {
+	mkContents, err := ioutil.ReadFile(mkFile)
+	if err != nil {
+		return err
+	}
+	parser := mkparser.NewParser(mkFile, bytes.NewBuffer(mkContents))
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		for _, e := range errs {
+			fmt.Fprintln(os.Stderr, "ERROR:", e)
+		}
+		return fmt.Errorf("cannot parse %s", mkFile)
+	}
+	for _, node := range nodes {
+		asgn, ok := node.(*mkparser.Assignment)
+		if !ok {
+			continue
+		}
+		// We are looking for a variable called '_product_list_vars'
+		// or '_product_single_value_vars'.
+		if !asgn.Name.Const() {
+			continue
+		}
+		varName := asgn.Name.Strings[0]
+		var starType starlarkType
+		if varName == "_product_list_vars" {
+			starType = starlarkTypeList
+		} else if varName == "_product_single_value_vars" {
+			starType = starlarkTypeUnknown
+		} else {
+			continue
+		}
+		for _, name := range strings.Fields(asgn.Value.Dump()) {
+			vr.NewVariable(name, VarClassConfig, starType)
+		}
+
+	}
+	return nil
+}
diff --git a/mk2rbc/config_variables_test.go b/mk2rbc/config_variables_test.go
new file mode 100644
index 0000000..f5a5180
--- /dev/null
+++ b/mk2rbc/config_variables_test.go
@@ -0,0 +1,60 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"testing"
+)
+
+type testVar struct {
+	name string
+	cl   varClass
+	ty   starlarkType
+}
+
+type testVariables struct {
+	v []testVar
+}
+
+func (v *testVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
+	v.v = append(v.v, testVar{name, varClass, valueType})
+}
+
+// getTestDirectory returns the test directory, which should be the test/ subdirectory
+func getTestDirectory() string {
+	_, myFile, _, _ := runtime.Caller(1)
+	return filepath.Join(filepath.Dir(myFile), "test")
+}
+
+func TestConfigVariables(t *testing.T) {
+	testFile := filepath.Join(getTestDirectory(), "config_variables.mk.test")
+	var actual testVariables
+	if err := FindConfigVariables(testFile, &actual); err != nil {
+		t.Fatal(err)
+	}
+	expected := testVariables{[]testVar{
+		{"PRODUCT_NAME", VarClassConfig, starlarkTypeUnknown},
+		{"PRODUCT_MODEL", VarClassConfig, starlarkTypeUnknown},
+		{"PRODUCT_LOCALES", VarClassConfig, starlarkTypeList},
+		{"PRODUCT_AAPT_CONFIG", VarClassConfig, starlarkTypeList},
+		{"PRODUCT_AAPT_PREF_CONFIG", VarClassConfig, starlarkTypeUnknown},
+	}}
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("\nExpected: %v\n  Actual: %v", expected, actual)
+	}
+}
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
new file mode 100644
index 0000000..9266520
--- /dev/null
+++ b/mk2rbc/expr.go
@@ -0,0 +1,798 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Represents an expression in the Starlark code. An expression has a type.
+type starlarkExpr interface {
+	starlarkNode
+	typ() starlarkType
+	// Emit the code to copy the expression, otherwise we will end up
+	// with source and target pointing to the same list.
+	emitListVarCopy(gctx *generationContext)
+	// Return the expression, calling the transformer func for
+	// every expression in the tree. If the transformer func returns non-nil,
+	// its result is used in place of the expression it was called with in the
+	// resulting expression. The resulting starlarkExpr will contain as many
+	// of the same objects from the original expression as possible.
+	transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr
+}
+
+func maybeString(expr starlarkExpr) (string, bool) {
+	if x, ok := expr.(*stringLiteralExpr); ok {
+		return x.literal, true
+	}
+	return "", false
+}
+
+type stringLiteralExpr struct {
+	literal string
+}
+
+func (s *stringLiteralExpr) emit(gctx *generationContext) {
+	gctx.writef("%q", s.literal)
+}
+
+func (_ *stringLiteralExpr) typ() starlarkType {
+	return starlarkTypeString
+}
+
+func (s *stringLiteralExpr) emitListVarCopy(gctx *generationContext) {
+	s.emit(gctx)
+}
+
+func (s *stringLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
+// Integer literal
+type intLiteralExpr struct {
+	literal int
+}
+
+func (s *intLiteralExpr) emit(gctx *generationContext) {
+	gctx.writef("%d", s.literal)
+}
+
+func (_ *intLiteralExpr) typ() starlarkType {
+	return starlarkTypeInt
+}
+
+func (s *intLiteralExpr) emitListVarCopy(gctx *generationContext) {
+	s.emit(gctx)
+}
+
+func (s *intLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
+// Boolean literal
+type boolLiteralExpr struct {
+	literal bool
+}
+
+func (b *boolLiteralExpr) emit(gctx *generationContext) {
+	if b.literal {
+		gctx.write("True")
+	} else {
+		gctx.write("False")
+	}
+}
+
+func (_ *boolLiteralExpr) typ() starlarkType {
+	return starlarkTypeBool
+}
+
+func (b *boolLiteralExpr) emitListVarCopy(gctx *generationContext) {
+	b.emit(gctx)
+}
+
+func (b *boolLiteralExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
+type globalsExpr struct {
+}
+
+func (g *globalsExpr) emit(gctx *generationContext) {
+	gctx.write("g")
+}
+
+func (g *globalsExpr) typ() starlarkType {
+	return starlarkTypeUnknown
+}
+
+func (g *globalsExpr) emitListVarCopy(gctx *generationContext) {
+	g.emit(gctx)
+}
+
+func (g *globalsExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(g); replacement != nil {
+		return replacement
+	} else {
+		return g
+	}
+}
+
+// interpolateExpr represents Starlark's interpolation operator <string> % list
+// we break <string> into a list of chunks, i.e., "first%second%third" % (X, Y)
+// will have chunks = ["first", "second", "third"] and args = [X, Y]
+type interpolateExpr struct {
+	chunks []string // string chunks, separated by '%'
+	args   []starlarkExpr
+}
+
+func NewInterpolateExpr(parts []starlarkExpr) starlarkExpr {
+	result := &interpolateExpr{}
+	needString := true
+	for _, part := range parts {
+		if needString {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks = append(result.chunks, strLit.literal)
+			} else {
+				result.chunks = append(result.chunks, "")
+			}
+			needString = false
+		} else {
+			if strLit, ok := part.(*stringLiteralExpr); ok {
+				result.chunks[len(result.chunks)-1] += strLit.literal
+			} else {
+				result.args = append(result.args, part)
+				needString = true
+			}
+		}
+	}
+	if len(result.chunks) == len(result.args) {
+		result.chunks = append(result.chunks, "")
+	}
+	if len(result.args) == 0 {
+		return &stringLiteralExpr{literal: strings.Join(result.chunks, "")}
+	}
+	return result
+}
+
+func (xi *interpolateExpr) emit(gctx *generationContext) {
+	if len(xi.chunks) != len(xi.args)+1 {
+		panic(fmt.Errorf("malformed interpolateExpr: #chunks(%d) != #args(%d)+1",
+			len(xi.chunks), len(xi.args)))
+	}
+	// Generate format as join of chunks, but first escape '%' in them
+	format := strings.ReplaceAll(xi.chunks[0], "%", "%%")
+	for _, chunk := range xi.chunks[1:] {
+		format += "%s" + strings.ReplaceAll(chunk, "%", "%%")
+	}
+	gctx.writef("%q %% ", format)
+	emitArg := func(arg starlarkExpr) {
+		if arg.typ() == starlarkTypeList {
+			gctx.write(`" ".join(`)
+			arg.emit(gctx)
+			gctx.write(`)`)
+		} else {
+			arg.emit(gctx)
+		}
+	}
+	if len(xi.args) == 1 {
+		emitArg(xi.args[0])
+	} else {
+		sep := "("
+		for _, arg := range xi.args {
+			gctx.write(sep)
+			emitArg(arg)
+			sep = ", "
+		}
+		gctx.write(")")
+	}
+}
+
+func (_ *interpolateExpr) typ() starlarkType {
+	return starlarkTypeString
+}
+
+func (xi *interpolateExpr) emitListVarCopy(gctx *generationContext) {
+	xi.emit(gctx)
+}
+
+func (xi *interpolateExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	for i := range xi.args {
+		xi.args[i] = xi.args[i].transform(transformer)
+	}
+	if replacement := transformer(xi); replacement != nil {
+		return replacement
+	} else {
+		return xi
+	}
+}
+
+type variableRefExpr struct {
+	ref variable
+}
+
+func NewVariableRefExpr(ref variable) starlarkExpr {
+	if predefined, ok := ref.(*predefinedVariable); ok {
+		return predefined.value
+	}
+	return &variableRefExpr{ref}
+}
+
+func (v *variableRefExpr) emit(gctx *generationContext) {
+	v.ref.emitGet(gctx)
+}
+
+func (v *variableRefExpr) typ() starlarkType {
+	return v.ref.valueType()
+}
+
+func (v *variableRefExpr) emitListVarCopy(gctx *generationContext) {
+	v.emit(gctx)
+	if v.typ() == starlarkTypeList {
+		gctx.write("[:]") // this will copy the list
+	}
+}
+
+func (v *variableRefExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(v); replacement != nil {
+		return replacement
+	} else {
+		return v
+	}
+}
+
+type toStringExpr struct {
+	expr starlarkExpr
+}
+
+func (s *toStringExpr) emit(ctx *generationContext) {
+	switch s.expr.typ() {
+	case starlarkTypeString, starlarkTypeUnknown:
+		// Assume unknown types are strings already.
+		s.expr.emit(ctx)
+	case starlarkTypeList:
+		ctx.write(`" ".join(`)
+		s.expr.emit(ctx)
+		ctx.write(")")
+	case starlarkTypeInt:
+		ctx.write(`("%d" % (`)
+		s.expr.emit(ctx)
+		ctx.write("))")
+	case starlarkTypeBool:
+		ctx.write(`("true" if (`)
+		s.expr.emit(ctx)
+		ctx.write(`) else "")`)
+	case starlarkTypeVoid:
+		ctx.write(`""`)
+	default:
+		panic("Unknown starlark type!")
+	}
+}
+
+func (s *toStringExpr) typ() starlarkType {
+	return starlarkTypeString
+}
+
+func (s *toStringExpr) emitListVarCopy(gctx *generationContext) {
+	s.emit(gctx)
+}
+
+func (s *toStringExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	s.expr = s.expr.transform(transformer)
+	if replacement := transformer(s); replacement != nil {
+		return replacement
+	} else {
+		return s
+	}
+}
+
+type notExpr struct {
+	expr starlarkExpr
+}
+
+func (n *notExpr) emit(ctx *generationContext) {
+	ctx.write("not ")
+	n.expr.emit(ctx)
+}
+
+func (_ *notExpr) typ() starlarkType {
+	return starlarkTypeBool
+}
+
+func (n *notExpr) emitListVarCopy(gctx *generationContext) {
+	n.emit(gctx)
+}
+
+func (n *notExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	n.expr = n.expr.transform(transformer)
+	if replacement := transformer(n); replacement != nil {
+		return replacement
+	} else {
+		return n
+	}
+}
+
+type eqExpr struct {
+	left, right starlarkExpr
+	isEq        bool // if false, it's !=
+}
+
+func (eq *eqExpr) emit(gctx *generationContext) {
+	if eq.left.typ() != eq.right.typ() {
+		eq.left = &toStringExpr{expr: eq.left}
+		eq.right = &toStringExpr{expr: eq.right}
+	}
+
+	// General case
+	eq.left.emit(gctx)
+	if eq.isEq {
+		gctx.write(" == ")
+	} else {
+		gctx.write(" != ")
+	}
+	eq.right.emit(gctx)
+}
+
+func (_ *eqExpr) typ() starlarkType {
+	return starlarkTypeBool
+}
+
+func (eq *eqExpr) emitListVarCopy(gctx *generationContext) {
+	eq.emit(gctx)
+}
+
+func (eq *eqExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	eq.left = eq.left.transform(transformer)
+	eq.right = eq.right.transform(transformer)
+	if replacement := transformer(eq); replacement != nil {
+		return replacement
+	} else {
+		return eq
+	}
+}
+
+type listExpr struct {
+	items []starlarkExpr
+}
+
+func (l *listExpr) emit(gctx *generationContext) {
+	if !gctx.inAssignment || len(l.items) < 2 {
+		gctx.write("[")
+		sep := ""
+		for _, item := range l.items {
+			gctx.write(sep)
+			item.emit(gctx)
+			sep = ", "
+		}
+		gctx.write("]")
+		return
+	}
+
+	gctx.write("[")
+	gctx.indentLevel += 2
+
+	for _, item := range l.items {
+		gctx.newLine()
+		item.emit(gctx)
+		gctx.write(",")
+	}
+	gctx.indentLevel -= 2
+	gctx.newLine()
+	gctx.write("]")
+}
+
+func (_ *listExpr) typ() starlarkType {
+	return starlarkTypeList
+}
+
+func (l *listExpr) emitListVarCopy(gctx *generationContext) {
+	l.emit(gctx)
+}
+
+func (l *listExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(l.items))
+	for i, item := range l.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	l.items = itemsCopy
+	if replacement := transformer(l); replacement != nil {
+		return replacement
+	} else {
+		return l
+	}
+}
+
+func newStringListExpr(items []string) *listExpr {
+	v := listExpr{}
+	for _, item := range items {
+		v.items = append(v.items, &stringLiteralExpr{item})
+	}
+	return &v
+}
+
+// concatExpr generates expr1 + expr2 + ... + exprN in Starlark.
+type concatExpr struct {
+	items []starlarkExpr
+}
+
+func (c *concatExpr) emit(gctx *generationContext) {
+	if len(c.items) == 1 {
+		c.items[0].emit(gctx)
+		return
+	}
+
+	if !gctx.inAssignment {
+		c.items[0].emit(gctx)
+		for _, item := range c.items[1:] {
+			gctx.write(" + ")
+			item.emit(gctx)
+		}
+		return
+	}
+	gctx.write("(")
+	c.items[0].emit(gctx)
+	gctx.indentLevel += 2
+	for _, item := range c.items[1:] {
+		gctx.write(" +")
+		gctx.newLine()
+		item.emit(gctx)
+	}
+	gctx.write(")")
+	gctx.indentLevel -= 2
+}
+
+func (_ *concatExpr) typ() starlarkType {
+	return starlarkTypeList
+}
+
+func (c *concatExpr) emitListVarCopy(gctx *generationContext) {
+	c.emit(gctx)
+}
+
+func (c *concatExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	itemsCopy := make([]starlarkExpr, len(c.items))
+	for i, item := range c.items {
+		itemsCopy[i] = item.transform(transformer)
+	}
+	c.items = itemsCopy
+	if replacement := transformer(c); replacement != nil {
+		return replacement
+	} else {
+		return c
+	}
+}
+
+// inExpr generates <expr> [not] in <list>
+type inExpr struct {
+	expr  starlarkExpr
+	list  starlarkExpr
+	isNot bool
+}
+
+func (i *inExpr) emit(gctx *generationContext) {
+	i.expr.emit(gctx)
+	if i.isNot {
+		gctx.write(" not in ")
+	} else {
+		gctx.write(" in ")
+	}
+	i.list.emit(gctx)
+}
+
+func (_ *inExpr) typ() starlarkType {
+	return starlarkTypeBool
+}
+
+func (i *inExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *inExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.expr = i.expr.transform(transformer)
+	i.list = i.list.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type indexExpr struct {
+	array starlarkExpr
+	index starlarkExpr
+}
+
+func (ix *indexExpr) emit(gctx *generationContext) {
+	ix.array.emit(gctx)
+	gctx.write("[")
+	ix.index.emit(gctx)
+	gctx.write("]")
+}
+
+func (ix *indexExpr) typ() starlarkType {
+	return starlarkTypeString
+}
+
+func (ix *indexExpr) emitListVarCopy(gctx *generationContext) {
+	ix.emit(gctx)
+}
+
+func (ix *indexExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	ix.array = ix.array.transform(transformer)
+	ix.index = ix.index.transform(transformer)
+	if replacement := transformer(ix); replacement != nil {
+		return replacement
+	} else {
+		return ix
+	}
+}
+
+type callExpr struct {
+	object     starlarkExpr // nil if static call
+	name       string
+	args       []starlarkExpr
+	returnType starlarkType
+}
+
+func (cx *callExpr) emit(gctx *generationContext) {
+	if cx.object != nil {
+		gctx.write("(")
+		cx.object.emit(gctx)
+		gctx.write(")")
+		gctx.write(".", cx.name, "(")
+	} else {
+		gctx.write(cx.name, "(")
+	}
+	sep := ""
+	for _, arg := range cx.args {
+		gctx.write(sep)
+		arg.emit(gctx)
+		sep = ", "
+	}
+	gctx.write(")")
+}
+
+func (cx *callExpr) typ() starlarkType {
+	return cx.returnType
+}
+
+func (cx *callExpr) emitListVarCopy(gctx *generationContext) {
+	cx.emit(gctx)
+}
+
+func (cx *callExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if cx.object != nil {
+		cx.object = cx.object.transform(transformer)
+	}
+	for i := range cx.args {
+		cx.args[i] = cx.args[i].transform(transformer)
+	}
+	if replacement := transformer(cx); replacement != nil {
+		return replacement
+	} else {
+		return cx
+	}
+}
+
+type ifExpr struct {
+	condition starlarkExpr
+	ifTrue    starlarkExpr
+	ifFalse   starlarkExpr
+}
+
+func (i *ifExpr) emit(gctx *generationContext) {
+	gctx.write("(")
+	i.ifTrue.emit(gctx)
+	gctx.write(" if ")
+	i.condition.emit(gctx)
+	gctx.write(" else ")
+	i.ifFalse.emit(gctx)
+	gctx.write(")")
+}
+
+func (i *ifExpr) typ() starlarkType {
+	tType := i.ifTrue.typ()
+	fType := i.ifFalse.typ()
+	if tType != fType && tType != starlarkTypeUnknown && fType != starlarkTypeUnknown {
+		panic("Conflicting types in if expression")
+	}
+	if tType != starlarkTypeUnknown {
+		return tType
+	} else {
+		return fType
+	}
+}
+
+func (i *ifExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *ifExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	i.condition = i.condition.transform(transformer)
+	i.ifTrue = i.ifTrue.transform(transformer)
+	i.ifFalse = i.ifFalse.transform(transformer)
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type identifierExpr struct {
+	name string
+}
+
+func (i *identifierExpr) emit(gctx *generationContext) {
+	gctx.write(i.name)
+}
+
+func (i *identifierExpr) typ() starlarkType {
+	return starlarkTypeUnknown
+}
+
+func (i *identifierExpr) emitListVarCopy(gctx *generationContext) {
+	i.emit(gctx)
+}
+
+func (i *identifierExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(i); replacement != nil {
+		return replacement
+	} else {
+		return i
+	}
+}
+
+type foreachExpr struct {
+	varName string
+	list    starlarkExpr
+	action  starlarkExpr
+}
+
+func (f *foreachExpr) emit(gctx *generationContext) {
+	gctx.write("[")
+	f.action.emit(gctx)
+	gctx.write(" for " + f.varName + " in ")
+	f.list.emit(gctx)
+	gctx.write("]")
+}
+
+func (f *foreachExpr) typ() starlarkType {
+	return starlarkTypeList
+}
+
+func (f *foreachExpr) emitListVarCopy(gctx *generationContext) {
+	f.emit(gctx)
+}
+
+func (f *foreachExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	f.list = f.list.transform(transformer)
+	f.action = f.action.transform(transformer)
+	if replacement := transformer(f); replacement != nil {
+		return replacement
+	} else {
+		return f
+	}
+}
+
+type binaryOpExpr struct {
+	left, right starlarkExpr
+	op          string
+	returnType  starlarkType
+}
+
+func (b *binaryOpExpr) emit(gctx *generationContext) {
+	b.left.emit(gctx)
+	gctx.write(" " + b.op + " ")
+	b.right.emit(gctx)
+}
+
+func (b *binaryOpExpr) typ() starlarkType {
+	return b.returnType
+}
+
+func (b *binaryOpExpr) emitListVarCopy(gctx *generationContext) {
+	b.emit(gctx)
+}
+
+func (b *binaryOpExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	b.left = b.left.transform(transformer)
+	b.right = b.right.transform(transformer)
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
+type badExpr struct {
+	errorLocation ErrorLocation
+	message       string
+}
+
+func (b *badExpr) emit(gctx *generationContext) {
+	gctx.emitConversionError(b.errorLocation, b.message)
+}
+
+func (_ *badExpr) typ() starlarkType {
+	return starlarkTypeUnknown
+}
+
+func (_ *badExpr) emitListVarCopy(_ *generationContext) {
+	panic("implement me")
+}
+
+func (b *badExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
+	if replacement := transformer(b); replacement != nil {
+		return replacement
+	} else {
+		return b
+	}
+}
+
+func maybeConvertToStringList(expr starlarkExpr) starlarkExpr {
+	if xString, ok := expr.(*stringLiteralExpr); ok {
+		return newStringListExpr(strings.Fields(xString.literal))
+	}
+	return expr
+}
+
+func isEmptyString(expr starlarkExpr) bool {
+	x, ok := expr.(*stringLiteralExpr)
+	return ok && x.literal == ""
+}
+
+func negateExpr(expr starlarkExpr) starlarkExpr {
+	switch typedExpr := expr.(type) {
+	case *notExpr:
+		return typedExpr.expr
+	case *inExpr:
+		typedExpr.isNot = !typedExpr.isNot
+		return typedExpr
+	case *eqExpr:
+		typedExpr.isEq = !typedExpr.isEq
+		return typedExpr
+	case *binaryOpExpr:
+		switch typedExpr.op {
+		case ">":
+			typedExpr.op = "<="
+			return typedExpr
+		case "<":
+			typedExpr.op = ">="
+			return typedExpr
+		case ">=":
+			typedExpr.op = "<"
+			return typedExpr
+		case "<=":
+			typedExpr.op = ">"
+			return typedExpr
+		default:
+			return &notExpr{expr: expr}
+		}
+	default:
+		return &notExpr{expr: expr}
+	}
+}
diff --git a/mk2rbc/find_mockfs.go b/mk2rbc/find_mockfs.go
new file mode 100644
index 0000000..73eff07
--- /dev/null
+++ b/mk2rbc/find_mockfs.go
@@ -0,0 +1,121 @@
+package mk2rbc
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+// Mock FS. Maps a directory name to an array of entries.
+// An entry implements fs.DirEntry, fs.FIleInfo and fs.File interface
+type FindMockFS struct {
+	dirs map[string][]myFileInfo
+}
+
+func (m FindMockFS) locate(name string) (myFileInfo, bool) {
+	if name == "." {
+		return myFileInfo{".", true}, true
+	}
+	dir := filepath.Dir(name)
+	base := filepath.Base(name)
+	if entries, ok := m.dirs[dir]; ok {
+		for _, e := range entries {
+			if e.name == base {
+				return e, true
+			}
+		}
+	}
+	return myFileInfo{}, false
+}
+
+func (m FindMockFS) create(name string, isDir bool) {
+	dir := filepath.Dir(name)
+	m.dirs[dir] = append(m.dirs[dir], myFileInfo{filepath.Base(name), isDir})
+}
+
+func (m FindMockFS) Stat(name string) (fs.FileInfo, error) {
+	if fi, ok := m.locate(name); ok {
+		return fi, nil
+	}
+	return nil, os.ErrNotExist
+}
+
+type myFileInfo struct {
+	name  string
+	isDir bool
+}
+
+func (m myFileInfo) Info() (fs.FileInfo, error) {
+	panic("implement me")
+}
+
+func (m myFileInfo) Size() int64 {
+	panic("implement me")
+}
+
+func (m myFileInfo) Mode() fs.FileMode {
+	panic("implement me")
+}
+
+func (m myFileInfo) ModTime() time.Time {
+	panic("implement me")
+}
+
+func (m myFileInfo) Sys() interface{} {
+	return nil
+}
+
+func (m myFileInfo) Stat() (fs.FileInfo, error) {
+	return m, nil
+}
+
+func (m myFileInfo) Read(bytes []byte) (int, error) {
+	panic("implement me")
+}
+
+func (m myFileInfo) Close() error {
+	panic("implement me")
+}
+
+func (m myFileInfo) Name() string {
+	return m.name
+}
+
+func (m myFileInfo) IsDir() bool {
+	return m.isDir
+}
+
+func (m myFileInfo) Type() fs.FileMode {
+	return m.Mode()
+}
+
+func (m FindMockFS) Open(name string) (fs.File, error) {
+	panic("implement me")
+}
+
+func (m FindMockFS) ReadDir(name string) ([]fs.DirEntry, error) {
+	if d, ok := m.dirs[name]; ok {
+		var res []fs.DirEntry
+		for _, e := range d {
+			res = append(res, e)
+		}
+		return res, nil
+	}
+	return nil, os.ErrNotExist
+}
+
+func NewFindMockFS(files []string) FindMockFS {
+	myfs := FindMockFS{make(map[string][]myFileInfo)}
+	for _, f := range files {
+		isDir := false
+		for f != "." {
+			if _, ok := myfs.locate(f); !ok {
+				myfs.create(f, isDir)
+			}
+			isDir = true
+			f = filepath.Dir(f)
+		}
+	}
+	return myfs
+}
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
new file mode 100644
index 0000000..8f4fea4
--- /dev/null
+++ b/mk2rbc/mk2rbc.go
@@ -0,0 +1,2065 @@
+// Copyright 2021 Google LLC
+//
+// 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.
+
+// Convert makefile containing device configuration to Starlark file
+// The conversion can handle the following constructs in a makefile:
+//   * comments
+//   * simple variable assignments
+//   * $(call init-product,<file>)
+//   * $(call inherit-product-if-exists
+//   * if directives
+// All other constructs are carried over to the output starlark file as comments.
+//
+package mk2rbc
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/fs"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+	"text/scanner"
+
+	mkparser "android/soong/androidmk/parser"
+)
+
+const (
+	annotationCommentPrefix = "RBC#"
+	baseUri                 = "//build/make/core:product_config.rbc"
+	// The name of the struct exported by the product_config.rbc
+	// that contains the functions and variables available to
+	// product configuration Starlark files.
+	baseName = "rblf"
+
+	soongNsPrefix = "SOONG_CONFIG_"
+
+	// And here are the functions and variables:
+	cfnGetCfg         = baseName + ".cfg"
+	cfnMain           = baseName + ".product_configuration"
+	cfnBoardMain      = baseName + ".board_configuration"
+	cfnPrintVars      = baseName + ".printvars"
+	cfnInherit        = baseName + ".inherit"
+	cfnSetListDefault = baseName + ".setdefault"
+)
+
+const (
+	soongConfigAppend = "soong_config_append"
+	soongConfigAssign = "soong_config_set"
+)
+
+var knownFunctions = map[string]interface {
+	parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr
+}{
+	"abspath":                              &simpleCallParser{name: baseName + ".abspath", returnType: starlarkTypeString},
+	"add-product-dex-preopt-module-config": &simpleCallParser{name: baseName + ".add_product_dex_preopt_module_config", returnType: starlarkTypeString, addHandle: true},
+	"add_soong_config_namespace":           &simpleCallParser{name: baseName + ".soong_config_namespace", returnType: starlarkTypeVoid, addGlobals: true},
+	"add_soong_config_var_value":           &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
+	soongConfigAssign:                      &simpleCallParser{name: baseName + ".soong_config_set", returnType: starlarkTypeVoid, addGlobals: true},
+	soongConfigAppend:                      &simpleCallParser{name: baseName + ".soong_config_append", returnType: starlarkTypeVoid, addGlobals: true},
+	"soong_config_get":                     &simpleCallParser{name: baseName + ".soong_config_get", returnType: starlarkTypeString, addGlobals: true},
+	"add-to-product-copy-files-if-exists":  &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList},
+	"addprefix":                            &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
+	"addsuffix":                            &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
+	"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, 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},
+	"foreach":                              &foreachCallParser{},
+	"if":                                   &ifCallParser{},
+	"info":                                 &makeControlFuncParser{name: baseName + ".mkinfo"},
+	"is-board-platform":                    &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
+	"is-board-platform2":                   &simpleCallParser{name: baseName + ".board_platform_is", returnType: starlarkTypeBool, addGlobals: true},
+	"is-board-platform-in-list":            &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
+	"is-board-platform-in-list2":           &simpleCallParser{name: baseName + ".board_platform_in", returnType: starlarkTypeBool, addGlobals: true},
+	"is-product-in-list":                   &isProductInListCallParser{},
+	"is-vendor-board-platform":             &isVendorBoardPlatformCallParser{},
+	"is-vendor-board-qcom":                 &isVendorBoardQcomCallParser{},
+	"lastword":                             &firstOrLastwordCallParser{isLastWord: true},
+	"notdir":                               &simpleCallParser{name: baseName + ".notdir", returnType: starlarkTypeString},
+	"math_max":                             &mathMaxOrMinCallParser{function: "max"},
+	"math_min":                             &mathMaxOrMinCallParser{function: "min"},
+	"math_gt_or_eq":                        &mathComparisonCallParser{op: ">="},
+	"math_gt":                              &mathComparisonCallParser{op: ">"},
+	"math_lt":                              &mathComparisonCallParser{op: "<"},
+	"my-dir":                               &myDirCallParser{},
+	"patsubst":                             &substCallParser{fname: "patsubst"},
+	"product-copy-files-by-pattern":        &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
+	"require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
+	"require-artifacts-in-path-relaxed":    &simpleCallParser{name: baseName + ".require_artifacts_in_path_relaxed", returnType: starlarkTypeVoid, addHandle: true},
+	// TODO(asmundak): remove it once all calls are removed from configuration makefiles. see b/183161002
+	"shell":    &shellCallParser{},
+	"sort":     &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
+	"strip":    &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString},
+	"subst":    &substCallParser{fname: "subst"},
+	"warning":  &makeControlFuncParser{name: baseName + ".mkwarning"},
+	"word":     &wordCallParser{},
+	"wildcard": &simpleCallParser{name: baseName + ".expand_wildcard", returnType: starlarkTypeList},
+}
+
+// The same as knownFunctions, but returns a []starlarkNode instead of a starlarkExpr
+var knownNodeFunctions = map[string]interface {
+	parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode
+}{
+	"eval":                      &evalNodeParser{},
+	"if":                        &ifCallNodeParser{},
+	"inherit-product":           &inheritProductCallParser{loadAlways: true},
+	"inherit-product-if-exists": &inheritProductCallParser{loadAlways: false},
+	"foreach":                   &foreachCallNodeParser{},
+}
+
+// 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{
+	"find-word-in-list":                   true, // internal macro
+	"get-vendor-board-platforms":          true, // internal macro, used by is-board-platform, etc.
+	"is-android-codename":                 true, // unused by product config
+	"is-android-codename-in-list":         true, // unused by product config
+	"is-chipset-in-board-platform":        true, // unused by product config
+	"is-chipset-prefix-in-board-platform": true, // unused by product config
+	"is-not-board-platform":               true, // defined but never used
+	"is-platform-sdk-version-at-least":    true, // unused by product config
+	"match-prefix":                        true, // internal macro
+	"match-word":                          true, // internal macro
+	"match-word-in-list":                  true, // internal macro
+	"tb-modules":                          true, // defined in hardware/amlogic/tb_modules/tb_detect.mk, unused
+}
+
+var identifierFullMatchRegex = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
+
+// Conversion request parameters
+type Request struct {
+	MkFile          string    // file to convert
+	Reader          io.Reader // if set, read input from this stream instead
+	OutputSuffix    string    // generated Starlark files suffix
+	OutputDir       string    // if set, root of the output hierarchy
+	ErrorLogger     ErrorLogger
+	TracedVariables []string // trace assignment to these variables
+	TraceCalls      bool
+	SourceFS        fs.FS
+	MakefileFinder  MakefileFinder
+}
+
+// ErrorLogger prints errors and gathers error statistics.
+// Its NewError function is called on every error encountered during the conversion.
+type ErrorLogger interface {
+	NewError(el ErrorLocation, node mkparser.Node, text string, args ...interface{})
+}
+
+type ErrorLocation struct {
+	MkFile string
+	MkLine int
+}
+
+func (el ErrorLocation) String() string {
+	return fmt.Sprintf("%s:%d", el.MkFile, el.MkLine)
+}
+
+// Derives module name for a given file. It is base name
+// (file name without suffix), with some characters replaced to make it a Starlark identifier
+func moduleNameForFile(mkFile string) string {
+	base := strings.TrimSuffix(filepath.Base(mkFile), filepath.Ext(mkFile))
+	// TODO(asmundak): what else can be in the product file names?
+	return strings.NewReplacer("-", "_", ".", "_").Replace(base)
+
+}
+
+func cloneMakeString(mkString *mkparser.MakeString) *mkparser.MakeString {
+	r := &mkparser.MakeString{StringPos: mkString.StringPos}
+	r.Strings = append(r.Strings, mkString.Strings...)
+	r.Variables = append(r.Variables, mkString.Variables...)
+	return r
+}
+
+func isMakeControlFunc(s string) bool {
+	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
+	varAssignments *varAssignmentScope
+}
+
+func NewGenerateContext(ss *StarlarkScript) *generationContext {
+	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
+func (gctx *generationContext) emit() string {
+	ss := gctx.starScript
+
+	// The emitted code has the following layout:
+	//    <initial comments>
+	//    preamble, i.e.,
+	//      load statement for the runtime support
+	//      load statement for each unique submodule pulled in by this one
+	//    def init(g, handle):
+	//      cfg = rblf.cfg(handle)
+	//      <statements>
+	//      <warning if conversion was not clean>
+
+	iNode := len(ss.nodes)
+	for i, node := range ss.nodes {
+		if _, ok := node.(*commentNode); !ok {
+			iNode = i
+			break
+		}
+		node.emit(gctx)
+	}
+
+	gctx.emitPreamble()
+
+	gctx.newLine()
+	// The arguments passed to the init function are the global dictionary
+	// ('g') and the product configuration dictionary ('cfg')
+	gctx.write("def init(g, handle):")
+	gctx.indentLevel++
+	if gctx.starScript.traceCalls {
+		gctx.newLine()
+		gctx.writef(`print(">%s")`, gctx.starScript.mkFile)
+	}
+	gctx.newLine()
+	gctx.writef("cfg = %s(handle)", cfnGetCfg)
+	for _, node := range ss.nodes[iNode:] {
+		node.emit(gctx)
+	}
+
+	if gctx.starScript.traceCalls {
+		gctx.newLine()
+		gctx.writef(`print("<%s")`, gctx.starScript.mkFile)
+	}
+	gctx.indentLevel--
+	gctx.write("\n")
+	return gctx.buf.String()
+}
+
+func (gctx *generationContext) emitPreamble() {
+	gctx.newLine()
+	gctx.writef("load(%q, %q)", baseUri, baseName)
+	// Emit exactly one load statement for each URI.
+	loadedSubConfigs := make(map[string]string)
+	for _, mi := range gctx.starScript.inherited {
+		uri := mi.path
+		if m, ok := loadedSubConfigs[uri]; ok {
+			// No need to emit load statement, but fix module name.
+			mi.moduleLocalName = m
+			continue
+		}
+		if mi.optional || mi.missing {
+			uri += "|init"
+		}
+		gctx.newLine()
+		gctx.writef("load(%q, %s = \"init\")", uri, mi.entryName())
+		loadedSubConfigs[uri] = mi.moduleLocalName
+	}
+	gctx.write("\n")
+}
+
+func (gctx *generationContext) emitPass() {
+	gctx.newLine()
+	gctx.write("pass")
+}
+
+func (gctx *generationContext) write(ss ...string) {
+	for _, s := range ss {
+		gctx.buf.WriteString(s)
+	}
+}
+
+func (gctx *generationContext) writef(format string, args ...interface{}) {
+	gctx.write(fmt.Sprintf(format, args...))
+}
+
+func (gctx *generationContext) newLine() {
+	if gctx.buf.Len() == 0 {
+		return
+	}
+	gctx.write("\n")
+	gctx.writef("%*s", 2*gctx.indentLevel, "")
+}
+
+func (gctx *generationContext) emitConversionError(el ErrorLocation, message string) {
+	gctx.writef(`rblf.mk2rbc_error("%s", %q)`, el, message)
+}
+
+func (gctx *generationContext) emitLoadCheck(im inheritedModule) {
+	if !im.needsLoadCheck() {
+		return
+	}
+	gctx.newLine()
+	gctx.writef("if not %s:", im.entryName())
+	gctx.indentLevel++
+	gctx.newLine()
+	gctx.write(`rblf.mkerror("`, gctx.starScript.mkFile, `", "Cannot find %s" % (`)
+	im.pathExpr().emit(gctx)
+	gctx.write("))")
+	gctx.indentLevel--
+}
+
+type knownVariable struct {
+	name      string
+	class     varClass
+	valueType starlarkType
+}
+
+type knownVariables map[string]knownVariable
+
+func (pcv knownVariables) NewVariable(name string, varClass varClass, valueType starlarkType) {
+	v, exists := pcv[name]
+	if !exists {
+		pcv[name] = knownVariable{name, varClass, valueType}
+		return
+	}
+	// Conflict resolution:
+	//    * config class trumps everything
+	//    * any type trumps unknown type
+	match := varClass == v.class
+	if !match {
+		if varClass == VarClassConfig {
+			v.class = VarClassConfig
+			match = true
+		} else if v.class == VarClassConfig {
+			match = true
+		}
+	}
+	if valueType != v.valueType {
+		if valueType != starlarkTypeUnknown {
+			if v.valueType == starlarkTypeUnknown {
+				v.valueType = valueType
+			} else {
+				match = false
+			}
+		}
+	}
+	if !match {
+		fmt.Fprintf(os.Stderr, "cannot redefine %s as %v/%v (already defined as %v/%v)\n",
+			name, varClass, valueType, v.class, v.valueType)
+	}
+}
+
+// All known product variables.
+var KnownVariables = make(knownVariables)
+
+func init() {
+	for _, kv := range []string{
+		// Kernel-related variables that we know are lists.
+		"BOARD_VENDOR_KERNEL_MODULES",
+		"BOARD_VENDOR_RAMDISK_KERNEL_MODULES",
+		"BOARD_VENDOR_RAMDISK_KERNEL_MODULES_LOAD",
+		"BOARD_RECOVERY_KERNEL_MODULES",
+		// Other variables we knwo are lists
+		"ART_APEX_JARS",
+	} {
+		KnownVariables.NewVariable(kv, VarClassSoong, starlarkTypeList)
+	}
+}
+
+// Information about the generated Starlark script.
+type StarlarkScript struct {
+	mkFile         string
+	moduleName     string
+	mkPos          scanner.Position
+	nodes          []starlarkNode
+	inherited      []*moduleInfo
+	hasErrors      bool
+	traceCalls     bool // print enter/exit each init function
+	sourceFS       fs.FS
+	makefileFinder MakefileFinder
+	nodeLocator    func(pos mkparser.Pos) int
+}
+
+// parseContext holds the script we are generating and all the ephemeral data
+// needed during the parsing.
+type parseContext struct {
+	script           *StarlarkScript
+	nodes            []mkparser.Node // Makefile as parsed by mkparser
+	currentNodeIndex int             // Node in it we are processing
+	ifNestLevel      int
+	moduleNameCount  map[string]int // count of imported modules with given basename
+	fatalError       error
+	outputSuffix     string
+	errorLogger      ErrorLogger
+	tracedVariables  map[string]bool // variables to be traced in the generated script
+	variables        map[string]variable
+	outputDir        string
+	dependentModules map[string]*moduleInfo
+	soongNamespaces  map[string]map[string]bool
+	includeTops      []string
+	typeHints        map[string]starlarkType
+	atTopOfMakefile  bool
+}
+
+func newParseContext(ss *StarlarkScript, nodes []mkparser.Node) *parseContext {
+	predefined := []struct{ name, value string }{
+		{"SRC_TARGET_DIR", filepath.Join("build", "make", "target")},
+		{"LOCAL_PATH", filepath.Dir(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"},
+		{"TARGET_COPY_OUT_SYSTEM_OTHER", "system_other"},
+		{"TARGET_COPY_OUT_DATA", "data"},
+		{"TARGET_COPY_OUT_ASAN", filepath.Join("data", "asan")},
+		{"TARGET_COPY_OUT_OEM", "oem"},
+		{"TARGET_COPY_OUT_RAMDISK", "ramdisk"},
+		{"TARGET_COPY_OUT_DEBUG_RAMDISK", "debug_ramdisk"},
+		{"TARGET_COPY_OUT_VENDOR_DEBUG_RAMDISK", "vendor_debug_ramdisk"},
+		{"TARGET_COPY_OUT_TEST_HARNESS_RAMDISK", "test_harness_ramdisk"},
+		{"TARGET_COPY_OUT_ROOT", "root"},
+		{"TARGET_COPY_OUT_RECOVERY", "recovery"},
+		{"TARGET_COPY_OUT_VENDOR_RAMDISK", "vendor_ramdisk"},
+		// TODO(asmundak): to process internal config files, we need the following variables:
+		//    TARGET_VENDOR
+		//    target_base_product
+		//
+
+		// the following utility variables are set in build/make/common/core.mk:
+		{"empty", ""},
+		{"space", " "},
+		{"comma", ","},
+		{"newline", "\n"},
+		{"pound", "#"},
+		{"backslash", "\\"},
+	}
+	ctx := &parseContext{
+		script:           ss,
+		nodes:            nodes,
+		currentNodeIndex: 0,
+		ifNestLevel:      0,
+		moduleNameCount:  make(map[string]int),
+		variables:        make(map[string]variable),
+		dependentModules: make(map[string]*moduleInfo),
+		soongNamespaces:  make(map[string]map[string]bool),
+		includeTops:      []string{},
+		typeHints:        make(map[string]starlarkType),
+		atTopOfMakefile:  true,
+	}
+	for _, item := range predefined {
+		ctx.variables[item.name] = &predefinedVariable{
+			baseVariable: baseVariable{nam: item.name, typ: starlarkTypeString},
+			value:        &stringLiteralExpr{item.value},
+		}
+	}
+
+	return ctx
+}
+
+func (ctx *parseContext) hasNodes() bool {
+	return ctx.currentNodeIndex < len(ctx.nodes)
+}
+
+func (ctx *parseContext) getNode() mkparser.Node {
+	if !ctx.hasNodes() {
+		return nil
+	}
+	node := ctx.nodes[ctx.currentNodeIndex]
+	ctx.currentNodeIndex++
+	return node
+}
+
+func (ctx *parseContext) backNode() {
+	if ctx.currentNodeIndex <= 0 {
+		panic("Cannot back off")
+	}
+	ctx.currentNodeIndex--
+}
+
+func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
+	// Handle only simple variables
+	if !a.Name.Const() {
+		return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
+	}
+	name := a.Name.Strings[0]
+	// The `override` directive
+	//      override FOO :=
+	// is parsed as an assignment to a variable named `override FOO`.
+	// There are very few places where `override` is used, just flag it.
+	if strings.HasPrefix(name, "override ") {
+		return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
+	}
+
+	// Soong configuration
+	if strings.HasPrefix(name, soongNsPrefix) {
+		return ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
+	}
+	lhs := ctx.addVariable(name)
+	if lhs == nil {
+		return []starlarkNode{ctx.newBadNode(a, "unknown variable %s", name)}
+	}
+	_, isTraced := ctx.tracedVariables[lhs.name()]
+	asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
+	if lhs.valueType() == starlarkTypeUnknown {
+		// Try to divine variable type from the RHS
+		asgn.value = ctx.parseMakeString(a, a.Value)
+		if xBad, ok := asgn.value.(*badExpr); ok {
+			return []starlarkNode{&exprNode{xBad}}
+		}
+		inferred_type := asgn.value.typ()
+		if inferred_type != starlarkTypeUnknown {
+			lhs.setValueType(inferred_type)
+		}
+	}
+	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
+		}
+	} 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 &&
+		asgn.value.typ() != starlarkTypeUnknown &&
+		asgn.value.typ() != starlarkTypeString {
+		asgn.value = &toStringExpr{expr: asgn.value}
+	}
+
+	switch a.Type {
+	case "=", ":=":
+		asgn.flavor = asgnSet
+	case "+=":
+		asgn.flavor = asgnAppend
+	case "?=":
+		asgn.flavor = asgnMaybeSet
+	default:
+		panic(fmt.Errorf("unexpected assignment type %s", a.Type))
+	}
+
+	return []starlarkNode{asgn}
+}
+
+func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) []starlarkNode {
+	val := ctx.parseMakeString(asgn, asgn.Value)
+	if xBad, ok := val.(*badExpr); ok {
+		return []starlarkNode{&exprNode{expr: xBad}}
+	}
+
+	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
+	// variables instead of via add_soong_config_namespace + add_soong_config_var_value.
+	// Try to divine the call from the assignment as follows:
+	if name == "NAMESPACES" {
+		// Upon seeng
+		//      SOONG_CONFIG_NAMESPACES += foo
+		//    remember that there is a namespace `foo` and act as we saw
+		//      $(call add_soong_config_namespace,foo)
+		s, ok := maybeString(val)
+		if !ok {
+			return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")}
+		}
+		result := make([]starlarkNode, 0)
+		for _, ns := range strings.Fields(s) {
+			ctx.addSoongNamespace(ns)
+			result = append(result, &exprNode{&callExpr{
+				name:       baseName + ".soong_config_namespace",
+				args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
+				returnType: starlarkTypeVoid,
+			}})
+		}
+		return result
+	} else {
+		// Upon seeing
+		//      SOONG_CONFIG_x_y = v
+		// find a namespace called `x` and act as if we encountered
+		//      $(call soong_config_set,x,y,v)
+		// or check that `x_y` is a namespace, and then add the RHS of this assignment as variables in
+		// it.
+		// Emit an error in the ambiguous situation (namespaces `foo_bar` with a variable `baz`
+		// and `foo` with a variable `bar_baz`.
+		namespaceName := ""
+		if ctx.hasSoongNamespace(name) {
+			namespaceName = name
+		}
+		var varName string
+		for pos, ch := range name {
+			if !(ch == '_' && ctx.hasSoongNamespace(name[0:pos])) {
+				continue
+			}
+			if namespaceName != "" {
+				return []starlarkNode{ctx.newBadNode(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])}
+			}
+			namespaceName = name[0:pos]
+			varName = name[pos+1:]
+		}
+		if namespaceName == "" {
+			return []starlarkNode{ctx.newBadNode(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")}
+		}
+		if varName == "" {
+			// Remember variables in this namespace
+			s, ok := maybeString(val)
+			if !ok {
+				return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")}
+			}
+			ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
+			return []starlarkNode{}
+		}
+
+		// Finally, handle assignment to a namespace variable
+		if !ctx.hasNamespaceVar(namespaceName, varName) {
+			return []starlarkNode{ctx.newBadNode(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)}
+		}
+		fname := baseName + "." + soongConfigAssign
+		if asgn.Type == "+=" {
+			fname = baseName + "." + soongConfigAppend
+		}
+		return []starlarkNode{&exprNode{&callExpr{
+			name:       fname,
+			args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
+			returnType: starlarkTypeVoid,
+		}}}
+	}
+}
+
+func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) (*concatExpr, *badExpr) {
+	xConcat := &concatExpr{}
+	var xItemList *listExpr
+	addToItemList := func(x ...starlarkExpr) {
+		if xItemList == nil {
+			xItemList = &listExpr{[]starlarkExpr{}}
+		}
+		xItemList.items = append(xItemList.items, x...)
+	}
+	finishItemList := func() {
+		if xItemList != nil {
+			xConcat.items = append(xConcat.items, xItemList)
+			xItemList = nil
+		}
+	}
+
+	items := a.Value.Words()
+	for _, item := range items {
+		// A function call in RHS is supposed to return a list, all other item
+		// expressions return individual elements.
+		switch x := ctx.parseMakeString(a, item).(type) {
+		case *badExpr:
+			return nil, x
+		case *stringLiteralExpr:
+			addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
+		default:
+			switch x.typ() {
+			case starlarkTypeList:
+				finishItemList()
+				xConcat.items = append(xConcat.items, x)
+			case starlarkTypeString:
+				finishItemList()
+				xConcat.items = append(xConcat.items, &callExpr{
+					object:     x,
+					name:       "split",
+					args:       nil,
+					returnType: starlarkTypeList,
+				})
+			default:
+				addToItemList(x)
+			}
+		}
+	}
+	if xItemList != nil {
+		xConcat.items = append(xConcat.items, xItemList)
+	}
+	return xConcat, nil
+}
+
+func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
+	modulePath := ctx.loadedModulePath(path)
+	if mi, ok := ctx.dependentModules[modulePath]; ok {
+		mi.optional = mi.optional && optional
+		return mi
+	}
+	moduleName := moduleNameForFile(path)
+	moduleLocalName := "_" + moduleName
+	n, found := ctx.moduleNameCount[moduleName]
+	if found {
+		moduleLocalName += fmt.Sprintf("%d", n)
+	}
+	ctx.moduleNameCount[moduleName] = n + 1
+	_, err := fs.Stat(ctx.script.sourceFS, path)
+	mi := &moduleInfo{
+		path:            modulePath,
+		originalPath:    path,
+		moduleLocalName: moduleLocalName,
+		optional:        optional,
+		missing:         err != nil,
+	}
+	ctx.dependentModules[modulePath] = mi
+	ctx.script.inherited = append(ctx.script.inherited, mi)
+	return mi
+}
+
+func (ctx *parseContext) handleSubConfig(
+	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule) starlarkNode) []starlarkNode {
+
+	// Allow seeing $(sort $(wildcard realPathExpr)) or $(wildcard realPathExpr)
+	// because those are functionally the same as not having the sort/wildcard calls.
+	if ce, ok := pathExpr.(*callExpr); ok && ce.name == "rblf.mksort" && len(ce.args) == 1 {
+		if ce2, ok2 := ce.args[0].(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
+			pathExpr = ce2.args[0]
+		}
+	} else if ce2, ok2 := pathExpr.(*callExpr); ok2 && ce2.name == "rblf.expand_wildcard" && len(ce2.args) == 1 {
+		pathExpr = ce2.args[0]
+	}
+
+	// In a simple case, the name of a module to inherit/include is known statically.
+	if path, ok := maybeString(pathExpr); ok {
+		// Note that even if this directive loads a module unconditionally, a module may be
+		// absent without causing any harm if this directive is inside an if/else block.
+		moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
+		if strings.Contains(path, "*") {
+			if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
+				sort.Strings(paths)
+				result := make([]starlarkNode, 0)
+				for _, p := range paths {
+					mi := ctx.newDependentModule(p, !moduleShouldExist)
+					result = append(result, processModule(inheritedStaticModule{mi, loadAlways}))
+				}
+				return result
+			} else {
+				return []starlarkNode{ctx.newBadNode(v, "cannot glob wildcard argument")}
+			}
+		} else {
+			mi := ctx.newDependentModule(path, !moduleShouldExist)
+			return []starlarkNode{processModule(inheritedStaticModule{mi, loadAlways})}
+		}
+	}
+
+	// If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
+	// source tree that may be a match and the corresponding variable values. For instance, if the source tree
+	// contains vendor1/foo/abc/dev.mk and vendor2/foo/def/dev.mk, the first one will be inherited when
+	// (v1, v2) == ('vendor1', 'abc'), and the second one when (v1, v2) == ('vendor2', 'def').
+	// We then emit the code that loads all of them, e.g.:
+	//    load("//vendor1/foo/abc:dev.rbc", _dev1_init="init")
+	//    load("//vendor2/foo/def/dev.rbc", _dev2_init="init")
+	// And then inherit it as follows:
+	//    _e = {
+	//       "vendor1/foo/abc/dev.mk": ("vendor1/foo/abc/dev", _dev1_init),
+	//       "vendor2/foo/def/dev.mk": ("vendor2/foo/def/dev", _dev_init2) }.get("%s/foo/%s/dev.mk" % (v1, v2))
+	//    if _e:
+	//       rblf.inherit(handle, _e[0], _e[1])
+	//
+	var matchingPaths []string
+	varPath, ok := pathExpr.(*interpolateExpr)
+	if !ok {
+		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}
+	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
+		// by always loading the dynamic files as optional.
+		res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
+	}
+	return []starlarkNode{processModule(res)}
+}
+
+func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
+	files := ctx.script.makefileFinder.Find(".")
+	if len(pattern) == 0 {
+		return files
+	}
+
+	// Create regular expression from the pattern
+	s_regexp := "^" + regexp.QuoteMeta(pattern[0])
+	for _, s := range pattern[1:] {
+		s_regexp += ".*" + regexp.QuoteMeta(s)
+	}
+	s_regexp += "$"
+	rex := regexp.MustCompile(s_regexp)
+
+	// Now match
+	var res []string
+	for _, p := range files {
+		if rex.MatchString(p) {
+			res = append(res, p)
+		}
+	}
+	return res
+}
+
+type inheritProductCallParser struct {
+	loadAlways bool
+}
+
+func (p *inheritProductCallParser) parse(ctx *parseContext, v mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+	args.TrimLeftSpaces()
+	args.TrimRightSpaces()
+	pathExpr := ctx.parseMakeString(v, args)
+	if _, ok := pathExpr.(*badExpr); ok {
+		return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
+	}
+	return ctx.handleSubConfig(v, pathExpr, p.loadAlways, func(im inheritedModule) starlarkNode {
+		return &inheritNode{im, p.loadAlways}
+	})
+}
+
+func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) []starlarkNode {
+	return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
+		return &includeNode{im, loadAlways}
+	})
+}
+
+func (ctx *parseContext) handleVariable(v *mkparser.Variable) []starlarkNode {
+	// Handle:
+	//   $(call inherit-product,...)
+	//   $(call inherit-product-if-exists,...)
+	//   $(info xxx)
+	//   $(warning xxx)
+	//   $(error xxx)
+	//   $(call other-custom-functions,...)
+
+	if name, args, ok := ctx.maybeParseFunctionCall(v, v.Name); ok {
+		if kf, ok := knownNodeFunctions[name]; ok {
+			return kf.parse(ctx, v, args)
+		}
+	}
+
+	return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
+}
+
+func (ctx *parseContext) maybeHandleDefine(directive *mkparser.Directive) starlarkNode {
+	macro_name := strings.Fields(directive.Args.Strings[0])[0]
+	// Ignore the macros that we handle
+	_, ignored := ignoredDefines[macro_name]
+	_, known := knownFunctions[macro_name]
+	if !ignored && !known {
+		return ctx.newBadNode(directive, "define is not supported: %s", macro_name)
+	}
+	return nil
+}
+
+func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) starlarkNode {
+	ssSwitch := &switchNode{
+		ssCases: []*switchCase{ctx.processBranch(ifDirective)},
+	}
+	for ctx.hasNodes() && ctx.fatalError == nil {
+		node := ctx.getNode()
+		switch x := node.(type) {
+		case *mkparser.Directive:
+			switch x.Name {
+			case "else", "elifdef", "elifndef", "elifeq", "elifneq":
+				ssSwitch.ssCases = append(ssSwitch.ssCases, ctx.processBranch(x))
+			case "endif":
+				return ssSwitch
+			default:
+				return ctx.newBadNode(node, "unexpected directive %s", x.Name)
+			}
+		default:
+			return ctx.newBadNode(ifDirective, "unexpected statement")
+		}
+	}
+	if ctx.fatalError == nil {
+		ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
+	}
+	return ctx.newBadNode(ifDirective, "no matching endif for %s", ifDirective.Dump())
+}
+
+// processBranch processes a single branch (if/elseif/else) until the next directive
+// on the same level.
+func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
+	block := &switchCase{gate: ctx.parseCondition(check)}
+	defer func() {
+		ctx.ifNestLevel--
+	}()
+	ctx.ifNestLevel++
+
+	for ctx.hasNodes() {
+		node := ctx.getNode()
+		if d, ok := node.(*mkparser.Directive); ok {
+			switch d.Name {
+			case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
+				ctx.backNode()
+				return block
+			}
+		}
+		block.nodes = append(block.nodes, ctx.handleSimpleStatement(node)...)
+	}
+	ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
+	return block
+}
+
+func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
+	switch check.Name {
+	case "ifdef", "ifndef", "elifdef", "elifndef":
+		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]))
+		if strings.HasSuffix(check.Name, "ndef") {
+			v = &notExpr{v}
+		}
+		return &ifNode{
+			isElif: strings.HasPrefix(check.Name, "elif"),
+			expr:   v,
+		}
+	case "ifeq", "ifneq", "elifeq", "elifneq":
+		return &ifNode{
+			isElif: strings.HasPrefix(check.Name, "elif"),
+			expr:   ctx.parseCompare(check),
+		}
+	case "else":
+		return &elseNode{}
+	default:
+		panic(fmt.Errorf("%s: unknown directive: %s", ctx.script.mkFile, check.Dump()))
+	}
+}
+
+func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
+	if ctx.errorLogger != nil {
+		ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
+	}
+	ctx.script.hasErrors = true
+	return &badExpr{errorLocation: ctx.errorLocation(node), message: fmt.Sprintf(text, args...)}
+}
+
+// records that the given node failed to be converted and includes an explanatory message
+func (ctx *parseContext) newBadNode(failedNode mkparser.Node, message string, args ...interface{}) starlarkNode {
+	return &exprNode{ctx.newBadExpr(failedNode, message, args...)}
+}
+
+func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
+	// Strip outer parentheses
+	mkArg := cloneMakeString(cond.Args)
+	mkArg.Strings[0] = strings.TrimLeft(mkArg.Strings[0], "( ")
+	n := len(mkArg.Strings)
+	mkArg.Strings[n-1] = strings.TrimRight(mkArg.Strings[n-1], ") ")
+	args := mkArg.Split(",")
+	// TODO(asmundak): handle the case where the arguments are in quotes and space-separated
+	if len(args) != 2 {
+		return ctx.newBadExpr(cond, "ifeq/ifneq len(args) != 2 %s", cond.Dump())
+	}
+	args[0].TrimRightSpaces()
+	args[1].TrimLeftSpaces()
+
+	isEq := !strings.HasSuffix(cond.Name, "neq")
+	xLeft := ctx.parseMakeString(cond, args[0])
+	xRight := ctx.parseMakeString(cond, args[1])
+	if bad, ok := xLeft.(*badExpr); ok {
+		return bad
+	}
+	if bad, ok := xRight.(*badExpr); ok {
+		return bad
+	}
+
+	if expr, ok := ctx.parseCompareSpecialCases(cond, xLeft, xRight); ok {
+		return expr
+	}
+
+	var stringOperand string
+	var otherOperand starlarkExpr
+	if s, ok := maybeString(xLeft); ok {
+		stringOperand = s
+		otherOperand = xRight
+	} else if s, ok := maybeString(xRight); ok {
+		stringOperand = s
+		otherOperand = xLeft
+	}
+
+	// If we've identified one of the operands as being a string literal, check
+	// for some special cases we can do to simplify the resulting expression.
+	if otherOperand != nil {
+		if stringOperand == "" {
+			if isEq {
+				return negateExpr(otherOperand)
+			} else {
+				return otherOperand
+			}
+		}
+		if stringOperand == "true" && otherOperand.typ() == starlarkTypeBool {
+			if !isEq {
+				return negateExpr(otherOperand)
+			} else {
+				return otherOperand
+			}
+		}
+		if intOperand, err := strconv.Atoi(strings.TrimSpace(stringOperand)); err == nil && otherOperand.typ() == starlarkTypeInt {
+			return &eqExpr{
+				left:  otherOperand,
+				right: &intLiteralExpr{literal: intOperand},
+				isEq:  isEq,
+			}
+		}
+	}
+
+	return &eqExpr{left: xLeft, right: xRight, isEq: isEq}
+}
+
+// Given an if statement's directive and the left/right starlarkExprs,
+// check if the starlarkExprs are one of a few hardcoded special cases
+// that can be converted to a simpler equality expression than simply comparing
+// the two.
+func (ctx *parseContext) parseCompareSpecialCases(directive *mkparser.Directive, left starlarkExpr,
+	right starlarkExpr) (starlarkExpr, bool) {
+	isEq := !strings.HasSuffix(directive.Name, "neq")
+
+	// All the special cases require a call on one side and a
+	// string literal/variable on the other. Turn the left/right variables into
+	// call/value variables, and return false if that's not possible.
+	var value starlarkExpr = nil
+	call, ok := left.(*callExpr)
+	if ok {
+		switch right.(type) {
+		case *stringLiteralExpr, *variableRefExpr:
+			value = right
+		}
+	} else {
+		call, _ = right.(*callExpr)
+		switch left.(type) {
+		case *stringLiteralExpr, *variableRefExpr:
+			value = left
+		}
+	}
+
+	if call == nil || value == nil {
+		return nil, false
+	}
+
+	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":
+		return ctx.parseCompareStripFuncResult(directive, call, value, !isEq), true
+	}
+	return nil, false
+}
+
+func (ctx *parseContext) parseCompareFilterFuncResult(cond *mkparser.Directive,
+	filterFuncCall *callExpr, xValue starlarkExpr, negate bool) (starlarkExpr, bool) {
+	// We handle:
+	// *  ifeq/ifneq (,$(filter v1 v2 ..., EXPR) becomes if EXPR not in/in ["v1", "v2", ...]
+	// *  ifeq/ifneq (,$(filter EXPR, v1 v2 ...) becomes if EXPR not in/in ["v1", "v2", ...]
+	if x, ok := xValue.(*stringLiteralExpr); !ok || x.literal != "" {
+		return nil, false
+	}
+	xPattern := filterFuncCall.args[0]
+	xText := filterFuncCall.args[1]
+	var xInList *stringLiteralExpr
+	var expr starlarkExpr
+	var ok bool
+	if xInList, ok = xPattern.(*stringLiteralExpr); ok && !strings.ContainsRune(xInList.literal, '%') && xText.typ() == starlarkTypeList {
+		expr = xText
+	} else if xInList, ok = xText.(*stringLiteralExpr); ok {
+		expr = xPattern
+	} else {
+		return nil, false
+	}
+	slExpr := newStringListExpr(strings.Fields(xInList.literal))
+	// Generate simpler code for the common cases:
+	if expr.typ() == starlarkTypeList {
+		if len(slExpr.items) == 1 {
+			// Checking that a string belongs to list
+			return &inExpr{isNot: negate, list: expr, expr: slExpr.items[0]}, true
+		} else {
+			return nil, false
+		}
+	} else if len(slExpr.items) == 1 {
+		return &eqExpr{left: expr, right: slExpr.items[0], isEq: !negate}, true
+	} else {
+		return &inExpr{isNot: negate, list: newStringListExpr(strings.Fields(xInList.literal)), expr: expr}, true
+	}
+}
+
+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) {
+		return &eqExpr{
+			left: &callExpr{
+				object:     xCall.args[1],
+				name:       "find",
+				args:       []starlarkExpr{xCall.args[0]},
+				returnType: starlarkTypeInt,
+			},
+			right: &intLiteralExpr{-1},
+			isEq:  !negate,
+		}
+	} else if s, ok := maybeString(xValue); ok {
+		if s2, ok := maybeString(xCall.args[0]); ok && s == s2 {
+			return &eqExpr{
+				left: &callExpr{
+					object:     xCall.args[1],
+					name:       "find",
+					args:       []starlarkExpr{xCall.args[0]},
+					returnType: starlarkTypeInt,
+				},
+				right: &intLiteralExpr{-1},
+				isEq:  negate,
+			}
+		}
+	}
+	return ctx.newBadExpr(directive, "$(findstring) can only be compared to nothing or its first argument")
+}
+
+func (ctx *parseContext) parseCompareStripFuncResult(directive *mkparser.Directive,
+	xCall *callExpr, xValue starlarkExpr, negate bool) starlarkExpr {
+	if _, ok := xValue.(*stringLiteralExpr); !ok {
+		return ctx.newBadExpr(directive, "strip result can be compared only to string: %s", xValue)
+	}
+	return &eqExpr{
+		left: &callExpr{
+			name:       "strip",
+			args:       xCall.args,
+			returnType: starlarkTypeString,
+		},
+		right: xValue, isEq: !negate}
+}
+
+func (ctx *parseContext) maybeParseFunctionCall(node mkparser.Node, ref *mkparser.MakeString) (name string, args *mkparser.MakeString, ok bool) {
+	ref.TrimLeftSpaces()
+	ref.TrimRightSpaces()
+
+	words := ref.SplitN(" ", 2)
+	if !words[0].Const() {
+		return "", nil, false
+	}
+
+	name = words[0].Dump()
+	args = mkparser.SimpleMakeString("", words[0].Pos())
+	if len(words) >= 2 {
+		args = words[1]
+	}
+	args.TrimLeftSpaces()
+	if name == "call" {
+		words = args.SplitN(",", 2)
+		if words[0].Empty() || !words[0].Const() {
+			return "", nil, false
+		}
+		name = words[0].Dump()
+		if len(words) < 2 {
+			args = mkparser.SimpleMakeString("", words[0].Pos())
+		} else {
+			args = words[1]
+		}
+	}
+	ok = true
+	return
+}
+
+// parses $(...), returning an expression
+func (ctx *parseContext) parseReference(node mkparser.Node, ref *mkparser.MakeString) starlarkExpr {
+	ref.TrimLeftSpaces()
+	ref.TrimRightSpaces()
+	refDump := ref.Dump()
+
+	// 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 it is a single word, it can be a simple variable
+	// reference or a function call
+	if len(words) == 1 && !isMakeControlFunc(refDump) && refDump != "shell" && refDump != "eval" {
+		if strings.HasPrefix(refDump, soongNsPrefix) {
+			// TODO (asmundak): if we find many, maybe handle them.
+			return ctx.newBadExpr(node, "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: %s", refDump)
+		}
+		// Handle substitution references: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html
+		if strings.Contains(refDump, ":") {
+			parts := strings.SplitN(refDump, ":", 2)
+			substParts := strings.SplitN(parts[1], "=", 2)
+			if len(substParts) < 2 || strings.Count(substParts[0], "%") > 1 {
+				return ctx.newBadExpr(node, "Invalid substitution reference")
+			}
+			if !strings.Contains(substParts[0], "%") {
+				if strings.Contains(substParts[1], "%") {
+					return ctx.newBadExpr(node, "A substitution reference must have a %% in the \"before\" part of the substitution if it has one in the \"after\" part.")
+				}
+				substParts[0] = "%" + substParts[0]
+				substParts[1] = "%" + substParts[1]
+			}
+			v := ctx.addVariable(parts[0])
+			if v == nil {
+				return ctx.newBadExpr(node, "unknown variable %s", refDump)
+			}
+			return &callExpr{
+				name:       baseName + ".mkpatsubst",
+				returnType: starlarkTypeString,
+				args: []starlarkExpr{
+					&stringLiteralExpr{literal: substParts[0]},
+					&stringLiteralExpr{literal: substParts[1]},
+					NewVariableRefExpr(v),
+				},
+			}
+		}
+		if v := ctx.addVariable(refDump); v != nil {
+			return NewVariableRefExpr(v)
+		}
+		return ctx.newBadExpr(node, "unknown variable %s", refDump)
+	}
+
+	if name, args, ok := ctx.maybeParseFunctionCall(node, ref); ok {
+		if kf, found := knownFunctions[name]; found {
+			return kf.parse(ctx, node, args)
+		} else {
+			return ctx.newBadExpr(node, "cannot handle invoking %s", name)
+		}
+	} else {
+		return ctx.newBadExpr(node, "cannot handle %s", refDump)
+	}
+}
+
+type simpleCallParser struct {
+	name       string
+	returnType starlarkType
+	addGlobals bool
+	addHandle  bool
+}
+
+func (p *simpleCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	expr := &callExpr{name: p.name, returnType: p.returnType}
+	if p.addGlobals {
+		expr.args = append(expr.args, &globalsExpr{})
+	}
+	if p.addHandle {
+		expr.args = append(expr.args, &identifierExpr{name: "handle"})
+	}
+	for _, arg := range args.Split(",") {
+		arg.TrimLeftSpaces()
+		arg.TrimRightSpaces()
+		x := ctx.parseMakeString(node, arg)
+		if xBad, ok := x.(*badExpr); ok {
+			return xBad
+		}
+		expr.args = append(expr.args, x)
+	}
+	return expr
+}
+
+type makeControlFuncParser struct {
+	name string
+}
+
+func (p *makeControlFuncParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	// Make control functions need special treatment as everything
+	// after the name is a single text argument
+	x := ctx.parseMakeString(node, args)
+	if xBad, ok := x.(*badExpr); ok {
+		return xBad
+	}
+	return &callExpr{
+		name: p.name,
+		args: []starlarkExpr{
+			&stringLiteralExpr{ctx.script.mkFile},
+			x,
+		},
+		returnType: starlarkTypeUnknown,
+	}
+}
+
+type shellCallParser struct{}
+
+func (p *shellCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	// Shell functions need special treatment as everything
+	// after the name is a single text argument
+	x := ctx.parseMakeString(node, args)
+	if xBad, ok := x.(*badExpr); ok {
+		return xBad
+	}
+	return &callExpr{
+		name:       baseName + ".shell",
+		args:       []starlarkExpr{x},
+		returnType: starlarkTypeUnknown,
+	}
+}
+
+type myDirCallParser struct{}
+
+func (p *myDirCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	if !args.Empty() {
+		return ctx.newBadExpr(node, "my-dir function cannot have any arguments passed to it.")
+	}
+	return &stringLiteralExpr{literal: filepath.Dir(ctx.script.mkFile)}
+}
+
+type isProductInListCallParser struct{}
+
+func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	if args.Empty() {
+		return ctx.newBadExpr(node, "is-product-in-list requires an argument")
+	}
+	return &inExpr{
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_PRODUCT")),
+		list:  maybeConvertToStringList(ctx.parseMakeString(node, args)),
+		isNot: false,
+	}
+}
+
+type isVendorBoardPlatformCallParser struct{}
+
+func (p *isVendorBoardPlatformCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	if args.Empty() || !identifierFullMatchRegex.MatchString(args.Dump()) {
+		return ctx.newBadExpr(node, "cannot handle non-constant argument to is-vendor-board-platform")
+	}
+	return &inExpr{
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
+		list:  NewVariableRefExpr(ctx.addVariable(args.Dump() + "_BOARD_PLATFORMS")),
+		isNot: false,
+	}
+}
+
+type isVendorBoardQcomCallParser struct{}
+
+func (p *isVendorBoardQcomCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	if !args.Empty() {
+		return ctx.newBadExpr(node, "is-vendor-board-qcom does not accept any arguments")
+	}
+	return &inExpr{
+		expr:  NewVariableRefExpr(ctx.addVariable("TARGET_BOARD_PLATFORM")),
+		list:  NewVariableRefExpr(ctx.addVariable("QCOM_BOARD_PLATFORMS")),
+		isNot: false,
+	}
+}
+
+type substCallParser struct {
+	fname string
+}
+
+func (p *substCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 3 {
+		return ctx.newBadExpr(node, "%s function should have 3 arguments", p.fname)
+	}
+	from := ctx.parseMakeString(node, words[0])
+	if xBad, ok := from.(*badExpr); ok {
+		return xBad
+	}
+	to := ctx.parseMakeString(node, words[1])
+	if xBad, ok := to.(*badExpr); ok {
+		return xBad
+	}
+	words[2].TrimLeftSpaces()
+	words[2].TrimRightSpaces()
+	obj := ctx.parseMakeString(node, words[2])
+	typ := obj.typ()
+	if typ == starlarkTypeString && p.fname == "subst" {
+		// Optimization: if it's $(subst from, to, string), emit string.replace(from, to)
+		return &callExpr{
+			object:     obj,
+			name:       "replace",
+			args:       []starlarkExpr{from, to},
+			returnType: typ,
+		}
+	}
+	return &callExpr{
+		name:       baseName + ".mk" + p.fname,
+		args:       []starlarkExpr{from, to, obj},
+		returnType: obj.typ(),
+	}
+}
+
+type ifCallParser struct{}
+
+func (p *ifCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 2 && len(words) != 3 {
+		return ctx.newBadExpr(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))
+	}
+	condition := ctx.parseMakeString(node, words[0])
+	ifTrue := ctx.parseMakeString(node, words[1])
+	var ifFalse starlarkExpr
+	if len(words) == 3 {
+		ifFalse = ctx.parseMakeString(node, words[2])
+	} else {
+		switch ifTrue.typ() {
+		case starlarkTypeList:
+			ifFalse = &listExpr{items: []starlarkExpr{}}
+		case starlarkTypeInt:
+			ifFalse = &intLiteralExpr{literal: 0}
+		case starlarkTypeBool:
+			ifFalse = &boolLiteralExpr{literal: false}
+		default:
+			ifFalse = &stringLiteralExpr{literal: ""}
+		}
+	}
+	return &ifExpr{
+		condition,
+		ifTrue,
+		ifFalse,
+	}
+}
+
+type ifCallNodeParser struct{}
+
+func (p *ifCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+	words := args.Split(",")
+	if len(words) != 2 && len(words) != 3 {
+		return []starlarkNode{ctx.newBadNode(node, "if function should have 2 or 3 arguments, found "+strconv.Itoa(len(words)))}
+	}
+
+	ifn := &ifNode{expr: ctx.parseMakeString(node, words[0])}
+	cases := []*switchCase{
+		{
+			gate:  ifn,
+			nodes: ctx.parseNodeMakeString(node, words[1]),
+		},
+	}
+	if len(words) == 3 {
+		cases = append(cases, &switchCase{
+			gate:  &elseNode{},
+			nodes: ctx.parseNodeMakeString(node, words[2]),
+		})
+	}
+	if len(cases) == 2 {
+		if len(cases[1].nodes) == 0 {
+			// Remove else branch if it has no contents
+			cases = cases[:1]
+		} else if len(cases[0].nodes) == 0 {
+			// If the if branch has no contents but the else does,
+			// move them to the if and negate its condition
+			ifn.expr = negateExpr(ifn.expr)
+			cases[0].nodes = cases[1].nodes
+			cases = cases[:1]
+		}
+	}
+
+	return []starlarkNode{&switchNode{ssCases: cases}}
+}
+
+type foreachCallParser struct{}
+
+func (p *foreachCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 3 {
+		return ctx.newBadExpr(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))
+	}
+	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+		return ctx.newBadExpr(node, "first argument to foreach function must be a simple string identifier")
+	}
+	loopVarName := words[0].Strings[0]
+	list := ctx.parseMakeString(node, words[1])
+	action := ctx.parseMakeString(node, words[2]).transform(func(expr starlarkExpr) starlarkExpr {
+		if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+			return &identifierExpr{loopVarName}
+		}
+		return nil
+	})
+
+	if list.typ() != starlarkTypeList {
+		list = &callExpr{
+			name:       baseName + ".words",
+			returnType: starlarkTypeList,
+			args:       []starlarkExpr{list},
+		}
+	}
+
+	return &foreachExpr{
+		varName: loopVarName,
+		list:    list,
+		action:  action,
+	}
+}
+
+func transformNode(node starlarkNode, transformer func(expr starlarkExpr) starlarkExpr) {
+	switch a := node.(type) {
+	case *ifNode:
+		a.expr = a.expr.transform(transformer)
+	case *switchCase:
+		transformNode(a.gate, transformer)
+		for _, n := range a.nodes {
+			transformNode(n, transformer)
+		}
+	case *switchNode:
+		for _, n := range a.ssCases {
+			transformNode(n, transformer)
+		}
+	case *exprNode:
+		a.expr = a.expr.transform(transformer)
+	case *assignmentNode:
+		a.value = a.value.transform(transformer)
+	case *foreachNode:
+		a.list = a.list.transform(transformer)
+		for _, n := range a.actions {
+			transformNode(n, transformer)
+		}
+	}
+}
+
+type foreachCallNodeParser struct{}
+
+func (p *foreachCallNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+	words := args.Split(",")
+	if len(words) != 3 {
+		return []starlarkNode{ctx.newBadNode(node, "foreach function should have 3 arguments, found "+strconv.Itoa(len(words)))}
+	}
+	if !words[0].Const() || words[0].Empty() || !identifierFullMatchRegex.MatchString(words[0].Strings[0]) {
+		return []starlarkNode{ctx.newBadNode(node, "first argument to foreach function must be a simple string identifier")}
+	}
+
+	loopVarName := words[0].Strings[0]
+
+	list := ctx.parseMakeString(node, words[1])
+	if list.typ() != starlarkTypeList {
+		list = &callExpr{
+			name:       baseName + ".words",
+			returnType: starlarkTypeList,
+			args:       []starlarkExpr{list},
+		}
+	}
+
+	actions := ctx.parseNodeMakeString(node, words[2])
+	// TODO(colefaust): Replace transforming code with something more elegant
+	for _, action := range actions {
+		transformNode(action, func(expr starlarkExpr) starlarkExpr {
+			if varRefExpr, ok := expr.(*variableRefExpr); ok && varRefExpr.ref.name() == loopVarName {
+				return &identifierExpr{loopVarName}
+			}
+			return nil
+		})
+	}
+
+	return []starlarkNode{&foreachNode{
+		varName: loopVarName,
+		list:    list,
+		actions: actions,
+	}}
+}
+
+type wordCallParser struct{}
+
+func (p *wordCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	words := args.Split(",")
+	if len(words) != 2 {
+		return ctx.newBadExpr(node, "word function should have 2 arguments")
+	}
+	var index uint64 = 0
+	if words[0].Const() {
+		index, _ = strconv.ParseUint(strings.TrimSpace(words[0].Strings[0]), 10, 64)
+	}
+	if index < 1 {
+		return ctx.newBadExpr(node, "word index should be constant positive integer")
+	}
+	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 {
+		return bad
+	}
+	index := &intLiteralExpr{0}
+	if p.isLastWord {
+		if v, ok := arg.(*variableRefExpr); ok && v.ref.name() == "MAKEFILE_LIST" {
+			return &stringLiteralExpr{ctx.script.mkFile}
+		}
+		index.literal = -1
+	}
+	if arg.typ() == starlarkTypeList {
+		return &indexExpr{arg, index}
+	}
+	return &indexExpr{&callExpr{object: arg, name: "split", returnType: starlarkTypeList}, index}
+}
+
+func parseIntegerArguments(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString, expectedArgs int) ([]starlarkExpr, error) {
+	parsedArgs := make([]starlarkExpr, 0)
+	for _, arg := range args.Split(",") {
+		expr := ctx.parseMakeString(node, arg)
+		if expr.typ() == starlarkTypeList {
+			return nil, fmt.Errorf("argument to math argument has type list, which cannot be converted to int")
+		}
+		if s, ok := maybeString(expr); ok {
+			intVal, err := strconv.Atoi(strings.TrimSpace(s))
+			if err != nil {
+				return nil, err
+			}
+			expr = &intLiteralExpr{literal: intVal}
+		} else if expr.typ() != starlarkTypeInt {
+			expr = &callExpr{
+				name:       "int",
+				args:       []starlarkExpr{expr},
+				returnType: starlarkTypeInt,
+			}
+		}
+		parsedArgs = append(parsedArgs, expr)
+	}
+	if len(parsedArgs) != expectedArgs {
+		return nil, fmt.Errorf("function should have %d arguments", expectedArgs)
+	}
+	return parsedArgs, nil
+}
+
+type mathComparisonCallParser struct {
+	op string
+}
+
+func (p *mathComparisonCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
+	if err != nil {
+		return ctx.newBadExpr(node, err.Error())
+	}
+	return &binaryOpExpr{
+		left:       parsedArgs[0],
+		right:      parsedArgs[1],
+		op:         p.op,
+		returnType: starlarkTypeBool,
+	}
+}
+
+type mathMaxOrMinCallParser struct {
+	function string
+}
+
+func (p *mathMaxOrMinCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	parsedArgs, err := parseIntegerArguments(ctx, node, args, 2)
+	if err != nil {
+		return ctx.newBadExpr(node, err.Error())
+	}
+	return &callExpr{
+		object:     nil,
+		name:       p.function,
+		args:       parsedArgs,
+		returnType: starlarkTypeInt,
+	}
+}
+
+type evalNodeParser struct{}
+
+func (p *evalNodeParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) []starlarkNode {
+	parser := mkparser.NewParser("Eval expression", strings.NewReader(args.Dump()))
+	nodes, errs := parser.Parse()
+	if errs != nil {
+		return []starlarkNode{ctx.newBadNode(node, "Unable to parse eval statement")}
+	}
+
+	if len(nodes) == 0 {
+		return []starlarkNode{}
+	} else if len(nodes) == 1 {
+		switch n := nodes[0].(type) {
+		case *mkparser.Assignment:
+			if n.Name.Const() {
+				return ctx.handleAssignment(n)
+			}
+		case *mkparser.Comment:
+			return []starlarkNode{&commentNode{strings.TrimSpace("#" + n.Comment)}}
+		}
+	}
+
+	return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments and comments are supported")}
+}
+
+func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
+	if mk.Const() {
+		return &stringLiteralExpr{mk.Dump()}
+	}
+	if mkRef, ok := mk.SingleVariable(); ok {
+		return ctx.parseReference(node, mkRef)
+	}
+	// If we reached here, it's neither string literal nor a simple variable,
+	// we need a full-blown interpolation node that will generate
+	// "a%b%c" % (X, Y) for a$(X)b$(Y)c
+	parts := make([]starlarkExpr, len(mk.Variables)+len(mk.Strings))
+	for i := 0; i < len(parts); i++ {
+		if i%2 == 0 {
+			parts[i] = &stringLiteralExpr{literal: mk.Strings[i/2]}
+		} else {
+			parts[i] = ctx.parseReference(node, mk.Variables[i/2].Name)
+			if x, ok := parts[i].(*badExpr); ok {
+				return x
+			}
+		}
+	}
+	return NewInterpolateExpr(parts)
+}
+
+func (ctx *parseContext) parseNodeMakeString(node mkparser.Node, mk *mkparser.MakeString) []starlarkNode {
+	// Discard any constant values in the make string, as they would be top level
+	// string literals and do nothing.
+	result := make([]starlarkNode, 0, len(mk.Variables))
+	for i := range mk.Variables {
+		result = append(result, ctx.handleVariable(&mk.Variables[i])...)
+	}
+	return result
+}
+
+// Handles the statements whose treatment is the same in all contexts: comment,
+// assignment, variable (which is a macro call in reality) and all constructs that
+// do not handle in any context ('define directive and any unrecognized stuff).
+func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) []starlarkNode {
+	var result []starlarkNode
+	switch x := node.(type) {
+	case *mkparser.Comment:
+		if n, handled := ctx.maybeHandleAnnotation(x); handled && n != nil {
+			result = []starlarkNode{n}
+		} else if !handled {
+			result = []starlarkNode{&commentNode{strings.TrimSpace("#" + x.Comment)}}
+		}
+	case *mkparser.Assignment:
+		result = ctx.handleAssignment(x)
+	case *mkparser.Variable:
+		result = ctx.handleVariable(x)
+	case *mkparser.Directive:
+		switch x.Name {
+		case "define":
+			if res := ctx.maybeHandleDefine(x); res != nil {
+				result = []starlarkNode{res}
+			}
+		case "include", "-include":
+			result = ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+		case "ifeq", "ifneq", "ifdef", "ifndef":
+			result = []starlarkNode{ctx.handleIfBlock(x)}
+		default:
+			result = []starlarkNode{ctx.newBadNode(x, "unexpected directive %s", x.Name)}
+		}
+	default:
+		result = []starlarkNode{ctx.newBadNode(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))}
+	}
+
+	// Clear the includeTops after each non-comment statement
+	// so that include annotations placed on certain statements don't apply
+	// globally for the rest of the makefile was well.
+	if _, wasComment := node.(*mkparser.Comment); !wasComment {
+		ctx.atTopOfMakefile = false
+		ctx.includeTops = []string{}
+	}
+
+	if result == nil {
+		result = []starlarkNode{}
+	}
+
+	return result
+}
+
+// The types allowed in a type_hint
+var typeHintMap = map[string]starlarkType{
+	"string": starlarkTypeString,
+	"list":   starlarkTypeList,
+}
+
+// Processes annotation. An annotation is a comment that starts with #RBC# and provides
+// a conversion hint -- say, where to look for the dynamically calculated inherit/include
+// paths. Returns true if the comment was a successfully-handled annotation.
+func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) (starlarkNode, bool) {
+	maybeTrim := func(s, prefix string) (string, bool) {
+		if strings.HasPrefix(s, prefix) {
+			return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
+		}
+		return s, false
+	}
+	annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
+	if !ok {
+		return nil, false
+	}
+	if p, ok := maybeTrim(annotation, "include_top"); ok {
+		// Don't allow duplicate include tops, because then we will generate
+		// invalid starlark code. (duplicate keys in the _entry dictionary)
+		for _, top := range ctx.includeTops {
+			if top == p {
+				return nil, true
+			}
+		}
+		ctx.includeTops = append(ctx.includeTops, p)
+		return nil, true
+	} else if p, ok := maybeTrim(annotation, "type_hint"); ok {
+		// Type hints must come at the beginning the file, to avoid confusion
+		// if a type hint was specified later and thus only takes effect for half
+		// of the file.
+		if !ctx.atTopOfMakefile {
+			return ctx.newBadNode(cnode, "type_hint annotations must come before the first Makefile statement"), true
+		}
+
+		parts := strings.Fields(p)
+		if len(parts) <= 1 {
+			return ctx.newBadNode(cnode, "Invalid type_hint annotation: %s. Must be a variable type followed by a list of variables of that type", p), true
+		}
+
+		var varType starlarkType
+		if varType, ok = typeHintMap[parts[0]]; !ok {
+			varType = starlarkTypeUnknown
+		}
+		if varType == starlarkTypeUnknown {
+			return ctx.newBadNode(cnode, "Invalid type_hint annotation. Only list/string types are accepted, found %s", parts[0]), true
+		}
+
+		for _, name := range parts[1:] {
+			// Don't allow duplicate type hints
+			if _, ok := ctx.typeHints[name]; ok {
+				return ctx.newBadNode(cnode, "Duplicate type hint for variable %s", name), true
+			}
+			ctx.typeHints[name] = varType
+		}
+		return nil, true
+	}
+	return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
+}
+
+func (ctx *parseContext) loadedModulePath(path string) string {
+	// During the transition to Roboleaf some of the product configuration files
+	// will be converted and checked in while the others will be generated on the fly
+	// and run. The runner  (rbcrun application) accommodates this by allowing three
+	// different ways to specify the loaded file location:
+	//  1) load(":<file>",...) loads <file> from the same directory
+	//  2) load("//path/relative/to/source/root:<file>", ...) loads <file> source tree
+	//  3) load("/absolute/path/to/<file> absolute path
+	// If the file being generated and the file it wants to load are in the same directory,
+	// generate option 1.
+	// Otherwise, if output directory is not specified, generate 2)
+	// Finally, if output directory has been specified and the file being generated and
+	// the file it wants to load from are in the different directories, generate 2) or 3):
+	//  * if the file being loaded exists in the source tree, generate 2)
+	//  * otherwise, generate 3)
+	// Finally, figure out the loaded module path and name and create a node for it
+	loadedModuleDir := filepath.Dir(path)
+	base := filepath.Base(path)
+	loadedModuleName := strings.TrimSuffix(base, filepath.Ext(base)) + ctx.outputSuffix
+	if loadedModuleDir == filepath.Dir(ctx.script.mkFile) {
+		return ":" + loadedModuleName
+	}
+	if ctx.outputDir == "" {
+		return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
+	}
+	if _, err := os.Stat(filepath.Join(loadedModuleDir, loadedModuleName)); err == nil {
+		return fmt.Sprintf("//%s:%s", loadedModuleDir, loadedModuleName)
+	}
+	return filepath.Join(ctx.outputDir, loadedModuleDir, loadedModuleName)
+}
+
+func (ctx *parseContext) addSoongNamespace(ns string) {
+	if _, ok := ctx.soongNamespaces[ns]; ok {
+		return
+	}
+	ctx.soongNamespaces[ns] = make(map[string]bool)
+}
+
+func (ctx *parseContext) hasSoongNamespace(name string) bool {
+	_, ok := ctx.soongNamespaces[name]
+	return ok
+}
+
+func (ctx *parseContext) updateSoongNamespace(replace bool, namespaceName string, varNames []string) {
+	ctx.addSoongNamespace(namespaceName)
+	vars := ctx.soongNamespaces[namespaceName]
+	if replace {
+		vars = make(map[string]bool)
+		ctx.soongNamespaces[namespaceName] = vars
+	}
+	for _, v := range varNames {
+		vars[v] = true
+	}
+}
+
+func (ctx *parseContext) hasNamespaceVar(namespaceName string, varName string) bool {
+	vars, ok := ctx.soongNamespaces[namespaceName]
+	if ok {
+		_, ok = vars[varName]
+	}
+	return ok
+}
+
+func (ctx *parseContext) errorLocation(node mkparser.Node) ErrorLocation {
+	return ErrorLocation{ctx.script.mkFile, ctx.script.nodeLocator(node.Pos())}
+}
+
+func (ss *StarlarkScript) String() string {
+	return NewGenerateContext(ss).emit()
+}
+
+func (ss *StarlarkScript) SubConfigFiles() []string {
+
+	var subs []string
+	for _, src := range ss.inherited {
+		subs = append(subs, src.originalPath)
+	}
+	return subs
+}
+
+func (ss *StarlarkScript) HasErrors() bool {
+	return ss.hasErrors
+}
+
+// Convert reads and parses a makefile. If successful, parsed tree
+// is returned and then can be passed to String() to get the generated
+// Starlark file.
+func Convert(req Request) (*StarlarkScript, error) {
+	reader := req.Reader
+	if reader == nil {
+		mkContents, err := ioutil.ReadFile(req.MkFile)
+		if err != nil {
+			return nil, err
+		}
+		reader = bytes.NewBuffer(mkContents)
+	}
+	parser := mkparser.NewParser(req.MkFile, reader)
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		for _, e := range errs {
+			fmt.Fprintln(os.Stderr, "ERROR:", e)
+		}
+		return nil, fmt.Errorf("bad makefile %s", req.MkFile)
+	}
+	starScript := &StarlarkScript{
+		moduleName:     moduleNameForFile(req.MkFile),
+		mkFile:         req.MkFile,
+		traceCalls:     req.TraceCalls,
+		sourceFS:       req.SourceFS,
+		makefileFinder: req.MakefileFinder,
+		nodeLocator:    func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
+		nodes:          make([]starlarkNode, 0),
+	}
+	ctx := newParseContext(starScript, nodes)
+	ctx.outputSuffix = req.OutputSuffix
+	ctx.outputDir = req.OutputDir
+	ctx.errorLogger = req.ErrorLogger
+	if len(req.TracedVariables) > 0 {
+		ctx.tracedVariables = make(map[string]bool)
+		for _, v := range req.TracedVariables {
+			ctx.tracedVariables[v] = true
+		}
+	}
+	for ctx.hasNodes() && ctx.fatalError == nil {
+		starScript.nodes = append(starScript.nodes, ctx.handleSimpleStatement(ctx.getNode())...)
+	}
+	if ctx.fatalError != nil {
+		return nil, ctx.fatalError
+	}
+	return starScript, nil
+}
+
+func Launcher(mainModuleUri, inputVariablesUri, mainModuleName string) string {
+	var buf bytes.Buffer
+	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
+	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
+	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
+	fmt.Fprintf(&buf, "%s(%s(%q, init, input_variables_init))\n", cfnPrintVars, cfnMain, mainModuleName)
+	return buf.String()
+}
+
+func BoardLauncher(mainModuleUri string, inputVariablesUri string) string {
+	var buf bytes.Buffer
+	fmt.Fprintf(&buf, "load(%q, %q)\n", baseUri, baseName)
+	fmt.Fprintf(&buf, "load(%q, \"init\")\n", mainModuleUri)
+	fmt.Fprintf(&buf, "load(%q, input_variables_init = \"init\")\n", inputVariablesUri)
+	fmt.Fprintf(&buf, "%s(%s(init, input_variables_init))\n", cfnPrintVars, cfnBoardMain)
+	return buf.String()
+}
+
+func MakePath2ModuleName(mkPath string) string {
+	return strings.TrimSuffix(mkPath, filepath.Ext(mkPath))
+}
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
new file mode 100644
index 0000000..de75129
--- /dev/null
+++ b/mk2rbc/mk2rbc_test.go
@@ -0,0 +1,1619 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"bytes"
+	"io/fs"
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+var testCases = []struct {
+	desc     string
+	mkname   string
+	in       string
+	expected string
+}{
+	{
+		desc:   "Comment",
+		mkname: "product.mk",
+		in: `
+# Comment
+# FOO= a\
+     b
+`,
+		expected: `# Comment
+# FOO= a
+#     b
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+`,
+	},
+	{
+		desc:   "Name conversion",
+		mkname: "path/bar-baz.mk",
+		in: `
+# Comment
+`,
+		expected: `# Comment
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+`,
+	},
+	{
+		desc:   "Item variable",
+		mkname: "pixel3.mk",
+		in: `
+PRODUCT_NAME := Pixel 3
+PRODUCT_MODEL :=
+local_var = foo
+local-var-with-dashes := bar
+$(warning local-var-with-dashes: $(local-var-with-dashes))
+GLOBAL-VAR-WITH-DASHES := baz
+$(warning GLOBAL-VAR-WITH-DASHES: $(GLOBAL-VAR-WITH-DASHES))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_NAME"] = "Pixel 3"
+  cfg["PRODUCT_MODEL"] = ""
+  _local_var = "foo"
+  _local_var_with_dashes = "bar"
+  rblf.mkwarning("pixel3.mk", "local-var-with-dashes: %s" % _local_var_with_dashes)
+  g["GLOBAL-VAR-WITH-DASHES"] = "baz"
+  rblf.mkwarning("pixel3.mk", "GLOBAL-VAR-WITH-DASHES: %s" % g["GLOBAL-VAR-WITH-DASHES"])
+`,
+	},
+	{
+		desc:   "List variable",
+		mkname: "pixel4.mk",
+		in: `
+PRODUCT_PACKAGES = package1  package2
+PRODUCT_COPY_FILES += file2:target
+PRODUCT_PACKAGES += package3
+PRODUCT_COPY_FILES =
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_PACKAGES"] = [
+      "package1",
+      "package2",
+  ]
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += ["file2:target"]
+  cfg["PRODUCT_PACKAGES"] += ["package3"]
+  cfg["PRODUCT_COPY_FILES"] = []
+`,
+	},
+	{
+		desc:   "Unknown function",
+		mkname: "product.mk",
+		in: `
+PRODUCT_NAME := $(call foo1, bar)
+PRODUCT_NAME := $(call foo0)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "cannot handle invoking foo1")
+  rblf.mk2rbc_error("product.mk:3", "cannot handle invoking foo0")
+`,
+	},
+	{
+		desc:   "Inherit configuration always",
+		mkname: "product.mk",
+		in: `
+$(call inherit-product, part.mk)
+ifdef PRODUCT_NAME
+$(call inherit-product, part1.mk)
+else # Comment
+$(call inherit-product, $(LOCAL_PATH)/part.mk)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load(":part.star", _part_init = "init")
+load(":part1.star|init", _part1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "part", _part_init)
+  if cfg.get("PRODUCT_NAME", ""):
+    if not _part1_init:
+      rblf.mkerror("product.mk", "Cannot find %s" % (":part1.star"))
+    rblf.inherit(handle, "part1", _part1_init)
+  else:
+    # Comment
+    rblf.inherit(handle, "part", _part_init)
+`,
+	},
+	{
+		desc:   "Inherit configuration if it exists",
+		mkname: "product.mk",
+		in: `
+$(call inherit-product-if-exists, part.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load(":part.star|init", _part_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if _part_init:
+    rblf.inherit(handle, "part", _part_init)
+`,
+	},
+
+	{
+		desc:   "Include configuration",
+		mkname: "product.mk",
+		in: `
+include part.mk
+ifdef PRODUCT_NAME
+include part1.mk
+else
+-include $(LOCAL_PATH)/part1.mk)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load(":part.star", _part_init = "init")
+load(":part1.star|init", _part1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _part_init(g, handle)
+  if cfg.get("PRODUCT_NAME", ""):
+    if not _part1_init:
+      rblf.mkerror("product.mk", "Cannot find %s" % (":part1.star"))
+    _part1_init(g, handle)
+  else:
+    if _part1_init != None:
+      _part1_init(g, handle)
+`,
+	},
+
+	{
+		desc:   "Synonymous inherited configurations",
+		mkname: "path/product.mk",
+		in: `
+$(call inherit-product, */font.mk)
+$(call inherit-product, $(sort $(wildcard */font.mk)))
+$(call inherit-product, $(wildcard */font.mk))
+
+include */font.mk
+include $(sort $(wildcard */font.mk))
+include $(wildcard */font.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//bar:font.star", _font_init = "init")
+load("//foo:font.star", _font1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.inherit(handle, "bar/font", _font_init)
+  rblf.inherit(handle, "foo/font", _font1_init)
+  rblf.inherit(handle, "bar/font", _font_init)
+  rblf.inherit(handle, "foo/font", _font1_init)
+  rblf.inherit(handle, "bar/font", _font_init)
+  rblf.inherit(handle, "foo/font", _font1_init)
+  _font_init(g, handle)
+  _font1_init(g, handle)
+  _font_init(g, handle)
+  _font1_init(g, handle)
+  _font_init(g, handle)
+  _font1_init(g, handle)
+`,
+	},
+	{
+		desc:   "Directive define",
+		mkname: "product.mk",
+		in: `
+define some-macro
+    $(info foo)
+endef
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "define is not supported: some-macro")
+`,
+	},
+	{
+		desc:   "Ifdef",
+		mkname: "product.mk",
+		in: `
+ifdef  PRODUCT_NAME
+  PRODUCT_NAME = gizmo
+else
+endif
+local_var :=
+ifdef local_var
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if cfg.get("PRODUCT_NAME", ""):
+    cfg["PRODUCT_NAME"] = "gizmo"
+  else:
+    pass
+  _local_var = ""
+  if _local_var:
+    pass
+`,
+	},
+	{
+		desc:   "Simple functions",
+		mkname: "product.mk",
+		in: `
+$(warning this is the warning)
+$(warning)
+$(warning # this warning starts with a pound)
+$(warning this warning has a # in the middle)
+$(info this is the info)
+$(error this is the error)
+PRODUCT_NAME:=$(shell echo *)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mkwarning("product.mk", "this is the warning")
+  rblf.mkwarning("product.mk", "")
+  rblf.mkwarning("product.mk", "# this warning starts with a pound")
+  rblf.mkwarning("product.mk", "this warning has a # in the middle")
+  rblf.mkinfo("product.mk", "this is the info")
+  rblf.mkerror("product.mk", "this is the error")
+  cfg["PRODUCT_NAME"] = rblf.shell("echo *")
+`,
+	},
+	{
+		desc:   "Empty if",
+		mkname: "product.mk",
+		in: `
+ifdef PRODUCT_NAME
+# Comment
+else
+  TARGET_COPY_OUT_RECOVERY := foo
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if cfg.get("PRODUCT_NAME", ""):
+    # Comment
+    pass
+  else:
+    rblf.mk2rbc_error("product.mk:5", "cannot set predefined variable TARGET_COPY_OUT_RECOVERY to \"foo\", its value should be \"recovery\"")
+`,
+	},
+	{
+		desc:   "if/else/endif",
+		mkname: "product.mk",
+		in: `
+ifndef PRODUCT_NAME
+  PRODUCT_NAME=gizmo1
+else
+  PRODUCT_NAME=gizmo2
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if not cfg.get("PRODUCT_NAME", ""):
+    cfg["PRODUCT_NAME"] = "gizmo1"
+  else:
+    cfg["PRODUCT_NAME"] = "gizmo2"
+`,
+	},
+	{
+		desc:   "else if",
+		mkname: "product.mk",
+		in: `
+ifdef  PRODUCT_NAME
+  PRODUCT_NAME = gizmo
+else ifndef PRODUCT_PACKAGES   # Comment
+endif
+	`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if cfg.get("PRODUCT_NAME", ""):
+    cfg["PRODUCT_NAME"] = "gizmo"
+  elif not cfg.get("PRODUCT_PACKAGES", []):
+    # Comment
+    pass
+`,
+	},
+	{
+		desc:   "ifeq / ifneq",
+		mkname: "product.mk",
+		in: `
+ifeq (aosp_arm, $(TARGET_PRODUCT))
+  PRODUCT_MODEL = pix2
+else
+  PRODUCT_MODEL = pix21
+endif
+ifneq (aosp_x86, $(TARGET_PRODUCT))
+  PRODUCT_MODEL = pix3
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "aosp_arm" == g["TARGET_PRODUCT"]:
+    cfg["PRODUCT_MODEL"] = "pix2"
+  else:
+    cfg["PRODUCT_MODEL"] = "pix21"
+  if "aosp_x86" != g["TARGET_PRODUCT"]:
+    cfg["PRODUCT_MODEL"] = "pix3"
+`,
+	},
+	{
+		desc:   "ifeq with soong_config_get",
+		mkname: "product.mk",
+		in: `
+ifeq (true,$(call soong_config_get,art_module,source_build))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "true" == rblf.soong_config_get(g, "art_module", "source_build"):
+    pass
+`,
+	},
+	{
+		desc:   "ifeq with $(NATIVE_COVERAGE)",
+		mkname: "product.mk",
+		in: `
+ifeq ($(NATIVE_COVERAGE),true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if g.get("NATIVE_COVERAGE", False):
+    pass
+`,
+	},
+	{
+		desc:   "Check filter result",
+		mkname: "product.mk",
+		in: `
+ifeq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+endif
+ifneq (,$(filter userdebug,$(TARGET_BUILD_VARIANT))
+endif
+ifneq (,$(filter plaf,$(PLATFORM_LIST)))
+endif
+ifeq ($(TARGET_BUILD_VARIANT), $(filter $(TARGET_BUILD_VARIANT), userdebug eng))
+endif
+ifneq (, $(filter $(TARGET_BUILD_VARIANT), userdebug eng))
+endif
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+endif
+ifneq (,$(filter true, $(v1)$(v2)))
+endif
+ifeq (,$(filter barbet coral%,$(TARGET_PRODUCT)))
+else ifneq (,$(filter barbet%,$(TARGET_PRODUCT)))
+endif
+ifeq (,$(filter-out sunfish_kasan, $(TARGET_PRODUCT)))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if not rblf.filter("userdebug eng", g["TARGET_BUILD_VARIANT"]):
+    pass
+  if rblf.filter("userdebug", g["TARGET_BUILD_VARIANT"]):
+    pass
+  if "plaf" in g.get("PLATFORM_LIST", []):
+    pass
+  if g["TARGET_BUILD_VARIANT"] == " ".join(rblf.filter(g["TARGET_BUILD_VARIANT"], "userdebug eng")):
+    pass
+  if g["TARGET_BUILD_VARIANT"] in ["userdebug", "eng"]:
+    pass
+  if rblf.filter("userdebug eng", g["TARGET_BUILD_VARIANT"]):
+    pass
+  if rblf.filter("true", "%s%s" % (_v1, _v2)):
+    pass
+  if not rblf.filter("barbet coral%", g["TARGET_PRODUCT"]):
+    pass
+  elif rblf.filter("barbet%", g["TARGET_PRODUCT"]):
+    pass
+  if not rblf.filter_out("sunfish_kasan", g["TARGET_PRODUCT"]):
+    pass
+`,
+	},
+	{
+		desc:   "Get filter result",
+		mkname: "product.mk",
+		in: `
+PRODUCT_LIST2=$(filter-out %/foo.ko,$(wildcard path/*.ko))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_LIST2"] = rblf.filter_out("%/foo.ko", rblf.expand_wildcard("path/*.ko"))
+`,
+	},
+	{
+		desc:   "filter $(VAR), values",
+		mkname: "product.mk",
+		in: `
+ifeq (,$(filter $(TARGET_PRODUCT), yukawa_gms yukawa_sei510_gms)
+  ifneq (,$(filter $(TARGET_PRODUCT), yukawa_gms)
+  endif
+endif
+
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if g["TARGET_PRODUCT"] not in ["yukawa_gms", "yukawa_sei510_gms"]:
+    if g["TARGET_PRODUCT"] == "yukawa_gms":
+      pass
+`,
+	},
+	{
+		desc:   "filter $(V1), $(V2)",
+		mkname: "product.mk",
+		in: `
+ifneq (, $(filter $(PRODUCT_LIST), $(TARGET_PRODUCT)))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.filter(g.get("PRODUCT_LIST", []), g["TARGET_PRODUCT"]):
+    pass
+`,
+	},
+	{
+		desc:   "ifeq",
+		mkname: "product.mk",
+		in: `
+ifeq (aosp, $(TARGET_PRODUCT)) # Comment
+else ifneq (, $(TARGET_PRODUCT))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "aosp" == g["TARGET_PRODUCT"]:
+    # Comment
+    pass
+  elif g["TARGET_PRODUCT"]:
+    pass
+`,
+	},
+	{
+		desc:   "Nested if",
+		mkname: "product.mk",
+		in: `
+ifdef PRODUCT_NAME
+  PRODUCT_PACKAGES = pack-if0
+  ifdef PRODUCT_MODEL
+    PRODUCT_PACKAGES = pack-if-if
+  else ifdef PRODUCT_NAME
+    PRODUCT_PACKAGES = pack-if-elif
+  else
+    PRODUCT_PACKAGES = pack-if-else
+  endif
+  PRODUCT_PACKAGES = pack-if
+else ifneq (,$(TARGET_PRODUCT))
+  PRODUCT_PACKAGES = pack-elif
+else
+  PRODUCT_PACKAGES = pack-else
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if cfg.get("PRODUCT_NAME", ""):
+    cfg["PRODUCT_PACKAGES"] = ["pack-if0"]
+    if cfg.get("PRODUCT_MODEL", ""):
+      cfg["PRODUCT_PACKAGES"] = ["pack-if-if"]
+    elif cfg.get("PRODUCT_NAME", ""):
+      cfg["PRODUCT_PACKAGES"] = ["pack-if-elif"]
+    else:
+      cfg["PRODUCT_PACKAGES"] = ["pack-if-else"]
+    cfg["PRODUCT_PACKAGES"] = ["pack-if"]
+  elif g["TARGET_PRODUCT"]:
+    cfg["PRODUCT_PACKAGES"] = ["pack-elif"]
+  else:
+    cfg["PRODUCT_PACKAGES"] = ["pack-else"]
+`,
+	},
+	{
+		desc:   "Wildcard",
+		mkname: "product.mk",
+		in: `
+ifeq (,$(wildcard foo.mk))
+endif
+ifneq (,$(wildcard foo*.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"):
+    pass
+  if rblf.file_wildcard_exists("foo*.mk"):
+    pass
+`,
+	},
+	{
+		desc:   "if with interpolation",
+		mkname: "product.mk",
+		in: `
+ifeq ($(VARIABLE1)text$(VARIABLE2),true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "%stext%s" % (g.get("VARIABLE1", ""), g.get("VARIABLE2", "")) == "true":
+    pass
+`,
+	},
+	{
+		desc:   "ifneq $(X),true",
+		mkname: "product.mk",
+		in: `
+ifneq ($(VARIABLE),true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if g.get("VARIABLE", "") != "true":
+    pass
+`,
+	},
+	{
+		desc:   "Const neq",
+		mkname: "product.mk",
+		in: `
+ifneq (1,0)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "1" != "0":
+    pass
+`,
+	},
+	{
+		desc:   "is-board calls",
+		mkname: "product.mk",
+		in: `
+ifeq ($(call is-board-platform-in-list,msm8998), true)
+else ifneq ($(call is-board-platform,copper),true)
+else ifneq ($(call is-vendor-board-platform,QCOM),true)
+else ifeq ($(call is-product-in-list, $(PLATFORM_LIST)), true)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.board_platform_in(g, "msm8998"):
+    pass
+  elif not rblf.board_platform_is(g, "copper"):
+    pass
+  elif g.get("TARGET_BOARD_PLATFORM", "") not in g.get("QCOM_BOARD_PLATFORMS", ""):
+    pass
+  elif g["TARGET_PRODUCT"] in g.get("PLATFORM_LIST", []):
+    pass
+`,
+	},
+	{
+		desc:   "new is-board calls",
+		mkname: "product.mk",
+		in: `
+ifneq (,$(call is-board-platform-in-list2,msm8998 $(X))
+else ifeq (,$(call is-board-platform2,copper)
+else ifneq (,$(call is-vendor-board-qcom))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.board_platform_in(g, "msm8998 %s" % g.get("X", "")):
+    pass
+  elif not rblf.board_platform_is(g, "copper"):
+    pass
+  elif g.get("TARGET_BOARD_PLATFORM", "") in g.get("QCOM_BOARD_PLATFORMS", ""):
+    pass
+`,
+	},
+	{
+		desc:   "findstring call",
+		mkname: "product.mk",
+		in: `
+result := $(findstring a,a b c)
+result := $(findstring b,x y z)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _result = rblf.findstring("a", "a b c")
+  _result = rblf.findstring("b", "x y z")
+`,
+	},
+	{
+		desc:   "findstring in if statement",
+		mkname: "product.mk",
+		in: `
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+endif
+ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),)
+endif
+ifeq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
+ifneq ($(findstring foo,$(PRODUCT_PACKAGES)),foo)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
+    pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") != -1:
+    pass
+  if (cfg.get("PRODUCT_PACKAGES", [])).find("foo") == -1:
+    pass
+`,
+	},
+	{
+		desc:   "rhs call",
+		mkname: "product.mk",
+		in: `
+PRODUCT_COPY_FILES = $(call add-to-product-copy-files-if-exists, path:distpath) \
+ $(call find-copy-subdir-files, *, fromdir, todir) $(wildcard foo.*)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_COPY_FILES"] = (rblf.copy_if_exists("path:distpath") +
+      rblf.find_and_copy("*", "fromdir", "todir") +
+      rblf.expand_wildcard("foo.*"))
+`,
+	},
+	{
+		desc:   "inferred type",
+		mkname: "product.mk",
+		in: `
+HIKEY_MODS := $(wildcard foo/*.ko)
+BOARD_VENDOR_KERNEL_MODULES += $(HIKEY_MODS)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["HIKEY_MODS"] = rblf.expand_wildcard("foo/*.ko")
+  g.setdefault("BOARD_VENDOR_KERNEL_MODULES", [])
+  g["BOARD_VENDOR_KERNEL_MODULES"] += g["HIKEY_MODS"]
+`,
+	},
+	{
+		desc:   "list with vars",
+		mkname: "product.mk",
+		in: `
+PRODUCT_COPY_FILES += path1:$(TARGET_PRODUCT)/path1 $(PRODUCT_MODEL)/path2:$(TARGET_PRODUCT)/path2
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += (("path1:%s/path1" % g["TARGET_PRODUCT"]).split() +
+      ("%s/path2:%s/path2" % (cfg.get("PRODUCT_MODEL", ""), g["TARGET_PRODUCT"])).split())
+`,
+	},
+	{
+		desc:   "misc calls",
+		mkname: "product.mk",
+		in: `
+$(call enforce-product-packages-exist,)
+$(call enforce-product-packages-exist, foo)
+$(call require-artifacts-in-path, foo, bar)
+$(call require-artifacts-in-path-relaxed, foo, bar)
+$(call dist-for-goals, goal, from:to)
+$(call add-product-dex-preopt-module-config,MyModule,disable)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  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")
+  rblf.add_product_dex_preopt_module_config(handle, "MyModule", "disable")
+`,
+	},
+	{
+		desc:   "list with functions",
+		mkname: "product.mk",
+		in: `
+PRODUCT_COPY_FILES := $(call find-copy-subdir-files,*.kl,from1,to1) \
+ $(call find-copy-subdir-files,*.kc,from2,to2) \
+ foo bar
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_COPY_FILES"] = (rblf.find_and_copy("*.kl", "from1", "to1") +
+      rblf.find_and_copy("*.kc", "from2", "to2") +
+      [
+          "foo",
+          "bar",
+      ])
+`,
+	},
+	{
+		desc:   "Text functions",
+		mkname: "product.mk",
+		in: `
+PRODUCT_COPY_FILES := $(addprefix pfx-,a b c)
+PRODUCT_COPY_FILES := $(addsuffix .sff, a b c)
+PRODUCT_NAME := $(word 1, $(subst ., ,$(TARGET_BOARD_PLATFORM)))
+$(info $(patsubst %.pub,$(PRODUCT_NAME)%,$(PRODUCT_ADB_KEYS)))
+$(info $$(dir foo/bar): $(dir foo/bar))
+$(info $(firstword $(PRODUCT_COPY_FILES)))
+$(info $(lastword $(PRODUCT_COPY_FILES)))
+$(info $(dir $(lastword $(MAKEFILE_LIST))))
+$(info $(dir $(lastword $(PRODUCT_COPY_FILES))))
+$(info $(dir $(lastword $(foobar))))
+$(info $(abspath foo/bar))
+$(info $(notdir foo/bar))
+$(call add_soong_config_namespace,snsconfig)
+$(call add_soong_config_var_value,snsconfig,imagetype,odm_image)
+$(call soong_config_set, snsconfig, foo, foo_value)
+$(call soong_config_append, snsconfig, bar, bar_value)
+PRODUCT_COPY_FILES := $(call copy-files,$(wildcard foo*.mk),etc)
+PRODUCT_COPY_FILES := $(call product-copy-files-by-pattern,from/%,to/%,a b c)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  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]
+  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.abspath("foo/bar"))
+  rblf.mkinfo("product.mk", rblf.notdir("foo/bar"))
+  rblf.soong_config_namespace(g, "snsconfig")
+  rblf.soong_config_set(g, "snsconfig", "imagetype", "odm_image")
+  rblf.soong_config_set(g, "snsconfig", "foo", "foo_value")
+  rblf.soong_config_append(g, "snsconfig", "bar", "bar_value")
+  cfg["PRODUCT_COPY_FILES"] = rblf.copy_files(rblf.expand_wildcard("foo*.mk"), "etc")
+  cfg["PRODUCT_COPY_FILES"] = rblf.product_copy_files_by_pattern("from/%", "to/%", "a b c")
+`,
+	},
+	{
+		desc:   "subst in list",
+		mkname: "product.mk",
+		in: `
+files = $(call find-copy-subdir-files,*,from,to)
+PRODUCT_COPY_FILES += $(subst foo,bar,$(files))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _files = rblf.find_and_copy("*", "from", "to")
+  rblf.setdefault(handle, "PRODUCT_COPY_FILES")
+  cfg["PRODUCT_COPY_FILES"] += rblf.mksubst("foo", "bar", _files)
+`,
+	},
+	{
+		desc:   "assignment flavors",
+		mkname: "product.mk",
+		in: `
+PRODUCT_LIST1 := a
+PRODUCT_LIST2 += a
+PRODUCT_LIST1 += b
+PRODUCT_LIST2 += b
+PRODUCT_LIST3 ?= a
+PRODUCT_LIST1 = c
+PLATFORM_LIST += x
+PRODUCT_PACKAGES := $(PLATFORM_LIST)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_LIST1"] = ["a"]
+  rblf.setdefault(handle, "PRODUCT_LIST2")
+  cfg["PRODUCT_LIST2"] += ["a"]
+  cfg["PRODUCT_LIST1"] += ["b"]
+  cfg["PRODUCT_LIST2"] += ["b"]
+  if cfg.get("PRODUCT_LIST3") == None:
+    cfg["PRODUCT_LIST3"] = ["a"]
+  cfg["PRODUCT_LIST1"] = ["c"]
+  g.setdefault("PLATFORM_LIST", [])
+  g["PLATFORM_LIST"] += ["x"]
+  cfg["PRODUCT_PACKAGES"] = g["PLATFORM_LIST"][:]
+`,
+	},
+	{
+		desc:   "assigment flavors2",
+		mkname: "product.mk",
+		in: `
+PRODUCT_LIST1 = a
+ifeq (0,1)
+  PRODUCT_LIST1 += b
+  PRODUCT_LIST2 += b
+endif
+PRODUCT_LIST1 += c
+PRODUCT_LIST2 += c
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_LIST1"] = ["a"]
+  if "0" == "1":
+    cfg["PRODUCT_LIST1"] += ["b"]
+    rblf.setdefault(handle, "PRODUCT_LIST2")
+    cfg["PRODUCT_LIST2"] += ["b"]
+  cfg["PRODUCT_LIST1"] += ["c"]
+  rblf.setdefault(handle, "PRODUCT_LIST2")
+  cfg["PRODUCT_LIST2"] += ["c"]
+`,
+	},
+	{
+		desc:   "assigment setdefaults",
+		mkname: "product.mk",
+		in: `
+# All of these should have a setdefault because they're self-referential and not defined before
+PRODUCT_LIST1 = a $(PRODUCT_LIST1)
+PRODUCT_LIST2 ?= a $(PRODUCT_LIST2)
+PRODUCT_LIST3 += a
+
+# Now doing them again should not have a setdefault because they've already been set
+PRODUCT_LIST1 = a $(PRODUCT_LIST1)
+PRODUCT_LIST2 ?= a $(PRODUCT_LIST2)
+PRODUCT_LIST3 += a
+`,
+		expected: `# All of these should have a setdefault because they're self-referential and not defined before
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.setdefault(handle, "PRODUCT_LIST1")
+  cfg["PRODUCT_LIST1"] = (["a"] +
+      cfg.get("PRODUCT_LIST1", []))
+  if cfg.get("PRODUCT_LIST2") == None:
+    rblf.setdefault(handle, "PRODUCT_LIST2")
+    cfg["PRODUCT_LIST2"] = (["a"] +
+        cfg.get("PRODUCT_LIST2", []))
+  rblf.setdefault(handle, "PRODUCT_LIST3")
+  cfg["PRODUCT_LIST3"] += ["a"]
+  # Now doing them again should not have a setdefault because they've already been set
+  cfg["PRODUCT_LIST1"] = (["a"] +
+      cfg["PRODUCT_LIST1"])
+  if cfg.get("PRODUCT_LIST2") == None:
+    cfg["PRODUCT_LIST2"] = (["a"] +
+        cfg["PRODUCT_LIST2"])
+  cfg["PRODUCT_LIST3"] += ["a"]
+`,
+	},
+	{
+		desc:   "soong namespace assignments",
+		mkname: "product.mk",
+		in: `
+SOONG_CONFIG_NAMESPACES += cvd
+SOONG_CONFIG_cvd += launch_configs
+SOONG_CONFIG_cvd_launch_configs = cvd_config_auto.json
+SOONG_CONFIG_cvd += grub_config
+SOONG_CONFIG_cvd_grub_config += grub.cfg
+x := $(SOONG_CONFIG_cvd_grub_config)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.soong_config_namespace(g, "cvd")
+  rblf.soong_config_set(g, "cvd", "launch_configs", "cvd_config_auto.json")
+  rblf.soong_config_append(g, "cvd", "grub_config", "grub.cfg")
+  rblf.mk2rbc_error("product.mk:7", "SOONG_CONFIG_ variables cannot be referenced, use soong_config_get instead: SOONG_CONFIG_cvd_grub_config")
+`,
+	}, {
+		desc:   "soong namespace accesses",
+		mkname: "product.mk",
+		in: `
+SOONG_CONFIG_NAMESPACES += cvd
+SOONG_CONFIG_cvd += launch_configs
+SOONG_CONFIG_cvd_launch_configs = cvd_config_auto.json
+SOONG_CONFIG_cvd += grub_config
+SOONG_CONFIG_cvd_grub_config += grub.cfg
+x := $(call soong_config_get,cvd,grub_config)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.soong_config_namespace(g, "cvd")
+  rblf.soong_config_set(g, "cvd", "launch_configs", "cvd_config_auto.json")
+  rblf.soong_config_append(g, "cvd", "grub_config", "grub.cfg")
+  _x = rblf.soong_config_get(g, "cvd", "grub_config")
+`,
+	},
+	{
+		desc:   "string split",
+		mkname: "product.mk",
+		in: `
+PRODUCT_LIST1 = a
+local = b
+local += c
+FOO = d
+FOO += e
+PRODUCT_LIST1 += $(local)
+PRODUCT_LIST1 += $(FOO)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_LIST1"] = ["a"]
+  _local = "b"
+  _local += " " + "c"
+  g["FOO"] = "d"
+  g["FOO"] += " " + "e"
+  cfg["PRODUCT_LIST1"] += (_local).split()
+  cfg["PRODUCT_LIST1"] += (g["FOO"]).split()
+`,
+	},
+	{
+		desc:   "apex_jars",
+		mkname: "product.mk",
+		in: `
+PRODUCT_BOOT_JARS := $(ART_APEX_JARS) framework-minus-apex
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  cfg["PRODUCT_BOOT_JARS"] = (g.get("ART_APEX_JARS", []) +
+      ["framework-minus-apex"])
+`,
+	},
+	{
+		desc:   "strip/sort functions",
+		mkname: "product.mk",
+		in: `
+ifeq ($(filter hwaddress,$(PRODUCT_PACKAGES)),)
+   PRODUCT_PACKAGES := $(strip $(PRODUCT_PACKAGES) hwaddress)
+endif
+MY_VAR := $(sort b a c)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if "hwaddress" not in cfg.get("PRODUCT_PACKAGES", []):
+    rblf.setdefault(handle, "PRODUCT_PACKAGES")
+    cfg["PRODUCT_PACKAGES"] = (rblf.mkstrip("%s hwaddress" % " ".join(cfg.get("PRODUCT_PACKAGES", [])))).split()
+  g["MY_VAR"] = rblf.mksort("b a c")
+`,
+	},
+	{
+		desc:   "strip func in condition",
+		mkname: "product.mk",
+		in: `
+ifneq ($(strip $(TARGET_VENDOR)),)
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.mkstrip(g.get("TARGET_VENDOR", "")):
+    pass
+`,
+	},
+	{
+		desc:   "ref after set",
+		mkname: "product.mk",
+		in: `
+PRODUCT_ADB_KEYS:=value
+FOO := $(PRODUCT_ADB_KEYS)
+ifneq (,$(PRODUCT_ADB_KEYS))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["PRODUCT_ADB_KEYS"] = "value"
+  g["FOO"] = g["PRODUCT_ADB_KEYS"]
+  if g["PRODUCT_ADB_KEYS"]:
+    pass
+`,
+	},
+	{
+		desc:   "ref before set",
+		mkname: "product.mk",
+		in: `
+V1 := $(PRODUCT_ADB_KEYS)
+ifeq (,$(PRODUCT_ADB_KEYS))
+  V2 := $(PRODUCT_ADB_KEYS)
+  PRODUCT_ADB_KEYS:=foo
+  V3 := $(PRODUCT_ADB_KEYS)
+endif`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["V1"] = g.get("PRODUCT_ADB_KEYS", "")
+  if not g.get("PRODUCT_ADB_KEYS", ""):
+    g["V2"] = g.get("PRODUCT_ADB_KEYS", "")
+    g["PRODUCT_ADB_KEYS"] = "foo"
+    g["V3"] = g["PRODUCT_ADB_KEYS"]
+`,
+	},
+	{
+		desc:   "Dynamic inherit path",
+		mkname: "product.mk",
+		in: `
+MY_PATH:=foo
+$(call inherit-product,vendor/$(MY_PATH)/cfg.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
+load("//vendor/bar/baz:cfg.star|init", _cfg1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_PATH"] = "foo"
+  _entry = {
+    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+    "vendor/bar/baz/cfg.mk": ("vendor/bar/baz/cfg", _cfg1_init),
+  }.get("vendor/%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("vendor/%s/cfg.mk" % g["MY_PATH"]))
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
+	{
+		desc:   "Dynamic inherit with hint",
+		mkname: "product.mk",
+		in: `
+MY_PATH:=foo
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_PATH)/cfg.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_PATH"] = "foo"
+  _entry = {
+    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+  }.get("%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
+	{
+		desc:   "Dynamic inherit with duplicated hint",
+		mkname: "product.mk",
+		in: `
+MY_PATH:=foo
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_PATH)/cfg.mk)
+#RBC# include_top vendor/foo1
+#RBC# include_top vendor/foo1
+$(call inherit-product,$(MY_PATH)/cfg.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//vendor/foo1:cfg.star|init", _cfg_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["MY_PATH"] = "foo"
+  _entry = {
+    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
+  }.get("%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("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("%s/cfg.mk" % g["MY_PATH"])
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
+	{
+		desc:   "Dynamic inherit path that lacks hint",
+		mkname: "product.mk",
+		in: `
+#RBC# include_top foo
+$(call inherit-product,$(MY_VAR)/font.mk)
+
+#RBC# include_top foo
+
+# There's some space and even this comment between the include_top and the inherit-product
+
+$(call inherit-product,$(MY_VAR)/font.mk)
+
+$(call inherit-product,$(MY_VAR)/font.mk)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+load("//foo:font.star|init", _font_init = "init")
+load("//bar:font.star|init", _font1_init = "init")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  _entry = {
+    "foo/font.mk": ("foo/font", _font_init),
+  }.get("%s/font.mk" % g.get("MY_VAR", ""))
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/font.mk" % g.get("MY_VAR", "")))
+  rblf.inherit(handle, _varmod, _varmod_init)
+  # There's some space and even this comment between the include_top and the inherit-product
+  _entry = {
+    "foo/font.mk": ("foo/font", _font_init),
+  }.get("%s/font.mk" % g.get("MY_VAR", ""))
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/font.mk" % g.get("MY_VAR", "")))
+  rblf.inherit(handle, _varmod, _varmod_init)
+  rblf.mkwarning("product.mk:11", "Please avoid starting an include path with a variable. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.")
+  _entry = {
+    "foo/font.mk": ("foo/font", _font_init),
+    "bar/font.mk": ("bar/font", _font1_init),
+  }.get("%s/font.mk" % g.get("MY_VAR", ""))
+  (_varmod, _varmod_init) = _entry if _entry else (None, None)
+  if not _varmod_init:
+    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/font.mk" % g.get("MY_VAR", "")))
+  rblf.inherit(handle, _varmod, _varmod_init)
+`,
+	},
+	{
+		desc:   "Ignore make rules",
+		mkname: "product.mk",
+		in: `
+foo: foo.c
+	gcc -o $@ $*`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "unsupported line rule:       foo: foo.c\n#gcc -o $@ $*")
+`,
+	},
+	{
+		desc:   "Flag override",
+		mkname: "product.mk",
+		in: `
+override FOO:=`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:2", "cannot handle override directive")
+`,
+	},
+	{
+		desc:   "Bad expression",
+		mkname: "build/product.mk",
+		in: `
+ifeq (,$(call foobar))
+endif
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if rblf.mk2rbc_error("build/product.mk:2", "cannot handle invoking foobar"):
+    pass
+`,
+	},
+	{
+		desc:   "if expression",
+		mkname: "product.mk",
+		in: `
+TEST_VAR := foo
+TEST_VAR_LIST := foo
+TEST_VAR_LIST += bar
+TEST_VAR_2 := $(if $(TEST_VAR),bar)
+TEST_VAR_3 := $(if $(TEST_VAR),bar,baz)
+TEST_VAR_4 := $(if $(TEST_VAR),$(TEST_VAR_LIST))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["TEST_VAR"] = "foo"
+  g["TEST_VAR_LIST"] = ["foo"]
+  g["TEST_VAR_LIST"] += ["bar"]
+  g["TEST_VAR_2"] = ("bar" if g["TEST_VAR"] else "")
+  g["TEST_VAR_3"] = ("bar" if g["TEST_VAR"] else "baz")
+  g["TEST_VAR_4"] = (g["TEST_VAR_LIST"] if g["TEST_VAR"] else [])
+`,
+	},
+	{
+		desc:   "substitution references",
+		mkname: "product.mk",
+		in: `
+SOURCES := foo.c bar.c
+OBJECTS := $(SOURCES:.c=.o)
+OBJECTS2 := $(SOURCES:%.c=%.o)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["SOURCES"] = "foo.c bar.c"
+  g["OBJECTS"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"])
+  g["OBJECTS2"] = rblf.mkpatsubst("%.c", "%.o", g["SOURCES"])
+`,
+	},
+	{
+		desc:   "foreach expressions",
+		mkname: "product.mk",
+		in: `
+BOOT_KERNEL_MODULES := foo.ko bar.ko
+BOOT_KERNEL_MODULES_FILTER := $(foreach m,$(BOOT_KERNEL_MODULES),%/$(m))
+BOOT_KERNEL_MODULES_LIST := foo.ko
+BOOT_KERNEL_MODULES_LIST += bar.ko
+BOOT_KERNEL_MODULES_FILTER_2 := $(foreach m,$(BOOT_KERNEL_MODULES_LIST),%/$(m))
+
+FOREACH_WITH_IF := $(foreach module,\
+  $(BOOT_KERNEL_MODULES_LIST),\
+  $(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
+
+# Same as above, but not assigning it to a variable allows it to be converted to statements
+$(foreach module,\
+  $(BOOT_KERNEL_MODULES_LIST),\
+  $(if $(filter $(module),foo.ko),,$(error module "$(module)" has an error!)))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["BOOT_KERNEL_MODULES"] = "foo.ko bar.ko"
+  g["BOOT_KERNEL_MODULES_FILTER"] = ["%%/%s" % m for m in rblf.words(g["BOOT_KERNEL_MODULES"])]
+  g["BOOT_KERNEL_MODULES_LIST"] = ["foo.ko"]
+  g["BOOT_KERNEL_MODULES_LIST"] += ["bar.ko"]
+  g["BOOT_KERNEL_MODULES_FILTER_2"] = ["%%/%s" % m for m in g["BOOT_KERNEL_MODULES_LIST"]]
+  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"]:
+    if not rblf.filter(module, "foo.ko"):
+      rblf.mkerror("product.mk", "module \"%s\" has an error!" % module)
+`,
+	},
+	{
+		desc:   "List appended to string",
+		mkname: "product.mk",
+		in: `
+NATIVE_BRIDGE_PRODUCT_PACKAGES := \
+    libnative_bridge_vdso.native_bridge \
+    native_bridge_guest_app_process.native_bridge \
+    native_bridge_guest_linker.native_bridge
+
+NATIVE_BRIDGE_MODIFIED_GUEST_LIBS := \
+    libaaudio \
+    libamidi \
+    libandroid \
+    libandroid_runtime
+
+NATIVE_BRIDGE_PRODUCT_PACKAGES += \
+    $(addsuffix .native_bridge,$(NATIVE_BRIDGE_ORIG_GUEST_LIBS))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["NATIVE_BRIDGE_PRODUCT_PACKAGES"] = "libnative_bridge_vdso.native_bridge      native_bridge_guest_app_process.native_bridge      native_bridge_guest_linker.native_bridge"
+  g["NATIVE_BRIDGE_MODIFIED_GUEST_LIBS"] = "libaaudio      libamidi      libandroid      libandroid_runtime"
+  g["NATIVE_BRIDGE_PRODUCT_PACKAGES"] += " " + " ".join(rblf.addsuffix(".native_bridge", g.get("NATIVE_BRIDGE_ORIG_GUEST_LIBS", "")))
+`,
+	},
+	{
+		desc:   "Math functions",
+		mkname: "product.mk",
+		in: `
+# Test the math functions defined in build/make/common/math.mk
+ifeq ($(call math_max,2,5),5)
+endif
+ifeq ($(call math_min,2,5),2)
+endif
+ifeq ($(call math_gt_or_eq,2,5),true)
+endif
+ifeq ($(call math_gt,2,5),true)
+endif
+ifeq ($(call math_lt,2,5),true)
+endif
+ifeq ($(call math_gt_or_eq,2,5),)
+endif
+ifeq ($(call math_gt,2,5),)
+endif
+ifeq ($(call math_lt,2,5),)
+endif
+ifeq ($(call math_gt_or_eq,$(MY_VAR), 5),true)
+endif
+ifeq ($(call math_gt_or_eq,$(MY_VAR),$(MY_OTHER_VAR)),true)
+endif
+ifeq ($(call math_gt_or_eq,100$(MY_VAR),10),true)
+endif
+`,
+		expected: `# Test the math functions defined in build/make/common/math.mk
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  if max(2, 5) == 5:
+    pass
+  if min(2, 5) == 2:
+    pass
+  if 2 >= 5:
+    pass
+  if 2 > 5:
+    pass
+  if 2 < 5:
+    pass
+  if 2 < 5:
+    pass
+  if 2 <= 5:
+    pass
+  if 2 >= 5:
+    pass
+  if int(g.get("MY_VAR", "")) >= 5:
+    pass
+  if int(g.get("MY_VAR", "")) >= int(g.get("MY_OTHER_VAR", "")):
+    pass
+  if int("100%s" % g.get("MY_VAR", "")) >= 10:
+    pass
+`,
+	},
+	{
+		desc:   "Type hints",
+		mkname: "product.mk",
+		in: `
+# Test type hints
+#RBC# type_hint list MY_VAR MY_VAR_2
+# Unsupported type
+#RBC# type_hint bool MY_VAR_3
+# Invalid syntax
+#RBC# type_hint list
+# Duplicated variable
+#RBC# type_hint list MY_VAR_2
+#RBC# type_hint list my-local-var-with-dashes
+#RBC# type_hint string MY_STRING_VAR
+
+MY_VAR := foo
+MY_VAR_UNHINTED := foo
+
+# Vars set after other statements still get the hint
+MY_VAR_2 := foo
+
+# You can't specify a type hint after the first statement
+#RBC# type_hint list MY_VAR_4
+MY_VAR_4 := foo
+
+my-local-var-with-dashes := foo
+
+MY_STRING_VAR := $(wildcard foo/bar.mk)
+`,
+		expected: `# Test type hints
+# Unsupported type
+load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  rblf.mk2rbc_error("product.mk:5", "Invalid type_hint annotation. Only list/string types are accepted, found bool")
+  # Invalid syntax
+  rblf.mk2rbc_error("product.mk:7", "Invalid type_hint annotation: list. Must be a variable type followed by a list of variables of that type")
+  # Duplicated variable
+  rblf.mk2rbc_error("product.mk:9", "Duplicate type hint for variable MY_VAR_2")
+  g["MY_VAR"] = ["foo"]
+  g["MY_VAR_UNHINTED"] = "foo"
+  # Vars set after other statements still get the hint
+  g["MY_VAR_2"] = ["foo"]
+  # You can't specify a type hint after the first statement
+  rblf.mk2rbc_error("product.mk:20", "type_hint annotations must come before the first Makefile statement")
+  g["MY_VAR_4"] = "foo"
+  _my_local_var_with_dashes = ["foo"]
+  g["MY_STRING_VAR"] = " ".join(rblf.expand_wildcard("foo/bar.mk"))
+`,
+	},
+	{
+		desc:   "Set LOCAL_PATH to my-dir",
+		mkname: "product.mk",
+		in: `
+LOCAL_PATH := $(call my-dir)
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  
+`,
+	},
+	{
+		desc:   "Evals",
+		mkname: "product.mk",
+		in: `
+$(eval)
+$(eval MY_VAR := foo)
+$(eval # This is a test of eval functions)
+$(eval $(TOO_COMPLICATED) := bar)
+$(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))) \
+)
+
+`,
+		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()
+`,
+	},
+}
+
+var known_variables = []struct {
+	name  string
+	class varClass
+	starlarkType
+}{
+	{"NATIVE_COVERAGE", VarClassSoong, starlarkTypeBool},
+	{"PRODUCT_NAME", VarClassConfig, starlarkTypeString},
+	{"PRODUCT_MODEL", VarClassConfig, starlarkTypeString},
+	{"PRODUCT_PACKAGES", VarClassConfig, starlarkTypeList},
+	{"PRODUCT_BOOT_JARS", VarClassConfig, starlarkTypeList},
+	{"PRODUCT_COPY_FILES", VarClassConfig, starlarkTypeList},
+	{"PRODUCT_IS_64BIT", VarClassConfig, starlarkTypeString},
+	{"PRODUCT_LIST1", VarClassConfig, starlarkTypeList},
+	{"PRODUCT_LIST2", VarClassConfig, starlarkTypeList},
+	{"PRODUCT_LIST3", VarClassConfig, starlarkTypeList},
+	{"TARGET_PRODUCT", VarClassSoong, starlarkTypeString},
+	{"TARGET_BUILD_VARIANT", VarClassSoong, starlarkTypeString},
+	{"TARGET_BOARD_PLATFORM", VarClassSoong, starlarkTypeString},
+	{"QCOM_BOARD_PLATFORMS", VarClassSoong, starlarkTypeString},
+	{"PLATFORM_LIST", VarClassSoong, starlarkTypeList}, // TODO(asmundak): make it local instead of soong
+}
+
+type testMakefileFinder struct {
+	fs    fs.FS
+	root  string
+	files []string
+}
+
+func (t *testMakefileFinder) Find(root string) []string {
+	if t.files != nil || root == t.root {
+		return t.files
+	}
+	t.files = make([]string, 0)
+	fs.WalkDir(t.fs, root, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if d.IsDir() {
+			base := filepath.Base(path)
+			if base[0] == '.' && len(base) > 1 {
+				return fs.SkipDir
+			}
+			return nil
+		}
+		if strings.HasSuffix(path, ".mk") {
+			t.files = append(t.files, path)
+		}
+		return nil
+	})
+	return t.files
+}
+
+func TestGood(t *testing.T) {
+	for _, v := range known_variables {
+		KnownVariables.NewVariable(v.name, v.class, v.starlarkType)
+	}
+	fs := NewFindMockFS([]string{
+		"vendor/foo1/cfg.mk",
+		"vendor/bar/baz/cfg.mk",
+		"part.mk",
+		"foo/font.mk",
+		"bar/font.mk",
+	})
+	for _, test := range testCases {
+		t.Run(test.desc,
+			func(t *testing.T) {
+				ss, err := Convert(Request{
+					MkFile:         test.mkname,
+					Reader:         bytes.NewBufferString(test.in),
+					OutputSuffix:   ".star",
+					SourceFS:       fs,
+					MakefileFinder: &testMakefileFinder{fs: fs},
+				})
+				if err != nil {
+					t.Error(err)
+					return
+				}
+				got := ss.String()
+				if got != test.expected {
+					t.Errorf("%q failed\nExpected:\n%s\nActual:\n%s\n", test.desc,
+						strings.ReplaceAll(test.expected, "\n", "␤\n"),
+						strings.ReplaceAll(got, "\n", "␤\n"))
+				}
+			})
+	}
+}
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
new file mode 100644
index 0000000..7c39b9e
--- /dev/null
+++ b/mk2rbc/node.go
@@ -0,0 +1,324 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"fmt"
+	"strings"
+
+	mkparser "android/soong/androidmk/parser"
+)
+
+// A parsed node for which starlark code will be generated
+// by calling emit().
+type starlarkNode interface {
+	emit(ctx *generationContext)
+}
+
+// Types used to keep processed makefile data:
+type commentNode struct {
+	text string
+}
+
+func (c *commentNode) emit(gctx *generationContext) {
+	chunks := strings.Split(c.text, "\\\n")
+	gctx.newLine()
+	gctx.write(chunks[0]) // It has '#' at the beginning already.
+	for _, chunk := range chunks[1:] {
+		gctx.newLine()
+		gctx.write("#", chunk)
+	}
+}
+
+type moduleInfo struct {
+	path            string // Converted Starlark file path
+	originalPath    string // Makefile file path
+	moduleLocalName string
+	optional        bool
+	missing         bool // a module may not exist if a module that depends on it is loaded dynamically
+}
+
+func (im moduleInfo) entryName() string {
+	return im.moduleLocalName + "_init"
+}
+
+func (mi moduleInfo) name() string {
+	return fmt.Sprintf("%q", MakePath2ModuleName(mi.originalPath))
+}
+
+type inheritedModule interface {
+	name() string
+	entryName() string
+	emitSelect(gctx *generationContext)
+	pathExpr() starlarkExpr
+	needsLoadCheck() bool
+}
+
+type inheritedStaticModule struct {
+	*moduleInfo
+	loadAlways bool
+}
+
+func (im inheritedStaticModule) emitSelect(_ *generationContext) {
+}
+
+func (im inheritedStaticModule) pathExpr() starlarkExpr {
+	return &stringLiteralExpr{im.path}
+}
+
+func (im inheritedStaticModule) needsLoadCheck() bool {
+	return im.missing
+}
+
+type inheritedDynamicModule struct {
+	path             interpolateExpr
+	candidateModules []*moduleInfo
+	loadAlways       bool
+	location         ErrorLocation
+	needsWarning     bool
+}
+
+func (i inheritedDynamicModule) name() string {
+	return "_varmod"
+}
+
+func (i inheritedDynamicModule) entryName() string {
+	return i.name() + "_init"
+}
+
+func (i inheritedDynamicModule) emitSelect(gctx *generationContext) {
+	if i.needsWarning {
+		gctx.newLine()
+		gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Please avoid starting an include path with a variable. See https://source.android.com/setup/build/bazel/product_config/issues/includes for details.")
+	}
+	gctx.newLine()
+	gctx.writef("_entry = {")
+	gctx.indentLevel++
+	for _, mi := range i.candidateModules {
+		gctx.newLine()
+		gctx.writef(`"%s": (%s, %s),`, mi.originalPath, mi.name(), mi.entryName())
+	}
+	gctx.indentLevel--
+	gctx.newLine()
+	gctx.write("}.get(")
+	i.path.emit(gctx)
+	gctx.write(")")
+	gctx.newLine()
+	gctx.writef("(%s, %s) = _entry if _entry else (None, None)", i.name(), i.entryName())
+}
+
+func (i inheritedDynamicModule) pathExpr() starlarkExpr {
+	return &i.path
+}
+
+func (i inheritedDynamicModule) needsLoadCheck() bool {
+	return true
+}
+
+type inheritNode struct {
+	module     inheritedModule
+	loadAlways bool
+}
+
+func (inn *inheritNode) emit(gctx *generationContext) {
+	// Unconditional case:
+	//    maybe check that loaded
+	//    rblf.inherit(handle, <module>, module_init)
+	// Conditional case:
+	//    if <module>_init != None:
+	//      same as above
+	inn.module.emitSelect(gctx)
+	name := inn.module.name()
+	entry := inn.module.entryName()
+	if inn.loadAlways {
+		gctx.emitLoadCheck(inn.module)
+		gctx.newLine()
+		gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
+		return
+	}
+
+	gctx.newLine()
+	gctx.writef("if %s:", entry)
+	gctx.indentLevel++
+	gctx.newLine()
+	gctx.writef("%s(handle, %s, %s)", cfnInherit, name, entry)
+	gctx.indentLevel--
+}
+
+type includeNode struct {
+	module     inheritedModule
+	loadAlways bool
+}
+
+func (inn *includeNode) emit(gctx *generationContext) {
+	inn.module.emitSelect(gctx)
+	entry := inn.module.entryName()
+	if inn.loadAlways {
+		gctx.emitLoadCheck(inn.module)
+		gctx.newLine()
+		gctx.writef("%s(g, handle)", entry)
+		return
+	}
+
+	gctx.newLine()
+	gctx.writef("if %s != None:", entry)
+	gctx.indentLevel++
+	gctx.newLine()
+	gctx.writef("%s(g, handle)", entry)
+	gctx.indentLevel--
+}
+
+type assignmentFlavor int
+
+const (
+	// Assignment flavors
+	asgnSet      assignmentFlavor = iota // := or =
+	asgnMaybeSet assignmentFlavor = iota // ?=
+	asgnAppend   assignmentFlavor = iota // +=
+)
+
+type assignmentNode struct {
+	lhs      variable
+	value    starlarkExpr
+	mkValue  *mkparser.MakeString
+	flavor   assignmentFlavor
+	location ErrorLocation
+	isTraced bool
+}
+
+func (asgn *assignmentNode) emit(gctx *generationContext) {
+	gctx.newLine()
+	gctx.inAssignment = true
+	asgn.lhs.emitSet(gctx, asgn)
+	gctx.inAssignment = false
+
+	if asgn.isTraced {
+		gctx.newLine()
+		gctx.tracedCount++
+		gctx.writef(`print("%s.%d: %s := ", `, gctx.starScript.mkFile, gctx.tracedCount, asgn.lhs.name())
+		asgn.lhs.emitGet(gctx)
+		gctx.writef(")")
+	}
+}
+
+func (asgn *assignmentNode) isSelfReferential() bool {
+	if asgn.flavor == asgnAppend {
+		return true
+	}
+	isSelfReferential := false
+	asgn.value.transform(func(expr starlarkExpr) starlarkExpr {
+		if ref, ok := expr.(*variableRefExpr); ok && ref.ref.name() == asgn.lhs.name() {
+			isSelfReferential = true
+		}
+		return nil
+	})
+	return isSelfReferential
+}
+
+type exprNode struct {
+	expr starlarkExpr
+}
+
+func (exn *exprNode) emit(gctx *generationContext) {
+	gctx.newLine()
+	exn.expr.emit(gctx)
+}
+
+type ifNode struct {
+	isElif bool // true if this is 'elif' statement
+	expr   starlarkExpr
+}
+
+func (in *ifNode) emit(gctx *generationContext) {
+	ifElif := "if "
+	if in.isElif {
+		ifElif = "elif "
+	}
+
+	gctx.newLine()
+	gctx.write(ifElif)
+	in.expr.emit(gctx)
+	gctx.write(":")
+}
+
+type elseNode struct{}
+
+func (br *elseNode) emit(gctx *generationContext) {
+	gctx.newLine()
+	gctx.write("else:")
+}
+
+// switchCase represents as single if/elseif/else branch. All the necessary
+// info about flavor (if/elseif/else) is supposed to be kept in `gate`.
+type switchCase struct {
+	gate  starlarkNode
+	nodes []starlarkNode
+}
+
+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 {
+			hasStatements = true
+		}
+		node.emit(gctx)
+	}
+	if !hasStatements {
+		gctx.emitPass()
+	}
+	gctx.indentLevel--
+	gctx.popVariableAssignments()
+}
+
+// A single complete if ... elseif ... else ... endif sequences
+type switchNode struct {
+	ssCases []*switchCase
+}
+
+func (ssw *switchNode) emit(gctx *generationContext) {
+	for _, ssCase := range ssw.ssCases {
+		ssCase.emit(gctx)
+	}
+}
+
+type foreachNode struct {
+	varName string
+	list    starlarkExpr
+	actions []starlarkNode
+}
+
+func (f *foreachNode) emit(gctx *generationContext) {
+	gctx.pushVariableAssignments()
+	gctx.newLine()
+	gctx.writef("for %s in ", f.varName)
+	f.list.emit(gctx)
+	gctx.write(":")
+	gctx.indentLevel++
+	hasStatements := false
+	for _, a := range f.actions {
+		if _, ok := a.(*commentNode); !ok {
+			hasStatements = true
+		}
+		a.emit(gctx)
+	}
+	if !hasStatements {
+		gctx.emitPass()
+	}
+	gctx.indentLevel--
+	gctx.popVariableAssignments()
+}
diff --git a/mk2rbc/soong_variables.go b/mk2rbc/soong_variables.go
new file mode 100644
index 0000000..de46925
--- /dev/null
+++ b/mk2rbc/soong_variables.go
@@ -0,0 +1,151 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strings"
+
+	mkparser "android/soong/androidmk/parser"
+)
+
+type context struct {
+	includeFileScope mkparser.Scope
+	registrar        variableRegistrar
+}
+
+// Scans the makefile Soong uses to generate soong.variables file,
+// collecting variable names and types from the lines that look like this:
+//    $(call add_json_XXX,  <...>,             $(VAR))
+//
+func FindSoongVariables(mkFile string, includeFileScope mkparser.Scope, registrar variableRegistrar) error {
+	ctx := context{includeFileScope, registrar}
+	return ctx.doFind(mkFile)
+}
+
+func (ctx *context) doFind(mkFile string) error {
+	mkContents, err := ioutil.ReadFile(mkFile)
+	if err != nil {
+		return err
+	}
+	parser := mkparser.NewParser(mkFile, bytes.NewBuffer(mkContents))
+	nodes, errs := parser.Parse()
+	if len(errs) > 0 {
+		for _, e := range errs {
+			fmt.Fprintln(os.Stderr, "ERROR:", e)
+		}
+		return fmt.Errorf("cannot parse %s", mkFile)
+	}
+	for _, node := range nodes {
+		switch t := node.(type) {
+		case *mkparser.Variable:
+			ctx.handleVariable(t)
+		case *mkparser.Directive:
+			ctx.handleInclude(t)
+		}
+	}
+	return nil
+}
+
+func (ctx context) NewSoongVariable(name, typeString string) {
+	var valueType starlarkType
+	switch typeString {
+	case "bool":
+		valueType = starlarkTypeBool
+	case "csv":
+		// Only PLATFORM_VERSION_ALL_CODENAMES, and it's a list
+		valueType = starlarkTypeList
+	case "list":
+		valueType = starlarkTypeList
+	case "str":
+		valueType = starlarkTypeString
+	case "val":
+		// Only PLATFORM_SDK_VERSION uses this, and it's integer
+		valueType = starlarkTypeInt
+	default:
+		panic(fmt.Errorf("unknown Soong variable type %s", typeString))
+	}
+
+	ctx.registrar.NewVariable(name, VarClassSoong, valueType)
+}
+
+func (ctx context) handleInclude(t *mkparser.Directive) {
+	if t.Name != "include" && t.Name != "-include" {
+		return
+	}
+	includedPath := t.Args.Value(ctx.includeFileScope)
+	err := ctx.doFind(includedPath)
+	if err != nil && t.Name == "include" {
+		fmt.Fprintf(os.Stderr, "cannot include %s: %s", includedPath, err)
+	}
+}
+
+var callFuncRex = regexp.MustCompile("^call +add_json_(str|val|bool|csv|list) *,")
+
+func (ctx context) handleVariable(t *mkparser.Variable) {
+	// From the variable reference looking as follows:
+	//  $(call json_add_TYPE,arg1,$(VAR))
+	// we infer that the type of $(VAR) is TYPE
+	// VAR can be a simple variable name, or another call
+	// (e.g., $(call invert_bool, $(X)), from which we can infer
+	// that the type of X is bool
+	if prefix, v, ok := prefixedVariable(t.Name); ok && strings.HasPrefix(prefix, "call add_json") {
+		if match := callFuncRex.FindStringSubmatch(prefix); match != nil {
+			ctx.inferSoongVariableType(match[1], v)
+			// NOTE(asmundak): sometimes arg1 (the name of the Soong variable defined
+			// in this statement) may indicate that there is a Make counterpart. E.g, from
+			//     $(call add_json_bool, DisablePreopt, $(call invert_bool,$(ENABLE_PREOPT)))
+			// it may be inferred that there is a Make boolean variable DISABLE_PREOPT.
+			// Unfortunately, Soong variable names have no 1:1 correspondence to Make variables,
+			// for instance,
+			//       $(call add_json_list, PatternsOnSystemOther, $(SYSTEM_OTHER_ODEX_FILTER))
+			// does not mean that there is PATTERNS_ON_SYSTEM_OTHER
+			// Our main interest lies in finding the variables whose values are lists, and
+			// so far there are none that can be found this way, so it is not important.
+		} else {
+			panic(fmt.Errorf("cannot match the call: %s", prefix))
+		}
+	}
+}
+
+var (
+	callInvertBoolRex = regexp.MustCompile("^call +invert_bool *, *$")
+	callFilterBoolRex = regexp.MustCompile("^(filter|filter-out) +(true|false), *$")
+)
+
+func (ctx context) inferSoongVariableType(vType string, n *mkparser.MakeString) {
+	if n.Const() {
+		ctx.NewSoongVariable(n.Strings[0], vType)
+		return
+	}
+	if prefix, v, ok := prefixedVariable(n); ok {
+		if callInvertBoolRex.MatchString(prefix) || callFilterBoolRex.MatchString(prefix) {
+			// It is $(call invert_bool, $(VAR)) or $(filter[-out] [false|true],$(VAR))
+			ctx.inferSoongVariableType("bool", v)
+		}
+	}
+}
+
+// If MakeString is foo$(BAR), returns 'foo', BAR(as *MakeString) and true
+func prefixedVariable(s *mkparser.MakeString) (string, *mkparser.MakeString, bool) {
+	if len(s.Strings) != 2 || s.Strings[1] != "" {
+		return "", nil, false
+	}
+	return s.Strings[0], s.Variables[0].Name, true
+}
diff --git a/mk2rbc/soong_variables_test.go b/mk2rbc/soong_variables_test.go
new file mode 100644
index 0000000..c883882
--- /dev/null
+++ b/mk2rbc/soong_variables_test.go
@@ -0,0 +1,51 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"testing"
+)
+
+type dirResolverForTest struct {
+	ScopeBase
+}
+
+func (t dirResolverForTest) Get(name string) string {
+	if name != "BUILD_SYSTEM" {
+		return fmt.Sprintf("$(%s)", name)
+	}
+	return getTestDirectory()
+}
+
+func TestSoongVariables(t *testing.T) {
+	testFile := filepath.Join(getTestDirectory(), "soong_variables.mk.test")
+	var actual testVariables
+	if err := FindSoongVariables(testFile, dirResolverForTest{}, &actual); err != nil {
+		t.Fatal(err)
+	}
+	expected := testVariables{[]testVar{
+		{"BUILD_ID", VarClassSoong, starlarkTypeString},
+		{"PLATFORM_SDK_VERSION", VarClassSoong, starlarkTypeInt},
+		{"DEVICE_PACKAGE_OVERLAYS", VarClassSoong, starlarkTypeList},
+		{"ENABLE_CFI", VarClassSoong, starlarkTypeBool},
+		{"ENABLE_PREOPT", VarClassSoong, starlarkTypeBool},
+	}}
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("\nExpected: %v\n  Actual: %v", expected, actual)
+	}
+}
diff --git a/mk2rbc/test/android_products.mk.test b/mk2rbc/test/android_products.mk.test
new file mode 100644
index 0000000..a2220ed
--- /dev/null
+++ b/mk2rbc/test/android_products.mk.test
@@ -0,0 +1,4 @@
+PRODUCT_MAKEFILES := \
+    $(LOCAL_DIR)/aosp_tv_arm.mk \
+    $(LOCAL_DIR)/aosp_tv_arm64.mk \
+    aosp_cf_x86_tv:$(LOCAL_DIR)/vsoc_x86/tv/device.mk
\ No newline at end of file
diff --git a/mk2rbc/test/config_variables.mk.test b/mk2rbc/test/config_variables.mk.test
new file mode 100644
index 0000000..e5cd0e9
--- /dev/null
+++ b/mk2rbc/test/config_variables.mk.test
@@ -0,0 +1,12 @@
+_product_single_value_vars :=
+
+# Variables that are lists of values.
+_product_list_vars :=
+
+_product_single_value_vars += PRODUCT_NAME
+_product_single_value_vars += PRODUCT_MODEL
+
+# The resoure configuration options to use for this product.
+_product_list_vars += PRODUCT_LOCALES
+_product_list_vars += PRODUCT_AAPT_CONFIG
+_product_single_value_vars += PRODUCT_AAPT_PREF_CONFIG
diff --git a/mk2rbc/test/soong_included.mk.test b/mk2rbc/test/soong_included.mk.test
new file mode 100644
index 0000000..255ecc5
--- /dev/null
+++ b/mk2rbc/test/soong_included.mk.test
@@ -0,0 +1 @@
+  $(call add_json_bool, DisablePreopt,                           $(call invert_bool,$(ENABLE_PREOPT)))
diff --git a/mk2rbc/test/soong_variables.mk.test b/mk2rbc/test/soong_variables.mk.test
new file mode 100644
index 0000000..ca60c9c
--- /dev/null
+++ b/mk2rbc/test/soong_variables.mk.test
@@ -0,0 +1,5 @@
+$(call add_json_str,  BuildId,                           $(BUILD_ID))
+$(call add_json_val,  Platform_sdk_version,              $(PLATFORM_SDK_VERSION))
+$(call add_json_list, DeviceResourceOverlays,            $(DEVICE_PACKAGE_OVERLAYS))
+$(call add_json_bool, EnableCFI,                         $(call invert_bool,$(filter false,$(ENABLE_CFI))))
+include $(BUILD_SYSTEM)/soong_included.mk.test
diff --git a/mk2rbc/test/version_defaults.mk.test b/mk2rbc/test/version_defaults.mk.test
new file mode 100644
index 0000000..1666392
--- /dev/null
+++ b/mk2rbc/test/version_defaults.mk.test
@@ -0,0 +1,22 @@
+INTERNAL_BUILD_ID_MAKEFILE := $(wildcard $(BUILD_SYSTEM)/build_id.mk)
+ifdef INTERNAL_BUILD_ID_MAKEFILE
+  include $(INTERNAL_BUILD_ID_MAKEFILE)
+endif
+
+DEFAULT_PLATFORM_VERSION := TP1A
+.KATI_READONLY := DEFAULT_PLATFORM_VERSION
+MIN_PLATFORM_VERSION := TP1A
+MAX_PLATFORM_VERSION := TP1A
+PLATFORM_VERSION_LAST_STABLE := 12
+PLATFORM_VERSION_CODENAME.SP2A := Sv2
+PLATFORM_VERSION_CODENAME.TP1A := Tiramisu
+ifndef PLATFORM_SDK_VERSION
+  PLATFORM_SDK_VERSION := 31
+endif
+.KATI_READONLY := PLATFORM_SDK_VERSION
+PLATFORM_SDK_EXTENSION_VERSION := 1
+PLATFORM_BASE_SDK_EXTENSION_VERSION := 0
+ifndef PLATFORM_SECURITY_PATCH
+    PLATFORM_SECURITY_PATCH := 2021-10-05
+endif
+include $(BUILD_SYSTEM)/version_util.mk
diff --git a/mk2rbc/types.go b/mk2rbc/types.go
new file mode 100644
index 0000000..46c6aa9
--- /dev/null
+++ b/mk2rbc/types.go
@@ -0,0 +1,75 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+// Starlark expression types we use
+type starlarkType int
+
+const (
+	// Variable types. Initially we only know the types of the  product
+	// configuration variables that are lists, and the types of some
+	// hardwired variables. The remaining variables are first entered as
+	// having an unknown type and treated as strings, but sometimes we
+	//  can infer variable's type from the value assigned to it.
+	starlarkTypeUnknown starlarkType = iota
+	starlarkTypeList    starlarkType = iota
+	starlarkTypeString  starlarkType = iota
+	starlarkTypeInt     starlarkType = iota
+	starlarkTypeBool    starlarkType = iota
+	starlarkTypeVoid    starlarkType = iota
+)
+
+type hiddenArgType int
+
+const (
+	// Some functions have an implicitly emitted first argument, which may be
+	// a global ('g') or configuration ('cfg') variable.
+	hiddenArgNone   hiddenArgType = iota
+	hiddenArgGlobal hiddenArgType = iota
+	hiddenArgConfig hiddenArgType = iota
+)
+
+type varClass int
+
+const (
+	VarClassConfig varClass = iota
+	VarClassSoong  varClass = iota
+	VarClassLocal  varClass = iota
+)
+
+type variableRegistrar interface {
+	NewVariable(name string, varClass varClass, valueType starlarkType)
+}
+
+// ScopeBase is a placeholder implementation of the mkparser.Scope.
+// All our scopes are read-only and resolve only simple variables.
+type ScopeBase struct{}
+
+func (s ScopeBase) Set(_, _ string) {
+	panic("implement me")
+}
+
+func (s ScopeBase) Call(_ string, _ []string) []string {
+	panic("implement me")
+}
+
+func (s ScopeBase) SetFunc(_ string, _ func([]string) []string) {
+	panic("implement me")
+}
+
+// Used to find all makefiles in the source tree
+type MakefileFinder interface {
+	Find(root string) []string
+}
diff --git a/mk2rbc/variable.go b/mk2rbc/variable.go
new file mode 100644
index 0000000..0a26ed8
--- /dev/null
+++ b/mk2rbc/variable.go
@@ -0,0 +1,321 @@
+// Copyright 2021 Google LLC
+//
+// 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 mk2rbc
+
+import (
+	"fmt"
+	"strings"
+)
+
+type variable interface {
+	name() string
+	emitGet(gctx *generationContext)
+	emitSet(gctx *generationContext, asgn *assignmentNode)
+	valueType() starlarkType
+	setValueType(t starlarkType)
+	defaultValueString() string
+	isPreset() bool
+}
+
+type baseVariable struct {
+	nam    string
+	typ    starlarkType
+	preset bool // true if it has been initialized at startup
+}
+
+func (v baseVariable) name() string {
+	return v.nam
+}
+
+func (v baseVariable) valueType() starlarkType {
+	return v.typ
+}
+
+func (v *baseVariable) setValueType(t starlarkType) {
+	v.typ = t
+}
+
+func (v baseVariable) isPreset() bool {
+	return v.preset
+}
+
+var defaultValuesByType = map[starlarkType]string{
+	starlarkTypeUnknown: `""`,
+	starlarkTypeList:    "[]",
+	starlarkTypeString:  `""`,
+	starlarkTypeInt:     "0",
+	starlarkTypeBool:    "False",
+	starlarkTypeVoid:    "None",
+}
+
+func (v baseVariable) defaultValueString() string {
+	if v, ok := defaultValuesByType[v.valueType()]; ok {
+		return v
+	}
+	panic(fmt.Errorf("%s has unknown type %q", v.name(), v.valueType()))
+}
+
+type productConfigVariable struct {
+	baseVariable
+}
+
+func (pcv productConfigVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
+	emitAssignment := func() {
+		gctx.writef("cfg[%q] = ", pcv.nam)
+		asgn.value.emitListVarCopy(gctx)
+	}
+	emitAppend := func() {
+		gctx.writef("cfg[%q] += ", pcv.nam)
+		value := asgn.value
+		if pcv.valueType() == starlarkTypeString {
+			gctx.writef(`" " + `)
+			value = &toStringExpr{expr: value}
+		}
+		value.emit(gctx)
+	}
+	emitSetDefault := func() {
+		if pcv.typ == starlarkTypeList {
+			gctx.writef("%s(handle, %q)", cfnSetListDefault, pcv.name())
+		} else {
+			gctx.writef("cfg.setdefault(%q, %s)", pcv.name(), pcv.defaultValueString())
+		}
+		gctx.newLine()
+	}
+
+	// If we are not sure variable has been assigned before, emit setdefault
+	needsSetDefault := !gctx.hasBeenAssigned(&pcv) && !pcv.isPreset() && asgn.isSelfReferential()
+
+	switch asgn.flavor {
+	case asgnSet:
+		if needsSetDefault {
+			emitSetDefault()
+		}
+		emitAssignment()
+	case asgnAppend:
+		if needsSetDefault {
+			emitSetDefault()
+		}
+		emitAppend()
+	case asgnMaybeSet:
+		gctx.writef("if cfg.get(%q) == None:", pcv.nam)
+		gctx.indentLevel++
+		gctx.newLine()
+		if needsSetDefault {
+			emitSetDefault()
+		}
+		emitAssignment()
+		gctx.indentLevel--
+	}
+
+	gctx.setHasBeenAssigned(&pcv)
+}
+
+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())
+	}
+}
+
+type otherGlobalVariable struct {
+	baseVariable
+}
+
+func (scv otherGlobalVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
+	emitAssignment := func() {
+		gctx.writef("g[%q] = ", scv.nam)
+		asgn.value.emitListVarCopy(gctx)
+	}
+
+	emitAppend := func() {
+		gctx.writef("g[%q] += ", scv.nam)
+		value := asgn.value
+		if scv.valueType() == starlarkTypeString {
+			gctx.writef(`" " + `)
+			value = &toStringExpr{expr: value}
+		}
+		value.emit(gctx)
+	}
+
+	// If we are not sure variable has been assigned before, emit setdefault
+	needsSetDefault := !gctx.hasBeenAssigned(&scv) && !scv.isPreset() && asgn.isSelfReferential()
+
+	switch asgn.flavor {
+	case asgnSet:
+		if needsSetDefault {
+			gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
+			gctx.newLine()
+		}
+		emitAssignment()
+	case asgnAppend:
+		if needsSetDefault {
+			gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
+			gctx.newLine()
+		}
+		emitAppend()
+	case asgnMaybeSet:
+		gctx.writef("if g.get(%q) == None:", scv.nam)
+		gctx.indentLevel++
+		gctx.newLine()
+		if needsSetDefault {
+			gctx.writef("g.setdefault(%q, %s)", scv.name(), scv.defaultValueString())
+			gctx.newLine()
+		}
+		emitAssignment()
+		gctx.indentLevel--
+	}
+
+	gctx.setHasBeenAssigned(&scv)
+}
+
+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())
+	}
+}
+
+type localVariable struct {
+	baseVariable
+}
+
+func (lv localVariable) String() string {
+	return "_" + lv.nam
+}
+
+func (lv localVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
+	switch asgn.flavor {
+	case asgnSet, asgnMaybeSet:
+		gctx.writef("%s = ", lv)
+		asgn.value.emitListVarCopy(gctx)
+	case asgnAppend:
+		gctx.writef("%s += ", lv)
+		value := asgn.value
+		if lv.valueType() == starlarkTypeString {
+			gctx.writef(`" " + `)
+			value = &toStringExpr{expr: value}
+		}
+		value.emit(gctx)
+	}
+}
+
+func (lv localVariable) emitGet(gctx *generationContext) {
+	gctx.writef("%s", lv)
+}
+
+type predefinedVariable struct {
+	baseVariable
+	value starlarkExpr
+}
+
+func (pv predefinedVariable) emitGet(gctx *generationContext) {
+	pv.value.emit(gctx)
+}
+
+func (pv predefinedVariable) emitSet(gctx *generationContext, asgn *assignmentNode) {
+	if expectedValue, ok1 := maybeString(pv.value); ok1 {
+		actualValue, ok2 := maybeString(asgn.value)
+		if ok2 {
+			if actualValue == expectedValue {
+				return
+			}
+			gctx.emitConversionError(asgn.location,
+				fmt.Sprintf("cannot set predefined variable %s to %q, its value should be %q",
+					pv.name(), actualValue, expectedValue))
+			gctx.starScript.hasErrors = true
+			return
+		}
+	}
+	panic(fmt.Errorf("cannot set predefined variable %s to %q", pv.name(), asgn.mkValue.Dump()))
+}
+
+var localProductConfigVariables = map[string]string{
+	"LOCAL_AUDIO_PRODUCT_PACKAGE":         "PRODUCT_PACKAGES",
+	"LOCAL_AUDIO_PRODUCT_COPY_FILES":      "PRODUCT_COPY_FILES",
+	"LOCAL_AUDIO_DEVICE_PACKAGE_OVERLAYS": "DEVICE_PACKAGE_OVERLAYS",
+	"LOCAL_DUMPSTATE_PRODUCT_PACKAGE":     "PRODUCT_PACKAGES",
+	"LOCAL_GATEKEEPER_PRODUCT_PACKAGE":    "PRODUCT_PACKAGES",
+	"LOCAL_HEALTH_PRODUCT_PACKAGE":        "PRODUCT_PACKAGES",
+	"LOCAL_SENSOR_PRODUCT_PACKAGE":        "PRODUCT_PACKAGES",
+	"LOCAL_KEYMASTER_PRODUCT_PACKAGE":     "PRODUCT_PACKAGES",
+	"LOCAL_KEYMINT_PRODUCT_PACKAGE":       "PRODUCT_PACKAGES",
+}
+
+var presetVariables = map[string]bool{
+	"BUILD_ID":                  true,
+	"HOST_ARCH":                 true,
+	"HOST_OS":                   true,
+	"HOST_BUILD_TYPE":           true,
+	"OUT_DIR":                   true,
+	"PLATFORM_VERSION_CODENAME": true,
+	"PLATFORM_VERSION":          true,
+	"TARGET_ARCH":               true,
+	"TARGET_ARCH_VARIANT":       true,
+	"TARGET_BUILD_TYPE":         true,
+	"TARGET_BUILD_VARIANT":      true,
+	"TARGET_PRODUCT":            true,
+}
+
+// addVariable returns a variable with a given name. A variable is
+// added if it does not exist yet.
+func (ctx *parseContext) addVariable(name string) variable {
+	// Get the hintType before potentially changing the variable name
+	var hintType starlarkType
+	var ok bool
+	if hintType, ok = ctx.typeHints[name]; !ok {
+		hintType = starlarkTypeUnknown
+	}
+	// Heuristics: if variable's name is all lowercase, consider it local
+	// string variable.
+	isLocalVariable := name == strings.ToLower(name)
+	// Local variables can't have special characters in them, because they
+	// will be used as starlark identifiers
+	if isLocalVariable {
+		name = strings.ReplaceAll(strings.TrimSpace(name), "-", "_")
+	}
+	v, found := ctx.variables[name]
+	if !found {
+		if vi, found := KnownVariables[name]; found {
+			_, preset := presetVariables[name]
+			switch vi.class {
+			case VarClassConfig:
+				v = &productConfigVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
+			case VarClassSoong:
+				v = &otherGlobalVariable{baseVariable{nam: name, typ: vi.valueType, preset: preset}}
+			}
+		} else if isLocalVariable {
+			v = &localVariable{baseVariable{nam: name, typ: hintType}}
+		} else {
+			vt := hintType
+			// Heuristics: local variables that contribute to corresponding config variables
+			if cfgVarName, found := localProductConfigVariables[name]; found && vt == starlarkTypeUnknown {
+				vi, found2 := KnownVariables[cfgVarName]
+				if !found2 {
+					panic(fmt.Errorf("unknown config variable %s for %s", cfgVarName, name))
+				}
+				vt = vi.valueType
+			}
+			if strings.HasSuffix(name, "_LIST") && vt == starlarkTypeUnknown {
+				// Heuristics: Variables with "_LIST" suffix are lists
+				vt = starlarkTypeList
+			}
+			v = &otherGlobalVariable{baseVariable{nam: name, typ: vt}}
+		}
+		ctx.variables[name] = v
+	}
+	return v
+}
diff --git a/provenance/Android.bp b/provenance/Android.bp
new file mode 100644
index 0000000..6fd67aa
--- /dev/null
+++ b/provenance/Android.bp
@@ -0,0 +1,36 @@
+/*
+ * 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-provenance",
+    pkgPath: "android/soong/provenance",
+    srcs: [
+        "provenance_singleton.go",
+    ],
+    deps: [
+        "soong-android",
+    ],
+    testSrcs: [
+        "provenance_singleton_test.go",
+    ],
+    pluginFor: [
+        "soong_build",
+    ],
+}
diff --git a/provenance/provenance_metadata_proto/Android.bp b/provenance/provenance_metadata_proto/Android.bp
new file mode 100644
index 0000000..7fc47a9
--- /dev/null
+++ b/provenance/provenance_metadata_proto/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+    name: "provenance_metadata_proto",
+    version: {
+        py3: {
+            enabled: true,
+        },
+    },
+    srcs: [
+        "provenance_metadata.proto",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+    },
+}
diff --git a/provenance/provenance_metadata_proto/provenance_metadata.proto b/provenance/provenance_metadata_proto/provenance_metadata.proto
new file mode 100644
index 0000000..f42aba7
--- /dev/null
+++ b/provenance/provenance_metadata_proto/provenance_metadata.proto
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package provenance_metadata_proto;
+option go_package = "android/soong/provenance/provenance_metadata_proto";
+
+// Provenance metadata of artifacts.
+message ProvenanceMetadata {
+  // Name of the module/target that creates the artifact.
+  // It is either a Soong module name or Bazel target label.
+  string module_name = 1;
+
+  // The path to the prebuilt artifacts, which is relative to the source tree
+  // directory. For example, “prebuilts/runtime/mainline/i18n/apex/com.android.i18n-arm.apex”.
+  string artifact_path = 2;
+
+  // The SHA256 hash of the artifact.
+  string artifact_sha256 = 3;
+
+  // The install path of the artifact in filesystem images.
+  // This is the absolute path of the artifact on the device.
+  string artifact_install_path = 4;
+
+  // Path of the attestation file of a prebuilt artifact, which is relative to
+  // the source tree directory. This is for prebuilt artifacts which have
+  // corresponding attestation files checked in the source tree.
+  string attestation_path = 5;
+}
+
+message ProvenanceMetaDataList {
+  repeated ProvenanceMetadata metadata = 1;
+}
\ No newline at end of file
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
new file mode 100644
index 0000000..e49f3d4
--- /dev/null
+++ b/provenance/provenance_singleton.go
@@ -0,0 +1,121 @@
+/*
+ * 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 provenance
+
+import (
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/provenance")
+	rule = pctx.HostBinToolVariable("gen_provenance_metadata", "gen_provenance_metadata")
+
+	genProvenanceMetaData = pctx.AndroidStaticRule("genProvenanceMetaData",
+		blueprint.RuleParams{
+			Command: `rm -rf "$out" && ` +
+				`${gen_provenance_metadata} --module_name=${module_name} ` +
+				`--artifact_path=$in --install_path=${install_path} --metadata_path=$out`,
+			CommandDeps: []string{"${gen_provenance_metadata}"},
+		}, "module_name", "install_path")
+
+	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 && ` +
+				`touch $out.temp && cat $out.temp $in | grep -v "^#.*" >> $out && rm -rf $out.temp`,
+		})
+)
+
+type ProvenanceMetadata interface {
+	ProvenanceMetaDataFile() android.OutputPath
+}
+
+func init() {
+	RegisterProvenanceSingleton(android.InitRegistrationContext)
+}
+
+func RegisterProvenanceSingleton(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("provenance_metadata_singleton", provenanceInfoSingletonFactory)
+}
+
+var PrepareForTestWithProvenanceSingleton = android.FixtureRegisterWithContext(RegisterProvenanceSingleton)
+
+func provenanceInfoSingletonFactory() android.Singleton {
+	return &provenanceInfoSingleton{}
+}
+
+type provenanceInfoSingleton struct {
+	mergedMetaDataFile android.OutputPath
+}
+
+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())
+		}
+	})
+	p.mergedMetaDataFile = android.PathForOutput(context, "provenance_metadata.textproto")
+	context.Build(pctx, android.BuildParams{
+		Rule:        mergeProvenanceMetaData,
+		Description: "merge provenance metadata",
+		Inputs:      allMetaDataFiles,
+		Output:      p.mergedMetaDataFile,
+	})
+
+	context.Build(pctx, android.BuildParams{
+		Rule:        blueprint.Phony,
+		Description: "phony rule of merge provenance metadata",
+		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 {
+	if !module.Enabled() || module.IsSkipInstall() {
+		return false
+	}
+	if p, ok := module.(ProvenanceMetadata); ok {
+		return p.ProvenanceMetaDataFile().String() != ""
+	}
+	return false
+}
+
+func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
+	onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
+	artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        genProvenanceMetaData,
+		Description: "generate artifact provenance metadata",
+		Inputs:      []android.Path{artifactPath},
+		Output:      artifactMetaDataFile,
+		Args: map[string]string{
+			"module_name":  ctx.ModuleName(),
+			"install_path": onDevicePathOfInstalledFile,
+		}})
+
+	return artifactMetaDataFile
+}
+
+func (p *provenanceInfoSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droidcore", p.mergedMetaDataFile)
+}
+
+var _ android.SingletonMakeVarsProvider = (*provenanceInfoSingleton)(nil)
diff --git a/provenance/provenance_singleton_test.go b/provenance/provenance_singleton_test.go
new file mode 100644
index 0000000..0f1eae2
--- /dev/null
+++ b/provenance/provenance_singleton_test.go
@@ -0,0 +1,48 @@
+/*
+ * 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 provenance
+
+import (
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestProvenanceSingleton(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithProvenanceSingleton,
+		android.PrepareForTestWithAndroidMk).RunTestWithBp(t, "")
+
+	outputs := result.SingletonForTests("provenance_metadata_singleton").AllOutputs()
+	for _, output := range outputs {
+		testingBuildParam := result.SingletonForTests("provenance_metadata_singleton").Output(output)
+		switch {
+		case strings.Contains(output, "soong/provenance_metadata.textproto"):
+			android.AssertStringEquals(t, "Invalid build rule", "android/soong/provenance.mergeProvenanceMetaData", testingBuildParam.Rule.String())
+			android.AssertIntEquals(t, "Invalid input", len(testingBuildParam.Inputs), 0)
+			android.AssertStringDoesContain(t, "Invalid output path", output, "soong/provenance_metadata.textproto")
+			android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+
+		case strings.HasSuffix(output, "provenance_metadata"):
+			android.AssertStringEquals(t, "Invalid build rule", "<builtin>:phony", testingBuildParam.Rule.String())
+			android.AssertStringEquals(t, "Invalid input", testingBuildParam.Inputs[0].String(), "out/soong/provenance_metadata.textproto")
+			android.AssertStringEquals(t, "Invalid output path", output, "provenance_metadata")
+			android.AssertIntEquals(t, "Invalid args", len(testingBuildParam.Args), 0)
+		}
+	}
+}
diff --git a/provenance/tools/Android.bp b/provenance/tools/Android.bp
new file mode 100644
index 0000000..0eddd76
--- /dev/null
+++ b/provenance/tools/Android.bp
@@ -0,0 +1,51 @@
+/*
+ * 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "gen_provenance_metadata",
+    srcs: [
+        "gen_provenance_metadata.py",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+    libs: [
+        "provenance_metadata_proto",
+        "libprotobuf-python",
+    ],
+}
+
+python_test_host {
+    name: "gen_provenance_metadata_test",
+    main: "gen_provenance_metadata_test.py",
+    srcs: [
+        "gen_provenance_metadata_test.py",
+    ],
+    data: [
+        ":gen_provenance_metadata",
+    ],
+    libs: [
+        "provenance_metadata_proto",
+        "libprotobuf-python",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/provenance/tools/gen_provenance_metadata.py b/provenance/tools/gen_provenance_metadata.py
new file mode 100644
index 0000000..b33f911
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import argparse
+import hashlib
+import sys
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+def Log(*info):
+  if args.verbose:
+    for i in info:
+      print(i)
+
+def ParseArgs(argv):
+  parser = argparse.ArgumentParser(description='Create provenance metadata for a prebuilt artifact')
+  parser.add_argument('-v', '--verbose', action='store_true', help='Print more information in execution')
+  parser.add_argument('--module_name', help='Module name', required=True)
+  parser.add_argument('--artifact_path', help='Relative path of the prebuilt artifact in source tree', required=True)
+  parser.add_argument('--install_path', help='Absolute path of the artifact in the filesystem images', required=True)
+  parser.add_argument('--metadata_path', help='Path of the provenance metadata file created for the artifact', required=True)
+  return parser.parse_args(argv)
+
+def main(argv):
+  global args
+  args = ParseArgs(argv)
+  Log("Args:", vars(args))
+
+  provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+  provenance_metadata.module_name = args.module_name
+  provenance_metadata.artifact_path = args.artifact_path
+  provenance_metadata.artifact_install_path = args.install_path
+
+  Log("Generating SHA256 hash")
+  h = hashlib.sha256()
+  with open(args.artifact_path, "rb") as artifact_file:
+    h.update(artifact_file.read())
+  provenance_metadata.artifact_sha256 = h.hexdigest()
+
+  text_proto = [
+      "# proto-file: build/soong/provenance/proto/provenance_metadata.proto",
+      "# proto-message: ProvenanceMetaData",
+      "",
+      text_format.MessageToString(provenance_metadata)
+  ]
+  with open(args.metadata_path, "wt") as metadata_file:
+    file_content = "\n".join(text_proto)
+    Log("Writing provenance metadata in textproto:", file_content)
+    metadata_file.write(file_content)
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/provenance/tools/gen_provenance_metadata_test.py b/provenance/tools/gen_provenance_metadata_test.py
new file mode 100644
index 0000000..2fc04bf
--- /dev/null
+++ b/provenance/tools/gen_provenance_metadata_test.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import hashlib
+import logging
+import os
+import subprocess
+import tempfile
+import unittest
+
+import google.protobuf.text_format as text_format
+import provenance_metadata_pb2
+
+logger = logging.getLogger(__name__)
+
+def run(args, verbose=None, **kwargs):
+  """Creates and returns a subprocess.Popen object.
+
+  Args:
+    args: The command represented as a list of strings.
+    verbose: Whether the commands should be shown. Default to the global
+        verbosity if unspecified.
+    kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+        stdin, etc. stdout and stderr will default to subprocess.PIPE and
+        subprocess.STDOUT respectively unless caller specifies any of them.
+        universal_newlines will default to True, as most of the users in
+        releasetools expect string output.
+
+  Returns:
+    A subprocess.Popen object.
+  """
+  if 'stdout' not in kwargs and 'stderr' not in kwargs:
+    kwargs['stdout'] = subprocess.PIPE
+    kwargs['stderr'] = subprocess.STDOUT
+  if 'universal_newlines' not in kwargs:
+    kwargs['universal_newlines'] = True
+  if verbose:
+    logger.info("  Running: \"%s\"", " ".join(args))
+  return subprocess.Popen(args, **kwargs)
+
+
+def run_and_check_output(args, verbose=None, **kwargs):
+  """Runs the given command and returns the output.
+
+  Args:
+    args: The command represented as a list of strings.
+    verbose: Whether the commands should be shown. Default to the global
+        verbosity if unspecified.
+    kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
+        stdin, etc. stdout and stderr will default to subprocess.PIPE and
+        subprocess.STDOUT respectively unless caller specifies any of them.
+
+  Returns:
+    The output string.
+
+  Raises:
+    ExternalError: On non-zero exit from the command.
+  """
+  proc = run(args, verbose=verbose, **kwargs)
+  output, _ = proc.communicate()
+  if output is None:
+    output = ""
+  if verbose:
+    logger.info("%s", output.rstrip())
+  if proc.returncode != 0:
+    raise RuntimeError(
+        "Failed to run command '{}' (exit code {}):\n{}".format(
+            args, proc.returncode, output))
+  return output
+
+def run_host_command(args, verbose=None, **kwargs):
+  host_build_top = os.environ.get("ANDROID_BUILD_TOP")
+  if host_build_top:
+    host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin")
+    args[0] = os.path.join(host_command_dir, args[0])
+  return run_and_check_output(args, verbose, **kwargs)
+
+def sha256(s):
+  h = hashlib.sha256()
+  h.update(bytearray(s, 'utf-8'))
+  return h.hexdigest()
+
+class ProvenanceMetaDataToolTest(unittest.TestCase):
+
+  def test_gen_provenance_metadata(self):
+    artifact_content = "test artifact"
+    artifact_file = tempfile.mktemp()
+    with open(artifact_file,"wt") as f:
+      f.write(artifact_content)
+    metadata_file = tempfile.mktemp()
+    cmd = ["gen_provenance_metadata"]
+    cmd.extend(["--module_name", "a"])
+    cmd.extend(["--artifact_path", artifact_file])
+    cmd.extend(["--install_path", "b"])
+    cmd.extend(["--metadata_path", metadata_file])
+    output = run_host_command(cmd)
+    self.assertEqual(output, "")
+
+    with open(metadata_file,"rt") as f:
+      data = f.read()
+      provenance_metadata = provenance_metadata_pb2.ProvenanceMetadata()
+      text_format.Parse(data, provenance_metadata)
+      self.assertEqual(provenance_metadata.module_name, "a")
+      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))
+
+    os.remove(artifact_file)
+    os.remove(metadata_file)
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
\ No newline at end of file
diff --git a/python/androidmk.go b/python/androidmk.go
index 13b4172..233d867 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -75,16 +75,10 @@
 }
 
 func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if base.Target().Os.Class == android.Host {
-		entries.OutputFile = android.OptionalPathForPath(installer.path)
-	}
-
 	entries.Required = append(entries.Required, "libc++")
 	entries.ExtraEntries = append(entries.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			path, file := filepath.Split(installer.path.ToMakePath().String())
+			path, file := filepath.Split(installer.path.String())
 			stem := strings.TrimSuffix(file, filepath.Ext(file))
 
 			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
diff --git a/python/binary.go b/python/binary.go
index e955492..99c6259 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -27,7 +27,6 @@
 
 func init() {
 	registerPythonBinaryComponents(android.InitRegistrationContext)
-	android.RegisterBp2BuildMutator("python_binary_host", PythonBinaryBp2Build)
 }
 
 func registerPythonBinaryComponents(ctx android.RegistrationContext) {
@@ -35,75 +34,46 @@
 }
 
 type bazelPythonBinaryAttributes struct {
-	Main           string
+	Main           *string
 	Srcs           bazel.LabelListAttribute
-	Data           bazel.LabelListAttribute
-	Python_version string
+	Deps           bazel.LabelListAttribute
+	Python_version *string
 }
 
-type bazelPythonBinary struct {
-	android.BazelTargetModuleBase
-	bazelPythonBinaryAttributes
-}
-
-func BazelPythonBinaryFactory() android.Module {
-	module := &bazelPythonBinary{}
-	module.AddProperties(&module.bazelPythonBinaryAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func (m *bazelPythonBinary) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelPythonBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-func PythonBinaryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*Module)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
-	// a Module can be something other than a python_binary_host
-	if ctx.ModuleType() != "python_binary_host" {
-		return
-	}
-
-	var main string
+func pythonBinaryBp2Build(ctx android.TopDownMutatorContext, m *Module) {
+	var main *string
 	for _, propIntf := range m.GetProperties() {
 		if props, ok := propIntf.(*BinaryProperties); ok {
 			// main is optional.
 			if props.Main != nil {
-				main = *props.Main
+				main = props.Main
 				break
 			}
 		}
 	}
+
 	// TODO(b/182306917): this doesn't fully handle all nested props versioned
 	// by the python version, which would have been handled by the version split
 	// mutator. This is sufficient for very simple python_binary_host modules
 	// under Bionic.
 	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false)
 	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
-	var python_version string
+	var python_version *string
 	if py3Enabled && py2Enabled {
 		panic(fmt.Errorf(
 			"error for '%s' module: bp2build's python_binary_host converter does not support "+
 				"converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name()))
 	} else if py2Enabled {
-		python_version = "PY2"
+		python_version = &pyVersion2
 	} else {
 		// do nothing, since python_version defaults to PY3.
 	}
 
-	srcs := android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
-	data := android.BazelLabelForModuleSrc(ctx, m.properties.Data)
-
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
 	attrs := &bazelPythonBinaryAttributes{
 		Main:           main,
-		Srcs:           bazel.MakeLabelListAttribute(srcs),
-		Data:           bazel.MakeLabelListAttribute(data),
+		Srcs:           baseAttrs.Srcs,
+		Deps:           baseAttrs.Deps,
 		Python_version: python_version,
 	}
 
@@ -112,7 +82,10 @@
 		Rule_class: "py_binary",
 	}
 
-	ctx.CreateBazelTargetModule(BazelPythonBinaryFactory, m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
 }
 
 type BinaryProperties struct {
diff --git a/python/library.go b/python/library.go
index 9663b3c..d026c13 100644
--- a/python/library.go
+++ b/python/library.go
@@ -18,6 +18,9 @@
 
 import (
 	"android/soong/android"
+	"android/soong/bazel"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -32,11 +35,57 @@
 func PythonLibraryHostFactory() android.Module {
 	module := newModule(android.HostSupported, android.MultilibFirst)
 
+	android.InitBazelModule(module)
+
 	return module.init()
 }
 
+type bazelPythonLibraryAttributes struct {
+	Srcs         bazel.LabelListAttribute
+	Deps         bazel.LabelListAttribute
+	Srcs_version *string
+}
+
+func pythonLibBp2Build(ctx android.TopDownMutatorContext, m *Module) {
+	// TODO(b/182306917): this doesn't fully handle all nested props versioned
+	// by the python version, which would have been handled by the version split
+	// mutator. This is sufficient for very simple python_library modules under
+	// Bionic.
+	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, true)
+	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
+	var python_version *string
+	if py2Enabled && !py3Enabled {
+		python_version = &pyVersion2
+	} else if !py2Enabled && py3Enabled {
+		python_version = &pyVersion3
+	} else if !py2Enabled && !py3Enabled {
+		ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
+	} else {
+		// do nothing, since python_version defaults to PY2ANDPY3
+	}
+
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
+	attrs := &bazelPythonLibraryAttributes{
+		Srcs:         baseAttrs.Srcs,
+		Deps:         baseAttrs.Deps,
+		Srcs_version: python_version,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "py_library",
+		Bzl_load_location: "//build/bazel/rules/python:library.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
+}
+
 func PythonLibraryFactory() android.Module {
 	module := newModule(android.HostAndDeviceSupported, android.MultilibBoth)
 
+	android.InitBazelModule(module)
+
 	return module.init()
 }
diff --git a/python/python.go b/python/python.go
index 0f5b788..b100cc3 100644
--- a/python/python.go
+++ b/python/python.go
@@ -22,6 +22,8 @@
 	"regexp"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -45,7 +47,7 @@
 type VersionProperties struct {
 	// whether the module is required to be built with this version.
 	// Defaults to true for Python 3, and false otherwise.
-	Enabled *bool `android:"arch_variant"`
+	Enabled *bool
 
 	// list of source files specific to this Python version.
 	// Using the syntax ":module", srcs may reference the outputs of other modules that produce source files,
@@ -60,7 +62,7 @@
 	Libs []string `android:"arch_variant"`
 
 	// whether the binary is required to be built with embedded launcher for this version, defaults to false.
-	Embedded_launcher *bool `android:"arch_variant"` // TODO(b/174041232): Remove this property
+	Embedded_launcher *bool // TODO(b/174041232): Remove this property
 }
 
 // properties that apply to all python modules
@@ -70,10 +72,10 @@
 	// eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
 	// (from a.b.c import ...) statement.
 	// if left unspecified, all the source/data files path is unchanged within zip file.
-	Pkg_path *string `android:"arch_variant"`
+	Pkg_path *string
 
 	// true, if the Python module is used internally, eg, Python std libs.
-	Is_internal *bool `android:"arch_variant"`
+	Is_internal *bool
 
 	// list of source (.py) files compatible both with Python2 and Python3 used to compile the
 	// Python module.
@@ -120,6 +122,18 @@
 	Embedded_launcher *bool `blueprint:"mutated"`
 }
 
+type baseAttributes struct {
+	// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
+	//Pkg_path    bazel.StringAttribute
+	// TODO: Related to Pkg_bath and similarLy gated
+	//Is_internal bazel.BoolAttribute
+	// Combines Srcs and Exclude_srcs
+	Srcs bazel.LabelListAttribute
+	Deps bazel.LabelListAttribute
+	// Combines Data and Java_data (invariant)
+	Data bazel.LabelListAttribute
+}
+
 // Used to store files of current module after expanding dependencies
 type pathMapping struct {
 	dest string
@@ -177,6 +191,25 @@
 	}
 }
 
+func (m *Module) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
+	var attrs baseAttributes
+	archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
+	for axis, configToProps := range archVariantBaseProps {
+		for config, props := range configToProps {
+			if baseProps, ok := props.(*BaseProperties); ok {
+				attrs.Srcs.SetSelectValue(axis, config,
+					android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
+				attrs.Deps.SetSelectValue(axis, config,
+					android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
+				data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
+				data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
+				attrs.Data.SetSelectValue(axis, config, data)
+			}
+		}
+	}
+	return attrs
+}
+
 // bootstrapper interface should be implemented for runnable modules, e.g. binary and test
 type bootstrapper interface {
 	bootstrapperProps() []interface{}
@@ -310,13 +343,16 @@
 // HostToolPath returns a path if appropriate such that this module can be used as a host tool,
 // fulfilling HostToolProvider interface.
 func (p *Module) HostToolPath() android.OptionalPath {
-	if p.installer == nil {
-		// python_library is just meta module, and doesn't have any installer.
-		return android.OptionalPath{}
+	if p.installer != nil {
+		if bin, ok := p.installer.(*binaryDecorator); ok {
+			// TODO: This should only be set when building host binaries -- tests built for device would be
+			// setting this incorrectly.
+			return android.OptionalPathForPath(bin.path)
+		}
 	}
-	// TODO: This should only be set when building host binaries -- tests built for device would be
-	// setting this incorrectly.
-	return android.OptionalPathForPath(p.installer.(*binaryDecorator).path)
+
+	return android.OptionalPath{}
+
 }
 
 // OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
@@ -387,6 +423,9 @@
 		if ctx.Target().Os.Bionic() {
 			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
 		}
+		if ctx.Target().Os == android.LinuxMusl && !ctx.Config().HostStaticBinaries() {
+			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
+		}
 
 		switch p.properties.Actual_version {
 		case pyVersion2:
@@ -396,6 +435,7 @@
 			if p.bootstrapper.autorun() {
 				launcherModule = "py2-launcher-autorun"
 			}
+
 			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
 
 		case pyVersion3:
@@ -405,6 +445,9 @@
 			if p.bootstrapper.autorun() {
 				launcherModule = "py3-launcher-autorun"
 			}
+			if ctx.Config().HostStaticBinaries() && ctx.Target().Os == android.LinuxMusl {
+				launcherModule += "-static"
+			}
 
 			if ctx.Device() {
 				launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
@@ -632,18 +675,25 @@
 }
 
 // isPythonLibModule returns whether the given module is a Python library Module or not
-// This is distinguished by the fact that Python libraries are not installable, while other Python
-// modules are.
 func isPythonLibModule(module blueprint.Module) bool {
 	if m, ok := module.(*Module); ok {
-		// Python library has no bootstrapper or installer
-		if m.bootstrapper == nil && m.installer == nil {
-			return true
-		}
+		return m.isLibrary()
 	}
 	return false
 }
 
+// This is distinguished by the fact that Python libraries are not installable, while other Python
+// modules are.
+func (p *Module) isLibrary() bool {
+	// Python library has no bootstrapper or installer
+	return p.bootstrapper == nil && p.installer == nil
+}
+
+func (p *Module) isBinary() bool {
+	_, ok := p.bootstrapper.(*binaryDecorator)
+	return ok
+}
+
 // collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
 // for module and its transitive dependencies and collects list of data/source file
 // zips for transitive dependencies.
@@ -675,7 +725,7 @@
 		if !isPythonLibModule(child) {
 			ctx.PropertyErrorf("libs",
 				"the dependency %q of module %q is not Python library!",
-				ctx.ModuleName(), ctx.OtherModuleName(child))
+				ctx.OtherModuleName(child), ctx.ModuleName())
 		}
 		// collect source and data paths, checking that there are no duplicate output file conflicts
 		if dep, ok := child.(pythonDependency); ok {
@@ -717,6 +767,14 @@
 	return true
 }
 
+func (p *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	if p.isLibrary() {
+		pythonLibBp2Build(ctx, p)
+	} else if p.isBinary() {
+		pythonBinaryBp2Build(ctx, p)
+	}
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/python/test.go b/python/test.go
index 6713189..7413782 100644
--- a/python/test.go
+++ b/python/test.go
@@ -15,6 +15,8 @@
 package python
 
 import (
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 )
@@ -102,6 +104,9 @@
 	binary.pythonInstaller = NewPythonInstaller("nativetest", "nativetest64")
 
 	test := &testDecorator{binaryDecorator: binary}
+	if hod == android.HostSupportedNoCross && test.testProperties.Test_options.Unit_test == nil {
+		test.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
 
 	module.bootstrapper = test
 	module.installer = test
diff --git a/rust/Android.bp b/rust/Android.bp
index b611672..3fd68e5 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -11,8 +11,10 @@
         "soong-bloaty",
         "soong-cc",
         "soong-rust-config",
+        "soong-snapshot",
     ],
     srcs: [
+        "afdo.go",
         "androidmk.go",
         "benchmark.go",
         "binary.go",
@@ -32,10 +34,12 @@
         "rust.go",
         "sanitize.go",
         "source_provider.go",
+        "snapshot_prebuilt.go",
         "snapshot_utils.go",
         "strip.go",
         "test.go",
         "testing.go",
+        "toolchain_library.go",
     ],
     testSrcs: [
         "benchmark_test.go",
@@ -48,11 +52,14 @@
         "fuzz_test.go",
         "image_test.go",
         "library_test.go",
+        "proc_macro_test.go",
         "project_json_test.go",
         "protobuf_test.go",
         "rust_test.go",
+        "sanitize_test.go",
         "source_provider_test.go",
         "test_test.go",
+        "vendor_snapshot_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/rust/OWNERS b/rust/OWNERS
index b5b795c..ddaebc5 100644
--- a/rust/OWNERS
+++ b/rust/OWNERS
@@ -1,5 +1,5 @@
 # Additional owner/reviewers for rust rules, including parent directory owners.
-per-file * = chh@google.com, ivanlozano@google.com, jeffv@google.com, mmaurer@google.com, srhines@google.com
+per-file * = chiw@google.com, chriswailes@google.com, ivanlozano@google.com, jeffv@google.com, mmaurer@google.com, srhines@google.com
 
 # Limited owners/reviewers of the allowed list.
-per-file allowed_list.go = chh@google.com, ivanlozano@google.com, jeffv@google.com, jgalenson@google.com, mmaurer@google.com, srhines@google.com
+per-file allowed_list.go = chiw@google.com, chriswailes@google.com, ivanlozano@google.com, jeffv@google.com, mmaurer@google.com, srhines@google.com
diff --git a/rust/afdo.go b/rust/afdo.go
new file mode 100644
index 0000000..996fd7e
--- /dev/null
+++ b/rust/afdo.go
@@ -0,0 +1,48 @@
+// Copyright 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 rust
+
+import (
+	"fmt"
+
+	"android/soong/cc"
+)
+
+const afdoFlagFormat = "-Zprofile-sample-use=%s"
+
+type afdo struct {
+	Properties cc.AfdoProperties
+}
+
+func (afdo *afdo) props() []interface{} {
+	return []interface{}{&afdo.Properties}
+}
+
+func (afdo *afdo) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
+	if ctx.Host() {
+		return flags, deps
+	}
+
+	if afdo != nil && afdo.Properties.Afdo {
+		if profileFile := afdo.Properties.GetAfdoProfileFile(ctx, ctx.ModuleName()); profileFile.Valid() {
+			profileUseFlag := fmt.Sprintf(afdoFlagFormat, profileFile)
+			flags.RustFlags = append(flags.RustFlags, profileUseFlag)
+
+			profileFilePath := profileFile.Path()
+			deps.AfdoProfiles = append(deps.AfdoProfiles, profileFilePath)
+		}
+	}
+	return flags, deps
+}
diff --git a/rust/androidmk.go b/rust/androidmk.go
index ea45ebd..2361e03 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -50,8 +50,8 @@
 	}
 
 	ret := android.AndroidMkEntries{
-		OutputFile: mod.unstrippedOutputFile,
-		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
+		OutputFile: android.OptionalPathForPath(mod.UnstrippedOutputFile()),
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
@@ -106,6 +106,9 @@
 			}
 			entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
 			entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
+			if test.Properties.Data_bins != nil {
+				entries.AddStrings("LOCAL_TEST_DATA_BINS", test.Properties.Data_bins...)
+			}
 		})
 
 	cc.AndroidMkWriteTestData(test.data, ret)
@@ -137,10 +140,15 @@
 	} else if library.shared() {
 		ret.Class = "SHARED_LIBRARIES"
 	}
-
 	if library.distFile.Valid() {
 		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
 	}
+	ret.ExtraEntries = append(ret.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			if library.tocFile.Valid() {
+				entries.SetString("LOCAL_SOONG_TOC", library.tocFile.String())
+			}
+		})
 }
 
 func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
@@ -181,19 +189,14 @@
 		return
 	}
 
-	var unstrippedOutputFile android.OptionalPath
-	// Soong installation is only supported for host modules. Have Make
-	// installation trigger Soong installation.
-	if ctx.Target().Os.Class == android.Host {
-		ret.OutputFile = android.OptionalPathForPath(compiler.path)
-	} else if compiler.strippedOutputFile.Valid() {
-		unstrippedOutputFile = ret.OutputFile
+	if compiler.strippedOutputFile.Valid() {
 		ret.OutputFile = compiler.strippedOutputFile
 	}
+
 	ret.ExtraEntries = append(ret.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
-			path, file := filepath.Split(compiler.path.ToMakePath().String())
+			entries.SetPath("LOCAL_SOONG_UNSTRIPPED_BINARY", compiler.unstrippedOutputFile)
+			path, file := filepath.Split(compiler.path.String())
 			stem, suffix, _ := android.SplitFileExt(file)
 			entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
 			entries.SetString("LOCAL_MODULE_PATH", path)
@@ -205,24 +208,24 @@
 	ctx.SubAndroidMk(entries, fuzz.binaryDecorator)
 
 	var fuzzFiles []string
-	for _, d := range fuzz.corpus {
+	for _, d := range fuzz.fuzzPackagedModule.Corpus {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.corpusIntermediateDir.String())+":corpus/"+d.Base())
+			filepath.Dir(fuzz.fuzzPackagedModule.CorpusIntermediateDir.String())+":corpus/"+d.Base())
 	}
 
-	for _, d := range fuzz.data {
+	for _, d := range fuzz.fuzzPackagedModule.Data {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.dataIntermediateDir.String())+":data/"+d.Rel())
+			filepath.Dir(fuzz.fuzzPackagedModule.DataIntermediateDir.String())+":data/"+d.Rel())
 	}
 
-	if fuzz.dictionary != nil {
+	if fuzz.fuzzPackagedModule.Dictionary != nil {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.dictionary.String())+":"+fuzz.dictionary.Base())
+			filepath.Dir(fuzz.fuzzPackagedModule.Dictionary.String())+":"+fuzz.fuzzPackagedModule.Dictionary.Base())
 	}
 
-	if fuzz.config != nil {
+	if fuzz.fuzzPackagedModule.Config != nil {
 		fuzzFiles = append(fuzzFiles,
-			filepath.Dir(fuzz.config.String())+":config.json")
+			filepath.Dir(fuzz.fuzzPackagedModule.Config.String())+":config.json")
 	}
 
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
diff --git a/rust/binary.go b/rust/binary.go
index ffc0413..0dc320e 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -31,6 +31,12 @@
 	Static_executable *bool `android:"arch_variant"`
 }
 
+type binaryInterface interface {
+	binary() bool
+	staticallyLinked() bool
+	testBinary() bool
+}
+
 type binaryDecorator struct {
 	*baseCompiler
 	stripper Stripper
@@ -86,14 +92,23 @@
 func (binary *binaryDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = binary.baseCompiler.compilerDeps(ctx, deps)
 
+	static := Bool(binary.Properties.Static_executable)
 	if ctx.toolchain().Bionic() {
-		deps = bionicDeps(ctx, deps, Bool(binary.Properties.Static_executable))
-		if Bool(binary.Properties.Static_executable) {
-			deps.CrtBegin = "crtbegin_static"
+		deps = bionicDeps(ctx, deps, static)
+		if static {
+			deps.CrtBegin = []string{"crtbegin_static"}
 		} else {
-			deps.CrtBegin = "crtbegin_dynamic"
+			deps.CrtBegin = []string{"crtbegin_dynamic"}
 		}
-		deps.CrtEnd = "crtend_android"
+		deps.CrtEnd = []string{"crtend_android"}
+	} else if ctx.Os() == android.LinuxMusl {
+		deps = muslDeps(ctx, deps, static)
+		if static {
+			deps.CrtBegin = []string{"libc_musl_crtbegin_static"}
+		} else {
+			deps.CrtBegin = []string{"libc_musl_crtbegin_dynamic", "musl_linker_script"}
+		}
+		deps.CrtEnd = []string{"libc_musl_crtend"}
 	}
 
 	return deps
@@ -117,26 +132,33 @@
 	fileName := binary.getStem(ctx) + ctx.toolchain().ExecutableSuffix()
 	srcPath, _ := srcPathFromModuleSrcs(ctx, binary.baseCompiler.Properties.Srcs)
 	outputFile := android.PathForModuleOut(ctx, fileName)
+	ret := outputFile
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
 
+	if binary.stripper.NeedsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
+		binary.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
+
+		binary.baseCompiler.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
+	}
+	binary.baseCompiler.unstrippedOutputFile = outputFile
+
 	TransformSrcToBinary(ctx, srcPath, deps, flags, outputFile)
 
-	if binary.stripper.NeedsStrip(ctx) {
-		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
-		binary.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
-		binary.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
-	}
-
-	return outputFile
+	return ret
 }
 
 func (binary *binaryDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
 	// Binaries default to dylib dependencies for device, rlib for host.
 	if binary.preferRlib() {
 		return rlibAutoDep
+	} else if mod, ok := ctx.Module().(*Module); ok && mod.InVendor() {
+		// Vendor Rust binaries should prefer rlibs.
+		return rlibAutoDep
 	} else if ctx.Device() {
 		return dylibAutoDep
 	} else {
@@ -147,10 +169,20 @@
 func (binary *binaryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
 	if binary.preferRlib() {
 		return RlibLinkage
+	} else if ctx.RustModule().InVendor() {
+		return RlibLinkage
 	}
 	return binary.baseCompiler.stdLinkage(ctx)
 }
 
-func (binary *binaryDecorator) isDependencyRoot() bool {
+func (binary *binaryDecorator) binary() bool {
 	return true
 }
+
+func (binary *binaryDecorator) staticallyLinked() bool {
+	return Bool(binary.Properties.Static_executable)
+}
+
+func (binary *binaryDecorator) testBinary() bool {
+	return false
+}
diff --git a/rust/binary_test.go b/rust/binary_test.go
index 86f50d3..7dac249 100644
--- a/rust/binary_test.go
+++ b/rust/binary_test.go
@@ -106,7 +106,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Output("fizz-buzz")
+	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Rule("rustc")
 
 	flags := fizzBuzz.Args["rustcFlags"]
 	if strings.Contains(flags, "--test") {
@@ -114,6 +114,23 @@
 	}
 }
 
+// Test that the bootstrap property sets the appropriate linker
+func TestBootstrap(t *testing.T) {
+	ctx := testRust(t, `
+		rust_binary {
+			name: "foo",
+			srcs: ["foo.rs"],
+			bootstrap: true,
+		}`)
+
+	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
+
+	flag := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker64"
+	if !strings.Contains(foo.Args["linkFlags"], flag) {
+		t.Errorf("missing link flag to use bootstrap linker, expecting %#v, linkFlags: %#v", flag, foo.Args["linkFlags"])
+	}
+}
+
 func TestStaticBinaryFlags(t *testing.T) {
 	ctx := testRust(t, `
 		rust_binary {
@@ -122,7 +139,7 @@
 			static_executable: true,
 		}`)
 
-	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Output("fizz")
+	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
 	fizzMod := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Module().(*Module)
 
 	flags := fizzOut.Args["rustcFlags"]
@@ -156,7 +173,7 @@
 			name: "libfoo",
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Output("fizz-buzz")
+	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Rule("rustc")
 	linkFlags := fizzBuzz.Args["linkFlags"]
 	if !strings.Contains(linkFlags, "/libfoo.so") {
 		t.Errorf("missing shared dependency 'libfoo.so' in linkFlags: %#v", linkFlags)
@@ -180,15 +197,17 @@
 	`)
 
 	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a")
-	foo.Output("stripped/foo")
+	foo.Output("unstripped/foo")
+	foo.Output("foo")
+
 	// Check that the `cp` rules is using the stripped version as input.
 	cp := foo.Rule("android.Cp")
-	if !strings.HasSuffix(cp.Input.String(), "stripped/foo") {
+	if strings.HasSuffix(cp.Input.String(), "unstripped/foo") {
 		t.Errorf("installed binary not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("bar", "android_arm64_armv8-a").MaybeOutput("stripped/bar")
+	fizzBar := ctx.ModuleForTests("bar", "android_arm64_armv8-a").MaybeOutput("unstripped/bar")
 	if fizzBar.Rule != nil {
-		t.Errorf("stripped version of bar has been generated")
+		t.Errorf("unstripped binary exists, so stripped binary has incorrectly been generated")
 	}
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index f9e6cd0..b4626a0 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -15,6 +15,7 @@
 package rust
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -29,7 +30,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r416183b"
+	bindgenClangVersion = "clang-r450784d"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -137,15 +138,40 @@
 	implicits = append(implicits, deps.depGeneratedHeaders...)
 
 	// Default clang flags
-	cflags = append(cflags, "${cc_config.CommonClangGlobalCflags}")
+	cflags = append(cflags, "${cc_config.CommonGlobalCflags}")
 	if ctx.Device() {
-		cflags = append(cflags, "${cc_config.DeviceClangGlobalCflags}")
+		cflags = append(cflags, "${cc_config.DeviceGlobalCflags}")
 	}
 
 	// Toolchain clang flags
 	cflags = append(cflags, "-target "+ccToolchain.ClangTriple())
-	cflags = append(cflags, strings.ReplaceAll(ccToolchain.ClangCflags(), "${config.", "${cc_config."))
-	cflags = append(cflags, strings.ReplaceAll(ccToolchain.ToolchainClangCflags(), "${config.", "${cc_config."))
+	cflags = append(cflags, strings.ReplaceAll(ccToolchain.Cflags(), "${config.", "${cc_config."))
+	cflags = append(cflags, strings.ReplaceAll(ccToolchain.ToolchainCflags(), "${config.", "${cc_config."))
+
+	if ctx.RustModule().UseVndk() {
+		cflags = append(cflags, "-D__ANDROID_VNDK__")
+		if ctx.RustModule().InVendor() {
+			cflags = append(cflags, "-D__ANDROID_VENDOR__")
+		} else if ctx.RustModule().InProduct() {
+			cflags = append(cflags, "-D__ANDROID_PRODUCT__")
+		}
+	}
+
+	if ctx.RustModule().InRecovery() {
+		cflags = append(cflags, "-D__ANDROID_RECOVERY__")
+	}
+
+	if mctx, ok := ctx.(*moduleContext); ok && mctx.apexVariationName() != "" {
+		cflags = append(cflags, "-D__ANDROID_APEX__")
+		if ctx.Device() {
+			cflags = append(cflags, fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
+				ctx.RustModule().apexSdkVersion.FinalOrFutureInt()))
+		}
+	}
+
+	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+		cflags = append(cflags, "-D__ANDROID_NATIVE_BRIDGE__")
+	}
 
 	// Dependency clang flags and include paths
 	cflags = append(cflags, deps.depClangFlags...)
@@ -262,6 +288,8 @@
 	deps = b.BaseSourceProvider.SourceProviderDeps(ctx, deps)
 	if ctx.toolchain().Bionic() {
 		deps = bionicDeps(ctx, deps, false)
+	} else if ctx.Os() == android.LinuxMusl {
+		deps = muslDeps(ctx, deps, false)
 	}
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
diff --git a/rust/builder.go b/rust/builder.go
index 6db508d..20ca5db 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -95,7 +95,7 @@
 
 func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin")
 }
@@ -112,13 +112,13 @@
 
 func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib")
 }
 
 func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto")
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib")
 }
 
@@ -185,7 +185,7 @@
 }
 
 func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, crate_type string) buildOutput {
+	outputFile android.WritablePath, crateType string) buildOutput {
 
 	var inputs android.Paths
 	var implicits android.Paths
@@ -204,7 +204,7 @@
 	// Collect rustc flags
 	rustcFlags = append(rustcFlags, flags.GlobalRustFlags...)
 	rustcFlags = append(rustcFlags, flags.RustFlags...)
-	rustcFlags = append(rustcFlags, "--crate-type="+crate_type)
+	rustcFlags = append(rustcFlags, "--crate-type="+crateType)
 	if crateName != "" {
 		rustcFlags = append(rustcFlags, "--crate-name="+crateName)
 	}
@@ -216,10 +216,26 @@
 	// Suppress an implicit sysroot
 	rustcFlags = append(rustcFlags, "--sysroot=/dev/null")
 
+	// Enable incremental compilation if requested by user
+	if ctx.Config().IsEnvTrue("SOONG_RUSTC_INCREMENTAL") {
+		incrementalPath := android.PathForOutput(ctx, "rustc").String()
+
+		rustcFlags = append(rustcFlags, "-C incremental="+incrementalPath)
+	}
+
 	// Collect linker flags
 	linkFlags = append(linkFlags, flags.GlobalLinkFlags...)
 	linkFlags = append(linkFlags, flags.LinkFlags...)
 
+	// Check if this module needs to use the bootstrap linker
+	if ctx.RustModule().Bootstrap() && !ctx.RustModule().InRecovery() && !ctx.RustModule().InRamdisk() && !ctx.RustModule().InVendorRamdisk() {
+		dynamicLinker := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker"
+		if ctx.toolchain().Is64Bit() {
+			dynamicLinker += "64"
+		}
+		linkFlags = append(linkFlags, dynamicLinker)
+	}
+
 	libFlags := makeLibFlags(deps)
 
 	// Collect dependencies
@@ -229,10 +245,10 @@
 	implicits = append(implicits, deps.StaticLibs...)
 	implicits = append(implicits, deps.SharedLibDeps...)
 	implicits = append(implicits, deps.srcProviderFiles...)
+	implicits = append(implicits, deps.AfdoProfiles...)
 
-	if deps.CrtBegin.Valid() {
-		implicits = append(implicits, deps.CrtBegin.Path(), deps.CrtEnd.Path())
-	}
+	implicits = append(implicits, deps.CrtBegin...)
+	implicits = append(implicits, deps.CrtEnd...)
 
 	if len(deps.SrcDeps) > 0 {
 		moduleGenDir := ctx.RustModule().compiler.CargoOutDir()
@@ -258,7 +274,18 @@
 		implicits = append(implicits, outputs.Paths()...)
 	}
 
-	envVars = append(envVars, "ANDROID_RUST_VERSION="+config.RustDefaultVersion)
+	envVars = append(envVars, "ANDROID_RUST_VERSION="+config.GetRustVersion(ctx))
+
+	if ctx.RustModule().compiler.CargoEnvCompat() {
+		if _, ok := ctx.RustModule().compiler.(*binaryDecorator); ok {
+			envVars = append(envVars, "CARGO_BIN_NAME="+strings.TrimSuffix(outputFile.Base(), outputFile.Ext()))
+		}
+		envVars = append(envVars, "CARGO_CRATE_NAME="+ctx.RustModule().CrateName())
+		pkgVersion := ctx.RustModule().compiler.CargoPkgVersion()
+		if pkgVersion != "" {
+			envVars = append(envVars, "CARGO_PKG_VERSION="+pkgVersion)
+		}
+	}
 
 	if flags.Clippy {
 		clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy")
@@ -291,8 +318,8 @@
 			"rustcFlags": strings.Join(rustcFlags, " "),
 			"linkFlags":  strings.Join(linkFlags, " "),
 			"libFlags":   strings.Join(libFlags, " "),
-			"crtBegin":   deps.CrtBegin.String(),
-			"crtEnd":     deps.CrtEnd.String(),
+			"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
+			"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
 			"envVars":    strings.Join(envVars, " "),
 		},
 	})
@@ -323,6 +350,12 @@
 	rustdocFlags = append(rustdocFlags, makeLibFlags(deps)...)
 	docTimestampFile := android.PathForModuleOut(ctx, "rustdoc.timestamp")
 
+	// Silence warnings about renamed lints for third-party crates
+	modulePath := android.PathForModuleSrc(ctx).String()
+	if android.IsThirdPartyPath(modulePath) {
+		rustdocFlags = append(rustdocFlags, " -A renamed_and_removed_lints")
+	}
+
 	// Yes, the same out directory is used simultaneously by all rustdoc builds.
 	// This is what cargo does. The docs for individual crates get generated to
 	// a subdirectory named for the crate, and rustdoc synchronizes writes to
@@ -335,7 +368,7 @@
 		Description: "rustdoc " + main.Rel(),
 		Output:      docTimestampFile,
 		Input:       main,
-		Implicit:    ctx.RustModule().unstrippedOutputFile.Path(),
+		Implicit:    ctx.RustModule().UnstrippedOutputFile(),
 		Args: map[string]string{
 			"rustdocFlags": strings.Join(rustdocFlags, " "),
 			"outDir":       docDir.String(),
diff --git a/rust/compiler.go b/rust/compiler.go
index 1598ebf..19499fa 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"strings"
 
 	"github.com/google/blueprint/proptools"
 
@@ -64,7 +65,11 @@
 )
 
 type BaseCompilerProperties struct {
-	// path to the source file that is the main entry point of the program (e.g. main.rs or lib.rs)
+	// path to the source file that is the main entry point of the program (e.g. main.rs or lib.rs).
+	// Only a single source file can be defined. Modules which generate source can be included by prefixing
+	// the module name with ":", for example ":libfoo_bindgen"
+	//
+	// If no source file is defined, a single generated source module can be defined to be used as the main source.
 	Srcs []string `android:"path,arch_variant"`
 
 	// name of the lint set that should be used to validate this module.
@@ -116,6 +121,12 @@
 	// include all of the static libraries symbols in any dylibs or binaries which use this rlib as well.
 	Whole_static_libs []string `android:"arch_variant"`
 
+	// list of Rust system library dependencies.
+	//
+	// This is usually only needed when `no_stdlibs` is true, in which case it can be used to depend on system crates
+	// like `core` and `alloc`.
+	Stdlibs []string `android:"arch_variant"`
+
 	// crate name, required for modules which produce Rust libraries: rust_library, rust_ffi and SourceProvider
 	// modules which create library variants (rust_bindgen). This must be the expected extern crate name used in
 	// source, and is required to conform to an enforced format matching library output files (if the output file is
@@ -153,6 +164,14 @@
 	// linkage if all dependencies of the root binary module do not link against libstd\
 	// the same way.
 	Prefer_rlib *bool `android:"arch_variant"`
+
+	// Enables emitting certain Cargo environment variables. Only intended to be used for compatibility purposes.
+	// Will set CARGO_CRATE_NAME to the crate_name property's value.
+	// Will set CARGO_BIN_NAME to the output filename value without the extension.
+	Cargo_env_compat *bool
+
+	// If cargo_env_compat is true, sets the CARGO_PKG_VERSION env var to this value.
+	Cargo_pkg_version *string
 }
 
 type baseCompiler struct {
@@ -168,7 +187,11 @@
 	sanitize *sanitize
 
 	distFile android.OptionalPath
-	// Stripped output file. If Valid(), this file will be installed instead of outputFile.
+
+	// unstripped output file.
+	unstrippedOutputFile android.Path
+
+	// stripped output file.
 	strippedOutputFile android.OptionalPath
 
 	// If a crate has a source-generated dependency, a copy of the source file
@@ -218,6 +241,7 @@
 	for _, cfg := range compiler.Properties.Cfgs {
 		flags = append(flags, "--cfg '"+cfg+"'")
 	}
+
 	return flags
 }
 
@@ -226,6 +250,24 @@
 	for _, feature := range compiler.Properties.Features {
 		flags = append(flags, "--cfg 'feature=\""+feature+"\"'")
 	}
+
+	return flags
+}
+
+func (compiler *baseCompiler) featureFlags(ctx ModuleContext, flags Flags) Flags {
+	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
+
+	return flags
+}
+
+func (compiler *baseCompiler) cfgFlags(ctx ModuleContext, flags Flags) Flags {
+	if ctx.RustModule().UseVndk() {
+		compiler.Properties.Cfgs = append(compiler.Properties.Cfgs, "android_vndk")
+	}
+
+	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
 	return flags
 }
 
@@ -235,12 +277,27 @@
 	if err != nil {
 		ctx.PropertyErrorf("lints", err.Error())
 	}
+
+	// linkage-related flags are disallowed.
+	for _, s := range compiler.Properties.Ld_flags {
+		if strings.HasPrefix(s, "-Wl,-l") || strings.HasPrefix(s, "-Wl,-L") {
+			ctx.PropertyErrorf("ld_flags", "'-Wl,-l' and '-Wl,-L' flags cannot be manually specified")
+		}
+	}
+	for _, s := range compiler.Properties.Flags {
+		if strings.HasPrefix(s, "-l") || strings.HasPrefix(s, "-L") {
+			ctx.PropertyErrorf("flags", "'-l' and '-L' flags cannot be manually specified")
+		}
+		if strings.HasPrefix(s, "--extern") {
+			ctx.PropertyErrorf("flags", "'--extern' flag cannot be manually specified")
+		}
+		if strings.HasPrefix(s, "-Clink-args=") || strings.HasPrefix(s, "-C link-args=") {
+			ctx.PropertyErrorf("flags", "'-C link-args' flag cannot be manually specified")
+		}
+	}
+
 	flags.RustFlags = append(flags.RustFlags, lintFlags)
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
-	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
-	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.featuresToFlags()...)
 	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
 	flags.RustdocFlags = append(flags.RustdocFlags, "--edition="+compiler.edition())
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
@@ -264,10 +321,6 @@
 		flags.LinkFlags = append(flags.LinkFlags, "-Wl,-rpath,"+rpathPrefix+"../"+rpath)
 	}
 
-	if ctx.RustModule().UseVndk() {
-		flags.RustFlags = append(flags.RustFlags, "--cfg 'android_vndk'")
-	}
-
 	return flags
 }
 
@@ -289,8 +342,16 @@
 	return android.OptionalPathForPath(compiler.cargoOutDir)
 }
 
-func (compiler *baseCompiler) isDependencyRoot() bool {
-	return false
+func (compiler *baseCompiler) CargoEnvCompat() bool {
+	return Bool(compiler.Properties.Cargo_env_compat)
+}
+
+func (compiler *baseCompiler) CargoPkgVersion() string {
+	return String(compiler.Properties.Cargo_pkg_version)
+}
+
+func (compiler *baseCompiler) unstrippedOutputFilePath() android.Path {
+	return compiler.unstrippedOutputFile
 }
 
 func (compiler *baseCompiler) strippedOutputFilePath() android.OptionalPath {
@@ -305,12 +366,13 @@
 	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
 	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs...)
 	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...)
+	deps.Stdlibs = append(deps.Stdlibs, compiler.Properties.Stdlibs...)
 
 	if !Bool(compiler.Properties.No_stdlibs) {
 		for _, stdlib := range config.Stdlibs {
-			// If we're building for the primary arch of the build host, use the compiler's stdlibs
-			if ctx.Target().Os == android.BuildOs {
-				stdlib = stdlib + "_" + ctx.toolchain().RustTriple()
+			// If we're building for the build host, use the prebuilt stdlibs
+			if ctx.Target().Os == android.Linux || ctx.Target().Os == android.Darwin {
+				stdlib = "prebuilt_" + stdlib
 			}
 			deps.Stdlibs = append(deps.Stdlibs, stdlib)
 		}
@@ -330,13 +392,29 @@
 	} else {
 		deps.SharedLibs = append(deps.SharedLibs, bionicLibs...)
 	}
-
+	if ctx.RustModule().StaticExecutable() {
+		deps.StaticLibs = append(deps.StaticLibs, "libunwind")
+	}
 	if libRuntimeBuiltins := config.BuiltinsRuntimeLibrary(ctx.toolchain()); libRuntimeBuiltins != "" {
 		deps.StaticLibs = append(deps.StaticLibs, libRuntimeBuiltins)
 	}
 	return deps
 }
 
+func muslDeps(ctx DepsContext, deps Deps, static bool) Deps {
+	muslLibs := []string{"libc_musl"}
+	if static {
+		deps.StaticLibs = append(deps.StaticLibs, muslLibs...)
+	} else {
+		deps.SharedLibs = append(deps.SharedLibs, muslLibs...)
+	}
+	if libRuntimeBuiltins := config.BuiltinsRuntimeLibrary(ctx.toolchain()); libRuntimeBuiltins != "" {
+		deps.StaticLibs = append(deps.StaticLibs, libRuntimeBuiltins)
+	}
+
+	return deps
+}
+
 func (compiler *baseCompiler) crateName() string {
 	return compiler.Properties.Crate_name
 }
@@ -359,8 +437,15 @@
 	}
 
 	if compiler.location == InstallInData && ctx.RustModule().UseVndk() {
-		dir = filepath.Join(dir, "vendor")
+		if ctx.RustModule().InProduct() {
+			dir = filepath.Join(dir, "product")
+		} else if ctx.RustModule().InVendor() {
+			dir = filepath.Join(dir, "vendor")
+		} else {
+			ctx.ModuleErrorf("Unknown data+VNDK installation kind")
+		}
 	}
+
 	return android.PathForModuleInstall(ctx, dir, compiler.subDir,
 		compiler.relativeInstallPath(), compiler.relative)
 }
@@ -393,6 +478,10 @@
 
 // Returns the Path for the main source file along with Paths for generated source files from modules listed in srcs.
 func srcPathFromModuleSrcs(ctx ModuleContext, srcs []string) (android.Path, android.Paths) {
+	if len(srcs) == 0 {
+		ctx.PropertyErrorf("srcs", "srcs must not be empty")
+	}
+
 	// The srcs can contain strings with prefix ":".
 	// They are dependent modules of this module, with android.SourceDepTag.
 	// They are not the main source file compiled by rustc.
@@ -404,12 +493,18 @@
 			srcIndex = i
 		}
 	}
-	if numSrcs != 1 {
+	if numSrcs > 1 {
 		ctx.PropertyErrorf("srcs", incorrectSourcesError)
 	}
+
+	// If a main source file is not provided we expect only a single SourceProvider module to be defined
+	// within srcs, with the expectation that the first source it provides is the entry point.
 	if srcIndex != 0 {
 		ctx.PropertyErrorf("srcs", "main source file must be the first in srcs")
+	} else if numSrcs > 1 {
+		ctx.PropertyErrorf("srcs", "only a single generated source module can be defined without a main source file.")
 	}
+
 	paths := android.PathsForModuleSrc(ctx, srcs)
 	return paths[srcIndex], paths[1:]
 }
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index 5ca9e7f..ec6829a 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -98,6 +98,65 @@
 		}`)
 }
 
+// Test that we reject _no_ source files.
+func TestEnforceMissingSourceFiles(t *testing.T) {
+
+	singleSrcError := "srcs must not be empty"
+
+	// Test libraries
+	testRustError(t, singleSrcError, `
+		rust_library_host {
+			name: "foo-bar-library",
+			crate_name: "foo",
+		}`)
+
+	// Test binaries
+	testRustError(t, singleSrcError, `
+		rust_binary_host {
+			name: "foo-bar-binary",
+			crate_name: "foo",
+		}`)
+
+	// Test proc_macros
+	testRustError(t, singleSrcError, `
+		rust_proc_macro {
+			name: "foo-bar-proc-macro",
+			crate_name: "foo",
+		}`)
+
+	// Test prebuilts
+	testRustError(t, singleSrcError, `
+		rust_prebuilt_dylib {
+			name: "foo-bar-prebuilt",
+			crate_name: "foo",
+		  host_supported: true,
+		}`)
+}
+
+// Test environment vars for Cargo compat are set.
+func TestCargoCompat(t *testing.T) {
+	ctx := testRust(t, `
+		rust_binary {
+			name: "fizz",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			cargo_env_compat: true,
+			cargo_pkg_version: "1.0.0"
+		}`)
+
+	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
+
+	if !strings.Contains(fizz.Args["envVars"], "CARGO_BIN_NAME=fizz") {
+		t.Fatalf("expected 'CARGO_BIN_NAME=fizz' in envVars, actual envVars: %#v", fizz.Args["envVars"])
+	}
+	if !strings.Contains(fizz.Args["envVars"], "CARGO_CRATE_NAME=foo") {
+		t.Fatalf("expected 'CARGO_CRATE_NAME=foo' in envVars, actual envVars: %#v", fizz.Args["envVars"])
+	}
+	if !strings.Contains(fizz.Args["envVars"], "CARGO_PKG_VERSION=1.0.0") {
+		t.Fatalf("expected 'CARGO_PKG_VERSION=1.0.0' in envVars, actual envVars: %#v", fizz.Args["envVars"])
+	}
+}
+
 func TestInstallDir(t *testing.T) {
 	ctx := testRust(t, `
 		rust_library_dylib {
@@ -208,3 +267,73 @@
 		t.Errorf("libstd is not linked dynamically for dylibs")
 	}
 }
+
+// Ensure that manual link flags are disallowed.
+func TestManualLinkageRejection(t *testing.T) {
+	// rustc flags
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			flags: ["-lbar"],
+		}
+	`)
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			flags: ["--extern=foo"],
+		}
+	`)
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			flags: ["-Clink-args=foo"],
+		}
+	`)
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			flags: ["-C link-args=foo"],
+		}
+	`)
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			flags: ["-L foo/"],
+		}
+	`)
+
+	// lld flags
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			ld_flags: ["-Wl,-L bar/"],
+		}
+	`)
+	testRustError(t, ".* cannot be manually specified", `
+		rust_binary {
+			name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			ld_flags: ["-Wl,-lbar"],
+		}
+	`)
+}
diff --git a/rust/config/Android.bp b/rust/config/Android.bp
index 5b121c3..7757c79 100644
--- a/rust/config/Android.bp
+++ b/rust/config/Android.bp
@@ -16,7 +16,7 @@
         "lints.go",
         "toolchain.go",
         "allowed_list.go",
-        "x86_darwin_host.go",
+        "darwin_host.go",
         "x86_linux_bionic_host.go",
         "x86_linux_host.go",
         "x86_device.go",
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 394fcc5..802e1da 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -11,24 +11,49 @@
 		"external/crosvm",
 		"external/libchromeos-rs",
 		"external/minijail",
+		"external/open-dice",
 		"external/rust",
+		"external/selinux/libselinux",
+		"external/uwb",
 		"external/vm_tools/p9",
 		"frameworks/native/libs/binder/rust",
 		"frameworks/proto_logging/stats",
+		"hardware/interfaces/security",
+		"hardware/interfaces/uwb",
+		"packages/modules/Bluetooth",
 		"packages/modules/DnsResolver",
+		"packages/modules/Uwb",
 		"packages/modules/Virtualization",
+		"platform_testing/tests/codecoverage/native/rust",
 		"prebuilts/rust",
-		"system/bt",
+		"system/core/debuggerd/rust",
 		"system/core/libstats/pull_rust",
+		"system/core/trusty/libtrusty-rs",
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
 		"system/hardware/interfaces/keystore2",
+		"system/librustutils",
+		"system/logging/liblog",
 		"system/logging/rust",
+		"system/nfc",
 		"system/security",
 		"system/tools/aidl",
+		"tools/security/fuzzing/example_rust_fuzzer",
+		"tools/security/fuzzing/orphans",
+		"tools/security/remote_provisioning/cert_validator",
+		"tools/vendor",
+		"vendor/",
+	}
+
+	DownstreamRustAllowedPaths = []string{
+		// Add downstream allowed Rust paths here.
 	}
 
 	RustModuleTypes = []string{
+		// Don't add rust_bindgen or rust_protobuf as these are code generation modules
+		// and can be expected to be in paths without Rust code.
+		"rust_benchmark",
+		"rust_benchmark_host",
 		"rust_binary",
 		"rust_binary_host",
 		"rust_library",
@@ -37,6 +62,7 @@
 		"rust_ffi",
 		"rust_ffi_shared",
 		"rust_ffi_static",
+		"rust_fuzz",
 		"rust_library_host",
 		"rust_library_host_dylib",
 		"rust_library_host_rlib",
diff --git a/rust/config/x86_darwin_host.go b/rust/config/darwin_host.go
similarity index 64%
rename from rust/config/x86_darwin_host.go
rename to rust/config/darwin_host.go
index ddd93e8..03bea82 100644
--- a/rust/config/x86_darwin_host.go
+++ b/rust/config/darwin_host.go
@@ -25,41 +25,64 @@
 	DarwinRustLinkFlags = []string{
 		"-B${cc_config.MacToolPath}",
 	}
+	darwinArm64Rustflags = []string{}
+	darwinArm64Linkflags = []string{}
 	darwinX8664Rustflags = []string{}
 	darwinX8664Linkflags = []string{}
 )
 
 func init() {
+	registerToolchainFactory(android.Darwin, android.Arm64, darwinArm64ToolchainFactory)
 	registerToolchainFactory(android.Darwin, android.X86_64, darwinX8664ToolchainFactory)
+
 	pctx.StaticVariable("DarwinToolchainRustFlags", strings.Join(DarwinRustFlags, " "))
 	pctx.StaticVariable("DarwinToolchainLinkFlags", strings.Join(DarwinRustLinkFlags, " "))
+
+	pctx.StaticVariable("DarwinToolchainArm64RustFlags", strings.Join(darwinArm64Rustflags, " "))
+	pctx.StaticVariable("DarwinToolchainArm64LinkFlags", strings.Join(darwinArm64Linkflags, " "))
 	pctx.StaticVariable("DarwinToolchainX8664RustFlags", strings.Join(darwinX8664Rustflags, " "))
 	pctx.StaticVariable("DarwinToolchainX8664LinkFlags", strings.Join(darwinX8664Linkflags, " "))
 
 }
 
 type toolchainDarwin struct {
+	toolchain64Bit
 	toolchainRustFlags string
 	toolchainLinkFlags string
 }
 
-type toolchainDarwinX8664 struct {
-	toolchain64Bit
+type toolchainDarwinArm64 struct {
 	toolchainDarwin
 }
 
+type toolchainDarwinX8664 struct {
+	toolchainDarwin
+}
+
+func (toolchainDarwinArm64) Supported() bool {
+	return true
+}
+
 func (toolchainDarwinX8664) Supported() bool {
 	return true
 }
 
-func (toolchainDarwinX8664) Bionic() bool {
+func (toolchainDarwin) Bionic() bool {
 	return false
 }
 
+func (t *toolchainDarwinArm64) Name() string {
+	return "arm64"
+}
+
 func (t *toolchainDarwinX8664) Name() string {
 	return "x86_64"
 }
 
+func (t *toolchainDarwinArm64) RustTriple() string {
+	return "aarch64-apple-darwin"
+}
+
 func (t *toolchainDarwinX8664) RustTriple() string {
 	return "x86_64-apple-darwin"
 }
@@ -76,17 +99,31 @@
 	return ".dylib"
 }
 
+func (t *toolchainDarwinArm64) ToolchainLinkFlags() string {
+	// Prepend the lld flags from cc_config so we stay in sync with cc
+	return "${cc_config.DarwinLldflags} ${config.DarwinToolchainLinkFlags} ${config.DarwinToolchainArm64LinkFlags}"
+}
+
+func (t *toolchainDarwinArm64) ToolchainRustFlags() string {
+	return "${config.DarwinToolchainRustFlags} ${config.DarwinToolchainArm64RustFlags}"
+}
+
 func (t *toolchainDarwinX8664) ToolchainLinkFlags() string {
 	// Prepend the lld flags from cc_config so we stay in sync with cc
-	return "${cc_config.DarwinClangLldflags} ${config.DarwinToolchainLinkFlags} ${config.DarwinToolchainX8664LinkFlags}"
+	return "${cc_config.DarwinLldflags} ${config.DarwinToolchainLinkFlags} ${config.DarwinToolchainX8664LinkFlags}"
 }
 
 func (t *toolchainDarwinX8664) ToolchainRustFlags() string {
 	return "${config.DarwinToolchainRustFlags} ${config.DarwinToolchainX8664RustFlags}"
 }
 
+func darwinArm64ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainDarwinArm64Singleton
+}
+
 func darwinX8664ToolchainFactory(arch android.Arch) Toolchain {
 	return toolchainDarwinX8664Singleton
 }
 
+var toolchainDarwinArm64Singleton Toolchain = &toolchainDarwinArm64{}
 var toolchainDarwinX8664Singleton Toolchain = &toolchainDarwinX8664{}
diff --git a/rust/config/global.go b/rust/config/global.go
index 1b56237..2d5fa99 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,9 +24,9 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.51.0"
+	RustDefaultVersion = "1.59.0"
 	RustDefaultBase    = "prebuilts/rust/"
-	DefaultEdition     = "2018"
+	DefaultEdition     = "2021"
 	Stdlibs            = []string{
 		"libstd",
 	}
@@ -41,19 +41,22 @@
 	}
 
 	GlobalRustFlags = []string{
-		"--remap-path-prefix $$(pwd)=",
+		"-Z remap-cwd-prefix=.",
 		"-C codegen-units=1",
 		"-C debuginfo=2",
 		"-C opt-level=3",
 		"-C relocation-model=pic",
 		"-C overflow-checks=on",
+		"-C force-unwind-tables=yes",
 		// Use v0 mangling to distinguish from C++ symbols
-		"-Z symbol-mangling-version=v0",
+		"-C symbol-mangling-version=v0",
 	}
 
 	deviceGlobalRustFlags = []string{
 		"-C panic=abort",
 		"-Z link-native-libraries=no",
+		// Generate additional debug info for AutoFDO
+		"-Z debug-info-for-profiling",
 	}
 
 	deviceGlobalLinkFlags = []string{
@@ -83,12 +86,7 @@
 		return "${RustDefaultBase}"
 	})
 
-	pctx.VariableFunc("RustVersion", func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv("RUST_PREBUILTS_VERSION"); override != "" {
-			return override
-		}
-		return RustDefaultVersion
-	})
+	pctx.VariableFunc("RustVersion", getRustVersionPctx)
 
 	pctx.StaticVariable("RustPath", "${RustBase}/${HostPrebuiltTag}/${RustVersion}")
 	pctx.StaticVariable("RustBin", "${RustPath}/bin")
@@ -100,3 +98,14 @@
 	pctx.StaticVariable("DeviceGlobalLinkFlags", strings.Join(deviceGlobalLinkFlags, " "))
 
 }
+
+func getRustVersionPctx(ctx android.PackageVarContext) string {
+	return GetRustVersion(ctx)
+}
+
+func GetRustVersion(ctx android.PathContext) string {
+	if override := ctx.Config().Getenv("RUST_PREBUILTS_VERSION"); override != "" {
+		return override
+	}
+	return RustDefaultVersion
+}
diff --git a/rust/config/toolchain.go b/rust/config/toolchain.go
index a769f12..9c9d572 100644
--- a/rust/config/toolchain.go
+++ b/rust/config/toolchain.go
@@ -121,14 +121,7 @@
 }
 
 func LibclangRuntimeLibrary(t Toolchain, library string) string {
-	arch := t.LibclangRuntimeLibraryArch()
-	if arch == "" {
-		return ""
-	}
-	if !t.Bionic() {
-		return "libclang_rt." + library + "-" + arch
-	}
-	return "libclang_rt." + library + "-" + arch + "-android"
+	return "libclang_rt." + library
 }
 
 func LibRustRuntimeLibrary(t Toolchain, library string) string {
diff --git a/rust/config/x86_device.go b/rust/config/x86_device.go
index aae1125..5ae30e7 100644
--- a/rust/config/x86_device.go
+++ b/rust/config/x86_device.go
@@ -65,7 +65,7 @@
 
 func (t *toolchainX86) ToolchainLinkFlags() string {
 	// Prepend the lld flags from cc_config so we stay in sync with cc
-	return "${config.DeviceGlobalLinkFlags} ${cc_config.X86ClangLldflags} ${config.X86ToolchainLinkFlags}"
+	return "${config.DeviceGlobalLinkFlags} ${cc_config.X86Lldflags} ${config.X86ToolchainLinkFlags}"
 }
 
 func (t *toolchainX86) ToolchainRustFlags() string {
diff --git a/rust/config/x86_linux_host.go b/rust/config/x86_linux_host.go
index b63e14d..4d7c422 100644
--- a/rust/config/x86_linux_host.go
+++ b/rust/config/x86_linux_host.go
@@ -22,11 +22,27 @@
 
 var (
 	LinuxRustFlags     = []string{}
+	LinuxMuslRustFlags = []string{
+		// disable rustc's builtin fallbacks for crt objects
+		"-C link_self_contained=no",
+		// force rustc to use a dynamic musl libc
+		"-C target-feature=-crt-static",
+		"-Z link-native-libraries=no",
+	}
 	LinuxRustLinkFlags = []string{
 		"-B${cc_config.ClangBin}",
 		"-fuse-ld=lld",
 		"-Wl,--undefined-version",
 	}
+	LinuxRustGlibcLinkFlags = []string{
+		"--sysroot ${cc_config.LinuxGccRoot}/sysroot",
+	}
+	LinuxRustMuslLinkFlags = []string{
+		"--sysroot /dev/null",
+		"-nodefaultlibs",
+		"-nostdlib",
+		"-Wl,--no-dynamic-linker",
+	}
 	linuxX86Rustflags   = []string{}
 	linuxX86Linkflags   = []string{}
 	linuxX8664Rustflags = []string{}
@@ -34,11 +50,17 @@
 )
 
 func init() {
-	registerToolchainFactory(android.Linux, android.X86_64, linuxX8664ToolchainFactory)
-	registerToolchainFactory(android.Linux, android.X86, linuxX86ToolchainFactory)
+	registerToolchainFactory(android.Linux, android.X86_64, linuxGlibcX8664ToolchainFactory)
+	registerToolchainFactory(android.Linux, android.X86, linuxGlibcX86ToolchainFactory)
+
+	registerToolchainFactory(android.LinuxMusl, android.X86_64, linuxMuslX8664ToolchainFactory)
+	registerToolchainFactory(android.LinuxMusl, android.X86, linuxMuslX86ToolchainFactory)
 
 	pctx.StaticVariable("LinuxToolchainRustFlags", strings.Join(LinuxRustFlags, " "))
+	pctx.StaticVariable("LinuxMuslToolchainRustFlags", strings.Join(LinuxMuslRustFlags, " "))
 	pctx.StaticVariable("LinuxToolchainLinkFlags", strings.Join(LinuxRustLinkFlags, " "))
+	pctx.StaticVariable("LinuxGlibcToolchainLinkFlags", strings.Join(LinuxRustGlibcLinkFlags, " "))
+	pctx.StaticVariable("LinuxMuslToolchainLinkFlags", strings.Join(LinuxRustMuslLinkFlags, " "))
 	pctx.StaticVariable("LinuxToolchainX86RustFlags", strings.Join(linuxX86Rustflags, " "))
 	pctx.StaticVariable("LinuxToolchainX86LinkFlags", strings.Join(linuxX86Linkflags, " "))
 	pctx.StaticVariable("LinuxToolchainX8664RustFlags", strings.Join(linuxX8664Rustflags, " "))
@@ -46,19 +68,9 @@
 
 }
 
-type toolchainLinux struct {
-	toolchainRustFlags string
-	toolchainLinkFlags string
-}
-
-type toolchainLinuxX86 struct {
-	toolchain32Bit
-	toolchainLinux
-}
-
+// Base 64-bit linux rust toolchain
 type toolchainLinuxX8664 struct {
 	toolchain64Bit
-	toolchainLinux
 }
 
 func (toolchainLinuxX8664) Supported() bool {
@@ -73,13 +85,9 @@
 	return "x86_64"
 }
 
-func (t *toolchainLinuxX8664) RustTriple() string {
-	return "x86_64-unknown-linux-gnu"
-}
-
 func (t *toolchainLinuxX8664) ToolchainLinkFlags() string {
 	// Prepend the lld flags from cc_config so we stay in sync with cc
-	return "${cc_config.LinuxClangLldflags} ${cc_config.LinuxX8664ClangLldflags} " +
+	return "${cc_config.LinuxLldflags} ${cc_config.LinuxX8664Lldflags} " +
 		"${config.LinuxToolchainLinkFlags} ${config.LinuxToolchainX8664LinkFlags}"
 }
 
@@ -87,8 +95,49 @@
 	return "${config.LinuxToolchainRustFlags} ${config.LinuxToolchainX8664RustFlags}"
 }
 
-func linuxX8664ToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainLinuxX8664Singleton
+// Specialization of the 64-bit linux rust toolchain for glibc.  Adds the gnu rust triple and
+// sysroot linker flags.
+type toolchainLinuxGlibcX8664 struct {
+	toolchainLinuxX8664
+}
+
+func (t *toolchainLinuxX8664) RustTriple() string {
+	return "x86_64-unknown-linux-gnu"
+}
+
+func (t *toolchainLinuxGlibcX8664) ToolchainLinkFlags() string {
+	return t.toolchainLinuxX8664.ToolchainLinkFlags() + " " + "${config.LinuxGlibcToolchainLinkFlags}"
+}
+
+func linuxGlibcX8664ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxGlibcX8664Singleton
+}
+
+// Specialization of the 64-bit linux rust toolchain for musl.  Adds the musl rust triple and
+// linker flags to avoid using the host sysroot.
+type toolchainLinuxMuslX8664 struct {
+	toolchainLinuxX8664
+}
+
+func (t *toolchainLinuxMuslX8664) RustTriple() string {
+	return "x86_64-unknown-linux-musl"
+}
+
+func (t *toolchainLinuxMuslX8664) ToolchainLinkFlags() string {
+	return t.toolchainLinuxX8664.ToolchainLinkFlags() + " " + "${config.LinuxMuslToolchainLinkFlags}"
+}
+
+func (t *toolchainLinuxMuslX8664) ToolchainRustFlags() string {
+	return t.toolchainLinuxX8664.ToolchainRustFlags() + " " + "${config.LinuxMuslToolchainRustFlags}"
+}
+
+func linuxMuslX8664ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxMuslX8664Singleton
+}
+
+// Base 32-bit linux rust toolchain
+type toolchainLinuxX86 struct {
+	toolchain32Bit
 }
 
 func (toolchainLinuxX86) Supported() bool {
@@ -111,13 +160,9 @@
 	return "x86_64"
 }
 
-func (t *toolchainLinuxX86) RustTriple() string {
-	return "i686-unknown-linux-gnu"
-}
-
 func (t *toolchainLinuxX86) ToolchainLinkFlags() string {
 	// Prepend the lld flags from cc_config so we stay in sync with cc
-	return "${cc_config.LinuxClangLldflags} ${cc_config.LinuxX86ClangLldflags} " +
+	return "${cc_config.LinuxLldflags} ${cc_config.LinuxX86Lldflags} " +
 		"${config.LinuxToolchainLinkFlags} ${config.LinuxToolchainX86LinkFlags}"
 }
 
@@ -125,9 +170,47 @@
 	return "${config.LinuxToolchainRustFlags} ${config.LinuxToolchainX86RustFlags}"
 }
 
-func linuxX86ToolchainFactory(arch android.Arch) Toolchain {
-	return toolchainLinuxX86Singleton
+// Specialization of the 32-bit linux rust toolchain for glibc.  Adds the gnu rust triple and
+// sysroot linker flags.
+type toolchainLinuxGlibcX86 struct {
+	toolchainLinuxX86
 }
 
-var toolchainLinuxX8664Singleton Toolchain = &toolchainLinuxX8664{}
-var toolchainLinuxX86Singleton Toolchain = &toolchainLinuxX86{}
+func (t *toolchainLinuxGlibcX86) RustTriple() string {
+	return "i686-unknown-linux-gnu"
+}
+
+func (t *toolchainLinuxGlibcX86) ToolchainLinkFlags() string {
+	return t.toolchainLinuxX86.ToolchainLinkFlags() + " " + "${config.LinuxGlibcToolchainLinkFlags}"
+}
+
+func linuxGlibcX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxGlibcX86Singleton
+}
+
+// Specialization of the 32-bit linux rust toolchain for musl.  Adds the musl rust triple and
+// linker flags to avoid using the host sysroot.
+type toolchainLinuxMuslX86 struct {
+	toolchainLinuxX86
+}
+
+func (t *toolchainLinuxMuslX86) RustTriple() string {
+	return "i686-unknown-linux-musl"
+}
+
+func (t *toolchainLinuxMuslX86) ToolchainLinkFlags() string {
+	return t.toolchainLinuxX86.ToolchainLinkFlags() + " " + "${config.LinuxMuslToolchainLinkFlags}"
+}
+
+func (t *toolchainLinuxMuslX86) ToolchainRustFlags() string {
+	return t.toolchainLinuxX86.ToolchainRustFlags() + " " + "${config.LinuxMuslToolchainRustFlags}"
+}
+
+func linuxMuslX86ToolchainFactory(arch android.Arch) Toolchain {
+	return toolchainLinuxMuslX86Singleton
+}
+
+var toolchainLinuxGlibcX8664Singleton Toolchain = &toolchainLinuxGlibcX8664{}
+var toolchainLinuxGlibcX86Singleton Toolchain = &toolchainLinuxGlibcX86{}
+var toolchainLinuxMuslX8664Singleton Toolchain = &toolchainLinuxMuslX8664{}
+var toolchainLinuxMuslX86Singleton Toolchain = &toolchainLinuxMuslX86{}
diff --git a/rust/coverage.go b/rust/coverage.go
index dac526a..651ce6e 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -22,6 +22,7 @@
 
 var CovLibraryName = "libprofile-clang-extras"
 
+// Add '%c' to default specifier after we resolve http://b/210012154
 const profileInstrFlag = "-fprofile-instr-generate=/data/misc/trace/clang-%p-%m.profraw"
 
 type coverage struct {
@@ -55,10 +56,14 @@
 		flags.Coverage = true
 		coverage := ctx.GetDirectDepWithTag(CovLibraryName, cc.CoverageDepTag).(cc.LinkableInterface)
 		flags.RustFlags = append(flags.RustFlags,
-			"-Z instrument-coverage", "-g", "-C link-dead-code")
+			"-Z instrument-coverage", "-g")
 		flags.LinkFlags = append(flags.LinkFlags,
 			profileInstrFlag, "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,open")
 		deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile().Path())
+		if cc.EnableContinuousCoverage(ctx) {
+			flags.RustFlags = append(flags.RustFlags, "-C llvm-args=--runtime-counter-relocation")
+			flags.LinkFlags = append(flags.LinkFlags, "-Wl,-mllvm,-runtime-counter-relocation")
+		}
 	}
 
 	return flags, deps
diff --git a/rust/coverage_test.go b/rust/coverage_test.go
index 4b6c9d4..f3cd375 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -56,7 +56,7 @@
 	fizzCov := ctx.ModuleForTests("fizz_cov", "android_arm64_armv8-a_cov").Rule("rustc")
 	buzzNoCov := ctx.ModuleForTests("buzzNoCov", "android_arm64_armv8-a").Rule("rustc")
 
-	rustcCoverageFlags := []string{"-Z instrument-coverage", " -g ", "-C link-dead-code"}
+	rustcCoverageFlags := []string{"-Z instrument-coverage", " -g "}
 	for _, flag := range rustcCoverageFlags {
 		missingErrorStr := "missing rustc flag '%s' for '%s' module with coverage enabled; rustcFlags: %#v"
 		containsErrorStr := "contains rustc flag '%s' for '%s' module with coverage disabled; rustcFlags: %#v"
diff --git a/rust/doc.go b/rust/doc.go
index e7f1371..fe3581b 100644
--- a/rust/doc.go
+++ b/rust/doc.go
@@ -29,6 +29,14 @@
 type rustdocSingleton struct{}
 
 func (n *rustdocSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	docDir := android.PathForOutput(ctx, "rustdoc")
+	docZip := android.PathForOutput(ctx, "rustdoc.zip")
+	rule := android.NewRuleBuilder(pctx, ctx)
+	zipCmd := rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", docZip).
+		FlagWithArg("-C ", docDir.String()).
+		FlagWithArg("-D ", docDir.String())
+
 	ctx.VisitAllModules(func(module android.Module) {
 		if !module.Enabled() {
 			return
@@ -36,8 +44,10 @@
 
 		if m, ok := module.(*Module); ok {
 			if m.docTimestampFile.Valid() {
-				ctx.Phony("rustdoc", m.docTimestampFile.Path())
+				zipCmd.Implicit(m.docTimestampFile.Path())
 			}
 		}
 	})
+	rule.Build("rustdoc-zip", "Zipping all built Rust documentation...")
+	ctx.Phony("rustdoc", docZip)
 }
diff --git a/rust/fuzz.go b/rust/fuzz.go
index 7e1c55a..55921ba 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/fuzz"
 	"android/soong/rust/config"
 )
 
@@ -32,16 +33,10 @@
 type fuzzDecorator struct {
 	*binaryDecorator
 
-	Properties            cc.FuzzProperties
-	dictionary            android.Path
-	corpus                android.Paths
-	corpusIntermediateDir android.Path
-	config                android.Path
-	data                  android.Paths
-	dataIntermediateDir   android.Path
+	fuzzPackagedModule fuzz.FuzzPackagedModule
 }
 
-var _ compiler = (*binaryDecorator)(nil)
+var _ compiler = (*fuzzDecorator)(nil)
 
 // rust_binary produces a binary that is runnable on a device.
 func RustFuzzFactory() android.Module {
@@ -88,7 +83,7 @@
 
 func (fuzzer *fuzzDecorator) compilerProps() []interface{} {
 	return append(fuzzer.binaryDecorator.compilerProps(),
-		&fuzzer.Properties)
+		&fuzzer.fuzzPackagedModule.FuzzProperties)
 }
 
 func (fuzzer *fuzzDecorator) stdLinkage(ctx *depsContext) RustLinkage {
@@ -102,32 +97,23 @@
 // Responsible for generating GNU Make rules that package fuzz targets into
 // their architecture & target/host specific zip file.
 type rustFuzzPackager struct {
-	packages    android.Paths
-	fuzzTargets map[string]bool
+	fuzz.FuzzPackager
 }
 
 func rustFuzzPackagingFactory() android.Singleton {
 	return &rustFuzzPackager{}
 }
 
-type fileToZip struct {
-	SourceFilePath        android.Path
-	DestinationPathPrefix string
-}
-
-type archOs struct {
-	hostOrTarget string
-	arch         string
-	dir          string
-}
-
 func (s *rustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
-
 	// Map between each architecture + host/device combination.
-	archDirs := make(map[archOs][]fileToZip)
+	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
 
 	// List of individual fuzz targets.
-	s.fuzzTargets = make(map[string]bool)
+	s.FuzzTargets = make(map[string]bool)
+
+	// Map tracking whether each shared library has an install rule to avoid duplicate install rules from
+	// multiple fuzzers that depend on the same shared library.
+	sharedLibraryInstalled := make(map[string]bool)
 
 	ctx.VisitAllModules(func(module android.Module) {
 		// Discard non-fuzz targets.
@@ -136,23 +122,15 @@
 			return
 		}
 
+		if ok := fuzz.IsValid(rustModule.FuzzModule); !ok || rustModule.Properties.PreventInstall {
+			return
+		}
+
 		fuzzModule, ok := rustModule.compiler.(*fuzzDecorator)
 		if !ok {
 			return
 		}
 
-		// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
-		// fuzz targets we're going to package anyway.
-		if !rustModule.Enabled() || rustModule.Properties.PreventInstall ||
-			rustModule.InRamdisk() || rustModule.InVendorRamdisk() || rustModule.InRecovery() {
-			return
-		}
-
-		// Discard modules that are in an unavailable namespace.
-		if !rustModule.ExportedToMake() {
-			return
-		}
-
 		hostOrTargetString := "target"
 		if rustModule.Host() {
 			hostOrTargetString = "host"
@@ -160,126 +138,40 @@
 
 		archString := rustModule.Arch().ArchType.String()
 		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
-		archOs := archOs{hostOrTarget: hostOrTargetString, arch: archString, dir: archDir.String()}
+		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
 
-		var files []fileToZip
+		var files []fuzz.FileToZip
 		builder := android.NewRuleBuilder(pctx, ctx)
 
-		// Package the corpora into a zipfile.
-		if fuzzModule.corpus != nil {
-			corpusZip := archDir.Join(ctx, module.Name()+"_seed_corpus.zip")
-			command := builder.Command().BuiltTool("soong_zip").
-				Flag("-j").
-				FlagWithOutput("-o ", corpusZip)
-			rspFile := corpusZip.ReplaceExtension(ctx, "rsp")
-			command.FlagWithRspFileInputList("-r ", rspFile, fuzzModule.corpus)
-			files = append(files, fileToZip{corpusZip, ""})
-		}
-
-		// Package the data into a zipfile.
-		if fuzzModule.data != nil {
-			dataZip := archDir.Join(ctx, module.Name()+"_data.zip")
-			command := builder.Command().BuiltTool("soong_zip").
-				FlagWithOutput("-o ", dataZip)
-			for _, f := range fuzzModule.data {
-				intermediateDir := strings.TrimSuffix(f.String(), f.Rel())
-				command.FlagWithArg("-C ", intermediateDir)
-				command.FlagWithInput("-f ", f)
-			}
-			files = append(files, fileToZip{dataZip, ""})
-		}
+		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
+		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
 
 		// The executable.
-		files = append(files, fileToZip{rustModule.unstrippedOutputFile.Path(), ""})
+		files = append(files, fuzz.FileToZip{rustModule.UnstrippedOutputFile(), ""})
 
-		// The dictionary.
-		if fuzzModule.dictionary != nil {
-			files = append(files, fileToZip{fuzzModule.dictionary, ""})
+		// Grab the list of required shared libraries.
+		sharedLibraries := fuzz.CollectAllSharedDependencies(ctx, module, cc.UnstrippedOutputFile, cc.IsValidSharedDependency)
+
+		// Package shared libraries
+		files = append(files, cc.GetSharedLibsToZip(sharedLibraries, rustModule, &s.FuzzPackager, archString, &sharedLibraryInstalled)...)
+
+		archDirs[archOs], ok = s.BuildZipFile(ctx, module, fuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
+		if !ok {
+			return
 		}
 
-		// Additional fuzz config.
-		if fuzzModule.config != nil {
-			files = append(files, fileToZip{fuzzModule.config, ""})
-		}
-
-		fuzzZip := archDir.Join(ctx, module.Name()+".zip")
-
-		command := builder.Command().BuiltTool("soong_zip").
-			Flag("-j").
-			FlagWithOutput("-o ", fuzzZip)
-
-		for _, file := range files {
-			if file.DestinationPathPrefix != "" {
-				command.FlagWithArg("-P ", file.DestinationPathPrefix)
-			} else {
-				command.Flag("-P ''")
-			}
-			command.FlagWithInput("-f ", file.SourceFilePath)
-		}
-
-		builder.Build("create-"+fuzzZip.String(),
-			"Package "+module.Name()+" for "+archString+"-"+hostOrTargetString)
-
-		// Don't add modules to 'make haiku-rust' that are set to not be
-		// exported to the fuzzing infrastructure.
-		if config := fuzzModule.Properties.Fuzz_config; config != nil {
-			if rustModule.Host() && !BoolDefault(config.Fuzz_on_haiku_host, true) {
-				return
-			} else if !BoolDefault(config.Fuzz_on_haiku_device, true) {
-				return
-			}
-		}
-
-		s.fuzzTargets[module.Name()] = true
-		archDirs[archOs] = append(archDirs[archOs], fileToZip{fuzzZip, ""})
 	})
-
-	var archOsList []archOs
-	for archOs := range archDirs {
-		archOsList = append(archOsList, archOs)
-	}
-	sort.Slice(archOsList, func(i, j int) bool { return archOsList[i].dir < archOsList[j].dir })
-
-	for _, archOs := range archOsList {
-		filesToZip := archDirs[archOs]
-		arch := archOs.arch
-		hostOrTarget := archOs.hostOrTarget
-		builder := android.NewRuleBuilder(pctx, ctx)
-		outputFile := android.PathForOutput(ctx, "fuzz-rust-"+hostOrTarget+"-"+arch+".zip")
-		s.packages = append(s.packages, outputFile)
-
-		command := builder.Command().BuiltTool("soong_zip").
-			Flag("-j").
-			FlagWithOutput("-o ", outputFile).
-			Flag("-L 0") // No need to try and re-compress the zipfiles.
-
-		for _, fileToZip := range filesToZip {
-			if fileToZip.DestinationPathPrefix != "" {
-				command.FlagWithArg("-P ", fileToZip.DestinationPathPrefix)
-			} else {
-				command.Flag("-P ''")
-			}
-			command.FlagWithInput("-f ", fileToZip.SourceFilePath)
-		}
-		builder.Build("create-fuzz-package-"+arch+"-"+hostOrTarget,
-			"Create fuzz target packages for "+arch+"-"+hostOrTarget)
-	}
-
+	s.CreateFuzzPackage(ctx, archDirs, fuzz.Rust, pctx)
 }
 
 func (s *rustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
-	packages := s.packages.Strings()
+	packages := s.Packages.Strings()
 	sort.Strings(packages)
 
 	ctx.Strict("SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
 
-	// Preallocate the slice of fuzz targets to minimise memory allocations.
-	fuzzTargets := make([]string, 0, len(s.fuzzTargets))
-	for target, _ := range s.fuzzTargets {
-		fuzzTargets = append(fuzzTargets, target)
-	}
-	sort.Strings(fuzzTargets)
-	ctx.Strict("ALL_RUST_FUZZ_TARGETS", strings.Join(fuzzTargets, " "))
+	// Preallocate the slice of fuzz targets to minimize memory allocations.
+	s.PreallocateSlice(ctx, "ALL_RUST_FUZZ_TARGETS")
 }
 
 func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
@@ -289,13 +181,13 @@
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzz.binaryDecorator.baseCompiler.install(ctx)
 
-	if fuzz.Properties.Corpus != nil {
-		fuzz.corpus = android.PathsForModuleSrc(ctx, fuzz.Properties.Corpus)
+	if fuzz.fuzzPackagedModule.FuzzProperties.Corpus != nil {
+		fuzz.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Corpus)
 	}
-	if fuzz.Properties.Data != nil {
-		fuzz.data = android.PathsForModuleSrc(ctx, fuzz.Properties.Data)
+	if fuzz.fuzzPackagedModule.FuzzProperties.Data != nil {
+		fuzz.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Data)
 	}
-	if fuzz.Properties.Dictionary != nil {
-		fuzz.dictionary = android.PathForModuleSrc(ctx, *fuzz.Properties.Dictionary)
+	if fuzz.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
+		fuzz.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzz.fuzzPackagedModule.FuzzProperties.Dictionary)
 	}
 }
diff --git a/rust/fuzz_test.go b/rust/fuzz_test.go
index 2524f91..865665e 100644
--- a/rust/fuzz_test.go
+++ b/rust/fuzz_test.go
@@ -45,19 +45,19 @@
 	}
 
 	// Check that compiler flags are set appropriately .
-	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Output("fuzz_libtest")
+	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
 	if !strings.Contains(fuzz_libtest.Args["rustcFlags"], "-Z sanitizer=hwaddress") ||
-		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "-C passes='sancov'") ||
+		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "-C passes='sancov-module'") ||
 		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "--cfg fuzzing") {
-		t.Errorf("rust_fuzz module does not contain the expected flags (sancov, cfg fuzzing, hwaddress sanitizer).")
+		t.Errorf("rust_fuzz module does not contain the expected flags (sancov-module, cfg fuzzing, hwaddress sanitizer).")
 
 	}
 
 	// Check that dependencies have 'fuzzer' variants produced for them as well.
 	libtest_fuzzer := ctx.ModuleForTests("libtest_fuzzing", "android_arm64_armv8-a_rlib_rlib-std_fuzzer").Output("libtest_fuzzing.rlib")
 	if !strings.Contains(libtest_fuzzer.Args["rustcFlags"], "-Z sanitizer=hwaddress") ||
-		!strings.Contains(libtest_fuzzer.Args["rustcFlags"], "-C passes='sancov'") ||
+		!strings.Contains(libtest_fuzzer.Args["rustcFlags"], "-C passes='sancov-module'") ||
 		!strings.Contains(libtest_fuzzer.Args["rustcFlags"], "--cfg fuzzing") {
-		t.Errorf("rust_fuzz dependent library does not contain the expected flags (sancov, cfg fuzzing, hwaddress sanitizer).")
+		t.Errorf("rust_fuzz dependent library does not contain the expected flags (sancov-module, cfg fuzzing, hwaddress sanitizer).")
 	}
 }
diff --git a/rust/image.go b/rust/image.go
index 1b2df1d..dfc7f74 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -34,11 +34,11 @@
 }
 
 func (mod *Module) ProductAvailable() bool {
-	return false
+	return Bool(mod.VendorProperties.Product_available)
 }
 
 func (mod *Module) RamdiskAvailable() bool {
-	return false
+	return Bool(mod.Properties.Ramdisk_available)
 }
 
 func (mod *Module) VendorRamdiskAvailable() bool {
@@ -50,7 +50,7 @@
 }
 
 func (mod *Module) RecoveryAvailable() bool {
-	return false
+	return Bool(mod.Properties.Recovery_available)
 }
 
 func (mod *Module) ExtraVariants() []string {
@@ -62,9 +62,7 @@
 }
 
 func (mod *Module) SetRamdiskVariantNeeded(b bool) {
-	if b {
-		panic("Setting ramdisk variant needed for Rust module is unsupported: " + mod.BaseModuleName())
-	}
+	mod.Properties.RamdiskVariantNeeded = b
 }
 
 func (mod *Module) SetVendorRamdiskVariantNeeded(b bool) {
@@ -72,9 +70,7 @@
 }
 
 func (mod *Module) SetRecoveryVariantNeeded(b bool) {
-	if b {
-		panic("Setting recovery variant needed for Rust module is unsupported: " + mod.BaseModuleName())
-	}
+	mod.Properties.RecoveryVariantNeeded = b
 }
 
 func (mod *Module) SetCoreVariantNeeded(b bool) {
@@ -82,7 +78,12 @@
 }
 
 func (mod *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
-	panic("Rust modules do not support snapshotting: " + mod.BaseModuleName())
+	if snapshot, ok := mod.compiler.(cc.SnapshotInterface); ok {
+		return snapshot.Version()
+	} else {
+		mctx.ModuleErrorf("version is unknown for snapshot prebuilt")
+		return ""
+	}
 }
 
 func (mod *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
@@ -94,7 +95,7 @@
 }
 
 func (mod *Module) RamdiskVariantNeeded(android.BaseModuleContext) bool {
-	return mod.InRamdisk()
+	return mod.Properties.RamdiskVariantNeeded
 }
 
 func (mod *Module) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
@@ -102,7 +103,7 @@
 }
 
 func (mod *Module) RecoveryVariantNeeded(android.BaseModuleContext) bool {
-	return mod.InRecovery()
+	return mod.Properties.RecoveryVariantNeeded
 }
 
 func (mod *Module) ExtraImageVariations(android.BaseModuleContext) []string {
@@ -110,7 +111,9 @@
 }
 
 func (mod *Module) IsSnapshotPrebuilt() bool {
-	// Rust does not support prebuilts in its snapshots
+	if p, ok := mod.compiler.(cc.SnapshotInterface); ok {
+		return p.IsSnapshotPrebuilt()
+	}
 	return false
 }
 
@@ -133,12 +136,21 @@
 }
 
 func (ctx *moduleContext) ProductSpecific() bool {
-	return false
+	return ctx.ModuleContext.ProductSpecific() || ctx.RustModule().productSpecificModuleContext()
+}
+
+func (c *Module) productSpecificModuleContext() bool {
+	// Additionally check if this module is inProduct() that means it is a "product" variant of a
+	// module. As well as product specific modules, product variants must be installed to /product.
+	return c.InProduct()
 }
 
 func (mod *Module) InRecovery() bool {
-	// TODO(b/165791368)
-	return false
+	return mod.ModuleBase.InRecovery() || mod.ModuleBase.InstallInRecovery()
+}
+
+func (mod *Module) InRamdisk() bool {
+	return mod.ModuleBase.InRamdisk() || mod.ModuleBase.InstallInRamdisk()
 }
 
 func (mod *Module) InVendorRamdisk() bool {
@@ -159,6 +171,11 @@
 	return false
 }
 
+func (mod *Module) OnlyInProduct() bool {
+	//TODO(b/165791368)
+	return false
+}
+
 // Returns true when this module is configured to have core and vendor variants.
 func (mod *Module) HasVendorVariant() bool {
 	return Bool(mod.VendorProperties.Vendor_available) || Bool(mod.VendorProperties.Odm_available)
@@ -174,7 +191,7 @@
 }
 
 func (mod *Module) InProduct() bool {
-	return false
+	return mod.Properties.ImageVariationPrefix == cc.ProductVariationPrefix
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
@@ -186,6 +203,8 @@
 	m := module.(*Module)
 	if variant == android.VendorRamdiskVariation {
 		m.MakeAsPlatform()
+	} else if variant == android.RecoveryVariation {
+		m.MakeAsPlatform()
 	} else if strings.HasPrefix(variant, cc.VendorVariationPrefix) {
 		m.Properties.ImageVariationPrefix = cc.VendorVariationPrefix
 		m.Properties.VndkVersion = strings.TrimPrefix(variant, cc.VendorVariationPrefix)
@@ -197,15 +216,17 @@
 			m.Properties.HideFromMake = true
 			m.HideFromMake()
 		}
+	} else if strings.HasPrefix(variant, cc.ProductVariationPrefix) {
+		m.Properties.ImageVariationPrefix = cc.ProductVariationPrefix
+		m.Properties.VndkVersion = strings.TrimPrefix(variant, cc.ProductVariationPrefix)
 	}
 }
 
 func (mod *Module) ImageMutatorBegin(mctx android.BaseModuleContext) {
 	// Rust does not support installing to the product image yet.
-	if Bool(mod.VendorProperties.Product_available) {
-		mctx.PropertyErrorf("product_available",
-			"Rust modules do not yet support being available to the product image")
-	} else if mctx.ProductSpecific() {
+	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
+
+	if mctx.ProductSpecific() {
 		mctx.PropertyErrorf("product_specific",
 			"Rust modules do not yet support installing to the product image.")
 	} else if Bool(mod.VendorProperties.Double_loadable) {
@@ -217,11 +238,10 @@
 			mctx.PropertyErrorf("vendor_ramdisk_available", "cannot be set for rust_ffi or rust_ffi_shared modules.")
 		}
 	}
-	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
 	if vendorSpecific {
-		mctx.PropertyErrorf("vendor or soc_specific",
-			"Rust modules do not yet support soc-specific modules")
-
+		if lib, ok := mod.compiler.(libraryInterface); ok && lib.buildDylib() {
+			mctx.PropertyErrorf("vendor", "Vendor-only dylibs are not yet supported, use rust_library_rlib.")
+		}
 	}
 
 	cc.MutateImage(mctx, mod)
diff --git a/rust/library.go b/rust/library.go
index 1bdf83a..62eaefd 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -21,6 +21,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/snapshot"
 )
 
 var (
@@ -99,6 +100,11 @@
 	MutatedProperties LibraryMutatedProperties
 	includeDirs       android.Paths
 	sourceProvider    SourceProvider
+
+	collectedSnapshotHeaders android.Paths
+
+	// table-of-contents file for cdylib crates to optimize out relinking when possible
+	tocFile android.OptionalPath
 }
 
 type libraryInterface interface {
@@ -122,7 +128,8 @@
 	setStatic()
 	setSource()
 
-	// Set libstd linkage
+	// libstd linkage functions
+	rlibStd() bool
 	setRlibStd()
 	setDylibStd()
 
@@ -133,12 +140,18 @@
 	BuildOnlyDylib()
 	BuildOnlyStatic()
 	BuildOnlyShared()
+
+	toc() android.OptionalPath
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
 	return true
 }
 
+func (library *libraryDecorator) toc() android.OptionalPath {
+	return library.tocFile
+}
+
 func (library *libraryDecorator) rlib() bool {
 	return library.MutatedProperties.VariantIsRlib
 }
@@ -193,6 +206,10 @@
 	library.MutatedProperties.VariantIsShared = false
 }
 
+func (library *libraryDecorator) rlibStd() bool {
+	return library.MutatedProperties.VariantIsStaticStd
+}
+
 func (library *libraryDecorator) setRlibStd() {
 	library.MutatedProperties.VariantIsStaticStd = true
 }
@@ -220,7 +237,10 @@
 }
 
 func (library *libraryDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
-	if library.preferRlib() {
+	if ctx.Module().(*Module).InVendor() {
+		// Vendor modules should statically link libstd.
+		return rlibAutoDep
+	} else if library.preferRlib() {
 		return rlibAutoDep
 	} else if library.rlib() || library.static() {
 		return rlibAutoDep
@@ -236,7 +256,10 @@
 }
 
 func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
-	if library.static() || library.MutatedProperties.VariantIsStaticStd {
+	if ctx.RustModule().InVendor() {
+		// Vendor modules should statically link libstd.
+		return RlibLinkage
+	} else if library.static() || library.MutatedProperties.VariantIsStaticStd {
 		return RlibLinkage
 	} else if library.baseCompiler.preferRlib() {
 		return RlibLinkage
@@ -403,10 +426,16 @@
 func (library *libraryDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps = library.baseCompiler.compilerDeps(ctx, deps)
 
-	if ctx.toolchain().Bionic() && (library.dylib() || library.shared()) {
-		deps = bionicDeps(ctx, deps, false)
-		deps.CrtBegin = "crtbegin_so"
-		deps.CrtEnd = "crtend_so"
+	if library.dylib() || library.shared() {
+		if ctx.toolchain().Bionic() {
+			deps = bionicDeps(ctx, deps, false)
+			deps.CrtBegin = []string{"crtbegin_so"}
+			deps.CrtEnd = []string{"crtend_so"}
+		} else if ctx.Os() == android.LinuxMusl {
+			deps = muslDeps(ctx, deps, false)
+			deps.CrtBegin = []string{"libc_musl_crtbegin_so"}
+			deps.CrtEnd = []string{"libc_musl_crtend_so"}
+		}
 	}
 
 	return deps
@@ -416,9 +445,25 @@
 	return library.getStem(ctx) + ctx.toolchain().SharedLibSuffix()
 }
 
+func (library *libraryDecorator) cfgFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = library.baseCompiler.cfgFlags(ctx, flags)
+	if library.dylib() {
+		// We need to add a dependency on std in order to link crates as dylibs.
+		// The hack to add this dependency is guarded by the following cfg so
+		// that we don't force a dependency when it isn't needed.
+		library.baseCompiler.Properties.Cfgs = append(library.baseCompiler.Properties.Cfgs, "android_dylib")
+	}
+
+	flags.RustFlags = append(flags.RustFlags, library.baseCompiler.cfgsToFlags()...)
+	flags.RustdocFlags = append(flags.RustdocFlags, library.baseCompiler.cfgsToFlags()...)
+
+	return flags
+}
+
 func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
-	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 	flags = library.baseCompiler.compilerFlags(ctx, flags)
+
+	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 	if library.shared() || library.static() {
 		library.includeDirs = append(library.includeDirs, android.PathsForModuleSrc(ctx, library.Properties.Include_dirs)...)
 	}
@@ -430,7 +475,7 @@
 }
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
-	var outputFile android.ModuleOutPath
+	var outputFile, ret android.ModuleOutPath
 	var fileName string
 	srcPath := library.srcPath(ctx, deps)
 
@@ -438,6 +483,34 @@
 		deps.srcProviderFiles = append(deps.srcProviderFiles, library.sourceProvider.Srcs()...)
 	}
 
+	// Calculate output filename
+	if library.rlib() {
+		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.dylib() {
+		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.static() {
+		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	} else if library.shared() {
+		fileName = library.sharedLibFilename(ctx)
+		outputFile = android.PathForModuleOut(ctx, fileName)
+		ret = outputFile
+	}
+
+	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
+		strippedOutputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "unstripped", fileName)
+		library.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
+
+		library.baseCompiler.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
+	}
+	library.baseCompiler.unstrippedOutputFile = outputFile
+
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
@@ -449,34 +522,17 @@
 		flags.RustFlags = append(flags.RustFlags, "-C prefer-dynamic")
 	}
 
+	// Call the appropriate builder for this library type
 	if library.rlib() {
-		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoRlib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.dylib() {
-		fileName = library.getStem(ctx) + ctx.toolchain().DylibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoDylib(ctx, srcPath, deps, flags, outputFile)
 	} else if library.static() {
-		fileName = library.getStem(ctx) + ctx.toolchain().StaticLibSuffix()
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoStatic(ctx, srcPath, deps, flags, outputFile)
 	} else if library.shared() {
-		fileName = library.sharedLibFilename(ctx)
-		outputFile = android.PathForModuleOut(ctx, fileName)
-
 		TransformSrctoShared(ctx, srcPath, deps, flags, outputFile)
 	}
 
-	if !library.rlib() && !library.static() && library.stripper.NeedsStrip(ctx) {
-		strippedOutputFile := android.PathForModuleOut(ctx, "stripped", fileName)
-		library.stripper.StripExecutableOrSharedLib(ctx, outputFile, strippedOutputFile)
-		library.strippedOutputFile = android.OptionalPathForPath(strippedOutputFile)
-	}
-
 	if library.rlib() || library.dylib() {
 		library.flagExporter.exportLinkDirs(deps.linkDirs...)
 		library.flagExporter.exportLinkObjects(deps.linkObjects...)
@@ -489,10 +545,16 @@
 	}
 
 	if library.shared() {
+		// Optimize out relinking against shared libraries whose interface hasn't changed by
+		// depending on a table of contents file instead of the library itself.
+		tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.SharedLibSuffix()[1:]+".toc")
+		library.tocFile = android.OptionalPathForPath(tocFile)
+		cc.TransformSharedObjectToToc(ctx, outputFile, tocFile)
+
 		ctx.SetProvider(cc.SharedLibraryInfoProvider, cc.SharedLibraryInfo{
-			SharedLibrary:           outputFile,
-			UnstrippedSharedLibrary: outputFile,
-			Target:                  ctx.Target(),
+			TableOfContents: android.OptionalPathForPath(tocFile),
+			SharedLibrary:   outputFile,
+			Target:          ctx.Target(),
 		})
 	}
 
@@ -507,7 +569,7 @@
 
 	library.flagExporter.setProvider(ctx)
 
-	return outputFile
+	return ret
 }
 
 func (library *libraryDecorator) srcPath(ctx ModuleContext, deps PathDeps) android.Path {
@@ -623,6 +685,25 @@
 				// Disable dylib Vendor Ramdisk variations until we support these.
 				v.(*Module).Disable()
 			}
+
+			variation := v.(*Module).ModuleBase.ImageVariation().Variation
+			if strings.HasPrefix(variation, cc.VendorVariationPrefix) {
+				// TODO(b/204303985)
+				// Disable vendor dylibs until they are supported
+				v.(*Module).Disable()
+			}
+
+			if strings.HasPrefix(variation, cc.VendorVariationPrefix) &&
+				m.HasVendorVariant() &&
+				!snapshot.IsVendorProprietaryModule(mctx) &&
+				strings.TrimPrefix(variation, cc.VendorVariationPrefix) == mctx.DeviceConfig().VndkVersion() {
+
+				// cc.MutateImage runs before LibraryMutator, so vendor variations which are meant for rlibs only are
+				// produced for Dylibs; however, dylibs should not be enabled for boardVndkVersion for
+				// non-vendor proprietary modules.
+				v.(*Module).Disable()
+			}
+
 		case "source":
 			v.(*Module).compiler.(libraryInterface).setSource()
 			// The source variant does not produce any library.
@@ -659,9 +740,10 @@
 				dylib := modules[1].(*Module)
 				rlib.compiler.(libraryInterface).setRlibStd()
 				dylib.compiler.(libraryInterface).setDylibStd()
-				if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+				if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation ||
+					strings.HasPrefix(dylib.ModuleBase.ImageVariation().Variation, cc.VendorVariationPrefix) {
 					// TODO(b/165791368)
-					// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
+					// Disable rlibs that link against dylib-std on vendor and vendor ramdisk variations until those dylib
 					// variants are properly supported.
 					dylib.Disable()
 				}
@@ -671,3 +753,55 @@
 		}
 	}
 }
+
+func (l *libraryDecorator) snapshotHeaders() android.Paths {
+	if l.collectedSnapshotHeaders == nil {
+		panic("snapshotHeaders() must be called after collectHeadersForSnapshot()")
+	}
+	return l.collectedSnapshotHeaders
+}
+
+// collectHeadersForSnapshot collects all exported headers from library.
+// It globs header files in the source tree for exported include directories,
+// and tracks generated header files separately.
+//
+// This is to be called from GenerateAndroidBuildActions, and then collected
+// header files can be retrieved by snapshotHeaders().
+func (l *libraryDecorator) collectHeadersForSnapshot(ctx android.ModuleContext, deps PathDeps) {
+	ret := android.Paths{}
+
+	// Glob together the headers from the modules include_dirs property
+	for _, path := range android.CopyOfPaths(l.includeDirs) {
+		dir := path.String()
+		globDir := dir + "/**/*"
+		glob, err := ctx.GlobWithDeps(globDir, nil)
+		if err != nil {
+			ctx.ModuleErrorf("glob of %q failed: %s", globDir, err)
+			return
+		}
+
+		for _, header := range glob {
+			// Filter out only the files with extensions that are headers.
+			found := false
+			for _, ext := range cc.HeaderExts {
+				if strings.HasSuffix(header, ext) {
+					found = true
+					break
+				}
+			}
+			if !found {
+				continue
+			}
+			ret = append(ret, android.PathForSource(ctx, header))
+		}
+	}
+
+	// Glob together the headers from C dependencies as well, starting with non-generated headers.
+	ret = append(ret, cc.GlobHeadersForSnapshot(ctx, append(android.CopyOfPaths(deps.depIncludePaths), deps.depSystemIncludePaths...))...)
+
+	// Collect generated headers from C dependencies.
+	ret = append(ret, cc.GlobGeneratedHeadersForSnapshot(ctx, deps.depGeneratedHeaders)...)
+
+	// TODO(185577950): If support for generated headers is added, they need to be collected here as well.
+	l.collectedSnapshotHeaders = ret
+}
diff --git a/rust/library_test.go b/rust/library_test.go
index 54cd2a5..4633cc7 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -37,10 +37,10 @@
                 }`)
 
 	// Test all variants are being built.
-	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std").Output("libfoo.rlib")
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
-	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").Output("libfoo.ffi.a")
-	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Output("libfoo.ffi.so")
+	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").Rule("rustc")
+	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Rule("rustc")
 
 	rlibCrateType := "rlib"
 	dylibCrateType := "dylib"
@@ -78,13 +78,29 @@
 			crate_name: "foo",
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Output("libfoo.dylib.so")
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "prefer-dynamic") {
 		t.Errorf("missing prefer-dynamic flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
 	}
 }
 
+// Check that we are passing the android_dylib config flag
+func TestAndroidDylib(t *testing.T) {
+	ctx := testRust(t, `
+		rust_library_host_dylib {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+		}`)
+
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+
+	if !strings.Contains(libfooDylib.Args["rustcFlags"], "--cfg 'android_dylib'") {
+		t.Errorf("missing android_dylib cfg flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
+	}
+}
+
 func TestValidateLibraryStem(t *testing.T) {
 	testRustError(t, "crate_name must be defined.", `
 			rust_library_host {
@@ -132,7 +148,7 @@
 
 	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared")
 
-	libfooOutput := libfoo.Output("libfoo.so")
+	libfooOutput := libfoo.Rule("rustc")
 	if !strings.Contains(libfooOutput.Args["linkFlags"], "-Wl,-soname=libfoo.so") {
 		t.Errorf("missing expected -Wl,-soname linker flag for libfoo shared lib, linkFlags: %#v",
 			libfooOutput.Args["linkFlags"])
@@ -144,6 +160,26 @@
 	}
 }
 
+func TestSharedLibraryToc(t *testing.T) {
+	ctx := testRust(t, `
+		rust_ffi_shared {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+		}
+		cc_binary {
+			name: "fizzbuzz",
+			shared_libs: ["libfoo"],
+		}`)
+
+	fizzbuzz := ctx.ModuleForTests("fizzbuzz", "android_arm64_armv8-a").Rule("ld")
+
+	if !android.SuffixInList(fizzbuzz.Implicits.Strings(), "libfoo.so.toc") {
+		t.Errorf("missing expected libfoo.so.toc implicit dependency, instead found: %#v",
+			fizzbuzz.Implicits.Strings())
+	}
+}
+
 func TestStaticLibraryLinkage(t *testing.T) {
 	ctx := testRust(t, `
 		rust_ffi_static {
@@ -164,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")
@@ -203,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.")
+		}
 	}
 }
 
@@ -226,16 +275,17 @@
 	`)
 
 	foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib")
-	foo.Output("stripped/libfoo.dylib.so")
+	foo.Output("libfoo.dylib.so")
+	foo.Output("unstripped/libfoo.dylib.so")
 	// Check that the `cp` rule is using the stripped version as input.
 	cp := foo.Rule("android.Cp")
-	if !strings.HasSuffix(cp.Input.String(), "stripped/libfoo.dylib.so") {
-		t.Errorf("installed binary not based on stripped version: %v", cp.Input)
+	if strings.HasSuffix(cp.Input.String(), "unstripped/libfoo.dylib.so") {
+		t.Errorf("installed library not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeOutput("stripped/libbar.dylib.so")
+	fizzBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeOutput("unstripped/libbar.dylib.so")
 	if fizzBar.Rule != nil {
-		t.Errorf("stripped version of bar has been generated")
+		t.Errorf("unstripped library exists, so stripped library has incorrectly been generated")
 	}
 }
 
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index 49f3c0f..6cdd07d 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -22,6 +22,7 @@
 	android.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	android.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
 	android.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
+	android.RegisterModuleType("rust_prebuilt_proc_macro", PrebuiltProcMacroFactory)
 }
 
 type PrebuiltProperties struct {
@@ -32,12 +33,48 @@
 }
 
 type prebuiltLibraryDecorator struct {
+	android.Prebuilt
+
 	*libraryDecorator
 	Properties PrebuiltProperties
 }
 
+type prebuiltProcMacroDecorator struct {
+	android.Prebuilt
+
+	*procMacroDecorator
+	Properties PrebuiltProperties
+}
+
+func PrebuiltProcMacroFactory() android.Module {
+	module, _ := NewPrebuiltProcMacro(android.HostSupportedNoCross)
+	return module.Init()
+}
+
+type rustPrebuilt interface {
+	prebuiltSrcs() []string
+	prebuilt() *android.Prebuilt
+}
+
+func NewPrebuiltProcMacro(hod android.HostOrDeviceSupported) (*Module, *prebuiltProcMacroDecorator) {
+	module, library := NewProcMacro(hod)
+	prebuilt := &prebuiltProcMacroDecorator{
+		procMacroDecorator: library,
+	}
+	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
+	return module, prebuilt
+}
+
 var _ compiler = (*prebuiltLibraryDecorator)(nil)
 var _ exportedFlagsProducer = (*prebuiltLibraryDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltLibraryDecorator)(nil)
+
+var _ compiler = (*prebuiltProcMacroDecorator)(nil)
+var _ exportedFlagsProducer = (*prebuiltProcMacroDecorator)(nil)
+var _ rustPrebuilt = (*prebuiltProcMacroDecorator)(nil)
 
 func PrebuiltLibraryFactory() android.Module {
 	module, _ := NewPrebuiltLibrary(android.HostAndDeviceSupported)
@@ -54,6 +91,13 @@
 	return module.Init()
 }
 
+func addSrcSupplier(module android.PrebuiltInterface, prebuilt rustPrebuilt) {
+	srcsSupplier := func(_ android.BaseModuleContext, _ android.Module) []string {
+		return prebuilt.prebuiltSrcs()
+	}
+	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "srcs")
+}
+
 func NewPrebuiltLibrary(hod android.HostOrDeviceSupported) (*Module, *prebuiltLibraryDecorator) {
 	module, library := NewRustLibrary(hod)
 	library.BuildOnlyRust()
@@ -62,6 +106,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -73,6 +120,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -84,6 +134,9 @@
 		libraryDecorator: library,
 	}
 	module.compiler = prebuilt
+
+	addSrcSupplier(module, prebuilt)
+
 	return module, prebuilt
 }
 
@@ -100,6 +153,7 @@
 	if len(paths) > 0 {
 		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
 	}
+	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
 	return srcPath
 }
 
@@ -129,3 +183,48 @@
 
 	return srcs
 }
+
+func (prebuilt *prebuiltLibraryDecorator) prebuilt() *android.Prebuilt {
+	return &prebuilt.Prebuilt
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuiltSrcs() []string {
+	srcs := prebuilt.Properties.Srcs
+	return srcs
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) prebuilt() *android.Prebuilt {
+	return &prebuilt.Prebuilt
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerProps() []interface{} {
+	return append(prebuilt.procMacroDecorator.compilerProps(),
+		&prebuilt.Properties)
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
+	prebuilt.flagExporter.setProvider(ctx)
+
+	srcPath, paths := srcPathFromModuleSrcs(ctx, prebuilt.prebuiltSrcs())
+	if len(paths) > 0 {
+		ctx.PropertyErrorf("srcs", "prebuilt libraries can only have one entry in srcs (the prebuilt path)")
+	}
+	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
+	return srcPath
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) rustdoc(ctx ModuleContext, flags Flags,
+	deps PathDeps) android.OptionalPath {
+
+	return android.OptionalPath{}
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) compilerDeps(ctx DepsContext, deps Deps) Deps {
+	deps = prebuilt.baseCompiler.compilerDeps(ctx, deps)
+	return deps
+}
+
+func (prebuilt *prebuiltProcMacroDecorator) nativeCoverage() bool {
+	return false
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index c217959..f8a4bbd 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -33,6 +33,7 @@
 }
 
 type procMacroInterface interface {
+	ProcMacro() bool
 }
 
 var _ compiler = (*procMacroDecorator)(nil)
@@ -63,12 +64,19 @@
 		&procMacro.Properties)
 }
 
+func (procMacro *procMacroDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = procMacro.baseCompiler.compilerFlags(ctx, flags)
+	flags.RustFlags = append(flags.RustFlags, "--extern proc_macro")
+	return flags
+}
+
 func (procMacro *procMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
 	fileName := procMacro.getStem(ctx) + ctx.toolchain().ProcMacroSuffix()
 	outputFile := android.PathForModuleOut(ctx, fileName)
 
 	srcPath, _ := srcPathFromModuleSrcs(ctx, procMacro.baseCompiler.Properties.Srcs)
 	TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
+	procMacro.baseCompiler.unstrippedOutputFile = outputFile
 	return outputFile
 }
 
@@ -83,6 +91,10 @@
 	return rlibAutoDep
 }
 
+func (procMacro *procMacroDecorator) ProcMacro() bool {
+	return true
+}
+
 func (procMacro *procMacroDecorator) everInstallable() bool {
 	// Proc_macros are never installed
 	return false
diff --git a/rust/proc_macro_test.go b/rust/proc_macro_test.go
new file mode 100644
index 0000000..cc81938
--- /dev/null
+++ b/rust/proc_macro_test.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestRustProcMacro(t *testing.T) {
+	ctx := testRust(t, `
+          rust_proc_macro {
+	    name: "libprocmacro",
+	    srcs: ["foo.rs"],
+	    crate_name: "procmacro",
+	  }
+	`)
+
+	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
+
+	if !strings.Contains(libprocmacro.Args["rustcFlags"], "--extern proc_macro") {
+		t.Errorf("--extern proc_macro flag not being passed to rustc for proc macro %#v", libprocmacro.Args["rustcFlags"])
+	}
+}
diff --git a/rust/project_json.go b/rust/project_json.go
index c28bc7b..fe259d6 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -51,10 +51,10 @@
 	Deps        []rustProjectDep  `json:"deps"`
 	Cfg         []string          `json:"cfg"`
 	Env         map[string]string `json:"env"`
+	ProcMacro   bool              `json:"is_proc_macro"`
 }
 
 type rustProjectJson struct {
-	Roots  []string           `json:"roots"`
 	Crates []rustProjectCrate `json:"crates"`
 }
 
@@ -209,6 +209,10 @@
 		comp = c.baseCompiler
 	case *testDecorator:
 		comp = c.binaryDecorator.baseCompiler
+	case *procMacroDecorator:
+		comp = c.baseCompiler
+	case *toolchainLibraryDecorator:
+		comp = c.baseCompiler
 	default:
 		return nil, nil, false
 	}
@@ -225,6 +229,8 @@
 		return 0, false
 	}
 
+	_, procMacro := rModule.compiler.(*procMacroDecorator)
+
 	crate := rustProjectCrate{
 		DisplayName: rModule.Name(),
 		RootModule:  rootModule,
@@ -232,6 +238,7 @@
 		Deps:        make([]rustProjectDep, 0),
 		Cfg:         make([]string, 0),
 		Env:         make(map[string]string),
+		ProcMacro:   procMacro,
 	}
 
 	if comp.CargoOutDir().Valid() {
@@ -248,9 +255,6 @@
 	idx := len(singleton.project.Crates)
 	singleton.knownCrates[rModule.Name()] = crateInfo{Idx: idx, Deps: deps}
 	singleton.project.Crates = append(singleton.project.Crates, crate)
-	// rust-analyzer requires that all crates belong to at least one root:
-	// https://github.com/rust-analyzer/rust-analyzer/issues/4735.
-	singleton.project.Roots = append(singleton.project.Roots, path.Dir(crate.RootModule))
 	return idx, true
 }
 
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 09d30db..255b2e5 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -36,7 +36,7 @@
 	// The JSON file is generated via WriteFileToOutputDir. Therefore, it
 	// won't appear in the Output of the TestingSingleton. Manually verify
 	// it exists.
-	content, err := ioutil.ReadFile(filepath.Join(result.Config.BuildDir(), rustProjectJsonFileName))
+	content, err := ioutil.ReadFile(filepath.Join(result.Config.SoongOutDir(), rustProjectJsonFileName))
 	if err != nil {
 		t.Errorf("rust-project.json has not been generated")
 	}
@@ -117,6 +117,58 @@
 	validateJsonCrates(t, jsonContent)
 }
 
+func TestProjectJsonProcMacroDep(t *testing.T) {
+	bp := `
+	rust_proc_macro {
+		name: "libproc_macro",
+		srcs: ["a/src/lib.rs"],
+		crate_name: "proc_macro"
+	}
+	rust_library {
+		name: "librust",
+		srcs: ["b/src/lib.rs"],
+		crate_name: "rust",
+		proc_macros: ["libproc_macro"],
+	}
+	`
+	jsonContent := testProjectJson(t, bp)
+	crates := validateJsonCrates(t, jsonContent)
+	libproc_macro_count := 0
+	librust_count := 0
+	for _, c := range crates {
+		crate := validateCrate(t, c)
+		procMacro, ok := crate["is_proc_macro"].(bool)
+		if !ok {
+			t.Fatalf("Unexpected type for is_proc_macro: %v", crate["is_proc_macro"])
+		}
+
+		name, ok := crate["display_name"].(string)
+		if !ok {
+			t.Fatalf("Unexpected type for display_name: %v", crate["display_name"])
+		}
+
+		switch name {
+		case "libproc_macro":
+			libproc_macro_count += 1
+			if !procMacro {
+				t.Fatalf("'libproc_macro' is marked with is_proc_macro=false")
+			}
+		case "librust":
+			librust_count += 1
+			if procMacro {
+				t.Fatalf("'librust' is not a proc macro crate, but is marked with is_proc_macro=true")
+			}
+		default:
+			break
+		}
+	}
+
+	if libproc_macro_count != 1 || librust_count != 1 {
+		t.Fatalf("Unexpected crate counts: libproc_macro_count: %v, librust_count: %v",
+			libproc_macro_count, librust_count)
+	}
+}
+
 func TestProjectJsonFeature(t *testing.T) {
 	bp := `
 	rust_library {
@@ -176,6 +228,8 @@
 }
 
 func TestProjectJsonBindGen(t *testing.T) {
+	buildOS := android.TestConfig(t.TempDir(), nil, "", nil).BuildOS
+
 	bp := `
 	rust_library {
 		name: "libd",
@@ -214,9 +268,9 @@
 		if strings.Contains(rootModule, "libbindings1") && !strings.Contains(rootModule, "android_arm64") {
 			t.Errorf("The source path for libbindings1 does not contain android_arm64, got %v", rootModule)
 		}
-		if strings.Contains(rootModule, "libbindings2") && !strings.Contains(rootModule, android.BuildOs.String()) {
+		if strings.Contains(rootModule, "libbindings2") && !strings.Contains(rootModule, buildOS.String()) {
 			t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
-				rootModule, android.BuildOs.String())
+				rootModule, buildOS.String())
 		}
 		// Check that libbindings1 does not depend on itself.
 		if strings.Contains(rootModule, "libbindings1") {
diff --git a/rust/protobuf.go b/rust/protobuf.go
index b91fea8..9fe27c4c 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -188,6 +188,12 @@
 			lines,
 			"pub mod empty {",
 			"    pub use protobuf::well_known_types::Empty;",
+			"}",
+			"pub mod wrappers {",
+			"    pub use protobuf::well_known_types::{",
+			"        DoubleValue, FloatValue, Int64Value, UInt64Value, Int32Value, UInt32Value,",
+			"        BoolValue, StringValue, BytesValue",
+			"    };",
 			"}")
 	}
 
diff --git a/rust/rust.go b/rust/rust.go
index f068b3d..c4fd148 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -25,7 +25,9 @@
 	"android/soong/bloaty"
 	"android/soong/cc"
 	cc_config "android/soong/cc/config"
+	"android/soong/fuzz"
 	"android/soong/rust/config"
+	"android/soong/snapshot"
 )
 
 var pctx = android.NewPackageContext("android/soong/rust")
@@ -35,7 +37,7 @@
 
 	android.AddNeverAllowRules(
 		android.NeverAllow().
-			NotIn(config.RustAllowedPaths...).
+			NotIn(append(config.RustAllowedPaths, config.DownstreamRustAllowedPaths...)...).
 			ModuleType(config.RustModuleTypes...))
 
 	android.RegisterModuleType("rust_defaults", defaultsFactory)
@@ -83,27 +85,60 @@
 	// Set by imageMutator
 	CoreVariantNeeded          bool     `blueprint:"mutated"`
 	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
+	RamdiskVariantNeeded       bool     `blueprint:"mutated"`
+	RecoveryVariantNeeded      bool     `blueprint:"mutated"`
 	ExtraVariants              []string `blueprint:"mutated"`
 
+	// Allows this module to use non-APEX version of libraries. Useful
+	// for building binaries that are started before APEXes are activated.
+	Bootstrap *bool
+
+	// Used by vendor snapshot to record dependencies from snapshot modules.
+	SnapshotSharedLibs []string `blueprint:"mutated"`
+	SnapshotStaticLibs []string `blueprint:"mutated"`
+
+	// Make this module available when building for ramdisk.
+	// On device without a dedicated recovery partition, the module is only
+	// available after switching root into
+	// /first_stage_ramdisk. To expose the module before switching root, install
+	// the recovery variant instead.
+	Ramdisk_available *bool
+
 	// Make this module available when building for vendor ramdisk.
 	// On device without a dedicated recovery partition, the module is only
 	// available after switching root into
 	// /first_stage_ramdisk. To expose the module before switching root, install
-	// the recovery variant instead (TODO(b/165791368) recovery not yet supported)
+	// the recovery variant instead
 	Vendor_ramdisk_available *bool
 
+	// Normally Soong uses the directory structure to decide which modules
+	// should be included (framework) or excluded (non-framework) from the
+	// different snapshots (vendor, recovery, etc.), but this property
+	// allows a partner to exclude a module normally thought of as a
+	// framework module from the vendor snapshot.
+	Exclude_from_vendor_snapshot *bool
+
+	// Normally Soong uses the directory structure to decide which modules
+	// should be included (framework) or excluded (non-framework) from the
+	// different snapshots (vendor, recovery, etc.), but this property
+	// allows a partner to exclude a module normally thought of as a
+	// framework module from the recovery snapshot.
+	Exclude_from_recovery_snapshot *bool
+
+	// Make this module available when building for recovery
+	Recovery_available *bool
+
 	// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
 	Min_sdk_version *string
 
-	PreventInstall bool
-	HideFromMake   bool
-	Installable    *bool
+	HideFromMake   bool `blueprint:"mutated"`
+	PreventInstall bool `blueprint:"mutated"`
+
+	Installable *bool
 }
 
 type Module struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	android.ApexModuleBase
+	fuzz.FuzzModule
 
 	VendorProperties cc.VendorProperties
 
@@ -114,6 +149,7 @@
 
 	makeLinkType string
 
+	afdo             *afdo
 	compiler         compiler
 	coverage         *coverage
 	clippy           *clippy
@@ -122,13 +158,15 @@
 	sourceProvider   SourceProvider
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
-	// Unstripped output. This is usually used when this module is linked to another module
-	// as a library. The stripped output which is used for installation can be found via
-	// compiler.strippedOutputFile if it exists.
-	unstrippedOutputFile android.OptionalPath
-	docTimestampFile     android.OptionalPath
+	// Output file to be installed, may be stripped or unstripped.
+	outputFile android.OptionalPath
+
+	docTimestampFile android.OptionalPath
 
 	hideApexVariantFromMake bool
+
+	// For apex variants, this is set as apex.min_sdk_version
+	apexSdkVersion android.ApiLevel
 }
 
 func (mod *Module) Header() bool {
@@ -144,8 +182,8 @@
 	mod.Properties.HideFromMake = true
 }
 
-func (c *Module) HiddenFromMake() bool {
-	return c.Properties.HideFromMake
+func (mod *Module) HiddenFromMake() bool {
+	return mod.Properties.HideFromMake
 }
 
 func (mod *Module) SanitizePropDefined() bool {
@@ -154,13 +192,6 @@
 	return mod.sanitize != nil && mod.compiler != nil
 }
 
-func (mod *Module) IsDependencyRoot() bool {
-	if mod.compiler != nil {
-		return mod.compiler.isDependencyRoot()
-	}
-	panic("IsDependencyRoot called on a non-compiler Rust module")
-}
-
 func (mod *Module) IsPrebuilt() bool {
 	if _, ok := mod.compiler.(*prebuiltLibraryDecorator); ok {
 		return true
@@ -234,14 +265,19 @@
 }
 
 func (mod *Module) Binary() bool {
-	if mod.compiler != nil {
-		if _, ok := mod.compiler.(*binaryDecorator); ok {
-			return true
-		}
+	if binary, ok := mod.compiler.(binaryInterface); ok {
+		return binary.binary()
 	}
 	return false
 }
 
+func (mod *Module) StaticExecutable() bool {
+	if !mod.Binary() {
+		return false
+	}
+	return mod.StaticallyLinked()
+}
+
 func (mod *Module) Object() bool {
 	// Rust has no modules which produce only object files.
 	return false
@@ -249,8 +285,8 @@
 
 func (mod *Module) Toc() android.OptionalPath {
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(libraryInterface); ok {
-			return android.OptionalPath{}
+		if lib, ok := mod.compiler.(libraryInterface); ok {
+			return lib.toc()
 		}
 	}
 	panic(fmt.Errorf("Toc() called on non-library module: %q", mod.BaseModuleName()))
@@ -271,6 +307,10 @@
 	return mod.Properties.VndkVersion != ""
 }
 
+func (mod *Module) Bootstrap() bool {
+	return Bool(mod.Properties.Bootstrap)
+}
+
 func (mod *Module) MustUseVendorVariant() bool {
 	return true
 }
@@ -292,6 +332,20 @@
 	return false
 }
 
+func (mod *Module) IsVndkPrebuiltLibrary() bool {
+	// Rust modules do not provide VNDK prebuilts
+	return false
+}
+
+func (mod *Module) IsVendorPublicLibrary() bool {
+	return mod.VendorProperties.IsVendorPublicLibrary
+}
+
+func (mod *Module) SdkAndPlatformVariantVisibleToMake() bool {
+	// Rust modules to not provide Sdk variants
+	return false
+}
+
 func (c *Module) IsVndkPrivate() bool {
 	return false
 }
@@ -328,10 +382,6 @@
 	return ""
 }
 
-func (mod *Module) MinSdkVersion() string {
-	return ""
-}
-
 func (mod *Module) AlwaysSdk() bool {
 	return false
 }
@@ -355,7 +405,11 @@
 	WholeStaticLibs []string
 	HeaderLibs      []string
 
-	CrtBegin, CrtEnd string
+	// Used for data dependencies adjacent to tests
+	DataLibs []string
+	DataBins []string
+
+	CrtBegin, CrtEnd []string
 }
 
 type PathDeps struct {
@@ -365,6 +419,7 @@
 	SharedLibDeps android.Paths
 	StaticLibs    android.Paths
 	ProcMacros    RustLibraries
+	AfdoProfiles  android.Paths
 
 	// depFlags and depLinkFlags are rustc and linker (clang) flags.
 	depFlags     []string
@@ -381,8 +436,8 @@
 	depGeneratedHeaders   android.Paths
 	depSystemIncludePaths android.Paths
 
-	CrtBegin android.OptionalPath
-	CrtEnd   android.OptionalPath
+	CrtBegin android.Paths
+	CrtEnd   android.Paths
 
 	// Paths to generated source files
 	SrcDeps          android.Paths
@@ -399,6 +454,8 @@
 type compiler interface {
 	initialize(ctx ModuleContext)
 	compilerFlags(ctx ModuleContext, flags Flags) Flags
+	cfgFlags(ctx ModuleContext, flags Flags) Flags
+	featureFlags(ctx ModuleContext, flags Flags) Flags
 	compilerProps() []interface{}
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path
 	compilerDeps(ctx DepsContext, deps Deps) Deps
@@ -409,6 +466,12 @@
 	// copied. This is equivalent to Cargo's OUT_DIR variable.
 	CargoOutDir() android.OptionalPath
 
+	// CargoPkgVersion returns the value of the Cargo_pkg_version property.
+	CargoPkgVersion() string
+
+	// CargoEnvCompat returns whether Cargo environment variables should be used.
+	CargoEnvCompat() bool
+
 	inData() bool
 	install(ctx ModuleContext)
 	relativeInstallPath() string
@@ -420,8 +483,8 @@
 	SetDisabled()
 
 	stdLinkage(ctx *depsContext) RustLinkage
-	isDependencyRoot() bool
 
+	unstrippedOutputFilePath() android.Path
 	strippedOutputFilePath() android.OptionalPath
 }
 
@@ -482,10 +545,6 @@
 	return mod.Properties.PreventInstall
 }
 
-func (mod *Module) HideFromMake() {
-	mod.Properties.HideFromMake = true
-}
-
 func (mod *Module) MarkAsCoverageVariant(coverage bool) {
 	mod.coverage.Properties.IsCoverageVariant = coverage
 }
@@ -509,6 +568,7 @@
 	module.AddProperties(props...)
 	module.AddProperties(
 		&BaseProperties{},
+		&cc.AfdoProperties{},
 		&cc.VendorProperties{},
 		&BenchmarkProperties{},
 		&BindgenProperties{},
@@ -535,7 +595,7 @@
 
 func (mod *Module) CcLibrary() bool {
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(*libraryDecorator); ok {
+		if _, ok := mod.compiler.(libraryInterface); ok {
 			return true
 		}
 	}
@@ -553,6 +613,13 @@
 	return false
 }
 
+func (mod *Module) UnstrippedOutputFile() android.Path {
+	if mod.compiler != nil {
+		return mod.compiler.unstrippedOutputFilePath()
+	}
+	return nil
+}
+
 func (mod *Module) IncludeDirs() android.Paths {
 	if mod.compiler != nil {
 		if library, ok := mod.compiler.(*libraryDecorator); ok {
@@ -605,10 +672,7 @@
 }
 
 func (mod *Module) OutputFile() android.OptionalPath {
-	if mod.compiler != nil && mod.compiler.strippedOutputFilePath().Valid() {
-		return mod.compiler.strippedOutputFilePath()
-	}
-	return mod.unstrippedOutputFile
+	return mod.outputFile
 }
 
 func (mod *Module) CoverageFiles() android.Paths {
@@ -619,6 +683,10 @@
 }
 
 func (mod *Module) installable(apexInfo android.ApexInfo) bool {
+	if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
+		return false
+	}
+
 	// The apex variant is not installable because it is included in the APEX and won't appear
 	// in the system partition as a standalone file.
 	if !apexInfo.IsForPlatform() {
@@ -628,12 +696,19 @@
 	return mod.OutputFile().Valid() && !mod.Properties.PreventInstall
 }
 
+func (ctx moduleContext) apexVariationName() string {
+	return ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).ApexVariationName
+}
+
 var _ cc.LinkableInterface = (*Module)(nil)
 
 func (mod *Module) Init() android.Module {
 	mod.AddProperties(&mod.Properties)
 	mod.AddProperties(&mod.VendorProperties)
 
+	if mod.afdo != nil {
+		mod.AddProperties(mod.afdo.props()...)
+	}
 	if mod.compiler != nil {
 		mod.AddProperties(mod.compiler.compilerProps()...)
 	}
@@ -665,6 +740,7 @@
 }
 func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	module := newBaseModule(hod, multilib)
+	module.afdo = &afdo{}
 	module.coverage = &coverage{}
 	module.clippy = &clippy{}
 	module.sanitize = &sanitize{}
@@ -728,6 +804,10 @@
 }
 
 func (mod *Module) nativeCoverage() bool {
+	// Bug: http://b/137883967 - native-bridge modules do not currently work with coverage
+	if mod.Target().NativeBridge == android.NativeBridgeEnabled {
+		return false
+	}
 	return mod.compiler != nil && mod.compiler.nativeCoverage()
 }
 
@@ -741,6 +821,13 @@
 	return mod.Properties.Installable
 }
 
+func (mod *Module) ProcMacro() bool {
+	if pm, ok := mod.compiler.(procMacroInterface); ok {
+		return pm.ProcMacro()
+	}
+	return false
+}
+
 func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if mod.cachedToolchain == nil {
 		mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -768,12 +855,7 @@
 	toolchain := mod.toolchain(ctx)
 	mod.makeLinkType = cc.GetMakeLinkType(actx, mod)
 
-	// Differentiate static libraries that are vendor available
-	if mod.UseVndk() {
-		mod.Properties.SubName += cc.VendorSuffix
-	} else if mod.InVendorRamdisk() && !mod.OnlyInVendorRamdisk() {
-		mod.Properties.SubName += cc.VendorRamdiskSuffix
-	}
+	mod.Properties.SubName = cc.GetSubnameProperty(actx, mod)
 
 	if !toolchain.Supported() {
 		// This toolchain's unsupported, there's nothing to do for this mod.
@@ -785,8 +867,14 @@
 		Toolchain: toolchain,
 	}
 
+	// Calculate rustc flags
+	if mod.afdo != nil {
+		flags, deps = mod.afdo.flags(ctx, flags, deps)
+	}
 	if mod.compiler != nil {
 		flags = mod.compiler.compilerFlags(ctx, flags)
+		flags = mod.compiler.cfgFlags(ctx, flags)
+		flags = mod.compiler.featureFlags(ctx, flags)
 	}
 	if mod.coverage != nil {
 		flags, deps = mod.coverage.flags(ctx, flags, deps)
@@ -816,16 +904,49 @@
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
-		unstrippedOutputFile := mod.compiler.compile(ctx, flags, deps)
-		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
-		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), mod.unstrippedOutputFile)
+		outputFile := mod.compiler.compile(ctx, flags, deps)
+		if ctx.Failed() {
+			return
+		}
+		mod.outputFile = android.OptionalPathForPath(outputFile)
+		bloaty.MeasureSizeForPaths(ctx, mod.compiler.strippedOutputFilePath(), android.OptionalPathForPath(mod.compiler.unstrippedOutputFilePath()))
 
 		mod.docTimestampFile = mod.compiler.rustdoc(ctx, flags, deps)
+		if mod.docTimestampFile.Valid() {
+			ctx.CheckbuildFile(mod.docTimestampFile.Path())
+		}
+
+		// glob exported headers for snapshot, if BOARD_VNDK_VERSION is current or
+		// RECOVERY_SNAPSHOT_VERSION is current.
+		if lib, ok := mod.compiler.(snapshotLibraryInterface); ok {
+			if cc.ShouldCollectHeadersForSnapshot(ctx, mod, apexInfo) {
+				lib.collectHeadersForSnapshot(ctx, deps)
+			}
+		}
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-		if mod.installable(apexInfo) {
-			mod.compiler.install(ctx)
+		if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) && !mod.ProcMacro() {
+			// If the module has been specifically configure to not be installed then
+			// hide from make as otherwise it will break when running inside make as the
+			// output path to install will not be specified. Not all uninstallable
+			// modules can be hidden from make as some are needed for resolving make
+			// side dependencies. In particular, proc-macros need to be captured in the
+			// host snapshot.
+			mod.HideFromMake()
+		} else if !mod.installable(apexInfo) {
+			mod.SkipInstall()
 		}
+
+		// Still call install though, the installs will be stored as PackageSpecs to allow
+		// using the outputs in a genrule.
+		if mod.OutputFile().Valid() {
+			mod.compiler.install(ctx)
+			if ctx.Failed() {
+				return
+			}
+		}
+
+		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
 	}
 }
 
@@ -853,6 +974,7 @@
 	deps.ProcMacros = android.LastUniqueStrings(deps.ProcMacros)
 	deps.SharedLibs = android.LastUniqueStrings(deps.SharedLibs)
 	deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs)
+	deps.Stdlibs = android.LastUniqueStrings(deps.Stdlibs)
 	deps.WholeStaticLibs = android.LastUniqueStrings(deps.WholeStaticLibs)
 	return deps
 
@@ -863,6 +985,7 @@
 	name      string
 	library   bool
 	procMacro bool
+	dynamic   bool
 }
 
 // InstallDepNeeded returns true for rlibs, dylibs, and proc macros so that they or their transitive
@@ -873,13 +996,24 @@
 
 var _ android.InstallNeededDependencyTag = dependencyTag{}
 
+func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	if d.library && d.dynamic {
+		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+	}
+	return nil
+}
+
+var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}
+
 var (
 	customBindgenDepTag = dependencyTag{name: "customBindgenTag"}
 	rlibDepTag          = dependencyTag{name: "rlibTag", library: true}
-	dylibDepTag         = dependencyTag{name: "dylib", library: true}
+	dylibDepTag         = dependencyTag{name: "dylib", library: true, dynamic: true}
 	procMacroDepTag     = dependencyTag{name: "procMacro", procMacro: true}
 	testPerSrcDepTag    = dependencyTag{name: "rust_unit_tests"}
 	sourceDepTag        = dependencyTag{name: "source"}
+	dataLibDepTag       = dependencyTag{name: "data lib"}
+	dataBinDepTag       = dependencyTag{name: "data bin"}
 )
 
 func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
@@ -917,17 +1051,38 @@
 	}
 }
 
+func (mod *Module) Prebuilt() *android.Prebuilt {
+	if p, ok := mod.compiler.(rustPrebuilt); ok {
+		return p.prebuilt()
+	}
+	return nil
+}
+
 func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps {
 	var depPaths PathDeps
 
 	directRlibDeps := []*Module{}
 	directDylibDeps := []*Module{}
 	directProcMacroDeps := []*Module{}
-	directSharedLibDeps := [](cc.LinkableInterface){}
+	directSharedLibDeps := []cc.SharedLibraryInfo{}
 	directStaticLibDeps := [](cc.LinkableInterface){}
 	directSrcProvidersDeps := []*Module{}
 	directSrcDeps := [](android.SourceFileProducer){}
 
+	// For the dependency from platform to apex, use the latest stubs
+	mod.apexSdkVersion = android.FutureApiLevel
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if !apexInfo.IsForPlatform() {
+		mod.apexSdkVersion = apexInfo.MinSdkVersion
+	}
+
+	if android.InList("hwaddress", ctx.Config().SanitizeDevice()) {
+		// In hwasan build, we override apexSdkVersion to the FutureApiLevel(10000)
+		// so that even Q(29/Android10) apexes could use the dynamic unwinder by linking the newer stubs(e.g libc(R+)).
+		// (b/144430859)
+		mod.apexSdkVersion = android.FutureApiLevel
+	}
+
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
@@ -957,7 +1112,9 @@
 			case procMacroDepTag:
 				directProcMacroDeps = append(directProcMacroDeps, rustDep)
 				mod.Properties.AndroidMkProcMacroLibs = append(mod.Properties.AndroidMkProcMacroLibs, makeLibName)
-			case android.SourceDepTag:
+			}
+
+			if android.IsSourceDepTagWithOutputTag(depTag, "") {
 				// Since these deps are added in path_properties.go via AddDependencies, we need to ensure the correct
 				// OS/Arch variant is used.
 				var helper string
@@ -986,13 +1143,8 @@
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
-				linkFile := rustDep.unstrippedOutputFile
-				if !linkFile.Valid() {
-					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q",
-						depName, ctx.ModuleName())
-					return
-				}
-				linkDir := linkPathFromFilePath(linkFile.Path())
+				linkFile := rustDep.UnstrippedOutputFile()
+				linkDir := linkPathFromFilePath(linkFile)
 				if lib, ok := mod.compiler.(exportedFlagsProducer); ok {
 					lib.exportLinkDirs(linkDir)
 				}
@@ -1024,7 +1176,12 @@
 				if cc.IsWholeStaticLib(depTag) {
 					// rustc will bundle static libraries when they're passed with "-lstatic=<lib>". This will fail
 					// if the library is not prefixed by "lib".
-					if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
+					if mod.Binary() {
+						// Binaries may sometimes need to link whole static libraries that don't start with 'lib'.
+						// Since binaries don't need to 'rebundle' these like libraries and only use these for the
+						// final linkage, pass the args directly to the linker to handle these cases.
+						depPaths.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", linkObject.Path().String(), "-Wl,--no-whole-archive"}...)
+					} else if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
 						depPaths.depFlags = append(depPaths.depFlags, "-lstatic="+libName)
 					} else {
 						ctx.ModuleErrorf("'%q' cannot be listed as a whole_static_library in Rust modules unless the output is prefixed by 'lib'", depName, ctx.ModuleName())
@@ -1042,16 +1199,33 @@
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 				directStaticLibDeps = append(directStaticLibDeps, ccDep)
+
+				// Record baseLibName for snapshots.
+				mod.Properties.SnapshotStaticLibs = append(mod.Properties.SnapshotStaticLibs, cc.BaseLibName(depName))
+
 				mod.Properties.AndroidMkStaticLibs = append(mod.Properties.AndroidMkStaticLibs, makeLibName)
 			case cc.IsSharedDepTag(depTag):
+				// For the shared lib dependencies, we may link to the stub variant
+				// of the dependency depending on the context (e.g. if this
+				// dependency crosses the APEX boundaries).
+				sharedLibraryInfo, exportedInfo := cc.ChooseStubOrImpl(ctx, dep)
+
+				// Re-get linkObject as ChooseStubOrImpl actually tells us which
+				// object (either from stub or non-stub) to use.
+				linkObject = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
+				linkPath = linkPathFromFilePath(linkObject.Path())
+
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
 				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
-				exportedInfo := ctx.OtherModuleProvider(dep, cc.FlagExporterInfoProvider).(cc.FlagExporterInfo)
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
-				directSharedLibDeps = append(directSharedLibDeps, ccDep)
+				directSharedLibDeps = append(directSharedLibDeps, sharedLibraryInfo)
+
+				// Record baseLibName for snapshots.
+				mod.Properties.SnapshotSharedLibs = append(mod.Properties.SnapshotSharedLibs, cc.BaseLibName(depName))
+
 				mod.Properties.AndroidMkSharedLibs = append(mod.Properties.AndroidMkSharedLibs, makeLibName)
 				exportDep = true
 			case cc.IsHeaderDepTag(depTag):
@@ -1060,9 +1234,9 @@
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 			case depTag == cc.CrtBeginDepTag:
-				depPaths.CrtBegin = linkObject
+				depPaths.CrtBegin = append(depPaths.CrtBegin, linkObject.Path())
 			case depTag == cc.CrtEndDepTag:
-				depPaths.CrtEnd = linkObject
+				depPaths.CrtEnd = append(depPaths.CrtEnd, linkObject.Path())
 			}
 
 			// Make sure these dependencies are propagated
@@ -1070,11 +1244,17 @@
 				lib.exportLinkDirs(linkPath)
 				lib.exportLinkObjects(linkObject.String())
 			}
+		} else {
+			switch {
+			case depTag == cc.CrtBeginDepTag:
+				depPaths.CrtBegin = append(depPaths.CrtBegin, android.OutputFileForModule(ctx, dep, ""))
+			case depTag == cc.CrtEndDepTag:
+				depPaths.CrtEnd = append(depPaths.CrtEnd, android.OutputFileForModule(ctx, dep, ""))
+			}
 		}
 
 		if srcDep, ok := dep.(android.SourceFileProducer); ok {
-			switch depTag {
-			case android.SourceDepTag:
+			if android.IsSourceDepTagWithOutputTag(depTag, "") {
 				// These are usually genrules which don't have per-target variants.
 				directSrcDeps = append(directSrcDeps, srcDep)
 			}
@@ -1083,15 +1263,15 @@
 
 	var rlibDepFiles RustLibraries
 	for _, dep := range directRlibDeps {
-		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 	var dylibDepFiles RustLibraries
 	for _, dep := range directDylibDeps {
-		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 	var procMacroDepFiles RustLibraries
 	for _, dep := range directProcMacroDeps {
-		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
+		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 
 	var staticLibDepFiles android.Paths
@@ -1102,11 +1282,11 @@
 	var sharedLibFiles android.Paths
 	var sharedLibDepFiles android.Paths
 	for _, dep := range directSharedLibDeps {
-		sharedLibFiles = append(sharedLibFiles, dep.OutputFile().Path())
-		if dep.Toc().Valid() {
-			sharedLibDepFiles = append(sharedLibDepFiles, dep.Toc().Path())
+		sharedLibFiles = append(sharedLibFiles, dep.SharedLibrary)
+		if dep.TableOfContents.Valid() {
+			sharedLibDepFiles = append(sharedLibDepFiles, dep.TableOfContents.Path())
 		} else {
-			sharedLibDepFiles = append(sharedLibDepFiles, dep.OutputFile().Path())
+			sharedLibDepFiles = append(sharedLibDepFiles, dep.SharedLibrary)
 		}
 	}
 
@@ -1146,6 +1326,18 @@
 	return mod.compiler.inData()
 }
 
+func (mod *Module) InstallInRamdisk() bool {
+	return mod.InRamdisk()
+}
+
+func (mod *Module) InstallInVendorRamdisk() bool {
+	return mod.InVendorRamdisk()
+}
+
+func (mod *Module) InstallInRecovery() bool {
+	return mod.InRecovery()
+}
+
 func linkPathFromFilePath(filepath android.Path) string {
 	return strings.Split(filepath.String(), filepath.Base())[0]
 }
@@ -1157,6 +1349,11 @@
 
 	deps := mod.deps(ctx)
 	var commonDepVariations []blueprint.Variation
+	var snapshotInfo *cc.SnapshotInfo
+
+	if ctx.Os() == android.Android {
+		deps.SharedLibs, _ = cc.RewriteLibs(mod, &snapshotInfo, actx, ctx.Config(), deps.SharedLibs)
+	}
 
 	stdLinkage := "dylib-std"
 	if mod.compiler.stdLinkage(ctx) == RlibLinkage {
@@ -1164,61 +1361,104 @@
 	}
 
 	rlibDepVariations := commonDepVariations
+
 	if lib, ok := mod.compiler.(libraryInterface); !ok || !lib.sysroot() {
 		rlibDepVariations = append(rlibDepVariations,
 			blueprint.Variation{Mutator: "rust_stdlinkage", Variation: stdLinkage})
 	}
 
-	actx.AddVariationDependencies(
-		append(rlibDepVariations, []blueprint.Variation{
-			{Mutator: "rust_libraries", Variation: rlibVariation}}...),
-		rlibDepTag, deps.Rlibs...)
+	// 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(rlibDepVariations, depTag, lib)
+	}
+
+	// dylibs
 	actx.AddVariationDependencies(
 		append(commonDepVariations, []blueprint.Variation{
 			{Mutator: "rust_libraries", Variation: dylibVariation}}...),
 		dylibDepTag, deps.Dylibs...)
 
+	// rustlibs
 	if deps.Rustlibs != nil && !mod.compiler.Disabled() {
 		autoDep := mod.compiler.(autoDeppable).autoDep(ctx)
-		if autoDep.depTag == rlibDepTag {
-			actx.AddVariationDependencies(
-				append(rlibDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation}),
-				autoDep.depTag, deps.Rustlibs...)
-		} else {
-			actx.AddVariationDependencies(
-				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation}),
-				autoDep.depTag, deps.Rustlibs...)
+		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)
+				}
+			}
 		}
 	}
+	// stdlibs
 	if deps.Stdlibs != nil {
 		if mod.compiler.stdLinkage(ctx) == RlibLinkage {
-			actx.AddVariationDependencies(
-				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: "rlib"}),
-				rlibDepTag, deps.Stdlibs...)
+			for _, lib := range deps.Stdlibs {
+				depTag := rlibDepTag
+				lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+
+				actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}...),
+					depTag, lib)
+			}
 		} else {
 			actx.AddVariationDependencies(
 				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"}),
 				dylibDepTag, deps.Stdlibs...)
 		}
 	}
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "shared"}),
-		cc.SharedDepTag(), deps.SharedLibs...)
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "static"}),
-		cc.StaticDepTag(false), deps.StaticLibs...)
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "static"}),
-		cc.StaticDepTag(true), deps.WholeStaticLibs...)
+
+	for _, lib := range deps.SharedLibs {
+		depTag := cc.SharedDepTag()
+		name, version := cc.StubsLibNameAndVersion(lib)
+
+		variations := []blueprint.Variation{
+			{Mutator: "link", Variation: "shared"},
+		}
+		cc.AddSharedLibDependenciesWithVersions(ctx, mod, variations, depTag, name, version, false)
+	}
+
+	for _, lib := range deps.WholeStaticLibs {
+		depTag := cc.StaticDepTag(true)
+		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).StaticLibs)
+
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, lib)
+	}
+
+	for _, lib := range deps.StaticLibs {
+		depTag := cc.StaticDepTag(false)
+		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).StaticLibs)
+
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, lib)
+	}
 
 	actx.AddVariationDependencies(nil, cc.HeaderDepTag(), deps.HeaderLibs...)
 
 	crtVariations := cc.GetCrtVariations(ctx, mod)
-	if deps.CrtBegin != "" {
-		actx.AddVariationDependencies(crtVariations, cc.CrtBeginDepTag, deps.CrtBegin)
+	for _, crt := range deps.CrtBegin {
+		actx.AddVariationDependencies(crtVariations, cc.CrtBeginDepTag,
+			cc.RewriteSnapshotLib(crt, cc.GetSnapshot(mod, &snapshotInfo, actx).Objects))
 	}
-	if deps.CrtEnd != "" {
-		actx.AddVariationDependencies(crtVariations, cc.CrtEndDepTag, deps.CrtEnd)
+	for _, crt := range deps.CrtEnd {
+		actx.AddVariationDependencies(crtVariations, cc.CrtEndDepTag,
+			cc.RewriteSnapshotLib(crt, cc.GetSnapshot(mod, &snapshotInfo, actx).Objects))
 	}
 
 	if mod.sourceProvider != nil {
@@ -1228,10 +1468,23 @@
 				bindgen.Properties.Custom_bindgen)
 		}
 	}
+
+	actx.AddVariationDependencies([]blueprint.Variation{
+		{Mutator: "link", Variation: "shared"},
+	}, dataLibDepTag, deps.DataLibs...)
+
+	actx.AddVariationDependencies(nil, dataBinDepTag, deps.DataBins...)
+
 	// proc_macros are compiler plugins, and so we need the host arch variant as a dependendcy.
 	actx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), procMacroDepTag, deps.ProcMacros...)
 }
 
+// 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)
@@ -1263,6 +1516,7 @@
 }
 
 var _ android.HostToolProvider = (*Module)(nil)
+var _ snapshot.RelativeInstallPath = (*Module)(nil)
 
 func (mod *Module) HostToolPath() android.OptionalPath {
 	if !mod.Host() {
@@ -1270,21 +1524,23 @@
 	}
 	if binary, ok := mod.compiler.(*binaryDecorator); ok {
 		return android.OptionalPathForPath(binary.baseCompiler.path)
+	} else if pm, ok := mod.compiler.(*procMacroDecorator); ok {
+		// Even though proc-macros aren't strictly "tools", since they target the compiler
+		// and act as compiler plugins, we treat them similarly.
+		return android.OptionalPathForPath(pm.baseCompiler.path)
 	}
 	return android.OptionalPath{}
 }
 
 var _ android.ApexModule = (*Module)(nil)
 
-func (mod *Module) minSdkVersion() string {
+func (mod *Module) MinSdkVersion() string {
 	return String(mod.Properties.Min_sdk_version)
 }
 
-var _ android.ApexModule = (*Module)(nil)
-
 // Implements android.ApexModule
 func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
-	minSdkVersion := mod.minSdkVersion()
+	minSdkVersion := mod.MinSdkVersion()
 	if minSdkVersion == "apex_inherit" {
 		return nil
 	}
@@ -1342,7 +1598,7 @@
 // Overrides ApexModule.IsInstallabeToApex()
 func (mod *Module) IsInstallableToApex() bool {
 	if mod.compiler != nil {
-		if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.shared() || lib.dylib()) {
 			return true
 		}
 		if _, ok := mod.compiler.(*binaryDecorator); ok {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 6ae05d9..97bd541 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -37,21 +37,29 @@
 
 	genrule.PrepareForTestWithGenRuleBuildComponents,
 
-	PrepareForIntegrationTestWithRust,
+	PrepareForTestWithRustIncludeVndk,
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.DeviceVndkVersion = StringPtr("current")
+		variables.ProductVndkVersion = StringPtr("current")
+		variables.Platform_vndk_version = StringPtr("29")
+	}),
 )
 
 var rustMockedFiles = android.MockFS{
-	"foo.rs":          nil,
-	"foo.c":           nil,
-	"src/bar.rs":      nil,
-	"src/any.h":       nil,
-	"proto.proto":     nil,
-	"proto/buf.proto": nil,
-	"buf.proto":       nil,
-	"foo.proto":       nil,
-	"liby.so":         nil,
-	"libz.so":         nil,
-	"data.txt":        nil,
+	"foo.rs":                       nil,
+	"foo.c":                        nil,
+	"src/bar.rs":                   nil,
+	"src/any.h":                    nil,
+	"c_includes/c_header.h":        nil,
+	"rust_includes/rust_headers.h": nil,
+	"proto.proto":                  nil,
+	"proto/buf.proto":              nil,
+	"buf.proto":                    nil,
+	"foo.proto":                    nil,
+	"liby.so":                      nil,
+	"libz.so":                      nil,
+	"data.txt":                     nil,
+	"liblog.map.txt":               nil,
 }
 
 // testRust returns a TestContext in which a basic environment has been setup.
@@ -67,15 +75,46 @@
 }
 
 func testRustVndk(t *testing.T, bp string) *android.TestContext {
+	return testRustVndkFs(t, bp, rustMockedFiles)
+}
+
+const (
+	sharedVendorVariant   = "android_vendor.29_arm64_armv8-a_shared"
+	rlibVendorVariant     = "android_vendor.29_arm64_armv8-a_rlib_rlib-std"
+	sharedRecoveryVariant = "android_recovery_arm64_armv8-a_shared"
+	rlibRecoveryVariant   = "android_recovery_arm64_armv8-a_rlib_rlib-std"
+)
+
+func testRustVndkFs(t *testing.T, bp string, fs android.MockFS) *android.TestContext {
+	return testRustVndkFsVersions(t, bp, fs, "current", "current", "29")
+}
+
+func testRustVndkFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, product_version, vndk_version string) *android.TestContext {
 	skipTestIfOsNotSupported(t)
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
-		rustMockedFiles.AddToFixture(),
+		fs.AddToFixture(),
 		android.FixtureModifyProductVariables(
 			func(variables android.FixtureProductVariables) {
-				variables.DeviceVndkVersion = StringPtr("current")
-				variables.ProductVndkVersion = StringPtr("current")
-				variables.Platform_vndk_version = StringPtr("29")
+				variables.DeviceVndkVersion = StringPtr(device_version)
+				variables.ProductVndkVersion = StringPtr(product_version)
+				variables.Platform_vndk_version = StringPtr(vndk_version)
+			},
+		),
+	).RunTestWithBp(t, bp)
+	return result.TestContext
+}
+
+func testRustRecoveryFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, vndk_version, recovery_version string) *android.TestContext {
+	skipTestIfOsNotSupported(t)
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		fs.AddToFixture(),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = StringPtr(device_version)
+				variables.RecoverySnapshotVersion = StringPtr(recovery_version)
+				variables.Platform_vndk_version = StringPtr(vndk_version)
 			},
 		),
 	).RunTestWithBp(t, bp)
@@ -115,10 +154,14 @@
 
 // testRustVndkError is similar to testRustError, but can be used to test VNDK-related errors.
 func testRustVndkError(t *testing.T, pattern string, bp string) {
+	testRustVndkFsError(t, pattern, bp, rustMockedFiles)
+}
+
+func testRustVndkFsError(t *testing.T, pattern string, bp string, fs android.MockFS) {
 	skipTestIfOsNotSupported(t)
 	android.GroupFixturePreparers(
 		prepareForRustTest,
-		rustMockedFiles.AddToFixture(),
+		fs.AddToFixture(),
 		android.FixtureModifyProductVariables(
 			func(variables android.FixtureProductVariables) {
 				variables.DeviceVndkVersion = StringPtr("current")
@@ -413,6 +456,13 @@
 		}`)
 
 	m := ctx.SingletonForTests("file_metrics")
+	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
 	m.Output("libwaldo.dylib.so.bloaty.csv")
-	m.Output("stripped/libwaldo.dylib.so.bloaty.csv")
+}
+
+func assertString(t *testing.T, got, expected string) {
+	t.Helper()
+	if got != expected {
+		t.Errorf("expected %q got %q", expected, got)
+	}
 }
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 3d14d51..39aaf33 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -15,20 +15,39 @@
 package rust
 
 import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/rust/config"
-	"fmt"
-	"github.com/google/blueprint"
 )
 
+// TODO: When Rust has sanitizer-parity with CC, deduplicate this struct
 type SanitizeProperties struct {
 	// enable AddressSanitizer, HWAddressSanitizer, and others.
 	Sanitize struct {
 		Address   *bool `android:"arch_variant"`
 		Hwaddress *bool `android:"arch_variant"`
-		Fuzzer    *bool `android:"arch_variant"`
-		Never     *bool `android:"arch_variant"`
+
+		// Memory-tagging, only available on arm64
+		// if diag.memtag unset or false, enables async memory tagging
+		Memtag_heap *bool `android:"arch_variant"`
+		Fuzzer      *bool `android:"arch_variant"`
+		Never       *bool `android:"arch_variant"`
+
+		// Sanitizers to run in the diagnostic mode (as opposed to the release mode).
+		// Replaces abort() on error with a human-readable error message.
+		// Address and Thread sanitizers always run in diagnostic mode.
+		Diag struct {
+			// Memory-tagging, only available on arm64
+			// requires sanitizer.memtag: true
+			// if set, enables sync memory tagging
+			Memtag_heap *bool `android:"arch_variant"`
+		}
 	}
 	SanitizerEnabled bool `blueprint:"mutated"`
 	SanitizeDep      bool `blueprint:"mutated"`
@@ -38,7 +57,7 @@
 }
 
 var fuzzerFlags = []string{
-	"-C passes='sancov'",
+	"-C passes='sancov-module'",
 
 	"--cfg fuzzing",
 	"-C llvm-args=-sanitizer-coverage-level=3",
@@ -47,8 +66,11 @@
 	"-C llvm-args=-sanitizer-coverage-trace-geps",
 	"-C llvm-args=-sanitizer-coverage-prune-blocks=0",
 
+	// See https://github.com/rust-fuzz/cargo-fuzz/pull/193
+	"-C link-dead-code",
+
 	// Sancov breaks with lto
-	// TODO: Remove when https://bugs.llvm.org/show_bug.cgi?id=41734 is resolved and sancov works with LTO
+	// TODO: Remove when https://bugs.llvm.org/show_bug.cgi?id=41734 is resolved and sancov-module works with LTO
 	"-C lto=no",
 }
 
@@ -56,9 +78,18 @@
 	"-Z sanitizer=address",
 }
 
+// See cc/sanitize.go's hwasanGlobalOptions for global hwasan options.
 var hwasanFlags = []string{
 	"-Z sanitizer=hwaddress",
 	"-C target-feature=+tagged-globals",
+
+	// Flags from cc/sanitize.go hwasanFlags
+	"-C llvm-args=--aarch64-enable-global-isel-at-O=-1",
+	"-C llvm-args=-fast-isel=false",
+	"-C llvm-args=-instcombine-lower-dbg-declare=0",
+
+	// Additional flags for HWASAN-ified Rust/C interop
+	"-C llvm-args=--hwasan-with-ifunc",
 }
 
 func boolPtr(v bool) *bool {
@@ -76,7 +107,88 @@
 }
 
 func (sanitize *sanitize) begin(ctx BaseModuleContext) {
-	s := sanitize.Properties.Sanitize
+	s := &sanitize.Properties.Sanitize
+
+	// Never always wins.
+	if Bool(s.Never) {
+		return
+	}
+
+	// rust_test targets default to SYNC MemTag unless explicitly set to ASYNC (via diag: {Memtag_heap}).
+	if binary, ok := ctx.RustModule().compiler.(binaryInterface); ok && binary.testBinary() {
+		if s.Memtag_heap == nil {
+			s.Memtag_heap = proptools.BoolPtr(true)
+		}
+		if s.Diag.Memtag_heap == nil {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
+	}
+
+	var globalSanitizers []string
+	var globalSanitizersDiag []string
+
+	if ctx.Host() {
+		if !ctx.Windows() {
+			globalSanitizers = ctx.Config().SanitizeHost()
+		}
+	} else {
+		arches := ctx.Config().SanitizeDeviceArch()
+		if len(arches) == 0 || android.InList(ctx.Arch().ArchType.Name, arches) {
+			globalSanitizers = ctx.Config().SanitizeDevice()
+			globalSanitizersDiag = ctx.Config().SanitizeDeviceDiag()
+		}
+	}
+
+	if len(globalSanitizers) > 0 {
+		var found bool
+
+		// Global Sanitizers
+		if found, globalSanitizers = android.RemoveFromList("hwaddress", globalSanitizers); found && s.Hwaddress == nil {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet.
+			if !ctx.RustModule().StaticExecutable() {
+				s.Hwaddress = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("memtag_heap", globalSanitizers); found && s.Memtag_heap == nil {
+			if !ctx.Config().MemtagHeapDisabledForPath(ctx.ModuleDir()) {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("address", globalSanitizers); found && s.Address == nil {
+			s.Address = proptools.BoolPtr(true)
+		}
+
+		if found, globalSanitizers = android.RemoveFromList("fuzzer", globalSanitizers); found && s.Fuzzer == nil {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet, and fuzzer enables HWAsan
+			if !ctx.RustModule().StaticExecutable() {
+				s.Fuzzer = proptools.BoolPtr(true)
+			}
+		}
+
+		// Global Diag Sanitizers
+		if found, globalSanitizersDiag = android.RemoveFromList("memtag_heap", globalSanitizersDiag); found &&
+			s.Diag.Memtag_heap == nil && Bool(s.Memtag_heap) {
+			s.Diag.Memtag_heap = proptools.BoolPtr(true)
+		}
+	}
+
+	// Enable Memtag for all components in the include paths (for Aarch64 only)
+	if ctx.Arch().ArchType == android.Arm64 {
+		if ctx.Config().MemtagHeapSyncEnabledForPath(ctx.ModuleDir()) {
+			if s.Memtag_heap == nil {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+			if s.Diag.Memtag_heap == nil {
+				s.Diag.Memtag_heap = proptools.BoolPtr(true)
+			}
+		} else if ctx.Config().MemtagHeapAsyncEnabledForPath(ctx.ModuleDir()) {
+			if s.Memtag_heap == nil {
+				s.Memtag_heap = proptools.BoolPtr(true)
+			}
+		}
+	}
 
 	// TODO:(b/178369775)
 	// For now sanitizing is only supported on devices
@@ -93,7 +205,22 @@
 		s.Hwaddress = nil
 	}
 
-	if ctx.Os() == android.Android && Bool(s.Hwaddress) {
+	// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
+	// Keep libc instrumented so that ramdisk / vendor_ramdisk / recovery can run hwasan-instrumented code if necessary.
+	if (ctx.RustModule().InRamdisk() || ctx.RustModule().InVendorRamdisk() || ctx.RustModule().InRecovery()) && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
+		s.Hwaddress = nil
+	}
+
+	if Bool(s.Hwaddress) {
+		s.Address = nil
+	}
+
+	// Memtag_heap is only implemented on AArch64.
+	if ctx.Arch().ArchType != android.Arm64 {
+		s.Memtag_heap = nil
+	}
+
+	if ctx.Os() == android.Android && (Bool(s.Hwaddress) || Bool(s.Address) || Bool(s.Memtag_heap)) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
 }
@@ -113,12 +240,10 @@
 		} else {
 			flags.RustFlags = append(flags.RustFlags, asanFlags...)
 		}
-	}
-	if Bool(sanitize.Properties.Sanitize.Address) {
-		flags.RustFlags = append(flags.RustFlags, asanFlags...)
-	}
-	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
+	} else if Bool(sanitize.Properties.Sanitize.Hwaddress) {
 		flags.RustFlags = append(flags.RustFlags, hwasanFlags...)
+	} else if Bool(sanitize.Properties.Sanitize.Address) {
+		flags.RustFlags = append(flags.RustFlags, asanFlags...)
 	}
 	return flags, deps
 }
@@ -133,6 +258,26 @@
 			return
 		}
 
+		if Bool(mod.sanitize.Properties.Sanitize.Memtag_heap) && mod.Binary() {
+			noteDep := "note_memtag_heap_async"
+			if Bool(mod.sanitize.Properties.Sanitize.Diag.Memtag_heap) {
+				noteDep = "note_memtag_heap_sync"
+			}
+			// If we're using snapshots, redirect to snapshot whenever possible
+			// TODO(b/178470649): clean manual snapshot redirections
+			snapshot := mctx.Provider(cc.SnapshotInfoProvider).(cc.SnapshotInfo)
+			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
+				noteDep = lib
+			}
+			depTag := cc.StaticDepTag(true)
+			variations := append(mctx.Target().Variations(),
+				blueprint.Variation{Mutator: "link", Variation: "static"})
+			if mod.Device() {
+				variations = append(variations, mod.ImageVariation())
+			}
+			mctx.AddFarVariationDependencies(variations, depTag, noteDep)
+		}
+
 		variations := mctx.Target().Variations()
 		var depTag blueprint.DependencyTag
 		var deps []string
@@ -145,27 +290,24 @@
 			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "asan")}
 		} else if mod.IsSanitizerEnabled(cc.Hwasan) ||
 			(mod.IsSanitizerEnabled(cc.Fuzzer) && mctx.Arch().ArchType == android.Arm64) {
-			// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
-			if binary, ok := mod.compiler.(*binaryDecorator); ok {
-				if Bool(binary.Properties.Static_executable) {
+			// TODO(b/204776996): HWASan for static Rust binaries isn't supported yet.
+			if binary, ok := mod.compiler.(binaryInterface); ok {
+				if binary.staticallyLinked() {
 					mctx.ModuleErrorf("HWASan is not supported for static Rust executables yet.")
 				}
 			}
 
-			if mod.StaticallyLinked() {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "static"})
-				depTag = cc.StaticDepTag(false)
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan_static")}
-			} else {
-				variations = append(variations,
-					blueprint.Variation{Mutator: "link", Variation: "shared"})
-				depTag = cc.SharedDepTag()
-				deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
-			}
+			// Always link against the shared library -- static binaries will pull in the static
+			// library during final link if necessary
+			variations = append(variations,
+				blueprint.Variation{Mutator: "link", Variation: "shared"})
+			depTag = cc.SharedDepTag()
+			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "hwasan")}
 		}
 
-		mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		if len(deps) > 0 {
+			mctx.AddFarVariationDependencies(variations, depTag, deps...)
+		}
 	}
 }
 
@@ -181,6 +323,9 @@
 	case cc.Hwasan:
 		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
 		sanitizerSet = true
+	case cc.Memtag_heap:
+		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+		sanitizerSet = true
 	default:
 		panic(fmt.Errorf("setting unsupported sanitizerType %d", t))
 	}
@@ -240,6 +385,8 @@
 		return sanitize.Properties.Sanitize.Address
 	case cc.Hwasan:
 		return sanitize.Properties.Sanitize.Hwaddress
+	case cc.Memtag_heap:
+		return sanitize.Properties.Sanitize.Memtag_heap
 	default:
 		return nil
 	}
@@ -265,6 +412,12 @@
 	case cc.Asan:
 		return true
 	case cc.Hwasan:
+		// TODO(b/180495975): HWASan for static Rust binaries isn't supported yet.
+		if mod.StaticExecutable() {
+			return false
+		}
+		return true
+	case cc.Memtag_heap:
 		return true
 	default:
 		return false
@@ -308,8 +461,8 @@
 func (mod *Module) StaticallyLinked() bool {
 	if lib, ok := mod.compiler.(libraryInterface); ok {
 		return lib.rlib() || lib.static()
-	} else if binary, ok := mod.compiler.(*binaryDecorator); ok {
-		return Bool(binary.Properties.Static_executable)
+	} else if binary, ok := mod.compiler.(binaryInterface); ok {
+		return binary.staticallyLinked()
 	}
 	return false
 }
diff --git a/rust/sanitize_test.go b/rust/sanitize_test.go
new file mode 100644
index 0000000..d6a14b2
--- /dev/null
+++ b/rust/sanitize_test.go
@@ -0,0 +1,365 @@
+package rust
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+type MemtagNoteType int
+
+const (
+	None MemtagNoteType = iota + 1
+	Sync
+	Async
+)
+
+func (t MemtagNoteType) str() string {
+	switch t {
+	case None:
+		return "none"
+	case Sync:
+		return "sync"
+	case Async:
+		return "async"
+	default:
+		panic("type_note_invalid")
+	}
+}
+
+func checkHasMemtagNote(t *testing.T, m android.TestingModule, expected MemtagNoteType) {
+	t.Helper()
+	note_async := "note_memtag_heap_async"
+	note_sync := "note_memtag_heap_sync"
+
+	found := None
+	implicits := m.Rule("rustc").Implicits
+	for _, lib := range implicits {
+		if strings.Contains(lib.Rel(), note_async) {
+			found = Async
+			break
+		} else if strings.Contains(lib.Rel(), note_sync) {
+			found = Sync
+			break
+		}
+	}
+
+	if found != expected {
+		t.Errorf("Wrong Memtag note in target %q: found %q, expected %q", m.Module().(*Module).Name(), found.str(), expected.str())
+	}
+}
+
+var prepareForTestWithMemtagHeap = android.GroupFixturePreparers(
+	android.FixtureModifyMockFS(func(fs android.MockFS) {
+		templateBp := `
+		rust_test {
+			name: "unset_test_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_test {
+			name: "no_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_test {
+			name: "set_memtag_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_test {
+			name: "set_memtag_set_async_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_test {
+			name: "set_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_test {
+			name: "unset_memtag_set_sync_test_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_binary_%[1]s",
+			srcs: ["foo.rs"],
+		}
+
+		rust_binary {
+			name: "no_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: false },
+		}
+
+		rust_binary {
+			name: "set_memtag_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_async_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: false }  },
+		}
+
+		rust_binary {
+			name: "set_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { memtag_heap: true, diag: { memtag_heap: true }  },
+		}
+
+		rust_binary {
+			name: "unset_memtag_set_sync_binary_%[1]s",
+			srcs: ["foo.rs"],
+			sanitize: { diag: { memtag_heap: true }  },
+		}
+		`
+		subdirNoOverrideBp := fmt.Sprintf(templateBp, "no_override")
+		subdirOverrideDefaultDisableBp := fmt.Sprintf(templateBp, "override_default_disable")
+		subdirSyncBp := fmt.Sprintf(templateBp, "override_default_sync")
+		subdirAsyncBp := fmt.Sprintf(templateBp, "override_default_async")
+
+		fs.Merge(android.MockFS{
+			"subdir_no_override/Android.bp":              []byte(subdirNoOverrideBp),
+			"subdir_override_default_disable/Android.bp": []byte(subdirOverrideDefaultDisableBp),
+			"subdir_sync/Android.bp":                     []byte(subdirSyncBp),
+			"subdir_async/Android.bp":                    []byte(subdirAsyncBp),
+		})
+	}),
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.MemtagHeapExcludePaths = []string{"subdir_override_default_disable"}
+		// "subdir_override_default_disable" is covered by both include and override_default_disable paths. override_default_disable wins.
+		variables.MemtagHeapSyncIncludePaths = []string{"subdir_sync", "subdir_override_default_disable"}
+		variables.MemtagHeapAsyncIncludePaths = []string{"subdir_async", "subdir_override_default_disable"}
+	}),
+)
+
+func TestSanitizeMemtagHeap(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
+
+func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
+	variant := "android_arm64_armv8-a"
+
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		prepareForTestWithMemtagHeap,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.SanitizeDevice = []string{"memtag_heap"}
+			variables.SanitizeDeviceDiag = []string{"memtag_heap"}
+		}),
+	).RunTest(t)
+	ctx := result.TestContext
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+}
diff --git a/rust/snapshot_prebuilt.go b/rust/snapshot_prebuilt.go
new file mode 100644
index 0000000..dfbc1d1
--- /dev/null
+++ b/rust/snapshot_prebuilt.go
@@ -0,0 +1,132 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+
+	"github.com/google/blueprint/proptools"
+)
+
+const (
+	snapshotRlibSuffix = "_rlib."
+)
+
+type snapshotLibraryDecorator struct {
+	cc.BaseSnapshotDecorator
+	*libraryDecorator
+	properties          cc.SnapshotLibraryProperties
+	sanitizerProperties struct {
+		CfiEnabled bool `blueprint:"mutated"`
+
+		// Library flags for cfi variant.
+		Cfi cc.SnapshotLibraryProperties `android:"arch_variant"`
+	}
+}
+
+func init() {
+	registerRustSnapshotModules(android.InitRegistrationContext)
+}
+
+func registerRustSnapshotModules(ctx android.RegistrationContext) {
+	cc.VendorSnapshotImageSingleton.RegisterAdditionalModule(ctx,
+		"vendor_snapshot_rlib", VendorSnapshotRlibFactory)
+	cc.RecoverySnapshotImageSingleton.RegisterAdditionalModule(ctx,
+		"recovery_snapshot_rlib", RecoverySnapshotRlibFactory)
+}
+
+func snapshotLibraryFactory(image cc.SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+	module, library := NewRustLibrary(android.DeviceSupported)
+
+	module.sanitize = nil
+	library.stripper.StripProperties.Strip.None = proptools.BoolPtr(true)
+
+	prebuilt := &snapshotLibraryDecorator{
+		libraryDecorator: library,
+	}
+
+	module.compiler = prebuilt
+
+	prebuilt.Init(module, image, moduleSuffix)
+	module.AddProperties(
+		&prebuilt.properties,
+		&prebuilt.sanitizerProperties,
+	)
+
+	return module, prebuilt
+}
+
+func (library *snapshotLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+	var variant string
+	if library.static() {
+		variant = cc.SnapshotStaticSuffix
+	} else if library.shared() {
+		variant = cc.SnapshotSharedSuffix
+	} else if library.rlib() {
+		variant = cc.SnapshotRlibSuffix
+	}
+
+	if !library.dylib() {
+		// TODO(184042776): Remove this check when dylibs are supported in snapshots.
+		library.SetSnapshotAndroidMkSuffix(ctx, variant)
+	}
+
+	if !library.MatchesWithDevice(ctx.DeviceConfig()) {
+		return nil
+	}
+	outputFile := android.PathForModuleSrc(ctx, *library.properties.Src)
+	library.unstrippedOutputFile = outputFile
+	return outputFile
+}
+
+func (library *snapshotLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath {
+	return android.OptionalPath{}
+}
+
+// vendor_snapshot_rlib is a special prebuilt rlib library which is auto-generated by
+// development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_rlib
+// overrides the vendor variant of the rust rlib library with the same name, if BOARD_VNDK_VERSION
+// is set.
+func VendorSnapshotRlibFactory() android.Module {
+	module, prebuilt := snapshotLibraryFactory(cc.VendorSnapshotImageSingleton, cc.SnapshotRlibSuffix)
+	prebuilt.libraryDecorator.BuildOnlyRlib()
+	prebuilt.libraryDecorator.setNoStdlibs()
+	return module.Init()
+}
+
+func RecoverySnapshotRlibFactory() android.Module {
+	module, prebuilt := snapshotLibraryFactory(cc.RecoverySnapshotImageSingleton, cc.SnapshotRlibSuffix)
+	prebuilt.libraryDecorator.BuildOnlyRlib()
+	prebuilt.libraryDecorator.setNoStdlibs()
+	return module.Init()
+}
+
+func (library *snapshotLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
+	arches := config.Arches()
+	if len(arches) == 0 || arches[0].ArchType.String() != library.Arch() {
+		return false
+	}
+	if library.properties.Src == nil {
+		return false
+	}
+	return true
+}
+
+func (library *snapshotLibraryDecorator) IsSnapshotPrebuilt() bool {
+	return true
+}
+
+var _ cc.SnapshotInterface = (*snapshotLibraryDecorator)(nil)
diff --git a/rust/snapshot_utils.go b/rust/snapshot_utils.go
index 943c790..8dabd9b 100644
--- a/rust/snapshot_utils.go
+++ b/rust/snapshot_utils.go
@@ -18,18 +18,33 @@
 	"android/soong/android"
 )
 
+// snapshotLibraryInterface is an interface for libraries captured to VNDK / vendor snapshots.
+type snapshotLibraryInterface interface {
+	libraryInterface
+
+	// collectHeadersForSnapshot is called in GenerateAndroidBuildActions for snapshot aware
+	// modules (See isSnapshotAware below).
+	// This function should gather all headers needed for snapshot.
+	collectHeadersForSnapshot(ctx android.ModuleContext, deps PathDeps)
+
+	// snapshotHeaders should return collected headers by collectHeadersForSnapshot.
+	// Calling snapshotHeaders before collectHeadersForSnapshot is an error.
+	snapshotHeaders() android.Paths
+}
+
 func (mod *Module) ExcludeFromVendorSnapshot() bool {
-	// TODO Rust does not yet support snapshotting
-	return false
+	return Bool(mod.Properties.Exclude_from_vendor_snapshot)
 }
 
 func (mod *Module) ExcludeFromRecoverySnapshot() bool {
-	// TODO Rust does not yet support snapshotting
-	return false
+	return Bool(mod.Properties.Exclude_from_recovery_snapshot)
 }
 
 func (mod *Module) IsSnapshotLibrary() bool {
-	// TODO Rust does not yet support snapshotting
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		// Rust-native dylibs are not snapshot supported yet. Only snapshot the rlib-std variants of rlibs.
+		return lib.shared() || lib.static() || (lib.rlib() && lib.rlibStd())
+	}
 	return false
 }
 
@@ -39,8 +54,11 @@
 }
 
 func (mod *Module) SnapshotSharedLibs() []string {
-	// TODO Rust does not yet support snapshotting
-	return []string{}
+	return mod.Properties.SnapshotSharedLibs
+}
+
+func (mod *Module) SnapshotStaticLibs() []string {
+	return mod.Properties.SnapshotStaticLibs
 }
 
 func (mod *Module) Symlinks() []string {
@@ -49,6 +67,8 @@
 }
 
 func (m *Module) SnapshotHeaders() android.Paths {
-	// TODO Rust does not yet support snapshotting
+	if l, ok := m.compiler.(snapshotLibraryInterface); ok {
+		return l.snapshotHeaders()
+	}
 	return android.Paths{}
 }
diff --git a/rust/test.go b/rust/test.go
index e95b47c..250b765 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -18,6 +18,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/tradefed"
 )
 
@@ -49,6 +50,12 @@
 	// the test
 	Data []string `android:"path,arch_variant"`
 
+	// list of shared library modules that should be installed alongside the test
+	Data_libs []string `android:"arch_variant"`
+
+	// list of binary modules that should be installed alongside the test
+	Data_bins []string `android:"arch_variant"`
+
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -59,6 +66,10 @@
 
 	// Test options.
 	Test_options TestOptions
+
+	// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
+	// with root permission.
+	Require_root *bool
 }
 
 // A test module is a binary module with extra --test compiler flag
@@ -109,15 +120,56 @@
 }
 
 func (test *testDecorator) install(ctx ModuleContext) {
+	testInstallBase := "/data/local/tests/unrestricted"
+	if ctx.RustModule().InVendor() || ctx.RustModule().UseVndk() {
+		testInstallBase = "/data/local/tests/vendor"
+	}
+
+	var configs []tradefed.Config
+	if Bool(test.Properties.Require_root) {
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
+	} else {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "force-root", Value: "false"})
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
+	}
+
 	test.testConfig = tradefed.AutoGenRustTestConfig(ctx,
 		test.Properties.Test_config,
 		test.Properties.Test_config_template,
 		test.Properties.Test_suites,
-		nil,
-		test.Properties.Auto_gen_config)
+		configs,
+		test.Properties.Auto_gen_config,
+		testInstallBase)
 
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
 
+	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(cc.LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_lib %q is not a linkable module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+
+	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		linkableDep, ok := dep.(cc.LinkableInterface)
+		if !ok {
+			ctx.ModuleErrorf("data_bin %q is not a linkable module", depName)
+		}
+		if linkableDep.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+		}
+	})
+
 	for _, dataSrcPath := range dataSrcPaths {
 		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
 	}
@@ -158,6 +210,12 @@
 
 func RustTestFactory() android.Module {
 	module, _ := NewRustTest(android.HostAndDeviceSupported)
+
+	// NewRustTest will set MultilibBoth true, however the host variant
+	// cannot produce the non-primary target. Therefore, add the
+	// rustTestHostMultilib load hook to set MultilibFirst for the
+	// host target.
+	android.AddLoadHook(module, rustTestHostMultilib)
 	return module.Init()
 }
 
@@ -175,5 +233,25 @@
 
 	deps.Rustlibs = append(deps.Rustlibs, "libtest")
 
+	deps.DataLibs = append(deps.DataLibs, test.Properties.Data_libs...)
+	deps.DataBins = append(deps.DataBins, test.Properties.Data_bins...)
+
 	return deps
 }
+
+func (test *testDecorator) testBinary() bool {
+	return true
+}
+
+func rustTestHostMultilib(ctx android.LoadHookContext) {
+	type props struct {
+		Target struct {
+			Host struct {
+				Compile_multilib *string
+			}
+		}
+	}
+	p := &props{}
+	p.Target.Host.Compile_multilib = proptools.StringPtr("first")
+	ctx.AppendProperties(p)
+}
diff --git a/rust/test_test.go b/rust/test_test.go
index 892761a..1124176 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -74,3 +74,129 @@
 		t.Errorf("Device rust_test module 'my_test' does not link libstd as an rlib")
 	}
 }
+
+func TestDataLibs(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "test_lib",
+			srcs: ["test_lib.cpp"],
+		}
+
+		rust_binary {
+			name: "rusty",
+			srcs: ["foo.rs"],
+			compile_multilib: "both",
+		}
+
+		rust_ffi {
+			name: "librust_test_lib",
+			crate_name: "rust_test_lib",
+			srcs: ["test_lib.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_test {
+			name: "main_test",
+			srcs: ["foo.rs"],
+			data_libs: ["test_lib"],
+			data_bins: ["rusty"],
+		}
+ `
+
+	ctx := testRust(t, bp)
+
+	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testBinary := module.(*Module).compiler.(*testDecorator)
+	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
+	}
+	if len(outputFiles) != 1 {
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
+	}
+	if len(testBinary.dataPaths()) != 2 {
+		t.Fatalf("expected exactly two test data files. test data files: [%s]", testBinary.dataPaths())
+	}
+
+	outputPath := outputFiles[0].String()
+	dataLibraryPath := testBinary.dataPaths()[0].SrcPath.String()
+	dataBinaryPath := testBinary.dataPaths()[1].SrcPath.String()
+
+	if !strings.HasSuffix(outputPath, "/main_test") {
+		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
+	}
+	if !strings.HasSuffix(dataLibraryPath, "/test_lib.so") {
+		t.Errorf("expected test data file to be 'test_lib.so', but was '%s'", dataLibraryPath)
+	}
+	if !strings.HasSuffix(dataBinaryPath, "/rusty") {
+		t.Errorf("expected test data file to be 'test_lib.so', but was '%s'", dataBinaryPath)
+	}
+}
+
+func TestDataLibsRelativeInstallPath(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "test_lib",
+			srcs: ["test_lib.cpp"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_ffi {
+			name: "librust_test_lib",
+			crate_name: "rust_test_lib",
+			srcs: ["test_lib.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_binary {
+			name: "rusty",
+			srcs: ["foo.rs"],
+			relative_install_path: "foo/bar/baz",
+			compile_multilib: "64",
+		}
+
+		rust_test {
+			name: "main_test",
+			srcs: ["foo.rs"],
+			data_libs: ["test_lib", "librust_test_lib"],
+			data_bins: ["rusty"],
+			compile_multilib: "64",
+		}
+ `
+
+	ctx := testRust(t, bp)
+	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testBinary := module.(*Module).compiler.(*testDecorator)
+	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
+	}
+	if len(outputFiles) != 1 {
+		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
+	}
+	if len(testBinary.dataPaths()) != 3 {
+		t.Fatalf("expected exactly two test data files. test data files: [%s]", testBinary.dataPaths())
+	}
+
+	outputPath := outputFiles[0].String()
+
+	if !strings.HasSuffix(outputPath, "/main_test") {
+		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
+	}
+	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
+	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][1], ":librust_test_lib.so:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:librust_test_lib.so:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][1])
+	}
+	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][2], ":rusty:foo/bar/baz") {
+		t.Errorf("expected LOCAL_TEST_DATA to end with `:rusty:foo/bar/baz`,"+
+			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][2])
+	}
+}
diff --git a/rust/testing.go b/rust/testing.go
index 1e0c5ab..cb98bed 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -45,77 +45,22 @@
 	PrepareForTestWithRustDefaultModules,
 )
 
+var PrepareForTestWithRustIncludeVndk = android.GroupFixturePreparers(
+	PrepareForIntegrationTestWithRust,
+	cc.PrepareForTestWithCcIncludeVndk,
+)
+
 func GatherRequiredDepsForTest() string {
 	bp := `
 		rust_prebuilt_library {
-				name: "libstd_x86_64-unknown-linux-gnu",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_x86_64-unknown-linux-gnu",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libstd_i686-unknown-linux-gnu",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_i686-unknown-linux-gnu",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libstd_x86_64-apple-darwin",
-                                crate_name: "std",
-                                rlib: {
-                                    srcs: ["libstd.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libstd.so"],
-                                },
-				host_supported: true,
-				sysroot: true,
-		}
-		rust_prebuilt_library {
-				name: "libtest_x86_64-apple-darwin",
-                                crate_name: "test",
-                                rlib: {
-                                    srcs: ["libtest.rlib"],
-                                },
-                                dylib: {
-                                    srcs: ["libtest.so"],
-                                },
+				name: "libstd",
+				crate_name: "std",
+				rlib: {
+					srcs: ["libstd.rlib"],
+				},
+				dylib: {
+					srcs: ["libstd.so"],
+				},
 				host_supported: true,
 				sysroot: true,
 		}
@@ -130,6 +75,10 @@
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
+			recovery_available: true,
+			llndk: {
+				symbol_file: "liblog.map.txt",
+			},
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -139,11 +88,16 @@
 			export_include_dirs: ["libprotobuf-cpp-full-includes"],
 		}
 		cc_library {
-			name: "libclang_rt.asan-aarch64-android",
+			name: "libclang_rt.asan",
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
-			export_include_dirs: ["libprotobuf-cpp-full-includes"],
+		}
+		cc_library {
+			name: "libclang_rt.hwasan_static",
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
 		}
 		rust_library {
 			name: "libstd",
@@ -153,6 +107,7 @@
 			host_supported: true,
 			vendor_available: true,
 			vendor_ramdisk_available: true,
+			recovery_available: true,
 			native_coverage: false,
 			sysroot: true,
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
@@ -165,6 +120,7 @@
 			host_supported: true,
 			vendor_available: true,
 			vendor_ramdisk_available: true,
+			recovery_available: true,
 			native_coverage: false,
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
@@ -238,4 +194,8 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+	})
+	registerRustSnapshotModules(ctx)
 }
diff --git a/rust/toolchain_library.go b/rust/toolchain_library.go
new file mode 100644
index 0000000..326d529
--- /dev/null
+++ b/rust/toolchain_library.go
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package rust
+
+import (
+	"path"
+
+	"android/soong/android"
+	"android/soong/rust/config"
+)
+
+// This module is used to compile the rust toolchain libraries
+// When RUST_PREBUILTS_VERSION is set, the library will generated
+// from the given Rust version.
+func init() {
+	android.RegisterModuleType("rust_toolchain_library",
+		rustToolchainLibraryFactory)
+	android.RegisterModuleType("rust_toolchain_library_rlib",
+		rustToolchainLibraryRlibFactory)
+	android.RegisterModuleType("rust_toolchain_library_dylib",
+		rustToolchainLibraryDylibFactory)
+}
+
+type toolchainLibraryProperties struct {
+	// path to the toolchain source, relative to the top of the toolchain source
+	Toolchain_src *string `android:"arch_variant"`
+}
+
+type toolchainLibraryDecorator struct {
+	*libraryDecorator
+	Properties toolchainLibraryProperties
+}
+
+// rust_toolchain_library produces all rust variants.
+func rustToolchainLibraryFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyRust()
+
+	return initToolchainLibrary(module, library)
+}
+
+// rust_toolchain_library_dylib produces a dylib.
+func rustToolchainLibraryDylibFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyDylib()
+
+	return initToolchainLibrary(module, library)
+}
+
+// rust_toolchain_library_rlib produces an rlib.
+func rustToolchainLibraryRlibFactory() android.Module {
+	module, library := NewRustLibrary(android.HostAndDeviceSupported)
+	library.BuildOnlyRlib()
+
+	return initToolchainLibrary(module, library)
+}
+
+func initToolchainLibrary(module *Module, library *libraryDecorator) android.Module {
+	toolchainLibrary := &toolchainLibraryDecorator{
+		libraryDecorator: library,
+	}
+	module.compiler = toolchainLibrary
+	module.AddProperties(&toolchainLibrary.Properties)
+	android.AddLoadHook(module, rustSetToolchainSource)
+
+	return module.Init()
+}
+
+func rustSetToolchainSource(ctx android.LoadHookContext) {
+	if toolchainLib, ok := ctx.Module().(*Module).compiler.(*toolchainLibraryDecorator); ok {
+		prefix := "linux-x86/" + GetRustPrebuiltVersion(ctx)
+		newSrcs := []string{path.Join(prefix, android.String(toolchainLib.Properties.Toolchain_src))}
+
+		type props struct {
+			Srcs []string
+		}
+		p := &props{}
+		p.Srcs = newSrcs
+		ctx.AppendProperties(p)
+
+	} else {
+		ctx.ModuleErrorf("Called rustSetToolchainSource on a non-Rust Module.")
+	}
+}
+
+// GetRustPrebuiltVersion returns the RUST_PREBUILTS_VERSION env var, or the default version if it is not defined.
+func GetRustPrebuiltVersion(ctx android.LoadHookContext) string {
+	return ctx.AConfig().GetenvWithDefault("RUST_PREBUILTS_VERSION", config.RustDefaultVersion)
+}
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
new file mode 100644
index 0000000..7be0042
--- /dev/null
+++ b/rust/vendor_snapshot_test.go
@@ -0,0 +1,1372 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+func TestVendorSnapshotCapture(t *testing.T) {
+	bp := `
+	rust_ffi {
+		name: "libffivendor_available",
+		crate_name: "ffivendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_ffi {
+		name: "libffivendor",
+		crate_name: "ffivendor",
+		srcs: ["lib.rs"],
+		vendor: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_library {
+		name: "librustvendor_available",
+		crate_name: "rustvendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_library_rlib {
+		name: "librustvendor",
+		crate_name: "rustvendor",
+		srcs: ["lib.rs"],
+		vendor: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_binary {
+		name: "vendor_available_bin",
+		vendor_available: true,
+		srcs: ["srcs/lib.rs"],
+	}
+
+	rust_binary {
+		name: "vendor_bin",
+		vendor: true,
+		srcs: ["srcs/lib.rs"],
+	}
+    `
+	skipTestIfOsNotSupported(t)
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		rustMockedFiles.AddToFixture(),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = StringPtr("current")
+				variables.Platform_vndk_version = StringPtr("29")
+			},
+		),
+	).RunTestWithBp(t, bp)
+	ctx := result.TestContext
+
+	// Check Vendor snapshot output.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+	var jsonFiles []string
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, only non-VNDK vendor_available modules are captured
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "libffivendor_available.so.json"))
+
+		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
+		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.a", staticDir, staticVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor", "libffivendor.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libffivendor_available.a.json"))
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libffivendor.a.json"))
+
+		// For rlib libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor", "librustvendor.rlib", rlibDir, rlibVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librustvendor.rlib.json"))
+
+		// For binary executables, all vendor:true and vendor_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "vendor_available_bin.json"))
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "vendor_bin.json"))
+		}
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found; #%v", jsonFile, jsonFiles)
+		}
+	}
+
+	// fake snapshot should have all outputs in the normal snapshot.
+	fakeSnapshotSingleton := ctx.SingletonForTests("vendor-fake-snapshot")
+
+	for _, output := range snapshotSingleton.AllOutputs() {
+		fakeOutput := strings.Replace(output, "/vendor-snapshot/", "/fake/vendor-snapshot/", 1)
+		if fakeSnapshotSingleton.MaybeOutput(fakeOutput).Rule == nil {
+			t.Errorf("%q expected but not found", fakeOutput)
+		}
+	}
+}
+
+func TestVendorSnapshotDirected(t *testing.T) {
+	bp := `
+	rust_ffi_shared {
+		name: "libffivendor_available",
+		crate_name: "ffivendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_library {
+		name: "librustvendor_available",
+		crate_name: "rustvendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_ffi_shared {
+		name: "libffivendor_exclude",
+		crate_name: "ffivendor_exclude",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_library {
+		name: "librustvendor_exclude",
+		crate_name: "rustvendor_exclude",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+`
+	ctx := testRustVndk(t, bp)
+	ctx.Config().TestProductVariables.VendorSnapshotModules = make(map[string]bool)
+	ctx.Config().TestProductVariables.VendorSnapshotModules["librustvendor_available"] = true
+	ctx.Config().TestProductVariables.VendorSnapshotModules["libffivendor_available"] = true
+	ctx.Config().TestProductVariables.DirectedVendorSnapshot = true
+
+	// Check Vendor snapshot output.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+
+	var includeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_available.so.json"))
+
+		// Excluded modules. Modules not included in the directed vendor snapshot
+		// are still include as fake modules.
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librustvendor_exclude", "librustvendor_exclude.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "libffivendor_exclude", "libffivendor_exclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_exclude.rlib.json"))
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_exclude.so.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+}
+
+func TestVendorSnapshotExclude(t *testing.T) {
+
+	// This test verifies that the exclude_from_vendor_snapshot property
+	// makes its way from the Android.bp source file into the module data
+	// structure. It also verifies that modules are correctly included or
+	// excluded in the vendor snapshot based on their path (framework or
+	// vendor) and the exclude_from_vendor_snapshot property.
+
+	frameworkBp := `
+		rust_ffi_shared {
+			name: "libinclude",
+			crate_name: "include",
+			srcs: ["include.rs"],
+			vendor_available: true,
+		}
+
+		rust_ffi_shared {
+			name: "libexclude",
+			crate_name: "exclude",
+			srcs: ["exclude.rs"],
+			vendor: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_ffi_shared {
+			name: "libavailable_exclude",
+			crate_name: "available_exclude",
+			srcs: ["lib.rs"],
+			vendor_available: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_library {
+			name: "librust_include",
+			crate_name: "rust_include",
+			srcs: ["include.rs"],
+			vendor_available: true,
+		}
+
+		rust_library_rlib {
+			name: "librust_exclude",
+			crate_name: "rust_exclude",
+			srcs: ["exclude.rs"],
+			vendor: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_library {
+			name: "librust_available_exclude",
+			crate_name: "rust_available_exclude",
+			srcs: ["lib.rs"],
+			vendor_available: true,
+			exclude_from_vendor_snapshot: true,
+		}
+	`
+
+	mockFS := map[string][]byte{
+		"framework/Android.bp": []byte(frameworkBp),
+		"framework/include.rs": nil,
+		"framework/exclude.rs": nil,
+	}
+
+	ctx := testRustVndkFs(t, "", mockFS)
+
+	// Test an include and exclude framework module.
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false, sharedVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true, sharedVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true, sharedVendorVariant)
+
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_include", false, rlibVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_exclude", true, rlibVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_available_exclude", true, rlibVendorVariant)
+
+	// Verify the content of the vendor snapshot.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+
+	var includeJsonFiles []string
+	var excludeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librust_include", "librust_include.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librust_include.rlib.json"))
+
+		// Excluded modules
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_exclude", "librust_exclude.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_exclude.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_available_exclude", "librust_available_exclude.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_available_exclude.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+
+	// Verify that each json file for an excluded module has no rule.
+	for _, jsonFile := range excludeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
+			t.Errorf("exclude json file %q found", jsonFile)
+		}
+	}
+}
+
+func TestVendorSnapshotUse(t *testing.T) {
+	frameworkBp := `
+	cc_library {
+		name: "libvndk",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "libvendor",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	cc_library {
+		name: "libvendor_available",
+		vendor_available: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	cc_library {
+		name: "lib32",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "32",
+	}
+
+	cc_library {
+		name: "lib64",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "64",
+	}
+
+	rust_binary {
+		name: "bin",
+		vendor: true,
+		srcs: ["bin.rs"],
+	}
+
+	rust_binary {
+		name: "bin32",
+		vendor: true,
+		compile_multilib: "32",
+		srcs: ["bin.rs"],
+	}
+`
+
+	vndkBp := `
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm64",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm64: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+
+	// old snapshot module which has to be ignored
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "26",
+		target_arch: "arm64",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm64: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+`
+
+	vendorProprietaryBp := `
+	cc_library {
+		name: "libvendor_without_snapshot",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	rust_library {
+		name: "librust_vendor_available",
+		crate_name: "rust_vendor",
+		vendor_available: true,
+		srcs: ["client.rs"],
+	}
+
+	rust_ffi_shared {
+		name: "libclient",
+		crate_name: "client",
+		vendor: true,
+		shared_libs: ["libvndk", "libvendor_available"],
+		static_libs: ["libvendor", "libvendor_without_snapshot"],
+		rustlibs: ["librust_vendor_available"],
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
+		srcs: ["client.rs"],
+	}
+
+	rust_library_rlib {
+		name: "libclient_rust",
+		crate_name: "client_rust",
+		vendor: true,
+		shared_libs: ["libvndk", "libvendor_available"],
+		static_libs: ["libvendor", "libvendor_without_snapshot"],
+		rustlibs: ["librust_vendor_available"],
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
+		srcs: ["client.rs"],
+	}
+
+	rust_binary {
+		name: "bin_without_snapshot",
+		vendor: true,
+		static_libs: ["libvndk"],
+		srcs: ["bin.rs"],
+		rustlibs: ["librust_vendor_available"],
+	}
+
+	vendor_snapshot {
+		name: "vendor_snapshot",
+		version: "30",
+		arch: {
+			arm64: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvndk",
+					"libclang_rt.builtins",
+					"note_memtag_heap_sync",
+				],
+				shared_libs: [
+					"libvendor_available",
+					"lib64",
+				],
+				rlibs: [
+					"libstd",
+					"librust_vendor_available",
+				],
+				binaries: [
+					"bin",
+				],
+                objects: [
+				    "crtend_so",
+					"crtbegin_so",
+					"crtbegin_dynamic",
+					"crtend_android"
+				],
+			},
+			arm: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvndk",
+					"libclang_rt.builtins",
+				],
+				shared_libs: [
+					"libvendor_available",
+					"lib32",
+				],
+				rlibs: [
+					"libstd",
+					"librust_vendor_available",
+				],
+				binaries: [
+					"bin32",
+				],
+                objects: [
+				    "crtend_so",
+					"crtbegin_so",
+					"crtbegin_dynamic",
+					"crtend_android"
+				],
+
+			},
+		}
+	}
+
+	vendor_snapshot_object {
+		name: "crtend_so",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtend_so.o",
+			},
+			arm: {
+				src: "crtend_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtbegin_so",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtbegin_so.o",
+			},
+			arm: {
+				src: "crtbegin_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_rlib {
+		name: "libstd",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		sysroot: true,
+		arch: {
+			arm64: {
+				src: "libstd.rlib",
+			},
+			arm: {
+				src: "libstd.rlib",
+			},
+		},
+	}
+
+	vendor_snapshot_rlib {
+		name: "librust_vendor_available",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "librust_vendor_available.rlib",
+			},
+			arm: {
+				src: "librust_vendor_available.rlib",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtend_android",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtend_so.o",
+			},
+			arm: {
+				src: "crtend_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtbegin_dynamic",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtbegin_so.o",
+			},
+			arm: {
+				src: "crtbegin_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvndk.a",
+			},
+			arm: {
+				src: "libvndk.a",
+			},
+		},
+		shared_libs: ["libvndk"],
+		export_shared_lib_headers: ["libvndk"],
+	}
+
+	vendor_snapshot_static {
+		name: "libclang_rt.builtins",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "libclang_rt.builtins-arm-android.a",
+			},
+			arm64: {
+				src: "libclang_rt.builtins-aarch64-android.a",
+			},
+		},
+    }
+
+	vendor_snapshot_shared {
+		name: "lib32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.so",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib64",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.so",
+			},
+		},
+	}
+	vendor_snapshot_shared {
+		name: "liblog",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "liblog.so",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libvendor",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvendor.a",
+				export_include_dirs: ["include/libvendor"],
+			},
+			arm: {
+				src: "libvendor.a",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "libvendor_available",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+			arm: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	vendor_snapshot_binary {
+		name: "bin32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "bin32",
+			},
+		},
+	}
+
+	// Test sanitizers use the snapshot libraries
+	rust_binary {
+		name: "memtag_binary",
+		srcs: ["vendor/bin.rs"],
+		vendor: true,
+		compile_multilib: "64",
+		sanitize: {
+			memtag_heap: true,
+			diag: {
+				memtag_heap: true,
+			}
+		},
+	}
+
+	// old snapshot module which has to be ignored
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "26",
+		target_arch: "arm64",
+		compile_multilib: "first",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "30",
+		target_arch: "arm",
+		compile_multilib: "first",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "note_memtag_heap_sync",
+		vendor: true,
+		target_arch: "arm64",
+		version: "30",
+		arch: {
+			arm64: {
+				src: "note_memtag_heap_sync.a",
+			},
+		},
+	}
+
+`
+
+	mockFS := android.MockFS{
+		"framework/Android.bp":                          []byte(frameworkBp),
+		"framework/bin.rs":                              nil,
+		"note_memtag_heap_sync.a":                       nil,
+		"vendor/Android.bp":                             []byte(vendorProprietaryBp),
+		"vendor/bin":                                    nil,
+		"vendor/bin32":                                  nil,
+		"vendor/bin.rs":                                 nil,
+		"vendor/client.rs":                              nil,
+		"vendor/include/libvndk/a.h":                    nil,
+		"vendor/include/libvendor/b.h":                  nil,
+		"vendor/libvndk.a":                              nil,
+		"vendor/libvendor.a":                            nil,
+		"vendor/libvendor.so":                           nil,
+		"vendor/lib32.so":                               nil,
+		"vendor/lib64.so":                               nil,
+		"vendor/liblog.so":                              nil,
+		"vendor/libstd.rlib":                            nil,
+		"vendor/librust_vendor_available.rlib":          nil,
+		"vendor/crtbegin_so.o":                          nil,
+		"vendor/crtend_so.o":                            nil,
+		"vendor/libclang_rt.builtins-aarch64-android.a": nil,
+		"vendor/libclang_rt.builtins-arm-android.a":     nil,
+		"vndk/Android.bp":                               []byte(vndkBp),
+		"vndk/include/libvndk/a.h":                      nil,
+		"vndk/libvndk.so":                               nil,
+	}
+
+	sharedVariant := "android_vendor.30_arm64_armv8-a_shared"
+	rlibVariant := "android_vendor.30_arm64_armv8-a_rlib_rlib-std"
+	staticVariant := "android_vendor.30_arm64_armv8-a_static"
+	binaryVariant := "android_vendor.30_arm64_armv8-a"
+
+	shared32Variant := "android_vendor.30_arm_armv7-a-neon_shared"
+	binary32Variant := "android_vendor.30_arm_armv7-a-neon"
+
+	ctx := testRustVndkFsVersions(t, "", mockFS, "30", "current", "31")
+
+	// libclient uses libvndk.vndk.30.arm64, libvendor.vendor_static.30.arm64, libvendor_without_snapshot
+	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("rustc").Args["linkFlags"]
+	for _, input := range [][]string{
+		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
+		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
+		[]string{staticVariant, "libvendor_without_snapshot"},
+	} {
+		outputPaths := cc.GetOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
+		if !strings.Contains(libclientLdFlags, outputPaths[0].String()) {
+			t.Errorf("libflags for libclient must contain %#v, but was %#v", outputPaths[0], libclientLdFlags)
+		}
+	}
+
+	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkStaticLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkStaticLibs
+	if g, w := libclientAndroidMkStaticLibs, []string{"libvendor", "libvendor_without_snapshot", "libclang_rt.builtins.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient AndroidMkStaticLibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkRlibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkRlibs
+	if g, w := libclientAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkDylibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkDylibs
+	if len(libclientAndroidMkDylibs) > 0 {
+		t.Errorf("wanted libclient libclientAndroidMkDylibs [], got %q", libclientAndroidMkDylibs)
+	}
+
+	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	libclientRustAndroidMkRlibs := ctx.ModuleForTests("libclient_rust", rlibVariant).Module().(*Module).Properties.AndroidMkRlibs
+	if g, w := libclientRustAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
+	}
+
+	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("rustc").Args["linkFlags"]
+	libVndkStaticOutputPaths := cc.GetOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.30.arm64"})
+	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
+		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
+			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
+	}
+
+	// bin is installed by bin.vendor_binary.30.arm64
+	ctx.ModuleForTests("bin.vendor_binary.30.arm64", binaryVariant).Output("bin")
+
+	// bin32 is installed by bin32.vendor_binary.30.arm64
+	ctx.ModuleForTests("bin32.vendor_binary.30.arm64", binary32Variant).Output("bin32")
+
+	// bin_without_snapshot is installed by bin_without_snapshot
+	ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Output("bin_without_snapshot")
+
+	// libvendor, libvendor_available and bin don't have vendor.30 variant
+	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
+	if android.InList(sharedVariant, libvendorVariants) {
+		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
+	}
+
+	libvendorAvailableVariants := ctx.ModuleVariantsForTests("libvendor_available")
+	if android.InList(sharedVariant, libvendorAvailableVariants) {
+		t.Errorf("libvendor_available must not have variant %#v, but it does", sharedVariant)
+	}
+
+	binVariants := ctx.ModuleVariantsForTests("bin")
+	if android.InList(binaryVariant, binVariants) {
+		t.Errorf("bin must not have variant %#v, but it does", sharedVariant)
+	}
+
+	memtagStaticLibs := ctx.ModuleForTests("memtag_binary", "android_vendor.30_arm64_armv8-a").Module().(*Module).Properties.AndroidMkStaticLibs
+	if g, w := memtagStaticLibs, []string{"libclang_rt.builtins.vendor", "note_memtag_heap_sync.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted memtag_binary AndroidMkStaticLibs %q, got %q", w, g)
+	}
+}
+
+func TestRecoverySnapshotCapture(t *testing.T) {
+	bp := `
+	rust_ffi {
+		name: "librecovery",
+		recovery: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery",
+	}
+
+	rust_ffi {
+		name: "librecovery_available",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_available",
+	}
+
+	rust_library_rlib {
+		name: "librecovery_rlib",
+		recovery: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_rlib",
+	}
+
+	rust_library_rlib {
+		name: "librecovery_available_rlib",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+		crate_name: "recovery_available_rlib",
+	}
+
+	rust_binary {
+		name: "recovery_bin",
+		recovery: true,
+		srcs: ["foo.rs"],
+	}
+
+	rust_binary {
+		name: "recovery_available_bin",
+		recovery_available: true,
+		srcs: ["foo.rs"],
+	}
+
+`
+	// Check Recovery snapshot output.
+
+	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "", "29", "current")
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var jsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, all recovery:true and recovery_available modules are captured.
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "librecovery.so.json"),
+			filepath.Join(sharedDir, "librecovery_available.so.json"))
+
+		// For static libraries, all recovery:true and recovery_available modules are captured.
+		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "librecovery.a.json"),
+			filepath.Join(staticDir, "librecovery_available.a.json"))
+
+		// For rlib libraries, all recovery:true and recovery_available modules are captured.
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librecovery_rlib.rlib.json"),
+			filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))
+
+		// For binary executables, all recovery:true and recovery_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "recovery_bin.json"),
+				filepath.Join(binaryDir, "recovery_available_bin.json"))
+		}
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found", jsonFile)
+		}
+	}
+}
+
+func TestRecoverySnapshotExclude(t *testing.T) {
+	// This test verifies that the exclude_from_recovery_snapshot property
+	// makes its way from the Android.bp source file into the module data
+	// structure. It also verifies that modules are correctly included or
+	// excluded in the recovery snapshot based on their path (framework or
+	// vendor) and the exclude_from_recovery_snapshot property.
+
+	frameworkBp := `
+		rust_ffi_shared {
+			name: "libinclude",
+			srcs: ["src/include.rs"],
+			recovery_available: true,
+			crate_name: "include",
+		}
+		rust_ffi_shared {
+			name: "libexclude",
+			srcs: ["src/exclude.rs"],
+			recovery: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "exclude",
+		}
+		rust_ffi_shared {
+			name: "libavailable_exclude",
+			srcs: ["src/exclude.rs"],
+			recovery_available: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "available_exclude",
+		}
+		rust_library_rlib {
+			name: "libinclude_rlib",
+			srcs: ["src/include.rs"],
+			recovery_available: true,
+			crate_name: "include_rlib",
+		}
+		rust_library_rlib {
+			name: "libexclude_rlib",
+			srcs: ["src/exclude.rs"],
+			recovery: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "exclude_rlib",
+		}
+		rust_library_rlib {
+			name: "libavailable_exclude_rlib",
+			srcs: ["src/exclude.rs"],
+			recovery_available: true,
+			exclude_from_recovery_snapshot: true,
+			crate_name: "available_exclude_rlib",
+		}
+	`
+
+	vendorProprietaryBp := `
+		rust_ffi_shared {
+			name: "librecovery",
+			srcs: ["recovery.rs"],
+			recovery: true,
+			crate_name: "recovery",
+		}
+		rust_library_rlib {
+			name: "librecovery_rlib",
+			srcs: ["recovery.rs"],
+			recovery: true,
+			crate_name: "recovery_rlib",
+		}
+	`
+
+	mockFS := map[string][]byte{
+		"framework/Android.bp": []byte(frameworkBp),
+		"framework/include.rs": nil,
+		"framework/exclude.rs": nil,
+		"device/Android.bp":    []byte(vendorProprietaryBp),
+		"device/recovery.rs":   nil,
+	}
+
+	ctx := testRustRecoveryFsVersions(t, "", mockFS, "", "29", "current")
+
+	// Test an include and exclude framework module.
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude", false, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude", true, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude", true, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libinclude_rlib", false, rlibRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libexclude_rlib", true, rlibRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "libavailable_exclude_rlib", true, rlibRecoveryVariant)
+
+	// A recovery module is excluded, but by its path not the exclude_from_recovery_snapshot property
+	// ('device/' and 'vendor/' are default excluded). See snapshot/recovery_snapshot.go for more detail.
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery", false, sharedRecoveryVariant)
+	cc.AssertExcludeFromRecoverySnapshotIs(t, ctx, "librecovery_rlib", false, rlibRecoveryVariant)
+
+	// Verify the content of the recovery snapshot.
+
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var includeJsonFiles []string
+	var excludeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude_rlib", "libinclude_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "libinclude_rlib.rlib.json"))
+
+		// Excluded modules
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude_rlib", "libexclude_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libexclude_rlib.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude_rlib", "libavailable_exclude_rlib.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "libavailable_exclude_rlib.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+
+	// Verify that each json file for an excluded module has no rule.
+	for _, jsonFile := range excludeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
+			t.Errorf("exclude json file %q found", jsonFile)
+		}
+	}
+}
+
+func TestRecoverySnapshotDirected(t *testing.T) {
+	bp := `
+	rust_ffi_shared {
+		name: "librecovery",
+		recovery: true,
+		crate_name: "recovery",
+		srcs: ["foo.rs"],
+	}
+
+	rust_ffi_shared {
+		name: "librecovery_available",
+		recovery_available: true,
+		crate_name: "recovery_available",
+		srcs: ["foo.rs"],
+	}
+
+	rust_library_rlib {
+		name: "librecovery_rlib",
+		recovery: true,
+		crate_name: "recovery",
+		srcs: ["foo.rs"],
+	}
+
+	rust_library_rlib {
+		name: "librecovery_available_rlib",
+		recovery_available: true,
+		crate_name: "recovery_available",
+		srcs: ["foo.rs"],
+	}
+
+	/* TODO: Uncomment when Rust supports the "prefer" property for prebuilts
+	rust_library_rlib {
+		name: "libfoo_rlib",
+		recovery: true,
+		crate_name: "foo",
+	}
+
+	rust_prebuilt_rlib {
+		name: "libfoo_rlib",
+		recovery: true,
+		prefer: true,
+		srcs: ["libfoo.rlib"],
+		crate_name: "foo",
+	}
+	*/
+`
+	ctx := testRustRecoveryFsVersions(t, bp, rustMockedFiles, "current", "29", "current")
+	ctx.Config().TestProductVariables.RecoverySnapshotModules = make(map[string]bool)
+	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery"] = true
+	ctx.Config().TestProductVariables.RecoverySnapshotModules["librecovery_rlib"] = true
+	ctx.Config().TestProductVariables.DirectedRecoverySnapshot = true
+
+	// Check recovery snapshot output.
+	snapshotDir := "recovery-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
+
+	var includeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_recovery_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_rlib", "librecovery_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_rlib.rlib.json"))
+
+		// TODO: When Rust supports the "prefer" property for prebuilts, perform this check.
+		/*
+			// Check that snapshot captures "prefer: true" prebuilt
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo_rlib", "libfoo_rlib.rlib", rlibDir, rlibVariant)
+			includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo_rlib.rlib.json"))
+		*/
+
+		// Excluded modules. Modules not included in the directed recovery snapshot
+		// are still included as fake modules.
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery_available.so.json"))
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available_rlib", "librecovery_available_rlib.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librecovery_available_rlib.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found, %#v", jsonFile, includeJsonFiles)
+		}
+	}
+}
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 1c02bd0..4773579 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -1,5 +1,6 @@
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
+    default_visibility: ["//build/soong:__subpackages__"],
 }
 
 python_binary_host {
@@ -8,14 +9,6 @@
     srcs: [
         "check_boot_jars/check_boot_jars.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -24,14 +17,6 @@
     srcs: [
         "manifest_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -45,11 +30,8 @@
         "manifest_fixer.py",
     ],
     version: {
-        py2: {
-            enabled: true,
-        },
         py3: {
-            enabled: false,
+            embedded_launcher: true,
         },
     },
     libs: [
@@ -65,14 +47,7 @@
     srcs: [
         "manifest.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
+    visibility: ["//system/apex/apexer:__pkg__"],
 }
 
 python_binary_host {
@@ -81,14 +56,6 @@
     srcs: [
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -101,14 +68,6 @@
         "manifest_check_test.py",
         "manifest_check.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -123,14 +82,6 @@
     srcs: [
         "jsonmodify.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
 }
 
 python_binary_host {
@@ -139,14 +90,6 @@
     srcs: [
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -159,14 +102,6 @@
         "test_config_fixer_test.py",
         "test_config_fixer.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -179,14 +114,6 @@
     srcs: [
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -199,14 +126,6 @@
         "construct_context_test.py",
         "construct_context.py",
     ],
-    version: {
-        py2: {
-            enabled: true,
-        },
-        py3: {
-            enabled: false,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -253,11 +172,7 @@
         "conv_linker_config.py",
     ],
     version: {
-        py2: {
-            enabled: false,
-        },
         py3: {
-            enabled: true,
             embedded_launcher: true,
         },
     },
@@ -265,3 +180,16 @@
         "linker_config_proto",
     ],
 }
+
+python_binary_host {
+    name: "get_clang_version",
+    main: "get_clang_version.py",
+    srcs: [
+        "get_clang_version.py",
+    ],
+}
+
+sh_binary_host {
+    name: "list_image",
+    src: "list_image.sh",
+}
diff --git a/scripts/OWNERS b/scripts/OWNERS
index 2b9c2de..3f4f9c0 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,6 +1,5 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
-per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
-per-file construct_context.py = ngeoffray@google.com,calin@google.com,mathieuc@google.com,skvadrik@google.com
+per-file construct_context.py = ngeoffray@google.com,calin@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
-per-file gen_ndk*.sh = sophiez@google.com, allenhair@google.com
+per-file gen_ndk*.sh,gen_java*.sh = sophiez@google.com, allenhair@google.com
\ No newline at end of file
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index 8a5513e..1a16f7c 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -23,10 +23,10 @@
 export OUT_DIR=${OUT_DIR:-out}
 
 if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
-  # If ${OUT_DIR} has been created without --skip-make, Soong will create an
+  # If ${OUT_DIR} has been created without --soong-only, Soong will create an
   # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
   # expected to be supplied by the .mk files, and that might cause errors in
-  # "m --skip-make" below. We therefore default to a different out dir
+  # "m --soong-only" below. We therefore default to a different out dir
   # location in that case.
   AML_OUT_DIR=out/aml
   echo "Avoiding in-make OUT_DIR '${OUT_DIR}' - building in '${AML_OUT_DIR}' instead"
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
deleted file mode 100755
index 7d49492..0000000
--- a/scripts/build-mainline-modules.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/bin/bash -e
-
-# Non exhaustive list of modules where we want prebuilts. More can be added as
-# needed.
-MAINLINE_MODULES=(
-  com.android.art
-  com.android.art.debug
-  com.android.art.testing
-  com.android.conscrypt
-  com.android.i18n
-  com.android.os.statsd
-  com.android.runtime
-  com.android.tzdata
-)
-
-# List of SDKs and module exports we know of.
-MODULES_SDK_AND_EXPORTS=(
-  art-module-sdk
-  art-module-test-exports
-  conscrypt-module-host-exports
-  conscrypt-module-sdk
-  conscrypt-module-test-exports
-  i18n-module-host-exports
-  i18n-module-sdk
-  i18n-module-test-exports
-  platform-mainline-sdk
-  platform-mainline-test-exports
-  runtime-module-host-exports
-  runtime-module-sdk
-  statsd-module-sdk
-  statsd-module-sdk-for-art
-  tzdata-module-test-exports
-)
-
-# List of libraries installed on the platform that are needed for ART chroot
-# testing.
-PLATFORM_LIBRARIES=(
-  heapprofd_client_api
-  libartpalette-system
-  liblog
-)
-
-# We want to create apex modules for all supported architectures.
-PRODUCTS=(
-  aosp_arm
-  aosp_arm64
-  aosp_x86
-  aosp_x86_64
-)
-
-if [ ! -e "build/make/core/Makefile" ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
-
-echo_and_run() {
-  echo "$*"
-  "$@"
-}
-
-lib_dir() {
-  case $1 in
-    (aosp_arm|aosp_x86) echo "lib";;
-    (aosp_arm64|aosp_x86_64) echo "lib64";;
-  esac
-}
-
-# Make sure this build builds from source, regardless of the default.
-export SOONG_CONFIG_art_module_source_build=true
-
-# This script does not intend to handle compressed APEX
-export OVERRIDE_PRODUCT_COMPRESSED_APEX=false
-
-OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
-DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
-
-for product in "${PRODUCTS[@]}"; do
-  echo_and_run build/soong/soong_ui.bash --make-mode $@ \
-    TARGET_PRODUCT=${product} \
-    ${MAINLINE_MODULES[@]} \
-    ${PLATFORM_LIBRARIES[@]}
-
-  PRODUCT_OUT=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var PRODUCT_OUT)
-  TARGET_ARCH=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT=${product} get_build_var TARGET_ARCH)
-  rm -rf ${DIST_DIR}/${TARGET_ARCH}/
-  mkdir -p ${DIST_DIR}/${TARGET_ARCH}/
-  for module in "${MAINLINE_MODULES[@]}"; do
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/apex/${module}.apex ${DIST_DIR}/${TARGET_ARCH}/
-  done
-  for library in "${PLATFORM_LIBRARIES[@]}"; do
-    libdir=$(lib_dir $product)
-    echo_and_run cp ${PWD}/${PRODUCT_OUT}/system/${libdir}/${library}.so ${DIST_DIR}/${TARGET_ARCH}/
-  done
-done
-
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-# Create multi-archs SDKs in a different out directory. The multi-arch script
-# uses Soong in --skip-make mode which cannot use the same directory as normal
-# mode with make.
-export OUT_DIR=${OUT_DIR}/aml
-echo_and_run build/soong/scripts/build-aml-prebuilts.sh \
-  TARGET_PRODUCT=mainline_sdk ${MODULES_SDK_AND_EXPORTS[@]}
-
-rm -rf ${DIST_DIR}/mainline-sdks
-echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}
diff --git a/scripts/build-ndk-prebuilts.sh b/scripts/build-ndk-prebuilts.sh
index a1fa48d..b57963b 100755
--- a/scripts/build-ndk-prebuilts.sh
+++ b/scripts/build-ndk-prebuilts.sh
@@ -19,54 +19,9 @@
     exit 1
 fi
 
-TOP=$(pwd)
-
-source build/envsetup.sh
-PLATFORM_SDK_VERSION=$(get_build_var PLATFORM_SDK_VERSION)
-PLATFORM_VERSION_ALL_CODENAMES=$(get_build_var PLATFORM_VERSION_ALL_CODENAMES)
-
-# PLATFORM_VERSION_ALL_CODENAMES is a comma separated list like O,P. We need to
-# turn this into ["O","P"].
-PLATFORM_VERSION_ALL_CODENAMES=${PLATFORM_VERSION_ALL_CODENAMES/,/'","'}
-PLATFORM_VERSION_ALL_CODENAMES="[\"${PLATFORM_VERSION_ALL_CODENAMES}\"]"
-
-# Get the list of missing <uses-library> modules and convert it to a JSON array
-# (quote module names, add comma separator and wrap in brackets).
-MISSING_USES_LIBRARIES="$(get_build_var INTERNAL_PLATFORM_MISSING_USES_LIBRARIES)"
-MISSING_USES_LIBRARIES="[$(echo $MISSING_USES_LIBRARIES | sed -e 's/\([^ ]\+\)/\"\1\"/g' -e 's/[ ]\+/, /g')]"
-
-SOONG_OUT=${OUT_DIR}/soong
-SOONG_NDK_OUT=${OUT_DIR}/soong/ndk
-rm -rf ${SOONG_OUT}
-mkdir -p ${SOONG_OUT}
-
-# We only really need to set some of these variables, but soong won't merge this
-# with the defaults, so we need to write out all the defaults with our values
-# added.
-cat > ${SOONG_OUT}/soong.variables << EOF
-{
-    "Platform_sdk_version": ${PLATFORM_SDK_VERSION},
-    "Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
-
-    "DeviceName": "generic_arm64",
-    "HostArch": "x86_64",
-    "Malloc_not_svelte": false,
-    "Safestack": false,
-
-    "Ndk_abis": true,
-
-    "VendorVars": {
-        "art_module": {
-            "source_build": "true"
-        }
-    },
-
-    "MissingUsesLibraries": ${MISSING_USES_LIBRARIES}
-}
-EOF
-m --skip-make ${SOONG_OUT}/ndk.timestamp
+TARGET_PRODUCT=ndk build/soong/soong_ui.bash --make-mode --soong-only ${OUT_DIR}/soong/ndk.timestamp
 
 if [ -n "${DIST_DIR}" ]; then
     mkdir -p ${DIST_DIR} || true
-    tar cjf ${DIST_DIR}/ndk_platform.tar.bz2 -C ${SOONG_OUT} ndk
+    tar cjf ${DIST_DIR}/ndk_platform.tar.bz2 -C ${OUT_DIR}/soong ndk
 fi
diff --git a/scripts/build-rustdocs.sh b/scripts/build-rustdocs.sh
new file mode 100755
index 0000000..fda9688
--- /dev/null
+++ b/scripts/build-rustdocs.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -ex
+
+# 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.
+
+# Builds the platform rustdocs and copies them to the dist directory to provide
+# online docs for each build.
+
+if [ -z "${OUT_DIR}" ]; then
+    echo Must set OUT_DIR
+    exit 1
+fi
+
+source build/envsetup.sh
+m rustdoc
+
+if [ -n "${DIST_DIR}" ]; then
+    mkdir -p ${DIST_DIR}
+    cp ${OUT_DIR}/soong/rustdoc.zip $DIST_DIR
+fi
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index c271211..b711f9d 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -1,101 +1,102 @@
 #!/usr/bin/env python
+"""Check boot jars.
 
+Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> \
+<jar2> ...
 """
-Check boot jars.
-
-Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> <jar2> ...
-"""
+from __future__ import print_function
 import logging
-import os.path
 import re
 import subprocess
 import sys
 import xml.etree.ElementTree
 
-
 # The compiled allow list RE.
 allow_list_re = None
 
 
 def LoadAllowList(filename):
-  """ Load and compile allow list regular expressions from filename.
-  """
-  lines = []
-  with open(filename, 'r') as f:
-    for line in f:
-      line = line.strip()
-      if not line or line.startswith('#'):
-        continue
-      lines.append(line)
-  combined_re = r'^(%s)$' % '|'.join(lines)
-  global allow_list_re
-  try:
-    allow_list_re = re.compile(combined_re)
-  except re.error:
-    logging.exception(
-        'Cannot compile package allow list regular expression: %r',
-        combined_re)
-    allow_list_re = None
-    return False
-  return True
+    """ Load and compile allow list regular expressions from filename."""
+    lines = []
+    with open(filename, 'r') as f:
+        for line in f:
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+            lines.append(line)
+    combined_re = r'^(%s)$' % '|'.join(lines)
+    global allow_list_re #pylint: disable=global-statement
+    try:
+        allow_list_re = re.compile(combined_re)
+    except re.error:
+        logging.exception(
+            'Cannot compile package allow list regular expression: %r',
+            combined_re)
+        allow_list_re = None
+        return False
+    return True
 
 def CheckDexJar(dexdump_path, allow_list_path, jar):
-  """Check a dex jar file.
-  """
-  # Use dexdump to generate the XML representation of the dex jar file.
-  p = subprocess.Popen(args='%s -l xml %s' % (dexdump_path, jar),
-      stdout=subprocess.PIPE, shell=True)
-  stdout, _ = p.communicate()
-  if p.returncode != 0:
-    return False
+    """Check a dex jar file."""
+    # Use dexdump to generate the XML representation of the dex jar file.
+    p = subprocess.Popen(
+        args='%s -l xml %s' % (dexdump_path, jar),
+        stdout=subprocess.PIPE,
+        shell=True)
+    stdout, _ = p.communicate()
+    if p.returncode != 0:
+        return False
 
-  packages = 0
-  try:
-    # TODO(b/172063475) - improve performance
-    root = xml.etree.ElementTree.fromstring(stdout)
-  except xml.etree.ElementTree.ParseError as e:
-    print >> sys.stderr, 'Error processing jar %s - %s' % (jar, e)
-    print >> sys.stderr, stdout
-    return False
-  for package_elt in root.iterfind('package'):
-    packages += 1
-    package_name = package_elt.get('name')
-    if not package_name or not allow_list_re.match(package_name):
-      # Report the name of a class in the package as it is easier to navigate to
-      # the source of a concrete class than to a package which is often required
-      # to investigate this failure.
-      class_name = package_elt[0].get('name')
-      if package_name != "":
-        class_name = package_name + "." + class_name
-      print >> sys.stderr, ('Error: %s contains class file %s, whose package name "%s" is empty or'
-                            ' not in the allow list %s of packages allowed on the bootclasspath.'
-                            % (jar, class_name, package_name, allow_list_path))
-      return False
-  if packages == 0:
-    print >> sys.stderr, ('Error: %s does not contain any packages.' % jar)
-    return False
-  return True
-
+    packages = 0
+    try:
+        # TODO(b/172063475) - improve performance
+        root = xml.etree.ElementTree.fromstring(stdout)
+    except xml.etree.ElementTree.ParseError as e:
+        print('Error processing jar %s - %s' % (jar, e), file=sys.stderr)
+        print(stdout, file=sys.stderr)
+        return False
+    for package_elt in root.iterfind('package'):
+        packages += 1
+        package_name = package_elt.get('name')
+        if not package_name or not allow_list_re.match(package_name):
+            # Report the name of a class in the package as it is easier to
+            # navigate to the source of a concrete class than to a package
+            # which is often required to investigate this failure.
+            class_name = package_elt[0].get('name')
+            if package_name:
+                class_name = package_name + '.' + class_name
+            print((
+                'Error: %s contains class file %s, whose package name "%s" is '
+                'empty or not in the allow list %s of packages allowed on the '
+                'bootclasspath.'
+                % (jar, class_name, package_name, allow_list_path)),
+                  file=sys.stderr)
+            return False
+    if packages == 0:
+        print(('Error: %s does not contain any packages.' % jar),
+              file=sys.stderr)
+        return False
+    return True
 
 def main(argv):
-  if len(argv) < 3:
-    print __doc__
-    return 1
-  dexdump_path = argv[0]
-  allow_list_path = argv[1]
+    if len(argv) < 3:
+        print(__doc__)
+        return 1
+    dexdump_path = argv[0]
+    allow_list_path = argv[1]
 
-  if not LoadAllowList(allow_list_path):
-    return 1
+    if not LoadAllowList(allow_list_path):
+        return 1
 
-  passed = True
-  for jar in argv[2:]:
-    if not CheckDexJar(dexdump_path, allow_list_path, jar):
-      passed = False
-  if not passed:
-    return 1
+    passed = True
+    for jar in argv[2:]:
+        if not CheckDexJar(dexdump_path, allow_list_path, jar):
+            passed = False
+    if not passed:
+        return 1
 
-  return 0
+    return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
+    sys.exit(main(sys.argv[1:]))
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index 18ab427..a02c195 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -69,6 +69,11 @@
 javax\.xml\.transform\.stream
 javax\.xml\.validation
 javax\.xml\.xpath
+jdk\.internal
+jdk\.internal\.math
+jdk\.internal\.misc
+jdk\.internal\.ref
+jdk\.internal\.reflect
 jdk\.internal\.util
 jdk\.internal\.vm\.annotation
 jdk\.net
diff --git a/scripts/construct_context.py b/scripts/construct_context.py
index f0658ba..3f601c3 100755
--- a/scripts/construct_context.py
+++ b/scripts/construct_context.py
@@ -25,57 +25,78 @@
 
 
 def parse_args(args):
-  """Parse commandline arguments."""
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--target-sdk-version', default='', dest='sdk',
-    help='specify target SDK version (as it appears in the manifest)')
-  parser.add_argument('--host-context-for-sdk', dest='host_contexts',
-    action='append', nargs=2, metavar=('sdk','context'),
-    help='specify context on host for a given SDK version or "any" version')
-  parser.add_argument('--target-context-for-sdk', dest='target_contexts',
-    action='append', nargs=2, metavar=('sdk','context'),
-    help='specify context on target for a given SDK version or "any" version')
-  return parser.parse_args(args)
+    """Parse commandline arguments."""
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--target-sdk-version',
+        default='',
+        dest='sdk',
+        help='specify target SDK version (as it appears in the manifest)')
+    parser.add_argument(
+        '--host-context-for-sdk',
+        dest='host_contexts',
+        action='append',
+        nargs=2,
+        metavar=('sdk', 'context'),
+        help='specify context on host for a given SDK version or "any" version')
+    parser.add_argument(
+        '--target-context-for-sdk',
+        dest='target_contexts',
+        action='append',
+        nargs=2,
+        metavar=('sdk', 'context'),
+        help='specify context on target for a given SDK version or "any" '
+        'version'
+    )
+    return parser.parse_args(args)
+
 
 # Special keyword that means that the context should be added to class loader
 # context regardless of the target SDK version.
 any_sdk = 'any'
 
+
 # We assume that the order of context arguments passed to this script is
 # correct (matches the order computed by package manager). It is possible to
 # sort them here, but Soong needs to use deterministic order anyway, so it can
 # as well use the correct order.
 def construct_context(versioned_contexts, target_sdk):
-  context = []
-  for [sdk, ctx] in versioned_contexts:
-    if sdk == any_sdk or compare_version_gt(sdk, target_sdk):
-      context.append(ctx)
-  return context
+    context = []
+    for [sdk, ctx] in versioned_contexts:
+        if sdk == any_sdk or compare_version_gt(sdk, target_sdk):
+            context.append(ctx)
+    return context
+
 
 def construct_contexts(args):
-  host_context = construct_context(args.host_contexts, args.sdk)
-  target_context = construct_context(args.target_contexts, args.sdk)
-  context_sep = '#'
-  return ('class_loader_context_arg=--class-loader-context=PCL[]{%s} ; ' % context_sep.join(host_context) +
-    'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{%s}' % context_sep.join(target_context))
+    host_context = construct_context(args.host_contexts, args.sdk)
+    target_context = construct_context(args.target_contexts, args.sdk)
+    context_sep = '#'
+    return (
+        'class_loader_context_arg=--class-loader-context=PCL[]{%s} ; ' %
+        context_sep.join(host_context) +
+        'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{%s}' #pylint: disable=line-too-long
+        % context_sep.join(target_context))
+
 
 def main():
-  """Program entry point."""
-  try:
-    args = parse_args(sys.argv[1:])
-    if not args.sdk:
-      raise SystemExit('target sdk version is not set')
-    if not args.host_contexts:
-      args.host_contexts = []
-    if not args.target_contexts:
-      args.target_contexts = []
+    """Program entry point."""
+    try:
+        args = parse_args(sys.argv[1:])
+        if not args.sdk:
+            raise SystemExit('target sdk version is not set')
+        if not args.host_contexts:
+            args.host_contexts = []
+        if not args.target_contexts:
+            args.target_contexts = []
 
-    print(construct_contexts(args))
+        print(construct_contexts(args))
 
-  # pylint: disable=broad-except
-  except Exception as err:
-    print('error: ' + str(err), file=sys.stderr)
-    sys.exit(-1)
+    # pylint: disable=broad-except
+    except Exception as err:
+        print('error: ' + str(err), file=sys.stderr)
+        sys.exit(-1)
+
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/scripts/construct_context_test.py b/scripts/construct_context_test.py
index 3b05f90..2ff5ac5 100755
--- a/scripts/construct_context_test.py
+++ b/scripts/construct_context_test.py
@@ -23,53 +23,63 @@
 
 sys.dont_write_bytecode = True
 
+
 def construct_contexts(arglist):
-  args = cc.parse_args(arglist)
-  return cc.construct_contexts(args)
+    args = cc.parse_args(arglist)
+    return cc.construct_contexts(args)
+
 
 contexts = [
-  '--host-context-for-sdk', '28', 'PCL[out/zdir/z.jar]',
-  '--target-context-for-sdk', '28', 'PCL[/system/z.jar]',
-  '--host-context-for-sdk', '29', 'PCL[out/xdir/x.jar]#PCL[out/ydir/y.jar]',
-  '--target-context-for-sdk', '29', 'PCL[/system/x.jar]#PCL[/product/y.jar]',
-  '--host-context-for-sdk', 'any', 'PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]',
-  '--target-context-for-sdk', 'any', 'PCL[/system/a.jar]#PCL[/product/b.jar]',
+    '--host-context-for-sdk',
+    '28',
+    'PCL[out/zdir/z.jar]',
+    '--target-context-for-sdk',
+    '28',
+    'PCL[/system/z.jar]',
+    '--host-context-for-sdk',
+    '29',
+    'PCL[out/xdir/x.jar]#PCL[out/ydir/y.jar]',
+    '--target-context-for-sdk',
+    '29',
+    'PCL[/system/x.jar]#PCL[/product/y.jar]',
+    '--host-context-for-sdk',
+    'any',
+    'PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]',
+    '--target-context-for-sdk',
+    'any',
+    'PCL[/system/a.jar]#PCL[/product/b.jar]',
 ]
 
+#pylint: disable=line-too-long
 class ConstructContextTest(unittest.TestCase):
-  def test_construct_context_28(self):
-    args = ['--target-sdk-version', '28'] + contexts
-    result = construct_contexts(args)
-    expect = ('class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/xdir/x.jar]'
-      '#PCL[out/ydir/y.jar]'
-      '#PCL[out/adir/a.jar]'
-      '#PCL[out/bdir/b.jar]}'
-      ' ; '
-      'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/x.jar]'
-      '#PCL[/product/y.jar]'
-      '#PCL[/system/a.jar]'
-      '#PCL[/product/b.jar]}')
-    self.assertEqual(result, expect)
 
-  def test_construct_context_29(self):
-    args = ['--target-sdk-version', '29'] + contexts
-    result = construct_contexts(args)
-    expect = ('class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]'
-      '#PCL[out/bdir/b.jar]}'
-      ' ; '
-      'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]'
-      '#PCL[/product/b.jar]}')
-    self.assertEqual(result, expect)
+    def test_construct_context_28(self):
+        args = ['--target-sdk-version', '28'] + contexts
+        result = construct_contexts(args)
+        expect = (
+            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/xdir/x.jar]#PCL[out/ydir/y.jar]#PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+            ' ; '
+            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/x.jar]#PCL[/product/y.jar]#PCL[/system/a.jar]#PCL[/product/b.jar]}')
+        self.assertEqual(result, expect)
 
-  def test_construct_context_S(self):
-    args = ['--target-sdk-version', 'S'] + contexts
-    result = construct_contexts(args)
-    expect = ('class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]'
-      '#PCL[out/bdir/b.jar]}'
-      ' ; '
-      'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]'
-      '#PCL[/product/b.jar]}')
-    self.assertEqual(result, expect)
+    def test_construct_context_29(self):
+        args = ['--target-sdk-version', '29'] + contexts
+        result = construct_contexts(args)
+        expect = (
+            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+            ' ; '
+            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]#PCL[/product/b.jar]}')
+        self.assertEqual(result, expect)
+
+    def test_construct_context_S(self):
+        args = ['--target-sdk-version', 'S'] + contexts
+        result = construct_contexts(args)
+        expect = (
+            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+            ' ; '
+            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]#PCL[/product/b.jar]}')
+        self.assertEqual(result, expect)
+#pylint: enable=line-too-long
 
 if __name__ == '__main__':
-  unittest.main(verbosity=2)
+    unittest.main(verbosity=2)
diff --git a/scripts/conv_linker_config.py b/scripts/conv_linker_config.py
index 92f79da..e46efe4 100644
--- a/scripts/conv_linker_config.py
+++ b/scripts/conv_linker_config.py
@@ -20,178 +20,181 @@
 import json
 import os
 
-import linker_config_pb2
+import linker_config_pb2 #pylint: disable=import-error
 from google.protobuf.descriptor import FieldDescriptor
 from google.protobuf.json_format import ParseDict
 from google.protobuf.text_format import MessageToString
 
 
 def Proto(args):
-  json_content = ''
-  with open(args.source) as f:
-    for line in f:
-      if not line.lstrip().startswith('//'):
-        json_content += line
-  obj = json.loads(json_content, object_pairs_hook=collections.OrderedDict)
-  pb = ParseDict(obj, linker_config_pb2.LinkerConfig())
-  with open(args.output, 'wb') as f:
-    f.write(pb.SerializeToString())
+    json_content = ''
+    with open(args.source) as f:
+        for line in f:
+            if not line.lstrip().startswith('//'):
+                json_content += line
+    obj = json.loads(json_content, object_pairs_hook=collections.OrderedDict)
+    pb = ParseDict(obj, linker_config_pb2.LinkerConfig())
+    with open(args.output, 'wb') as f:
+        f.write(pb.SerializeToString())
 
 
 def Print(args):
-  with open(args.source, 'rb') as f:
-    pb = linker_config_pb2.LinkerConfig()
-    pb.ParseFromString(f.read())
-  print(MessageToString(pb))
+    with open(args.source, 'rb') as f:
+        pb = linker_config_pb2.LinkerConfig()
+        pb.ParseFromString(f.read())
+    print(MessageToString(pb))
 
 
 def SystemProvide(args):
-  pb = linker_config_pb2.LinkerConfig()
-  with open(args.source, 'rb') as f:
-    pb.ParseFromString(f.read())
-  libraries = args.value.split()
+    pb = linker_config_pb2.LinkerConfig()
+    with open(args.source, 'rb') as f:
+        pb.ParseFromString(f.read())
+    libraries = args.value.split()
 
-  def IsInLibPath(lib_name):
-    lib_path = os.path.join(args.system, 'lib', lib_name)
-    lib64_path = os.path.join(args.system, 'lib64', lib_name)
-    return os.path.exists(lib_path) or os.path.islink(lib_path) or os.path.exists(lib64_path) or os.path.islink(lib64_path)
+    def IsInLibPath(lib_name):
+        lib_path = os.path.join(args.system, 'lib', lib_name)
+        lib64_path = os.path.join(args.system, 'lib64', lib_name)
+        return os.path.exists(lib_path) or os.path.islink(
+            lib_path) or os.path.exists(lib64_path) or os.path.islink(
+                lib64_path)
 
-  installed_libraries = list(filter(IsInLibPath, libraries))
-  for item in installed_libraries:
-    if item not in getattr(pb, 'provideLibs'):
-      getattr(pb, 'provideLibs').append(item)
-  with open(args.output, 'wb') as f:
-    f.write(pb.SerializeToString())
+    installed_libraries = [lib for lib in libraries if IsInLibPath(lib)]
+    for item in installed_libraries:
+        if item not in getattr(pb, 'provideLibs'):
+            getattr(pb, 'provideLibs').append(item)
+    with open(args.output, 'wb') as f:
+        f.write(pb.SerializeToString())
 
 
 def Append(args):
-  pb = linker_config_pb2.LinkerConfig()
-  with open(args.source, 'rb') as f:
-    pb.ParseFromString(f.read())
+    pb = linker_config_pb2.LinkerConfig()
+    with open(args.source, 'rb') as f:
+        pb.ParseFromString(f.read())
 
-  if getattr(type(pb), args.key).DESCRIPTOR.label == FieldDescriptor.LABEL_REPEATED:
-    for value in args.value.split():
-      getattr(pb, args.key).append(value)
-  else:
-    setattr(pb, args.key, args.value)
+    if getattr(type(pb),
+               args.key).DESCRIPTOR.label == FieldDescriptor.LABEL_REPEATED:
+        for value in args.value.split():
+            getattr(pb, args.key).append(value)
+    else:
+        setattr(pb, args.key, args.value)
 
-  with open(args.output, 'wb') as f:
-    f.write(pb.SerializeToString())
+    with open(args.output, 'wb') as f:
+        f.write(pb.SerializeToString())
+
 
 def Merge(args):
-  pb = linker_config_pb2.LinkerConfig()
-  for other in args.input:
-    with open(other, 'rb') as f:
-      pb.MergeFromString(f.read())
+    pb = linker_config_pb2.LinkerConfig()
+    for other in args.input:
+        with open(other, 'rb') as f:
+            pb.MergeFromString(f.read())
 
-  with open(args.out, 'wb') as f:
-    f.write(pb.SerializeToString())
+    with open(args.out, 'wb') as f:
+        f.write(pb.SerializeToString())
+
 
 def GetArgParser():
-  parser = argparse.ArgumentParser()
-  subparsers = parser.add_subparsers()
+    parser = argparse.ArgumentParser()
+    subparsers = parser.add_subparsers()
 
-  parser_proto = subparsers.add_parser(
-      'proto', help='Convert the input JSON configuration file into protobuf.')
-  parser_proto.add_argument(
-      '-s',
-      '--source',
-      required=True,
-      type=str,
-      help='Source linker configuration file in JSON.')
-  parser_proto.add_argument(
-      '-o',
-      '--output',
-      required=True,
-      type=str,
-      help='Target path to create protobuf file.')
-  parser_proto.set_defaults(func=Proto)
+    parser_proto = subparsers.add_parser(
+        'proto',
+        help='Convert the input JSON configuration file into protobuf.')
+    parser_proto.add_argument(
+        '-s',
+        '--source',
+        required=True,
+        type=str,
+        help='Source linker configuration file in JSON.')
+    parser_proto.add_argument(
+        '-o',
+        '--output',
+        required=True,
+        type=str,
+        help='Target path to create protobuf file.')
+    parser_proto.set_defaults(func=Proto)
 
-  print_proto = subparsers.add_parser(
-      'print', help='Print configuration in human-readable text format.')
-  print_proto.add_argument(
-      '-s',
-      '--source',
-      required=True,
-      type=str,
-      help='Source linker configuration file in protobuf.')
-  print_proto.set_defaults(func=Print)
+    print_proto = subparsers.add_parser(
+        'print', help='Print configuration in human-readable text format.')
+    print_proto.add_argument(
+        '-s',
+        '--source',
+        required=True,
+        type=str,
+        help='Source linker configuration file in protobuf.')
+    print_proto.set_defaults(func=Print)
 
-  system_provide_libs = subparsers.add_parser(
-      'systemprovide', help='Append system provide libraries into the configuration.')
-  system_provide_libs.add_argument(
-      '-s',
-      '--source',
-      required=True,
-      type=str,
-      help='Source linker configuration file in protobuf.')
-  system_provide_libs.add_argument(
-      '-o',
-      '--output',
-      required=True,
-      type=str,
-      help='Target linker configuration file to write in protobuf.')
-  system_provide_libs.add_argument(
-      '--value',
-      required=True,
-      type=str,
-      help='Values of the libraries to append. If there are more than one it should be separated by empty space')
-  system_provide_libs.add_argument(
-      '--system',
-      required=True,
-      type=str,
-      help='Path of the system image.')
-  system_provide_libs.set_defaults(func=SystemProvide)
+    system_provide_libs = subparsers.add_parser(
+        'systemprovide',
+        help='Append system provide libraries into the configuration.')
+    system_provide_libs.add_argument(
+        '-s',
+        '--source',
+        required=True,
+        type=str,
+        help='Source linker configuration file in protobuf.')
+    system_provide_libs.add_argument(
+        '-o',
+        '--output',
+        required=True,
+        type=str,
+        help='Target linker configuration file to write in protobuf.')
+    system_provide_libs.add_argument(
+        '--value',
+        required=True,
+        type=str,
+        help='Values of the libraries to append. If there are more than one '
+        'it should be separated by empty space'
+    )
+    system_provide_libs.add_argument(
+        '--system', required=True, type=str, help='Path of the system image.')
+    system_provide_libs.set_defaults(func=SystemProvide)
 
-  append = subparsers.add_parser(
-      'append', help='Append value(s) to given key.')
-  append.add_argument(
-      '-s',
-      '--source',
-      required=True,
-      type=str,
-      help='Source linker configuration file in protobuf.')
-  append.add_argument(
-      '-o',
-      '--output',
-      required=True,
-      type=str,
-      help='Target linker configuration file to write in protobuf.')
-  append.add_argument(
-      '--key',
-      required=True,
-      type=str,
-      help='.')
-  append.add_argument(
-      '--value',
-      required=True,
-      type=str,
-      help='Values of the libraries to append. If there are more than one it should be separated by empty space')
-  append.set_defaults(func=Append)
+    append = subparsers.add_parser(
+        'append', help='Append value(s) to given key.')
+    append.add_argument(
+        '-s',
+        '--source',
+        required=True,
+        type=str,
+        help='Source linker configuration file in protobuf.')
+    append.add_argument(
+        '-o',
+        '--output',
+        required=True,
+        type=str,
+        help='Target linker configuration file to write in protobuf.')
+    append.add_argument('--key', required=True, type=str, help='.')
+    append.add_argument(
+        '--value',
+        required=True,
+        type=str,
+        help='Values of the libraries to append. If there are more than one'
+        'it should be separated by empty space'
+    )
+    append.set_defaults(func=Append)
 
-  append = subparsers.add_parser(
-      'merge', help='Merge configurations')
-  append.add_argument(
-      '-o',
-      '--out',
-      required=True,
-      type=str,
-      help='Ouptut linker configuration file to write in protobuf.')
-  append.add_argument(
-      '-i',
-      '--input',
-      nargs='+',
-      type=str,
-      help='Linker configuration files to merge.')
-  append.set_defaults(func=Merge)
+    append = subparsers.add_parser('merge', help='Merge configurations')
+    append.add_argument(
+        '-o',
+        '--out',
+        required=True,
+        type=str,
+        help='Output linker configuration file to write in protobuf.')
+    append.add_argument(
+        '-i',
+        '--input',
+        nargs='+',
+        type=str,
+        help='Linker configuration files to merge.')
+    append.set_defaults(func=Merge)
 
-  return parser
+    return parser
 
 
 def main():
-  args = GetArgParser().parse_args()
-  args.func(args)
+    args = GetArgParser().parse_args()
+    args.func(args)
 
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/scripts/diff_build_graphs.sh b/scripts/diff_build_graphs.sh
index 81010f3..8d01124 100755
--- a/scripts/diff_build_graphs.sh
+++ b/scripts/diff_build_graphs.sh
@@ -98,7 +98,7 @@
   # or in case it is affected by some of the changes we're testing
   make blueprint_tools
   # find multiproduct_kati and have it build the ninja files for each product
-  builder="$(echo $OUT_DIR/soong/host/*/bin/multiproduct_kati)"
+  builder="$(echo $OUT_DIR/host/*/bin/multiproduct_kati)"
   BUILD_NUMBER=sample "$builder" $PRODUCTS_ARG --keep --out "$OUT_DIR_TEMP" || true
   echo
 }
diff --git a/scripts/gen_java_usedby_apex.sh b/scripts/gen_java_usedby_apex.sh
new file mode 100755
index 0000000..e398541
--- /dev/null
+++ b/scripts/gen_java_usedby_apex.sh
@@ -0,0 +1,48 @@
+#!/bin/bash -e
+
+# Copyright 2020 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+printHelp() {
+    echo "**************************** Usage Instructions ****************************"
+    echo "This script is used to generate the Mainline modules used-by Java symbols."
+    echo ""
+    echo "To run this script use: ./gen_java_usedby_apex.sh \$BINARY_DEXDEPS_PATH \$OUTPUT_FILE_PATH \$JAR_AND_APK_LIST"
+    echo "For example: If all jar and apk files are '/myJar.jar /myApk.apk' and output write to /myModule.txt then the command would be:"
+    echo "./gen_java_usedby_apex.sh \$BINARY_DEXDEPS_PATH /myModule.txt /myJar.jar /myApk.apk"
+}
+
+genUsedByList() {
+  dexdeps="$1"
+  shift
+  out="$1"
+  shift
+  rm -f "$out"
+  touch "$out"
+  echo "<externals>" >> "$out"
+  for x in "$@"; do
+    "$dexdeps" "$x" >> "$out" || echo "</external>" >> "$out"
+  done
+  echo "</externals>" >> "$out"
+}
+
+if [[ "$1" == "help" ]]
+then
+  printHelp
+elif [[ "$#" -lt 2 ]]
+then
+  echo "Wrong argument length. Expecting at least 2 argument representing dexdeps path, output path, followed by a list of jar or apk files in the Mainline module."
+else
+  genUsedByList "$@"
+fi
\ No newline at end of file
diff --git a/scripts/gen_ndk_backedby_apex.sh b/scripts/gen_ndk_backedby_apex.sh
index 4abaaba..212362e 100755
--- a/scripts/gen_ndk_backedby_apex.sh
+++ b/scripts/gen_ndk_backedby_apex.sh
@@ -21,52 +21,29 @@
 # After the parse function below "dlopen" would be write to the output file.
 printHelp() {
     echo "**************************** Usage Instructions ****************************"
-    echo "This script is used to generate the Mainline modules backed-by NDK symbols."
+    echo "This script is used to generate the native libraries backed by Mainline modules."
     echo ""
-    echo "To run this script use: ./gen_ndk_backed_by_apex.sh \$OUTPUT_FILE_PATH \$NDK_LIB_NAME_LIST \$MODULE_LIB1 \$MODULE_LIB2..."
+    echo "To run this script use: ./gen_ndk_backed_by_apex.sh \$OUTPUT_FILE_PATH \$MODULE_LIB1 \$MODULE_LIB2..."
     echo "For example: If output write to /backedby.txt then the command would be:"
-    echo "./gen_ndk_backed_by_apex.sh /backedby.txt /ndkLibList.txt lib1.so lib2.so"
+    echo "./gen_ndk_backed_by_apex.sh /backedby.txt lib1.so lib2.so"
     echo "If the module1 is backing lib1 then the backedby.txt would contains: "
-    echo "lib1"
+    echo "lib1.so lib2.so"
 }
 
-contains() {
-  val="$1"
-  shift
-  for x in "$@"; do
-    if [ "$x" = "$val" ]; then
-      return 0
-    fi
-  done
-  return 1
-}
-
-
-genBackedByList() {
+genAllBackedByList() {
   out="$1"
   shift
-  ndk_list="$1"
-  shift
   rm -f "$out"
   touch "$out"
-  while IFS= read -r line
-  do
-    soFileName=$(echo "$line" | sed 's/\(.*so\).*/\1/')
-    if [[ ! -z "$soFileName" && "$soFileName" != *"#"* ]]
-    then
-      if contains "$soFileName" "$@"; then
-        echo "$soFileName" >> "$out"
-      fi
-    fi
-  done < "$ndk_list"
+  echo "$@" >> "$out"
 }
 
 if [[ "$1" == "help" ]]
 then
   printHelp
-elif [[ "$#" -lt 2 ]]
+elif [[ "$#" -lt 1 ]]
 then
-  echo "Wrong argument length. Expecting at least 2 argument representing output path, path to ndk library list, followed by a list of libraries in the Mainline module."
+  echo "Wrong argument length. Expecting at least 1 argument representing output path, followed by a list of libraries in the Mainline module."
 else
-  genBackedByList "$@"
+  genAllBackedByList "$@"
 fi
diff --git a/scripts/gen_sorted_bss_symbols.sh b/scripts/gen_sorted_bss_symbols.sh
deleted file mode 100755
index a9b61a1..0000000
--- a/scripts/gen_sorted_bss_symbols.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash -e
-
-# 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.
-
-# Script to generate a symbol ordering file that sorts bss section symbols by
-# their sizes.
-# Inputs:
-#  Environment:
-#   CLANG_BIN: path to the clang bin directory
-#  Arguments:
-#   $1: Input ELF file
-#   $2: Output symbol ordering file
-
-set -o pipefail
-
-${CLANG_BIN}/llvm-nm --size-sort $1 | awk '{if ($2 == "b" || $2 == "B") print $3}' > $2
diff --git a/scripts/generate-notice-files.py b/scripts/generate-notice-files.py
deleted file mode 100755
index 49011b2..0000000
--- a/scripts/generate-notice-files.py
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2012 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.
-"""
-Usage: generate-notice-files --text-output [plain text output file] \
-               --html-output [html output file] \
-               --xml-output [xml output file] \
-               -t [file title] -s [directory of notices]
-
-Generate the Android notice files, including both text and html files.
-
--h to display this usage message and exit.
-"""
-from collections import defaultdict
-import argparse
-import hashlib
-import itertools
-import os
-import os.path
-import re
-import sys
-
-MD5_BLOCKSIZE = 1024 * 1024
-HTML_ESCAPE_TABLE = {
-    "&": "&amp;",
-    '"': "&quot;",
-    "'": "&apos;",
-    ">": "&gt;",
-    "<": "&lt;",
-    }
-
-def hexify(s):
-    return ("%02x"*len(s)) % tuple(map(ord, s))
-
-def md5sum(filename):
-    """Calculate an MD5 of the file given by FILENAME,
-    and return hex digest as a string.
-    Output should be compatible with md5sum command"""
-
-    f = open(filename, "rb")
-    sum = hashlib.md5()
-    while 1:
-        block = f.read(MD5_BLOCKSIZE)
-        if not block:
-            break
-        sum.update(block)
-    f.close()
-    return hexify(sum.digest())
-
-
-def html_escape(text):
-    """Produce entities within text."""
-    return "".join(HTML_ESCAPE_TABLE.get(c,c) for c in text)
-
-HTML_OUTPUT_CSS="""
-<style type="text/css">
-body { padding: 0; font-family: sans-serif; }
-.same-license { background-color: #eeeeee; border-top: 20px solid white; padding: 10px; }
-.label { font-weight: bold; }
-.file-list { margin-left: 1em; color: blue; }
-</style>
-"""
-
-def combine_notice_files_html(file_hash, input_dir, output_filename):
-    """Combine notice files in FILE_HASH and output a HTML version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
-    # Set up a filename to row id table (anchors inside tables don't work in
-    # most browsers, but href's to table row ids do)
-    id_table = {}
-    id_count = 0
-    for value in file_hash:
-        for filename in value:
-             id_table[filename] = id_count
-        id_count += 1
-
-    # Open the output file, and output the header pieces
-    output_file = open(output_filename, "wb")
-
-    print >> output_file, "<html><head>"
-    print >> output_file, HTML_OUTPUT_CSS
-    print >> output_file, '</head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0">'
-
-    # Output our table of contents
-    print >> output_file, '<div class="toc">'
-    print >> output_file, "<ul>"
-
-    # Flatten the list of lists into a single list of filenames
-    sorted_filenames = sorted(itertools.chain.from_iterable(file_hash))
-
-    # Print out a nice table of contents
-    for filename in sorted_filenames:
-        stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<li><a href="#id%d">%s</a></li>' % (id_table.get(filename), stripped_filename)
-
-    print >> output_file, "</ul>"
-    print >> output_file, "</div><!-- table of contents -->"
-    # Output the individual notice file lists
-    print >>output_file, '<table cellpadding="0" cellspacing="0" border="0">'
-    for value in file_hash:
-        print >> output_file, '<tr id="id%d"><td class="same-license">' % id_table.get(value[0])
-        print >> output_file, '<div class="label">Notices for file(s):</div>'
-        print >> output_file, '<div class="file-list">'
-        for filename in value:
-            print >> output_file, "%s <br/>" % (SRC_DIR_STRIP_RE.sub(r"\1", filename))
-        print >> output_file, "</div><!-- file-list -->"
-        print >> output_file
-        print >> output_file, '<pre class="license-text">'
-        print >> output_file, html_escape(open(value[0]).read())
-        print >> output_file, "</pre><!-- license-text -->"
-        print >> output_file, "</td></tr><!-- same-license -->"
-        print >> output_file
-        print >> output_file
-        print >> output_file
-
-    # Finish off the file output
-    print >> output_file, "</table>"
-    print >> output_file, "</body></html>"
-    output_file.close()
-
-def combine_notice_files_text(file_hash, input_dir, output_filename, file_title):
-    """Combine notice files in FILE_HASH and output a text version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-    output_file = open(output_filename, "wb")
-    print >> output_file, file_title
-    for value in file_hash:
-      print >> output_file, "============================================================"
-      print >> output_file, "Notices for file(s):"
-      for filename in value:
-        print >> output_file, SRC_DIR_STRIP_RE.sub(r"\1", filename)
-      print >> output_file, "------------------------------------------------------------"
-      print >> output_file, open(value[0]).read()
-    output_file.close()
-
-def combine_notice_files_xml(files_with_same_hash, input_dir, output_filename):
-    """Combine notice files in FILE_HASH and output a XML version to OUTPUT_FILENAME."""
-
-    SRC_DIR_STRIP_RE = re.compile(input_dir + "(/.*).txt")
-
-    # Set up a filename to row id table (anchors inside tables don't work in
-    # most browsers, but href's to table row ids do)
-    id_table = {}
-    for file_key in files_with_same_hash.keys():
-        for filename in files_with_same_hash[file_key]:
-             id_table[filename] = file_key
-
-    # Open the output file, and output the header pieces
-    output_file = open(output_filename, "wb")
-
-    print >> output_file, '<?xml version="1.0" encoding="utf-8"?>'
-    print >> output_file, "<licenses>"
-
-    # Flatten the list of lists into a single list of filenames
-    sorted_filenames = sorted(id_table.keys())
-
-    # Print out a nice table of contents
-    for filename in sorted_filenames:
-        stripped_filename = SRC_DIR_STRIP_RE.sub(r"\1", filename)
-        print >> output_file, '<file-name contentId="%s">%s</file-name>' % (id_table.get(filename), stripped_filename)
-
-    print >> output_file
-    print >> output_file
-
-    processed_file_keys = []
-    # Output the individual notice file lists
-    for filename in sorted_filenames:
-        file_key = id_table.get(filename)
-        if file_key in processed_file_keys:
-            continue
-        processed_file_keys.append(file_key)
-
-        print >> output_file, '<file-content contentId="%s"><![CDATA[%s]]></file-content>' % (file_key, html_escape(open(filename).read()))
-        print >> output_file
-
-    # Finish off the file output
-    print >> output_file, "</licenses>"
-    output_file.close()
-
-def get_args():
-    parser = argparse.ArgumentParser()
-    parser.add_argument(
-        '--text-output', required=True,
-        help='The text output file path.')
-    parser.add_argument(
-        '--html-output',
-        help='The html output file path.')
-    parser.add_argument(
-        '--xml-output',
-        help='The xml output file path.')
-    parser.add_argument(
-        '-t', '--title', required=True,
-        help='The file title.')
-    parser.add_argument(
-        '-s', '--source-dir', required=True,
-        help='The directory containing notices.')
-    parser.add_argument(
-        '-i', '--included-subdirs', action='append',
-        help='The sub directories which should be included.')
-    parser.add_argument(
-        '-e', '--excluded-subdirs', action='append',
-        help='The sub directories which should be excluded.')
-    return parser.parse_args()
-
-def main(argv):
-    args = get_args()
-
-    txt_output_file = args.text_output
-    html_output_file = args.html_output
-    xml_output_file = args.xml_output
-    file_title = args.title
-    included_subdirs = []
-    excluded_subdirs = []
-    if args.included_subdirs is not None:
-        included_subdirs = args.included_subdirs
-    if args.excluded_subdirs is not None:
-        excluded_subdirs = args.excluded_subdirs
-
-    # Find all the notice files and md5 them
-    input_dir = os.path.normpath(args.source_dir)
-    files_with_same_hash = defaultdict(list)
-    for root, dir, files in os.walk(input_dir):
-        for file in files:
-            matched = True
-            if len(included_subdirs) > 0:
-                matched = False
-                for subdir in included_subdirs:
-                    if (root == (input_dir + '/' + subdir) or
-                        root.startswith(input_dir + '/' + subdir + '/')):
-                        matched = True
-                        break
-            elif len(excluded_subdirs) > 0:
-                for subdir in excluded_subdirs:
-                    if (root == (input_dir + '/' + subdir) or
-                        root.startswith(input_dir + '/' + subdir + '/')):
-                        matched = False
-                        break
-            if matched and file.endswith(".txt"):
-                filename = os.path.join(root, file)
-                file_md5sum = md5sum(filename)
-                files_with_same_hash[file_md5sum].append(filename)
-
-    filesets = [sorted(files_with_same_hash[md5]) for md5 in sorted(files_with_same_hash.keys())]
-
-    combine_notice_files_text(filesets, input_dir, txt_output_file, file_title)
-
-    if html_output_file is not None:
-        combine_notice_files_html(filesets, input_dir, html_output_file)
-
-    if xml_output_file is not None:
-        combine_notice_files_xml(files_with_same_hash, input_dir, xml_output_file)
-
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/scripts/get_clang_version.py b/scripts/get_clang_version.py
new file mode 100755
index 0000000..691c45d
--- /dev/null
+++ b/scripts/get_clang_version.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""A tool to report the current clang version used during build"""
+
+import os
+import re
+import sys
+
+
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".")
+LLVM_PREBUILTS_VERSION = os.environ.get("LLVM_PREBUILTS_VERSION")
+
+def get_clang_prebuilts_version(global_go):
+  if LLVM_PREBUILTS_VERSION:
+    return LLVM_PREBUILTS_VERSION
+
+  # TODO(b/187231324): Get clang version from the json file once it is no longer
+  # hard-coded in global.go
+  if global_go is None:
+      global_go = ANDROID_BUILD_TOP + '/build/soong/cc/config/global.go'
+  with open(global_go) as infile:
+    contents = infile.read()
+
+  regex_rev = r'\tClangDefaultVersion\s+= "(?P<rev>clang-.*)"'
+  match_rev = re.search(regex_rev, contents)
+  if match_rev is None:
+    raise RuntimeError('Parsing clang info failed')
+  return match_rev.group('rev')
+
+
+def main():
+  global_go = sys.argv[1] if len(sys.argv) > 1 else None
+  print(get_clang_prebuilts_version(global_go));
+
+
+if __name__ == '__main__':
+  main()
diff --git a/scripts/get_clang_version_test.py b/scripts/get_clang_version_test.py
new file mode 100644
index 0000000..e57df6c
--- /dev/null
+++ b/scripts/get_clang_version_test.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Unit tests for get_clang_version.py."""
+
+import unittest
+
+import get_clang_version
+
+class GetClangVersionTest(unittest.TestCase):
+  """Unit tests for get_clang_version."""
+
+  def test_get_clang_version(self):
+    """Test parsing of clang prebuilts version."""
+    self.assertIsNotNone(get_clang_version.get_clang_prebuilts_version())
+
+
+if __name__ == '__main__':
+  unittest.main(verbosity=2)
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index 7472f52..07878f9 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -19,6 +19,52 @@
 }
 
 python_binary_host {
+    name: "analyze_bcpf",
+    main: "analyze_bcpf.py",
+    srcs: ["analyze_bcpf.py"],
+    // Make sure that the bpmodify tool is built.
+    data: [":bpmodify"],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
+
+python_test_host {
+    name: "analyze_bcpf_test",
+    main: "analyze_bcpf_test.py",
+    srcs: [
+        "analyze_bcpf.py",
+        "analyze_bcpf_test.py",
+    ],
+    // Make sure that the bpmodify tool is built.
+    data: [":bpmodify"],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_binary_host {
     name: "merge_csv",
     main: "merge_csv.py",
     srcs: ["merge_csv.py"],
@@ -48,10 +94,13 @@
     },
 }
 
-python_binary_host {
-    name: "verify_overlaps",
-    main: "verify_overlaps.py",
-    srcs: ["verify_overlaps.py"],
+python_test_host {
+    name: "generate_hiddenapi_lists_test",
+    main: "generate_hiddenapi_lists_test.py",
+    srcs: [
+        "generate_hiddenapi_lists.py",
+        "generate_hiddenapi_lists_test.py",
+    ],
     version: {
         py2: {
             enabled: false,
@@ -61,4 +110,109 @@
             embedded_launcher: true,
         },
     },
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_library_host {
+    name: "signature_trie",
+    srcs: ["signature_trie.py"],
+}
+
+python_test_host {
+    name: "signature_trie_test",
+    main: "signature_trie_test.py",
+    srcs: ["signature_trie_test.py"],
+    libs: ["signature_trie"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_binary_host {
+    name: "verify_overlaps",
+    main: "verify_overlaps.py",
+    srcs: ["verify_overlaps.py"],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
+
+python_test_host {
+    name: "verify_overlaps_test",
+    main: "verify_overlaps_test.py",
+    srcs: [
+        "verify_overlaps.py",
+        "verify_overlaps_test.py",
+    ],
+    libs: [
+        "signature_trie",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+}
+
+python_binary_host {
+    name: "signature_patterns",
+    main: "signature_patterns.py",
+    srcs: ["signature_patterns.py"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
+
+python_test_host {
+    name: "signature_patterns_test",
+    main: "signature_patterns_test.py",
+    srcs: [
+        "signature_patterns.py",
+        "signature_patterns_test.py",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
 }
diff --git a/scripts/hiddenapi/analyze_bcpf.py b/scripts/hiddenapi/analyze_bcpf.py
new file mode 100644
index 0000000..595343b
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf.py
@@ -0,0 +1,1477 @@
+#!/usr/bin/env -S python -u
+#
+# 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.
+"""Analyze bootclasspath_fragment usage."""
+import argparse
+import dataclasses
+import enum
+import json
+import logging
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import typing
+from enum import Enum
+
+import sys
+
+from signature_trie import signature_trie
+
+_STUB_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-stub-flags.txt"
+
+_FLAGS_FILE = "out/soong/hiddenapi/hiddenapi-flags.csv"
+
+_INCONSISTENT_FLAGS = "ERROR: Hidden API flags are inconsistent:"
+
+
+class BuildOperation:
+
+    def __init__(self, popen):
+        self.popen = popen
+        self.returncode = None
+
+    def lines(self):
+        """Return an iterator over the lines output by the build operation.
+
+        The lines have had any trailing white space, including the newline
+        stripped.
+        """
+        return newline_stripping_iter(self.popen.stdout.readline)
+
+    def wait(self, *args, **kwargs):
+        self.popen.wait(*args, **kwargs)
+        self.returncode = self.popen.returncode
+
+
+@dataclasses.dataclass()
+class FlagDiffs:
+    """Encapsulates differences in flags reported by the build"""
+
+    # Map from member signature to the (module flags, monolithic flags)
+    diffs: typing.Dict[str, typing.Tuple[str, str]]
+
+
+@dataclasses.dataclass()
+class ModuleInfo:
+    """Provides access to the generated module-info.json file.
+
+    This is used to find the location of the file within which specific modules
+    are defined.
+    """
+
+    modules: typing.Dict[str, typing.Dict[str, typing.Any]]
+
+    @staticmethod
+    def load(filename):
+        with open(filename, "r", encoding="utf8") as f:
+            j = json.load(f)
+            return ModuleInfo(j)
+
+    def _module(self, module_name):
+        """Find module by name in module-info.json file"""
+        if module_name in self.modules:
+            return self.modules[module_name]
+
+        raise Exception(f"Module {module_name} could not be found")
+
+    def module_path(self, module_name):
+        module = self._module(module_name)
+        # The "path" is actually a list of paths, one for each class of module
+        # but as the modules are all created from bp files if a module does
+        # create multiple classes of make modules they should all have the same
+        # path.
+        paths = module["path"]
+        unique_paths = set(paths)
+        if len(unique_paths) != 1:
+            raise Exception(f"Expected module '{module_name}' to have a "
+                            f"single unique path but found {unique_paths}")
+        return paths[0]
+
+
+def extract_indent(line):
+    return re.match(r"([ \t]*)", line).group(1)
+
+
+_SPECIAL_PLACEHOLDER: str = "SPECIAL_PLACEHOLDER"
+
+
+@dataclasses.dataclass
+class BpModifyRunner:
+
+    bpmodify_path: str
+
+    def add_values_to_property(self, property_name, values, module_name,
+                               bp_file):
+        cmd = [
+            self.bpmodify_path, "-a", values, "-property", property_name, "-m",
+            module_name, "-w", bp_file, bp_file
+        ]
+
+        logging.debug(" ".join(cmd))
+        subprocess.run(
+            cmd,
+            stderr=subprocess.STDOUT,
+            stdout=log_stream_for_subprocess(),
+            check=True)
+
+
+@dataclasses.dataclass
+class FileChange:
+    path: str
+
+    description: str
+
+    def __lt__(self, other):
+        return self.path < other.path
+
+
+class PropertyChangeAction(Enum):
+    """Allowable actions that are supported by HiddenApiPropertyChange."""
+
+    # New values are appended to any existing values.
+    APPEND = 1
+
+    # New values replace any existing values.
+    REPLACE = 2
+
+
+@dataclasses.dataclass
+class HiddenApiPropertyChange:
+
+    property_name: str
+
+    values: typing.List[str]
+
+    property_comment: str = ""
+
+    # The action that indicates how this change is applied.
+    action: PropertyChangeAction = PropertyChangeAction.APPEND
+
+    def snippet(self, indent):
+        snippet = "\n"
+        snippet += format_comment_as_text(self.property_comment, indent)
+        snippet += f"{indent}{self.property_name}: ["
+        if self.values:
+            snippet += "\n"
+            for value in self.values:
+                snippet += f'{indent}    "{value}",\n'
+            snippet += f"{indent}"
+        snippet += "],\n"
+        return snippet
+
+    def fix_bp_file(self, bcpf_bp_file, bcpf, bpmodify_runner: BpModifyRunner):
+        # Add an additional placeholder value to identify the modification that
+        # bpmodify makes.
+        bpmodify_values = [_SPECIAL_PLACEHOLDER]
+
+        if self.action == PropertyChangeAction.APPEND:
+            # If adding the values to the existing values then pass the new
+            # values to bpmodify.
+            bpmodify_values.extend(self.values)
+        elif self.action == PropertyChangeAction.REPLACE:
+            # If replacing the existing values then it is not possible to use
+            # bpmodify for that directly. It could be used twice to remove the
+            # existing property and then add a new one but that does not remove
+            # any related comments and loses the position of the existing
+            # property as the new property is always added to the end of the
+            # containing block.
+            #
+            # So, instead of passing the new values to bpmodify this this just
+            # adds an extra placeholder to force bpmodify to format the list
+            # across multiple lines to ensure a consistent structure for the
+            # code that removes all the existing values and adds the new ones.
+            #
+            # This placeholder has to be different to the other placeholder as
+            # bpmodify dedups values.
+            bpmodify_values.append(_SPECIAL_PLACEHOLDER + "_REPLACE")
+        else:
+            raise ValueError(f"unknown action {self.action}")
+
+        packages = ",".join(bpmodify_values)
+        bpmodify_runner.add_values_to_property(
+            f"hidden_api.{self.property_name}", packages, bcpf, bcpf_bp_file)
+
+        with open(bcpf_bp_file, "r", encoding="utf8") as tio:
+            lines = tio.readlines()
+            lines = [line.rstrip("\n") for line in lines]
+
+        if self.fixup_bpmodify_changes(bcpf_bp_file, lines):
+            with open(bcpf_bp_file, "w", encoding="utf8") as tio:
+                for line in lines:
+                    print(line, file=tio)
+
+    def fixup_bpmodify_changes(self, bcpf_bp_file, lines):
+        """Fixup the output of bpmodify.
+
+        The bpmodify tool does not support all the capabilities that this needs
+        so it is used to do what it can, including marking the place in the
+        Android.bp file where it makes its changes and then this gets passed a
+        list of lines from that file which it then modifies to complete the
+        change.
+
+        This analyzes the list of lines to find the indices of the significant
+        lines and then applies some changes. As those changes can insert and
+        delete lines (changing the indices of following lines) the changes are
+        generally done in reverse order starting from the end and working
+        towards the beginning. That ensures that the changes do not invalidate
+        the indices of following lines.
+        """
+
+        # Find the line containing the placeholder that has been inserted.
+        place_holder_index = -1
+        for i, line in enumerate(lines):
+            if _SPECIAL_PLACEHOLDER in line:
+                place_holder_index = i
+                break
+        if place_holder_index == -1:
+            logging.debug("Could not find %s in %s", _SPECIAL_PLACEHOLDER,
+                          bcpf_bp_file)
+            return False
+
+        # Remove the place holder. Do this before inserting the comment as that
+        # would change the location of the place holder in the list.
+        place_holder_line = lines[place_holder_index]
+        if place_holder_line.endswith("],"):
+            place_holder_line = place_holder_line.replace(
+                f'"{_SPECIAL_PLACEHOLDER}"', "")
+            lines[place_holder_index] = place_holder_line
+        else:
+            del lines[place_holder_index]
+
+        # Scan forward to the end of the property block to remove a blank line
+        # that bpmodify inserts.
+        end_property_array_index = -1
+        for i in range(place_holder_index, len(lines)):
+            line = lines[i]
+            if line.endswith("],"):
+                end_property_array_index = i
+                break
+        if end_property_array_index == -1:
+            logging.debug("Could not find end of property array in %s",
+                          bcpf_bp_file)
+            return False
+
+        # If bdmodify inserted a blank line afterwards then remove it.
+        if (not lines[end_property_array_index + 1] and
+                lines[end_property_array_index + 2].endswith("},")):
+            del lines[end_property_array_index + 1]
+
+        # Scan back to find the preceding property line.
+        property_line_index = -1
+        for i in range(place_holder_index, 0, -1):
+            line = lines[i]
+            if line.lstrip().startswith(f"{self.property_name}: ["):
+                property_line_index = i
+                break
+        if property_line_index == -1:
+            logging.debug("Could not find property line in %s", bcpf_bp_file)
+            return False
+
+        # If this change is replacing the existing values then they need to be
+        # removed and replaced with the new values. That will change the lines
+        # after the property but it is necessary to do here as the following
+        # code operates on earlier lines.
+        if self.action == PropertyChangeAction.REPLACE:
+            # This removes the existing values and replaces them with the new
+            # values.
+            indent = extract_indent(lines[property_line_index + 1])
+            insert = [f'{indent}"{x}",' for x in self.values]
+            lines[property_line_index + 1:end_property_array_index] = insert
+            if not self.values:
+                # If the property has no values then merge the ], onto the
+                # same line as the property name.
+                del lines[property_line_index + 1]
+                lines[property_line_index] = lines[property_line_index] + "],"
+
+        # Only insert a comment if the property does not already have a comment.
+        line_preceding_property = lines[(property_line_index - 1)]
+        if (self.property_comment and
+                not re.match("([ \t]+)// ", line_preceding_property)):
+            # Extract the indent from the property line and use it to format the
+            # comment.
+            indent = extract_indent(lines[property_line_index])
+            comment_lines = format_comment_as_lines(self.property_comment,
+                                                    indent)
+
+            # If the line before the comment is not blank then insert an extra
+            # blank line at the beginning of the comment.
+            if line_preceding_property:
+                comment_lines.insert(0, "")
+
+            # Insert the comment before the property.
+            lines[property_line_index:property_line_index] = comment_lines
+        return True
+
+
+@dataclasses.dataclass()
+class PackagePropertyReason:
+    """Provides the reasons why a package was added to a specific property.
+
+    A split package is one that contains classes from the bootclasspath_fragment
+    and other bootclasspath modules. So, for a split package this contains the
+    corresponding lists of classes.
+
+    A single package is one that contains classes sub-packages from the
+    For a split package this contains a list of classes in that package that are
+    provided by the bootclasspath_fragment and a list of classes
+    """
+
+    # The list of classes/sub-packages that is provided by the
+    # bootclasspath_fragment.
+    bcpf: typing.List[str]
+
+    # The list of classes/sub-packages that is provided by other modules on the
+    # bootclasspath.
+    other: typing.List[str]
+
+
+@dataclasses.dataclass()
+class Result:
+    """Encapsulates the result of the analysis."""
+
+    # The diffs in the flags.
+    diffs: typing.Optional[FlagDiffs] = None
+
+    # A map from package name to the reason why it belongs in the
+    # split_packages property.
+    split_packages: typing.Dict[str, PackagePropertyReason] = dataclasses.field(
+        default_factory=dict)
+
+    # A map from package name to the reason why it belongs in the
+    # single_packages property.
+    single_packages: typing.Dict[str,
+                                 PackagePropertyReason] = dataclasses.field(
+                                     default_factory=dict)
+
+    # The list of packages to add to the package_prefixes property.
+    package_prefixes: typing.List[str] = dataclasses.field(default_factory=list)
+
+    # The bootclasspath_fragment hidden API properties changes.
+    property_changes: typing.List[HiddenApiPropertyChange] = dataclasses.field(
+        default_factory=list)
+
+    # The list of file changes.
+    file_changes: typing.List[FileChange] = dataclasses.field(
+        default_factory=list)
+
+
+class ClassProvider(enum.Enum):
+    """The source of a class found during the hidden API processing"""
+    BCPF = "bcpf"
+    OTHER = "other"
+
+
+# A fake member to use when using the signature trie to compute the package
+# properties from hidden API flags. This is needed because while that
+# computation only cares about classes the trie expects a class to be an
+# interior node but without a member it makes the class a leaf node. That causes
+# problems when analyzing inner classes as the outer class is a leaf node for
+# its own entry but is used as an interior node for inner classes.
+_FAKE_MEMBER = ";->fake()V"
+
+
+@dataclasses.dataclass()
+class BcpfAnalyzer:
+    # Path to this tool.
+    tool_path: str
+
+    # Directory pointed to by ANDROID_BUILD_OUT
+    top_dir: str
+
+    # Directory pointed to by OUT_DIR of {top_dir}/out if that is not set.
+    out_dir: str
+
+    # Directory pointed to by ANDROID_PRODUCT_OUT.
+    product_out_dir: str
+
+    # The name of the bootclasspath_fragment module.
+    bcpf: str
+
+    # The name of the apex module containing {bcpf}, only used for
+    # informational purposes.
+    apex: str
+
+    # The name of the sdk module containing {bcpf}, only used for
+    # informational purposes.
+    sdk: str
+
+    # If true then this will attempt to automatically fix any issues that are
+    # found.
+    fix: bool = False
+
+    # All the signatures, loaded from all-flags.csv, initialized by
+    # load_all_flags().
+    _signatures: typing.Set[str] = dataclasses.field(default_factory=set)
+
+    # All the classes, loaded from all-flags.csv, initialized by
+    # load_all_flags().
+    _classes: typing.Set[str] = dataclasses.field(default_factory=set)
+
+    # Information loaded from module-info.json, initialized by
+    # load_module_info().
+    module_info: ModuleInfo = None
+
+    @staticmethod
+    def reformat_report_test(text):
+        return re.sub(r"(.)\n([^\s])", r"\1 \2", text)
+
+    def report(self, text="", **kwargs):
+        # Concatenate lines that are not separated by a blank line together to
+        # eliminate formatting applied to the supplied text to adhere to python
+        # line length limitations.
+        text = self.reformat_report_test(text)
+        logging.info("%s", text, **kwargs)
+
+    def report_dedent(self, text, **kwargs):
+        text = textwrap.dedent(text)
+        self.report(text, **kwargs)
+
+    def run_command(self, cmd, *args, **kwargs):
+        cmd_line = " ".join(cmd)
+        logging.debug("Running %s", cmd_line)
+        subprocess.run(
+            cmd,
+            *args,
+            check=True,
+            cwd=self.top_dir,
+            stderr=subprocess.STDOUT,
+            stdout=log_stream_for_subprocess(),
+            text=True,
+            **kwargs)
+
+    @property
+    def signatures(self):
+        if not self._signatures:
+            raise Exception("signatures has not been initialized")
+        return self._signatures
+
+    @property
+    def classes(self):
+        if not self._classes:
+            raise Exception("classes has not been initialized")
+        return self._classes
+
+    def load_all_flags(self):
+        all_flags = self.find_bootclasspath_fragment_output_file(
+            "all-flags.csv")
+
+        # Extract the set of signatures and a separate set of classes produced
+        # by the bootclasspath_fragment.
+        with open(all_flags, "r", encoding="utf8") as f:
+            for line in newline_stripping_iter(f.readline):
+                signature = self.line_to_signature(line)
+                self._signatures.add(signature)
+                class_name = self.signature_to_class(signature)
+                self._classes.add(class_name)
+
+    def load_module_info(self):
+        module_info_file = os.path.join(self.product_out_dir,
+                                        "module-info.json")
+        self.report(f"\nMaking sure that {module_info_file} is up to date.\n")
+        output = self.build_file_read_output(module_info_file)
+        lines = output.lines()
+        for line in lines:
+            logging.debug("%s", line)
+        output.wait(timeout=10)
+        if output.returncode:
+            raise Exception(f"Error building {module_info_file}")
+        abs_module_info_file = os.path.join(self.top_dir, module_info_file)
+        self.module_info = ModuleInfo.load(abs_module_info_file)
+
+    @staticmethod
+    def line_to_signature(line):
+        return line.split(",")[0]
+
+    @staticmethod
+    def signature_to_class(signature):
+        return signature.split(";->")[0]
+
+    @staticmethod
+    def to_parent_package(pkg_or_class):
+        return pkg_or_class.rsplit("/", 1)[0]
+
+    def module_path(self, module_name):
+        return self.module_info.module_path(module_name)
+
+    def module_out_dir(self, module_name):
+        module_path = self.module_path(module_name)
+        return os.path.join(self.out_dir, "soong/.intermediates", module_path,
+                            module_name)
+
+    def find_bootclasspath_fragment_output_file(self, basename, required=True):
+        # Find the output file of the bootclasspath_fragment with the specified
+        # base name.
+        found_file = ""
+        bcpf_out_dir = self.module_out_dir(self.bcpf)
+        for (dirpath, _, filenames) in os.walk(bcpf_out_dir):
+            for f in filenames:
+                if f == basename:
+                    found_file = os.path.join(dirpath, f)
+                    break
+        if not found_file and required:
+            raise Exception(f"Could not find {basename} in {bcpf_out_dir}")
+        return found_file
+
+    def analyze(self):
+        """Analyze a bootclasspath_fragment module.
+
+        Provides help in resolving any existing issues and provides
+        optimizations that can be applied.
+        """
+        self.report(f"Analyzing bootclasspath_fragment module {self.bcpf}")
+        self.report_dedent(f"""
+            Run this tool to help initialize a bootclasspath_fragment module.
+            Before you start make sure that:
+
+            1. The current checkout is up to date.
+
+            2. The environment has been initialized using lunch, e.g.
+               lunch aosp_arm64-userdebug
+
+            3. You have added a bootclasspath_fragment module to the appropriate
+            Android.bp file. Something like this:
+
+               bootclasspath_fragment {{
+                 name: "{self.bcpf}",
+                 contents: [
+                   "...",
+                 ],
+            
+                 // The bootclasspath_fragments that provide APIs on which this
+                 // depends.
+                 fragments: [
+                   {{
+                     apex: "com.android.art",
+                     module: "art-bootclasspath-fragment",
+                   }},
+                 ],
+               }}
+            
+            4. You have added it to the platform_bootclasspath module in
+            frameworks/base/boot/Android.bp. Something like this:
+
+               platform_bootclasspath {{
+                 name: "platform-bootclasspath",
+                 fragments: [
+                   ...
+                   {{
+                     apex: "{self.apex}",
+                     module: "{self.bcpf}",
+                   }},
+                 ],
+               }}
+
+            5. You have added an sdk module. Something like this:
+
+               sdk {{
+                 name: "{self.sdk}",
+                 bootclasspath_fragments: ["{self.bcpf}"],
+               }}
+            """)
+
+        # Make sure that the module-info.json file is up to date.
+        self.load_module_info()
+
+        self.report_dedent("""
+            Cleaning potentially stale files.
+            """)
+        # Remove the out/soong/hiddenapi files.
+        shutil.rmtree(f"{self.out_dir}/soong/hiddenapi", ignore_errors=True)
+
+        # Remove any bootclasspath_fragment output files.
+        shutil.rmtree(self.module_out_dir(self.bcpf), ignore_errors=True)
+
+        self.build_monolithic_stubs_flags()
+
+        result = Result()
+
+        self.build_monolithic_flags(result)
+        self.analyze_hiddenapi_package_properties(result)
+        self.explain_how_to_check_signature_patterns()
+
+        # If there were any changes that need to be made to the Android.bp
+        # file then either apply or report them.
+        if result.property_changes:
+            bcpf_dir = self.module_info.module_path(self.bcpf)
+            bcpf_bp_file = os.path.join(self.top_dir, bcpf_dir, "Android.bp")
+            if self.fix:
+                tool_dir = os.path.dirname(self.tool_path)
+                bpmodify_path = os.path.join(tool_dir, "bpmodify")
+                bpmodify_runner = BpModifyRunner(bpmodify_path)
+                for property_change in result.property_changes:
+                    property_change.fix_bp_file(bcpf_bp_file, self.bcpf,
+                                                bpmodify_runner)
+
+                result.file_changes.append(
+                    self.new_file_change(
+                        bcpf_bp_file,
+                        f"Updated hidden_api properties of '{self.bcpf}'"))
+
+            else:
+                hiddenapi_snippet = ""
+                for property_change in result.property_changes:
+                    hiddenapi_snippet += property_change.snippet("        ")
+
+                # Remove leading and trailing blank lines.
+                hiddenapi_snippet = hiddenapi_snippet.strip("\n")
+
+                result.file_changes.append(
+                    self.new_file_change(
+                        bcpf_bp_file, f"""
+Add the following snippet into the {self.bcpf} bootclasspath_fragment module
+in the {bcpf_dir}/Android.bp file. If the hidden_api block already exists then
+merge these properties into it.
+
+    hidden_api: {{
+{hiddenapi_snippet}
+    }},
+"""))
+
+        if result.file_changes:
+            if self.fix:
+                file_change_message = textwrap.dedent("""
+                    The following files were modified by this script:
+                    """)
+            else:
+                file_change_message = textwrap.dedent("""
+                    The following modifications need to be made:
+                    """)
+
+            self.report(file_change_message)
+            result.file_changes.sort()
+            for file_change in result.file_changes:
+                self.report(f"    {file_change.path}")
+                self.report(f"        {file_change.description}")
+                self.report()
+
+            if not self.fix:
+                self.report_dedent("""
+                    Run the command again with the --fix option to automatically
+                    make the above changes.
+                    """.lstrip("\n"))
+
+    def new_file_change(self, file, description):
+        return FileChange(
+            path=os.path.relpath(file, self.top_dir), description=description)
+
+    def check_inconsistent_flag_lines(self, significant, module_line,
+                                      monolithic_line, separator_line):
+        if not (module_line.startswith("< ") and
+                monolithic_line.startswith("> ") and not separator_line):
+            # Something went wrong.
+            self.report("Invalid build output detected:")
+            self.report(f"  module_line: '{module_line}'")
+            self.report(f"  monolithic_line: '{monolithic_line}'")
+            self.report(f"  separator_line: '{separator_line}'")
+            sys.exit(1)
+
+        if significant:
+            logging.debug("%s", module_line)
+            logging.debug("%s", monolithic_line)
+            logging.debug("%s", separator_line)
+
+    def scan_inconsistent_flags_report(self, lines):
+        """Scans a hidden API flags report
+
+        The hidden API inconsistent flags report which looks something like
+        this.
+
+        < out/soong/.intermediates/.../filtered-stub-flags.csv
+        > out/soong/hiddenapi/hiddenapi-stub-flags.txt
+
+        < Landroid/compat/Compatibility;->clearOverrides()V
+        > Landroid/compat/Compatibility;->clearOverrides()V,core-platform-api
+
+        """
+
+        # The basic format of an entry in the inconsistent flags report is:
+        #   <module specific flag>
+        #   <monolithic flag>
+        #   <separator>
+        #
+        # Wrap the lines iterator in an iterator which returns a tuple
+        # consisting of the three separate lines.
+        triples = zip(lines, lines, lines)
+
+        module_line, monolithic_line, separator_line = next(triples)
+        significant = False
+        bcpf_dir = self.module_info.module_path(self.bcpf)
+        if os.path.join(bcpf_dir, self.bcpf) in module_line:
+            # These errors are related to the bcpf being analyzed so
+            # keep them.
+            significant = True
+        else:
+            self.report(f"Filtering out errors related to {module_line}")
+
+        self.check_inconsistent_flag_lines(significant, module_line,
+                                           monolithic_line, separator_line)
+
+        diffs = {}
+        for module_line, monolithic_line, separator_line in triples:
+            self.check_inconsistent_flag_lines(significant, module_line,
+                                               monolithic_line, "")
+
+            module_parts = module_line.removeprefix("< ").split(",")
+            module_signature = module_parts[0]
+            module_flags = module_parts[1:]
+
+            monolithic_parts = monolithic_line.removeprefix("> ").split(",")
+            monolithic_signature = monolithic_parts[0]
+            monolithic_flags = monolithic_parts[1:]
+
+            if module_signature != monolithic_signature:
+                # Something went wrong.
+                self.report("Inconsistent signatures detected:")
+                self.report(f"  module_signature: '{module_signature}'")
+                self.report(f"  monolithic_signature: '{monolithic_signature}'")
+                sys.exit(1)
+
+            diffs[module_signature] = (module_flags, monolithic_flags)
+
+            if separator_line:
+                # If the separator line is not blank then it is the end of the
+                # current report, and possibly the start of another.
+                return separator_line, diffs
+
+        return "", diffs
+
+    def build_file_read_output(self, filename):
+        # Make sure the filename is relative to top if possible as the build
+        # may be using relative paths as the target.
+        rel_filename = filename.removeprefix(self.top_dir)
+        cmd = ["build/soong/soong_ui.bash", "--make-mode", rel_filename]
+        cmd_line = " ".join(cmd)
+        logging.debug("%s", cmd_line)
+        # pylint: disable=consider-using-with
+        output = subprocess.Popen(
+            cmd,
+            cwd=self.top_dir,
+            stderr=subprocess.STDOUT,
+            stdout=subprocess.PIPE,
+            text=True,
+        )
+        return BuildOperation(popen=output)
+
+    def build_hiddenapi_flags(self, filename):
+        output = self.build_file_read_output(filename)
+
+        lines = output.lines()
+        diffs = None
+        for line in lines:
+            logging.debug("%s", line)
+            while line == _INCONSISTENT_FLAGS:
+                line, diffs = self.scan_inconsistent_flags_report(lines)
+
+        output.wait(timeout=10)
+        if output.returncode != 0:
+            logging.debug("Command failed with %s", output.returncode)
+        else:
+            logging.debug("Command succeeded")
+
+        return diffs
+
+    def build_monolithic_stubs_flags(self):
+        self.report_dedent(f"""
+            Attempting to build {_STUB_FLAGS_FILE} to verify that the
+            bootclasspath_fragment has the correct API stubs available...
+            """)
+
+        # Build the hiddenapi-stubs-flags.txt file.
+        diffs = self.build_hiddenapi_flags(_STUB_FLAGS_FILE)
+        if diffs:
+            self.report_dedent(f"""
+                There is a discrepancy between the stub API derived flags
+                created by the bootclasspath_fragment and the
+                platform_bootclasspath. See preceding error messages to see
+                which flags are inconsistent. The inconsistencies can occur for
+                a couple of reasons:
+
+                If you are building against prebuilts of the Android SDK, e.g.
+                by using TARGET_BUILD_APPS then the prebuilt versions of the
+                APIs this bootclasspath_fragment depends upon are out of date
+                and need updating. See go/update-prebuilts for help.
+
+                Otherwise, this is happening because there are some stub APIs
+                that are either provided by or used by the contents of the
+                bootclasspath_fragment but which are not available to it. There
+                are 4 ways to handle this:
+
+                1. A java_sdk_library in the contents property will
+                automatically make its stub APIs available to the
+                bootclasspath_fragment so nothing needs to be done.
+
+                2. If the API provided by the bootclasspath_fragment is created
+                by an api_only java_sdk_library (or a java_library that compiles
+                files generated by a separate droidstubs module then it cannot
+                be added to the contents and instead must be added to the
+                api.stubs property, e.g.
+
+                   bootclasspath_fragment {{
+                     name: "{self.bcpf}",
+                     ...
+                     api: {{
+                       stubs: ["$MODULE-api-only"],"
+                     }},
+                   }}
+
+                3. If the contents use APIs provided by another
+                bootclasspath_fragment then it needs to be added to the
+                fragments property, e.g.
+                
+                   bootclasspath_fragment {{
+                     name: "{self.bcpf}",
+                     ...
+                     // The bootclasspath_fragments that provide APIs on which this depends.
+                     fragments: [
+                       ...
+                       {{
+                         apex: "com.android.other",
+                         module: "com.android.other-bootclasspath-fragment",
+                       }},
+                     ],
+                   }}
+                
+                4. If the contents use APIs from a module that is not part of
+                another bootclasspath_fragment then it must be added to the
+                additional_stubs property, e.g.
+
+                   bootclasspath_fragment {{
+                     name: "{self.bcpf}",
+                     ...
+                     additional_stubs: ["android-non-updatable"],
+                   }}
+
+                   Like the api.stubs property these are typically
+                   java_sdk_library modules but can be java_library too.
+
+                   Note: The "android-non-updatable" is treated as if it was a
+                   java_sdk_library which it is not at the moment but will be in
+                   future.
+                """)
+
+        return diffs
+
+    def build_monolithic_flags(self, result):
+        self.report_dedent(f"""
+            Attempting to build {_FLAGS_FILE} to verify that the
+            bootclasspath_fragment has the correct hidden API flags...
+            """)
+
+        # Build the hiddenapi-flags.csv file and extract any differences in
+        # the flags between this bootclasspath_fragment and the monolithic
+        # files.
+        result.diffs = self.build_hiddenapi_flags(_FLAGS_FILE)
+
+        # Load information from the bootclasspath_fragment's all-flags.csv file.
+        self.load_all_flags()
+
+        if result.diffs:
+            self.report_dedent(f"""
+                There is a discrepancy between the hidden API flags created by
+                the bootclasspath_fragment and the platform_bootclasspath. See
+                preceding error messages to see which flags are inconsistent.
+                The inconsistencies can occur for a couple of reasons:
+
+                If you are building against prebuilts of this
+                bootclasspath_fragment then the prebuilt version of the sdk
+                snapshot (specifically the hidden API flag files) are
+                inconsistent with the prebuilt version of the apex {self.apex}.
+                Please ensure that they are both updated from the same build.
+
+                1. There are custom hidden API flags specified in the one of the
+                files in frameworks/base/boot/hiddenapi which apply to the
+                bootclasspath_fragment but which are not supplied to the
+                bootclasspath_fragment module.
+
+                2. The bootclasspath_fragment specifies invalid
+                "split_packages", "single_packages" and/of "package_prefixes"
+                properties that match packages and classes that it does not
+                provide.
+                """)
+
+            # Check to see if there are any hiddenapi related properties that
+            # need to be added to the
+            self.report_dedent("""
+                Checking custom hidden API flags....
+                """)
+            self.check_frameworks_base_boot_hidden_api_files(result)
+
+    def report_hidden_api_flag_file_changes(self, result, property_name,
+                                            flags_file, rel_bcpf_flags_file,
+                                            bcpf_flags_file):
+        matched_signatures = set()
+        # Open the flags file to read the flags from.
+        with open(flags_file, "r", encoding="utf8") as f:
+            for signature in newline_stripping_iter(f.readline):
+                if signature in self.signatures:
+                    # The signature is provided by the bootclasspath_fragment so
+                    # it will need to be moved to the bootclasspath_fragment
+                    # specific file.
+                    matched_signatures.add(signature)
+
+        # If the bootclasspath_fragment specific flags file is not empty
+        # then it contains flags. That could either be new flags just moved
+        # from frameworks/base or previous contents of the file. In either
+        # case the file must not be removed.
+        if matched_signatures:
+            insert = textwrap.indent("\n".join(matched_signatures),
+                                     "            ")
+            result.file_changes.append(
+                self.new_file_change(
+                    flags_file, f"""Remove the following entries:
+{insert}
+"""))
+
+            result.file_changes.append(
+                self.new_file_change(
+                    bcpf_flags_file, f"""Add the following entries:
+{insert}
+"""))
+
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name=property_name,
+                    values=[rel_bcpf_flags_file],
+                ))
+
+    def fix_hidden_api_flag_files(self, result, property_name, flags_file,
+                                  rel_bcpf_flags_file, bcpf_flags_file):
+        # Read the file in frameworks/base/boot/hiddenapi/<file> copy any
+        # flags that relate to the bootclasspath_fragment into a local
+        # file in the hiddenapi subdirectory.
+        tmp_flags_file = flags_file + ".tmp"
+
+        # Make sure the directory containing the bootclasspath_fragment specific
+        # hidden api flags exists.
+        os.makedirs(os.path.dirname(bcpf_flags_file), exist_ok=True)
+
+        bcpf_flags_file_exists = os.path.exists(bcpf_flags_file)
+
+        matched_signatures = set()
+        # Open the flags file to read the flags from.
+        with open(flags_file, "r", encoding="utf8") as f:
+            # Open a temporary file to write the flags (minus any removed
+            # flags).
+            with open(tmp_flags_file, "w", encoding="utf8") as t:
+                # Open the bootclasspath_fragment file for append just in
+                # case it already exists.
+                with open(bcpf_flags_file, "a", encoding="utf8") as b:
+                    for line in iter(f.readline, ""):
+                        signature = line.rstrip()
+                        if signature in self.signatures:
+                            # The signature is provided by the
+                            # bootclasspath_fragment so write it to the new
+                            # bootclasspath_fragment specific file.
+                            print(line, file=b, end="")
+                            matched_signatures.add(signature)
+                        else:
+                            # The signature is NOT provided by the
+                            # bootclasspath_fragment. Copy it to the new
+                            # monolithic file.
+                            print(line, file=t, end="")
+
+        # If the bootclasspath_fragment specific flags file is not empty
+        # then it contains flags. That could either be new flags just moved
+        # from frameworks/base or previous contents of the file. In either
+        # case the file must not be removed.
+        if matched_signatures:
+            # There are custom flags related to the bootclasspath_fragment
+            # so replace the frameworks/base/boot/hiddenapi file with the
+            # file that does not contain those flags.
+            shutil.move(tmp_flags_file, flags_file)
+
+            result.file_changes.append(
+                self.new_file_change(flags_file,
+                                     f"Removed '{self.bcpf}' specific entries"))
+
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name=property_name,
+                    values=[rel_bcpf_flags_file],
+                ))
+
+            # Make sure that the files are sorted.
+            self.run_command([
+                "tools/platform-compat/hiddenapi/sort_api.sh",
+                bcpf_flags_file,
+            ])
+
+            if bcpf_flags_file_exists:
+                desc = f"Added '{self.bcpf}' specific entries"
+            else:
+                desc = f"Created with '{self.bcpf}' specific entries"
+            result.file_changes.append(
+                self.new_file_change(bcpf_flags_file, desc))
+        else:
+            # There are no custom flags related to the
+            # bootclasspath_fragment so clean up the working files.
+            os.remove(tmp_flags_file)
+            if not bcpf_flags_file_exists:
+                os.remove(bcpf_flags_file)
+
+    def check_frameworks_base_boot_hidden_api_files(self, result):
+        hiddenapi_dir = os.path.join(self.top_dir,
+                                     "frameworks/base/boot/hiddenapi")
+        for basename in sorted(os.listdir(hiddenapi_dir)):
+            if not (basename.startswith("hiddenapi-") and
+                    basename.endswith(".txt")):
+                continue
+
+            flags_file = os.path.join(hiddenapi_dir, basename)
+
+            logging.debug("Checking %s for flags related to %s", flags_file,
+                          self.bcpf)
+
+            # Map the file name in frameworks/base/boot/hiddenapi into a
+            # slightly more meaningful name for use by the
+            # bootclasspath_fragment.
+            if basename == "hiddenapi-max-target-o.txt":
+                basename = "hiddenapi-max-target-o-low-priority.txt"
+            elif basename == "hiddenapi-max-target-r-loprio.txt":
+                basename = "hiddenapi-max-target-r-low-priority.txt"
+
+            property_name = basename.removeprefix("hiddenapi-")
+            property_name = property_name.removesuffix(".txt")
+            property_name = property_name.replace("-", "_")
+
+            rel_bcpf_flags_file = f"hiddenapi/{basename}"
+            bcpf_dir = self.module_info.module_path(self.bcpf)
+            bcpf_flags_file = os.path.join(self.top_dir, bcpf_dir,
+                                           rel_bcpf_flags_file)
+
+            if self.fix:
+                self.fix_hidden_api_flag_files(result, property_name,
+                                               flags_file, rel_bcpf_flags_file,
+                                               bcpf_flags_file)
+            else:
+                self.report_hidden_api_flag_file_changes(
+                    result, property_name, flags_file, rel_bcpf_flags_file,
+                    bcpf_flags_file)
+
+    @staticmethod
+    def split_package_comment(split_packages):
+        if split_packages:
+            return textwrap.dedent("""
+                The following packages contain classes from other modules on the
+                bootclasspath. That means that the hidden API flags for this
+                module has to explicitly list every single class this module
+                provides in that package to differentiate them from the classes
+                provided by other modules. That can include private classes that
+                are not part of the API.
+            """).strip("\n")
+
+        return "This module does not contain any split packages."
+
+    @staticmethod
+    def package_prefixes_comment():
+        return textwrap.dedent("""
+            The following packages and all their subpackages currently only
+            contain classes from this bootclasspath_fragment. Listing a package
+            here won't prevent other bootclasspath modules from adding classes
+            in any of those packages but it will prevent them from adding those
+            classes into an API surface, e.g. public, system, etc.. Doing so
+            will result in a build failure due to inconsistent flags.
+        """).strip("\n")
+
+    def analyze_hiddenapi_package_properties(self, result):
+        self.compute_hiddenapi_package_properties(result)
+
+        def indent_lines(lines):
+            return "\n".join([f"        {cls}" for cls in lines])
+
+        # TODO(b/202154151): Find those classes in split packages that are not
+        #  part of an API, i.e. are an internal implementation class, and so
+        #  can, and should, be safely moved out of the split packages.
+
+        split_packages = result.split_packages.keys()
+        result.property_changes.append(
+            HiddenApiPropertyChange(
+                property_name="split_packages",
+                values=split_packages,
+                property_comment=self.split_package_comment(split_packages),
+                action=PropertyChangeAction.REPLACE,
+            ))
+
+        if split_packages:
+            self.report_dedent(f"""
+                bootclasspath_fragment {self.bcpf} contains classes in packages
+                that also contain classes provided by other bootclasspath
+                modules. Those packages are called split packages. Split
+                packages should be avoided where possible but are often
+                unavoidable when modularizing existing code.
+
+                The hidden api processing needs to know which packages are split
+                (and conversely which are not) so that it can optimize the
+                hidden API flags to remove unnecessary implementation details.
+
+                By default (for backwards compatibility) the
+                bootclasspath_fragment assumes that all packages are split
+                unless one of the package_prefixes or split_packages properties
+                are specified. While that is safe it is not optimal and can lead
+                to unnecessary implementation details leaking into the hidden
+                API flags. Adding an empty split_packages property allows the
+                flags to be optimized and remove any unnecessary implementation
+                details.
+                """)
+
+            for package in split_packages:
+                reason = result.split_packages[package]
+                self.report(f"""
+    Package {package} is split because while this bootclasspath_fragment
+    provides the following classes:
+{indent_lines(reason.bcpf)}
+
+    Other module(s) on the bootclasspath provides the following classes in
+    that package:
+{indent_lines(reason.other)}
+""")
+
+        single_packages = result.single_packages.keys()
+        if single_packages:
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name="single_packages",
+                    values=single_packages,
+                    property_comment=textwrap.dedent("""
+                    The following packages currently only contain classes from
+                    this bootclasspath_fragment but some of their sub-packages
+                    contain classes from other bootclasspath modules. Packages
+                    should only be listed here when necessary for legacy
+                    purposes, new packages should match a package prefix.
+                    """),
+                    action=PropertyChangeAction.REPLACE,
+                ))
+
+            self.report_dedent(f"""
+                bootclasspath_fragment {self.bcpf} contains classes from
+                packages that has sub-packages which contain classes provided by
+                other bootclasspath modules. Those packages are called single
+                packages. Single packages should be avoided where possible but
+                are often unavoidable when modularizing existing code.
+
+                Because some sub-packages contains classes from other
+                bootclasspath modules it is not possible to use the package as a
+                package prefix as that treats the package and all its
+                sub-packages as being provided by this module.  
+                """)
+            for package in single_packages:
+                reason = result.single_packages[package]
+                self.report(f"""
+    Package {package} is not a package prefix because while this
+    bootclasspath_fragment provides the following sub-packages:
+{indent_lines(reason.bcpf)}
+
+    Other module(s) on the bootclasspath provide the following sub-packages:
+{indent_lines(reason.other)}
+""")
+
+        package_prefixes = result.package_prefixes
+        if package_prefixes:
+            result.property_changes.append(
+                HiddenApiPropertyChange(
+                    property_name="package_prefixes",
+                    values=package_prefixes,
+                    property_comment=self.package_prefixes_comment(),
+                    action=PropertyChangeAction.REPLACE,
+                ))
+
+    def explain_how_to_check_signature_patterns(self):
+        signature_patterns_files = self.find_bootclasspath_fragment_output_file(
+            "signature-patterns.csv", required=False)
+        if signature_patterns_files:
+            signature_patterns_files = signature_patterns_files.removeprefix(
+                self.top_dir)
+
+            self.report_dedent(f"""
+                The purpose of the hiddenapi split_packages and package_prefixes
+                properties is to allow the removal of implementation details
+                from the hidden API flags to reduce the coupling between sdk
+                snapshots and the APEX runtime. It cannot eliminate that
+                coupling completely though. Doing so may require changes to the
+                code.
+
+                This tool provides support for managing those properties but it
+                cannot decide whether the set of package prefixes suggested is
+                appropriate that needs the input of the developer.
+
+                Please run the following command:
+                    m {signature_patterns_files}
+
+                And then check the '{signature_patterns_files}' for any mention
+                of implementation classes and packages (i.e. those
+                classes/packages that do not contain any part of an API surface,
+                including the hidden API). If they are found then the code
+                should ideally be moved to a package unique to this module that
+                is contained within a package that is part of an API surface.
+
+                The format of the file is a list of patterns:
+
+                * Patterns for split packages will list every class in that package.
+
+                * Patterns for package prefixes will end with .../**.
+
+                * Patterns for packages which are not split but cannot use a
+                package prefix because there are sub-packages which are provided
+                by another module will end with .../*.
+                """)
+
+    def compute_hiddenapi_package_properties(self, result):
+        trie = signature_trie()
+        # Populate the trie with the classes that are provided by the
+        # bootclasspath_fragment tagging them to make it clear where they
+        # are from.
+        sorted_classes = sorted(self.classes)
+        for class_name in sorted_classes:
+            trie.add(class_name + _FAKE_MEMBER, ClassProvider.BCPF)
+
+        # Now the same for monolithic classes.
+        monolithic_classes = set()
+        abs_flags_file = os.path.join(self.top_dir, _FLAGS_FILE)
+        with open(abs_flags_file, "r", encoding="utf8") as f:
+            for line in iter(f.readline, ""):
+                signature = self.line_to_signature(line)
+                class_name = self.signature_to_class(signature)
+                if (class_name not in monolithic_classes and
+                        class_name not in self.classes):
+                    trie.add(
+                        class_name + _FAKE_MEMBER,
+                        ClassProvider.OTHER,
+                        only_if_matches=True)
+                    monolithic_classes.add(class_name)
+
+        self.recurse_hiddenapi_packages_trie(trie, result)
+
+    @staticmethod
+    def selector_to_java_reference(node):
+        return node.selector.replace("/", ".")
+
+    @staticmethod
+    def determine_reason_for_single_package(node):
+        bcpf_packages = []
+        other_packages = []
+
+        def recurse(n):
+            if n.type != "package":
+                return
+
+            providers = n.get_matching_rows("*")
+            package_ref = BcpfAnalyzer.selector_to_java_reference(n)
+            if ClassProvider.BCPF in providers:
+                bcpf_packages.append(package_ref)
+            else:
+                other_packages.append(package_ref)
+
+            children = n.child_nodes()
+            if children:
+                for child in children:
+                    recurse(child)
+
+        recurse(node)
+        return PackagePropertyReason(bcpf=bcpf_packages, other=other_packages)
+
+    @staticmethod
+    def determine_reason_for_split_package(node):
+        bcpf_classes = []
+        other_classes = []
+        for child in node.child_nodes():
+            if child.type != "class":
+                continue
+
+            providers = child.values(lambda _: True)
+            class_ref = BcpfAnalyzer.selector_to_java_reference(child)
+            if ClassProvider.BCPF in providers:
+                bcpf_classes.append(class_ref)
+            else:
+                other_classes.append(class_ref)
+
+        return PackagePropertyReason(bcpf=bcpf_classes, other=other_classes)
+
+    def recurse_hiddenapi_packages_trie(self, node, result):
+        nodes = node.child_nodes()
+        if nodes:
+            for child in nodes:
+                # Ignore any non-package nodes.
+                if child.type != "package":
+                    continue
+
+                package = self.selector_to_java_reference(child)
+
+                providers = set(child.get_matching_rows("**"))
+                if not providers:
+                    # The package and all its sub packages contain no
+                    # classes. This should never happen.
+                    pass
+                elif providers == {ClassProvider.BCPF}:
+                    # The package and all its sub packages only contain
+                    # classes provided by the bootclasspath_fragment.
+                    logging.debug("Package '%s.**' is not split", package)
+                    result.package_prefixes.append(package)
+                    # There is no point traversing into the sub packages.
+                    continue
+                elif providers == {ClassProvider.OTHER}:
+                    # The package and all its sub packages contain no
+                    # classes provided by the bootclasspath_fragment.
+                    # There is no point traversing into the sub packages.
+                    logging.debug("Package '%s.**' contains no classes from %s",
+                                  package, self.bcpf)
+                    continue
+                elif ClassProvider.BCPF in providers:
+                    # The package and all its sub packages contain classes
+                    # provided by the bootclasspath_fragment and other
+                    # sources.
+                    logging.debug(
+                        "Package '%s.**' contains classes from "
+                        "%s and other sources", package, self.bcpf)
+
+                providers = set(child.get_matching_rows("*"))
+                if not providers:
+                    # The package contains no classes.
+                    logging.debug("Package: %s contains no classes", package)
+                elif providers == {ClassProvider.BCPF}:
+                    # The package only contains classes provided by the
+                    # bootclasspath_fragment.
+                    logging.debug(
+                        "Package '%s.*' is not split but does have "
+                        "sub-packages from other modules", package)
+
+                    # Partition the sub-packages into those that are provided by
+                    # this bootclasspath_fragment and those provided by other
+                    # modules. They can be used to explain the reason for the
+                    # single package to developers.
+                    reason = self.determine_reason_for_single_package(child)
+                    result.single_packages[package] = reason
+
+                elif providers == {ClassProvider.OTHER}:
+                    # The package contains no classes provided by the
+                    # bootclasspath_fragment. Child nodes make contain such
+                    # classes.
+                    logging.debug("Package '%s.*' contains no classes from %s",
+                                  package, self.bcpf)
+                elif ClassProvider.BCPF in providers:
+                    # The package contains classes provided by both the
+                    # bootclasspath_fragment and some other source.
+                    logging.debug("Package '%s.*' is split", package)
+
+                    # Partition the classes in this split package into those
+                    # that come from this bootclasspath_fragment and those that
+                    # come from other modules. That can be used to explain the
+                    # reason for the split package to developers.
+                    reason = self.determine_reason_for_split_package(child)
+                    result.split_packages[package] = reason
+
+                self.recurse_hiddenapi_packages_trie(child, result)
+
+
+def newline_stripping_iter(iterator):
+    """Return an iterator over the iterator that strips trailing white space."""
+    lines = iter(iterator, "")
+    lines = (line.rstrip() for line in lines)
+    return lines
+
+
+def format_comment_as_text(text, indent):
+    return "".join(
+        [f"{line}\n" for line in format_comment_as_lines(text, indent)])
+
+
+def format_comment_as_lines(text, indent):
+    lines = textwrap.wrap(text.strip("\n"), width=77 - len(indent))
+    lines = [f"{indent}// {line}" for line in lines]
+    return lines
+
+
+def log_stream_for_subprocess():
+    stream = subprocess.DEVNULL
+    for handler in logging.root.handlers:
+        if handler.level == logging.DEBUG:
+            if isinstance(handler, logging.StreamHandler):
+                stream = handler.stream
+    return stream
+
+
+def main(argv):
+    args_parser = argparse.ArgumentParser(
+        description="Analyze a bootclasspath_fragment module.")
+    args_parser.add_argument(
+        "--bcpf",
+        help="The bootclasspath_fragment module to analyze",
+        required=True,
+    )
+    args_parser.add_argument(
+        "--apex",
+        help="The apex module to which the bootclasspath_fragment belongs. It "
+        "is not strictly necessary at the moment but providing it will "
+        "allow this script to give more useful messages and it may be"
+        "required in future.",
+        default="SPECIFY-APEX-OPTION")
+    args_parser.add_argument(
+        "--sdk",
+        help="The sdk module to which the bootclasspath_fragment belongs. It "
+        "is not strictly necessary at the moment but providing it will "
+        "allow this script to give more useful messages and it may be"
+        "required in future.",
+        default="SPECIFY-SDK-OPTION")
+    args_parser.add_argument(
+        "--fix",
+        help="Attempt to fix any issues found automatically.",
+        action="store_true",
+        default=False)
+    args = args_parser.parse_args(argv[1:])
+    top_dir = os.environ["ANDROID_BUILD_TOP"] + "/"
+    out_dir = os.environ.get("OUT_DIR", os.path.join(top_dir, "out"))
+    product_out_dir = os.environ.get("ANDROID_PRODUCT_OUT", top_dir)
+    # Make product_out_dir relative to the top so it can be used as part of a
+    # build target.
+    product_out_dir = product_out_dir.removeprefix(top_dir)
+    log_fd, abs_log_file = tempfile.mkstemp(
+        suffix="_analyze_bcpf.log", text=True)
+
+    with os.fdopen(log_fd, "w") as log_file:
+        # Set up debug logging to the log file.
+        logging.basicConfig(
+            level=logging.DEBUG,
+            format="%(levelname)-8s %(message)s",
+            stream=log_file)
+
+        # define a Handler which writes INFO messages or higher to the
+        # sys.stdout with just the message.
+        console = logging.StreamHandler()
+        console.setLevel(logging.INFO)
+        console.setFormatter(logging.Formatter("%(message)s"))
+        # add the handler to the root logger
+        logging.getLogger("").addHandler(console)
+
+        print(f"Writing log to {abs_log_file}")
+        try:
+            analyzer = BcpfAnalyzer(
+                tool_path=argv[0],
+                top_dir=top_dir,
+                out_dir=out_dir,
+                product_out_dir=product_out_dir,
+                bcpf=args.bcpf,
+                apex=args.apex,
+                sdk=args.sdk,
+                fix=args.fix,
+            )
+            analyzer.analyze()
+        finally:
+            print(f"Log written to {abs_log_file}")
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/scripts/hiddenapi/analyze_bcpf_test.py b/scripts/hiddenapi/analyze_bcpf_test.py
new file mode 100644
index 0000000..a32ffd0
--- /dev/null
+++ b/scripts/hiddenapi/analyze_bcpf_test.py
@@ -0,0 +1,661 @@
+#!/usr/bin/env python
+#
+# 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.
+"""Unit tests for analyzing bootclasspath_fragment modules."""
+import os.path
+import shutil
+import tempfile
+import unittest
+import unittest.mock
+
+import sys
+
+import analyze_bcpf as ab
+
+_FRAMEWORK_HIDDENAPI = "frameworks/base/boot/hiddenapi"
+_MAX_TARGET_O = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-o.txt"
+_MAX_TARGET_P = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-p.txt"
+_MAX_TARGET_Q = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-q.txt"
+_MAX_TARGET_R = f"{_FRAMEWORK_HIDDENAPI}/hiddenapi-max-target-r-loprio.txt"
+
+_MULTI_LINE_COMMENT = """
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu justo,
+bibendum eu malesuada vel, fringilla in odio. Etiam gravida ultricies sem
+tincidunt luctus.""".replace("\n", " ").strip()
+
+
+class FakeBuildOperation(ab.BuildOperation):
+
+    def __init__(self, lines, return_code):
+        ab.BuildOperation.__init__(self, None)
+        self._lines = lines
+        self.returncode = return_code
+
+    def lines(self):
+        return iter(self._lines)
+
+    def wait(self, *args, **kwargs):
+        return
+
+
+class TestAnalyzeBcpf(unittest.TestCase):
+
+    def setUp(self):
+        # Create a temporary directory
+        self.test_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        # Remove the directory after the test
+        shutil.rmtree(self.test_dir)
+
+    @staticmethod
+    def write_abs_file(abs_path, contents):
+        os.makedirs(os.path.dirname(abs_path), exist_ok=True)
+        with open(abs_path, "w", encoding="utf8") as f:
+            print(contents.removeprefix("\n"), file=f, end="")
+
+    def populate_fs(self, fs):
+        for path, contents in fs.items():
+            abs_path = os.path.join(self.test_dir, path)
+            self.write_abs_file(abs_path, contents)
+
+    def create_analyzer_for_test(self,
+                                 fs=None,
+                                 bcpf="bcpf",
+                                 apex="apex",
+                                 sdk="sdk",
+                                 fix=False):
+        if fs:
+            self.populate_fs(fs)
+
+        top_dir = self.test_dir
+        out_dir = os.path.join(self.test_dir, "out")
+        product_out_dir = "out/product"
+
+        bcpf_dir = f"{bcpf}-dir"
+        modules = {bcpf: {"path": [bcpf_dir]}}
+        module_info = ab.ModuleInfo(modules)
+
+        analyzer = ab.BcpfAnalyzer(
+            tool_path=os.path.join(out_dir, "bin"),
+            top_dir=top_dir,
+            out_dir=out_dir,
+            product_out_dir=product_out_dir,
+            bcpf=bcpf,
+            apex=apex,
+            sdk=sdk,
+            fix=fix,
+            module_info=module_info,
+        )
+        analyzer.load_all_flags()
+        return analyzer
+
+    def test_reformat_report_text(self):
+        lines = """
+99. An item in a numbered list
+that traverses multiple lines.
+
+   An indented example
+   that should not be reformatted.
+"""
+        reformatted = ab.BcpfAnalyzer.reformat_report_test(lines)
+        self.assertEqual(
+            """
+99. An item in a numbered list that traverses multiple lines.
+
+   An indented example
+   that should not be reformatted.
+""", reformatted)
+
+    def do_test_build_flags(self, fix):
+        lines = """
+ERROR: Hidden API flags are inconsistent:
+< out/soong/.intermediates/bcpf-dir/bcpf-dir/filtered-flags.csv
+> out/soong/hiddenapi/hiddenapi-flags.csv
+
+< Lacme/test/Class;-><init>()V,blocked
+> Lacme/test/Class;-><init>()V,max-target-o
+
+< Lacme/test/Other;->getThing()Z,blocked
+> Lacme/test/Other;->getThing()Z,max-target-p
+
+< Lacme/test/Widget;-><init()V,blocked
+> Lacme/test/Widget;-><init()V,max-target-q
+
+< Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+> Lacme/test/Gadget;->NAME:Ljava/lang/String;,lo-prio,max-target-r
+16:37:32 ninja failed with: exit status 1
+""".strip().splitlines()
+        operation = FakeBuildOperation(lines=lines, return_code=1)
+
+        fs = {
+            _MAX_TARGET_O:
+                """
+Lacme/items/Magnet;->size:I
+Lacme/test/Class;-><init>()V
+""",
+            _MAX_TARGET_P:
+                """
+Lacme/items/Rocket;->size:I
+Lacme/test/Other;->getThing()Z
+""",
+            _MAX_TARGET_Q:
+                """
+Lacme/items/Rock;->size:I
+Lacme/test/Widget;-><init()V
+""",
+            _MAX_TARGET_R:
+                """
+Lacme/items/Lever;->size:I
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+                """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+""",
+            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+                """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;,blocked
+Lacme/test/Widget;-><init()V,blocked
+Lacme/test/Class;-><init>()V,blocked
+Lacme/test/Other;->getThing()Z,blocked
+""",
+        }
+
+        analyzer = self.create_analyzer_for_test(fs, fix=fix)
+
+        # Override the build_file_read_output() method to just return a fake
+        # build operation.
+        analyzer.build_file_read_output = unittest.mock.Mock(
+            return_value=operation)
+
+        # Override the run_command() method to do nothing.
+        analyzer.run_command = unittest.mock.Mock()
+
+        result = ab.Result()
+
+        analyzer.build_monolithic_flags(result)
+        expected_diffs = {
+            "Lacme/test/Gadget;->NAME:Ljava/lang/String;":
+                (["blocked"], ["lo-prio", "max-target-r"]),
+            "Lacme/test/Widget;-><init()V": (["blocked"], ["max-target-q"]),
+            "Lacme/test/Class;-><init>()V": (["blocked"], ["max-target-o"]),
+            "Lacme/test/Other;->getThing()Z": (["blocked"], ["max-target-p"])
+        }
+        self.assertEqual(expected_diffs, result.diffs, msg="flag differences")
+
+        expected_property_changes = [
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_o_low_priority",
+                values=["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_p",
+                values=["hiddenapi/hiddenapi-max-target-p.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_q",
+                values=["hiddenapi/hiddenapi-max-target-q.txt"],
+                property_comment=""),
+            ab.HiddenApiPropertyChange(
+                property_name="max_target_r_low_priority",
+                values=["hiddenapi/hiddenapi-max-target-r-low-priority.txt"],
+                property_comment=""),
+        ]
+        self.assertEqual(
+            expected_property_changes,
+            result.property_changes,
+            msg="property changes")
+
+        return result
+
+    def test_build_flags_report(self):
+        result = self.do_test_build_flags(fix=False)
+
+        expected_file_changes = [
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-o-low-priority.txt",
+                description="""Add the following entries:
+            Lacme/test/Class;-><init>()V
+""",
+            ),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+                description="""Add the following entries:
+            Lacme/test/Other;->getThing()Z
+""",
+            ),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+                description="""Add the following entries:
+            Lacme/test/Widget;-><init()V
+"""),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-r-low-priority.txt",
+                description="""Add the following entries:
+            Lacme/test/Gadget;->NAME:Ljava/lang/String;
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-o.txt",
+                description="""Remove the following entries:
+            Lacme/test/Class;-><init>()V
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-p.txt",
+                description="""Remove the following entries:
+            Lacme/test/Other;->getThing()Z
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-q.txt",
+                description="""Remove the following entries:
+            Lacme/test/Widget;-><init()V
+"""),
+            ab.FileChange(
+                path="frameworks/base/boot/hiddenapi/"
+                "hiddenapi-max-target-r-loprio.txt",
+                description="""Remove the following entries:
+            Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""")
+        ]
+        result.file_changes.sort()
+        self.assertEqual(
+            expected_file_changes, result.file_changes, msg="file_changes")
+
+    def test_build_flags_fix(self):
+        result = self.do_test_build_flags(fix=True)
+
+        expected_file_changes = [
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-o-low-priority.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt",
+                description="Added 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path="bcpf-dir/hiddenapi/"
+                "hiddenapi-max-target-r-low-priority.txt",
+                description="Created with 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_O,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_P,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_Q,
+                description="Removed 'bcpf' specific entries"),
+            ab.FileChange(
+                path=_MAX_TARGET_R,
+                description="Removed 'bcpf' specific entries")
+        ]
+
+        result.file_changes.sort()
+        self.assertEqual(
+            expected_file_changes, result.file_changes, msg="file_changes")
+
+        expected_file_contents = {
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-o-low-priority.txt":
+                """
+Lacme/test/Class;-><init>()V
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-p.txt":
+                """
+Lacme/old/Class;->getWidget()Lacme/test/Widget;
+Lacme/test/Other;->getThing()Z
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-q.txt":
+                """
+Lacme/test/Widget;-><init()V
+""",
+            "bcpf-dir/hiddenapi/hiddenapi-max-target-r-low-priority.txt":
+                """
+Lacme/test/Gadget;->NAME:Ljava/lang/String;
+""",
+            _MAX_TARGET_O:
+                """
+Lacme/items/Magnet;->size:I
+""",
+            _MAX_TARGET_P:
+                """
+Lacme/items/Rocket;->size:I
+""",
+            _MAX_TARGET_Q:
+                """
+Lacme/items/Rock;->size:I
+""",
+            _MAX_TARGET_R:
+                """
+Lacme/items/Lever;->size:I
+""",
+        }
+        for file_change in result.file_changes:
+            path = file_change.path
+            expected_contents = expected_file_contents[path].lstrip()
+            abs_path = os.path.join(self.test_dir, path)
+            with open(abs_path, "r", encoding="utf8") as tio:
+                contents = tio.read()
+                self.assertEqual(
+                    expected_contents, contents, msg=f"{path} contents")
+
+    def test_compute_hiddenapi_package_properties(self):
+        fs = {
+            "out/soong/.intermediates/bcpf-dir/bcpf/all-flags.csv":
+                """
+La/b/C;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+""",
+            "out/soong/hiddenapi/hiddenapi-flags.csv":
+                """
+La/b/C;->m()V
+La/b/D;->m()V
+La/b/E;->m()V
+La/b/c/D;->m()V
+La/b/c/E;->m()V
+La/b/c/d/E;->m()V
+La/b/c/d/e/F;->m()V
+Lb/c/D;->m()V
+Lb/c/E;->m()V
+Lb/c/d/E;->m()V
+"""
+        }
+        analyzer = self.create_analyzer_for_test(fs)
+        analyzer.load_all_flags()
+
+        result = ab.Result()
+        analyzer.compute_hiddenapi_package_properties(result)
+        self.assertEqual(["a.b"], list(result.split_packages.keys()))
+
+        reason = result.split_packages["a.b"]
+        self.assertEqual(["a.b.C"], reason.bcpf)
+        self.assertEqual(["a.b.D", "a.b.E"], reason.other)
+
+        self.assertEqual(["a.b.c"], list(result.single_packages.keys()))
+
+        reason = result.single_packages["a.b.c"]
+        self.assertEqual(["a.b.c"], reason.bcpf)
+        self.assertEqual(["a.b.c.d", "a.b.c.d.e"], reason.other)
+
+        self.assertEqual(["b"], result.package_prefixes)
+
+
+class TestHiddenApiPropertyChange(unittest.TestCase):
+
+    def setUp(self):
+        # Create a temporary directory
+        self.test_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        # Remove the directory after the test
+        shutil.rmtree(self.test_dir)
+
+    def check_change_fix(self, change, bpmodify_output, expected):
+        file = os.path.join(self.test_dir, "Android.bp")
+
+        with open(file, "w", encoding="utf8") as tio:
+            tio.write(bpmodify_output.strip("\n"))
+
+        bpmodify_runner = ab.BpModifyRunner(
+            os.path.join(os.path.dirname(sys.argv[0]), "bpmodify"))
+        change.fix_bp_file(file, "bcpf", bpmodify_runner)
+
+        with open(file, "r", encoding="utf8") as tio:
+            contents = tio.read()
+            self.assertEqual(expected.lstrip("\n"), contents)
+
+    def check_change_snippet(self, change, expected):
+        snippet = change.snippet("        ")
+        self.assertEqual(expected, snippet)
+
+    def test_change_property_with_value_no_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["android.provider"],
+        )
+
+        self.check_change_snippet(
+            change, """
+        split_packages: [
+            "android.provider",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+    },
+}
+""")
+
+    def test_change_property_with_value_and_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["android.provider"],
+            property_comment=_MULTI_LINE_COMMENT,
+        )
+
+        self.check_change_snippet(
+            change, """
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "android.provider",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "android.provider",
+        ],
+
+        single_packages: [
+            "android.system",
+        ],
+
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "android.provider",
+        ],
+
+        single_packages: [
+            "android.system",
+        ],
+
+    },
+}
+""")
+
+    def test_set_property_with_value_and_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=["another.provider", "other.system"],
+            property_comment=_MULTI_LINE_COMMENT,
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(
+            change, """
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut arcu
+        // justo, bibendum eu malesuada vel, fringilla in odio. Etiam gravida
+        // ultricies sem tincidunt luctus.
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+    },
+}
+""")
+
+    def test_set_property_with_no_value_or_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=[],
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(change, """
+        split_packages: [],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [
+            "another.provider",
+            "other.system",
+        ],
+        package_prefixes: ["android.provider"],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""")
+
+    def test_set_empty_property_with_no_value_or_comment(self):
+        change = ab.HiddenApiPropertyChange(
+            property_name="split_packages",
+            values=[],
+            action=ab.PropertyChangeAction.REPLACE,
+        )
+
+        self.check_change_snippet(change, """
+        split_packages: [],
+""")
+
+        self.check_change_fix(
+            change, """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""", """
+bootclasspath_fragment {
+    name: "bcpf",
+
+    // modified by the Soong or platform compat team.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+        split_packages: [],
+        package_prefixes: ["android.provider"],
+    },
+}
+""")
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=3)
diff --git a/scripts/hiddenapi/generate_hiddenapi_lists.py b/scripts/hiddenapi/generate_hiddenapi_lists.py
index 5ab93d1..6546c7f 100755
--- a/scripts/hiddenapi/generate_hiddenapi_lists.py
+++ b/scripts/hiddenapi/generate_hiddenapi_lists.py
@@ -16,8 +16,6 @@
 """Generate API lists for non-SDK API enforcement."""
 import argparse
 from collections import defaultdict, namedtuple
-import functools
-import os
 import re
 import sys
 
@@ -29,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'
@@ -43,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,
@@ -60,15 +60,15 @@
 # For example, the max-target-P list is checked in as it was in P,
 # but signatures have changes since then. The flag instructs this
 # script to skip any entries which do not exist any more.
-FLAG_IGNORE_CONFLICTS = "ignore-conflicts"
+FLAG_IGNORE_CONFLICTS = 'ignore-conflicts'
 
 # Option specified after one of FLAGS_API_LIST to express that all
 # apis within a given set of packages should be assign the given flag.
-FLAG_PACKAGES = "packages"
+FLAG_PACKAGES = 'packages'
 
 # Option specified after one of FLAGS_API_LIST to indicate an extra
 # tag that should be added to the matching APIs.
-FLAG_TAG = "tag"
+FLAG_TAG = 'tag'
 
 # Regex patterns of fields/methods used in serialization. These are
 # considered public API despite being hidden.
@@ -84,24 +84,30 @@
 
 # Single regex used to match serialization API. It combines all the
 # SERIALIZATION_PATTERNS into a single regular expression.
-SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
+SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) +
+                                 r')$')
 
 # Predicates to be used with filter_apis.
-HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
+HAS_NO_API_LIST_ASSIGNED = \
+    lambda api,flags: not FLAGS_API_LIST_SET.intersection(flags)
+
 IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
 
 
 class StoreOrderedOptions(argparse.Action):
-    """An argparse action that stores a number of option arguments in the order that
-    they were specified.
+    """An argparse action that stores a number of option arguments in the order
+
+    that they were specified.
     """
-    def __call__(self, parser, args, values, option_string = None):
+
+    def __call__(self, parser, args, values, option_string=None):
         items = getattr(args, self.dest, None)
         if items is None:
             items = []
         items.append([option_string.lstrip('-'), values])
         setattr(args, self.dest, items)
 
+
 def get_args():
     """Parses command line arguments.
 
@@ -110,22 +116,43 @@
     """
     parser = argparse.ArgumentParser()
     parser.add_argument('--output', required=True)
-    parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
+    parser.add_argument(
+        '--csv',
+        nargs='*',
+        default=[],
+        metavar='CSV_FILE',
         help='CSV files to be merged into output')
 
     for flag in ALL_FLAGS:
-        parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE',
-            action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"')
-    parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0,
-        action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned '
-        'entries should be assign the given flag. Must follow a list of entries and applies '
-        'to the preceding such list.')
-    parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0,
-        action=StoreOrderedOptions, help='Indicates that the previous list of entries '
-        'is a list of packages. All members in those packages will be given the flag. '
-        'Must follow a list of entries and applies to the preceding such list.')
-    parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1,
-        action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. '
+        parser.add_argument(
+            '--' + flag,
+            dest='ordered_flags',
+            metavar='TXT_FILE',
+            action=StoreOrderedOptions,
+            help='lists of entries with flag "' + flag + '"')
+    parser.add_argument(
+        '--' + FLAG_IGNORE_CONFLICTS,
+        dest='ordered_flags',
+        nargs=0,
+        action=StoreOrderedOptions,
+        help='Indicates that only known and otherwise unassigned '
+        'entries should be assign the given flag. Must follow a list of '
+        'entries and applies to the preceding such list.')
+    parser.add_argument(
+        '--' + FLAG_PACKAGES,
+        dest='ordered_flags',
+        nargs=0,
+        action=StoreOrderedOptions,
+        help='Indicates that the previous list of entries '
+        'is a list of packages. All members in those packages will be given '
+        'the flag. Must follow a list of entries and applies to the preceding '
+        'such list.')
+    parser.add_argument(
+        '--' + FLAG_TAG,
+        dest='ordered_flags',
+        nargs=1,
+        action=StoreOrderedOptions,
+        help='Adds an extra tag to the previous list of entries. '
         'Must follow a list of entries and applies to the preceding such list.')
 
     return parser.parse_args()
@@ -143,9 +170,9 @@
         Lines of the file as a list of string.
     """
     with open(filename, 'r') as f:
-        lines = f.readlines();
-    lines = filter(lambda line: not line.startswith('#'), lines)
-    lines = map(lambda line: line.strip(), lines)
+        lines = f.readlines()
+    lines = [line for line in lines if not line.startswith('#')]
+    lines = [line.strip() for line in lines]
     return set(lines)
 
 
@@ -156,7 +183,7 @@
         filename (string): Path to the file to be writing into.
         lines (list): List of strings to write into the file.
     """
-    lines = map(lambda line: line + '\n', lines)
+    lines = [line + '\n' for line in lines]
     with open(filename, 'w') as f:
         f.writelines(lines)
 
@@ -170,17 +197,19 @@
     Returns:
         The package name of the class containing the field/method.
     """
-    full_class_name = signature.split(";->")[0]
+    full_class_name = signature.split(';->')[0]
     # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
-    if (full_class_name[0] != "L"):
-        raise ValueError("Expected to start with 'L': %s" % full_class_name)
+    if full_class_name[0] != 'L':
+        raise ValueError("Expected to start with 'L': %s"
+                         % full_class_name)
     full_class_name = full_class_name[1:]
     # If full_class_name doesn't contain '/', then package_name will be ''.
-    package_name = full_class_name.rpartition("/")[0]
+    package_name = full_class_name.rpartition('/')[0]
     return package_name.replace('/', '.')
 
 
 class FlagsDict:
+
     def __init__(self):
         self._dict_keyset = set()
         self._dict = defaultdict(set)
@@ -188,37 +217,43 @@
     def _check_entries_set(self, keys_subset, source):
         assert isinstance(keys_subset, set)
         assert keys_subset.issubset(self._dict_keyset), (
-            "Error: {} specifies signatures not present in code:\n"
-            "{}"
-            "Please visit go/hiddenapi for more information.").format(
-                source, "".join(map(lambda x: "  " + str(x) + "\n", keys_subset - self._dict_keyset)))
+            'Error: {} specifies signatures not present in code:\n'
+            '{}'
+            'Please visit go/hiddenapi for more information.').format(
+                source, ''.join(
+                    ['  ' + str(x) + '\n' for x in
+                     keys_subset - self._dict_keyset]))
 
     def _check_flags_set(self, flags_subset, source):
         assert isinstance(flags_subset, set)
         assert flags_subset.issubset(ALL_FLAGS_SET), (
-            "Error processing: {}\n"
-            "The following flags were not recognized: \n"
-            "{}\n"
-            "Please visit go/hiddenapi for more information.").format(
-                source, "\n".join(flags_subset - ALL_FLAGS_SET))
+            'Error processing: {}\n'
+            'The following flags were not recognized: \n'
+            '{}\n'
+            'Please visit go/hiddenapi for more information.').format(
+                source, '\n'.join(flags_subset - ALL_FLAGS_SET))
 
     def filter_apis(self, filter_fn):
         """Returns APIs which match a given predicate.
 
-        This is a helper function which allows to filter on both signatures (keys) and
-        flags (values). The built-in filter() invokes the lambda only with dict's keys.
+        This is a helper function which allows to filter on both signatures
+        (keys) and
+        flags (values). The built-in filter() invokes the lambda only with
+        dict's keys.
 
         Args:
-            filter_fn : Function which takes two arguments (signature/flags) and returns a boolean.
+            filter_fn : Function which takes two arguments (signature/flags) and
+              returns a boolean.
 
         Returns:
             A set of APIs which match the predicate.
         """
-        return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset))
+        return {x for x in self._dict_keyset if filter_fn(x, self._dict[x])}
 
     def get_valid_subset_of_unassigned_apis(self, api_subset):
-        """Sanitizes a key set input to only include keys which exist in the dictionary
-        and have not been assigned any API list flags.
+        """Sanitizes a key set input to only include keys which exist in the
+
+        dictionary and have not been assigned any API list flags.
 
         Args:
             entries_subset (set/list): Key set to be sanitized.
@@ -227,7 +262,8 @@
             Sanitized key set.
         """
         assert isinstance(api_subset, set)
-        return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
+        return api_subset.intersection(
+            self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
 
     def generate_csv(self):
         """Constructs CSV entries from a dictionary.
@@ -235,15 +271,16 @@
         Old versions of flags are used to generate the file.
 
         Returns:
-            List of lines comprising a CSV file. See "parse_and_merge_csv" for format description.
+            List of lines comprising a CSV file. See "parse_and_merge_csv" for
+            format description.
         """
         lines = []
         for api in self._dict:
-          flags = sorted(self._dict[api])
-          lines.append(",".join([api] + flags))
+            flags = sorted(self._dict[api])
+            lines.append(','.join([api] + flags))
         return sorted(lines)
 
-    def parse_and_merge_csv(self, csv_lines, source = "<unknown>"):
+    def parse_and_merge_csv(self, csv_lines, source='<unknown>'):
         """Parses CSV entries and merges them into a given dictionary.
 
         The expected CSV format is:
@@ -251,21 +288,20 @@
 
         Args:
             csv_lines (list of strings): Lines read from a CSV file.
-            source (string): Origin of `csv_lines`. Will be printed in error messages.
-
-        Throws:
-            AssertionError if parsed flags are invalid.
+            source (string): Origin of `csv_lines`. Will be printed in error
+              messages.
+        Throws: AssertionError if parsed flags are invalid.
         """
         # Split CSV lines into arrays of values.
-        csv_values = [ line.split(',') for line in csv_lines ]
+        csv_values = [line.split(',') for line in csv_lines]
 
         # Update the full set of API signatures.
-        self._dict_keyset.update([ csv[0] for csv in csv_values ])
+        self._dict_keyset.update([csv[0] for csv in csv_values])
 
         # Check that all flags are known.
         csv_flags = set()
         for csv in csv_values:
-          csv_flags.update(csv[1:])
+            csv_flags.update(csv[1:])
         self._check_flags_set(csv_flags, source)
 
         # Iterate over all CSV lines, find entry in dict and append flags to it.
@@ -275,47 +311,53 @@
                 flags.append(FLAG_SDK)
             self._dict[csv[0]].update(flags)
 
-    def assign_flag(self, flag, apis, source="<unknown>", tag = None):
+    def assign_flag(self, flag, apis, source='<unknown>', tag=None):
         """Assigns a flag to given subset of entries.
 
         Args:
             flag (string): One of ALL_FLAGS.
             apis (set): Subset of APIs to receive the flag.
-            source (string): Origin of `entries_subset`. Will be printed in error messages.
-
-        Throws:
-            AssertionError if parsed API signatures of flags are invalid.
+            source (string): Origin of `entries_subset`. Will be printed in
+              error messages.
+        Throws: AssertionError if parsed API signatures of flags are invalid.
         """
         # Check that all APIs exist in the dict.
         self._check_entries_set(apis, source)
 
         # Check that the flag is known.
-        self._check_flags_set(set([ flag ]), source)
+        self._check_flags_set(set([flag]), source)
 
-        # Iterate over the API subset, find each entry in dict and assign the flag to it.
+        # Iterate over the API subset, find each entry in dict and assign the
+        # flag to it.
         for api in apis:
             self._dict[api].add(flag)
             if tag:
                 self._dict[api].add(tag)
 
 
-FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
+FlagFile = namedtuple('FlagFile',
+                      ('flag', 'file', 'ignore_conflicts', 'packages', 'tag'))
+
 
 def parse_ordered_flags(ordered_flags):
     r = []
-    currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None
+    currentflag, file, ignore_conflicts, packages, tag = None, None, False, \
+        False, None
     for flag_value in ordered_flags:
         flag, value = flag_value[0], flag_value[1]
         if flag in ALL_FLAGS_SET:
             if currentflag:
-                r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
+                r.append(
+                    FlagFile(currentflag, file, ignore_conflicts, packages,
+                             tag))
                 ignore_conflicts, packages, tag = False, False, None
             currentflag = flag
             file = value
         else:
             if currentflag is None:
-                raise argparse.ArgumentError('--%s is only allowed after one of %s' % (
-                    flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
+                raise argparse.ArgumentError( #pylint: disable=no-value-for-parameter
+                    '--%s is only allowed after one of %s' %
+                    (flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET])))
             if flag == FLAG_IGNORE_CONFLICTS:
                 ignore_conflicts = True
             elif flag == FLAG_PACKAGES:
@@ -323,13 +365,12 @@
             elif flag == FLAG_TAG:
                 tag = value[0]
 
-
     if currentflag:
         r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag))
     return r
 
 
-def main(argv):
+def main(argv): #pylint: disable=unused-argument
     # Parse arguments.
     args = vars(get_args())
     flagfiles = parse_ordered_flags(args['ordered_flags'] or [])
@@ -342,7 +383,7 @@
     # contain the full set of APIs. Subsequent additions from text files
     # will be able to detect invalid entries, and/or filter all as-yet
     # unassigned entries.
-    for filename in args["csv"]:
+    for filename in args['csv']:
         flags.parse_and_merge_csv(read_lines(filename), filename)
 
     # Combine inputs which do not require any particular order.
@@ -352,24 +393,28 @@
     # (2) Merge text files with a known flag into the dictionary.
     for info in flagfiles:
         if (not info.ignore_conflicts) and (not info.packages):
-            flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag)
+            flags.assign_flag(info.flag, read_lines(info.file), info.file,
+                              info.tag)
 
     # Merge text files where conflicts should be ignored.
     # This will only assign the given flag if:
     # (a) the entry exists, and
     # (b) it has not been assigned any other flag.
-    # Because of (b), this must run after all strict assignments have been performed.
+    # Because of (b), this must run after all strict assignments have been
+    # performed.
     for info in flagfiles:
         if info.ignore_conflicts:
-            valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file))
-            flags.assign_flag(info.flag, valid_entries, filename, info.tag)
+            valid_entries = flags.get_valid_subset_of_unassigned_apis(
+                read_lines(info.file))
+            flags.assign_flag(info.flag, valid_entries, filename, info.tag) #pylint: disable=undefined-loop-variable
 
-    # All members in the specified packages will be assigned the appropriate flag.
+    # All members in the specified packages will be assigned the appropriate
+    # flag.
     for info in flagfiles:
         if info.packages:
             packages_needing_list = set(read_lines(info.file))
-            should_add_signature_to_list = lambda sig,lists: extract_package(
-                sig) in packages_needing_list and not lists
+            should_add_signature_to_list = lambda sig, lists: extract_package(
+                sig) in packages_needing_list and not lists #pylint: disable=cell-var-from-loop
             valid_entries = flags.filter_apis(should_add_signature_to_list)
             flags.assign_flag(info.flag, valid_entries, info.file, info.tag)
 
@@ -377,7 +422,8 @@
     flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
 
     # Write output.
-    write_lines(args["output"], flags.generate_csv())
+    write_lines(args['output'], flags.generate_csv())
 
-if __name__ == "__main__":
+
+if __name__ == '__main__':
     main(sys.argv)
diff --git a/scripts/hiddenapi/generate_hiddenapi_lists_test.py b/scripts/hiddenapi/generate_hiddenapi_lists_test.py
index ff3d708..204de97 100755
--- a/scripts/hiddenapi/generate_hiddenapi_lists_test.py
+++ b/scripts/hiddenapi/generate_hiddenapi_lists_test.py
@@ -15,34 +15,39 @@
 # limitations under the License.
 """Unit tests for Hidden API list generation."""
 import unittest
-from generate_hiddenapi_lists import *
+from generate_hiddenapi_lists import *  # pylint: disable=wildcard-import,unused-wildcard-import
+
 
 class TestHiddenapiListGeneration(unittest.TestCase):
-
     def test_filter_apis(self):
         # Initialize flags so that A and B are put on the allow list and
         # C, D, E are left unassigned. Try filtering for the unassigned ones.
         flags = FlagsDict()
-        flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B,' + FLAG_SDK,
-                        'C', 'D', 'E'])
+        flags.parse_and_merge_csv(
+            ['A,' + FLAG_SDK, 'B,' + FLAG_SDK, 'C', 'D', 'E']
+        )
         filter_set = flags.filter_apis(lambda api, flags: not flags)
         self.assertTrue(isinstance(filter_set, set))
-        self.assertEqual(filter_set, set([ 'C', 'D', 'E' ]))
+        self.assertEqual(filter_set, set(['C', 'D', 'E']))
 
     def test_get_valid_subset_of_unassigned_keys(self):
         # Create flags where only A is unassigned.
         flags = FlagsDict()
         flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C'])
         flags.assign_flag(FLAG_UNSUPPORTED, set(['C']))
-        self.assertEqual(flags.generate_csv(),
-            [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ])
+        self.assertEqual(
+            flags.generate_csv(),
+            ['A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED],
+        )
 
         # Check three things:
         # (1) B is selected as valid unassigned
         # (2) A is not selected because it is assigned to the allow list
         # (3) D is not selected because it is not a valid key
         self.assertEqual(
-            flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ]))
+            flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])),
+            set(['B']),
+        )
 
     def test_parse_and_merge_csv(self):
         flags = FlagsDict()
@@ -51,41 +56,48 @@
         self.assertEqual(flags.generate_csv(), [])
 
         # Test new additions.
-        flags.parse_and_merge_csv([
-            'A,' + FLAG_UNSUPPORTED,
-            'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
-            'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
-            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
-        ])
-        self.assertEqual(flags.generate_csv(), [
-            'A,' + FLAG_UNSUPPORTED,
-            'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
-            'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
-            'D,' + FLAG_TEST_API + ',' + FLAG_UNSUPPORTED,
-            'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
-        ])
+        flags.parse_and_merge_csv(
+            [
+                'A,' + FLAG_UNSUPPORTED,
+                'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O,
+                'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
+                'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API,
+                'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
+            ]
+        )
+        self.assertEqual(
+            flags.generate_csv(),
+            [
+                'A,' + FLAG_UNSUPPORTED,
+                'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O,
+                'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API,
+                'D,' + FLAG_TEST_API + ',' + FLAG_UNSUPPORTED,
+                'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API,
+            ],
+        )
 
         # Test unknown flag.
         with self.assertRaises(AssertionError):
-            flags.parse_and_merge_csv([ 'Z,foo' ])
+            flags.parse_and_merge_csv(['Z,foo'])
 
     def test_assign_flag(self):
         flags = FlagsDict()
         flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B'])
 
         # Test new additions.
-        flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ]))
-        self.assertEqual(flags.generate_csv(),
-            [ 'A,' + FLAG_SDK + "," + FLAG_UNSUPPORTED, 'B,' + FLAG_UNSUPPORTED ])
+        flags.assign_flag(FLAG_UNSUPPORTED, set(['A', 'B']))
+        self.assertEqual(
+            flags.generate_csv(),
+            ['A,' + FLAG_SDK + "," + FLAG_UNSUPPORTED, 'B,' + FLAG_UNSUPPORTED],
+        )
 
         # Test invalid API signature.
         with self.assertRaises(AssertionError):
-            flags.assign_flag(FLAG_SDK, set([ 'C' ]))
+            flags.assign_flag(FLAG_SDK, set(['C']))
 
         # Test invalid flag.
         with self.assertRaises(AssertionError):
-            flags.assign_flag('foo', set([ 'A' ]))
+            flags.assign_flag('foo', set(['A']))
 
     def test_extract_package(self):
         signature = 'Lcom/foo/bar/Baz;->method1()Lcom/bar/Baz;'
@@ -100,5 +112,6 @@
         expected_package = 'com.foo_bar.baz'
         self.assertEqual(extract_package(signature), expected_package)
 
+
 if __name__ == '__main__':
-    unittest.main()
+    unittest.main(verbosity=2)
diff --git a/scripts/hiddenapi/merge_csv.py b/scripts/hiddenapi/merge_csv.py
index a65326c..c17ec25 100755
--- a/scripts/hiddenapi/merge_csv.py
+++ b/scripts/hiddenapi/merge_csv.py
@@ -13,8 +13,7 @@
 # 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.
-"""
-Merge multiple CSV files, possibly with different columns.
+"""Merge multiple CSV files, possibly with different columns.
 """
 
 import argparse
@@ -26,34 +25,52 @@
 
 from zipfile import ZipFile
 
-args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
-args_parser.add_argument('--header', help='Comma separated field names; '
-                                          'if missing determines the header from input files.')
-args_parser.add_argument('--zip_input', help='Treat files as ZIP archives containing CSV files to merge.',
-                         action="store_true")
-args_parser.add_argument('--key_field', help='The name of the field by which the rows should be sorted. '
-                                             'Must be in the field names. '
-                                             'Will be the first field in the output. '
-                                             'All input files must be sorted by that field.')
-args_parser.add_argument('--output', help='Output file for merged CSV.',
-                         default='-', type=argparse.FileType('w'))
+args_parser = argparse.ArgumentParser(
+    description='Merge given CSV files into a single one.'
+)
+args_parser.add_argument(
+    '--header',
+    help='Comma separated field names; '
+    'if missing determines the header from input files.',
+)
+args_parser.add_argument(
+    '--zip_input',
+    help='Treat files as ZIP archives containing CSV files to merge.',
+    action="store_true",
+)
+args_parser.add_argument(
+    '--key_field',
+    help='The name of the field by which the rows should be sorted. '
+    'Must be in the field names. '
+    'Will be the first field in the output. '
+    'All input files must be sorted by that field.',
+)
+args_parser.add_argument(
+    '--output',
+    help='Output file for merged CSV.',
+    default='-',
+    type=argparse.FileType('w'),
+)
 args_parser.add_argument('files', nargs=argparse.REMAINDER)
 args = args_parser.parse_args()
 
 
-def dict_reader(input):
-    return csv.DictReader(input, delimiter=',', quotechar='|')
+def dict_reader(csvfile):
+    return csv.DictReader(csvfile, delimiter=',', quotechar='|')
+
 
 csv_readers = []
-if not(args.zip_input):
+if not args.zip_input:
     for file in args.files:
         csv_readers.append(dict_reader(open(file, 'r')))
 else:
     for file in args.files:
-        with ZipFile(file) as zip:
-            for entry in zip.namelist():
+        with ZipFile(file) as zipfile:
+            for entry in zipfile.namelist():
                 if entry.endswith('.uau'):
-                    csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+                    csv_readers.append(
+                        dict_reader(io.TextIOWrapper(zipfile.open(entry, 'r')))
+                    )
 
 if args.header:
     fieldnames = args.header.split(',')
@@ -73,8 +90,8 @@
     keyField = args.key_field
     if keyField:
         assert keyField in fieldnames, (
-            "--key_field {} not found, must be one of {}\n").format(
-            keyField, ",".join(fieldnames))
+            "--key_field {} not found, must be one of {}\n"
+        ).format(keyField, ",".join(fieldnames))
         # Make the key field the first field in the output
         keyFieldIndex = fieldnames.index(args.key_field)
         fieldnames.insert(0, fieldnames.pop(keyFieldIndex))
@@ -83,11 +100,17 @@
         all_rows = heapq.merge(*csv_readers, key=operator.itemgetter(keyField))
 
 # Write all rows from the input files to the output:
-writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
-                        dialect='unix', fieldnames=fieldnames)
+writer = csv.DictWriter(
+    args.output,
+    delimiter=',',
+    quotechar='|',
+    quoting=csv.QUOTE_MINIMAL,
+    dialect='unix',
+    fieldnames=fieldnames,
+)
 writer.writeheader()
 
 # Read all the rows from the input and write them to the output in the correct
 # order:
 for row in all_rows:
-  writer.writerow(row)
+    writer.writerow(row)
diff --git a/scripts/hiddenapi/signature_patterns.py b/scripts/hiddenapi/signature_patterns.py
new file mode 100755
index 0000000..5a82be7
--- /dev/null
+++ b/scripts/hiddenapi/signature_patterns.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Generate a set of signature patterns for a bootclasspath_fragment.
+
+The patterns are generated from the modular flags produced by the
+bootclasspath_fragment and are used to select a subset of the monolithic flags
+against which the modular flags can be compared.
+"""
+
+import argparse
+import csv
+import sys
+
+
+def dict_reader(csv_file):
+    return csv.DictReader(
+        csv_file, delimiter=',', quotechar='|', fieldnames=['signature'])
+
+
+def dot_package_to_slash_package(pkg):
+    return pkg.replace('.', '/')
+
+
+def dot_packages_to_slash_packages(pkgs):
+    return [dot_package_to_slash_package(p) for p in pkgs]
+
+
+def slash_package_to_dot_package(pkg):
+    return pkg.replace('/', '.')
+
+
+def slash_packages_to_dot_packages(pkgs):
+    return [slash_package_to_dot_package(p) for p in pkgs]
+
+
+def is_split_package(split_packages, pkg):
+    return split_packages and (pkg in split_packages or '*' in split_packages)
+
+
+def matched_by_package_prefix_pattern(package_prefixes, prefix):
+    for packagePrefix in package_prefixes:
+        if prefix == packagePrefix:
+            return packagePrefix
+        if (prefix.startswith(packagePrefix) and
+                prefix[len(packagePrefix)] == '/'):
+            return packagePrefix
+    return False
+
+
+def validate_package_is_not_matched_by_package_prefix(package_type, pkg,
+                                                      package_prefixes):
+    package_prefix = matched_by_package_prefix_pattern(package_prefixes, pkg)
+    if package_prefix:
+        # A package prefix matches the package.
+        package_for_output = slash_package_to_dot_package(pkg)
+        package_prefix_for_output = slash_package_to_dot_package(package_prefix)
+        return [
+            f'{package_type} {package_for_output} is matched by '
+            f'package prefix {package_prefix_for_output}'
+        ]
+    return []
+
+
+def validate_package_prefixes(split_packages, single_packages,
+                              package_prefixes):
+    # If there are no package prefixes then there is no possible conflict
+    # between them and the split packages.
+    if len(package_prefixes) == 0:
+        return []
+
+    # Check to make sure that the split packages and package prefixes do not
+    # overlap.
+    errors = []
+    for split_package in split_packages:
+        if split_package == '*':
+            # A package prefix matches a split package.
+            package_prefixes_for_output = ', '.join(
+                slash_packages_to_dot_packages(package_prefixes))
+            errors.append(
+                "split package '*' conflicts with all package prefixes "
+                f'{package_prefixes_for_output}\n'
+                '    add split_packages:[] to fix')
+        else:
+            errs = validate_package_is_not_matched_by_package_prefix(
+                'split package', split_package, package_prefixes)
+            errors.extend(errs)
+
+    # Check to make sure that the single packages and package prefixes do not
+    # overlap.
+    for single_package in single_packages:
+        errs = validate_package_is_not_matched_by_package_prefix(
+            'single package', single_package, package_prefixes)
+        errors.extend(errs)
+    return errors
+
+
+def validate_split_packages(split_packages):
+    errors = []
+    if '*' in split_packages and len(split_packages) > 1:
+        errors.append('split packages are invalid as they contain both the'
+                      ' wildcard (*) and specific packages, use the wildcard or'
+                      ' specific packages, not a mixture')
+    return errors
+
+
+def validate_single_packages(split_packages, single_packages):
+    overlaps = []
+    for single_package in single_packages:
+        if single_package in split_packages:
+            overlaps.append(single_package)
+    if overlaps:
+        indented = ''.join([f'\n    {o}' for o in overlaps])
+        return [
+            f'single_packages and split_packages overlap, please ensure the '
+            f'following packages are only present in one:{indented}'
+        ]
+    return []
+
+
+def produce_patterns_from_file(file,
+                               split_packages=None,
+                               single_packages=None,
+                               package_prefixes=None):
+    with open(file, 'r', encoding='utf8') as f:
+        return produce_patterns_from_stream(f, split_packages, single_packages,
+                                            package_prefixes)
+
+
+def produce_patterns_from_stream(stream,
+                                 split_packages=None,
+                                 single_packages=None,
+                                 package_prefixes=None):
+    split_packages = set(split_packages or [])
+    single_packages = set(single_packages or [])
+    package_prefixes = list(package_prefixes or [])
+    # Read in all the signatures into a list and remove any unnecessary class
+    # and member names.
+    patterns = set()
+    unmatched_packages = set()
+    for row in dict_reader(stream):
+        signature = row['signature']
+        text = signature.removeprefix('L')
+        # Remove the class specific member signature
+        pieces = text.split(';->')
+        qualified_class_name = pieces[0]
+        pieces = qualified_class_name.rsplit('/', maxsplit=1)
+        pkg = pieces[0]
+        # If the package is split across multiple modules then it cannot be used
+        # to select the subset of the monolithic flags that this module
+        # produces. In that case we need to keep the name of the class but can
+        # discard any nested class names as an outer class cannot be split
+        # across modules.
+        #
+        # If the package is not split then every class in the package must be
+        # provided by this module so there is no need to list the classes
+        # explicitly so just use the package name instead.
+        if is_split_package(split_packages, pkg):
+            # Remove inner class names.
+            pieces = qualified_class_name.split('$', maxsplit=1)
+            pattern = pieces[0]
+            patterns.add(pattern)
+        elif pkg in single_packages:
+            # Add a * to ensure that the pattern matches the classes in that
+            # package.
+            pattern = pkg + '/*'
+            patterns.add(pattern)
+        else:
+            unmatched_packages.add(pkg)
+
+    # Remove any unmatched packages that would be matched by a package prefix
+    # pattern.
+    unmatched_packages = [
+        p for p in unmatched_packages
+        if not matched_by_package_prefix_pattern(package_prefixes, p)
+    ]
+    errors = []
+    if unmatched_packages:
+        unmatched_packages.sort()
+        indented = ''.join([
+            f'\n    {slash_package_to_dot_package(p)}'
+            for p in unmatched_packages
+        ])
+        errors.append('The following packages were unexpected, please add them '
+                      'to one of the hidden_api properties, split_packages, '
+                      f'single_packages or package_prefixes:{indented}')
+
+    # Remove any patterns that would be matched by a package prefix pattern.
+    patterns = [
+        p for p in patterns
+        if not matched_by_package_prefix_pattern(package_prefixes, p)
+    ]
+    # Add the package prefix patterns to the list. Add a ** to ensure that each
+    # package prefix pattern will match the classes in that package and all
+    # sub-packages.
+    patterns = patterns + [f'{p}/**' for p in package_prefixes]
+    # Sort the patterns.
+    patterns.sort()
+    return patterns, errors
+
+
+def print_and_exit(errors):
+    for error in errors:
+        print(error)
+    sys.exit(1)
+
+
+def main(args):
+    args_parser = argparse.ArgumentParser(
+        description='Generate a set of signature patterns '
+        'that select a subset of monolithic hidden API files.')
+    args_parser.add_argument(
+        '--flags',
+        help='The stub flags file which contains an entry for every dex member',
+    )
+    args_parser.add_argument(
+        '--split-package',
+        action='append',
+        help='A package that is split across multiple bootclasspath_fragment '
+        'modules')
+    args_parser.add_argument(
+        '--package-prefix',
+        action='append',
+        help='A package prefix unique to this set of flags')
+    args_parser.add_argument(
+        '--single-package',
+        action='append',
+        help='A single package unique to this set of flags')
+    args_parser.add_argument('--output', help='Generated signature prefixes')
+    args = args_parser.parse_args(args)
+
+    split_packages = set(
+        dot_packages_to_slash_packages(args.split_package or []))
+    errors = validate_split_packages(split_packages)
+    if errors:
+        print_and_exit(errors)
+
+    single_packages = list(
+        dot_packages_to_slash_packages(args.single_package or []))
+
+    errors = validate_single_packages(split_packages, single_packages)
+    if errors:
+        print_and_exit(errors)
+
+    package_prefixes = dot_packages_to_slash_packages(args.package_prefix or [])
+
+    errors = validate_package_prefixes(split_packages, single_packages,
+                                       package_prefixes)
+    if errors:
+        print_and_exit(errors)
+
+    patterns = []
+    # Read in all the patterns into a list.
+    patterns, errors = produce_patterns_from_file(args.flags, split_packages,
+                                                  single_packages,
+                                                  package_prefixes)
+
+    if errors:
+        print_and_exit(errors)
+
+    # Write out all the patterns.
+    with open(args.output, 'w', encoding='utf8') as outputFile:
+        for pattern in patterns:
+            outputFile.write(pattern)
+            outputFile.write('\n')
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/scripts/hiddenapi/signature_patterns_test.py b/scripts/hiddenapi/signature_patterns_test.py
new file mode 100755
index 0000000..90b3d0b
--- /dev/null
+++ b/scripts/hiddenapi/signature_patterns_test.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Unit tests for signature_patterns.py."""
+import io
+import unittest
+
+import signature_patterns
+
+
+class TestGeneratedPatterns(unittest.TestCase):
+
+    csv_flags = """
+Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked
+Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,public-api
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
+"""
+
+    @staticmethod
+    def produce_patterns_from_string(csv_text,
+                                     split_packages=None,
+                                     single_packages=None,
+                                     package_prefixes=None):
+        with io.StringIO(csv_text) as f:
+            return signature_patterns.produce_patterns_from_stream(
+                f, split_packages, single_packages, package_prefixes)
+
+    def test_generate_unmatched(self):
+        _, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags)
+        self.assertEqual([
+            'The following packages were unexpected, please add them to one of '
+            'the hidden_api properties, split_packages, single_packages or '
+            'package_prefixes:\n'
+            '    java.lang'
+        ], errors)
+
+    def test_generate_default(self):
+        patterns, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags, single_packages=['java/lang'])
+        self.assertEqual([], errors)
+
+        expected = [
+            'java/lang/*',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_split_package(self):
+        patterns, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags, split_packages={'java/lang'})
+        self.assertEqual([], errors)
+
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_split_package_wildcard(self):
+        patterns, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags, split_packages={'*'})
+        self.assertEqual([], errors)
+
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix(self):
+        patterns, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags, package_prefixes={'java/lang'})
+        self.assertEqual([], errors)
+
+        expected = [
+            'java/lang/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix_top_package(self):
+        patterns, errors = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csv_flags, package_prefixes={'java'})
+        self.assertEqual([], errors)
+
+        expected = [
+            'java/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_split_package_wildcard_conflicts_with_other_split_packages(self):
+        errors = signature_patterns.validate_split_packages({'*', 'java'})
+        expected = [
+            'split packages are invalid as they contain both the wildcard (*)'
+            ' and specific packages, use the wildcard or specific packages,'
+            ' not a mixture'
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_wildcard_conflicts_with_package_prefixes(self):
+        errors = signature_patterns.validate_package_prefixes(
+            {'*'}, [], package_prefixes={'java'})
+        expected = [
+            "split package '*' conflicts with all package prefixes java\n"
+            '    add split_packages:[] to fix',
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_conflicts_with_package_prefixes(self):
+        errors = signature_patterns.validate_package_prefixes(
+            {'java/split'}, [], package_prefixes={'java'})
+        expected = [
+            'split package java.split is matched by package prefix java',
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_single_package_conflicts_with_package_prefixes(self):
+        errors = signature_patterns.validate_package_prefixes(
+            {}, ['java/single'], package_prefixes={'java'})
+        expected = [
+            'single package java.single is matched by package prefix java',
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_single_package_conflicts_with_split_packages(self):
+        errors = signature_patterns.validate_single_packages({'java/pkg'},
+                                                             ['java/pkg'])
+        expected = [
+            'single_packages and split_packages overlap, please ensure the '
+            'following packages are only present in one:\n    java/pkg'
+        ]
+        self.assertEqual(expected, errors)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/scripts/hiddenapi/signature_trie.py b/scripts/hiddenapi/signature_trie.py
new file mode 100644
index 0000000..3650fa1
--- /dev/null
+++ b/scripts/hiddenapi/signature_trie.py
@@ -0,0 +1,345 @@
+#!/usr/bin/env python
+#
+# 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.
+"""Verify that one set of hidden API flags is a subset of another."""
+import dataclasses
+import typing
+
+from itertools import chain
+
+
+@dataclasses.dataclass()
+class Node:
+    """A node in the signature trie."""
+
+    # The type of the node.
+    #
+    # Leaf nodes are of type "member".
+    # Interior nodes can be either "package", or "class".
+    type: str
+
+    # The selector of the node.
+    #
+    # That is a string that can be used to select the node, e.g. in a pattern
+    # that is passed to InteriorNode.get_matching_rows().
+    selector: str
+
+    def values(self, selector):
+        """Get the values from a set of selected nodes.
+
+        :param selector: a function that can be applied to a key in the nodes
+            attribute to determine whether to return its values.
+
+        :return: A list of iterables of all the values associated with
+            this node and its children.
+        """
+        values = []
+        self.append_values(values, selector)
+        return values
+
+    def append_values(self, values, selector):
+        """Append the values associated with this node and its children.
+
+        For each item (key, child) in nodes the child node's values are returned
+        if and only if the selector returns True when called on its key. A child
+        node's values are all the values associated with it and all its
+        descendant nodes.
+
+        :param selector: a function that can be applied to a key in the nodes
+        attribute to determine whether to return its values.
+        :param values: a list of a iterables of values.
+        """
+        raise NotImplementedError("Please Implement this method")
+
+    def child_nodes(self):
+        """Get an iterable of the child nodes of this node."""
+        raise NotImplementedError("Please Implement this method")
+
+
+# pylint: disable=line-too-long
+@dataclasses.dataclass()
+class InteriorNode(Node):
+    """An interior node in a trie.
+
+    Each interior node has a dict that maps from an element of a signature to
+    either another interior node or a leaf. Each interior node represents either
+    a package, class or nested class. Class members are represented by a Leaf.
+
+    Associating the set of flags [public-api] with the signature
+    "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following
+    nodes to be created:
+    Node()
+    ^- package:java -> Node()
+       ^- package:lang -> Node()
+           ^- class:Object -> Node()
+              ^- member:String()Ljava/lang/String; -> Leaf([public-api])
+
+    Associating the set of flags [blocked,core-platform-api] with the signature
+    "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;"
+    will cause the following nodes to be created:
+    Node()
+    ^- package:java -> Node()
+       ^- package:lang -> Node()
+           ^- class:Character -> Node()
+              ^- class:UnicodeScript -> Node()
+                 ^- member:of(I)Ljava/lang/Character$UnicodeScript;
+                    -> Leaf([blocked,core-platform-api])
+    """
+
+    # pylint: enable=line-too-long
+
+    # A dict from an element of the signature to the Node/Leaf containing the
+    # next element/value.
+    nodes: typing.Dict[str, Node] = dataclasses.field(default_factory=dict)
+
+    # pylint: disable=line-too-long
+    @staticmethod
+    def signature_to_elements(signature):
+        """Split a signature or a prefix into a number of elements:
+
+        1. The packages (excluding the leading L preceding the first package).
+        2. The class names, from outermost to innermost.
+        3. The member signature.
+        e.g.
+        Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
+        will be broken down into these elements:
+        1. package:java
+        2. package:lang
+        3. class:Character
+        4. class:UnicodeScript
+        5. member:of(I)Ljava/lang/Character$UnicodeScript;
+        """
+        # Remove the leading L.
+        #  - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
+        text = signature.removeprefix("L")
+        # Split the signature between qualified class name and the class member
+        # signature.
+        #  0 - java/lang/Character$UnicodeScript
+        #  1 - of(I)Ljava/lang/Character$UnicodeScript;
+        parts = text.split(";->")
+        # If there is no member then this will be an empty list.
+        member = parts[1:]
+        # Split the qualified class name into packages, and class name.
+        #  0 - java
+        #  1 - lang
+        #  2 - Character$UnicodeScript
+        elements = parts[0].split("/")
+        last_element = elements[-1]
+        wildcard = []
+        classes = []
+        if "*" in last_element:
+            if last_element not in ("*", "**"):
+                raise Exception(f"Invalid signature '{signature}': invalid "
+                                f"wildcard '{last_element}'")
+            packages = elements[0:-1]
+            # Cannot specify a wildcard and target a specific member
+            if member:
+                raise Exception(f"Invalid signature '{signature}': contains "
+                                f"wildcard '{last_element}' and "
+                                f"member signature '{member[0]}'")
+            wildcard = [last_element]
+        elif last_element.islower():
+            raise Exception(f"Invalid signature '{signature}': last element "
+                            f"'{last_element}' is lower case but should be an "
+                            f"upper case class name or wildcard")
+        else:
+            packages = elements[0:-1]
+            # Split the class name into outer / inner classes
+            #  0 - Character
+            #  1 - UnicodeScript
+            classes = last_element.removesuffix(";").split("$")
+
+        # Assemble the parts into a single list, adding prefixes to identify
+        # the different parts. If a wildcard is provided then it looks something
+        # like this:
+        #  0 - package:java
+        #  1 - package:lang
+        #  2 - *
+        #
+        # Otherwise, it looks something like this:
+        #  0 - package:java
+        #  1 - package:lang
+        #  2 - class:Character
+        #  3 - class:UnicodeScript
+        #  4 - member:of(I)Ljava/lang/Character$UnicodeScript;
+        return list(
+            chain([("package", x) for x in packages],
+                  [("class", x) for x in classes],
+                  [("member", x) for x in member],
+                  [("wildcard", x) for x in wildcard]))
+
+    # pylint: enable=line-too-long
+
+    @staticmethod
+    def split_element(element):
+        element_type, element_value = element
+        return element_type, element_value
+
+    @staticmethod
+    def element_type(element):
+        element_type, _ = InteriorNode.split_element(element)
+        return element_type
+
+    @staticmethod
+    def elements_to_selector(elements):
+        """Compute a selector for a set of elements.
+
+        A selector uniquely identifies a specific Node in the trie. It is
+        essentially a prefix of a signature (without the leading L).
+
+        e.g. a trie containing "Ljava/lang/Object;->String()Ljava/lang/String;"
+        would contain nodes with the following selectors:
+        * "java"
+        * "java/lang"
+        * "java/lang/Object"
+        * "java/lang/Object;->String()Ljava/lang/String;"
+        """
+        signature = ""
+        preceding_type = ""
+        for element in elements:
+            element_type, element_value = InteriorNode.split_element(element)
+            separator = ""
+            if element_type == "package":
+                separator = "/"
+            elif element_type == "class":
+                if preceding_type == "class":
+                    separator = "$"
+                else:
+                    separator = "/"
+            elif element_type == "wildcard":
+                separator = "/"
+            elif element_type == "member":
+                separator += ";->"
+
+            if signature:
+                signature += separator
+
+            signature += element_value
+
+            preceding_type = element_type
+
+        return signature
+
+    def add(self, signature, value, only_if_matches=False):
+        """Associate the value with the specific signature.
+
+        :param signature: the member signature
+        :param value: the value to associated with the signature
+        :param only_if_matches: True if the value is added only if the signature
+             matches at least one of the existing top level packages.
+        :return: n/a
+        """
+        # Split the signature into elements.
+        elements = self.signature_to_elements(signature)
+        # Find the Node associated with the deepest class.
+        node = self
+        for index, element in enumerate(elements[:-1]):
+            if element in node.nodes:
+                node = node.nodes[element]
+            elif only_if_matches and index == 0:
+                return
+            else:
+                selector = self.elements_to_selector(elements[0:index + 1])
+                next_node = InteriorNode(
+                    type=InteriorNode.element_type(element), selector=selector)
+                node.nodes[element] = next_node
+                node = next_node
+        # Add a Leaf containing the value and associate it with the member
+        # signature within the class.
+        last_element = elements[-1]
+        last_element_type = self.element_type(last_element)
+        if last_element_type != "member":
+            raise Exception(
+                f"Invalid signature: {signature}, does not identify a "
+                "specific member")
+        if last_element in node.nodes:
+            raise Exception(f"Duplicate signature: {signature}")
+        leaf = Leaf(
+            type=last_element_type,
+            selector=signature,
+            value=value,
+        )
+        node.nodes[last_element] = leaf
+
+    def get_matching_rows(self, pattern):
+        """Get the values (plural) associated with the pattern.
+
+        e.g. If the pattern is a full signature then this will return a list
+        containing the value associated with that signature.
+
+        If the pattern is a class then this will return a list containing the
+        values associated with all members of that class.
+
+        If the pattern ends with "*" then the preceding part is treated as a
+        package and this will return a list containing the values associated
+        with all the members of all the classes in that package.
+
+        If the pattern ends with "**" then the preceding part is treated
+        as a package and this will return a list containing the values
+        associated with all the members of all the classes in that package and
+        all sub-packages.
+
+        :param pattern: the pattern which could be a complete signature or a
+        class, or package wildcard.
+        :return: an iterable containing all the values associated with the
+        pattern.
+        """
+        elements = self.signature_to_elements(pattern)
+        node = self
+
+        # Include all values from this node and all its children.
+        selector = lambda x: True
+
+        last_element = elements[-1]
+        last_element_type, last_element_value = self.split_element(last_element)
+        if last_element_type == "wildcard":
+            elements = elements[:-1]
+            if last_element_value == "*":
+                # Do not include values from sub-packages.
+                selector = lambda x: InteriorNode.element_type(x) != "package"
+
+        for element in elements:
+            if element in node.nodes:
+                node = node.nodes[element]
+            else:
+                return []
+
+        return node.values(selector)
+
+    def append_values(self, values, selector):
+        for key, node in self.nodes.items():
+            if selector(key):
+                node.append_values(values, lambda x: True)
+
+    def child_nodes(self):
+        return self.nodes.values()
+
+
+@dataclasses.dataclass()
+class Leaf(Node):
+    """A leaf of the trie"""
+
+    # The value associated with this leaf.
+    value: typing.Any
+
+    def append_values(self, values, selector):
+        values.append(self.value)
+
+    def child_nodes(self):
+        return []
+
+
+def signature_trie():
+    return InteriorNode(type="root", selector="")
diff --git a/scripts/hiddenapi/signature_trie_test.py b/scripts/hiddenapi/signature_trie_test.py
new file mode 100755
index 0000000..6d4e660
--- /dev/null
+++ b/scripts/hiddenapi/signature_trie_test.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python
+#
+# 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.
+"""Unit tests for verify_overlaps_test.py."""
+import io
+import unittest
+
+from signature_trie import InteriorNode
+from signature_trie import signature_trie
+
+
+class TestSignatureToElements(unittest.TestCase):
+
+    @staticmethod
+    def signature_to_elements(signature):
+        return InteriorNode.signature_to_elements(signature)
+
+    @staticmethod
+    def elements_to_signature(elements):
+        return InteriorNode.elements_to_selector(elements)
+
+    def test_nested_inner_classes(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "ProcessBuilder"),
+            ("class", "Redirect"),
+            ("class", "1"),
+            ("member", "<init>()V"),
+        ]
+        signature = "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_basic_member(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "Object"),
+            ("member", "hashCode()I"),
+        ]
+        signature = "Ljava/lang/Object;->hashCode()I"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_double_dollar_class(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "CharSequence"),
+            ("class", ""),
+            ("class", "ExternalSyntheticLambda0"),
+            ("member", "<init>(Ljava/lang/CharSequence;)V"),
+        ]
+        signature = "Ljava/lang/CharSequence$$ExternalSyntheticLambda0;" \
+                    "-><init>(Ljava/lang/CharSequence;)V"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_no_member(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("class", "CharSequence"),
+            ("class", ""),
+            ("class", "ExternalSyntheticLambda0"),
+        ]
+        signature = "Ljava/lang/CharSequence$$ExternalSyntheticLambda0"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_wildcard(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("wildcard", "*"),
+        ]
+        signature = "java/lang/*"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
+
+    def test_recursive_wildcard(self):
+        elements = [
+            ("package", "java"),
+            ("package", "lang"),
+            ("wildcard", "**"),
+        ]
+        signature = "java/lang/**"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
+
+    def test_no_packages_wildcard(self):
+        elements = [
+            ("wildcard", "*"),
+        ]
+        signature = "*"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
+
+    def test_no_packages_recursive_wildcard(self):
+        elements = [
+            ("wildcard", "**"),
+        ]
+        signature = "**"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, self.elements_to_signature(elements))
+
+    def test_invalid_no_class_or_wildcard(self):
+        signature = "java/lang"
+        with self.assertRaises(Exception) as context:
+            self.signature_to_elements(signature)
+        self.assertIn(
+            "last element 'lang' is lower case but should be an "
+            "upper case class name or wildcard", str(context.exception))
+
+    def test_non_standard_class_name(self):
+        elements = [
+            ("package", "javax"),
+            ("package", "crypto"),
+            ("class", "extObjectInputStream"),
+        ]
+        signature = "Ljavax/crypto/extObjectInputStream"
+        self.assertEqual(elements, self.signature_to_elements(signature))
+        self.assertEqual(signature, "L" + self.elements_to_signature(elements))
+
+    def test_invalid_pattern_wildcard(self):
+        pattern = "Ljava/lang/Class*"
+        with self.assertRaises(Exception) as context:
+            self.signature_to_elements(pattern)
+        self.assertIn("invalid wildcard 'Class*'", str(context.exception))
+
+    def test_invalid_pattern_wildcard_and_member(self):
+        pattern = "Ljava/lang/*;->hashCode()I"
+        with self.assertRaises(Exception) as context:
+            self.signature_to_elements(pattern)
+        self.assertIn(
+            "contains wildcard '*' and member signature 'hashCode()I'",
+            str(context.exception))
+
+
+class TestValues(unittest.TestCase):
+    def test_add_then_get(self):
+        trie = signature_trie()
+        trie.add("La/b/C;->l()", 1)
+        trie.add("La/b/C$D;->m()", "A")
+        trie.add("La/b/C$D;->n()", {})
+
+        package_a_node = next(iter(trie.child_nodes()))
+        self.assertEqual("package", package_a_node.type)
+        self.assertEqual("a", package_a_node.selector)
+
+        package_b_node = next(iter(package_a_node.child_nodes()))
+        self.assertEqual("package", package_b_node.type)
+        self.assertEqual("a/b", package_b_node.selector)
+
+        class_c_node = next(iter(package_b_node.child_nodes()))
+        self.assertEqual("class", class_c_node.type)
+        self.assertEqual("a/b/C", class_c_node.selector)
+
+        self.assertEqual([1, "A", {}], class_c_node.values(lambda _: True))
+
+class TestGetMatchingRows(unittest.TestCase):
+    extractInput = """
+Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
+Ljava/lang/Character;->serialVersionUID:J
+Ljava/lang/Object;->hashCode()I
+Ljava/lang/Object;->toString()Ljava/lang/String;
+Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V
+Ljava/util/zip/ZipFile;-><clinit>()V
+"""
+
+    def read_trie(self):
+        trie = signature_trie()
+        with io.StringIO(self.extractInput.strip()) as f:
+            for line in iter(f.readline, ""):
+                line = line.rstrip()
+                trie.add(line, line)
+        return trie
+
+    def check_patterns(self, pattern, expected):
+        trie = self.read_trie()
+        self.check_node_patterns(trie, pattern, expected)
+
+    def check_node_patterns(self, node, pattern, expected):
+        actual = list(node.get_matching_rows(pattern))
+        actual.sort()
+        self.assertEqual(expected, actual)
+
+    def test_member_pattern(self):
+        self.check_patterns("java/util/zip/ZipFile;-><clinit>()V",
+                            ["Ljava/util/zip/ZipFile;-><clinit>()V"])
+
+    def test_class_pattern(self):
+        self.check_patterns("java/lang/Object", [
+            "Ljava/lang/Object;->hashCode()I",
+            "Ljava/lang/Object;->toString()Ljava/lang/String;",
+        ])
+
+    # pylint: disable=line-too-long
+    def test_nested_class_pattern(self):
+        self.check_patterns("java/lang/Character", [
+            "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;",
+            "Ljava/lang/Character;->serialVersionUID:J",
+        ])
+
+    def test_wildcard(self):
+        self.check_patterns("java/lang/*", [
+            "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;",
+            "Ljava/lang/Character;->serialVersionUID:J",
+            "Ljava/lang/Object;->hashCode()I",
+            "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+        ])
+
+    def test_recursive_wildcard(self):
+        self.check_patterns("java/**", [
+            "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;",
+            "Ljava/lang/Character;->serialVersionUID:J",
+            "Ljava/lang/Object;->hashCode()I",
+            "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            "Ljava/util/zip/ZipFile;-><clinit>()V",
+        ])
+
+    def test_node_wildcard(self):
+        trie = self.read_trie()
+        node = list(trie.child_nodes())[0]
+        self.check_node_patterns(node, "**", [
+            "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;",
+            "Ljava/lang/Character;->serialVersionUID:J",
+            "Ljava/lang/Object;->hashCode()I",
+            "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            "Ljava/util/zip/ZipFile;-><clinit>()V",
+        ])
+
+    # pylint: enable=line-too-long
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/scripts/hiddenapi/verify_overlaps.py b/scripts/hiddenapi/verify_overlaps.py
index bb0917e..f985a49 100755
--- a/scripts/hiddenapi/verify_overlaps.py
+++ b/scripts/hiddenapi/verify_overlaps.py
@@ -13,57 +13,198 @@
 # 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.
-"""
-Verify that one set of hidden API flags is a subset of another.
-"""
+"""Verify that one set of hidden API flags is a subset of another."""
 
 import argparse
 import csv
+import sys
+from itertools import chain
 
-args_parser = argparse.ArgumentParser(description='Verify that one set of hidden API flags is a subset of another.')
-args_parser.add_argument('all', help='All the flags')
-args_parser.add_argument('subsets', nargs=argparse.REMAINDER, help='Subsets of the flags')
-args = args_parser.parse_args()
+from signature_trie import signature_trie
 
 
-def dict_reader(input):
-    return csv.DictReader(input, delimiter=',', quotechar='|', fieldnames=['signature'])
-
-# Read in all the flags into a dict indexed by signature
-allFlagsBySignature = {}
-with open(args.all, 'r') as allFlagsFile:
-    allFlagsReader = dict_reader(allFlagsFile)
-    for row in allFlagsReader:
-        signature = row['signature']
-        allFlagsBySignature[signature]=row
-
-failed = False
-for subsetPath in args.subsets:
-    mismatchingSignatures = []
-    with open(subsetPath, 'r') as subsetFlagsFile:
-        subsetReader = dict_reader(subsetFlagsFile)
-        for row in subsetReader:
-            signature = row['signature']
-            if signature in allFlagsBySignature:
-                allFlags = allFlagsBySignature.get(signature)
-                if allFlags != row:
-                    mismatchingSignatures.append((signature, row.get(None, []), allFlags.get(None, [])))
-            else:
-                mismatchingSignatures.append((signature, row.get(None, []), []))
+def dict_reader(csv_file):
+    return csv.DictReader(
+        csv_file, delimiter=",", quotechar="|", fieldnames=["signature"])
 
 
-    if mismatchingSignatures:
-        failed = True
-        print("ERROR: Hidden API flags are inconsistent:")
-        print("< " + subsetPath)
-        print("> " + args.all)
-        for mismatch in mismatchingSignatures:
-            print()
-            print("< " + mismatch[0] + "," + ",".join(mismatch[1]))
-            if mismatch[2] != []:
-                print("> " + mismatch[0] + "," + ",".join(mismatch[2]))
-            else:
-                print("> " + mismatch[0] + " - missing")
+def read_flag_trie_from_file(file):
+    with open(file, "r", encoding="utf8") as stream:
+        return read_flag_trie_from_stream(stream)
 
-if failed:
-    sys.exit(1)
+
+def read_flag_trie_from_stream(stream):
+    trie = signature_trie()
+    reader = dict_reader(stream)
+    for row in reader:
+        signature = row["signature"]
+        trie.add(signature, row)
+    return trie
+
+
+def extract_subset_from_monolithic_flags_as_dict_from_file(
+        monolithic_trie, patterns_file):
+    """Extract a subset of flags from the dict of monolithic flags.
+
+    :param monolithic_trie: the trie containing all the monolithic flags.
+    :param patterns_file: a file containing a list of signature patterns that
+    define the subset.
+    :return: the dict from signature to row.
+    """
+    with open(patterns_file, "r", encoding="utf8") as stream:
+        return extract_subset_from_monolithic_flags_as_dict_from_stream(
+            monolithic_trie, stream)
+
+
+def extract_subset_from_monolithic_flags_as_dict_from_stream(
+        monolithic_trie, stream):
+    """Extract a subset of flags from the trie of monolithic flags.
+
+    :param monolithic_trie: the trie containing all the monolithic flags.
+    :param stream: a stream containing a list of signature patterns that define
+    the subset.
+    :return: the dict from signature to row.
+    """
+    dict_signature_to_row = {}
+    for pattern in stream:
+        pattern = pattern.rstrip()
+        rows = monolithic_trie.get_matching_rows(pattern)
+        for row in rows:
+            signature = row["signature"]
+            dict_signature_to_row[signature] = row
+    return dict_signature_to_row
+
+
+def read_signature_csv_from_stream_as_dict(stream):
+    """Read the csv contents from the stream into a dict.
+
+    The first column is assumed to be the signature and used as the
+    key.
+
+    The whole row is stored as the value.
+    :param stream: the csv contents to read
+    :return: the dict from signature to row.
+    """
+    dict_signature_to_row = {}
+    reader = dict_reader(stream)
+    for row in reader:
+        signature = row["signature"]
+        dict_signature_to_row[signature] = row
+    return dict_signature_to_row
+
+
+def read_signature_csv_from_file_as_dict(csv_file):
+    """Read the csvFile into a dict.
+
+    The first column is assumed to be the signature and used as the
+    key.
+
+    The whole row is stored as the value.
+    :param csv_file: the csv file to read
+    :return: the dict from signature to row.
+    """
+    with open(csv_file, "r", encoding="utf8") as f:
+        return read_signature_csv_from_stream_as_dict(f)
+
+
+def compare_signature_flags(monolithic_flags_dict, modular_flags_dict,
+                            implementation_flags):
+    """Compare the signature flags between the two dicts.
+
+    :param monolithic_flags_dict: the dict containing the subset of the
+    monolithic flags that should be equal to the modular flags.
+    :param modular_flags_dict:the dict containing the flags produced by a single
+    bootclasspath_fragment module.
+    :return: list of mismatches., each mismatch is a tuple where the first item
+    is the signature, and the second and third items are lists of the flags from
+    modular dict, and monolithic dict respectively.
+    """
+    mismatching_signatures = []
+    # Create a sorted set of all the signatures from both the monolithic and
+    # modular dicts.
+    all_signatures = sorted(
+        set(chain(monolithic_flags_dict.keys(), modular_flags_dict.keys())))
+    for signature in all_signatures:
+        monolithic_row = monolithic_flags_dict.get(signature, {})
+        monolithic_flags = monolithic_row.get(None, [])
+        if signature in modular_flags_dict:
+            modular_row = modular_flags_dict.get(signature, {})
+            modular_flags = modular_row.get(None, [])
+        else:
+            modular_flags = implementation_flags
+        if monolithic_flags != modular_flags:
+            mismatching_signatures.append(
+                (signature, modular_flags, monolithic_flags))
+    return mismatching_signatures
+
+
+def main(argv):
+    args_parser = argparse.ArgumentParser(
+        description="Verify that sets of hidden API flags are each a subset of "
+        "the monolithic flag file. For each module this uses the provided "
+        "signature patterns to select a subset of the monolithic flags and "
+        "then it compares that subset against the filtered flags provided by "
+        "the module. If the module's filtered flags does not contain flags for "
+        "a signature then it is assumed to have been filtered out because it "
+        "was not part of an API and so is assumed to have the implementation "
+        "flags.")
+    args_parser.add_argument(
+        "--monolithic-flags", help="The monolithic flag file")
+    args_parser.add_argument(
+        "--module-flags",
+        action="append",
+        help="A colon separated pair of paths. The first is a path to a "
+        "filtered set of flags, and the second is a path to a set of "
+        "signature patterns that identify the set of classes belonging to "
+        "a single bootclasspath_fragment module. Specify once for each module "
+        "that needs to be checked.")
+    args_parser.add_argument(
+        "--implementation-flag",
+        action="append",
+        help="A flag in the set of flags that identifies a signature which is "
+        "not part of an API, i.e. is the signature of a private implementation "
+        "member. Specify as many times as necessary to define the "
+        "implementation flag set. If this is not specified then the "
+        "implementation flag set is empty.")
+    args = args_parser.parse_args(argv[1:])
+
+    # Read in all the flags into the trie
+    monolithic_flags_path = args.monolithic_flags
+    monolithic_trie = read_flag_trie_from_file(monolithic_flags_path)
+
+    implementation_flags = args.implementation_flag or []
+
+    # For each subset specified on the command line, create dicts for the flags
+    # provided by the subset and the corresponding flags from the complete set
+    # of flags and compare them.
+    failed = False
+    module_pairs = args.module_flags or []
+    for modular_pair in module_pairs:
+        parts = modular_pair.split(":")
+        modular_flags_path = parts[0]
+        modular_patterns_path = parts[1]
+        modular_flags_dict = read_signature_csv_from_file_as_dict(
+            modular_flags_path)
+        monolithic_flags_subset_dict = \
+            extract_subset_from_monolithic_flags_as_dict_from_file(
+                monolithic_trie, modular_patterns_path)
+        mismatching_signatures = compare_signature_flags(
+            monolithic_flags_subset_dict, modular_flags_dict,
+            implementation_flags)
+        if mismatching_signatures:
+            failed = True
+            print("ERROR: Hidden API flags are inconsistent:")
+            print("< " + modular_flags_path)
+            print("> " + monolithic_flags_path)
+            for mismatch in mismatching_signatures:
+                signature = mismatch[0]
+                print()
+                print("< " + ",".join([signature] + mismatch[1]))
+                print("> " + ",".join([signature] + mismatch[2]))
+
+    if failed:
+        sys.exit(1)
+
+
+if __name__ == "__main__":
+    main(sys.argv)
diff --git a/scripts/hiddenapi/verify_overlaps_test.py b/scripts/hiddenapi/verify_overlaps_test.py
new file mode 100755
index 0000000..0a489ee
--- /dev/null
+++ b/scripts/hiddenapi/verify_overlaps_test.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Unit tests for verify_overlaps_test.py."""
+import io
+import unittest
+
+import verify_overlaps as vo
+
+
+class TestDetectOverlaps(unittest.TestCase):
+
+    @staticmethod
+    def read_flag_trie_from_string(csvdata):
+        with io.StringIO(csvdata) as f:
+            return vo.read_flag_trie_from_stream(f)
+
+    @staticmethod
+    def read_signature_csv_from_string_as_dict(csvdata):
+        with io.StringIO(csvdata) as f:
+            return vo.read_signature_csv_from_stream_as_dict(f)
+
+    @staticmethod
+    def extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns):
+        with io.StringIO(patterns) as f:
+            return vo.extract_subset_from_monolithic_flags_as_dict_from_stream(
+                monolithic, f)
+
+    extractInput = """
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
+Ljava/util/zip/ZipFile;-><clinit>()V,blocked
+Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,blocked
+Ljava/lang/Character;->serialVersionUID:J,sdk
+Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked
+"""
+
+    def test_extract_subset_signature(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "Ljava/lang/Object;->hashCode()I"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_extract_subset_class(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "java/lang/Object"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
+            },
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_extract_subset_outer_class(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "java/lang/Character"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_extract_subset_nested_class(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "java/lang/Character$UnicodeScript"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_extract_subset_package(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "java/lang/*"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
+            },
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
+            },
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            },
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_extract_subset_recursive_package(self):
+        monolithic = self.read_flag_trie_from_string(
+            TestDetectOverlaps.extractInput)
+
+        patterns = "java/**"
+
+        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
+            monolithic, patterns)
+        expected = {
+            "Ljava/lang/Character$UnicodeScript;"
+            "->of(I)Ljava/lang/Character$UnicodeScript;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Character$UnicodeScript;"
+                             "->of(I)Ljava/lang/Character$UnicodeScript;",
+            },
+            "Ljava/lang/Character;->serialVersionUID:J": {
+                None: ["sdk"],
+                "signature": "Ljava/lang/Character;->serialVersionUID:J",
+            },
+            "Ljava/lang/Object;->hashCode()I": {
+                None: ["public-api", "system-api", "test-api"],
+                "signature": "Ljava/lang/Object;->hashCode()I",
+            },
+            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
+            },
+            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
+            },
+            "Ljava/util/zip/ZipFile;-><clinit>()V": {
+                None: ["blocked"],
+                "signature": "Ljava/util/zip/ZipFile;-><clinit>()V",
+            },
+        }
+        self.assertEqual(expected, subset)
+
+    def test_read_trie_duplicate(self):
+        with self.assertRaises(Exception) as context:
+            self.read_flag_trie_from_string("""
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+Ljava/lang/Object;->hashCode()I,blocked
+""")
+        self.assertTrue("Duplicate signature: Ljava/lang/Object;->hashCode()I"
+                        in str(context.exception))
+
+    def test_read_trie_missing_member(self):
+        with self.assertRaises(Exception) as context:
+            self.read_flag_trie_from_string("""
+Ljava/lang/Object,public-api,system-api,test-api
+""")
+        self.assertTrue(
+            "Invalid signature: Ljava/lang/Object, "
+            "does not identify a specific member" in str(context.exception))
+
+    def test_match(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+""")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = []
+        self.assertEqual(expected, mismatches)
+
+    def test_mismatch_overlapping_flags(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api
+""")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = [
+            (
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                ["public-api"],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_mismatch_monolithic_blocked(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
+""")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = [
+            (
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                ["blocked"],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_mismatch_modular_blocked(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
+""")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = [
+            (
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["blocked"],
+                ["public-api", "system-api", "test-api"],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_match_treat_missing_from_modular_as_blocked(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = [
+            (
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                [],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_mismatch_treat_missing_from_modular_as_blocked(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+""")
+        modular = {}
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = [
+            (
+                "Ljava/lang/Object;->hashCode()I",
+                ["blocked"],
+                ["public-api", "system-api", "test-api"],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_blocked_missing_from_modular(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I,blocked
+""")
+        modular = {}
+        mismatches = vo.compare_signature_flags(monolithic, modular,
+                                                ["blocked"])
+        expected = []
+        self.assertEqual(expected, mismatches)
+
+    def test_match_treat_missing_from_modular_as_empty(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("")
+        modular = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
+""")
+        mismatches = vo.compare_signature_flags(monolithic, modular, [])
+        expected = [
+            (
+                "Ljava/lang/Object;->toString()Ljava/lang/String;",
+                ["public-api", "system-api", "test-api"],
+                [],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_mismatch_treat_missing_from_modular_as_empty(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
+""")
+        modular = {}
+        mismatches = vo.compare_signature_flags(monolithic, modular, [])
+        expected = [
+            (
+                "Ljava/lang/Object;->hashCode()I",
+                [],
+                ["public-api", "system-api", "test-api"],
+            ),
+        ]
+        self.assertEqual(expected, mismatches)
+
+    def test_empty_missing_from_modular(self):
+        monolithic = self.read_signature_csv_from_string_as_dict("""
+Ljava/lang/Object;->hashCode()I
+""")
+        modular = {}
+        mismatches = vo.compare_signature_flags(monolithic, modular, [])
+        expected = []
+        self.assertEqual(expected, mismatches)
+
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
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/manifest.py b/scripts/manifest.py
index 04f7405..81f9c61 100755
--- a/scripts/manifest.py
+++ b/scripts/manifest.py
@@ -123,4 +123,4 @@
 def write_xml(f, doc):
   f.write('<?xml version="1.0" encoding="utf-8"?>\n')
   for node in doc.childNodes:
-    f.write(node.toxml(encoding='utf-8') + '\n')
+    f.write(node.toxml() + '\n')
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 8168fbf..c8d4f76 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -25,7 +25,6 @@
 import sys
 from xml.dom import minidom
 
-
 from manifest import android_ns
 from manifest import get_children_with_tag
 from manifest import parse_manifest
@@ -33,49 +32,68 @@
 
 
 class ManifestMismatchError(Exception):
-  pass
+    pass
 
 
 def parse_args():
-  """Parse commandline arguments."""
+    """Parse commandline arguments."""
 
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--uses-library', dest='uses_libraries',
-                      action='append',
-                      help='specify uses-library entries known to the build system')
-  parser.add_argument('--optional-uses-library',
-                      dest='optional_uses_libraries',
-                      action='append',
-                      help='specify uses-library entries known to the build system with required:false')
-  parser.add_argument('--enforce-uses-libraries',
-                      dest='enforce_uses_libraries',
-                      action='store_true',
-                      help='check the uses-library entries known to the build system against the manifest')
-  parser.add_argument('--enforce-uses-libraries-relax',
-                      dest='enforce_uses_libraries_relax',
-                      action='store_true',
-                      help='do not fail immediately, just save the error message to file')
-  parser.add_argument('--enforce-uses-libraries-status',
-                      dest='enforce_uses_libraries_status',
-                      help='output file to store check status (error message)')
-  parser.add_argument('--extract-target-sdk-version',
-                      dest='extract_target_sdk_version',
-                      action='store_true',
-                      help='print the targetSdkVersion from the manifest')
-  parser.add_argument('--dexpreopt-config',
-                      dest='dexpreopt_configs',
-                      action='append',
-                      help='a paths to a dexpreopt.config of some library')
-  parser.add_argument('--aapt',
-                      dest='aapt',
-                      help='path to aapt executable')
-  parser.add_argument('--output', '-o', dest='output', help='output AndroidManifest.xml file')
-  parser.add_argument('input', help='input AndroidManifest.xml file')
-  return parser.parse_args()
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        '--uses-library',
+        dest='uses_libraries',
+        action='append',
+        help='specify uses-library entries known to the build system')
+    parser.add_argument(
+        '--optional-uses-library',
+        dest='optional_uses_libraries',
+        action='append',
+        help='specify uses-library entries known to the build system with '
+        'required:false'
+    )
+    parser.add_argument(
+        '--enforce-uses-libraries',
+        dest='enforce_uses_libraries',
+        action='store_true',
+        help='check the uses-library entries known to the build system against '
+        'the manifest'
+    )
+    parser.add_argument(
+        '--enforce-uses-libraries-relax',
+        dest='enforce_uses_libraries_relax',
+        action='store_true',
+        help='do not fail immediately, just save the error message to file')
+    parser.add_argument(
+        '--enforce-uses-libraries-status',
+        dest='enforce_uses_libraries_status',
+        help='output file to store check status (error message)')
+    parser.add_argument(
+        '--extract-target-sdk-version',
+        dest='extract_target_sdk_version',
+        action='store_true',
+        help='print the targetSdkVersion from the manifest')
+    parser.add_argument(
+        '--dexpreopt-config',
+        dest='dexpreopt_configs',
+        action='append',
+        help='a paths to a dexpreopt.config of some library')
+    parser.add_argument('--aapt', dest='aapt', help='path to aapt executable')
+    parser.add_argument(
+        '--output', '-o', dest='output', help='output AndroidManifest.xml file')
+    parser.add_argument('input', help='input AndroidManifest.xml file')
+    return parser.parse_args()
+
+
+C_RED = "\033[1;31m"
+C_GREEN = "\033[1;32m"
+C_BLUE = "\033[1;34m"
+C_OFF = "\033[0m"
+C_BOLD = "\033[1m"
 
 
 def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path):
-  """Verify that the <uses-library> tags in the manifest match those provided
+    """Verify that the <uses-library> tags in the manifest match those provided
+
   by the build system.
 
   Args:
@@ -84,254 +102,293 @@
     optional: optional libs known to the build system
     relax:    if true, suppress error on mismatch and just write it to file
     is_apk:   if the manifest comes from an APK or an XML file
-  """
-  if is_apk:
-    manifest_required, manifest_optional, tags = extract_uses_libs_apk(manifest)
-  else:
-    manifest_required, manifest_optional, tags = extract_uses_libs_xml(manifest)
+    """
+    if is_apk:
+        manifest_required, manifest_optional, tags = extract_uses_libs_apk(
+            manifest)
+    else:
+        manifest_required, manifest_optional, tags = extract_uses_libs_xml(
+            manifest)
 
-  if manifest_required == required and manifest_optional == optional:
-    return None
+    # Trim namespace component. Normally Soong does that automatically when it
+    # handles module names specified in Android.bp properties. However not all
+    # <uses-library> entries in the manifest correspond to real modules: some of
+    # the optional libraries may be missing at build time. Therefor this script
+    # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the
+    # optional namespace part manually.
+    required = trim_namespace_parts(required)
+    optional = trim_namespace_parts(optional)
 
-  errmsg = ''.join([
-    'mismatch in the <uses-library> tags between the build system and the '
-      'manifest:\n',
-    '\t- required libraries in build system: [%s]\n' % ', '.join(required),
-    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_required),
-    '\t- optional libraries in build system: [%s]\n' % ', '.join(optional),
-    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_optional),
-    '\t- tags in the manifest (%s):\n' % path,
-    '\t\t%s\n' % '\t\t'.join(tags),
-      'note: the following options are available:\n',
-    '\t- to temporarily disable the check on command line, rebuild with ',
-      'RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" ',
-      'and disable AOT-compilation in dexpreopt)\n',
-    '\t- to temporarily disable the check for the whole product, set ',
-      'PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles\n',
-    '\t- to fix the check, make build system properties coherent with the '
-      'manifest\n',
-    '\t- see build/make/Changes.md for details\n'])
+    if manifest_required == required and manifest_optional == optional:
+        return None
 
-  if not relax:
-    raise ManifestMismatchError(errmsg)
+    #pylint: disable=line-too-long
+    errmsg = ''.join([
+        'mismatch in the <uses-library> tags between the build system and the '
+        'manifest:\n',
+        '\t- required libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(required), C_OFF),
+        '\t                 vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_required), C_OFF),
+        '\t- optional libraries in build system: %s[%s]%s\n' % (C_RED, ', '.join(optional), C_OFF),
+        '\t                 vs. in the manifest: %s[%s]%s\n' % (C_RED, ', '.join(manifest_optional), C_OFF),
+        '\t- tags in the manifest (%s):\n' % path,
+        '\t\t%s\n' % '\t\t'.join(tags),
+        '%snote:%s the following options are available:\n' % (C_BLUE, C_OFF),
+        '\t- to temporarily disable the check on command line, rebuild with ',
+        '%sRELAX_USES_LIBRARY_CHECK=true%s' % (C_BOLD, C_OFF),
+        ' (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)\n',
+        '\t- to temporarily disable the check for the whole product, set ',
+        '%sPRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true%s in the product makefiles\n' % (C_BOLD, C_OFF),
+        '\t- to fix the check, make build system properties coherent with the manifest\n',
+        '\t- for details, see %sbuild/make/Changes.md%s' % (C_GREEN, C_OFF),
+        ' and %shttps://source.android.com/devices/tech/dalvik/art-class-loader-context%s\n' % (C_GREEN, C_OFF)
+    ])
+    #pylint: enable=line-too-long
 
-  return errmsg
+    if not relax:
+        raise ManifestMismatchError(errmsg)
+
+    return errmsg
+
+
+MODULE_NAMESPACE = re.compile('^//[^:]+:')
+
+
+def trim_namespace_parts(modules):
+    """Trim the namespace part of each module, if present.
+
+    Leave only the name.
+    """
+
+    trimmed = []
+    for module in modules:
+        trimmed.append(MODULE_NAMESPACE.sub('', module))
+    return trimmed
 
 
 def extract_uses_libs_apk(badging):
-  """Extract <uses-library> tags from the manifest of an APK."""
+    """Extract <uses-library> tags from the manifest of an APK."""
 
-  pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
+    pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
 
-  required = []
-  optional = []
-  lines = []
-  for match in re.finditer(pattern, badging):
-    lines.append(match.group(0))
-    libname = match.group(2)
-    if match.group(1) == None:
-      required.append(libname)
-    else:
-      optional.append(libname)
+    required = []
+    optional = []
+    lines = []
+    for match in re.finditer(pattern, badging):
+        lines.append(match.group(0))
+        libname = match.group(2)
+        if match.group(1) is None:
+            required.append(libname)
+        else:
+            optional.append(libname)
 
-  required = first_unique_elements(required)
-  optional = first_unique_elements(optional)
-  tags = first_unique_elements(lines)
-  return required, optional, tags
+    required = first_unique_elements(required)
+    optional = first_unique_elements(optional)
+    tags = first_unique_elements(lines)
+    return required, optional, tags
 
 
-def extract_uses_libs_xml(xml):
-  """Extract <uses-library> tags from the manifest."""
+def extract_uses_libs_xml(xml): #pylint: disable=inconsistent-return-statements
+    """Extract <uses-library> tags from the manifest."""
 
-  manifest = parse_manifest(xml)
-  elems = get_children_with_tag(manifest, 'application')
-  application = elems[0] if len(elems) == 1 else None
-  if len(elems) > 1:
-    raise RuntimeError('found multiple <application> tags')
-  elif not elems:
-    if uses_libraries or optional_uses_libraries:
-      raise ManifestMismatchError('no <application> tag found')
-    return
+    manifest = parse_manifest(xml)
+    elems = get_children_with_tag(manifest, 'application')
+    application = elems[0] if len(elems) == 1 else None
+    if len(elems) > 1: #pylint: disable=no-else-raise
+        raise RuntimeError('found multiple <application> tags')
+    elif not elems:
+        if uses_libraries or optional_uses_libraries: #pylint: disable=undefined-variable
+            raise ManifestMismatchError('no <application> tag found')
+        return
 
-  libs = get_children_with_tag(application, 'uses-library')
+    libs = get_children_with_tag(application, 'uses-library')
 
-  required = [uses_library_name(x) for x in libs if uses_library_required(x)]
-  optional = [uses_library_name(x) for x in libs if not uses_library_required(x)]
+    required = [uses_library_name(x) for x in libs if uses_library_required(x)]
+    optional = [
+        uses_library_name(x) for x in libs if not uses_library_required(x)
+    ]
 
-  # render <uses-library> tags as XML for a pretty error message
-  tags = []
-  for lib in libs:
-    tags.append(lib.toprettyxml())
+    # render <uses-library> tags as XML for a pretty error message
+    tags = []
+    for lib in libs:
+        tags.append(lib.toprettyxml())
 
-  required = first_unique_elements(required)
-  optional = first_unique_elements(optional)
-  tags = first_unique_elements(tags)
-  return required, optional, tags
+    required = first_unique_elements(required)
+    optional = first_unique_elements(optional)
+    tags = first_unique_elements(tags)
+    return required, optional, tags
 
 
 def first_unique_elements(l):
-  result = []
-  [result.append(x) for x in l if x not in result]
-  return result
+    result = []
+    for x in l:
+        if x not in result:
+            result.append(x)
+    return result
 
 
 def uses_library_name(lib):
-  """Extract the name attribute of a uses-library tag.
+    """Extract the name attribute of a uses-library tag.
 
   Args:
     lib: a <uses-library> tag.
-  """
-  name = lib.getAttributeNodeNS(android_ns, 'name')
-  return name.value if name is not None else ""
+    """
+    name = lib.getAttributeNodeNS(android_ns, 'name')
+    return name.value if name is not None else ''
 
 
 def uses_library_required(lib):
-  """Extract the required attribute of a uses-library tag.
+    """Extract the required attribute of a uses-library tag.
 
   Args:
     lib: a <uses-library> tag.
-  """
-  required = lib.getAttributeNodeNS(android_ns, 'required')
-  return (required.value == 'true') if required is not None else True
+    """
+    required = lib.getAttributeNodeNS(android_ns, 'required')
+    return (required.value == 'true') if required is not None else True
 
 
-def extract_target_sdk_version(manifest, is_apk = False):
-  """Returns the targetSdkVersion from the manifest.
+def extract_target_sdk_version(manifest, is_apk=False):
+    """Returns the targetSdkVersion from the manifest.
 
   Args:
     manifest: manifest (either parsed XML or aapt dump of APK)
     is_apk:   if the manifest comes from an APK or an XML file
-  """
-  if is_apk:
-    return extract_target_sdk_version_apk(manifest)
-  else:
-    return extract_target_sdk_version_xml(manifest)
+    """
+    if is_apk: #pylint: disable=no-else-return
+        return extract_target_sdk_version_apk(manifest)
+    else:
+        return extract_target_sdk_version_xml(manifest)
 
 
 def extract_target_sdk_version_apk(badging):
-  """Extract targetSdkVersion tags from the manifest of an APK."""
+    """Extract targetSdkVersion tags from the manifest of an APK."""
 
-  pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
+    pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
 
-  for match in re.finditer(pattern, badging):
-    return match.group(1)
+    for match in re.finditer(pattern, badging):
+        return match.group(1)
 
-  raise RuntimeError('cannot find targetSdkVersion in the manifest')
+    raise RuntimeError('cannot find targetSdkVersion in the manifest')
 
 
 def extract_target_sdk_version_xml(xml):
-  """Extract targetSdkVersion tags from the manifest."""
+    """Extract targetSdkVersion tags from the manifest."""
 
-  manifest = parse_manifest(xml)
+    manifest = parse_manifest(xml)
 
-  # Get or insert the uses-sdk element
-  uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
-  if len(uses_sdk) > 1:
-    raise RuntimeError('found multiple uses-sdk elements')
-  elif len(uses_sdk) == 0:
-    raise RuntimeError('missing uses-sdk element')
+    # Get or insert the uses-sdk element
+    uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
+    if len(uses_sdk) > 1: #pylint: disable=no-else-raise
+        raise RuntimeError('found multiple uses-sdk elements')
+    elif len(uses_sdk) == 0:
+        raise RuntimeError('missing uses-sdk element')
 
-  uses_sdk = uses_sdk[0]
+    uses_sdk = uses_sdk[0]
 
-  min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
-  if min_attr is None:
-    raise RuntimeError('minSdkVersion is not specified')
+    min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
+    if min_attr is None:
+        raise RuntimeError('minSdkVersion is not specified')
 
-  target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
-  if target_attr is None:
-    target_attr = min_attr
+    target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
+    if target_attr is None:
+        target_attr = min_attr
 
-  return target_attr.value
+    return target_attr.value
 
 
 def load_dexpreopt_configs(configs):
-  """Load dexpreopt.config files and map module names to library names."""
-  module_to_libname = {}
+    """Load dexpreopt.config files and map module names to library names."""
+    module_to_libname = {}
 
-  if configs is None:
-    configs = []
+    if configs is None:
+        configs = []
 
-  for config in configs:
-    with open(config, 'r') as f:
-      contents = json.load(f)
-    module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
+    for config in configs:
+        with open(config, 'r') as f:
+            contents = json.load(f)
+        module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
 
-  return module_to_libname
+    return module_to_libname
 
 
 def translate_libnames(modules, module_to_libname):
-  """Translate module names into library names using the mapping."""
-  if modules is None:
-    modules = []
+    """Translate module names into library names using the mapping."""
+    if modules is None:
+        modules = []
 
-  libnames = []
-  for name in modules:
-    if name in module_to_libname:
-      name = module_to_libname[name]
-    libnames.append(name)
+    libnames = []
+    for name in modules:
+        if name in module_to_libname:
+            name = module_to_libname[name]
+        libnames.append(name)
 
-  return libnames
+    return libnames
 
 
 def main():
-  """Program entry point."""
-  try:
-    args = parse_args()
+    """Program entry point."""
+    try:
+        args = parse_args()
 
-    # The input can be either an XML manifest or an APK, they are parsed and
-    # processed in different ways.
-    is_apk = args.input.endswith('.apk')
-    if is_apk:
-      aapt = args.aapt if args.aapt != None else "aapt"
-      manifest = subprocess.check_output([aapt, "dump", "badging", args.input])
-    else:
-      manifest = minidom.parse(args.input)
+        # The input can be either an XML manifest or an APK, they are parsed and
+        # processed in different ways.
+        is_apk = args.input.endswith('.apk')
+        if is_apk:
+            aapt = args.aapt if args.aapt is not None else 'aapt'
+            manifest = subprocess.check_output(
+                [aapt, 'dump', 'badging', args.input]).decode('utf-8')
+        else:
+            manifest = minidom.parse(args.input)
 
-    if args.enforce_uses_libraries:
-      # Load dexpreopt.config files and build a mapping from module names to
-      # library names. This is necessary because build system addresses
-      # libraries by their module name (`uses_libs`, `optional_uses_libs`,
-      # `LOCAL_USES_LIBRARIES`, `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain
-      # module names), while the manifest addresses libraries by their name.
-      mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
-      required = translate_libnames(args.uses_libraries, mod_to_lib)
-      optional = translate_libnames(args.optional_uses_libraries, mod_to_lib)
+        if args.enforce_uses_libraries:
+            # Load dexpreopt.config files and build a mapping from module
+            # names to library names. This is necessary because build system
+            # addresses libraries by their module name (`uses_libs`,
+            # `optional_uses_libs`, `LOCAL_USES_LIBRARIES`,
+            # `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain module names), while
+            # the manifest addresses libraries by their name.
+            mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
+            required = translate_libnames(args.uses_libraries, mod_to_lib)
+            optional = translate_libnames(args.optional_uses_libraries,
+                                          mod_to_lib)
 
-      # Check if the <uses-library> lists in the build system agree with those
-      # in the manifest. Raise an exception on mismatch, unless the script was
-      # passed a special parameter to suppress exceptions.
-      errmsg = enforce_uses_libraries(manifest, required, optional,
-        args.enforce_uses_libraries_relax, is_apk, args.input)
+            # Check if the <uses-library> lists in the build system agree with
+            # those in the manifest. Raise an exception on mismatch, unless the
+            # script was passed a special parameter to suppress exceptions.
+            errmsg = enforce_uses_libraries(manifest, required, optional,
+                                            args.enforce_uses_libraries_relax,
+                                            is_apk, args.input)
 
-      # Create a status file that is empty on success, or contains an error
-      # message on failure. When exceptions are suppressed, dexpreopt command
-      # command will check file size to determine if the check has failed.
-      if args.enforce_uses_libraries_status:
-        with open(args.enforce_uses_libraries_status, 'w') as f:
-          if not errmsg == None:
-            f.write("%s\n" % errmsg)
+            # Create a status file that is empty on success, or contains an
+            # error message on failure. When exceptions are suppressed,
+            # dexpreopt command command will check file size to determine if
+            # the check has failed.
+            if args.enforce_uses_libraries_status:
+                with open(args.enforce_uses_libraries_status, 'w') as f:
+                    if errmsg is not None:
+                        f.write('%s\n' % errmsg)
 
-    if args.extract_target_sdk_version:
-      try:
-        print(extract_target_sdk_version(manifest, is_apk))
-      except:
-        # Failed; don't crash, return "any" SDK version. This will result in
-        # dexpreopt not adding any compatibility libraries.
-        print(10000)
+        if args.extract_target_sdk_version:
+            try:
+                print(extract_target_sdk_version(manifest, is_apk))
+            except: #pylint: disable=bare-except
+                # Failed; don't crash, return "any" SDK version. This will
+                # result in dexpreopt not adding any compatibility libraries.
+                print(10000)
 
-    if args.output:
-      # XML output is supposed to be written only when this script is invoked
-      # with XML input manifest, not with an APK.
-      if is_apk:
-        raise RuntimeError('cannot save APK manifest as XML')
+        if args.output:
+            # XML output is supposed to be written only when this script is
+            # invoked with XML input manifest, not with an APK.
+            if is_apk:
+                raise RuntimeError('cannot save APK manifest as XML')
 
-      with open(args.output, 'wb') as f:
-        write_xml(f, manifest)
+            with open(args.output, 'w') as f:
+                write_xml(f, manifest)
 
-  # pylint: disable=broad-except
-  except Exception as err:
-    print('error: ' + str(err), file=sys.stderr)
-    sys.exit(-1)
+    # pylint: disable=broad-except
+    except Exception as err:
+        print('%serror:%s ' % (C_RED, C_OFF) + str(err), file=sys.stderr)
+        sys.exit(-1)
+
 
 if __name__ == '__main__':
-  main()
+    main()
diff --git a/scripts/manifest_check_test.py b/scripts/manifest_check_test.py
index 7159bdd..3be7a30 100755
--- a/scripts/manifest_check_test.py
+++ b/scripts/manifest_check_test.py
@@ -26,193 +26,235 @@
 
 
 def uses_library_xml(name, attr=''):
-  return '<uses-library android:name="%s"%s />' % (name, attr)
+    return '<uses-library android:name="%s"%s />' % (name, attr)
 
 
 def required_xml(value):
-  return ' android:required="%s"' % ('true' if value else 'false')
+    return ' android:required="%s"' % ('true' if value else 'false')
 
 
 def uses_library_apk(name, sfx=''):
-  return "uses-library%s:'%s'" % (sfx, name)
+    return "uses-library%s:'%s'" % (sfx, name)
 
 
 def required_apk(value):
-  return '' if value else '-not-required'
+    return '' if value else '-not-required'
 
 
 class EnforceUsesLibrariesTest(unittest.TestCase):
-  """Unit tests for add_extract_native_libs function."""
+    """Unit tests for add_extract_native_libs function."""
 
-  def run_test(self, xml, apk, uses_libraries=[], optional_uses_libraries=[]):
-    doc = minidom.parseString(xml)
-    try:
-      relax = False
-      manifest_check.enforce_uses_libraries(doc, uses_libraries,
-        optional_uses_libraries, relax, False, 'path/to/X/AndroidManifest.xml')
-      manifest_check.enforce_uses_libraries(apk, uses_libraries,
-        optional_uses_libraries, relax, True, 'path/to/X/X.apk')
-      return True
-    except manifest_check.ManifestMismatchError:
-      return False
+    def run_test(self, xml, apk, uses_libraries=[], optional_uses_libraries=[]): #pylint: disable=dangerous-default-value
+        doc = minidom.parseString(xml)
+        try:
+            relax = False
+            manifest_check.enforce_uses_libraries(
+                doc, uses_libraries, optional_uses_libraries, relax, False,
+                'path/to/X/AndroidManifest.xml')
+            manifest_check.enforce_uses_libraries(apk, uses_libraries,
+                                                  optional_uses_libraries,
+                                                  relax, True,
+                                                  'path/to/X/X.apk')
+            return True
+        except manifest_check.ManifestMismatchError:
+            return False
 
-  xml_tmpl = (
-      '<?xml version="1.0" encoding="utf-8"?>\n'
-      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <application>\n'
-      '    %s\n'
-      '    </application>\n'
-      '</manifest>\n')
+    xml_tmpl = (
+        '<?xml version="1.0" encoding="utf-8"?>\n<manifest '
+        'xmlns:android="http://schemas.android.com/apk/res/android">\n    '
+        '<application>\n    %s\n    </application>\n</manifest>\n')
 
-  apk_tmpl = (
-      "package: name='com.google.android.something' versionCode='100'\n"
-      "sdkVersion:'29'\n"
-      "targetSdkVersion:'29'\n"
-      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n"
-      "%s\n"
-      "densities: '160' '240' '320' '480' '640' '65534")
+    apk_tmpl = (
+        "package: name='com.google.android.something' versionCode='100'\n"
+        "sdkVersion:'29'\n"
+        "targetSdkVersion:'29'\n"
+        "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n"
+        '%s\n'
+        "densities: '160' '240' '320' '480' '640' '65534")
 
-  def test_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_apk('foo'))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_apk('foo'))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_uses_library_required(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(True)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(True)))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_uses_library_required(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(True)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(True)))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_expected_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_expected_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_expected_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_apk('foo'))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_expected_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_apk('foo'))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_missing_uses_library(self):
-    xml = self.xml_tmpl % ('')
-    apk = self.apk_tmpl % ('')
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_missing_uses_library(self):
+        xml = self.xml_tmpl % ('')
+        apk = self.apk_tmpl % ('')
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_missing_optional_uses_library(self):
-    xml = self.xml_tmpl % ('')
-    apk = self.apk_tmpl % ('')
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertFalse(matches)
+    def test_missing_optional_uses_library(self):
+        xml = self.xml_tmpl % ('')
+        apk = self.apk_tmpl % ('')
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertFalse(matches)
 
-  def test_extra_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo'))
-    apk = self.apk_tmpl % (uses_library_xml('foo'))
-    matches = self.run_test(xml, apk)
-    self.assertFalse(matches)
+    def test_extra_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo'))
+        apk = self.apk_tmpl % (uses_library_xml('foo'))
+        matches = self.run_test(xml, apk)
+        self.assertFalse(matches)
 
-  def test_extra_optional_uses_library(self):
-    xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
-    apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
-    matches = self.run_test(xml, apk)
-    self.assertFalse(matches)
+    def test_extra_optional_uses_library(self):
+        xml = self.xml_tmpl % (uses_library_xml('foo', required_xml(False)))
+        apk = self.apk_tmpl % (uses_library_apk('foo', required_apk(False)))
+        matches = self.run_test(xml, apk)
+        self.assertFalse(matches)
 
-  def test_multiple_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar')]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo', 'bar'])
-    self.assertTrue(matches)
+    def test_multiple_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('bar')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('bar')]))
+        matches = self.run_test(xml, apk, uses_libraries=['foo', 'bar'])
+        self.assertTrue(matches)
 
-  def test_multiple_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo', 'bar'])
-    self.assertTrue(matches)
+    def test_multiple_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, optional_uses_libraries=['foo', 'bar'])
+        self.assertTrue(matches)
 
-  def test_order_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar')]))
-    matches = self.run_test(xml, apk, uses_libraries=['bar', 'foo'])
-    self.assertFalse(matches)
+    def test_order_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('bar')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('bar')]))
+        matches = self.run_test(xml, apk, uses_libraries=['bar', 'foo'])
+        self.assertFalse(matches)
 
-  def test_order_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['bar', 'foo'])
-    self.assertFalse(matches)
+    def test_order_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, optional_uses_libraries=['bar', 'foo'])
+        self.assertFalse(matches)
 
-  def test_duplicate_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('foo')]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('foo')]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_duplicate_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join(
+            [uses_library_xml('foo'),
+             uses_library_xml('foo')]))
+        apk = self.apk_tmpl % ('\n'.join(
+            [uses_library_apk('foo'),
+             uses_library_apk('foo')]))
+        matches = self.run_test(xml, apk, uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_duplicate_optional_uses_library(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo', required_xml(False)),
-                                      uses_library_xml('foo', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo', required_apk(False)),
-                                      uses_library_apk('foo', required_apk(False))]))
-    matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
-    self.assertTrue(matches)
+    def test_duplicate_optional_uses_library(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo', required_xml(False)),
+            uses_library_xml('foo', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo', required_apk(False)),
+            uses_library_apk('foo', required_apk(False))
+        ]))
+        matches = self.run_test(xml, apk, optional_uses_libraries=['foo'])
+        self.assertTrue(matches)
 
-  def test_mixed(self):
-    xml = self.xml_tmpl % ('\n'.join([uses_library_xml('foo'),
-                                      uses_library_xml('bar', required_xml(False))]))
-    apk = self.apk_tmpl % ('\n'.join([uses_library_apk('foo'),
-                                      uses_library_apk('bar', required_apk(False))]))
-    matches = self.run_test(xml, apk, uses_libraries=['foo'],
-                            optional_uses_libraries=['bar'])
-    self.assertTrue(matches)
+    def test_mixed(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo'),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo'),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml, apk, uses_libraries=['foo'], optional_uses_libraries=['bar'])
+        self.assertTrue(matches)
+
+    def test_mixed_with_namespace(self):
+        xml = self.xml_tmpl % ('\n'.join([
+            uses_library_xml('foo'),
+            uses_library_xml('bar', required_xml(False))
+        ]))
+        apk = self.apk_tmpl % ('\n'.join([
+            uses_library_apk('foo'),
+            uses_library_apk('bar', required_apk(False))
+        ]))
+        matches = self.run_test(
+            xml,
+            apk,
+            uses_libraries=['//x/y/z:foo'],
+            optional_uses_libraries=['//x/y/z:bar'])
+        self.assertTrue(matches)
 
 
 class ExtractTargetSdkVersionTest(unittest.TestCase):
-  def run_test(self, xml, apk, version):
-    doc = minidom.parseString(xml)
-    v = manifest_check.extract_target_sdk_version(doc, is_apk=False)
-    self.assertEqual(v, version)
-    v = manifest_check.extract_target_sdk_version(apk, is_apk=True)
-    self.assertEqual(v, version)
 
-  xml_tmpl = (
-      '<?xml version="1.0" encoding="utf-8"?>\n'
-      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
-      '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="%s" />\n'
-      '</manifest>\n')
+    def run_test(self, xml, apk, version):
+        doc = minidom.parseString(xml)
+        v = manifest_check.extract_target_sdk_version(doc, is_apk=False)
+        self.assertEqual(v, version)
+        v = manifest_check.extract_target_sdk_version(apk, is_apk=True)
+        self.assertEqual(v, version)
 
-  apk_tmpl = (
-      "package: name='com.google.android.something' versionCode='100'\n"
-      "sdkVersion:'28'\n"
-      "targetSdkVersion:'%s'\n"
-      "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n")
+    xml_tmpl = (
+        '<?xml version="1.0" encoding="utf-8"?>\n<manifest '
+        'xmlns:android="http://schemas.android.com/apk/res/android">\n    '
+        '<uses-sdk android:minSdkVersion="28" android:targetSdkVersion="%s" '
+        '/>\n</manifest>\n')
 
-  def test_targert_sdk_version_28(self):
-    xml = self.xml_tmpl % "28"
-    apk = self.apk_tmpl % "28"
-    self.run_test(xml, apk, "28")
+    apk_tmpl = (
+        "package: name='com.google.android.something' versionCode='100'\n"
+        "sdkVersion:'28'\n"
+        "targetSdkVersion:'%s'\n"
+        "uses-permission: name='android.permission.ACCESS_NETWORK_STATE'\n")
 
-  def test_targert_sdk_version_29(self):
-    xml = self.xml_tmpl % "29"
-    apk = self.apk_tmpl % "29"
-    self.run_test(xml, apk, "29")
+    def test_targert_sdk_version_28(self):
+        xml = self.xml_tmpl % '28'
+        apk = self.apk_tmpl % '28'
+        self.run_test(xml, apk, '28')
+
+    def test_targert_sdk_version_29(self):
+        xml = self.xml_tmpl % '29'
+        apk = self.apk_tmpl % '29'
+        self.run_test(xml, apk, '29')
+
 
 if __name__ == '__main__':
-  unittest.main(verbosity=2)
+    unittest.main(verbosity=2)
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index 55d0fd1..2d3103b 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -65,6 +65,9 @@
   parser.add_argument('--has-no-code', dest='has_no_code', action='store_true',
                       help=('adds hasCode="false" attribute to application. Ignored if application elem '
                             'already has a hasCode attribute.'))
+  parser.add_argument('--test-only', dest='test_only', action='store_true',
+                      help=('adds testOnly="true" attribute to application. Assign true value if application elem '
+                            'already has a testOnly attribute.'))
   parser.add_argument('input', help='input AndroidManifest.xml file')
   parser.add_argument('output', help='output AndroidManifest.xml file')
   return parser.parse_args()
@@ -318,6 +321,26 @@
   attr.value = 'false'
   application.setAttributeNode(attr)
 
+def set_test_only_flag_to_true(doc):
+  manifest = parse_manifest(doc)
+  elems = get_children_with_tag(manifest, 'application')
+  application = elems[0] if len(elems) == 1 else None
+  if len(elems) > 1:
+    raise RuntimeError('found multiple <application> tags')
+  elif not elems:
+    application = doc.createElement('application')
+    indent = get_indent(manifest.firstChild, 1)
+    first = manifest.firstChild
+    manifest.insertBefore(doc.createTextNode(indent), first)
+    manifest.insertBefore(application, first)
+
+  attr = application.getAttributeNodeNS(android_ns, 'testOnly')
+  if attr is not None:
+    # Do nothing If the application already has a testOnly attribute.
+    return
+  attr = doc.createAttributeNS(android_ns, 'android:testOnly')
+  attr.value = 'true'
+  application.setAttributeNode(attr)
 
 def main():
   """Program entry point."""
@@ -349,10 +372,13 @@
     if args.has_no_code:
       set_has_code_to_false(doc)
 
+    if args.test_only:
+      set_test_only_flag_to_true(doc)
+
     if args.extract_native_libs is not None:
       add_extract_native_libs(doc, args.extract_native_libs)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
index 3a0a25d..199b279 100755
--- a/scripts/manifest_fixer_test.py
+++ b/scripts/manifest_fixer_test.py
@@ -16,16 +16,16 @@
 #
 """Unit tests for manifest_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
+import xml.etree.ElementTree as ET
 
 import manifest_fixer
 
 sys.dont_write_bytecode = True
 
-
 class CompareVersionGtTest(unittest.TestCase):
   """Unit tests for compare_version_gt function."""
 
@@ -59,7 +59,7 @@
     doc = minidom.parseString(input_manifest)
     manifest_fixer.raise_min_sdk_version(doc, min_sdk_version,
                                          target_sdk_version, library)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -80,13 +80,16 @@
       attrs += ' ' + extra
     return '    <uses-sdk%s/>\n' % (attrs)
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def test_no_uses_sdk(self):
     """Tests inserting a uses-sdk element into a manifest."""
 
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -95,7 +98,7 @@
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28',
                                                   extra='extra="foo"')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_min(self):
     """Tests inserting a minSdkVersion attribute into a uses-sdk element."""
@@ -103,7 +106,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise(self):
     """Tests raising a minSdkVersion attribute."""
@@ -111,7 +114,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_min(self):
     """Tests a minSdkVersion that doesn't need raising."""
@@ -119,7 +122,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '27', '27', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_raise_codename(self):
     """Tests raising a minSdkVersion attribute to a codename."""
@@ -127,7 +130,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='28')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='P')
     output = self.raise_min_sdk_version_test(manifest_input, 'P', 'P', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_raise_codename(self):
     """Tests a minSdkVersion codename that doesn't need raising."""
@@ -135,7 +138,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='P')
     expected = self.manifest_tmpl % self.uses_sdk(min='P', target='28')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '28', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target(self):
     """Tests an existing targetSdkVersion is preserved."""
@@ -143,7 +146,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='26', target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -151,7 +154,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_target_no_min(self):
     """"Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -159,7 +162,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -167,7 +170,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='29')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -175,7 +178,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(min='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion exists."""
@@ -183,7 +186,7 @@
     manifest_input = self.manifest_tmpl % self.uses_sdk(target='27')
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='27')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_library_no_target_no_min(self):
     """Tests inserting targetSdkVersion when minSdkVersion does not exist."""
@@ -191,7 +194,7 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_sdk(min='28', target='16')
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_extra(self):
     """Tests that extra attributes and elements are maintained."""
@@ -204,12 +207,12 @@
     # pylint: disable=line-too-long
     expected = self.manifest_tmpl % (
         '    <!-- comment -->\n'
-        '    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29" extra="foo"/>\n'
+        '    <uses-sdk android:minSdkVersion="28" extra="foo" android:targetSdkVersion="29"/>\n'
         '    <application/>\n')
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_indent(self):
     """Tests that an inserted element copies the existing indentation."""
@@ -223,17 +226,20 @@
 
     output = self.raise_min_sdk_version_test(manifest_input, '28', '29', False)
 
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddLoggingParentTest(unittest.TestCase):
   """Unit tests for add_logging_parent function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def add_logging_parent_test(self, input_manifest, logging_parent=None):
     doc = minidom.parseString(input_manifest)
     if logging_parent:
       manifest_fixer.add_logging_parent(doc, logging_parent)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -257,23 +263,26 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent()
     output = self.add_logging_parent_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_logging_parent(self):
     """Tests manifest_fixer with no logging_parent."""
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.uses_logging_parent('FOO')
     output = self.add_logging_parent_test(manifest_input, 'FOO')
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesLibrariesTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, new_uses_libraries):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_libraries(doc, new_uses_libraries, True)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -301,7 +310,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, [])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_not_overwrite(self):
     """new_uses_libraries must not overwrite existing tags."""
@@ -310,7 +319,7 @@
         ('bar', 'false')])
     expected = manifest_input
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_add(self):
     """New names are added with 'required:true'."""
@@ -323,7 +332,7 @@
         ('baz', 'true'),
         ('qux', 'true')])
     output = self.run_test(manifest_input, ['bar', 'baz', 'qux'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_no_application(self):
     """When there is no <application> tag, the tag is added."""
@@ -336,7 +345,7 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_empty_application(self):
     """Even when here is an empty <application/> tag, the libs are added."""
@@ -350,16 +359,19 @@
         ('foo', 'true'),
         ('bar', 'true')])
     output = self.run_test(manifest_input, ['foo', 'bar'])
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class AddUsesNonSdkApiTest(unittest.TestCase):
   """Unit tests for add_uses_libraries function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_uses_non_sdk_api(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -377,23 +389,26 @@
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(False)
     expected = self.manifest_tmpl % self.uses_non_sdk_api(True)
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_already_set(self):
     """new_uses_libraries must not overwrite existing tags."""
     manifest_input = self.manifest_tmpl % self.uses_non_sdk_api(True)
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
 
 class UseEmbeddedDexTest(unittest.TestCase):
   """Unit tests for add_use_embedded_dex function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_use_embedded_dex(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -410,13 +425,13 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.use_embedded_dex('true')
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('true')
     expected = manifest_input
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_manifest_with_not_use_embedded_dex(self):
     manifest_input = self.manifest_tmpl % self.use_embedded_dex('false')
@@ -426,10 +441,13 @@
 class AddExtractNativeLibsTest(unittest.TestCase):
   """Unit tests for add_extract_native_libs function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest, value):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.add_extract_native_libs(doc, value)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -446,19 +464,19 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('true')
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_set_false(self):
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % self.extract_native_libs('false')
     output = self.run_test(manifest_input, False)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_match(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
     expected = manifest_input
     output = self.run_test(manifest_input, True)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_conflict(self):
     manifest_input = self.manifest_tmpl % self.extract_native_libs('true')
@@ -468,10 +486,13 @@
 class AddNoCodeApplicationTest(unittest.TestCase):
   """Unit tests for set_has_code_to_false function."""
 
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
   def run_test(self, input_manifest):
     doc = minidom.parseString(input_manifest)
     manifest_fixer.set_has_code_to_false(doc)
-    output = StringIO.StringIO()
+    output = io.StringIO()
     manifest_fixer.write_xml(output, doc)
     return output.getvalue()
 
@@ -485,27 +506,70 @@
     manifest_input = self.manifest_tmpl % ''
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_no_has_code(self):
     manifest_input = self.manifest_tmpl % '    <application/>\n'
     expected = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, expected)
+    self.assert_xml_equal(output, expected)
 
   def test_has_application_has_code_false(self):
     """ Do nothing if there's already an application elemeent. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="false"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
   def test_has_application_has_code_true(self):
-    """ Do nothing if there's already an application elemeent even if its
+    """ Do nothing if there's already an application element even if its
      hasCode attribute is true. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="true"/>\n'
     output = self.run_test(manifest_input)
-    self.assertEqual(output, manifest_input)
+    self.assert_xml_equal(output, manifest_input)
 
 
+class AddTestOnlyApplicationTest(unittest.TestCase):
+  """Unit tests for set_test_only_flag_to_true function."""
+
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
+  def run_test(self, input_manifest):
+    doc = minidom.parseString(input_manifest)
+    manifest_fixer.set_test_only_flag_to_true(doc)
+    output = io.StringIO()
+    manifest_fixer.write_xml(output, doc)
+    return output.getvalue()
+
+  manifest_tmpl = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '%s'
+      '</manifest>\n')
+
+  def test_no_application(self):
+    manifest_input = self.manifest_tmpl % ''
+    expected = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, expected)
+
+  def test_has_application_no_test_only(self):
+    manifest_input = self.manifest_tmpl % '    <application/>\n'
+    expected = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, expected)
+
+  def test_has_application_test_only_true(self):
+    """ If there's already an application element."""
+    manifest_input = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, manifest_input)
+
+  def test_has_application_test_only_false(self):
+    """ If there's already an application element with the testOnly attribute as false."""
+    manifest_input = self.manifest_tmpl % '    <application android:testOnly="false"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, manifest_input)
+
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/scripts/mergenotice.py b/scripts/mergenotice.py
deleted file mode 100755
index fe99073..0000000
--- a/scripts/mergenotice.py
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2019 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.
-#
-"""
-Merges input notice files to the output file while ignoring duplicated files
-This script shouldn't be confused with build/soong/scripts/generate-notice-files.py
-which is responsible for creating the final notice file for all artifacts
-installed. This script has rather limited scope; it is meant to create a merged
-notice file for a set of modules that are packaged together, e.g. in an APEX.
-The merged notice file does not reveal the individual files in the package.
-"""
-
-import sys
-import argparse
-
-def get_args():
-  parser = argparse.ArgumentParser(description='Merge notice files.')
-  parser.add_argument('--output', help='output file path.')
-  parser.add_argument('inputs', metavar='INPUT', nargs='+',
-                      help='input notice file')
-  return parser.parse_args()
-
-def main(argv):
-  args = get_args()
-
-  processed = set()
-  with open(args.output, 'w+') as output:
-    for input in args.inputs:
-      with open(input, 'r') as f:
-        data = f.read().strip()
-        if data not in processed:
-          processed.add(data)
-          output.write('%s\n\n' % data)
-
-if __name__ == '__main__':
-  main(sys.argv)
diff --git a/scripts/microfactory.bash b/scripts/microfactory.bash
index 4bb6058..192b38f 100644
--- a/scripts/microfactory.bash
+++ b/scripts/microfactory.bash
@@ -59,7 +59,7 @@
     BUILDDIR=$(getoutdir) \
       SRCDIR=${TOP} \
       BLUEPRINTDIR=${TOP}/build/blueprint \
-      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
+      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path rbcrun=${TOP}/build/make/tools/rbcrun -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf -pkg-path go.starlark.net=${TOP}/external/starlark-go" \
       build_go $@
 }
 
diff --git a/scripts/package-check.sh b/scripts/package-check.sh
index d7e602f..9f4a9da 100755
--- a/scripts/package-check.sh
+++ b/scripts/package-check.sh
@@ -42,7 +42,7 @@
   fi
   # Transform to a slash-separated path and add a trailing slash to enforce
   # package name boundary.
-  prefixes+=("${package//\./\/}/")
+  prefixes+=("${package//\.//}/")
   shift
 done
 
diff --git a/scripts/rbc-run b/scripts/rbc-run
new file mode 100755
index 0000000..8d93f0e
--- /dev/null
+++ b/scripts/rbc-run
@@ -0,0 +1,18 @@
+#! /bin/bash
+# Convert and run one configuration
+# Args: a product/board makefile optionally followed by additional arguments
+#       that will be passed to rbcrun.
+[[ $# -gt 1 && -f "$1" && -f "$2" ]] || { echo "Usage: ${0##*/} product.mk input_variables.mk [Additional rbcrun arguments]" >&2; exit 1; }
+set -eu
+
+declare -r output_root="${OUT_DIR:-out}"
+declare -r runner="${output_root}/rbcrun"
+declare -r converter="${output_root}/mk2rbc"
+declare -r launcher="${output_root}/rbc/launcher.rbc"
+declare -r makefile_list="${output_root}/.module_paths/configuration.list"
+declare -r makefile="$1"
+declare -r input_variables="$2"
+shift 2
+"${converter}" -mode=write -r --outdir "${output_root}/rbc" --input_variables "${input_variables}" --launcher="${launcher}" --makefile_list="${makefile_list}" "${makefile}"
+"${runner}" RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $@ "${launcher}"
+
diff --git a/scripts/reverse-deps.sh b/scripts/reverse-deps.sh
index 02b7dcb..410f5c0 100755
--- a/scripts/reverse-deps.sh
+++ b/scripts/reverse-deps.sh
@@ -67,6 +67,9 @@
       $0 ~ /^\S\S*:$/ {
         inoutput = 0
       }
+      $1 == "validations:" {
+        inoutput = 0
+      }
       inoutput != 0 {
         print gensub(/^\s*/, "", "g")" "depth
       }
diff --git a/scripts/rustfmt.toml b/scripts/rustfmt.toml
index 617d425..cefaa42 100644
--- a/scripts/rustfmt.toml
+++ b/scripts/rustfmt.toml
@@ -1,5 +1,5 @@
 # Android Format Style
 
-edition = "2018"
+edition = "2021"
 use_small_heuristics = "Max"
 newline_style = "Unix"
diff --git a/scripts/setup_go_workspace_for_soong.sh b/scripts/setup_go_workspace_for_soong.sh
index 479d09c..434d2fb 100755
--- a/scripts/setup_go_workspace_for_soong.sh
+++ b/scripts/setup_go_workspace_for_soong.sh
@@ -346,7 +346,7 @@
   "${ANDROID_PATH}/build/blueprint|${OUTPUT_PATH}/src/github.com/google/blueprint"
   "${ANDROID_PATH}/build/soong|${OUTPUT_PATH}/src/android/soong"
   "${ANDROID_PATH}/art/build|${OUTPUT_PATH}/src/android/soong/art"
-  "${ANDROID_PATH}/external/golang-protobuf|${OUTPUT_PATH}/src/github.com/golang/protobuf"
+  "${ANDROID_PATH}/external/golang-protobuf|${OUTPUT_PATH}/src/google.golang.org/protobuf"
   "${ANDROID_PATH}/external/llvm/soong|${OUTPUT_PATH}/src/android/soong/llvm"
   "${ANDROID_PATH}/external/clang/soong|${OUTPUT_PATH}/src/android/soong/clang"
   "${ANDROID_PATH}/external/robolectric-shadows/soong|${OUTPUT_PATH}/src/android/soong/robolectric"
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 32d5b17..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":
@@ -86,7 +88,7 @@
     if args.test_file_name:
       overwrite_test_file_name(doc, args.test_file_name)
 
-    with open(args.output, 'wb') as f:
+    with open(args.output, 'w') as f:
       write_xml(f, doc)
 
   # pylint: disable=broad-except
diff --git a/scripts/test_config_fixer_test.py b/scripts/test_config_fixer_test.py
index 1272c6b..39ce5b3 100644
--- a/scripts/test_config_fixer_test.py
+++ b/scripts/test_config_fixer_test.py
@@ -16,7 +16,7 @@
 #
 """Unit tests for test_config_fixer.py."""
 
-import StringIO
+import io
 import sys
 import unittest
 from xml.dom import minidom
@@ -59,7 +59,7 @@
     manifest = minidom.parseString(self.manifest)
 
     test_config_fixer.overwrite_package_name(doc, manifest, "com.soong.foo")
-    output = StringIO.StringIO()
+    output = io.StringIO()
     test_config_fixer.write_xml(output, doc)
 
     # Only the matching package name in a test node should be updated.
@@ -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 = StringIO.StringIO()
+    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/scripts/transitive-deps.sh b/scripts/transitive-deps.sh
index ba36ba4..cf9fb94 100755
--- a/scripts/transitive-deps.sh
+++ b/scripts/transitive-deps.sh
@@ -100,7 +100,7 @@
         currFileName = ""
         currExt = ""
       }
-      $1 == "outputs:" {
+      $1 == "outputs:" || $1 == "validations:" {
         ininput = 0
       }
       ininput == 0 && $0 ~ /^\S\S*:$/ {
diff --git a/scripts/unpack-prebuilt-apex.sh b/scripts/unpack-prebuilt-apex.sh
index 1acdeb5..f34a480 100755
--- a/scripts/unpack-prebuilt-apex.sh
+++ b/scripts/unpack-prebuilt-apex.sh
@@ -29,8 +29,6 @@
 shift 4
 REQUIRED_PATHS=$@
 
-set -x 1
-
 rm -fr $OUTPUT_DIR
 mkdir -p $OUTPUT_DIR
 
diff --git a/scripts/update_out b/scripts/update_out
new file mode 100755
index 0000000..d3950cf
--- /dev/null
+++ b/scripts/update_out
@@ -0,0 +1,21 @@
+#! /bin/bash
+# Run given command application and update the contents of a given file.
+# Will not change the file if its contents has not changed.
+[[ $# -gt 1 ]] || { echo "Usage: ${0##*/} FILE COMMAND" >&2; exit 1; }
+set -u
+declare -r outfile="$1"
+shift
+if [[ ! -f $outfile ]]; then
+	$@ >$outfile
+	exit
+fi
+
+declare -r newout=${outfile}.new
+$@ >$newout
+rc=$?
+if cmp -s $newout $outfile; then
+	rm $newout
+else
+	mv -f $newout $outfile
+fi
+exit $rc
diff --git a/sdk/Android.bp b/sdk/Android.bp
index 368c03a..f42b478 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -11,23 +11,30 @@
         "soong-android",
         "soong-apex",
         "soong-cc",
+        "soong-dexpreopt",
         "soong-java",
     ],
     srcs: [
         "bp.go",
+        "build_release.go",
         "exports.go",
+        "member_trait.go",
+        "member_type.go",
         "sdk.go",
         "update.go",
     ],
     testSrcs: [
         "bootclasspath_fragment_sdk_test.go",
         "bp_test.go",
+        "build_release_test.go",
         "cc_sdk_test.go",
         "compat_config_sdk_test.go",
         "exports_test.go",
         "java_sdk_test.go",
         "license_sdk_test.go",
+        "member_trait_test.go",
         "sdk_test.go",
+        "systemserverclasspath_fragment_sdk_test.go",
         "testing.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 412e806..2dacdb5 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -40,6 +40,7 @@
 			}
 		`, apex, fragment)),
 		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+		android.FixtureAddFile("frameworks/base/config/boot-image-profile.txt", nil),
 		android.FixtureAddFile("build/soong/scripts/check_boot_jars/package_allowed_list.txt", nil),
 	)
 }
@@ -134,11 +135,12 @@
     image_name: "art",
     contents: ["mybootlib"],
     hidden_api: {
-        stub_flags: "hiddenapi/stub-flags.csv",
         annotation_flags: "hiddenapi/annotation-flags.csv",
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
-        all_flags: "hiddenapi/all-flags.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -147,7 +149,7 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["com.android.art"],
-    jars: ["java/mybootlib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
 }
 `),
 		checkVersionedAndroidBpContents(`
@@ -161,11 +163,12 @@
     image_name: "art",
     contents: ["mysdk_mybootlib@current"],
     hidden_api: {
-        stub_flags: "hiddenapi/stub-flags.csv",
         annotation_flags: "hiddenapi/annotation-flags.csv",
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
-        all_flags: "hiddenapi/all-flags.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -174,7 +177,7 @@
     sdk_member_name: "mybootlib",
     visibility: ["//visibility:public"],
     apex_available: ["com.android.art"],
-    jars: ["java/mybootlib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
 }
 
 sdk_snapshot {
@@ -185,12 +188,13 @@
 }
 `),
 		checkAllCopyRules(`
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
-.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
+.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
 		`),
 		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
 
@@ -224,7 +228,7 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary", "mycoreplatform"),
-		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib", "myapex:myothersdklibrary"),
+		java.FixtureConfigureApexBootJars("myapex:mybootlib", "myapex:myothersdklibrary"),
 		prepareForSdkTestWithApex,
 
 		// Add a platform_bootclasspath that depends on the fragment.
@@ -332,11 +336,12 @@
         stub_libs: ["mycoreplatform"],
     },
     hidden_api: {
-        stub_flags: "hiddenapi/stub-flags.csv",
         annotation_flags: "hiddenapi/annotation-flags.csv",
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
-        all_flags: "hiddenapi/all-flags.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -345,7 +350,7 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java/mybootlib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
     permitted_packages: ["mybootlib"],
 }
 
@@ -416,11 +421,12 @@
         stub_libs: ["mysdk_mycoreplatform@current"],
     },
     hidden_api: {
-        stub_flags: "hiddenapi/stub-flags.csv",
         annotation_flags: "hiddenapi/annotation-flags.csv",
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
-        all_flags: "hiddenapi/all-flags.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -429,7 +435,7 @@
     sdk_member_name: "mybootlib",
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java/mybootlib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
     permitted_packages: ["mybootlib"],
 }
 
@@ -494,12 +500,13 @@
 }
 		`),
 		checkAllCopyRules(`
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
-.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
+.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
 .intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
 .intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
@@ -533,13 +540,138 @@
         snapshot/hiddenapi/index.csv
 			`, rule)
 
-			// Make sure that the permitted packages from the prebuilts end up in the
-			// updatable-bcp-packages.txt file.
-			rule = module.Output("updatable-bcp-packages.txt")
-			expectedContents := `'mybootlib\nmyothersdklibrary\n'`
-			android.AssertStringEquals(t, "updatable-bcp-packages.txt", expectedContents, rule.Args["content"])
+			rule = module.Output("out/soong/hiddenapi/hiddenapi-flags.csv.valid")
+			android.AssertStringDoesContain(t, "verify-overlaps", rule.RuleParams.Command, " snapshot/hiddenapi/filtered-flags.csv:snapshot/hiddenapi/signature-patterns.csv ")
 		}),
 		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestChecker(checkSnapshotWithSourcePreferred, func(t *testing.T, result *android.TestResult) {
+			module := result.ModuleForTests("platform-bootclasspath", "android_common")
+			rule := module.Output("out/soong/hiddenapi/hiddenapi-flags.csv.valid")
+			android.AssertStringDoesContain(t, "verify-overlaps", rule.RuleParams.Command, " out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv ")
+		}),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
+	)
+}
+
+// TestSnapshotWithBootClasspathFragment_Fragments makes sure that the fragments property of a
+// bootclasspath_fragment is correctly output to the sdk snapshot.
+func TestSnapshotWithBootClasspathFragment_Fragments(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary"),
+		java.FixtureConfigureApexBootJars("someapex:mysdklibrary", "myotherapex:myotherlib"),
+		prepareForSdkTestWithApex,
+
+		// Some additional files needed for the myotherapex.
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/myotherapex-file_contexts": nil,
+			"myotherapex/apex_manifest.json":                 nil,
+			"myotherapex/Test.java":                          nil,
+		}),
+
+		android.FixtureAddTextFile("myotherapex/Android.bp", `
+			apex {
+				name: "myotherapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				bootclasspath_fragments: ["myotherbootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "myotherbootclasspathfragment",
+				apex_available: ["myotherapex"],
+				contents: [
+					"myotherlib",
+				],
+			}
+
+			java_library {
+				name: "myotherlib",
+				apex_available: ["myotherapex"],
+				srcs: ["Test.java"],
+				min_sdk_version: "2",
+				permitted_packages: ["myothersdklibrary"],
+				compile_dex: true,
+			}
+		`),
+
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				contents: [
+					"mysdklibrary",
+				],
+				fragments: [
+					{
+						apex: "myotherapex",
+						module: "myotherbootclasspathfragment"
+					},
+				],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				shared_library: false,
+				public: {enabled: true},
+				min_sdk_version: "2",
+			}
+		`),
+	).RunTest(t)
+
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    contents: ["mysdklibrary"],
+    fragments: [
+        {
+            apex: "myotherapex",
+            module: "myotherbootclasspathfragment",
+        },
+    ],
+    hidden_api: {
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+		`),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
 		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
 	)
 }
@@ -607,7 +739,7 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("mysdklibrary"),
-		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib"),
+		java.FixtureConfigureApexBootJars("myapex:mybootlib"),
 		prepareForSdkTestWithApex,
 
 		// Add a platform_bootclasspath that depends on the fragment.
@@ -717,11 +849,12 @@
         max_target_o_low_priority: ["hiddenapi/my-max-target-o-low-priority.txt"],
         blocked: ["hiddenapi/my-blocked.txt"],
         unsupported_packages: ["hiddenapi/my-unsupported-packages.txt"],
-        stub_flags: "hiddenapi/stub-flags.csv",
         annotation_flags: "hiddenapi/annotation-flags.csv",
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
-        all_flags: "hiddenapi/all-flags.csv",
+        signature_patterns: "hiddenapi/signature-patterns.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -730,7 +863,7 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["myapex"],
-    jars: ["java/mybootlib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar"],
     permitted_packages: ["mybootlib"],
 }
 
@@ -760,12 +893,13 @@
 my-max-target-o-low-priority.txt -> hiddenapi/my-max-target-o-low-priority.txt
 my-blocked.txt -> hiddenapi/my-blocked.txt
 my-unsupported-packages.txt -> hiddenapi/my-unsupported-packages.txt
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
 .intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
-.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
-.intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/signature-patterns.csv -> hiddenapi/signature-patterns.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-stub-flags.csv -> hiddenapi/filtered-stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/filtered-flags.csv -> hiddenapi/filtered-flags.csv
+.intermediates/mysdk/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/mybootlib.jar
 .intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
diff --git a/sdk/build_release.go b/sdk/build_release.go
new file mode 100644
index 0000000..4c2277e
--- /dev/null
+++ b/sdk/build_release.go
@@ -0,0 +1,388 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+// Supports customizing sdk snapshot output based on target build release.
+
+// buildRelease represents the version of a build system used to create a specific release.
+//
+// The name of the release, is the same as the code for the dessert release, e.g. S, T, etc.
+type buildRelease struct {
+	// The name of the release, e.g. S, T, etc.
+	name string
+
+	// The index of this structure within the buildReleases list.
+	ordinal int
+}
+
+// String returns the name of the build release.
+func (s *buildRelease) String() string {
+	return s.name
+}
+
+// buildReleaseSet represents a set of buildRelease objects.
+type buildReleaseSet struct {
+	// Set of *buildRelease represented as a map from *buildRelease to struct{}.
+	contents map[*buildRelease]struct{}
+}
+
+// addItem adds a build release to the set.
+func (s *buildReleaseSet) addItem(release *buildRelease) {
+	s.contents[release] = struct{}{}
+}
+
+// addRange adds all the build releases from start (inclusive) to end (inclusive).
+func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) {
+	for i := start.ordinal; i <= end.ordinal; i += 1 {
+		s.addItem(buildReleases[i])
+	}
+}
+
+// contains returns true if the set contains the specified build release.
+func (s *buildReleaseSet) contains(release *buildRelease) bool {
+	_, ok := s.contents[release]
+	return ok
+}
+
+// String returns a string representation of the set, sorted from earliest to latest release.
+func (s *buildReleaseSet) String() string {
+	list := []string{}
+	for _, release := range buildReleases {
+		if _, ok := s.contents[release]; ok {
+			list = append(list, release.name)
+		}
+	}
+	return fmt.Sprintf("[%s]", strings.Join(list, ","))
+}
+
+var (
+	// nameToBuildRelease contains a map from name to build release.
+	nameToBuildRelease = map[string]*buildRelease{}
+
+	// buildReleases lists all the available build releases.
+	buildReleases = []*buildRelease{}
+
+	// allBuildReleaseSet is the set of all build releases.
+	allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	// Add the build releases from oldest to newest.
+	buildReleaseS = initBuildRelease("S")
+	buildReleaseT = initBuildRelease("Tiramisu")
+)
+
+// initBuildRelease creates a new build release with the specified name.
+func initBuildRelease(name string) *buildRelease {
+	ordinal := len(nameToBuildRelease)
+	release := &buildRelease{name: name, ordinal: ordinal}
+	nameToBuildRelease[name] = release
+	buildReleases = append(buildReleases, release)
+	allBuildReleaseSet.addItem(release)
+	return release
+}
+
+// latestBuildRelease returns the latest build release, i.e. the last one added.
+func latestBuildRelease() *buildRelease {
+	return buildReleases[len(buildReleases)-1]
+}
+
+// nameToRelease maps from build release name to the corresponding build release (if it exists) or
+// the error if it does not.
+func nameToRelease(name string) (*buildRelease, error) {
+	if r, ok := nameToBuildRelease[name]; ok {
+		return r, nil
+	}
+
+	return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet)
+}
+
+// parseBuildReleaseSet parses a build release set string specification into a build release set.
+//
+// The specification consists of one of the following:
+// * a single build release name, e.g. S, T, etc.
+// * a closed range (inclusive to inclusive), e.g. S-T
+// * an open range, e.g. T+.
+//
+// This returns the set if the specification was valid or an error.
+func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) {
+	set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	if strings.HasSuffix(specification, "+") {
+		rangeStart := strings.TrimSuffix(specification, "+")
+		start, err := nameToRelease(rangeStart)
+		if err != nil {
+			return nil, err
+		}
+		end := latestBuildRelease()
+		set.addRange(start, end)
+	} else if strings.Contains(specification, "-") {
+		limits := strings.SplitN(specification, "-", 2)
+		start, err := nameToRelease(limits[0])
+		if err != nil {
+			return nil, err
+		}
+
+		end, err := nameToRelease(limits[1])
+		if err != nil {
+			return nil, err
+		}
+
+		if start.ordinal > end.ordinal {
+			return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name)
+		}
+
+		set.addRange(start, end)
+	} else {
+		release, err := nameToRelease(specification)
+		if err != nil {
+			return nil, err
+		}
+		set.addItem(release)
+	}
+
+	return set, nil
+}
+
+// Given a set of properties (struct value), set the value of a field within that struct (or one of
+// its embedded structs) to its zero value.
+type fieldPrunerFunc func(structValue reflect.Value)
+
+// A property that can be cleared by a propertyPruner.
+type prunerProperty struct {
+	// The name of the field for this property. It is a "."-separated path for fields in non-anonymous
+	// sub-structs.
+	name string
+
+	// Sets the associated field to its zero value.
+	prunerFunc fieldPrunerFunc
+}
+
+// propertyPruner provides support for pruning (i.e. setting to their zero value) properties from
+// a properties structure.
+type propertyPruner struct {
+	// The properties that the pruner will clear.
+	properties []prunerProperty
+}
+
+// gatherFields recursively processes the supplied structure and a nested structures, selecting the
+// fields that require pruning and populates the propertyPruner.properties with the information
+// needed to prune those fields.
+//
+// containingStructAccessor is a func that if given an object will return a field whose value is
+// of the supplied structType. It is nil on initial entry to this method but when this method is
+// called recursively on a field that is a nested structure containingStructAccessor is set to a
+// func that provides access to the field's value.
+//
+// namePrefix is the prefix to the fields that are being visited. It is "" on initial entry to this
+// method but when this method is called recursively on a field that is a nested structure
+// namePrefix is the result of appending the field name (plus a ".") to the previous name prefix.
+// Unless the field is anonymous in which case it is passed through unchanged.
+//
+// selector is a func that will select whether the supplied field requires pruning or not. If it
+// returns true then the field will be added to those to be pruned, otherwise it will not.
+func (p *propertyPruner) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string, selector fieldSelectorFunc) {
+	for f := 0; f < structType.NumField(); f++ {
+		field := structType.Field(f)
+		if field.PkgPath != "" {
+			// Ignore unexported fields.
+			continue
+		}
+
+		// Save a copy of the field index for use in the function.
+		fieldIndex := f
+
+		name := namePrefix + field.Name
+
+		fieldGetter := func(container reflect.Value) reflect.Value {
+			if containingStructAccessor != nil {
+				// This is an embedded structure so first access the field for the embedded
+				// structure.
+				container = containingStructAccessor(container)
+			}
+
+			// Skip through interface and pointer values to find the structure.
+			container = getStructValue(container)
+
+			defer func() {
+				if r := recover(); r != nil {
+					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
+				}
+			}()
+
+			// Return the field.
+			return container.Field(fieldIndex)
+		}
+
+		fieldType := field.Type
+		if selector(name, field) {
+			zeroValue := reflect.Zero(fieldType)
+			fieldPruner := func(container reflect.Value) {
+				if containingStructAccessor != nil {
+					// This is an embedded structure so first access the field for the embedded
+					// structure.
+					container = containingStructAccessor(container)
+				}
+
+				// Skip through interface and pointer values to find the structure.
+				container = getStructValue(container)
+
+				defer func() {
+					if r := recover(); r != nil {
+						panic(fmt.Errorf("%s\n\tfor field (index %d, name %s)", r, fieldIndex, name))
+					}
+				}()
+
+				// Set the field.
+				container.Field(fieldIndex).Set(zeroValue)
+			}
+
+			property := prunerProperty{
+				name,
+				fieldPruner,
+			}
+			p.properties = append(p.properties, property)
+		} else {
+			switch fieldType.Kind() {
+			case reflect.Struct:
+				// Gather fields from the nested or embedded structure.
+				var subNamePrefix string
+				if field.Anonymous {
+					subNamePrefix = namePrefix
+				} else {
+					subNamePrefix = name + "."
+				}
+				p.gatherFields(fieldType, fieldGetter, subNamePrefix, selector)
+
+			case reflect.Map:
+				// Get the type of the values stored in the map.
+				valueType := fieldType.Elem()
+				// Skip over * types.
+				if valueType.Kind() == reflect.Ptr {
+					valueType = valueType.Elem()
+				}
+				if valueType.Kind() == reflect.Struct {
+					// If this is not referenced by a pointer then it is an error as it is impossible to
+					// modify a struct that is stored directly as a value in a map.
+					if fieldType.Elem().Kind() != reflect.Ptr {
+						panic(fmt.Errorf("Cannot prune struct %s stored by value in map %s, map values must"+
+							" be pointers to structs",
+							fieldType.Elem(), name))
+					}
+
+					// Create a new pruner for the values of the map.
+					valuePruner := newPropertyPrunerForStructType(valueType, selector)
+
+					// Create a new fieldPruner that will iterate over all the items in the map and call the
+					// pruner on them.
+					fieldPruner := func(container reflect.Value) {
+						mapValue := fieldGetter(container)
+
+						for _, keyValue := range mapValue.MapKeys() {
+							itemValue := mapValue.MapIndex(keyValue)
+
+							defer func() {
+								if r := recover(); r != nil {
+									panic(fmt.Errorf("%s\n\tfor key %q", r, keyValue))
+								}
+							}()
+
+							valuePruner.pruneProperties(itemValue.Interface())
+						}
+					}
+
+					// Add the map field pruner to the list of property pruners.
+					property := prunerProperty{
+						name + "[*]",
+						fieldPruner,
+					}
+					p.properties = append(p.properties, property)
+				}
+			}
+		}
+	}
+}
+
+// pruneProperties will prune (set to zero value) any properties in the struct referenced by the
+// supplied struct pointer.
+//
+// The struct must be of the same type as was originally passed to newPropertyPruner to create this
+// propertyPruner.
+func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) {
+
+	defer func() {
+		if r := recover(); r != nil {
+			panic(fmt.Errorf("%s\n\tof container %#v", r, propertiesStruct))
+		}
+	}()
+
+	structValue := reflect.ValueOf(propertiesStruct)
+	for _, property := range p.properties {
+		property.prunerFunc(structValue)
+	}
+}
+
+// fieldSelectorFunc is called to select whether a specific field should be pruned or not.
+// name is the name of the field, including any prefixes from containing str
+type fieldSelectorFunc func(name string, field reflect.StructField) bool
+
+// newPropertyPruner creates a new property pruner for the structure type for the supplied
+// properties struct.
+//
+// The returned pruner can be used on any properties structure of the same type as the supplied set
+// of properties.
+func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner {
+	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
+	return newPropertyPrunerForStructType(structType, selector)
+}
+
+// newPropertyPruner creates a new property pruner for the supplied properties struct type.
+//
+// The returned pruner can be used on any properties structure of the supplied type.
+func newPropertyPrunerForStructType(structType reflect.Type, selector fieldSelectorFunc) *propertyPruner {
+	pruner := &propertyPruner{}
+	pruner.gatherFields(structType, nil, "", selector)
+	return pruner
+}
+
+// newPropertyPrunerByBuildRelease creates a property pruner that will clear any properties in the
+// structure which are not supported by the specified target build release.
+//
+// A property is pruned if its field has a tag of the form:
+//     `supported_build_releases:"<build-release-set>"`
+// and the resulting build release set does not contain the target build release. Properties that
+// have no such tag are assumed to be supported by all releases.
+func newPropertyPrunerByBuildRelease(propertiesStruct interface{}, targetBuildRelease *buildRelease) *propertyPruner {
+	return newPropertyPruner(propertiesStruct, func(name string, field reflect.StructField) bool {
+		if supportedBuildReleases, ok := field.Tag.Lookup("supported_build_releases"); ok {
+			set, err := parseBuildReleaseSet(supportedBuildReleases)
+			if err != nil {
+				panic(fmt.Errorf("invalid `supported_build_releases` tag on %s of %T: %s", name, propertiesStruct, err))
+			}
+
+			// If the field does not support tha target release then prune it.
+			return !set.contains(targetBuildRelease)
+
+		} else {
+			// Any untagged fields are assumed to be supported by all build releases so should never be
+			// pruned.
+			return false
+		}
+	})
+}
diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go
new file mode 100644
index 0000000..6f1ef9e
--- /dev/null
+++ b/sdk/build_release_test.go
@@ -0,0 +1,227 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+// Tests for build_release.go
+
+var (
+	// Some additional test specific releases that are added after the currently supported ones and
+	// so are treated as being for future releases.
+	buildReleaseFuture1 = initBuildRelease("F1")
+	buildReleaseFuture2 = initBuildRelease("F2")
+)
+
+func TestNameToRelease(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		release, err := nameToRelease("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertDeepEquals(t, "release", buildReleaseS, release)
+	})
+	t.Run("invalid release", func(t *testing.T) {
+		release, err := nameToRelease("A")
+		android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release)
+		// Uses a wildcard in the error message to allow for additional build releases to be added to
+		// the supported set without breaking this test.
+		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,T.*,F1,F2\]`, []error{err})
+	})
+}
+
+func TestParseBuildReleaseSet(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S]", set.String())
+	})
+	t.Run("open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1+")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[F1,F2]", set.String())
+	})
+	t.Run("closed range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S-F1")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S,Tiramisu,F1]", set.String())
+	})
+	invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String()
+	t.Run("invalid release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A+")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range start", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range end", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("Tiramisu-A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid closed range reversed", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), `invalid closed range, start release "F1" is later than end release "S"`)
+	})
+}
+
+func TestBuildReleaseSetContains(t *testing.T) {
+	t.Run("contains", func(t *testing.T) {
+		set, _ := parseBuildReleaseSet("F1-F2")
+		android.AssertBoolEquals(t, "set contains F1", true, set.contains(buildReleaseFuture1))
+		android.AssertBoolEquals(t, "set does not contain S", false, set.contains(buildReleaseS))
+		android.AssertBoolEquals(t, "set contains F2", true, set.contains(buildReleaseFuture2))
+		android.AssertBoolEquals(t, "set does not contain T", false, set.contains(buildReleaseT))
+	})
+}
+
+func TestPropertyPrunerInvalidTag(t *testing.T) {
+	type brokenStruct struct {
+		Broken string `supported_build_releases:"A"`
+	}
+	type containingStruct struct {
+		Nested brokenStruct
+	}
+
+	t.Run("broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Broken of *sdk.brokenStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&brokenStruct{}, buildReleaseS)
+		})
+	})
+
+	t.Run("nested broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Nested.Broken of *sdk.containingStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&containingStruct{}, buildReleaseS)
+		})
+	})
+}
+
+func TestPropertyPrunerByBuildRelease(t *testing.T) {
+	type nested struct {
+		F1_only string `supported_build_releases:"F1"`
+	}
+
+	type mapped struct {
+		Default string
+		T_only  string `supported_build_releases:"Tiramisu"`
+	}
+
+	type testBuildReleasePruner struct {
+		Default      string
+		S_and_T_only string `supported_build_releases:"S-Tiramisu"`
+		T_later      string `supported_build_releases:"Tiramisu+"`
+		Nested       nested
+		Mapped       map[string]*mapped
+	}
+
+	inputFactory := func() testBuildReleasePruner {
+		return testBuildReleasePruner{
+			Default:      "Default",
+			S_and_T_only: "S_and_T_only",
+			T_later:      "T_later",
+			Nested: nested{
+				F1_only: "F1_only",
+			},
+			Mapped: map[string]*mapped{
+				"one": {
+					Default: "one-default",
+					T_only:  "one-t-only",
+				},
+				"two": {
+					Default: "two-default",
+					T_only:  "two-t-only",
+				},
+			},
+		}
+	}
+
+	marshal := func(t interface{}) string {
+		bytes, err := json.MarshalIndent(t, "", "  ")
+		if err != nil {
+			panic(err)
+		}
+		return string(bytes)
+	}
+
+	assertJsonEquals := func(t *testing.T, expected, actual interface{}) {
+		t.Helper()
+		expectedJson := marshal(expected)
+		actualJson := marshal(actual)
+		if actualJson != expectedJson {
+			t.Errorf("test struct: expected:\n%s\n got:\n%s", expectedJson, actualJson)
+		}
+	}
+
+	t.Run("target S", func(t *testing.T) {
+		testStruct := inputFactory()
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseS)
+		pruner.pruneProperties(&testStruct)
+
+		expected := inputFactory()
+		expected.T_later = ""
+		expected.Nested.F1_only = ""
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
+	})
+
+	t.Run("target T", func(t *testing.T) {
+		testStruct := inputFactory()
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseT)
+		pruner.pruneProperties(&testStruct)
+
+		expected := inputFactory()
+		expected.Nested.F1_only = ""
+		assertJsonEquals(t, expected, testStruct)
+	})
+
+	t.Run("target F1", func(t *testing.T) {
+		testStruct := inputFactory()
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture1)
+		pruner.pruneProperties(&testStruct)
+
+		expected := inputFactory()
+		expected.S_and_T_only = ""
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
+	})
+
+	t.Run("target F2", func(t *testing.T) {
+		testStruct := inputFactory()
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture2)
+		pruner.pruneProperties(&testStruct)
+
+		expected := inputFactory()
+		expected.S_and_T_only = ""
+		expected.Nested.F1_only = ""
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
+	})
+}
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 31555c0..571d214 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -15,6 +15,7 @@
 package sdk
 
 import (
+	"fmt"
 	"testing"
 
 	"android/soong/android"
@@ -32,6 +33,23 @@
 	"some/where/stubslib.map.txt":     nil,
 }
 
+// Adds a native bridge target to the configured list of targets.
+var prepareForTestWithNativeBridgeTarget = android.FixtureModifyConfig(func(config android.Config) {
+	config.Targets[android.Android] = append(config.Targets[android.Android], android.Target{
+		Os: android.Android,
+		Arch: android.Arch{
+			ArchType:     android.Arm64,
+			ArchVariant:  "armv8-a",
+			CpuVariant:   "cpu",
+			Abi:          nil,
+			ArchFeatures: nil,
+		},
+		NativeBridge:             android.NativeBridgeEnabled,
+		NativeBridgeHostArchName: "x86_64",
+		NativeBridgeRelativePath: "native_bridge",
+	})
+})
+
 func testSdkWithCc(t *testing.T, bp string) *android.TestResult {
 	t.Helper()
 	return testSdkWithFs(t, bp, ccTestFs)
@@ -175,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, `
@@ -347,6 +257,7 @@
 		cc_object {
 			name: "crtobj",
 			stl: "none",
+			system_shared_libs: [],
 			sanitize: {
 				never: true,
 			},
@@ -364,6 +275,7 @@
     apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
+    system_shared_libs: [],
     sanitize: {
         never: true,
     },
@@ -388,6 +300,7 @@
     apex_available: ["//apex_available:platform"],
     stl: "none",
     compile_multilib: "both",
+    system_shared_libs: [],
     sanitize: {
         never: true,
     },
@@ -1751,7 +1664,6 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    recovery_available: true,
     vendor_available: true,
     stl: "none",
     compile_multilib: "both",
@@ -1786,7 +1698,6 @@
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
     installable: false,
-    recovery_available: true,
     vendor_available: true,
     stl: "none",
     compile_multilib: "both",
@@ -1976,6 +1887,146 @@
 	)
 }
 
+func TestSnapshotWithCcHeadersLibraryAndNativeBridgeSupport(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		PrepareForTestWithSdkBuildComponents,
+		ccTestFs.AddToFixture(),
+		prepareForTestWithNativeBridgeTarget,
+	).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				native_bridge_support: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			native_bridge_supported: true,
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_library_headers {
+    name: "mynativeheaders",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    native_bridge_supported: true,
+    stl: "none",
+    compile_multilib: "both",
+    system_shared_libs: [],
+    export_include_dirs: ["include/myinclude"],
+}
+`),
+		checkAllCopyRules(`
+myinclude/Test.h -> include/myinclude/Test.h
+`),
+	)
+}
+
+// TestSnapshotWithCcHeadersLibrary_DetectsNativeBridgeSpecificProperties verifies that when a
+// module that has different output files for a native bridge target requests the native bridge
+// variants are copied into the sdk snapshot that it reports an error.
+func TestSnapshotWithCcHeadersLibrary_DetectsNativeBridgeSpecificProperties(t *testing.T) {
+	android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		PrepareForTestWithSdkBuildComponents,
+		ccTestFs.AddToFixture(),
+		prepareForTestWithNativeBridgeTarget,
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`\QArchitecture variant "arm64_native_bridge" of sdk member "mynativeheaders" has properties distinct from other variants; this is not yet supported. The properties are:
+        export_include_dirs: [
+            "arm64_native_bridge/include/myinclude_nativebridge",
+            "arm64_native_bridge/include/myinclude",
+        ],\E`)).
+		RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				native_bridge_support: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			native_bridge_supported: true,
+			target: {
+				native_bridge: {
+					export_include_dirs: ["myinclude_nativebridge"],
+				},
+			},
+		}
+	`)
+}
+
+func TestSnapshotWithCcHeadersLibraryAndImageVariants(t *testing.T) {
+	testImageVariant := func(t *testing.T, property, trait string) {
+		result := android.GroupFixturePreparers(
+			cc.PrepareForTestWithCcDefaultModules,
+			PrepareForTestWithSdkBuildComponents,
+			ccTestFs.AddToFixture(),
+		).RunTestWithBp(t, fmt.Sprintf(`
+		sdk {
+			name: "mysdk",
+			native_header_libs: ["mynativeheaders"],
+			traits: {
+				%s: ["mynativeheaders"],
+			},
+		}
+
+		cc_library_headers {
+			name: "mynativeheaders",
+			export_include_dirs: ["myinclude"],
+			stl: "none",
+			system_shared_libs: [],
+			%s: true,
+		}
+	`, trait, property))
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkUnversionedAndroidBpContents(fmt.Sprintf(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_library_headers {
+    name: "mynativeheaders",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    %s: true,
+    stl: "none",
+    compile_multilib: "both",
+    system_shared_libs: [],
+    export_include_dirs: ["include/myinclude"],
+}
+`, property)),
+			checkAllCopyRules(`
+myinclude/Test.h -> include/myinclude/Test.h
+`),
+		)
+	}
+
+	t.Run("ramdisk", func(t *testing.T) {
+		testImageVariant(t, "ramdisk_available", "ramdisk_image_required")
+	})
+
+	t.Run("recovery", func(t *testing.T) {
+		testImageVariant(t, "recovery_available", "recovery_image_required")
+	})
+}
+
 func TestHostSnapshotWithCcHeadersLibrary(t *testing.T) {
 	result := testSdkWithCc(t, `
 		sdk {
@@ -2676,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.
@@ -2707,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 813dcfd..a99fa1f 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,
@@ -453,7 +369,7 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    jars: ["java/myjavalib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar"],
     permitted_packages: ["pkg.myjavalib"],
 }
 `),
@@ -465,7 +381,7 @@
     sdk_member_name: "myjavalib",
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
-    jars: ["java/myjavalib.jar"],
+    jars: ["java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar"],
     permitted_packages: ["pkg.myjavalib"],
 }
 
@@ -474,9 +390,75 @@
     visibility: ["//visibility:public"],
     java_boot_libs: ["myexports_myjavalib@current"],
 }
+
 `),
 		checkAllCopyRules(`
-.intermediates/myjavalib/android_common/withres/myjavalib.jar -> java/myjavalib.jar
+.intermediates/myexports/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar
+`),
+	)
+}
+
+func TestSnapshotWithJavaSystemserverLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
+	).RunTestWithBp(t, `
+		module_exports {
+			name: "myexports",
+			java_systemserver_libs: ["myjavalib"],
+		}
+
+		java_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			java_resources: ["resource.txt"],
+			// The aidl files should not be copied to the snapshot because a java_systemserver_libs member
+			// is not intended to be used for compiling Java, only for accessing the dex implementation
+			// jar.
+			aidl: {
+				export_include_dirs: ["aidl"],
+			},
+			system_modules: "none",
+			sdk_version: "none",
+			compile_dex: true,
+			permitted_packages: ["pkg.myjavalib"],
+		}
+	`)
+
+	CheckSnapshot(t, result, "myexports", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    permitted_packages: ["pkg.myjavalib"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myexports_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+    permitted_packages: ["pkg.myjavalib"],
+}
+
+module_exports_snapshot {
+    name: "myexports@current",
+    visibility: ["//visibility:public"],
+    java_systemserver_libs: ["myexports_myjavalib@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/myexports/common_os/empty -> java_systemserver_libs/snapshot/jars/are/invalid/myjavalib.jar
 `),
 	)
 }
@@ -1204,6 +1186,107 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_AnnotationsZip(t *testing.T) {
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        annotations: "sdk_library/public/myjavalib_annotations.zip",
+        sdk_version: "current",
+    },
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_annotations.zip -> sdk_library/public/myjavalib_annotations.zip
+		`),
+		checkMergeZips(".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip"),
+	)
+}
+
+func TestSnapshotWithJavaSdkLibrary_AnnotationsZip_PreT(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJavaSdkLibrary,
+		android.FixtureMergeEnv(map[string]string{
+			"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+		}),
+	).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+		`),
+		checkMergeZips(".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip"),
+	)
+}
+
 func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
@@ -1257,7 +1340,7 @@
 			ctx := android.ModuleInstallPathContextForTesting(result.Config)
 			dexJarBuildPath := func(name string, kind android.SdkKind) string {
 				dep := result.Module(name, "android_common").(java.SdkLibraryDependency)
-				path := dep.SdkApiStubDexJar(ctx, kind)
+				path := dep.SdkApiStubDexJar(ctx, kind).Path()
 				return path.RelativeToTop().String()
 			}
 
diff --git a/sdk/member_trait.go b/sdk/member_trait.go
new file mode 100644
index 0000000..4229ca8
--- /dev/null
+++ b/sdk/member_trait.go
@@ -0,0 +1,126 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"reflect"
+
+	"android/soong/android"
+	"github.com/google/blueprint/proptools"
+)
+
+// Contains information about the sdk properties that list sdk members by trait, e.g.
+// native_bridge.
+type sdkMemberTraitListProperty struct {
+	// getter for the list of member names
+	getter func(properties interface{}) []string
+
+	// the trait of member referenced in the list
+	memberTrait android.SdkMemberTrait
+}
+
+// Cache of dynamically generated dynamicSdkMemberTraits objects. The key is the pointer
+// to a slice of SdkMemberTrait instances returned by android.RegisteredSdkMemberTraits().
+var dynamicSdkMemberTraitsMap android.OncePer
+
+// A dynamically generated set of member list properties and associated structure type.
+//
+// Instances of this are created by createDynamicSdkMemberTraits.
+type dynamicSdkMemberTraits struct {
+	// The dynamically generated structure type.
+	//
+	// Contains one []string exported field for each SdkMemberTrait returned by android.RegisteredSdkMemberTraits(). The name of
+	// the field is the exported form of the value returned by SdkMemberTrait.SdkPropertyName().
+	propertiesStructType reflect.Type
+
+	// Information about each of the member trait specific list properties.
+	memberTraitListProperties []*sdkMemberTraitListProperty
+}
+
+func (d *dynamicSdkMemberTraits) createMemberTraitListProperties() interface{} {
+	return reflect.New(d.propertiesStructType).Interface()
+}
+
+func getDynamicSdkMemberTraits(key android.OnceKey, registeredTraits []android.SdkMemberTrait) *dynamicSdkMemberTraits {
+	// Get the cached value, creating new instance if necessary.
+	return dynamicSdkMemberTraitsMap.Once(key, func() interface{} {
+		return createDynamicSdkMemberTraits(registeredTraits)
+	}).(*dynamicSdkMemberTraits)
+}
+
+// Create the dynamicSdkMemberTraits from the list of registered member traits.
+//
+// A struct is created which contains one exported field per member trait corresponding to
+// the SdkMemberTrait.SdkPropertyName() value.
+//
+// A list of sdkMemberTraitListProperty instances is created, one per member trait that provides:
+// * a reference to the member trait.
+// * a getter for the corresponding field in the properties struct.
+//
+func createDynamicSdkMemberTraits(sdkMemberTraits []android.SdkMemberTrait) *dynamicSdkMemberTraits {
+
+	var listProperties []*sdkMemberTraitListProperty
+	memberTraitToProperty := map[android.SdkMemberTrait]*sdkMemberTraitListProperty{}
+	var fields []reflect.StructField
+
+	// Iterate over the member traits creating StructField and sdkMemberTraitListProperty objects.
+	nextFieldIndex := 0
+	for _, memberTrait := range sdkMemberTraits {
+
+		p := memberTrait.SdkPropertyName()
+
+		var getter func(properties interface{}) []string
+
+		// Create a dynamic exported field for the member trait's property.
+		fields = append(fields, reflect.StructField{
+			Name: proptools.FieldNameForProperty(p),
+			Type: reflect.TypeOf([]string{}),
+		})
+
+		// Copy the field index for use in the getter func as using the loop variable directly will
+		// cause all funcs to use the last value.
+		fieldIndex := nextFieldIndex
+		nextFieldIndex += 1
+
+		getter = func(properties interface{}) []string {
+			// The properties is expected to be of the following form (where
+			// <Module_traits> is the name of an SdkMemberTrait.SdkPropertyName().
+			//     properties *struct {<Module_traits> []string, ....}
+			//
+			// Although it accesses the field by index the following reflection code is equivalent to:
+			//    *properties.<Module_traits>
+			//
+			list := reflect.ValueOf(properties).Elem().Field(fieldIndex).Interface().([]string)
+			return list
+		}
+
+		// Create an sdkMemberTraitListProperty for the member trait.
+		memberListProperty := &sdkMemberTraitListProperty{
+			getter:      getter,
+			memberTrait: memberTrait,
+		}
+
+		memberTraitToProperty[memberTrait] = memberListProperty
+		listProperties = append(listProperties, memberListProperty)
+	}
+
+	// Create a dynamic struct from the collated fields.
+	propertiesStructType := reflect.StructOf(fields)
+
+	return &dynamicSdkMemberTraits{
+		memberTraitListProperties: listProperties,
+		propertiesStructType:      propertiesStructType,
+	}
+}
diff --git a/sdk/member_trait_test.go b/sdk/member_trait_test.go
new file mode 100644
index 0000000..a3db189
--- /dev/null
+++ b/sdk/member_trait_test.go
@@ -0,0 +1,287 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"fmt"
+	"path/filepath"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+	"github.com/google/blueprint"
+)
+
+type fakeMemberTrait struct {
+	android.SdkMemberTraitBase
+}
+
+type fakeMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (t *fakeMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
+	for _, name := range names {
+		ctx.AddVariationDependencies(nil, dependencyTag, name)
+
+		if ctx.RequiresTrait(name, extraTrait) {
+			ctx.AddVariationDependencies(nil, dependencyTag, name+"_extra")
+		}
+		if ctx.RequiresTrait(name, specialTrait) {
+			ctx.AddVariationDependencies(nil, dependencyTag, name+"_special")
+		}
+	}
+}
+
+func (t *fakeMemberType) IsInstance(module android.Module) bool {
+	return true
+}
+
+func (t *fakeMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
+	moduleType := "java_import"
+	if ctx.RequiresTrait(extraTrait) {
+		moduleType = "java_test_import"
+	}
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, moduleType)
+}
+
+func (t *fakeMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+	return &fakeMemberTypeProperties{}
+}
+
+type fakeMemberTypeProperties struct {
+	android.SdkMemberPropertiesBase
+
+	path android.Path
+}
+
+func (t *fakeMemberTypeProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
+	headerJars := variant.(java.ApexDependency).HeaderJars()
+	if len(headerJars) != 1 {
+		panic(fmt.Errorf("there must be only one header jar from %q", variant.Name()))
+	}
+
+	t.path = headerJars[0]
+}
+
+func (t *fakeMemberTypeProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
+	if t.path != nil {
+		relative := filepath.Join("javalibs", t.path.Base())
+		ctx.SnapshotBuilder().CopyToSnapshot(t.path, relative)
+		propertySet.AddProperty("jars", []string{relative})
+	}
+}
+
+var (
+	extraTrait = &fakeMemberTrait{
+		SdkMemberTraitBase: android.SdkMemberTraitBase{
+			PropertyName: "extra",
+		},
+	}
+
+	specialTrait = &fakeMemberTrait{
+		SdkMemberTraitBase: android.SdkMemberTraitBase{
+			PropertyName: "special",
+		},
+	}
+
+	fakeType = &fakeMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "fake_members",
+			SupportsSdk:  true,
+			Traits: []android.SdkMemberTrait{
+				extraTrait,
+				specialTrait,
+			},
+		},
+	}
+)
+
+func init() {
+	android.RegisterSdkMemberTrait(extraTrait)
+	android.RegisterSdkMemberTrait(specialTrait)
+	android.RegisterSdkMemberType(fakeType)
+}
+
+func TestBasicTrait_WithoutTrait(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				fake_members: ["myjavalib"],
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    fake_members: ["mysdk_myjavalib@current"],
+}
+`),
+	)
+}
+
+func TestBasicTrait_MultipleTraits(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				fake_members: ["myjavalib", "anotherjavalib"],
+				traits: {
+					extra: ["myjavalib"],
+					special: ["myjavalib", "anotherjavalib"],
+				},
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "myjavalib_extra",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "myjavalib_special",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "anotherjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+
+			java_library {
+				name: "anotherjavalib_special",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_test_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib_extra",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib_extra.jar"],
+}
+
+java_import {
+    name: "myjavalib_special",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/myjavalib_special.jar"],
+}
+
+java_import {
+    name: "anotherjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/anotherjavalib.jar"],
+}
+
+java_import {
+    name: "anotherjavalib_special",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["javalibs/anotherjavalib_special.jar"],
+}
+`),
+	)
+}
+
+func TestTraitUnsupportedByMemberType(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				java_header_libs: ["myjavalib"],
+				traits: {
+					extra: ["myjavalib"],
+				},
+			}
+
+			java_library {
+				name: "myjavalib",
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+			}
+		`),
+	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+		`\Qsdk member "myjavalib" has traits [extra] that are unsupported by its member type "java_header_libs"\E`)).
+		RunTest(t)
+}
diff --git a/sdk/member_type.go b/sdk/member_type.go
new file mode 100644
index 0000000..10669fe
--- /dev/null
+++ b/sdk/member_type.go
@@ -0,0 +1,157 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"reflect"
+
+	"android/soong/android"
+	"github.com/google/blueprint/proptools"
+)
+
+// Contains information about the sdk properties that list sdk members by type, e.g.
+// Java_header_libs.
+type sdkMemberTypeListProperty struct {
+	// getter for the list of member names
+	getter func(properties interface{}) []string
+
+	// setter for the list of member names
+	setter func(properties interface{}, list []string)
+
+	// the type of member referenced in the list
+	memberType android.SdkMemberType
+
+	// the dependency tag used for items in this list that can be used to determine the memberType
+	// for a resolved dependency.
+	dependencyTag android.SdkMemberDependencyTag
+}
+
+func (p *sdkMemberTypeListProperty) propertyName() string {
+	return p.memberType.SdkPropertyName()
+}
+
+// Cache of dynamically generated dynamicSdkMemberTypes objects. The key is the pointer
+// to a slice of SdkMemberType instances held in android.SdkMemberTypes.
+var dynamicSdkMemberTypesMap android.OncePer
+
+// A dynamically generated set of member list properties and associated structure type.
+type dynamicSdkMemberTypes struct {
+	// The dynamically generated structure type.
+	//
+	// Contains one []string exported field for each android.SdkMemberTypes. The name of the field
+	// is the exported form of the value returned by SdkMemberType.SdkPropertyName().
+	propertiesStructType reflect.Type
+
+	// Information about each of the member type specific list properties.
+	memberTypeListProperties []*sdkMemberTypeListProperty
+
+	memberTypeToProperty map[android.SdkMemberType]*sdkMemberTypeListProperty
+}
+
+func (d *dynamicSdkMemberTypes) createMemberTypeListProperties() interface{} {
+	return reflect.New(d.propertiesStructType).Interface()
+}
+
+func getDynamicSdkMemberTypes(key android.OnceKey, registeredTypes []android.SdkMemberType) *dynamicSdkMemberTypes {
+	// Get the cached value, creating new instance if necessary.
+	return dynamicSdkMemberTypesMap.Once(key, func() interface{} {
+		return createDynamicSdkMemberTypes(registeredTypes)
+	}).(*dynamicSdkMemberTypes)
+}
+
+// Create the dynamicSdkMemberTypes from the list of registered member types.
+//
+// A struct is created which contains one exported field per member type corresponding to
+// the SdkMemberType.SdkPropertyName() value.
+//
+// A list of sdkMemberTypeListProperty instances is created, one per member type that provides:
+// * a reference to the member type.
+// * a getter for the corresponding field in the properties struct.
+// * a dependency tag that identifies the member type of a resolved dependency.
+//
+func createDynamicSdkMemberTypes(sdkMemberTypes []android.SdkMemberType) *dynamicSdkMemberTypes {
+
+	var listProperties []*sdkMemberTypeListProperty
+	memberTypeToProperty := map[android.SdkMemberType]*sdkMemberTypeListProperty{}
+	var fields []reflect.StructField
+
+	// Iterate over the member types creating StructField and sdkMemberTypeListProperty objects.
+	nextFieldIndex := 0
+	for _, memberType := range sdkMemberTypes {
+
+		p := memberType.SdkPropertyName()
+
+		var getter func(properties interface{}) []string
+		var setter func(properties interface{}, list []string)
+		if memberType.RequiresBpProperty() {
+			// Create a dynamic exported field for the member type's property.
+			fields = append(fields, reflect.StructField{
+				Name: proptools.FieldNameForProperty(p),
+				Type: reflect.TypeOf([]string{}),
+				Tag:  `android:"arch_variant"`,
+			})
+
+			// Copy the field index for use in the getter func as using the loop variable directly will
+			// cause all funcs to use the last value.
+			fieldIndex := nextFieldIndex
+			nextFieldIndex += 1
+
+			getter = func(properties interface{}) []string {
+				// The properties is expected to be of the following form (where
+				// <Module_types> is the name of an SdkMemberType.SdkPropertyName().
+				//     properties *struct {<Module_types> []string, ....}
+				//
+				// Although it accesses the field by index the following reflection code is equivalent to:
+				//    *properties.<Module_types>
+				//
+				list := reflect.ValueOf(properties).Elem().Field(fieldIndex).Interface().([]string)
+				return list
+			}
+
+			setter = func(properties interface{}, list []string) {
+				// The properties is expected to be of the following form (where
+				// <Module_types> is the name of an SdkMemberType.SdkPropertyName().
+				//     properties *struct {<Module_types> []string, ....}
+				//
+				// Although it accesses the field by index the following reflection code is equivalent to:
+				//    *properties.<Module_types> = list
+				//
+				reflect.ValueOf(properties).Elem().Field(fieldIndex).Set(reflect.ValueOf(list))
+			}
+		}
+
+		// Create an sdkMemberTypeListProperty for the member type.
+		memberListProperty := &sdkMemberTypeListProperty{
+			getter:     getter,
+			setter:     setter,
+			memberType: memberType,
+
+			// Dependencies added directly from member properties are always exported.
+			dependencyTag: android.DependencyTagForSdkMemberType(memberType, true),
+		}
+
+		memberTypeToProperty[memberType] = memberListProperty
+		listProperties = append(listProperties, memberListProperty)
+	}
+
+	// Create a dynamic struct from the collated fields.
+	propertiesStructType := reflect.StructOf(fields)
+
+	return &dynamicSdkMemberTypes{
+		memberTypeListProperties: listProperties,
+		memberTypeToProperty:     memberTypeToProperty,
+		propertiesStructType:     propertiesStructType,
+	}
+}
diff --git a/sdk/sdk.go b/sdk/sdk.go
index b1c8aeb..c8c7b79 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -17,7 +17,6 @@
 import (
 	"fmt"
 	"io"
-	"reflect"
 	"strconv"
 
 	"github.com/google/blueprint"
@@ -40,7 +39,6 @@
 	ctx.RegisterModuleType("sdk", SdkModuleFactory)
 	ctx.RegisterModuleType("sdk_snapshot", SnapshotModuleFactory)
 	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
 }
 
 type sdk struct {
@@ -50,10 +48,17 @@
 	// The dynamically generated information about the registered SdkMemberType
 	dynamicSdkMemberTypes *dynamicSdkMemberTypes
 
-	// The dynamically created instance of the properties struct containing the sdk member
+	// The dynamically created instance of the properties struct containing the sdk member type
 	// list properties, e.g. java_libs.
 	dynamicMemberTypeListProperties interface{}
 
+	// The dynamically generated information about the registered SdkMemberTrait
+	dynamicSdkMemberTraits *dynamicSdkMemberTraits
+
+	// The dynamically created instance of the properties struct containing the sdk member trait
+	// list properties.
+	dynamicMemberTraitListProperties interface{}
+
 	// Information about the OsType specific member variants depended upon by this variant.
 	//
 	// Set by OsType specific variants in the collectMembers() method and used by the
@@ -95,148 +100,6 @@
 	Prebuilt_visibility []string
 }
 
-// Contains information about the sdk properties that list sdk members, e.g.
-// Java_header_libs.
-type sdkMemberListProperty struct {
-	// getter for the list of member names
-	getter func(properties interface{}) []string
-
-	// setter for the list of member names
-	setter func(properties interface{}, list []string)
-
-	// the type of member referenced in the list
-	memberType android.SdkMemberType
-
-	// the dependency tag used for items in this list that can be used to determine the memberType
-	// for a resolved dependency.
-	dependencyTag android.SdkMemberTypeDependencyTag
-}
-
-func (p *sdkMemberListProperty) propertyName() string {
-	return p.memberType.SdkPropertyName()
-}
-
-// Cache of dynamically generated dynamicSdkMemberTypes objects. The key is the pointer
-// to a slice of SdkMemberType instances held in android.SdkMemberTypes.
-var dynamicSdkMemberTypesMap android.OncePer
-
-// A dynamically generated set of member list properties and associated structure type.
-type dynamicSdkMemberTypes struct {
-	// The dynamically generated structure type.
-	//
-	// Contains one []string exported field for each android.SdkMemberTypes. The name of the field
-	// is the exported form of the value returned by SdkMemberType.SdkPropertyName().
-	propertiesStructType reflect.Type
-
-	// Information about each of the member type specific list properties.
-	memberListProperties []*sdkMemberListProperty
-
-	memberTypeToProperty map[android.SdkMemberType]*sdkMemberListProperty
-}
-
-func (d *dynamicSdkMemberTypes) createMemberListProperties() interface{} {
-	return reflect.New(d.propertiesStructType).Interface()
-}
-
-func getDynamicSdkMemberTypes(registry *android.SdkMemberTypesRegistry) *dynamicSdkMemberTypes {
-
-	// Get a key that uniquely identifies the registry contents.
-	key := registry.UniqueOnceKey()
-
-	// Get the registered types.
-	registeredTypes := registry.RegisteredTypes()
-
-	// Get the cached value, creating new instance if necessary.
-	return dynamicSdkMemberTypesMap.Once(key, func() interface{} {
-		return createDynamicSdkMemberTypes(registeredTypes)
-	}).(*dynamicSdkMemberTypes)
-}
-
-// Create the dynamicSdkMemberTypes from the list of registered member types.
-//
-// A struct is created which contains one exported field per member type corresponding to
-// the SdkMemberType.SdkPropertyName() value.
-//
-// A list of sdkMemberListProperty instances is created, one per member type that provides:
-// * a reference to the member type.
-// * a getter for the corresponding field in the properties struct.
-// * a dependency tag that identifies the member type of a resolved dependency.
-//
-func createDynamicSdkMemberTypes(sdkMemberTypes []android.SdkMemberType) *dynamicSdkMemberTypes {
-
-	var listProperties []*sdkMemberListProperty
-	memberTypeToProperty := map[android.SdkMemberType]*sdkMemberListProperty{}
-	var fields []reflect.StructField
-
-	// Iterate over the member types creating StructField and sdkMemberListProperty objects.
-	nextFieldIndex := 0
-	for _, memberType := range sdkMemberTypes {
-
-		p := memberType.SdkPropertyName()
-
-		var getter func(properties interface{}) []string
-		var setter func(properties interface{}, list []string)
-		if memberType.RequiresBpProperty() {
-			// Create a dynamic exported field for the member type's property.
-			fields = append(fields, reflect.StructField{
-				Name: proptools.FieldNameForProperty(p),
-				Type: reflect.TypeOf([]string{}),
-				Tag:  `android:"arch_variant"`,
-			})
-
-			// Copy the field index for use in the getter func as using the loop variable directly will
-			// cause all funcs to use the last value.
-			fieldIndex := nextFieldIndex
-			nextFieldIndex += 1
-
-			getter = func(properties interface{}) []string {
-				// The properties is expected to be of the following form (where
-				// <Module_types> is the name of an SdkMemberType.SdkPropertyName().
-				//     properties *struct {<Module_types> []string, ....}
-				//
-				// Although it accesses the field by index the following reflection code is equivalent to:
-				//    *properties.<Module_types>
-				//
-				list := reflect.ValueOf(properties).Elem().Field(fieldIndex).Interface().([]string)
-				return list
-			}
-
-			setter = func(properties interface{}, list []string) {
-				// The properties is expected to be of the following form (where
-				// <Module_types> is the name of an SdkMemberType.SdkPropertyName().
-				//     properties *struct {<Module_types> []string, ....}
-				//
-				// Although it accesses the field by index the following reflection code is equivalent to:
-				//    *properties.<Module_types> = list
-				//
-				reflect.ValueOf(properties).Elem().Field(fieldIndex).Set(reflect.ValueOf(list))
-			}
-		}
-
-		// Create an sdkMemberListProperty for the member type.
-		memberListProperty := &sdkMemberListProperty{
-			getter:     getter,
-			setter:     setter,
-			memberType: memberType,
-
-			// Dependencies added directly from member properties are always exported.
-			dependencyTag: android.DependencyTagForSdkMemberType(memberType, true),
-		}
-
-		memberTypeToProperty[memberType] = memberListProperty
-		listProperties = append(listProperties, memberListProperty)
-	}
-
-	// Create a dynamic struct from the collated fields.
-	propertiesStructType := reflect.StructOf(fields)
-
-	return &dynamicSdkMemberTypes{
-		memberListProperties: listProperties,
-		memberTypeToProperty: memberTypeToProperty,
-		propertiesStructType: propertiesStructType,
-	}
-}
-
 // sdk defines an SDK which is a logical group of modules (e.g. native libs, headers, java libs, etc.)
 // which Mainline modules like APEX can choose to build with.
 func SdkModuleFactory() android.Module {
@@ -247,17 +110,25 @@
 	s := &sdk{}
 	s.properties.Module_exports = moduleExports
 	// Get the dynamic sdk member type data for the currently registered sdk member types.
-	var registry *android.SdkMemberTypesRegistry
-	if moduleExports {
-		registry = android.ModuleExportsMemberTypes
-	} else {
-		registry = android.SdkMemberTypes
-	}
-	s.dynamicSdkMemberTypes = getDynamicSdkMemberTypes(registry)
+	sdkMemberTypeKey, sdkMemberTypes := android.RegisteredSdkMemberTypes(moduleExports)
+	s.dynamicSdkMemberTypes = getDynamicSdkMemberTypes(sdkMemberTypeKey, sdkMemberTypes)
 	// Create an instance of the dynamically created struct that contains all the
 	// properties for the member type specific list properties.
-	s.dynamicMemberTypeListProperties = s.dynamicSdkMemberTypes.createMemberListProperties()
-	s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties)
+	s.dynamicMemberTypeListProperties = s.dynamicSdkMemberTypes.createMemberTypeListProperties()
+
+	sdkMemberTraitsKey, sdkMemberTraits := android.RegisteredSdkMemberTraits()
+	s.dynamicSdkMemberTraits = getDynamicSdkMemberTraits(sdkMemberTraitsKey, sdkMemberTraits)
+	// Create an instance of the dynamically created struct that contains all the properties for the
+	// member trait specific list properties.
+	s.dynamicMemberTraitListProperties = s.dynamicSdkMemberTraits.createMemberTraitListProperties()
+
+	// Create a wrapper around the dynamic trait specific properties so that they have to be
+	// specified within a traits:{} section in the .bp file.
+	traitsWrapper := struct {
+		Traits interface{}
+	}{s.dynamicMemberTraitListProperties}
+
+	s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties, &traitsWrapper)
 
 	// Make sure that the prebuilt visibility property is verified for errors.
 	android.AddVisibilityProperty(s, "prebuilt_visibility", &s.properties.Prebuilt_visibility)
@@ -280,14 +151,19 @@
 	return s
 }
 
-func (s *sdk) memberListProperties() []*sdkMemberListProperty {
-	return s.dynamicSdkMemberTypes.memberListProperties
+func (s *sdk) memberTypeListProperties() []*sdkMemberTypeListProperty {
+	return s.dynamicSdkMemberTypes.memberTypeListProperties
 }
 
-func (s *sdk) memberListProperty(memberType android.SdkMemberType) *sdkMemberListProperty {
+func (s *sdk) memberTypeListProperty(memberType android.SdkMemberType) *sdkMemberTypeListProperty {
 	return s.dynamicSdkMemberTypes.memberTypeToProperty[memberType]
 }
 
+// memberTraitListProperties returns the list of *sdkMemberTraitListProperty instances for this sdk.
+func (s *sdk) memberTraitListProperties() []*sdkMemberTraitListProperty {
+	return s.dynamicSdkMemberTraits.memberTraitListProperties
+}
+
 func (s *sdk) snapshot() bool {
 	return s.properties.Snapshot
 }
@@ -341,6 +217,57 @@
 	}}
 }
 
+// gatherTraits gathers the traits from the dynamically generated trait specific properties.
+//
+// Returns a map from member name to the set of required traits.
+func (s *sdk) gatherTraits() map[string]android.SdkMemberTraitSet {
+	traitListByMember := map[string][]android.SdkMemberTrait{}
+	for _, memberListProperty := range s.memberTraitListProperties() {
+		names := memberListProperty.getter(s.dynamicMemberTraitListProperties)
+		for _, name := range names {
+			traitListByMember[name] = append(traitListByMember[name], memberListProperty.memberTrait)
+		}
+	}
+
+	traitSetByMember := map[string]android.SdkMemberTraitSet{}
+	for name, list := range traitListByMember {
+		traitSetByMember[name] = android.NewSdkMemberTraitSet(list)
+	}
+
+	return traitSetByMember
+}
+
+// newDependencyContext creates a new SdkDependencyContext for this sdk.
+func (s *sdk) newDependencyContext(mctx android.BottomUpMutatorContext) android.SdkDependencyContext {
+	traits := s.gatherTraits()
+
+	return &dependencyContext{
+		BottomUpMutatorContext: mctx,
+		requiredTraits:         traits,
+	}
+}
+
+type dependencyContext struct {
+	android.BottomUpMutatorContext
+
+	// Map from member name to the set of traits that the sdk requires the member provides.
+	requiredTraits map[string]android.SdkMemberTraitSet
+}
+
+func (d *dependencyContext) RequiredTraits(name string) android.SdkMemberTraitSet {
+	if s, ok := d.requiredTraits[name]; ok {
+		return s
+	} else {
+		return android.EmptySdkMemberTraitSet()
+	}
+}
+
+func (d *dependencyContext) RequiresTrait(name string, trait android.SdkMemberTrait) bool {
+	return d.RequiredTraits(name).Contains(trait)
+}
+
+var _ android.SdkDependencyContext = (*dependencyContext)(nil)
+
 // RegisterPreDepsMutators registers pre-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
@@ -350,20 +277,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
 }
@@ -410,14 +323,28 @@
 	if s, ok := mctx.Module().(*sdk); ok {
 		// Add dependencies from enabled and non CommonOS variants to the sdk member variants.
 		if s.Enabled() && !s.IsCommonOSVariant() {
-			for _, memberListProperty := range s.memberListProperties() {
+			ctx := s.newDependencyContext(mctx)
+			for _, memberListProperty := range s.memberTypeListProperties() {
 				if memberListProperty.getter == nil {
 					continue
 				}
 				names := memberListProperty.getter(s.dynamicMemberTypeListProperties)
 				if len(names) > 0 {
+					memberType := memberListProperty.memberType
+
+					// Verify that the member type supports the specified traits.
+					supportedTraits := memberType.SupportedTraits()
+					for _, name := range names {
+						requiredTraits := ctx.RequiredTraits(name)
+						unsupportedTraits := requiredTraits.Subtract(supportedTraits)
+						if !unsupportedTraits.Empty() {
+							ctx.ModuleErrorf("sdk member %q has traits %s that are unsupported by its member type %q", name, unsupportedTraits, memberType.SdkPropertyName())
+						}
+					}
+
+					// Add dependencies using the appropriate tag.
 					tag := memberListProperty.dependencyTag
-					memberListProperty.memberType.AddDependencies(mctx, tag, names)
+					memberType.AddDependencies(ctx, tag, names)
 				}
 			}
 		}
@@ -471,103 +398,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 a13b0d7..40de150 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -15,83 +15,28 @@
 package sdk
 
 import (
-	"android/soong/android"
 	"log"
 	"os"
+	"runtime"
 	"testing"
 
+	"android/soong/android"
+	"android/soong/java"
+
 	"github.com/google/blueprint/proptools"
 )
 
 // Needed in an _test.go file in this package to ensure tests run correctly, particularly in IDE.
 func TestMain(m *testing.M) {
-	if android.BuildOs != android.Linux {
+	if runtime.GOOS != "linux" {
 		// b/145598135 - Generating host snapshots for anything other than linux is not supported.
-		log.Printf("Skipping as sdk snapshot generation is only supported on %s not %s", android.Linux, android.BuildOs)
+		log.Printf("Skipping as sdk snapshot generation is only supported on linux not %s", runtime.GOOS)
 		os.Exit(0)
 	}
 
 	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) {
@@ -565,6 +510,49 @@
 		)
 	})
 
+	t.Run("SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR=module:build_from_source", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR": "module:build_from_source",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-current.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    use_source_config_var: {
+        config_namespace: "module",
+        var_name: "build_from_source",
+    },
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@current"],
+}
+			`),
+		)
+	})
+
 	t.Run("SOONG_SDK_SNAPSHOT_VERSION=unversioned", func(t *testing.T) {
 		result := android.GroupFixturePreparers(
 			preparer,
@@ -661,4 +649,86 @@
 			snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)),
 		)
 	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			prepareForSdkTestWithJava,
+			java.PrepareForTestWithJavaDefaultModules,
+			java.PrepareForTestWithJavaSdkLibraryFiles,
+			java.FixtureWithLastReleaseApis("mysdklibrary"),
+			android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
+				contents: ["mysdklibrary"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				public: {enabled: true},
+				permitted_packages: ["mysdklibrary"],
+			}
+		`),
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+			}),
+		).RunTest(t)
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: ["mysdklibrary"],
+    hidden_api: {
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        stub_flags: "hiddenapi/stub-flags.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    permitted_packages: ["mysdklibrary"],
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+`),
+
+			checkAllCopyRules(`
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
+`),
+		)
+	})
+
 }
diff --git a/sdk/systemserverclasspath_fragment_sdk_test.go b/sdk/systemserverclasspath_fragment_sdk_test.go
new file mode 100644
index 0000000..16e3e7f
--- /dev/null
+++ b/sdk/systemserverclasspath_fragment_sdk_test.go
@@ -0,0 +1,171 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+	"android/soong/java"
+)
+
+func TestSnapshotWithSystemServerClasspathFragment(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mysdklibrary"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:mylib", "myapex:mysdklibrary"),
+		prepareForSdkTestWithApex,
+
+		android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+				java_sdk_libs: [
+					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+					// a java_sdk_libs module because it is used in the mysystemserverclasspathfragment's
+					// contents property. However, it is specified here to ensure that duplicates are
+					// correctly deduped.
+					"mysdklibrary",
+				],
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+			}
+
+			systemserverclasspath_fragment {
+				name: "mysystemserverclasspathfragment",
+				apex_available: ["myapex"],
+				contents: [
+					"mylib",
+					"mysdklibrary",
+				],
+			}
+
+			java_library {
+				name: "mylib",
+				apex_available: ["myapex"],
+				srcs: ["Test.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				min_sdk_version: "2",
+				compile_dex: true,
+				permitted_packages: ["mylib"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				apex_available: ["myapex"],
+				srcs: ["Test.java"],
+				shared_library: false,
+				public: {enabled: true},
+				min_sdk_version: "2",
+			}
+		`),
+	).RunTest(t)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_import {
+    name: "mylib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    permitted_packages: ["mylib"],
+}
+
+prebuilt_systemserverclasspath_fragment {
+    name: "mysystemserverclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: [
+        "mylib",
+        "mysdklibrary",
+    ],
+}
+`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "mysdk_mysdklibrary@current",
+    sdk_member_name: "mysdklibrary",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_import {
+    name: "mysdk_mylib@current",
+    sdk_member_name: "mylib",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    jars: ["java_systemserver_libs/snapshot/jars/are/invalid/mylib.jar"],
+    permitted_packages: ["mylib"],
+}
+
+prebuilt_systemserverclasspath_fragment {
+    name: "mysdk_mysystemserverclasspathfragment@current",
+    sdk_member_name: "mysystemserverclasspathfragment",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: [
+        "mysdk_mylib@current",
+        "mysdk_mysdklibrary@current",
+    ],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_sdk_libs: ["mysdk_mysdklibrary@current"],
+    java_systemserver_libs: ["mysdk_mylib@current"],
+    systemserverclasspath_fragments: ["mysdk_mysystemserverclasspathfragment@current"],
+}
+`),
+	)
+}
diff --git a/sdk/testing.go b/sdk/testing.go
index 3254cf9..062f200 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -25,6 +25,8 @@
 	"android/soong/cc"
 	"android/soong/genrule"
 	"android/soong/java"
+
+	"github.com/google/blueprint/proptools"
 )
 
 // Prepare for running an sdk test with an apex.
@@ -81,6 +83,11 @@
 		}
 	}),
 
+	// Add a build number file.
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.BuildNumberFile = proptools.StringPtr(BUILD_NUMBER_FILE)
+	}),
+
 	// Make sure that every test provides all the source files.
 	android.PrepareForTestDisallowNonExistentPaths,
 	android.MockFS{
@@ -136,12 +143,15 @@
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
+		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
 
 	buildParams := sdk.BuildParamsForTests()
 	copyRules := &strings.Builder{}
 	otherCopyRules := &strings.Builder{}
 	snapshotDirPrefix := sdk.builderForTests.snapshotDir.String() + "/"
+
+	seenBuildNumberFile := false
 	for _, bp := range buildParams {
 		switch bp.Rule.String() {
 		case android.Cp.String():
@@ -151,8 +161,14 @@
 			src := android.NormalizePathForTesting(bp.Input)
 			// We differentiate between copy rules for the snapshot, and copy rules for the install file.
 			if strings.HasPrefix(output.String(), snapshotDirPrefix) {
-				// Get source relative to build directory.
-				_, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest)
+				// Don't include the build-number.txt file in the copy rules as that would break lots of
+				// tests, just verify that it is copied here as it should appear in every snapshot.
+				if output.Base() == BUILD_NUMBER_FILE {
+					seenBuildNumberFile = true
+				} else {
+					// Get source relative to build directory.
+					_, _ = fmt.Fprintf(copyRules, "%s -> %s\n", src, dest)
+				}
 				info.snapshotContents = append(info.snapshotContents, dest)
 			} else {
 				_, _ = fmt.Fprintf(otherCopyRules, "%s -> %s\n", src, dest)
@@ -188,6 +204,10 @@
 		}
 	}
 
+	if !seenBuildNumberFile {
+		panic(fmt.Sprintf("Every snapshot must include the %s file", BUILD_NUMBER_FILE))
+	}
+
 	info.copyRules = copyRules.String()
 	info.otherCopyRules = otherCopyRules.String()
 
@@ -253,6 +273,13 @@
 	}
 	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
 
+	// If the generated snapshot builders not for the current release then it cannot be loaded by
+	// the current release.
+	currentBuildRelease := latestBuildRelease()
+	if snapshotBuildInfo.targetBuildRelease != currentBuildRelease {
+		return
+	}
+
 	// The preparers from the original source fixture.
 	sourcePreparers := result.Preparer()
 
@@ -476,6 +503,9 @@
 	// The final output zip.
 	outputZip string
 
+	// The target build release.
+	targetBuildRelease *buildRelease
+
 	// The test specific customizations for each snapshot test.
 	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
 }
diff --git a/sdk/update.go b/sdk/update.go
index 84f0e4e..5db604b 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -22,6 +22,7 @@
 
 	"android/soong/apex"
 	"android/soong/cc"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -35,6 +36,37 @@
 //     By default every unversioned module in the generated snapshot has prefer: false. Building it
 //     with SOONG_SDK_SNAPSHOT_PREFER=true will force them to use prefer: true.
 //
+// SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR
+//     If set this specifies the Soong config var that can be used to control whether the prebuilt
+//     modules from the generated snapshot or the original source modules. Values must be a colon
+//     separated pair of strings, the first of which is the Soong config namespace, and the second
+//     is the name of the variable within that namespace.
+//
+//     The config namespace and var name are used to set the `use_source_config_var` property. That
+//     in turn will cause the generated prebuilts to use the soong config variable to select whether
+//     source or the prebuilt is used.
+//     e.g. If an sdk snapshot is built using:
+//       m SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR=acme:build_from_source sdkextensions-sdk
+//     Then the resulting snapshot will include:
+//       use_source_config_var: {
+//         config_namespace: "acme",
+//         var_name: "build_from_source",
+//       }
+//
+//     Assuming that the config variable is defined in .mk using something like:
+//       $(call add_soong_config_namespace,acme)
+//       $(call add_soong_config_var_value,acme,build_from_source,true)
+//
+//     Then when the snapshot is unpacked in the repository it will have the following behavior:
+//       m droid - will use the sdkextensions-sdk prebuilts if present. Otherwise, it will use the
+//           sources.
+//       m SOONG_CONFIG_acme_build_from_source=true droid - will use the sdkextensions-sdk
+//            sources, if present. Otherwise, it will use the prebuilts.
+//
+//     This is a temporary mechanism to control the prefer flags and will be removed once a more
+//     maintainable solution has been implemented.
+//     TODO(b/174997203): Remove when no longer necessary.
+//
 // SOONG_SDK_SNAPSHOT_VERSION
 //     This provides control over the version of the generated snapshot.
 //
@@ -49,6 +81,19 @@
 //     snapshot module only. The zip file containing the generated snapshot will be
 //     <sdk-name>-<number>.zip.
 //
+// SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE
+//     This allows the target build release (i.e. the release version of the build within which
+//     the snapshot will be used) of the snapshot to be specified. If unspecified then it defaults
+//     to the current build release version. Otherwise, it must be the name of one of the build
+//     releases defined in nameToBuildRelease, e.g. S, T, etc..
+//
+//     The generated snapshot must only be used in the specified target release. If the target
+//     build release is not the current build release then the generated Android.bp file not be
+//     checked for compatibility.
+//
+//     e.g. if setting SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S will cause the generated snapshot
+//     to be compatible with S.
+//
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
@@ -113,8 +158,16 @@
 	gc.indentLevel--
 }
 
-func (gc *generatedContents) Printfln(format string, args ...interface{}) {
-	fmt.Fprintf(&(gc.content), strings.Repeat("    ", gc.indentLevel)+format+"\n", args...)
+// IndentedPrintf will add spaces to indent the line to the appropriate level before printing the
+// arguments.
+func (gc *generatedContents) IndentedPrintf(format string, args ...interface{}) {
+	fmt.Fprintf(&(gc.content), strings.Repeat("    ", gc.indentLevel)+format, args...)
+}
+
+// UnindentedPrintf does not add spaces to indent the line to the appropriate level before printing
+// the arguments.
+func (gc *generatedContents) UnindentedPrintf(format string, args ...interface{}) {
+	fmt.Fprintf(&(gc.content), format, args...)
 }
 
 func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
@@ -145,7 +198,7 @@
 	s.multilibUsages = multilibNone
 	ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
 		tag := ctx.OtherModuleDependencyTag(child)
-		if memberTag, ok := tag.(android.SdkMemberTypeDependencyTag); ok {
+		if memberTag, ok := tag.(android.SdkMemberDependencyTag); ok {
 			memberType := memberTag.SdkMemberType(child)
 
 			// If a nil SdkMemberType was returned then this module should not be added to the sdk.
@@ -211,7 +264,7 @@
 	}
 
 	var members []*sdkMember
-	for _, memberListProperty := range s.memberListProperties() {
+	for _, memberListProperty := range s.memberTypeListProperties() {
 		membersOfType := byType[memberListProperty.memberType]
 		members = append(members, membersOfType...)
 	}
@@ -228,6 +281,10 @@
 	return append(variants, newVariant)
 }
 
+// BUILD_NUMBER_FILE is the name of the file in the snapshot zip that will contain the number of
+// the build from which the snapshot was produced.
+const BUILD_NUMBER_FILE = "snapshot-creation-build-number.txt"
+
 // SDK directory structure
 // <sdk_root>/
 //     Android.bp   : definition of a 'sdk' module is here. This is a hand-made one.
@@ -318,6 +375,14 @@
 		snapshotZipFileSuffix = "-" + version
 	}
 
+	currentBuildRelease := latestBuildRelease()
+	targetBuildReleaseEnv := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE", currentBuildRelease.name)
+	targetBuildRelease, err := nameToRelease(targetBuildReleaseEnv)
+	if err != nil {
+		ctx.ModuleErrorf("invalid SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE: %s", err)
+		targetBuildRelease = currentBuildRelease
+	}
+
 	builder := &snapshotBuilder{
 		ctx:                   ctx,
 		sdk:                   s,
@@ -329,6 +394,7 @@
 		prebuiltModules:       make(map[string]*bpModule),
 		allMembersByName:      allMembersByName,
 		exportedMembersByName: exportedMembersByName,
+		targetBuildRelease:    targetBuildRelease,
 	}
 	s.builderForTests = builder
 
@@ -353,10 +419,18 @@
 	members := s.groupMemberVariantsByMemberThenType(ctx, memberVariantDeps)
 
 	// Create the prebuilt modules for each of the member modules.
+	traits := s.gatherTraits()
 	for _, member := range members {
 		memberType := member.memberType
 
-		memberCtx := &memberContext{ctx, builder, memberType, member.name}
+		name := member.name
+		requiredTraits := traits[name]
+		if requiredTraits == nil {
+			requiredTraits = android.EmptySdkMemberTraitSet()
+		}
+
+		// Create the snapshot for the member.
+		memberCtx := &memberContext{ctx, builder, memberType, name, requiredTraits}
 
 		prebuiltModule := memberType.AddPrebuiltModule(memberCtx, member)
 		s.createMemberSnapshot(memberCtx, member, prebuiltModule.(*bpModule))
@@ -401,10 +475,17 @@
 	generateBpContents(&bp.generatedContents, bpFile)
 
 	contents := bp.content.String()
-	syntaxCheckSnapshotBpFile(ctx, contents)
+	// If the snapshot is being generated for the current build release then check the syntax to make
+	// sure that it is compatible.
+	if targetBuildRelease == currentBuildRelease {
+		syntaxCheckSnapshotBpFile(ctx, contents)
+	}
 
 	bp.build(pctx, ctx, nil)
 
+	// Copy the build number file into the snapshot.
+	builder.CopyToSnapshot(ctx.Config().BuildNumberFile(ctx), BUILD_NUMBER_FILE)
+
 	filesToZip := builder.filesToZip
 
 	// zip them all
@@ -627,7 +708,7 @@
 		staticProperties := &snapshotModuleStaticProperties{
 			Compile_multilib: sdkVariant.multilibUsages.String(),
 		}
-		dynamicProperties := s.dynamicSdkMemberTypes.createMemberListProperties()
+		dynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
 
 		combinedProperties := &combinedSnapshotModuleProperties{
 			sdkVariant:        sdkVariant,
@@ -647,7 +728,7 @@
 		}
 
 		combined := sdkVariantToCombinedProperties[memberVariantDep.sdkVariant]
-		memberListProperty := s.memberListProperty(memberVariantDep.memberType)
+		memberListProperty := s.memberTypeListProperty(memberVariantDep.memberType)
 		memberName := ctx.OtherModuleName(memberVariantDep.variant)
 
 		if memberListProperty.getter == nil {
@@ -677,7 +758,7 @@
 	}
 
 	// Extract the common members, removing them from the original properties.
-	commonDynamicProperties := s.dynamicSdkMemberTypes.createMemberListProperties()
+	commonDynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
 	extractor := newCommonValueExtractor(commonDynamicProperties)
 	extractCommonProperties(ctx, extractor, commonDynamicProperties, propertyContainers)
 
@@ -710,7 +791,7 @@
 	}
 
 	dynamicMemberTypeListProperties := combined.dynamicProperties
-	for _, memberListProperty := range s.memberListProperties() {
+	for _, memberListProperty := range s.memberTypeListProperties() {
 		if memberListProperty.getter == nil {
 			continue
 		}
@@ -725,6 +806,8 @@
 	name string
 }
 
+var _ android.BpPropertyTag = propertyTag{}
+
 // A BpPropertyTag to add to a property that contains references to other sdk members.
 //
 // This will cause the references to be rewritten to a versioned reference in the version
@@ -752,6 +835,8 @@
 	module.insertAfter("name", "sdk_member_name", name)
 	// Remove the prefer property if present as versioned modules never need marking with prefer.
 	module.removeProperty("prefer")
+	// Ditto for use_source_config_var
+	module.removeProperty("use_source_config_var")
 	return module
 }
 
@@ -809,13 +894,13 @@
 }
 
 func generateFilteredBpContents(contents *generatedContents, bpFile *bpFile, moduleFilter func(module *bpModule) bool) {
-	contents.Printfln("// This is auto-generated. DO NOT EDIT.")
+	contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n")
 	for _, bpModule := range bpFile.order {
 		if moduleFilter(bpModule) {
-			contents.Printfln("")
-			contents.Printfln("%s {", bpModule.moduleType)
+			contents.IndentedPrintf("\n")
+			contents.IndentedPrintf("%s {\n", bpModule.moduleType)
 			outputPropertySet(contents, bpModule.bpPropertySet)
-			contents.Printfln("}")
+			contents.IndentedPrintf("}\n")
 		}
 	}
 }
@@ -826,7 +911,7 @@
 	addComment := func(name string) {
 		if text, ok := set.comments[name]; ok {
 			for _, line := range strings.Split(text, "\n") {
-				contents.Printfln("// %s", line)
+				contents.IndentedPrintf("// %s\n", line)
 			}
 		}
 	}
@@ -843,29 +928,8 @@
 		}
 
 		addComment(name)
-		switch v := value.(type) {
-		case []string:
-			length := len(v)
-			if length > 1 {
-				contents.Printfln("%s: [", name)
-				contents.Indent()
-				for i := 0; i < length; i = i + 1 {
-					contents.Printfln("%q,", v[i])
-				}
-				contents.Dedent()
-				contents.Printfln("],")
-			} else if length == 0 {
-				contents.Printfln("%s: [],", name)
-			} else {
-				contents.Printfln("%s: [%q],", name, v[0])
-			}
-
-		case bool:
-			contents.Printfln("%s: %t,", name, v)
-
-		default:
-			contents.Printfln("%s: %q,", name, value)
-		}
+		reflectValue := reflect.ValueOf(value)
+		outputNamedValue(contents, name, reflectValue)
 	}
 
 	for _, name := range set.order {
@@ -875,15 +939,94 @@
 		switch v := value.(type) {
 		case *bpPropertySet:
 			addComment(name)
-			contents.Printfln("%s: {", name)
+			contents.IndentedPrintf("%s: {\n", name)
 			outputPropertySet(contents, v)
-			contents.Printfln("},")
+			contents.IndentedPrintf("},\n")
 		}
 	}
 
 	contents.Dedent()
 }
 
+// outputNamedValue outputs a value that has an associated name. The name will be indented, followed
+// by the value and then followed by a , and a newline.
+func outputNamedValue(contents *generatedContents, name string, value reflect.Value) {
+	contents.IndentedPrintf("%s: ", name)
+	outputUnnamedValue(contents, value)
+	contents.UnindentedPrintf(",\n")
+}
+
+// outputUnnamedValue outputs a single value. The value is not indented and is not followed by
+// either a , or a newline. With multi-line values, e.g. slices, all but the first line will be
+// indented and all but the last line will end with a newline.
+func outputUnnamedValue(contents *generatedContents, value reflect.Value) {
+	valueType := value.Type()
+	switch valueType.Kind() {
+	case reflect.Bool:
+		contents.UnindentedPrintf("%t", value.Bool())
+
+	case reflect.String:
+		contents.UnindentedPrintf("%q", value)
+
+	case reflect.Ptr:
+		outputUnnamedValue(contents, value.Elem())
+
+	case reflect.Slice:
+		length := value.Len()
+		if length == 0 {
+			contents.UnindentedPrintf("[]")
+		} else {
+			firstValue := value.Index(0)
+			if length == 1 && !multiLineValue(firstValue) {
+				contents.UnindentedPrintf("[")
+				outputUnnamedValue(contents, firstValue)
+				contents.UnindentedPrintf("]")
+			} else {
+				contents.UnindentedPrintf("[\n")
+				contents.Indent()
+				for i := 0; i < length; i++ {
+					itemValue := value.Index(i)
+					contents.IndentedPrintf("")
+					outputUnnamedValue(contents, itemValue)
+					contents.UnindentedPrintf(",\n")
+				}
+				contents.Dedent()
+				contents.IndentedPrintf("]")
+			}
+		}
+
+	case reflect.Struct:
+		// Avoid unlimited recursion by requiring every structure to implement android.BpPrintable.
+		v := value.Interface()
+		if _, ok := v.(android.BpPrintable); !ok {
+			panic(fmt.Errorf("property value %#v of type %T does not implement android.BpPrintable", v, v))
+		}
+		contents.UnindentedPrintf("{\n")
+		contents.Indent()
+		for f := 0; f < valueType.NumField(); f++ {
+			fieldType := valueType.Field(f)
+			if fieldType.Anonymous {
+				continue
+			}
+			fieldValue := value.Field(f)
+			fieldName := fieldType.Name
+			propertyName := proptools.PropertyNameForField(fieldName)
+			outputNamedValue(contents, propertyName, fieldValue)
+		}
+		contents.Dedent()
+		contents.IndentedPrintf("}")
+
+	default:
+		panic(fmt.Errorf("Unknown type: %T of value %#v", value, value))
+	}
+}
+
+// multiLineValue returns true if the supplied value may require multiple lines in the output.
+func multiLineValue(value reflect.Value) bool {
+	kind := value.Kind()
+	return kind == reflect.Slice || kind == reflect.Struct
+}
+
 func (s *sdk) GetAndroidBpContentsForTests() string {
 	contents := &generatedContents{}
 	generateBpContents(contents, s.builderForTests.bpFile)
@@ -930,6 +1073,9 @@
 	filesToZip  android.Paths
 	zipsToMerge android.Paths
 
+	// The path to an empty file.
+	emptyFile android.WritablePath
+
 	prebuiltModules map[string]*bpModule
 	prebuiltOrder   []*bpModule
 
@@ -938,6 +1084,9 @@
 
 	// The set of exported members by name.
 	exportedMembersByName map[string]struct{}
+
+	// The target build release for which the snapshot is to be generated.
+	targetBuildRelease *buildRelease
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -980,6 +1129,19 @@
 	s.zipsToMerge = append(s.zipsToMerge, tmpZipPath)
 }
 
+func (s *snapshotBuilder) EmptyFile() android.Path {
+	if s.emptyFile == nil {
+		ctx := s.ctx
+		s.emptyFile = android.PathForModuleOut(ctx, "empty")
+		s.ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Touch,
+			Output: s.emptyFile,
+		})
+	}
+
+	return s.emptyFile
+}
+
 func (s *snapshotBuilder) AddPrebuiltModule(member android.SdkMember, moduleType string) android.BpModule {
 	name := member.Name()
 	if s.prebuiltModules[name] != nil {
@@ -1267,19 +1429,19 @@
 	osInfo.Properties = osSpecificVariantPropertiesFactory()
 
 	// Group the variants by arch type.
-	var variantsByArchName = make(map[string][]android.Module)
-	var archTypes []android.ArchType
+	var variantsByArchId = make(map[archId][]android.Module)
+	var archIds []archId
 	for _, variant := range osTypeVariants {
-		archType := variant.Target().Arch.ArchType
-		archTypeName := archType.Name
-		if _, ok := variantsByArchName[archTypeName]; !ok {
-			archTypes = append(archTypes, archType)
+		target := variant.Target()
+		id := archIdFromTarget(target)
+		if _, ok := variantsByArchId[id]; !ok {
+			archIds = append(archIds, id)
 		}
 
-		variantsByArchName[archTypeName] = append(variantsByArchName[archTypeName], variant)
+		variantsByArchId[id] = append(variantsByArchId[id], variant)
 	}
 
-	if commonVariants, ok := variantsByArchName["common"]; ok {
+	if commonVariants, ok := variantsByArchId[commonArchId]; ok {
 		if len(osTypeVariants) != 1 {
 			panic(fmt.Errorf("Expected to only have 1 variant when arch type is common but found %d", len(osTypeVariants)))
 		}
@@ -1289,11 +1451,9 @@
 		osInfo.Properties.PopulateFromVariant(ctx, commonVariants[0])
 	} else {
 		// Create an arch specific info for each supported architecture type.
-		for _, archType := range archTypes {
-			archTypeName := archType.Name
-
-			archVariants := variantsByArchName[archTypeName]
-			archInfo := newArchSpecificInfo(ctx, archType, osType, osSpecificVariantPropertiesFactory, archVariants)
+		for _, id := range archIds {
+			archVariants := variantsByArchId[id]
+			archInfo := newArchSpecificInfo(ctx, id, osType, osSpecificVariantPropertiesFactory, archVariants)
 
 			osInfo.archInfos = append(osInfo.archInfos, archInfo)
 		}
@@ -1302,6 +1462,16 @@
 	return osInfo
 }
 
+func (osInfo *osTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(osInfo.archInfos) == 0 {
+		pruner.pruneProperties(osInfo.Properties)
+	} else {
+		for _, archInfo := range osInfo.archInfos {
+			archInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from arch type specific
 // properties into os type specific properties.
 func (osInfo *osTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
@@ -1312,7 +1482,7 @@
 
 	multilib := multilibNone
 	for _, archInfo := range osInfo.archInfos {
-		multilib = multilib.addArchType(archInfo.archType)
+		multilib = multilib.addArchType(archInfo.archId.archType)
 
 		// Optimize the arch properties first.
 		archInfo.optimizeProperties(ctx, commonValueExtractor)
@@ -1405,23 +1575,67 @@
 	return fmt.Sprintf("OsType{%s}", osInfo.osType)
 }
 
+// archId encapsulates the information needed to identify a combination of arch type and native
+// bridge support.
+//
+// Conceptually, native bridge support is a facet of an android.Target, not an android.Arch as it is
+// essentially using one android.Arch to implement another. However, in terms of the handling of
+// the variants native bridge is treated as part of the arch variation. See the ArchVariation method
+// on android.Target.
+//
+// So, it makes sense when optimizing the variants to combine native bridge with the arch type.
+type archId struct {
+	// The arch type of the variant's target.
+	archType android.ArchType
+
+	// True if the variants is for the native bridge, false otherwise.
+	nativeBridge bool
+}
+
+// propertyName returns the name of the property corresponding to use for this arch id.
+func (i *archId) propertyName() string {
+	name := i.archType.Name
+	if i.nativeBridge {
+		// Note: This does not result in a valid property because there is no architecture specific
+		// native bridge property, only a generic "native_bridge" property. However, this will be used
+		// in error messages if there is an attempt to use this in a generated bp file.
+		name += "_native_bridge"
+	}
+	return name
+}
+
+func (i *archId) String() string {
+	return fmt.Sprintf("ArchType{%s}, NativeBridge{%t}", i.archType, i.nativeBridge)
+}
+
+// archIdFromTarget returns an archId initialized from information in the supplied target.
+func archIdFromTarget(target android.Target) archId {
+	return archId{
+		archType:     target.Arch.ArchType,
+		nativeBridge: target.NativeBridge == android.NativeBridgeEnabled,
+	}
+}
+
+// commonArchId is the archId for the common architecture.
+var commonArchId = archId{archType: android.Common}
+
 type archTypeSpecificInfo struct {
 	baseInfo
 
-	archType android.ArchType
-	osType   android.OsType
+	archId archId
+	osType android.OsType
 
-	linkInfos []*linkTypeSpecificInfo
+	imageVariantInfos []*imageVariantSpecificInfo
 }
 
 var _ propertiesContainer = (*archTypeSpecificInfo)(nil)
 
 // Create a new archTypeSpecificInfo for the specified arch type and its properties
 // structures populated with information from the variants.
-func newArchSpecificInfo(ctx android.SdkMemberContext, archType android.ArchType, osType android.OsType, variantPropertiesFactory variantPropertiesFactoryFunc, archVariants []android.Module) *archTypeSpecificInfo {
+func newArchSpecificInfo(ctx android.SdkMemberContext, archId archId, osType android.OsType, variantPropertiesFactory variantPropertiesFactoryFunc, archVariants []android.Module) *archTypeSpecificInfo {
 
 	// Create an arch specific info into which the variant properties can be copied.
-	archInfo := &archTypeSpecificInfo{archType: archType, osType: osType}
+	archInfo := &archTypeSpecificInfo{archId: archId, osType: osType}
 
 	// Create the properties into which the arch type specific properties will be
 	// added.
@@ -1430,27 +1644,23 @@
 	if len(archVariants) == 1 {
 		archInfo.Properties.PopulateFromVariant(ctx, archVariants[0])
 	} else {
-		// There is more than one variant for this arch type which must be differentiated
-		// by link type.
-		for _, linkVariant := range archVariants {
-			linkType := getLinkType(linkVariant)
-			if linkType == "" {
-				panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(archVariants)))
-			} else {
-				linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant)
+		// Group the variants by image type.
+		variantsByImage := make(map[string][]android.Module)
+		for _, variant := range archVariants {
+			image := variant.ImageVariation().Variation
+			variantsByImage[image] = append(variantsByImage[image], variant)
+		}
 
-				archInfo.linkInfos = append(archInfo.linkInfos, linkInfo)
-			}
+		// Create the image variant info in a fixed order.
+		for _, imageVariantName := range android.SortedStringKeys(variantsByImage) {
+			variants := variantsByImage[imageVariantName]
+			archInfo.imageVariantInfos = append(archInfo.imageVariantInfos, newImageVariantSpecificInfo(ctx, imageVariantName, variantPropertiesFactory, variants))
 		}
 	}
 
 	return archInfo
 }
 
-func (archInfo *archTypeSpecificInfo) optimizableProperties() interface{} {
-	return archInfo.Properties
-}
-
 // Get the link type of the variant
 //
 // If the variant is not differentiated by link type then it returns "",
@@ -1471,34 +1681,159 @@
 	return linkType
 }
 
+func (archInfo *archTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(archInfo.imageVariantInfos) == 0 {
+		pruner.pruneProperties(archInfo.Properties)
+	} else {
+		for _, imageVariantInfo := range archInfo.imageVariantInfos {
+			imageVariantInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from link type specific
 // properties into arch type specific properties.
 func (archInfo *archTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
-	if len(archInfo.linkInfos) == 0 {
+	if len(archInfo.imageVariantInfos) == 0 {
 		return
 	}
 
-	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.linkInfos)
+	// Optimize the image variant properties first.
+	for _, imageVariantInfo := range archInfo.imageVariantInfos {
+		imageVariantInfo.optimizeProperties(ctx, commonValueExtractor)
+	}
+
+	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.imageVariantInfos)
 }
 
 // Add the properties for an arch type to a property set.
 func (archInfo *archTypeSpecificInfo) addToPropertySet(ctx *memberContext, archPropertySet android.BpPropertySet, archOsPrefix string) {
-	archTypeName := archInfo.archType.Name
-	archTypePropertySet := archPropertySet.AddPropertySet(archOsPrefix + archTypeName)
+	archPropertySuffix := archInfo.archId.propertyName()
+	propertySetName := archOsPrefix + archPropertySuffix
+	archTypePropertySet := archPropertySet.AddPropertySet(propertySetName)
 	// Enable the <os>_<arch> variant explicitly when we've disabled it by default on host.
 	if ctx.memberType.IsHostOsDependent() && archInfo.osType.Class == android.Host {
 		archTypePropertySet.AddProperty("enabled", true)
 	}
 	addSdkMemberPropertiesToSet(ctx, archInfo.Properties, archTypePropertySet)
 
-	for _, linkInfo := range archInfo.linkInfos {
-		linkPropertySet := archTypePropertySet.AddPropertySet(linkInfo.linkType)
-		addSdkMemberPropertiesToSet(ctx, linkInfo.Properties, linkPropertySet)
+	for _, imageVariantInfo := range archInfo.imageVariantInfos {
+		imageVariantInfo.addToPropertySet(ctx, archTypePropertySet)
+	}
+
+	// If this is for a native bridge architecture then make sure that the property set does not
+	// contain any properties as providing native bridge specific properties is not currently
+	// supported.
+	if archInfo.archId.nativeBridge {
+		propertySetContents := getPropertySetContents(archTypePropertySet)
+		if propertySetContents != "" {
+			ctx.SdkModuleContext().ModuleErrorf("Architecture variant %q of sdk member %q has properties distinct from other variants; this is not yet supported. The properties are:\n%s",
+				propertySetName, ctx.name, propertySetContents)
+		}
 	}
 }
 
+// getPropertySetContents returns the string representation of the contents of a property set, after
+// recursively pruning any empty nested property sets.
+func getPropertySetContents(propertySet android.BpPropertySet) string {
+	set := propertySet.(*bpPropertySet)
+	set.transformContents(pruneEmptySetTransformer{})
+	if len(set.properties) != 0 {
+		contents := &generatedContents{}
+		contents.Indent()
+		outputPropertySet(contents, set)
+		setAsString := contents.content.String()
+		return setAsString
+	}
+	return ""
+}
+
 func (archInfo *archTypeSpecificInfo) String() string {
-	return fmt.Sprintf("ArchType{%s}", archInfo.archType)
+	return archInfo.archId.String()
+}
+
+type imageVariantSpecificInfo struct {
+	baseInfo
+
+	imageVariant string
+
+	linkInfos []*linkTypeSpecificInfo
+}
+
+func newImageVariantSpecificInfo(ctx android.SdkMemberContext, imageVariant string, variantPropertiesFactory variantPropertiesFactoryFunc, imageVariants []android.Module) *imageVariantSpecificInfo {
+
+	// Create an image variant specific info into which the variant properties can be copied.
+	imageInfo := &imageVariantSpecificInfo{imageVariant: imageVariant}
+
+	// Create the properties into which the image variant specific properties will be added.
+	imageInfo.Properties = variantPropertiesFactory()
+
+	if len(imageVariants) == 1 {
+		imageInfo.Properties.PopulateFromVariant(ctx, imageVariants[0])
+	} else {
+		// There is more than one variant for this image variant which must be differentiated by link
+		// type.
+		for _, linkVariant := range imageVariants {
+			linkType := getLinkType(linkVariant)
+			if linkType == "" {
+				panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(imageVariants)))
+			} else {
+				linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant)
+
+				imageInfo.linkInfos = append(imageInfo.linkInfos, linkInfo)
+			}
+		}
+	}
+
+	return imageInfo
+}
+
+func (imageInfo *imageVariantSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(imageInfo.linkInfos) == 0 {
+		pruner.pruneProperties(imageInfo.Properties)
+	} else {
+		for _, linkInfo := range imageInfo.linkInfos {
+			linkInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
+// Optimize the properties by extracting common properties from link type specific
+// properties into arch type specific properties.
+func (imageInfo *imageVariantSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
+	if len(imageInfo.linkInfos) == 0 {
+		return
+	}
+
+	extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, imageInfo.Properties, imageInfo.linkInfos)
+}
+
+// Add the properties for an arch type to a property set.
+func (imageInfo *imageVariantSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) {
+	if imageInfo.imageVariant != android.CoreVariation {
+		propertySet = propertySet.AddPropertySet(imageInfo.imageVariant)
+	}
+
+	addSdkMemberPropertiesToSet(ctx, imageInfo.Properties, propertySet)
+
+	for _, linkInfo := range imageInfo.linkInfos {
+		linkInfo.addToPropertySet(ctx, propertySet)
+	}
+
+	// If this is for a non-core image variant then make sure that the property set does not contain
+	// any properties as providing non-core image variant specific properties for prebuilts is not
+	// currently supported.
+	if imageInfo.imageVariant != android.CoreVariation {
+		propertySetContents := getPropertySetContents(propertySet)
+		if propertySetContents != "" {
+			ctx.SdkModuleContext().ModuleErrorf("Image variant %q of sdk member %q has properties distinct from other variants; this is not yet supported. The properties are:\n%s",
+				imageInfo.imageVariant, ctx.name, propertySetContents)
+		}
+	}
+}
+
+func (imageInfo *imageVariantSpecificInfo) String() string {
+	return imageInfo.imageVariant
 }
 
 type linkTypeSpecificInfo struct {
@@ -1524,6 +1859,15 @@
 	return linkInfo
 }
 
+func (l *linkTypeSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) {
+	linkPropertySet := propertySet.AddPropertySet(l.linkType)
+	addSdkMemberPropertiesToSet(ctx, l.Properties, linkPropertySet)
+}
+
+func (l *linkTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	pruner.pruneProperties(l.Properties)
+}
+
 func (l *linkTypeSpecificInfo) String() string {
 	return fmt.Sprintf("LinkType{%s}", l.linkType)
 }
@@ -1533,6 +1877,9 @@
 	builder          *snapshotBuilder
 	memberType       android.SdkMemberType
 	name             string
+
+	// The set of traits required of this member.
+	requiredTraits android.SdkMemberTraitSet
 }
 
 func (m *memberContext) SdkModuleContext() android.ModuleContext {
@@ -1551,23 +1898,38 @@
 	return m.name
 }
 
+func (m *memberContext) RequiresTrait(trait android.SdkMemberTrait) bool {
+	return m.requiredTraits.Contains(trait)
+}
+
 func (s *sdk) createMemberSnapshot(ctx *memberContext, member *sdkMember, bpModule *bpModule) {
 
 	memberType := member.memberType
 
 	// Do not add the prefer property if the member snapshot module is a source module type.
+	config := ctx.sdkMemberContext.Config()
 	if !memberType.UsesSourceModuleTypeInSnapshot() {
 		// Set the prefer based on the environment variable. This is a temporary work around to allow a
 		// snapshot to be created that sets prefer: true.
 		// TODO(b/174997203): Remove once the ability to select the modules to prefer can be done
 		//  dynamically at build time not at snapshot generation time.
-		prefer := ctx.sdkMemberContext.Config().IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER")
+		prefer := config.IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER")
 
 		// Set prefer. Setting this to false is not strictly required as that is the default but it does
 		// provide a convenient hook to post-process the generated Android.bp file, e.g. in tests to
 		// check the behavior when a prebuilt is preferred. It also makes it explicit what the default
 		// behavior is for the module.
 		bpModule.insertAfter("name", "prefer", prefer)
+
+		configVar := config.Getenv("SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR")
+		if configVar != "" {
+			parts := strings.Split(configVar, ":")
+			cfp := android.ConfigVarProperties{
+				Config_namespace: proptools.StringPtr(parts[0]),
+				Var_name:         proptools.StringPtr(parts[1]),
+			}
+			bpModule.insertAfter("prefer", "use_source_config_var", cfp)
+		}
 	}
 
 	// Group the variants by os type.
@@ -1592,6 +1954,11 @@
 	commonProperties := variantPropertiesFactory()
 	commonProperties.Base().Os = android.CommonOS
 
+	// Create a property pruner that will prune any properties unsupported by the target build
+	// release.
+	targetBuildRelease := ctx.builder.targetBuildRelease
+	unsupportedPropertyPruner := newPropertyPrunerByBuildRelease(commonProperties, targetBuildRelease)
+
 	// Create common value extractor that can be used to optimize the properties.
 	commonValueExtractor := newCommonValueExtractor(commonProperties)
 
@@ -1606,6 +1973,8 @@
 		// independent properties structs.
 		osSpecificPropertiesContainers = append(osSpecificPropertiesContainers, osInfo)
 
+		osInfo.pruneUnsupportedProperties(unsupportedPropertyPruner)
+
 		// Optimize the properties across all the variants for a specific os type.
 		osInfo.optimizeProperties(ctx, commonValueExtractor)
 	}
@@ -1648,7 +2017,7 @@
 	var osTypes []android.OsType
 	for _, osType := range android.OsTypeList() {
 		if s.DeviceSupported() {
-			if osType.Class == android.Device && osType != android.Fuchsia {
+			if osType.Class == android.Device {
 				osTypes = append(osTypes, osType)
 			}
 		}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 42d5680..d1beaba 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -26,6 +26,7 @@
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/cc"
+	"android/soong/snapshot"
 	"android/soong/tradefed"
 )
 
@@ -41,8 +42,6 @@
 	pctx.Import("android/soong/android")
 
 	registerShBuildComponents(android.InitRegistrationContext)
-
-	android.RegisterBp2BuildMutator("sh_binary", ShBinaryBp2Build)
 }
 
 func registerShBuildComponents(ctx android.RegistrationContext) {
@@ -104,6 +103,12 @@
 	Recovery_available *bool
 }
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
@@ -143,6 +148,12 @@
 	// list of device library modules that should be installed alongside the test.
 	// Only available for host sh_test modules.
 	Data_device_libs []string `android:"path,arch_variant"`
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type ShBinary struct {
@@ -186,6 +197,9 @@
 	return proptools.String(s.properties.Sub_dir)
 }
 
+func (s *ShBinary) RelativeInstallPath() string {
+	return s.SubDir()
+}
 func (s *ShBinary) Installable() bool {
 	return s.properties.Installable == nil || proptools.Bool(s.properties.Installable)
 }
@@ -257,18 +271,25 @@
 func (s *ShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	s.generateAndroidBuildActions(ctx)
 	installDir := android.PathForModuleInstall(ctx, "bin", proptools.String(s.properties.Sub_dir))
+	if !s.Installable() {
+		s.SkipInstall()
+	}
 	s.installedFile = ctx.InstallExecutable(installDir, s.outputFilePath.Base(), s.outputFilePath)
+	for _, symlink := range s.Symlinks() {
+		ctx.InstallSymlink(installDir, symlink, s.installedFile)
+	}
 }
 
 func (s *ShBinary) AndroidMkEntries() []android.AndroidMkEntries {
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "EXECUTABLES",
 		OutputFile: android.OptionalPathForPath(s.outputFilePath),
-		Include:    "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
 				entries.SetString("LOCAL_MODULE_RELATIVE_PATH", proptools.String(s.properties.Sub_dir))
+				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !s.Installable())
 			},
 		},
 	}}
@@ -413,11 +434,11 @@
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "NATIVE_TESTS",
 		OutputFile: android.OptionalPathForPath(s.outputFilePath),
-		Include:    "$(BUILD_SYSTEM)/soong_cc_prebuilt.mk",
+		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				s.customAndroidMkEntries(entries)
-				entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
+				entries.SetPath("LOCAL_MODULE_PATH", s.installDir)
 				entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
 				if s.testConfig != nil {
 					entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
@@ -440,6 +461,13 @@
 					dir := strings.TrimSuffix(s.dataModules[relPath].String(), relPath)
 					entries.AddStrings("LOCAL_TEST_DATA", dir+":"+relPath)
 				}
+				if s.testProperties.Data_bins != nil {
+					entries.AddStrings("LOCAL_TEST_DATA_BINS", s.testProperties.Data_bins...)
+				}
+				if Bool(s.testProperties.Test_options.Unit_test) {
+					entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+				}
+				entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(s.testProperties.Per_testcase_directory))
 			},
 		},
 	}}
@@ -483,13 +511,19 @@
 	module := &ShTest{}
 	InitShBinaryModule(&module.ShBinary)
 	module.AddProperties(&module.testProperties)
+	// Default sh_test_host to unit_tests = true
+	if module.testProperties.Test_options.Unit_test == nil {
+		module.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
 }
 
 type bazelShBinaryAttributes struct {
-	Srcs bazel.LabelListAttribute
+	Srcs     bazel.LabelListAttribute
+	Filename *string
+	Sub_dir  *string
 	// Bazel also supports the attributes below, but (so far) these are not required for Bionic
 	// deps
 	// data
@@ -511,42 +545,34 @@
 	// visibility
 }
 
-type bazelShBinary struct {
-	android.BazelTargetModuleBase
-	bazelShBinaryAttributes
-}
-
-func BazelShBinaryFactory() android.Module {
-	module := &bazelShBinary{}
-	module.AddProperties(&module.bazelShBinaryAttributes)
-	android.InitBazelTargetModule(module)
-	return module
-}
-
-func ShBinaryBp2Build(ctx android.TopDownMutatorContext) {
-	m, ok := ctx.Module().(*ShBinary)
-	if !ok || !m.ConvertWithBp2build(ctx) {
-		return
-	}
-
+func (m *ShBinary) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	srcs := bazel.MakeLabelListAttribute(
 		android.BazelLabelForModuleSrc(ctx, []string{*m.properties.Src}))
 
+	var filename *string
+	if m.properties.Filename != nil {
+		filename = m.properties.Filename
+	}
+
+	var subDir *string
+	if m.properties.Sub_dir != nil {
+		subDir = m.properties.Sub_dir
+	}
+
 	attrs := &bazelShBinaryAttributes{
-		Srcs: srcs,
+		Srcs:     srcs,
+		Filename: filename,
+		Sub_dir:  subDir,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
-		Rule_class: "sh_binary",
+		Rule_class:        "sh_binary",
+		Bzl_load_location: "//build/bazel/rules:sh_binary.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(BazelShBinaryFactory, m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
-func (m *bazelShBinary) Name() string {
-	return m.BaseModuleName()
-}
-
-func (m *bazelShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
 var Bool = proptools.Bool
+
+var _ snapshot.RelativeInstallPath = (*ShBinary)(nil)
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 9e7e594..89b8126 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -3,6 +3,8 @@
 import (
 	"os"
 	"path/filepath"
+	"strconv"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -35,13 +37,18 @@
 //
 // deprecated
 func testShBinary(t *testing.T, bp string) (*android.TestContext, android.Config) {
+	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
+
 	result := prepareForShTest.RunTestWithBp(t, bp)
 
 	return result.TestContext, result.Config
 }
 
 func TestShTestSubDir(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -49,17 +56,20 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo_test"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 }
 
 func TestShTest(t *testing.T) {
-	ctx, config := testShBinary(t, `
+	result := android.GroupFixturePreparers(
+		prepareForShTest,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
+	).RunTestWithBp(t, `
 		sh_test {
 			name: "foo",
 			src: "test.sh",
@@ -71,13 +81,13 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
 	expectedPath := "out/target/product/test_device/data/nativetest64/foo"
 	actualPath := entries.EntryMap["LOCAL_MODULE_PATH"][0]
-	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", config, expectedPath, actualPath)
+	android.AssertStringPathRelativeToTopEquals(t, "LOCAL_MODULE_PATH[0]", result.Config, expectedPath, actualPath)
 
 	expectedData := []string{":testdata/data1", ":testdata/sub/data2"}
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
@@ -114,7 +124,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := config.BuildOS.String()
 	arches := []string{"android_arm64_armv8-a", buildOS + "_x86_64"}
 	for _, arch := range arches {
 		variant := ctx.ModuleForTests("foo", arch)
@@ -148,14 +158,20 @@
 				"testdata/data1",
 				"testdata/sub/data2",
 			],
+			test_options: {
+				unit_test: true,
+			},
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := ctx.Config().BuildOS.String()
 	mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest)
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a sh_test_host module.")
 	}
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	actualData, _ := strconv.ParseBool(entries.EntryMap["LOCAL_IS_UNIT_TEST"][0])
+	android.AssertBoolEquals(t, "LOCAL_IS_UNIT_TEST", true, actualData)
 }
 
 func TestShTestHost_dataDeviceModules(t *testing.T) {
@@ -185,7 +201,7 @@
 		}
 	`)
 
-	buildOS := android.BuildOs.String()
+	buildOS := config.BuildOS.String()
 	variant := ctx.ModuleForTests("foo", buildOS+"_x86_64")
 
 	relocated := variant.Output("relocated/lib64/libbar.so")
@@ -202,3 +218,40 @@
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
 }
+
+func TestShTestHost_dataDeviceModulesAutogenTradefedConfig(t *testing.T) {
+	ctx, config := testShBinary(t, `
+		sh_test_host {
+			name: "foo",
+			src: "test.sh",
+			data_device_bins: ["bar"],
+			data_device_libs: ["libbar"],
+		}
+
+		cc_binary {
+			name: "bar",
+			shared_libs: ["libbar"],
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+		}
+
+		cc_library {
+			name: "libbar",
+			no_libcrt: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+		}
+	`)
+
+	buildOS := config.BuildOS.String()
+	fooModule := ctx.ModuleForTests("foo", buildOS+"_x86_64")
+
+	expectedBinAutogenConfig := `<option name="push-file" key="bar" value="/data/local/tests/unrestricted/foo/bar" />`
+	autogen := fooModule.Rule("autogen")
+	if !strings.Contains(autogen.Args["extraConfigs"], expectedBinAutogenConfig) {
+		t.Errorf("foo extraConfings %v does not contain %q", autogen.Args["extraConfigs"], expectedBinAutogenConfig)
+	}
+}
diff --git a/shared/Android.bp b/shared/Android.bp
index deb17f8..3c84f55 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -9,11 +9,13 @@
         "env.go",
         "paths.go",
         "debug.go",
+        "proto.go",
     ],
     testSrcs: [
         "paths_test.go",
     ],
     deps: [
         "soong-bazel",
+        "golang-protobuf-proto",
     ],
 }
diff --git a/shared/proto.go b/shared/proto.go
new file mode 100644
index 0000000..232656b
--- /dev/null
+++ b/shared/proto.go
@@ -0,0 +1,41 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package shared
+
+import (
+	"io/ioutil"
+	"os"
+
+	"google.golang.org/protobuf/proto"
+)
+
+// Save takes a protobuf message, marshals to an array of bytes
+// and is then saved to a file.
+func Save(pb proto.Message, filepath string) (err error) {
+	data, err := proto.Marshal(pb)
+	if err != nil {
+		return err
+	}
+	tempFilepath := filepath + ".tmp"
+	if err := ioutil.WriteFile(tempFilepath, []byte(data), 0644 /* rw-r--r-- */); err != nil {
+		return err
+	}
+
+	if err := os.Rename(tempFilepath, filepath); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/snapshot/Android.bp b/snapshot/Android.bp
new file mode 100644
index 0000000..3354993
--- /dev/null
+++ b/snapshot/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-snapshot",
+    pkgPath: "android/soong/snapshot",
+    deps: [
+        "blueprint",
+        "blueprint-pathtools",
+        "soong",
+        "soong-android",
+    ],
+    // Source file name convention is to include _snapshot as a
+    // file suffix for files that are generating snapshots.
+    srcs: [
+        "host_fake_snapshot.go",
+        "host_snapshot.go",
+        "recovery_snapshot.go",
+        "snapshot.go",
+        "snapshot_base.go",
+        "util.go",
+        "vendor_snapshot.go",
+    ],
+    testSrcs: [
+        "host_test.go",
+        "test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
new file mode 100644
index 0000000..b04657d
--- /dev/null
+++ b/snapshot/host_fake_snapshot.go
@@ -0,0 +1,164 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package snapshot
+
+import (
+	"encoding/json"
+	"path/filepath"
+
+	"android/soong/android"
+)
+
+// The host_snapshot module creates a snapshot of host tools to be used
+// in a minimal source tree.   In order to create the host_snapshot the
+// user must explicitly list the modules to be included.  The
+// host-fake-snapshot, defined in this file, is a utility to help determine
+// which host modules are being used in the minimal source tree.
+//
+// The host-fake-snapshot is designed to run in a full source tree and
+// will result in a snapshot that contains an empty file for each host
+// tool found in the tree.  The fake snapshot is only used to determine
+// the host modules that the minimal source tree depends on, hence the
+// snapshot uses an empty file for each module and saves on having to
+// actually build any tool to generate the snapshot.  The fake snapshot
+// is compatible with an actual host_snapshot and is installed into a
+// minimal source tree via the development/vendor_snapshot/update.py
+// script.
+//
+// After generating the fake snapshot and installing into the minimal
+// source tree, the dependent modules are determined via the
+// development/vendor_snapshot/update.py script (see script for more
+// information).  These modules are then used to define the actual
+// host_snapshot to be used.  This is a similar process to the other
+// snapshots (vendor, recovery,...)
+//
+// Example
+//
+// Full source tree:
+//   1/ Generate fake host snapshot
+//
+// Minimal source tree:
+//   2/ Install the fake host snapshot
+//   3/ List the host modules used from the snapshot
+//   4/ Remove fake host snapshot
+//
+// Full source tree:
+//   4/ Create host_snapshot with modules identified in step 3
+//
+// Minimal source tree:
+//   5/ Install host snapshot
+//   6/ Build
+//
+// The host-fake-snapshot is a singleton module, that will be built
+// if HOST_FAKE_SNAPSHOT_ENABLE=true.
+
+func init() {
+	registerHostSnapshotComponents(android.InitRegistrationContext)
+}
+
+// Add prebuilt information to snapshot data
+type hostSnapshotFakeJsonFlags struct {
+	SnapshotJsonFlags
+	Prebuilt bool `json:",omitempty"`
+}
+
+func registerHostSnapshotComponents(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("host-fake-snapshot", HostToolsFakeAndroidSingleton)
+}
+
+type hostFakeSingleton struct {
+	snapshotDir string
+	zipFile     android.OptionalPath
+}
+
+func (c *hostFakeSingleton) init() {
+	c.snapshotDir = "host-fake-snapshot"
+
+}
+func HostToolsFakeAndroidSingleton() android.Singleton {
+	singleton := &hostFakeSingleton{}
+	singleton.init()
+	return singleton
+}
+
+func (c *hostFakeSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	if !ctx.DeviceConfig().HostFakeSnapshotEnabled() {
+		return
+	}
+	// Find all host binary modules add 'fake' versions to snapshot
+	var outputs android.Paths
+	seen := make(map[string]bool)
+	var jsonData []hostSnapshotFakeJsonFlags
+	prebuilts := make(map[string]bool)
+
+	ctx.VisitAllModules(func(module android.Module) {
+		if module.Target().Os != ctx.Config().BuildOSTarget.Os {
+			return
+		}
+		if module.Target().Arch.ArchType != ctx.Config().BuildOSTarget.Arch.ArchType {
+			return
+		}
+
+		if android.IsModulePrebuilt(module) {
+			// Add non-prebuilt module name to map of prebuilts
+			prebuilts[android.RemoveOptionalPrebuiltPrefix(module.Name())] = true
+			return
+		}
+		if !module.Enabled() || module.IsHideFromMake() {
+			return
+		}
+		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
+		if !apexInfo.IsForPlatform() {
+			return
+		}
+		path := hostToolPath(module)
+		if path.Valid() && path.String() != "" {
+			outFile := filepath.Join(c.snapshotDir, path.String())
+			if !seen[outFile] {
+				seen[outFile] = true
+				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
+				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(module), false})
+			}
+		}
+	})
+	// Update any module prebuilt information
+	for idx, _ := range jsonData {
+		if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
+			// Prebuilt exists for this module
+			jsonData[idx].Prebuilt = true
+		}
+	}
+	marsh, err := json.Marshal(jsonData)
+	if err != nil {
+		ctx.Errorf("host fake snapshot json marshal failure: %#v", err)
+		return
+	}
+	outputs = append(outputs, WriteStringToFileRule(ctx, string(marsh), filepath.Join(c.snapshotDir, "host_snapshot.json")))
+	c.zipFile = zipSnapshot(ctx, c.snapshotDir, c.snapshotDir, outputs)
+
+}
+func (c *hostFakeSingleton) MakeVars(ctx android.MakeVarsContext) {
+	if !c.zipFile.Valid() {
+		return
+	}
+	ctx.Phony(
+		"host-fake-snapshot",
+		c.zipFile.Path())
+
+	ctx.DistForGoal(
+		"host-fake-snapshot",
+		c.zipFile.Path())
+
+}
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
new file mode 100644
index 0000000..9793218
--- /dev/null
+++ b/snapshot/host_snapshot.go
@@ -0,0 +1,234 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package snapshot
+
+import (
+	"encoding/json"
+	"fmt"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+)
+
+//
+// The host_snapshot module creates a snapshot of the modules defined in
+// the deps property.  The modules within the deps property (host tools)
+// are ones that return a valid path via HostToolPath() of the
+// HostToolProvider.  The created snapshot contains the binaries and any
+// transitive PackagingSpecs of the included host tools, along with a JSON
+// meta file.
+//
+// The snapshot is installed into a source tree via
+// development/vendor_snapshot/update.py, the included modules are
+// provided as preferred prebuilts.
+//
+// To determine which tools to include in the host snapshot see
+// host_fake_snapshot.go.
+
+func init() {
+	registerHostBuildComponents(android.InitRegistrationContext)
+}
+
+func registerHostBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("host_snapshot", hostSnapshotFactory)
+}
+
+// Relative installation path
+type RelativeInstallPath interface {
+	RelativeInstallPath() string
+}
+
+type hostSnapshot struct {
+	android.ModuleBase
+	android.PackagingBase
+
+	outputFile android.OutputPath
+	installDir android.InstallPath
+}
+
+type ProcMacro interface {
+	ProcMacro() bool
+	CrateName() string
+}
+
+func hostSnapshotFactory() android.Module {
+	module := &hostSnapshot{}
+	initHostToolsModule(module)
+	return module
+}
+func initHostToolsModule(module *hostSnapshot) {
+	android.InitPackageModule(module)
+	android.InitAndroidMultiTargetsArchModule(module, android.HostSupported, android.MultilibCommon)
+}
+
+var dependencyTag = struct {
+	blueprint.BaseDependencyTag
+	android.InstallAlwaysNeededDependencyTag
+	android.PackagingItemAlwaysDepTag
+}{}
+
+func (f *hostSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
+	f.AddDeps(ctx, dependencyTag)
+}
+func (f *hostSnapshot) installFileName() string {
+	return f.Name() + ".zip"
+}
+
+// Create zipfile with JSON description, notice files... for dependent modules
+func (f *hostSnapshot) CreateMetaData(ctx android.ModuleContext, fileName string) android.OutputPath {
+	var jsonData []SnapshotJsonFlags
+	var metaPaths android.Paths
+
+	metaZipFile := android.PathForModuleOut(ctx, fileName).OutputPath
+
+	// Create JSON file based on the direct dependencies
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		desc := hostJsonDesc(dep)
+		if desc != nil {
+			jsonData = append(jsonData, *desc)
+		}
+		if len(dep.EffectiveLicenseFiles()) > 0 {
+			noticeFile := android.PathForModuleOut(ctx, "NOTICE_FILES", dep.Name()+".txt").OutputPath
+			android.CatFileRule(ctx, dep.EffectiveLicenseFiles(), noticeFile)
+			metaPaths = append(metaPaths, noticeFile)
+		}
+
+	})
+	// Sort notice paths and json data for repeatble build
+	sort.Slice(jsonData, func(i, j int) bool {
+		return (jsonData[i].ModuleName < jsonData[j].ModuleName)
+	})
+	sort.Slice(metaPaths, func(i, j int) bool {
+		return (metaPaths[i].String() < metaPaths[j].String())
+	})
+
+	marsh, err := json.Marshal(jsonData)
+	if err != nil {
+		ctx.ModuleErrorf("host snapshot json marshal failure: %#v", err)
+		return android.OutputPath{}
+	}
+
+	jsonZipFile := android.PathForModuleOut(ctx, "host_snapshot.json").OutputPath
+	metaPaths = append(metaPaths, jsonZipFile)
+	rspFile := android.PathForModuleOut(ctx, "host_snapshot.rsp").OutputPath
+	android.WriteFileRule(ctx, jsonZipFile, string(marsh))
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	builder.Command().
+		BuiltTool("soong_zip").
+		FlagWithArg("-C ", android.PathForModuleOut(ctx).OutputPath.String()).
+		FlagWithOutput("-o ", metaZipFile).
+		FlagWithRspFileInputList("-r ", rspFile, metaPaths)
+	builder.Build("zip_meta", fmt.Sprintf("zipping meta data for %s", ctx.ModuleName()))
+
+	return metaZipFile
+}
+
+// Create the host tool zip file
+func (f *hostSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Create a zip file for the binaries, and a zip of the meta data, then merge zips
+	depsZipFile := android.PathForModuleOut(ctx, f.Name()+"_deps.zip").OutputPath
+	modsZipFile := android.PathForModuleOut(ctx, f.Name()+"_mods.zip").OutputPath
+	f.outputFile = android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+
+	f.installDir = android.PathForModuleInstall(ctx)
+
+	f.CopyDepsToZip(ctx, f.GatherPackagingSpecs(ctx), depsZipFile)
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		BuiltTool("zip2zip").
+		FlagWithInput("-i ", depsZipFile).
+		FlagWithOutput("-o ", modsZipFile).
+		Text("**/*:" + proptools.ShellEscape(f.installDir.String()))
+
+	metaZipFile := f.CreateMetaData(ctx, f.Name()+"_meta.zip")
+
+	builder.Command().
+		BuiltTool("merge_zips").
+		Output(f.outputFile).
+		Input(metaZipFile).
+		Input(modsZipFile)
+
+	builder.Build("manifest", fmt.Sprintf("Adding manifest %s", f.installFileName()))
+	ctx.InstallFile(f.installDir, f.installFileName(), f.outputFile)
+
+}
+
+// Implements android.AndroidMkEntriesProvider
+func (f *hostSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(f.outputFile),
+		DistFiles:  android.MakeDefaultDistFiles(f.outputFile),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+			},
+		},
+	}}
+}
+
+// Get host tools path and relative install string helpers
+func hostToolPath(m android.Module) android.OptionalPath {
+	if provider, ok := m.(android.HostToolProvider); ok {
+		return provider.HostToolPath()
+	}
+	return android.OptionalPath{}
+
+}
+func hostRelativePathString(m android.Module) string {
+	var outString string
+	if rel, ok := m.(RelativeInstallPath); ok {
+		outString = rel.RelativeInstallPath()
+	}
+	return outString
+}
+
+// Create JSON description for given module, only create descriptions for binary modules
+// and rust_proc_macro modules which provide a valid HostToolPath
+func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+	path := hostToolPath(m)
+	relPath := hostRelativePathString(m)
+	procMacro := false
+	moduleStem := filepath.Base(path.String())
+	crateName := ""
+
+	if pm, ok := m.(ProcMacro); ok && pm.ProcMacro() {
+		procMacro = pm.ProcMacro()
+		moduleStem = strings.TrimSuffix(moduleStem, filepath.Ext(moduleStem))
+		crateName = pm.CrateName()
+	}
+
+	if path.Valid() && path.String() != "" {
+		return &SnapshotJsonFlags{
+			ModuleName:          m.Name(),
+			ModuleStemName:      moduleStem,
+			Filename:            path.String(),
+			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
+			RelativeInstallPath: relPath,
+			RustProcMacro:       procMacro,
+			CrateName:           crateName,
+		}
+	}
+	return nil
+}
diff --git a/snapshot/host_test.go b/snapshot/host_test.go
new file mode 100644
index 0000000..ab9fedd
--- /dev/null
+++ b/snapshot/host_test.go
@@ -0,0 +1,170 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package snapshot
+
+import (
+	"path/filepath"
+	"testing"
+
+	"android/soong/android"
+)
+
+// host_snapshot and host-fake-snapshot test functions
+
+type hostTestModule struct {
+	android.ModuleBase
+	props struct {
+		Deps []string
+	}
+}
+
+func hostTestBinOut(bin string) string {
+	return filepath.Join("out", "bin", bin)
+}
+
+func (c *hostTestModule) HostToolPath() android.OptionalPath {
+	return (android.OptionalPathForPath(android.PathForTesting(hostTestBinOut(c.Name()))))
+}
+
+func hostTestModuleFactory() android.Module {
+	m := &hostTestModule{}
+	m.AddProperties(&m.props)
+	android.InitAndroidArchModule(m, android.HostSupported, android.MultilibFirst)
+	return m
+}
+func (m *hostTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	builtFile := android.PathForModuleOut(ctx, m.Name())
+	dir := ctx.Target().Arch.ArchType.Multilib
+	installDir := android.PathForModuleInstall(ctx, dir)
+	ctx.InstallFile(installDir, m.Name(), builtFile)
+}
+
+// Common blueprint used for testing
+var hostTestBp = `
+		license_kind {
+			name: "test_notice",
+			conditions: ["notice"],
+		}
+		license {
+			name: "host_test_license",
+			visibility: ["//visibility:public"],
+			license_kinds: [
+				"test_notice"
+			],
+			license_text: [
+				"NOTICE",
+			],
+		}
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+		component {
+			name: "bar",
+			licenses: ["host_test_license"],
+		}
+		`
+
+var hostTestModBp = `
+		host_snapshot {
+			name: "test-host-snapshot",
+			deps: [
+				"foo",
+			],
+		}
+		`
+
+var prepareForHostTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.PrepareForTestWithLicenses,
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("component", hostTestModuleFactory)
+	}),
+)
+
+// Prepare for host_snapshot test
+var prepareForHostModTest = android.GroupFixturePreparers(
+	prepareForHostTest,
+	android.FixtureWithRootAndroidBp(hostTestBp+hostTestModBp),
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		registerHostBuildComponents(ctx)
+	}),
+)
+
+// Prepare for fake host snapshot test disabled
+var prepareForFakeHostTest = android.GroupFixturePreparers(
+	prepareForHostTest,
+	android.FixtureWithRootAndroidBp(hostTestBp),
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		registerHostSnapshotComponents(ctx)
+	}),
+)
+
+// Prepare for fake host snapshot test enabled
+var prepareForFakeHostTestEnabled = android.GroupFixturePreparers(
+	prepareForFakeHostTest,
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.HostFakeSnapshotEnabled = true
+	}),
+)
+
+// Validate that a hostSnapshot object is created containing zip files and JSON file
+// content of zip file is not validated as this is done by PackagingSpecs
+func TestHostSnapshot(t *testing.T) {
+	result := prepareForHostModTest.RunTest(t)
+	t.Helper()
+	ctx := result.TestContext.ModuleForTests("test-host-snapshot", result.Config.BuildOS.String()+"_common")
+	mod := ctx.Module().(*hostSnapshot)
+	if ctx.MaybeOutput("host_snapshot.json").Rule == nil {
+		t.Error("Manifest file not found")
+	}
+	zips := []string{"_deps.zip", "_mods.zip", ".zip"}
+
+	for _, zip := range zips {
+		zFile := mod.Name() + zip
+		if ctx.MaybeOutput(zFile).Rule == nil {
+			t.Error("Zip file ", zFile, "not found")
+		}
+
+	}
+}
+
+// Validate fake host snapshot contains binary modules as well as the JSON meta file
+func TestFakeHostSnapshotEnable(t *testing.T) {
+	result := prepareForFakeHostTestEnabled.RunTest(t)
+	t.Helper()
+	bins := []string{"foo", "bar"}
+	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
+	if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", "host_snapshot.json")).Rule == nil {
+		t.Error("Manifest file not found")
+	}
+	for _, bin := range bins {
+		if ctx.MaybeOutput(filepath.Join("host-fake-snapshot", hostTestBinOut(bin))).Rule == nil {
+			t.Error("Binary file ", bin, "not found")
+		}
+
+	}
+}
+
+// Validate not fake host snapshot if HostFakeSnapshotEnabled has not been set to true
+func TestFakeHostSnapshotDisable(t *testing.T) {
+	result := prepareForFakeHostTest.RunTest(t)
+	t.Helper()
+	ctx := result.TestContext.SingletonForTests("host-fake-snapshot")
+	if len(ctx.AllOutputs()) != 0 {
+		t.Error("Fake host snapshot not empty when disabled")
+	}
+
+}
diff --git a/snapshot/recovery_snapshot.go b/snapshot/recovery_snapshot.go
new file mode 100644
index 0000000..f1e31ca
--- /dev/null
+++ b/snapshot/recovery_snapshot.go
@@ -0,0 +1,134 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package snapshot
+
+import "android/soong/android"
+
+// Interface for modules which can be captured in the recovery snapshot.
+type RecoverySnapshotModuleInterface interface {
+	SnapshotModuleInterfaceBase
+	InRecovery() bool
+	ExcludeFromRecoverySnapshot() bool
+}
+
+var recoverySnapshotSingleton = SnapshotSingleton{
+	"recovery",                     // name
+	"SOONG_RECOVERY_SNAPSHOT_ZIP",  // makeVar
+	android.OptionalPath{},         // snapshotZipFile
+	RecoverySnapshotImageSingleton, // Image
+	false,                          // Fake
+}
+
+func RecoverySnapshotSingleton() android.Singleton {
+	return &recoverySnapshotSingleton
+}
+
+// Determine if a dir under source tree is an SoC-owned proprietary directory based
+// on recovery snapshot configuration
+// Examples: device/, vendor/
+func isRecoveryProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return RecoverySnapshotSingleton().(*SnapshotSingleton).Image.IsProprietaryPath(dir, deviceConfig)
+}
+
+func IsRecoveryProprietaryModule(ctx android.BaseModuleContext) bool {
+
+	// Any module in a recovery proprietary path is a recovery proprietary
+	// module.
+	if isRecoveryProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
+		return true
+	}
+
+	// However if the module is not in a recovery proprietary path, it may
+	// still be a recovery proprietary module. This happens for cc modules
+	// that are excluded from the recovery snapshot, and it means that the
+	// vendor has assumed control of the framework-provided module.
+
+	if c, ok := ctx.Module().(RecoverySnapshotModuleInterface); ok {
+		if c.ExcludeFromRecoverySnapshot() {
+			return true
+		}
+	}
+
+	return false
+}
+
+var RecoverySnapshotImageName = "recovery"
+
+type RecoverySnapshotImage struct{}
+
+func (RecoverySnapshotImage) Init(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("recovery-snapshot", RecoverySnapshotSingleton)
+}
+
+func (RecoverySnapshotImage) RegisterAdditionalModule(ctx android.RegistrationContext, name string, factory android.ModuleFactory) {
+	ctx.RegisterModuleType(name, factory)
+}
+
+func (RecoverySnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
+	// RECOVERY_SNAPSHOT_VERSION must be set to 'current' in order to generate a
+	// snapshot.
+	return ctx.DeviceConfig().RecoverySnapshotVersion() == "current"
+}
+
+func (RecoverySnapshotImage) InImage(m SnapshotModuleInterfaceBase) func() bool {
+	r, ok := m.(RecoverySnapshotModuleInterface)
+
+	if !ok {
+		// This module does not support recovery snapshot
+		return func() bool { return false }
+	}
+	return r.InRecovery
+}
+
+func (RecoverySnapshotImage) IsProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return isDirectoryExcluded(dir, deviceConfig.RecoverySnapshotDirsExcludedMap(), deviceConfig.RecoverySnapshotDirsIncludedMap())
+}
+
+func (RecoverySnapshotImage) ExcludeFromSnapshot(m SnapshotModuleInterfaceBase) bool {
+	r, ok := m.(RecoverySnapshotModuleInterface)
+
+	if !ok {
+		// This module does not support recovery snapshot
+		return true
+	}
+	return r.ExcludeFromRecoverySnapshot()
+}
+
+func (RecoverySnapshotImage) IsUsingSnapshot(cfg android.DeviceConfig) bool {
+	recoverySnapshotVersion := cfg.RecoverySnapshotVersion()
+	return recoverySnapshotVersion != "current" && recoverySnapshotVersion != ""
+}
+
+func (RecoverySnapshotImage) TargetSnapshotVersion(cfg android.DeviceConfig) string {
+	return cfg.RecoverySnapshotVersion()
+}
+
+func (RecoverySnapshotImage) ExcludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool {
+	// If we're using full snapshot, not directed snapshot, capture every module
+	if !cfg.DirectedRecoverySnapshot() {
+		return false
+	}
+	// Else, checks if name is in RECOVERY_SNAPSHOT_MODULES.
+	return !cfg.RecoverySnapshotModules()[name]
+}
+
+func (RecoverySnapshotImage) ImageName() string {
+	return RecoverySnapshotImageName
+}
+
+var RecoverySnapshotImageSingleton RecoverySnapshotImage
+
+func init() {
+	RecoverySnapshotImageSingleton.Init(android.InitRegistrationContext)
+}
diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go
new file mode 100644
index 0000000..294f8b6
--- /dev/null
+++ b/snapshot/snapshot.go
@@ -0,0 +1,122 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package snapshot
+
+import (
+	"path/filepath"
+	"sort"
+
+	"android/soong/android"
+)
+
+// This file contains singletons to capture snapshots. This singleton will generate snapshot of each target
+// image, and capturing snapshot module will be delegated to each module which implements GenerateSnapshotAction
+// function and register with RegisterSnapshotAction.
+
+var pctx = android.NewPackageContext("android/soong/snapshot")
+
+type SnapshotSingleton struct {
+	// Name, e.g., "vendor", "recovery", "ramdisk".
+	name string
+
+	// Make variable that points to the snapshot file, e.g.,
+	// "SOONG_RECOVERY_SNAPSHOT_ZIP".
+	makeVar string
+
+	// Path to the snapshot zip file.
+	snapshotZipFile android.OptionalPath
+
+	// Implementation of the image interface specific to the image
+	// associated with this snapshot (e.g., specific to the vendor image,
+	// recovery image, etc.).
+	Image SnapshotImage
+
+	// Whether this singleton is for fake snapshot or not.
+	// Fake snapshot is a snapshot whose prebuilt binaries and headers are empty.
+	// It is much faster to generate, and can be used to inspect dependencies.
+	Fake bool
+}
+
+// Interface of function to capture snapshot from each module
+type GenerateSnapshotAction func(snapshot SnapshotSingleton, ctx android.SingletonContext, snapshotArchDir string) android.Paths
+
+var snapshotActionList []GenerateSnapshotAction
+
+// Register GenerateSnapshotAction function so it can be called while generating snapshot
+func RegisterSnapshotAction(x GenerateSnapshotAction) {
+	snapshotActionList = append(snapshotActionList, x)
+}
+
+func (c *SnapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	if !c.Image.shouldGenerateSnapshot(ctx) {
+		return
+	}
+
+	var snapshotOutputs android.Paths
+
+	// Snapshot zipped artifacts will be captured under {SNAPSHOT_ARCH} directory
+
+	snapshotDir := c.name + "-snapshot"
+	if c.Fake {
+		// If this is a fake snapshot singleton, place all files under fake/ subdirectory to avoid
+		// collision with real snapshot files
+		snapshotDir = filepath.Join("fake", snapshotDir)
+	}
+	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
+
+	for _, f := range snapshotActionList {
+		snapshotOutputs = append(snapshotOutputs, f(*c, ctx, snapshotArchDir)...)
+	}
+
+	// All artifacts are ready. Sort them to normalize ninja and then zip.
+	sort.Slice(snapshotOutputs, func(i, j int) bool {
+		return snapshotOutputs[i].String() < snapshotOutputs[j].String()
+	})
+
+	zipPath := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+".zip")
+	zipRule := android.NewRuleBuilder(pctx, ctx)
+
+	// filenames in rspfile from FlagWithRspFileInputList might be single-quoted. Remove it with tr
+	snapshotOutputList := android.PathForOutput(
+		ctx,
+		snapshotDir,
+		c.name+"-"+ctx.Config().DeviceName()+"_list")
+	rspFile := snapshotOutputList.ReplaceExtension(ctx, "rsp")
+	zipRule.Command().
+		Text("tr").
+		FlagWithArg("-d ", "\\'").
+		FlagWithRspFileInputList("< ", rspFile, snapshotOutputs).
+		FlagWithOutput("> ", snapshotOutputList)
+
+	zipRule.Temporary(snapshotOutputList)
+
+	zipRule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", zipPath).
+		FlagWithArg("-C ", android.PathForOutput(ctx, snapshotDir).String()).
+		FlagWithInput("-l ", snapshotOutputList)
+
+	zipRule.Build(zipPath.String(), c.name+" snapshot "+zipPath.String())
+	zipRule.DeleteTemporaryFiles()
+	c.snapshotZipFile = android.OptionalPathForPath(zipPath)
+}
+
+func (c *SnapshotSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict(
+		c.makeVar,
+		c.snapshotZipFile.String())
+}
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
new file mode 100644
index 0000000..4a14f2e
--- /dev/null
+++ b/snapshot/snapshot_base.go
@@ -0,0 +1,122 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package snapshot
+
+import (
+	"android/soong/android"
+	"path/filepath"
+)
+
+// Interface for modules which can be captured in the snapshot.
+type SnapshotModuleInterfaceBase interface{}
+
+// Defines the specifics of different images to which the snapshot process is applicable, e.g.,
+// vendor, recovery, ramdisk.
+type SnapshotImage interface {
+	// Returns true if a snapshot should be generated for this image.
+	shouldGenerateSnapshot(ctx android.SingletonContext) bool
+
+	// Function that returns true if the module is included in this image.
+	// Using a function return instead of a value to prevent early
+	// evalution of a function that may be not be defined.
+	InImage(m SnapshotModuleInterfaceBase) func() bool
+
+	// Returns true if a dir under source tree is an SoC-owned proprietary
+	// directory, such as device/, vendor/, etc.
+	//
+	// For a given snapshot (e.g., vendor, recovery, etc.) if
+	// isProprietaryPath(dir, deviceConfig) returns true, then the module in dir
+	// will be built from sources.
+	IsProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool
+
+	// Whether a given module has been explicitly excluded from the
+	// snapshot, e.g., using the exclude_from_vendor_snapshot or
+	// exclude_from_recovery_snapshot properties.
+	ExcludeFromSnapshot(m SnapshotModuleInterfaceBase) bool
+
+	// Returns true if the build is using a snapshot for this image.
+	IsUsingSnapshot(cfg android.DeviceConfig) bool
+
+	// Returns a version of which the snapshot should be used in this target.
+	// This will only be meaningful when isUsingSnapshot is true.
+	TargetSnapshotVersion(cfg android.DeviceConfig) string
+
+	// Whether to exclude a given module from the directed snapshot or not.
+	// If the makefile variable DIRECTED_{IMAGE}_SNAPSHOT is true, directed snapshot is turned on,
+	// and only modules listed in {IMAGE}_SNAPSHOT_MODULES will be captured.
+	ExcludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool
+
+	// Returns target image name
+	ImageName() string
+}
+
+type directoryMap map[string]bool
+
+var (
+	// Modules under following directories are ignored. They are OEM's and vendor's
+	// proprietary modules(device/, kernel/, vendor/, and hardware/).
+	defaultDirectoryExcludedMap = directoryMap{
+		"device":   true,
+		"hardware": true,
+		"kernel":   true,
+		"vendor":   true,
+	}
+
+	// Modules under following directories are included as they are in AOSP,
+	// although hardware/ and kernel/ are normally for vendor's own.
+	defaultDirectoryIncludedMap = directoryMap{
+		"kernel/configs":              true,
+		"kernel/prebuilts":            true,
+		"kernel/tests":                true,
+		"hardware/interfaces":         true,
+		"hardware/libhardware":        true,
+		"hardware/libhardware_legacy": true,
+		"hardware/ril":                true,
+	}
+)
+
+func isDirectoryExcluded(dir string, excludedMap directoryMap, includedMap directoryMap) bool {
+	if dir == "." || dir == "/" {
+		return false
+	}
+	if includedMap[dir] {
+		return false
+	} else if excludedMap[dir] {
+		return true
+	} else if defaultDirectoryIncludedMap[dir] {
+		return false
+	} else if defaultDirectoryExcludedMap[dir] {
+		return true
+	} else {
+		return isDirectoryExcluded(filepath.Dir(dir), excludedMap, includedMap)
+	}
+}
+
+// This is to be saved as .json files, which is for development/vendor_snapshot/update.py.
+// These flags become Android.bp snapshot module properties.
+//
+// Attributes are optional and will be populated based on each module's need.
+// Common attributes are defined here, languages may extend this struct to add
+// additional attributes.
+type SnapshotJsonFlags struct {
+	ModuleName          string `json:",omitempty"`
+	RelativeInstallPath string `json:",omitempty"`
+	Filename            string `json:",omitempty"`
+	ModuleStemName      string `json:",omitempty"`
+	RustProcMacro       bool   `json:",omitempty"`
+	CrateName           string `json:",omitempty"`
+
+	// dependencies
+	Required []string `json:",omitempty"`
+}
diff --git a/snapshot/test.go b/snapshot/test.go
new file mode 100644
index 0000000..346af2b
--- /dev/null
+++ b/snapshot/test.go
@@ -0,0 +1,24 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package snapshot
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMain(m *testing.M) {
+	os.Exit(m.Run())
+}
diff --git a/snapshot/util.go b/snapshot/util.go
new file mode 100644
index 0000000..f447052
--- /dev/null
+++ b/snapshot/util.go
@@ -0,0 +1,55 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package snapshot
+
+import "android/soong/android"
+
+func WriteStringToFileRule(ctx android.SingletonContext, content, out string) android.OutputPath {
+	outPath := android.PathForOutput(ctx, out)
+	android.WriteFileRule(ctx, outPath, content)
+	return outPath
+}
+
+func CopyFileRule(pctx android.PackageContext, ctx android.SingletonContext, path android.Path, out string) android.OutputPath {
+	outPath := android.PathForOutput(ctx, out)
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Cp,
+		Input:       path,
+		Output:      outPath,
+		Description: "copy " + path.String() + " -> " + out,
+		Args: map[string]string{
+			"cpFlags": "-f -L",
+		},
+	})
+	return outPath
+}
+
+// zip snapshot
+func zipSnapshot(ctx android.SingletonContext, dir string, baseName string, snapshotOutputs android.Paths) android.OptionalPath {
+	zipPath := android.PathForOutput(
+		ctx, dir, baseName+".zip")
+
+	zipRule := android.NewRuleBuilder(pctx, ctx)
+	rspFile := android.PathForOutput(
+		ctx, dir, baseName+"_list.rsp")
+
+	zipRule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", zipPath).
+		FlagWithArg("-C ", android.PathForOutput(ctx, dir).String()).
+		FlagWithRspFileInputList("-r ", rspFile, snapshotOutputs)
+
+	zipRule.Build(zipPath.String(), baseName+" snapshot "+zipPath.String())
+	return android.OptionalPathForPath(zipPath)
+}
diff --git a/snapshot/vendor_snapshot.go b/snapshot/vendor_snapshot.go
new file mode 100644
index 0000000..9bd26c2
--- /dev/null
+++ b/snapshot/vendor_snapshot.go
@@ -0,0 +1,147 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package snapshot
+
+import "android/soong/android"
+
+// Interface for modules which can be captured in the vendor snapshot.
+type VendorSnapshotModuleInterface interface {
+	SnapshotModuleInterfaceBase
+	InVendor() bool
+	ExcludeFromVendorSnapshot() bool
+}
+
+var vendorSnapshotSingleton = SnapshotSingleton{
+	"vendor",                     // name
+	"SOONG_VENDOR_SNAPSHOT_ZIP",  // makeVar
+	android.OptionalPath{},       // snapshotZipFile
+	VendorSnapshotImageSingleton, // Image
+	false,                        // Fake
+}
+
+var vendorFakeSnapshotSingleton = SnapshotSingleton{
+	"vendor",                         // name
+	"SOONG_VENDOR_FAKE_SNAPSHOT_ZIP", // makeVar
+	android.OptionalPath{},           // snapshotZipFile
+	VendorSnapshotImageSingleton,     // Image
+	true,                             // Fake
+}
+
+func VendorSnapshotSingleton() android.Singleton {
+	return &vendorSnapshotSingleton
+}
+
+func VendorFakeSnapshotSingleton() android.Singleton {
+	return &vendorFakeSnapshotSingleton
+}
+
+// Determine if a dir under source tree is an SoC-owned proprietary directory based
+// on vendor snapshot configuration
+// Examples: device/, vendor/
+func isVendorProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return VendorSnapshotSingleton().(*SnapshotSingleton).Image.IsProprietaryPath(dir, deviceConfig)
+}
+
+func IsVendorProprietaryModule(ctx android.BaseModuleContext) bool {
+	// Any module in a vendor proprietary path is a vendor proprietary
+	// module.
+	if isVendorProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
+		return true
+	}
+
+	// However if the module is not in a vendor proprietary path, it may
+	// still be a vendor proprietary module. This happens for cc modules
+	// that are excluded from the vendor snapshot, and it means that the
+	// vendor has assumed control of the framework-provided module.
+	if c, ok := ctx.Module().(VendorSnapshotModuleInterface); ok {
+		if c.ExcludeFromVendorSnapshot() {
+			return true
+		}
+	}
+
+	return false
+}
+
+var VendorSnapshotImageName = "vendor"
+
+type VendorSnapshotImage struct{}
+
+func (VendorSnapshotImage) Init(ctx android.RegistrationContext) {
+	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
+	ctx.RegisterSingletonType("vendor-fake-snapshot", VendorFakeSnapshotSingleton)
+}
+
+func (VendorSnapshotImage) RegisterAdditionalModule(ctx android.RegistrationContext, name string, factory android.ModuleFactory) {
+	ctx.RegisterModuleType(name, factory)
+}
+
+func (VendorSnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
+	// BOARD_VNDK_VERSION must be set to 'current' in order to generate a snapshot.
+	return ctx.DeviceConfig().VndkVersion() == "current"
+}
+
+func (VendorSnapshotImage) InImage(m SnapshotModuleInterfaceBase) func() bool {
+	v, ok := m.(VendorSnapshotModuleInterface)
+
+	if !ok {
+		// This module does not support Vendor snapshot
+		return func() bool { return false }
+	}
+
+	return v.InVendor
+}
+
+func (VendorSnapshotImage) IsProprietaryPath(dir string, deviceConfig android.DeviceConfig) bool {
+	return isDirectoryExcluded(dir, deviceConfig.VendorSnapshotDirsExcludedMap(), deviceConfig.VendorSnapshotDirsIncludedMap())
+}
+
+func (VendorSnapshotImage) ExcludeFromSnapshot(m SnapshotModuleInterfaceBase) bool {
+	v, ok := m.(VendorSnapshotModuleInterface)
+
+	if !ok {
+		// This module does not support Vendor snapshot
+		return true
+	}
+
+	return v.ExcludeFromVendorSnapshot()
+}
+
+func (VendorSnapshotImage) IsUsingSnapshot(cfg android.DeviceConfig) bool {
+	vndkVersion := cfg.VndkVersion()
+	return vndkVersion != "current" && vndkVersion != ""
+}
+
+func (VendorSnapshotImage) TargetSnapshotVersion(cfg android.DeviceConfig) string {
+	return cfg.VndkVersion()
+}
+
+// returns true iff a given module SHOULD BE EXCLUDED, false if included
+func (VendorSnapshotImage) ExcludeFromDirectedSnapshot(cfg android.DeviceConfig, name string) bool {
+	// If we're using full snapshot, not directed snapshot, capture every module
+	if !cfg.DirectedVendorSnapshot() {
+		return false
+	}
+	// Else, checks if name is in VENDOR_SNAPSHOT_MODULES.
+	return !cfg.VendorSnapshotModules()[name]
+}
+
+func (VendorSnapshotImage) ImageName() string {
+	return VendorSnapshotImageName
+}
+
+var VendorSnapshotImageSingleton VendorSnapshotImage
+
+func init() {
+	VendorSnapshotImageSingleton.Init(android.InitRegistrationContext)
+}
diff --git a/soong.bash b/soong.bash
index 8cf2ec6..db7af7c 100755
--- a/soong.bash
+++ b/soong.bash
@@ -15,8 +15,8 @@
 # limitations under the License.
 
 echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
-echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Use `m --soong-only` with a standalone OUT_DIR instead.' >&2
 echo 'Without envsetup.sh, use:' >&2
-echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '  build/soong/soong_ui.bash --make-mode --soong-only' >&2
 echo '======================================================' >&2
 exit 1
diff --git a/soong_ui.bash b/soong_ui.bash
index c1c236b..49c4b78 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -53,6 +53,8 @@
 source ${TOP}/build/soong/scripts/microfactory.bash
 
 soong_build_go soong_ui android/soong/cmd/soong_ui
+soong_build_go mk2rbc android/soong/mk2rbc/cmd
+soong_build_go rbcrun rbcrun/cmd
 
 cd ${TOP}
 exec "$(getoutdir)/soong_ui" "$@"
diff --git a/cmd/host_bionic_inject/Android.bp b/starlark_fmt/Android.bp
similarity index 72%
copy from cmd/host_bionic_inject/Android.bp
copy to starlark_fmt/Android.bp
index 16bc179..8d80ccd 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/starlark_fmt/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -16,9 +16,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+bootstrap_go_package {
+    name: "soong-starlark-format",
+    pkgPath: "android/soong/starlark_fmt",
+    srcs: [
+        "format.go",
+    ],
+    testSrcs: [
+        "format_test.go",
+    ],
 }
diff --git a/starlark_fmt/format.go b/starlark_fmt/format.go
new file mode 100644
index 0000000..3e51fa1
--- /dev/null
+++ b/starlark_fmt/format.go
@@ -0,0 +1,101 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package starlark_fmt
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+const (
+	indent = 4
+)
+
+// Indention returns an indent string of the specified level.
+func Indention(level int) string {
+	if level < 0 {
+		panic(fmt.Errorf("indent level cannot be less than 0, but got %d", level))
+	}
+	return strings.Repeat(" ", level*indent)
+}
+
+// PrintBool returns a Starlark compatible bool string.
+func PrintBool(item bool) string {
+	return strings.Title(fmt.Sprintf("%t", item))
+}
+
+// PrintsStringList returns a Starlark-compatible string of a list of Strings/Labels.
+func PrintStringList(items []string, indentLevel int) string {
+	return PrintList(items, indentLevel, func(s string) string {
+		if strings.Contains(s, "\"") {
+			return `'''%s'''`
+		}
+		return `"%s"`
+	})
+}
+
+// PrintList returns a Starlark-compatible string of list formmated as requested.
+func PrintList(items []string, indentLevel int, formatString func(string) string) string {
+	if len(items) == 0 {
+		return "[]"
+	} else if len(items) == 1 {
+		return fmt.Sprintf("["+formatString(items[0])+"]", items[0])
+	}
+	list := make([]string, 0, len(items)+2)
+	list = append(list, "[")
+	innerIndent := Indention(indentLevel + 1)
+	for _, item := range items {
+		list = append(list, fmt.Sprintf(`%s`+formatString(item)+`,`, innerIndent, item))
+	}
+	list = append(list, Indention(indentLevel)+"]")
+	return strings.Join(list, "\n")
+}
+
+// PrintStringListDict returns a Starlark-compatible string formatted as dictionary with
+// string keys and list of string values.
+func PrintStringListDict(dict map[string][]string, indentLevel int) string {
+	formattedValueDict := make(map[string]string, len(dict))
+	for k, v := range dict {
+		formattedValueDict[k] = PrintStringList(v, indentLevel+1)
+	}
+	return PrintDict(formattedValueDict, indentLevel)
+}
+
+// PrintBoolDict returns a starlark-compatible string containing a dictionary with string keys and
+// values printed with no additional formatting.
+func PrintBoolDict(dict map[string]bool, indentLevel int) string {
+	formattedValueDict := make(map[string]string, len(dict))
+	for k, v := range dict {
+		formattedValueDict[k] = PrintBool(v)
+	}
+	return PrintDict(formattedValueDict, indentLevel)
+}
+
+// PrintDict returns a starlark-compatible string containing a dictionary with string keys and
+// values printed with no additional formatting.
+func PrintDict(dict map[string]string, indentLevel int) string {
+	if len(dict) == 0 {
+		return "{}"
+	}
+	items := make([]string, 0, len(dict))
+	for k, v := range dict {
+		items = append(items, fmt.Sprintf(`%s"%s": %s,`, Indention(indentLevel+1), k, v))
+	}
+	sort.Strings(items)
+	return fmt.Sprintf(`{
+%s
+%s}`, strings.Join(items, "\n"), Indention(indentLevel))
+}
diff --git a/starlark_fmt/format_test.go b/starlark_fmt/format_test.go
new file mode 100644
index 0000000..9450a31
--- /dev/null
+++ b/starlark_fmt/format_test.go
@@ -0,0 +1,173 @@
+// 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 starlark_fmt
+
+import (
+	"testing"
+)
+
+func simpleFormat(s string) string {
+	return "%s"
+}
+
+func TestPrintEmptyStringList(t *testing.T) {
+	in := []string{}
+	indentLevel := 0
+	out := PrintStringList(in, indentLevel)
+	expectedOut := "[]"
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintSingleElementStringList(t *testing.T) {
+	in := []string{"a"}
+	indentLevel := 0
+	out := PrintStringList(in, indentLevel)
+	expectedOut := `["a"]`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintMultiElementStringList(t *testing.T) {
+	in := []string{"a", "b"}
+	indentLevel := 0
+	out := PrintStringList(in, indentLevel)
+	expectedOut := `[
+    "a",
+    "b",
+]`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintEmptyList(t *testing.T) {
+	in := []string{}
+	indentLevel := 0
+	out := PrintList(in, indentLevel, simpleFormat)
+	expectedOut := "[]"
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintSingleElementList(t *testing.T) {
+	in := []string{"1"}
+	indentLevel := 0
+	out := PrintList(in, indentLevel, simpleFormat)
+	expectedOut := `[1]`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintMultiElementList(t *testing.T) {
+	in := []string{"1", "2"}
+	indentLevel := 0
+	out := PrintList(in, indentLevel, simpleFormat)
+	expectedOut := `[
+    1,
+    2,
+]`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestListWithNonZeroIndent(t *testing.T) {
+	in := []string{"1", "2"}
+	indentLevel := 1
+	out := PrintList(in, indentLevel, simpleFormat)
+	expectedOut := `[
+        1,
+        2,
+    ]`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestStringListDictEmpty(t *testing.T) {
+	in := map[string][]string{}
+	indentLevel := 0
+	out := PrintStringListDict(in, indentLevel)
+	expectedOut := `{}`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestStringListDict(t *testing.T) {
+	in := map[string][]string{
+		"key1": []string{},
+		"key2": []string{"a"},
+		"key3": []string{"1", "2"},
+	}
+	indentLevel := 0
+	out := PrintStringListDict(in, indentLevel)
+	expectedOut := `{
+    "key1": [],
+    "key2": ["a"],
+    "key3": [
+        "1",
+        "2",
+    ],
+}`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintDict(t *testing.T) {
+	in := map[string]string{
+		"key1": `""`,
+		"key2": `"a"`,
+		"key3": `[
+        1,
+        2,
+    ]`,
+	}
+	indentLevel := 0
+	out := PrintDict(in, indentLevel)
+	expectedOut := `{
+    "key1": "",
+    "key2": "a",
+    "key3": [
+        1,
+        2,
+    ],
+}`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
+
+func TestPrintDictWithIndent(t *testing.T) {
+	in := map[string]string{
+		"key1": `""`,
+		"key2": `"a"`,
+	}
+	indentLevel := 1
+	out := PrintDict(in, indentLevel)
+	expectedOut := `{
+        "key1": "",
+        "key2": "a",
+    }`
+	if out != expectedOut {
+		t.Errorf("Expected %q, got %q", expectedOut, out)
+	}
+}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index f1c2d0d..a29d4c3 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -145,6 +145,9 @@
 	// If set to true, allow this module to be dexed and installed on devices.
 	Installable *bool
 
+	// Make this module available when building for ramdisk
+	Ramdisk_available *bool
+
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
@@ -396,6 +399,7 @@
 	Recovery_available *bool
 	Vendor_available   *bool
 	Product_available  *bool
+	Ramdisk_available  *bool
 	Host_supported     *bool
 	Apex_available     []string
 	Min_sdk_version    *string
@@ -475,6 +479,7 @@
 	ccProps.Recovery_available = m.properties.Recovery_available
 	ccProps.Vendor_available = m.properties.Vendor_available
 	ccProps.Product_available = m.properties.Product_available
+	ccProps.Ramdisk_available = m.properties.Ramdisk_available
 	ccProps.Host_supported = m.properties.Host_supported
 	ccProps.Apex_available = m.ApexProperties.Apex_available
 	ccProps.Min_sdk_version = m.properties.Cpp.Min_sdk_version
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index f935f06..88ef615 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -98,9 +98,6 @@
 
 		"build/soong/scripts/jar-wrapper.sh": nil,
 
-		"build/make/core/proguard.flags":             nil,
-		"build/make/core/proguard_basic_keeps.flags": nil,
-
 		"jdk8/jre/lib/jce.jar": nil,
 		"jdk8/jre/lib/rt.jar":  nil,
 		"jdk8/lib/tools.jar":   nil,
diff --git a/tests/androidmk_test.sh b/tests/androidmk_test.sh
new file mode 100755
index 0000000..331dc77
--- /dev/null
+++ b/tests/androidmk_test.sh
@@ -0,0 +1,135 @@
+#!/bin/bash -eu
+
+set -o pipefail
+
+# How to run: bash path-to-script/androidmk_test.sh
+# Tests of converting license functionality of the androidmk tool
+REAL_TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+$REAL_TOP/build/soong/soong_ui.bash --make-mode androidmk
+
+source "$(dirname "$0")/lib.sh"
+
+# Expect to create a new license module
+function test_rewrite_license_property_inside_current_directory {
+  setup
+
+  # Create an Android.mk file
+  mkdir -p a/b
+  cat > a/b/Android.mk <<'EOF'
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind1 license_kind2
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/license_notice1 $(LOCAL_PATH)/license_notice2
+include $(BUILD_PACKAGE)
+EOF
+
+  # Create an expected Android.bp file for the module "foo"
+  cat > a/b/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+license {
+    name: "a_b_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "license_kind1",
+        "license_kind2",
+    ],
+    license_text: [
+        "license_notice1",
+        "license_notice2",
+    ],
+}
+
+android_app {
+    name: "foo",
+}
+EOF
+
+  run_androidmk_test "a/b/Android.mk" "a/b/Android.bp"
+}
+
+# Expect to reference to an existing license module
+function test_rewrite_license_property_outside_current_directory {
+  setup
+
+  # Create an Android.mk file
+  mkdir -p a/b/c/d
+  cat > a/b/c/d/Android.mk <<'EOF'
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_LICENSE_KINDS := license_kind1 license_kind2
+LOCAL_LICENSE_CONDITIONS := license_condition
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../license_notice1 $(LOCAL_PATH)/../../license_notice2
+include $(BUILD_PACKAGE)
+EOF
+
+  # Create an expected (input) Android.bp file at a/b/
+  cat > a/b/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+license {
+    name: "a_b_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "license_kind1",
+        "license_kind2",
+    ],
+    license_text: [
+        "license_notice1",
+        "license_notice2",
+    ],
+}
+
+android_app {
+    name: "bar",
+}
+EOF
+
+  # Create an expected (output) Android.bp file for the module "foo"
+  cat > a/b/c/d/Android.bp <<'EOF'
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "a_b_license",
+    ],
+}
+
+android_app {
+    name: "foo",
+}
+EOF
+
+  run_androidmk_test "a/b/c/d/Android.mk" "a/b/c/d/Android.bp"
+}
+
+run_androidmk_test () {
+  export ANDROID_BUILD_TOP="$MOCK_TOP"
+
+  local out=$($REAL_TOP/*/host/*/bin/androidmk "$1")
+  local expected=$(<"$2")
+
+  if [[ "$out" != "$expected" ]]; then
+    ANDROID_BUILD_TOP="$REAL_TOP"
+    cleanup_mock_top
+    fail "The output is not the same as the expected"
+  fi
+
+  ANDROID_BUILD_TOP="$REAL_TOP"
+  cleanup_mock_top
+  echo "Succeeded"
+}
+
+test_rewrite_license_property_inside_current_directory
+
+test_rewrite_license_property_outside_current_directory
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 3f51114..e92a561 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -7,6 +7,8 @@
 
 source "$(dirname "$0")/lib.sh"
 
+readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
+
 function test_smoke {
   setup
   run_soong
@@ -15,10 +17,10 @@
 function test_null_build() {
   setup
   run_soong
-  local bootstrap_mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
   local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
   run_soong
-  local bootstrap_mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
   local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
@@ -34,12 +36,12 @@
 function test_soong_build_rebuilt_if_blueprint_changes() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/.bootstrap/build.ninja)
+  local mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Bootstrap Ninja file did not change"
@@ -142,7 +144,7 @@
   run_soong
   local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  local glob_deps_file=out/soong/.primary/globs/0.d
+  local glob_deps_file=out/soong/globs/build/0.d
 
   if [ -e "$glob_deps_file" ]; then
     fail "Glob deps file unexpectedly written on first build"
@@ -470,27 +472,90 @@
   fi
 }
 
-function test_null_build_after_docs {
+function test_soong_docs_smoke() {
   setup
-  run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
 
-  prebuilts/build-tools/linux-x86/bin/ninja -f out/soong/build.ninja soong_docs
-  run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  run_soong soong_docs
 
-  if [[ "$mtime1" != "$mtime2" ]]; then
+  [[ -e "out/soong/docs/soong_build.html" ]] || fail "Documentation for main page not created"
+  [[ -e "out/soong/docs/cc.html" ]] || fail "Documentation for C++ modules not created"
+}
+
+function test_null_build_after_soong_docs() {
+  setup
+
+  run_soong
+  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  run_soong soong_docs
+  local docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html)
+
+  run_soong soong_docs
+  local docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html)
+
+  if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then
     fail "Output Ninja file changed on null build"
   fi
+
+  run_soong
+  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+
+  if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
+    fail "Output Ninja file changed on null build"
+  fi
+}
+
+function test_write_to_source_tree {
+  setup
+  mkdir -p a
+  cat > a/Android.bp <<EOF
+genrule {
+  name: "write_to_source_tree",
+  out: ["write_to_source_tree"],
+  cmd: "touch file_in_source_tree && touch \$(out)",
+}
+EOF
+  readonly EXPECTED_OUT=out/soong/.intermediates/a/write_to_source_tree/gen/write_to_source_tree
+  readonly ERROR_LOG=${MOCK_TOP}/out/error.log
+  readonly ERROR_MSG="Read-only file system"
+  readonly ERROR_HINT_PATTERN="BUILD_BROKEN_SRC_DIR"
+  # Test in ReadOnly source tree
+  run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \
+    fail "Write to source tree should not work in a ReadOnly source tree"
+
+  if grep -q "${ERROR_MSG}" ${ERROR_LOG} && grep -q "${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+    echo Error message and error hint found in logs >/dev/null
+  else
+    fail "Did not find Read-only error AND error hint in error.log"
+  fi
+
+  # Test in ReadWrite source tree
+  run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \
+    fail "Write to source tree did not succeed in a ReadWrite source tree"
+
+  if  grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+    fail "Found read-only error OR error hint in error.log"
+  fi
 }
 
 function test_bp2build_smoke {
   setup
-  GENERATE_BAZEL_FILES=1 run_soong
-  [[ -e out/soong/.bootstrap/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
+  run_soong bp2build
+  [[ -e out/soong/bp2build_workspace_marker ]] || fail "bp2build marker file not created"
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
 }
 
+function test_bp2build_generates_marker_file {
+  setup
+  create_mock_bazel
+
+  run_soong bp2build
+
+  if [[ ! -f "./out/soong/bp2build_workspace_marker" ]]; then
+    fail "Marker file was not generated"
+  fi
+}
+
 function test_bp2build_add_android_bp {
   setup
 
@@ -504,9 +569,9 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  [[ -e out/soong/bp2build/a/BUILD ]] || fail "a/BUILD not created"
-  [[ -L out/soong/workspace/a/BUILD ]] || fail "a/BUILD not symlinked"
+  run_soong bp2build
+  [[ -e out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
+  [[ -L out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
 
   mkdir -p b
   touch b/b.txt
@@ -518,19 +583,19 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  [[ -e out/soong/bp2build/b/BUILD ]] || fail "a/BUILD not created"
-  [[ -L out/soong/workspace/b/BUILD ]] || fail "a/BUILD not symlinked"
+  run_soong bp2build
+  [[ -e out/soong/bp2build/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
+  [[ -L out/soong/workspace/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
 }
 
 function test_bp2build_null_build {
   setup
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  run_soong bp2build
+  local mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  run_soong bp2build
+  local mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "Output Ninja file changed on null build"
@@ -550,22 +615,63 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  grep -q a1.txt out/soong/bp2build/a/BUILD || fail "a1.txt not in BUILD file"
+  run_soong bp2build
+  grep -q a1.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a1.txt not in ${GENERATED_BUILD_FILE_NAME} file"
 
   touch a/a2.txt
-  GENERATE_BAZEL_FILES=1 run_soong
-  grep -q a2.txt out/soong/bp2build/a/BUILD || fail "a2.txt not in BUILD file"
+  run_soong bp2build
+  grep -q a2.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a2.txt not in ${GENERATED_BUILD_FILE_NAME} file"
+}
+
+function test_multiple_soong_build_modes() {
+  setup
+  run_soong json-module-graph bp2build nothing
+  if [[ ! -f "out/soong/bp2build_workspace_marker" ]]; then
+    fail "bp2build marker file was not generated"
+  fi
+
+
+  if [[ ! -f "out/soong/module-graph.json" ]]; then
+    fail "JSON file was not created"
+  fi
+
+  if [[ ! -f "out/soong/build.ninja" ]]; then
+    fail "Main build.ninja file was not created"
+  fi
 }
 
 function test_dump_json_module_graph() {
   setup
-  SOONG_DUMP_JSON_MODULE_GRAPH="$MOCK_TOP/modules.json" run_soong
-  if [[ ! -r "$MOCK_TOP/modules.json" ]]; then
+  run_soong json-module-graph
+  if [[ ! -r "out/soong/module-graph.json" ]]; then
     fail "JSON file was not created"
   fi
 }
 
+function test_json_module_graph_back_and_forth_null_build() {
+  setup
+
+  run_soong
+  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  run_soong json-module-graph
+  local json_mtime1=$(stat -c "%y" out/soong/module-graph.json)
+
+  run_soong
+  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
+    fail "Output Ninja file changed after writing JSON module graph"
+  fi
+
+  run_soong json-module-graph
+  local json_mtime2=$(stat -c "%y" out/soong/module-graph.json)
+  if [[ "$json_mtime1" != "$json_mtime2" ]]; then
+    fail "JSON module graph file changed after writing Ninja file"
+  fi
+
+}
+
+
 function test_bp2build_bazel_workspace_structure {
   setup
 
@@ -580,11 +686,11 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
+  run_soong bp2build
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
   [[ -d out/soong/workspace/a/b ]] || fail "module directory not a directory"
-  [[ -L out/soong/workspace/a/b/BUILD ]] || fail "BUILD file not symlinked"
-  [[ "$(readlink -f out/soong/workspace/a/b/BUILD)" =~ bp2build/a/b/BUILD$ ]] \
+  [[ -L "out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
+  [[ "$(readlink -f out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/b/${GENERATED_BUILD_FILE_NAME}"$ ]] \
     || fail "BUILD files symlinked at the wrong place"
   [[ -L out/soong/workspace/a/b/b.txt ]] || fail "a/b/b.txt not symlinked"
   [[ -L out/soong/workspace/a/a.txt ]] || fail "a/b/a.txt not symlinked"
@@ -604,10 +710,10 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
+  run_soong bp2build
 
   touch a/a2.txt  # No reference in the .bp file needed
-  GENERATE_BAZEL_FILES=1 run_soong
+  run_soong bp2build
   [[ -L out/soong/workspace/a/a2.txt ]] || fail "a/a2.txt not symlinked"
 }
 
@@ -616,7 +722,7 @@
 
   mkdir -p a
   touch a/a.txt
-  touch a/BUILD
+  touch a/${GENERATED_BUILD_FILE_NAME}
   cat > a/Android.bp <<EOF
 filegroup {
   name: "a",
@@ -625,16 +731,16 @@
 }
 EOF
 
-  GENERATE_BAZEL_FILES=1 run_soong
-  [[ -L out/soong/workspace/a/BUILD ]] || fail "BUILD file not symlinked"
-  [[ "$(readlink -f out/soong/workspace/a/BUILD)" =~ bp2build/a/BUILD$ ]] \
-    || fail "BUILD files symlinked to the wrong place"
+  run_soong bp2build
+  [[ -L "out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
+  [[ "$(readlink -f out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/${GENERATED_BUILD_FILE_NAME}"$ ]] \
+    || fail "${GENERATED_BUILD_FILE_NAME} files symlinked to the wrong place"
 }
 
 function test_bp2build_reports_multiple_errors {
   setup
 
-  mkdir -p a/BUILD
+  mkdir -p "a/${GENERATED_BUILD_FILE_NAME}"
   touch a/a.txt
   cat > a/Android.bp <<EOF
 filegroup {
@@ -644,7 +750,7 @@
 }
 EOF
 
-  mkdir -p b/BUILD
+  mkdir -p "b/${GENERATED_BUILD_FILE_NAME}"
   touch b/b.txt
   cat > b/Android.bp <<EOF
 filegroup {
@@ -654,17 +760,75 @@
 }
 EOF
 
-  if GENERATE_BAZEL_FILES=1 run_soong >& "$MOCK_TOP/errors"; then
+  if run_soong bp2build >& "$MOCK_TOP/errors"; then
     fail "Build should have failed"
   fi
 
-  grep -q "a/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for a/BUILD not found"
-  grep -q "b/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for b/BUILD not found"
+  grep -q "a/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for a/${GENERATED_BUILD_FILE_NAME} not found"
+  grep -q "b/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for b/${GENERATED_BUILD_FILE_NAME} not found"
+}
+
+function test_bp2build_back_and_forth_null_build {
+  setup
+
+  run_soong
+  local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
+
+  run_soong bp2build
+  local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "Output Ninja file changed when switching to bp2build"
+  fi
+
+  local marker_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+  run_soong
+  local output_mtime3=$(stat -c "%y" out/soong/build.ninja)
+  local marker_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  if [[ "$output_mtime1" != "$output_mtime3" ]]; then
+    fail "Output Ninja file changed when switching to regular build from bp2build"
+  fi
+  if [[ "$marker_mtime1" != "$marker_mtime2" ]]; then
+    fail "bp2build marker file changed when switching to regular build from bp2build"
+  fi
+
+  run_soong bp2build
+  local output_mtime4=$(stat -c "%y" out/soong/build.ninja)
+  local marker_mtime3=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  if [[ "$output_mtime1" != "$output_mtime4" ]]; then
+    fail "Output Ninja file changed when switching back to bp2build"
+  fi
+  if [[ "$marker_mtime1" != "$marker_mtime3" ]]; then
+    fail "bp2build marker file changed when switching back to bp2build"
+  fi
+}
+
+function test_queryview_smoke() {
+  setup
+
+  run_soong queryview
+  [[ -e out/soong/queryview/WORKSPACE ]] || fail "queryview WORKSPACE file not created"
+
+}
+
+function test_queryview_null_build() {
+  setup
+
+  run_soong queryview
+  local output_mtime1=$(stat -c "%y" out/soong/queryview.marker)
+
+  run_soong queryview
+  local output_mtime2=$(stat -c "%y" out/soong/queryview.marker)
+
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "Queryview marker file changed on null build"
+  fi
 }
 
 test_smoke
 test_null_build
-test_null_build_after_docs
+test_soong_docs_smoke
+test_null_build_after_soong_docs
 test_soong_build_rebuilt_if_blueprint_changes
 test_glob_noop_incremental
 test_add_file_to_glob
@@ -674,9 +838,16 @@
 test_add_file_to_soong_build
 test_glob_during_bootstrapping
 test_soong_build_rerun_iff_environment_changes
+test_multiple_soong_build_modes
 test_dump_json_module_graph
+test_json_module_graph_back_and_forth_null_build
+test_write_to_source_tree
+test_queryview_smoke
+test_queryview_null_build
 test_bp2build_smoke
+test_bp2build_generates_marker_file
 test_bp2build_null_build
+test_bp2build_back_and_forth_null_build
 test_bp2build_add_android_bp
 test_bp2build_add_to_glob
 test_bp2build_bazel_workspace_structure
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 082cd06..4f37c2b 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -6,6 +6,48 @@
 
 source "$(dirname "$0")/lib.sh"
 
+readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
+
+function test_bp2build_null_build() {
+  setup
+  run_soong bp2build
+  local output_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+  run_soong bp2build
+  local output_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "Output bp2build marker file changed on null build"
+  fi
+}
+
+test_bp2build_null_build
+
+function test_bp2build_null_build_with_globs() {
+  setup
+
+  mkdir -p foo/bar
+  cat > foo/bar/Android.bp <<'EOF'
+filegroup {
+    name: "globs",
+    srcs: ["*.txt"],
+  }
+EOF
+  touch foo/bar/a.txt foo/bar/b.txt
+
+  run_soong bp2build
+  local output_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+  run_soong bp2build
+  local output_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+
+  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
+    fail "Output bp2build marker file changed on null build"
+  fi
+}
+
+test_bp2build_null_build_with_globs
+
 function test_bp2build_generates_all_buildfiles {
   setup
   create_mock_bazel
@@ -38,32 +80,32 @@
   }
 EOF
 
-  run_bp2build
+  run_soong bp2build
 
-  if [[ ! -f "./out/soong/workspace/foo/convertible_soong_module/BUILD" ]]; then
-    fail "./out/soong/workspace/foo/convertible_soong_module/BUILD was not generated"
+  if [[ ! -f "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME}" ]]; then
+    fail "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME} was not generated"
   fi
 
-  if [[ ! -f "./out/soong/workspace/foo/unconvertible_soong_module/BUILD" ]]; then
-    fail "./out/soong/workspace/foo/unconvertible_soong_module/BUILD was not generated"
+  if [[ ! -f "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}" ]]; then
+    fail "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME} was not generated"
   fi
 
-  if ! grep "the_answer" "./out/soong/workspace/foo/convertible_soong_module/BUILD"; then
-    fail "missing BUILD target the_answer in convertible_soong_module/BUILD"
+  if ! grep "the_answer" "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "missing BUILD target the_answer in convertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
-  if grep "not_the_answer" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
-    fail "found unexpected BUILD target not_the_answer in unconvertible_soong_module/BUILD"
+  if grep "not_the_answer" "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "found unexpected BUILD target not_the_answer in unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
-  if ! grep "filegroup" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
-    fail "missing filegroup in unconvertible_soong_module/BUILD"
+  if ! grep "filegroup" "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "missing filegroup in unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
   # NOTE: We don't actually use the extra BUILD file for anything here
   run_bazel build --package_path=out/soong/workspace //foo/...
 
-  local the_answer_file="bazel-out/k8-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
+  local the_answer_file="bazel-out/android_target-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
   if [[ ! -f "${the_answer_file}" ]]; then
     fail "Expected '${the_answer_file}' to be generated, but was missing"
   fi
diff --git a/tests/lib.sh b/tests/lib.sh
index e561a3d..1bb2df9 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -83,10 +83,14 @@
 function create_mock_soong {
   copy_directory build/blueprint
   copy_directory build/soong
+  copy_directory build/make/tools/rbcrun
 
   symlink_directory prebuilts/go
   symlink_directory prebuilts/build-tools
+  symlink_directory prebuilts/clang/host
+  symlink_directory external/go-cmp
   symlink_directory external/golang-protobuf
+  symlink_directory external/starlark-go
 
   touch "$MOCK_TOP/Android.bp"
 }
@@ -104,7 +108,7 @@
 }
 
 function run_soong() {
-  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests "$@"
+  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-config --soong-only --skip-soong-tests "$@"
 }
 
 function create_mock_bazel() {
@@ -112,8 +116,10 @@
 
   symlink_directory prebuilts/bazel
   symlink_directory prebuilts/jdk
+  symlink_directory external/bazel-skylib
 
   symlink_file WORKSPACE
+  symlink_file BUILD
   symlink_file tools/bazel
 }
 
@@ -121,8 +127,8 @@
   tools/bazel "$@"
 }
 
-run_bp2build() {
-  GENERATE_BAZEL_FILES=true build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests nothing
+run_ninja() {
+  build/soong/soong_ui.bash --make-mode --skip-config --soong-only --skip-soong-tests "$@"
 }
 
 info "Starting Soong integration test suite $(basename $0)"
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
index 80774bf..b408fd3 100755
--- a/tests/mixed_mode_test.sh
+++ b/tests/mixed_mode_test.sh
@@ -14,7 +14,7 @@
   setup
   create_mock_bazel
 
-  run_bazel info
+  STANDALONE_BAZEL=true run_bazel info
 }
 
 test_bazel_smoke
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 8399573..76a918b 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -3,6 +3,9 @@
 set -o pipefail
 
 TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+"$TOP/build/soong/tests/androidmk_test.sh"
 "$TOP/build/soong/tests/bootstrap_test.sh"
 "$TOP/build/soong/tests/mixed_mode_test.sh"
 "$TOP/build/soong/tests/bp2build_bazel_test.sh"
+"$TOP/build/soong/tests/soong_test.sh"
+"$TOP/build/bazel/ci/rbc_regression_test.sh" aosp_arm64-userdebug
diff --git a/tests/soong_test.sh b/tests/soong_test.sh
new file mode 100755
index 0000000..905d708
--- /dev/null
+++ b/tests/soong_test.sh
@@ -0,0 +1,22 @@
+#!/bin/bash -eu
+
+set -o pipefail
+
+# Tests of Soong functionality
+
+source "$(dirname "$0")/lib.sh"
+
+function test_m_clean_works {
+  setup
+
+  # Create a directory with files that cannot be removed
+  mkdir -p out/bad_directory_permissions
+  touch out/bad_directory_permissions/unremovable_file
+  # File permissions are fine but directory permissions are bad
+  chmod a+rwx out/bad_directory_permissions/unremovable_file
+  chmod a-rwx out/bad_directory_permissions
+
+  run_soong clean
+}
+
+test_m_clean_works
diff --git a/third_party/zip/reader_test.go b/third_party/zip/reader_test.go
index dfaae78..11c6d6e 100644
--- a/third_party/zip/reader_test.go
+++ b/third_party/zip/reader_test.go
@@ -786,7 +786,7 @@
 	}
 }
 
-// Verify the number of files is sane.
+// Verify the number of files is within expected bounds
 func TestIssue10956(t *testing.T) {
 	data := []byte("PK\x06\x06PK\x06\a0000\x00\x00\x00\x00\x00\x00\x00\x00" +
 		"0000PK\x05\x06000000000000" +
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index 3d96c84..c2429ab 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -188,20 +188,20 @@
 }
 
 func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
-	testSuites []string, autoGenConfig *bool, unitTest *bool) android.Path {
+	testSuites []string, config []Config, autoGenConfig *bool, unitTest *bool) android.Path {
 	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), nil, "")
+			autogenTemplate(ctx, autogenPath, templatePath.String(), config, "")
 		} else {
 			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${JavaTestConfigTemplate}", nil, "")
+				autogenTemplate(ctx, autogenPath, "${JavaTestConfigTemplate}", config, "")
 			} else {
 				if Bool(unitTest) {
-					autogenTemplate(ctx, autogenPath, "${JavaHostUnitTestConfigTemplate}", nil, "")
+					autogenTemplate(ctx, autogenPath, "${JavaHostUnitTestConfigTemplate}", config, "")
 				} else {
-					autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", nil, "")
+					autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", config, "")
 				}
 			}
 		}
@@ -227,17 +227,17 @@
 }
 
 func AutoGenRustTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool) android.Path {
+	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool, testInstallBase string) android.Path {
 	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
 	if autogenPath != nil {
 		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
 		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, "")
+			autogenTemplate(ctx, autogenPath, templatePath.String(), config, testInstallBase)
 		} else {
 			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${RustDeviceTestConfigTemplate}", config, "")
+				autogenTemplate(ctx, autogenPath, "${RustDeviceTestConfigTemplate}", config, testInstallBase)
 			} else {
-				autogenTemplate(ctx, autogenPath, "${RustHostTestConfigTemplate}", config, "")
+				autogenTemplate(ctx, autogenPath, "${RustHostTestConfigTemplate}", config, testInstallBase)
 			}
 		}
 		return autogenPath
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index d17b464..3dc87f5 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -60,7 +60,7 @@
         "path.go",
         "proc_sync.go",
         "rbe.go",
-        "signal.go",
+        "sandbox_config.go",
         "soong.go",
         "test_build.go",
         "upload.go",
@@ -86,5 +86,8 @@
             "config_linux.go",
             "sandbox_linux.go",
         ],
+        testSrcs: [
+            "sandbox_linux_test.go",
+        ],
     },
 }
diff --git a/ui/build/build.go b/ui/build/build.go
index 8f050d9..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"
@@ -28,16 +29,20 @@
 func SetupOutDir(ctx Context, config Config) {
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
-	if !config.SkipKati() {
-		// Run soong_build with Kati for a hybrid build, e.g. running the
-		// AndroidMk singleton and postinstall commands. Communicate this to
-		// soong_build by writing an empty .soong.kati_enabled marker file in the
-		// soong_build output directory for the soong_build primary builder to
-		// know if the user wants to run Kati after.
-		//
-		// This does not preclude running Kati for *product configuration purposes*.
-		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.kati_enabled"))
+
+	// Potentially write a marker file for whether kati is enabled. This is used by soong_build to
+	// potentially run the AndroidMk singleton and postinstall commands.
+	// Note that the absence of the  file does not not preclude running Kati for product
+	// configuration purposes.
+	katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled")
+	if config.SkipKatiNinja() {
+		os.Remove(katiEnabledMarker)
+		// Note that we can not remove the file for SkipKati builds yet -- some continuous builds
+		// --skip-make builds rely on kati targets being defined.
+	} else if !config.SkipKati() {
+		ensureEmptyFileExists(ctx, katiEnabledMarker)
 	}
+
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
@@ -201,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)
 
@@ -234,17 +241,33 @@
 		ctx.Verboseln("Skipping use of Kati ninja as requested")
 		what = what &^ RunKatiNinja
 	}
+	if config.SkipSoong() {
+		ctx.Verboseln("Skipping use of Soong as requested")
+		what = what &^ RunSoong
+	}
+
 	if config.SkipNinja() {
 		ctx.Verboseln("Skipping Ninja as requested")
 		what = what &^ RunNinja
 	}
 
+	if !config.SoongBuildInvocationNeeded() {
+		// This means that the output of soong_build is not needed and thus it would
+		// run unnecessarily. In addition, if this code wasn't there invocations
+		// with only special-cased target names like "m bp2build" would result in
+		// passing Ninja the empty target list and it would then build the default
+		// targets which is not what the user asked for.
+		what = what &^ RunNinja
+		what = what &^ RunKati
+	}
+
 	if config.StartGoma() {
 		startGoma(ctx, config)
 	}
 
 	if config.StartRBE() {
 		startRBE(ctx, config)
+		defer DumpRBEMetrics(ctx, config, filepath.Join(config.LogsDir(), "rbe_metrics.pb"))
 	}
 
 	if what&RunProductConfig != 0 {
@@ -269,11 +292,6 @@
 
 	if what&RunSoong != 0 {
 		runSoong(ctx, config)
-
-		if config.bazelBuildMode() == generateBuildFiles {
-			// Return early, if we're using Soong as solely the generator of BUILD files.
-			return
-		}
 	}
 
 	if what&RunKati != 0 {
@@ -314,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
@@ -328,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
@@ -347,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/cleanbuild.go b/ui/build/cleanbuild.go
index 19b5690..1c80cff 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -17,6 +17,7 @@
 import (
 	"bytes"
 	"fmt"
+	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -46,9 +47,49 @@
 	}
 }
 
+// Based on https://stackoverflow.com/questions/28969455/how-to-properly-instantiate-os-filemode
+// Because Go doesn't provide a nice way to set bits on a filemode
+const (
+	FILEMODE_READ         = 04
+	FILEMODE_WRITE        = 02
+	FILEMODE_EXECUTE      = 01
+	FILEMODE_USER_SHIFT   = 6
+	FILEMODE_USER_READ    = FILEMODE_READ << FILEMODE_USER_SHIFT
+	FILEMODE_USER_WRITE   = FILEMODE_WRITE << FILEMODE_USER_SHIFT
+	FILEMODE_USER_EXECUTE = FILEMODE_EXECUTE << FILEMODE_USER_SHIFT
+)
+
+// Ensures that files and directories in the out dir can be deleted.
+// For example, Bazen can generate output directories where the write bit isn't set, causing 'm' clean' to fail.
+func ensureOutDirRemovable(ctx Context, config Config) {
+	err := filepath.WalkDir(config.OutDir(), func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if d.IsDir() {
+			info, err := d.Info()
+			if err != nil {
+				return err
+			}
+			// Equivalent to running chmod u+rwx on each directory
+			newMode := info.Mode() | FILEMODE_USER_READ | FILEMODE_USER_WRITE | FILEMODE_USER_EXECUTE
+			if err := os.Chmod(path, newMode); err != nil {
+				return err
+			}
+		}
+		// Continue walking the out dir...
+		return nil
+	})
+	if err != nil && !os.IsNotExist(err) {
+		// Display the error, but don't crash.
+		ctx.Println(err.Error())
+	}
+}
+
 // Remove everything under the out directory. Don't remove the out directory
 // itself in case it's a symlink.
 func clean(ctx Context, config Config) {
+	ensureOutDirRemovable(ctx, config)
 	removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
 	ctx.Println("Entire build directory removed.")
 }
@@ -130,6 +171,7 @@
 		productOut("recovery"),
 		productOut("root"),
 		productOut("system"),
+		productOut("system_dlkm"),
 		productOut("system_other"),
 		productOut("vendor"),
 		productOut("vendor_dlkm"),
@@ -209,7 +251,10 @@
 	newFile = filepath.Join(basePath, newFile)
 	oldFile := newFile + ".previous"
 
-	if _, err := os.Stat(newFile); err != nil {
+	if _, err := os.Stat(newFile); os.IsNotExist(err) {
+		// If the file doesn't exist, assume no installed files exist either
+		return
+	} else if err != nil {
 		ctx.Fatalf("Expected %q to be readable", newFile)
 	}
 
diff --git a/ui/build/config.go b/ui/build/config.go
index 862e09f..e271bfc 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -15,8 +15,12 @@
 package build
 
 import (
+	"context"
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"runtime"
 	"strconv"
@@ -25,15 +29,24 @@
 
 	"android/soong/shared"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 
 	smpb "android/soong/ui/metrics/metrics_proto"
 )
 
+const (
+	envConfigDir = "vendor/google/tools/soong_config"
+	jsonSuffix   = "json"
+
+	configFetcher         = "vendor/google/tools/soong/expconfigfetcher"
+	envConfigFetchTimeout = 10 * time.Second
+)
+
 type Config struct{ *configImpl }
 
 type configImpl struct {
-	// From the environment
+	// Some targets that are implemented in soong_build
+	// (bp2build, json-module-graph) are not here and have their own bits below.
 	arguments     []string
 	goma          bool
 	environ       *Environment
@@ -41,16 +54,22 @@
 	buildDateTime string
 
 	// From the arguments
-	parallel       int
-	keepGoing      int
-	verbose        bool
-	checkbuild     bool
-	dist           bool
-	skipConfig     bool
-	skipKati       bool
-	skipKatiNinja  bool
-	skipNinja      bool
-	skipSoongTests bool
+	parallel        int
+	keepGoing       int
+	verbose         bool
+	checkbuild      bool
+	dist            bool
+	jsonModuleGraph bool
+	bp2build        bool
+	queryview       bool
+	reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
+	soongDocs       bool
+	skipConfig      bool
+	skipKati        bool
+	skipKatiNinja   bool
+	skipSoong       bool
+	skipNinja       bool
+	skipSoongTests  bool
 
 	// From the product config
 	katiArgs        []string
@@ -58,6 +77,7 @@
 	katiSuffix      string
 	targetDevice    string
 	targetDeviceDir string
+	sandboxConfig   *SandboxConfig
 
 	// Autodetected
 	totalRAM uint64
@@ -76,6 +96,8 @@
 
 	// Set by multiproduct_kati
 	emptyNinjaFile bool
+
+	metricsUploader string
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -104,9 +126,6 @@
 	// Don't use bazel at all.
 	noBazel bazelBuildMode = iota
 
-	// Only generate build files (in a subdirectory of the out directory) and exit.
-	generateBuildFiles
-
 	// Generate synthetic build files and incorporate these files into a build which
 	// partially uses Bazel. Build metadata may come from Android.bp or BUILD files.
 	mixedBuild
@@ -122,9 +141,102 @@
 	}
 }
 
+// fetchEnvConfig optionally fetches environment config from an
+// experiments system to control Soong features dynamically.
+func fetchEnvConfig(ctx Context, config *configImpl, envConfigName string) error {
+	configName := envConfigName + "." + jsonSuffix
+	expConfigFetcher := &smpb.ExpConfigFetcher{}
+	defer func() {
+		ctx.Metrics.ExpConfigFetcher(expConfigFetcher)
+	}()
+
+	s, err := os.Stat(configFetcher)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
+		}
+		return err
+	}
+	if s.Mode()&0111 == 0 {
+		status := smpb.ExpConfigFetcher_ERROR
+		expConfigFetcher.Status = &status
+		return fmt.Errorf("configuration fetcher binary %v is not executable: %v", configFetcher, s.Mode())
+	}
+
+	tCtx, cancel := context.WithTimeout(ctx, envConfigFetchTimeout)
+	defer cancel()
+	fetchStart := time.Now()
+	cmd := exec.CommandContext(tCtx, configFetcher, "-output_config_dir", config.OutDir(),
+		"-output_config_name", configName)
+	if err := cmd.Start(); err != nil {
+		status := smpb.ExpConfigFetcher_ERROR
+		expConfigFetcher.Status = &status
+		return err
+	}
+
+	if err := cmd.Wait(); err != nil {
+		status := smpb.ExpConfigFetcher_ERROR
+		expConfigFetcher.Status = &status
+		return err
+	}
+	fetchEnd := time.Now()
+	expConfigFetcher.Micros = proto.Uint64(uint64(fetchEnd.Sub(fetchStart).Microseconds()))
+	outConfigFilePath := filepath.Join(config.OutDir(), configName)
+	expConfigFetcher.Filename = proto.String(outConfigFilePath)
+	if _, err := os.Stat(outConfigFilePath); err == nil {
+		status := smpb.ExpConfigFetcher_CONFIG
+		expConfigFetcher.Status = &status
+	} else {
+		status := smpb.ExpConfigFetcher_NO_CONFIG
+		expConfigFetcher.Status = &status
+	}
+	return nil
+}
+
+func loadEnvConfig(ctx Context, config *configImpl) error {
+	bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
+	if bc == "" {
+		return nil
+	}
+
+	if err := fetchEnvConfig(ctx, config, bc); err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to fetch config file: %v\n", err)
+	}
+
+	configDirs := []string{
+		config.OutDir(),
+		os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR"),
+		envConfigDir,
+	}
+	for _, dir := range configDirs {
+		cfgFile := filepath.Join(os.Getenv("TOP"), dir, fmt.Sprintf("%s.%s", bc, jsonSuffix))
+		envVarsJSON, err := ioutil.ReadFile(cfgFile)
+		if err != nil {
+			continue
+		}
+		ctx.Verbosef("Loading config file %v\n", cfgFile)
+		var envVars map[string]map[string]string
+		if err := json.Unmarshal(envVarsJSON, &envVars); err != nil {
+			fmt.Fprintf(os.Stderr, "Env vars config file %s did not parse correctly: %s", cfgFile, err.Error())
+			continue
+		}
+		for k, v := range envVars["env"] {
+			if os.Getenv(k) != "" {
+				continue
+			}
+			config.environ.Set(k, v)
+		}
+		ctx.Verbosef("Finished loading config file %v\n", cfgFile)
+		break
+	}
+
+	return nil
+}
+
 func NewConfig(ctx Context, args ...string) Config {
 	ret := &configImpl{
-		environ: OsEnvironment(),
+		environ:       OsEnvironment(),
+		sandboxConfig: &SandboxConfig{},
 	}
 
 	// Default matching ninja
@@ -150,12 +262,22 @@
 		ret.environ.Set("OUT_DIR", outDir)
 	}
 
+	// loadEnvConfig needs to know what the OUT_DIR is, so it should
+	// be called after we determine the appropriate out directory.
+	if err := loadEnvConfig(ctx, ret); err != nil {
+		ctx.Fatalln("Failed to parse env config files: %v", err)
+	}
+
 	if distDir, ok := ret.environ.Get("DIST_DIR"); ok {
 		ret.distDir = filepath.Clean(distDir)
 	} else {
 		ret.distDir = filepath.Join(ret.OutDir(), "dist")
 	}
 
+	if srcDirIsWritable, ok := ret.environ.Get("BUILD_BROKEN_SRC_DIR_IS_WRITABLE"); ok {
+		ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
+	}
+
 	ret.environ.Unset(
 		// We're already using it
 		"USE_SOONG_UI",
@@ -228,7 +350,8 @@
 	// Precondition: the current directory is the top of the source tree
 	checkTopDir(ctx)
 
-	if srcDir := absPath(ctx, "."); strings.ContainsRune(srcDir, ' ') {
+	srcDir := absPath(ctx, ".")
+	if strings.ContainsRune(srcDir, ' ') {
 		ctx.Println("You are building in a directory whose absolute path contains a space character:")
 		ctx.Println()
 		ctx.Printf("%q\n", srcDir)
@@ -236,6 +359,8 @@
 		ctx.Fatalln("Directory names containing spaces are not supported")
 	}
 
+	ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)
+
 	if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
 		ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
 		ctx.Println()
@@ -256,10 +381,14 @@
 	java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
 	java9Home := filepath.Join("prebuilts/jdk/jdk9", ret.HostPrebuiltTag())
 	java11Home := filepath.Join("prebuilts/jdk/jdk11", ret.HostPrebuiltTag())
+	java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
 	javaHome := func() string {
 		if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
 			return override
 		}
+		if ret.environ.IsEnvTrue("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN") {
+			return java17Home
+		}
 		if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
 			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
 		}
@@ -336,12 +465,7 @@
 		return
 	}
 
-	b := &smpb.BuildConfig{
-		ForceUseGoma: proto.Bool(config.ForceUseGoma()),
-		UseGoma:      proto.Bool(config.UseGoma()),
-		UseRbe:       proto.Bool(config.UseRBE()),
-	}
-	ctx.Metrics.BuildConfig(b)
+	ctx.Metrics.BuildConfig(buildConfig(config))
 
 	s := &smpb.SystemResourceInfo{
 		TotalPhysicalMemory: proto.Uint64(config.TotalRAM()),
@@ -350,6 +474,19 @@
 	ctx.Metrics.SystemResourceInfo(s)
 }
 
+func buildConfig(config Config) *smpb.BuildConfig {
+	c := &smpb.BuildConfig{
+		ForceUseGoma:    proto.Bool(config.ForceUseGoma()),
+		UseGoma:         proto.Bool(config.UseGoma()),
+		UseRbe:          proto.Bool(config.UseRBE()),
+		BazelAsNinja:    proto.Bool(config.UseBazel()),
+		BazelMixedBuild: proto.Bool(config.bazelBuildMode() == mixedBuild),
+	}
+	c.Targets = append(c.Targets, config.arguments...)
+
+	return c
+}
+
 // getConfigArgs processes the command arguments based on the build action and creates a set of new
 // arguments to be accepted by Config.
 func getConfigArgs(action BuildAction, dir string, ctx Context, args []string) []string {
@@ -568,9 +705,14 @@
 		arg := strings.TrimSpace(args[i])
 		if arg == "showcommands" {
 			c.verbose = true
+		} else if arg == "--empty-ninja-file" {
+			c.emptyNinjaFile = true
 		} else if arg == "--skip-ninja" {
 			c.skipNinja = true
 		} else if arg == "--skip-make" {
+			// TODO(ccross): deprecate this, it has confusing behaviors.  It doesn't run kati,
+			//   but it does run a Kati ninja file if the .kati_enabled marker file was created
+			//   by a previous build.
 			c.skipConfig = true
 			c.skipKati = true
 		} else if arg == "--skip-kati" {
@@ -579,8 +721,16 @@
 		} else if arg == "--soong-only" {
 			c.skipKati = true
 			c.skipKatiNinja = true
+		} else if arg == "--config-only" {
+			c.skipKati = true
+			c.skipKatiNinja = true
+			c.skipSoong = true
+		} else if arg == "--skip-config" {
+			c.skipConfig = true
 		} else if arg == "--skip-soong-tests" {
 			c.skipSoongTests = true
+		} else if arg == "--mk-metrics" {
+			c.reportMkMetrics = true
 		} else if len(arg) > 0 && arg[0] == '-' {
 			parseArgNum := func(def int) int {
 				if len(arg) > 2 {
@@ -613,6 +763,14 @@
 			c.environ.Set(k, v)
 		} else if arg == "dist" {
 			c.dist = true
+		} else if arg == "json-module-graph" {
+			c.jsonModuleGraph = true
+		} else if arg == "bp2build" {
+			c.bp2build = true
+		} else if arg == "queryview" {
+			c.queryview = true
+		} else if arg == "soong_docs" {
+			c.soongDocs = true
 		} else {
 			if arg == "checkbuild" {
 				c.checkbuild = true
@@ -671,54 +829,6 @@
 	}
 }
 
-// Lunch configures the environment for a specific product similarly to the
-// `lunch` bash function.
-func (c *configImpl) Lunch(ctx Context, product, variant string) {
-	if variant != "eng" && variant != "userdebug" && variant != "user" {
-		ctx.Fatalf("Invalid variant %q. Must be one of 'user', 'userdebug' or 'eng'", variant)
-	}
-
-	c.environ.Set("TARGET_PRODUCT", product)
-	c.environ.Set("TARGET_BUILD_VARIANT", variant)
-	c.environ.Set("TARGET_BUILD_TYPE", "release")
-	c.environ.Unset("TARGET_BUILD_APPS")
-	c.environ.Unset("TARGET_BUILD_UNBUNDLED")
-}
-
-// Tapas configures the environment to build one or more unbundled apps,
-// similarly to the `tapas` bash function.
-func (c *configImpl) Tapas(ctx Context, apps []string, arch, variant string) {
-	if len(apps) == 0 {
-		apps = []string{"all"}
-	}
-	if variant == "" {
-		variant = "eng"
-	}
-
-	if variant != "eng" && variant != "userdebug" && variant != "user" {
-		ctx.Fatalf("Invalid variant %q. Must be one of 'user', 'userdebug' or 'eng'", variant)
-	}
-
-	var product string
-	switch arch {
-	case "arm", "":
-		product = "aosp_arm"
-	case "arm64":
-		product = "aosm_arm64"
-	case "x86":
-		product = "aosp_x86"
-	case "x86_64":
-		product = "aosp_x86_64"
-	default:
-		ctx.Fatalf("Invalid architecture: %q", arch)
-	}
-
-	c.environ.Set("TARGET_PRODUCT", product)
-	c.environ.Set("TARGET_BUILD_VARIANT", variant)
-	c.environ.Set("TARGET_BUILD_TYPE", "release")
-	c.environ.Set("TARGET_BUILD_APPS", strings.Join(apps, " "))
-}
-
 func (c *configImpl) Environment() *Environment {
 	return c.environ
 }
@@ -727,6 +837,27 @@
 	return c.arguments
 }
 
+func (c *configImpl) SoongBuildInvocationNeeded() bool {
+	if len(c.Arguments()) > 0 {
+		// Explicit targets requested that are not special targets like b2pbuild
+		// or the JSON module graph
+		return true
+	}
+
+	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() {
+		// Command line was empty, the default Ninja target is built
+		return true
+	}
+
+	// bp2build + dist may be used to dist bp2build logs but does not require SoongBuildInvocation
+	if c.Dist() && !c.Bp2Build() {
+		return true
+	}
+
+	// build.ninja doesn't need to be generated
+	return false
+}
+
 func (c *configImpl) OutDir() string {
 	if outDir, ok := c.environ.Get("OUT_DIR"); ok {
 		return outDir
@@ -761,6 +892,53 @@
 	return filepath.Join(c.OutDir(), "soong")
 }
 
+func (c *configImpl) PrebuiltOS() string {
+	switch runtime.GOOS {
+	case "linux":
+		return "linux-x86"
+	case "darwin":
+		return "darwin-x86"
+	default:
+		panic("Unknown GOOS")
+	}
+}
+
+func (c *configImpl) HostToolDir() string {
+	if c.SkipKatiNinja() {
+		return filepath.Join(c.SoongOutDir(), "host", c.PrebuiltOS(), "bin")
+	} else {
+		return filepath.Join(c.OutDir(), "host", c.PrebuiltOS(), "bin")
+	}
+}
+
+func (c *configImpl) NamedGlobFile(name string) string {
+	return shared.JoinPath(c.SoongOutDir(), "globs-"+name+".ninja")
+}
+
+func (c *configImpl) UsedEnvFile(tag string) string {
+	return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+tag)
+}
+
+func (c *configImpl) Bp2BuildMarkerFile() string {
+	return shared.JoinPath(c.SoongOutDir(), "bp2build_workspace_marker")
+}
+
+func (c *configImpl) SoongDocsHtml() string {
+	return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
+}
+
+func (c *configImpl) QueryviewMarkerFile() string {
+	return shared.JoinPath(c.SoongOutDir(), "queryview.marker")
+}
+
+func (c *configImpl) ModuleGraphFile() string {
+	return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
+}
+
+func (c *configImpl) ModuleActionsFile() string {
+	return shared.JoinPath(c.SoongOutDir(), "module-actions.json")
+}
+
 func (c *configImpl) TempDir() string {
 	return shared.TempDirForOutDir(c.SoongOutDir())
 }
@@ -786,6 +964,22 @@
 	return c.dist
 }
 
+func (c *configImpl) JsonModuleGraph() bool {
+	return c.jsonModuleGraph
+}
+
+func (c *configImpl) Bp2Build() bool {
+	return c.bp2build
+}
+
+func (c *configImpl) Queryview() bool {
+	return c.queryview
+}
+
+func (c *configImpl) SoongDocs() bool {
+	return c.soongDocs
+}
+
 func (c *configImpl) IsVerbose() bool {
 	return c.verbose
 }
@@ -798,6 +992,10 @@
 	return c.skipKatiNinja
 }
 
+func (c *configImpl) SkipSoong() bool {
+	return c.skipSoong
+}
+
 func (c *configImpl) SkipNinja() bool {
 	return c.skipNinja
 }
@@ -911,7 +1109,7 @@
 }
 
 func (c *configImpl) UseRBE() bool {
-	if v, ok := c.environ.Get("USE_RBE"); ok {
+	if v, ok := c.Environment().Get("USE_RBE"); ok {
 		v = strings.TrimSpace(v)
 		if v != "" && v != "false" {
 			return true
@@ -927,8 +1125,6 @@
 func (c *configImpl) bazelBuildMode() bazelBuildMode {
 	if c.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
 		return mixedBuild
-	} else if c.Environment().IsEnvTrue("GENERATE_BAZEL_FILES") {
-		return generateBuildFiles
 	} else {
 		return noBazel
 	}
@@ -1008,7 +1204,12 @@
 }
 
 func (c *configImpl) rbeAuth() (string, string) {
-	credFlags := []string{"use_application_default_credentials", "use_gce_credentials", "credential_file"}
+	credFlags := []string{
+		"use_application_default_credentials",
+		"use_gce_credentials",
+		"credential_file",
+		"use_google_prod_creds",
+	}
 	for _, cf := range credFlags {
 		for _, f := range []string{"RBE_" + cf, "FLAG_" + cf} {
 			if v, ok := c.environ.Get(f); ok {
@@ -1178,22 +1379,25 @@
 }
 
 func (c *configImpl) MetricsUploaderApp() string {
-	if p, ok := c.environ.Get("ANDROID_ENABLE_METRICS_UPLOAD"); ok {
-		return p
-	}
-	return ""
+	return c.metricsUploader
 }
 
-// LogsDir returns the logs directory where build log and metrics
-// files are located. By default, the logs directory is the out
+// LogsDir returns the absolute path to the logs directory where build log and
+// metrics files are located. By default, the logs directory is the out
 // directory. If the argument dist is specified, the logs directory
 // is <dist_dir>/logs.
 func (c *configImpl) LogsDir() string {
+	dir := c.OutDir()
 	if c.Dist() {
 		// Always write logs to the real dist dir, even if Bazel is using a rigged dist dir for other files
-		return filepath.Join(c.RealDistDir(), "logs")
+		dir = filepath.Join(c.RealDistDir(), "logs")
 	}
-	return c.OutDir()
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "\nError making log dir '%s' absolute: %s\n", dir, err.Error())
+		os.Exit(1)
+	}
+	return absDir
 }
 
 // BazelMetricsDir returns the <logs dir>/bazel_metrics directory
@@ -1202,6 +1406,11 @@
 	return filepath.Join(c.LogsDir(), "bazel_metrics")
 }
 
+// MkFileMetrics returns the file path for make-related metrics.
+func (c *configImpl) MkMetrics() string {
+	return filepath.Join(c.LogsDir(), "mk_metrics.pb")
+}
+
 func (c *configImpl) SetEmptyNinjaFile(v bool) {
 	c.emptyNinjaFile = v
 }
@@ -1209,3 +1418,14 @@
 func (c *configImpl) EmptyNinjaFile() bool {
 	return c.emptyNinjaFile
 }
+
+func GetMetricsUploader(topDir string, env *Environment) string {
+	if p, ok := env.Get("METRICS_UPLOADER"); ok {
+		metricsUploader := filepath.Join(topDir, p)
+		if _, err := os.Stat(metricsUploader); err == nil {
+			return metricsUploader
+		}
+	}
+
+	return ""
+}
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 7b14c47..e293275 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -26,7 +26,10 @@
 	"testing"
 
 	"android/soong/ui/logger"
+	smpb "android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
+
+	"google.golang.org/protobuf/proto"
 )
 
 func testContext() Context {
@@ -995,3 +998,189 @@
 		})
 	}
 }
+
+func TestBuildConfig(t *testing.T) {
+	tests := []struct {
+		name                string
+		environ             Environment
+		arguments           []string
+		useBazel            bool
+		expectedBuildConfig *smpb.BuildConfig
+	}{
+		{
+			name:    "none set",
+			environ: Environment{},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(false),
+			},
+		},
+		{
+			name:    "force use goma",
+			environ: Environment{"FORCE_USE_GOMA=1"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(true),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(false),
+			},
+		},
+		{
+			name:    "use goma",
+			environ: Environment{"USE_GOMA=1"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(true),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(false),
+			},
+		},
+		{
+			name:    "use rbe",
+			environ: Environment{"USE_RBE=1"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(true),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(false),
+			},
+		},
+		{
+			name:     "use bazel as ninja",
+			environ:  Environment{},
+			useBazel: true,
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(true),
+				BazelMixedBuild: proto.Bool(false),
+			},
+		},
+		{
+			name:    "bazel mixed build",
+			environ: Environment{"USE_BAZEL_ANALYSIS=1"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(false),
+				BazelMixedBuild: proto.Bool(true),
+			},
+		},
+		{
+			name:      "specified targets",
+			environ:   Environment{},
+			useBazel:  true,
+			arguments: []string{"droid", "dist"},
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(false),
+				UseGoma:         proto.Bool(false),
+				UseRbe:          proto.Bool(false),
+				BazelAsNinja:    proto.Bool(true),
+				BazelMixedBuild: proto.Bool(false),
+				Targets:         []string{"droid", "dist"},
+			},
+		},
+		{
+			name: "all set",
+			environ: Environment{
+				"FORCE_USE_GOMA=1",
+				"USE_GOMA=1",
+				"USE_RBE=1",
+				"USE_BAZEL_ANALYSIS=1",
+			},
+			useBazel: true,
+			expectedBuildConfig: &smpb.BuildConfig{
+				ForceUseGoma:    proto.Bool(true),
+				UseGoma:         proto.Bool(true),
+				UseRbe:          proto.Bool(true),
+				BazelAsNinja:    proto.Bool(true),
+				BazelMixedBuild: proto.Bool(true),
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			c := &configImpl{
+				environ:   &tc.environ,
+				useBazel:  tc.useBazel,
+				arguments: tc.arguments,
+			}
+			config := Config{c}
+			actualBuildConfig := buildConfig(config)
+			if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) {
+				t.Errorf("Expected build config != actual build config: %#v != %#v", *expected, *actualBuildConfig)
+			}
+		})
+	}
+}
+
+func TestGetMetricsUploaderApp(t *testing.T) {
+
+	metricsUploaderDir := "metrics_uploader_dir"
+	metricsUploaderBinary := "metrics_uploader_binary"
+	metricsUploaderPath := filepath.Join(metricsUploaderDir, metricsUploaderBinary)
+	tests := []struct {
+		description string
+		environ     Environment
+		createFiles bool
+		expected    string
+	}{{
+		description: "Uploader binary exist",
+		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
+		createFiles: true,
+		expected:    metricsUploaderPath,
+	}, {
+		description: "Uploader binary not exist",
+		environ:     Environment{"METRICS_UPLOADER=" + metricsUploaderPath},
+		createFiles: false,
+		expected:    "",
+	}, {
+		description: "Uploader binary variable not set",
+		createFiles: true,
+		expected:    "",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				t.Fatalf("got unexpected error: %v", err)
+			})
+
+			// Create the root source tree.
+			topDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create temp dir: %v", err)
+			}
+			defer os.RemoveAll(topDir)
+
+			expected := tt.expected
+			if len(expected) > 0 {
+				expected = filepath.Join(topDir, expected)
+			}
+
+			if tt.createFiles {
+				if err := os.MkdirAll(filepath.Join(topDir, metricsUploaderDir), 0755); err != nil {
+					t.Errorf("failed to create %s directory: %v", metricsUploaderDir, err)
+				}
+				if err := ioutil.WriteFile(filepath.Join(topDir, metricsUploaderPath), []byte{}, 0644); err != nil {
+					t.Errorf("failed to create file %s: %v", expected, err)
+				}
+			}
+
+			actual := GetMetricsUploader(topDir, &tt.environ)
+
+			if actual != expected {
+				t.Errorf("expecting: %s, actual: %s", expected, actual)
+			}
+		})
+	}
+}
diff --git a/ui/build/context.go b/ui/build/context.go
index 43e1e0f..4a4352c 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -20,7 +20,7 @@
 
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
-	"android/soong/ui/metrics/metrics_proto"
+	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
 	"android/soong/ui/tracer"
 )
@@ -71,9 +71,9 @@
 		realTime := end - begin
 		c.Metrics.SetTimeMetrics(
 			soong_metrics_proto.PerfInfo{
-				Desc:      &desc,
-				Name:      &name,
-				StartTime: &begin,
-				RealTime:  &realTime})
+				Description: &desc,
+				Name:        &name,
+				StartTime:   &begin,
+				RealTime:    &realTime})
 	}
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 54aeda0..285f156 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -163,6 +163,8 @@
 	"AUX_OS_VARIANT_LIST",
 	"PRODUCT_SOONG_NAMESPACES",
 	"SOONG_SDK_SNAPSHOT_PREFER",
+	"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE",
+	"SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR",
 	"SOONG_SDK_SNAPSHOT_VERSION",
 }
 
@@ -203,6 +205,9 @@
 		"CCACHE_SLOPPINESS",
 		"CCACHE_BASEDIR",
 		"CCACHE_CPP2",
+
+		// LLVM compiler wrapper options
+		"TOOLCHAIN_RUSAGE_OUTPUT",
 	}
 
 	allVars := append(append([]string{
@@ -225,6 +230,10 @@
 		// Extra environment variables to be exported to ninja
 		"BUILD_BROKEN_NINJA_USES_ENV_VARS",
 
+		// Used to restrict write access to source tree
+		"BUILD_BROKEN_SRC_DIR_IS_WRITABLE",
+		"BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST",
+
 		// Not used, but useful to be in the soong.log
 		"BOARD_VNDK_VERSION",
 
@@ -280,6 +289,8 @@
 	config.SetNinjaArgs(strings.Fields(makeVars["NINJA_GOALS"]))
 	config.SetTargetDevice(makeVars["TARGET_DEVICE"])
 	config.SetTargetDeviceDir(makeVars["TARGET_DEVICE_DIR"])
+	config.sandboxConfig.SetSrcDirIsRO(makeVars["BUILD_BROKEN_SRC_DIR_IS_WRITABLE"] == "false")
+	config.sandboxConfig.SetSrcDirRWAllowlist(strings.Fields(makeVars["BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST"]))
 
 	config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true")
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 2eb84ca..262de3d 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -15,15 +15,16 @@
 package build
 
 import (
-	"android/soong/finder"
-	"android/soong/finder/fs"
-	"android/soong/ui/logger"
 	"bytes"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
 
+	"android/soong/finder"
+	"android/soong/finder/fs"
+	"android/soong/ui/logger"
+
 	"android/soong/ui/metrics"
 )
 
@@ -72,10 +73,10 @@
 			"AndroidProducts.mk",
 			// General Soong build definitions, using the Blueprint syntax.
 			"Android.bp",
-			// build/blueprint build definitions, using the Blueprint syntax.
-			"Blueprints",
 			// Bazel build definitions.
 			"BUILD.bazel",
+			// Bazel build definitions.
+			"BUILD",
 			// Kati clean definitions.
 			"CleanSpec.mk",
 			// Ownership definition.
@@ -86,8 +87,8 @@
 			// Bazel top-level file to mark a directory as a Bazel workspace.
 			"WORKSPACE",
 		},
-		// Bazel Starlark configuration files.
-		IncludeSuffixes: []string{".bzl"},
+		// Bazel Starlark configuration files and all .mk files for product/board configuration.
+		IncludeSuffixes: []string{".bzl", ".mk"},
 	}
 	dumpDir := config.FileListDir()
 	f, err = finder.New(cacheParams, filesystem, logger.New(ioutil.Discard),
@@ -102,7 +103,20 @@
 func findBazelFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
 	matches := []string{}
 	for _, foundName := range entries.FileNames {
-		if foundName == "BUILD.bazel" || foundName == "WORKSPACE" || strings.HasSuffix(foundName, ".bzl") {
+		if foundName == "BUILD.bazel" || foundName == "BUILD" || foundName == "WORKSPACE" || strings.HasSuffix(foundName, ".bzl") {
+			matches = append(matches, foundName)
+		}
+	}
+	return entries.DirNames, matches
+}
+
+func findProductAndBoardConfigFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
+	matches := []string{}
+	for _, foundName := range entries.FileNames {
+		if foundName != "Android.mk" &&
+			foundName != "AndroidProducts.mk" &&
+			foundName != "CleanSpec.mk" &&
+			strings.HasSuffix(foundName, ".mk") {
 			matches = append(matches, foundName)
 		}
 	}
@@ -124,6 +138,17 @@
 		ctx.Fatalf("Could not export module list: %v", err)
 	}
 
+	// Gate collecting/reporting mk metrics on builds that specifically request
+	// it, as identifying the total number of mk files adds 4-5ms onto null
+	// builds.
+	if config.reportMkMetrics {
+		androidMksTotal := f.FindNamedAt(".", "Android.mk")
+
+		ctx.Metrics.SetToplevelMakefiles(len(androidMks))
+		ctx.Metrics.SetTotalMakefiles(len(androidMksTotal))
+		ctx.Metrics.DumpMkMetrics(config.MkMetrics())
+	}
+
 	// Stop searching a subdirectory recursively after finding a CleanSpec.mk.
 	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
 	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
@@ -163,8 +188,6 @@
 
 	// Recursively look for all Android.bp files
 	androidBps := f.FindNamedAt(".", "Android.bp")
-	// The files are named "Blueprints" only in the build/blueprint directory.
-	androidBps = append(androidBps, f.FindNamedAt("build/blueprint", "Blueprints")...)
 	if len(androidBps) == 0 {
 		ctx.Fatalf("No Android.bp found")
 	}
@@ -173,6 +196,13 @@
 		ctx.Fatalf("Could not find modules: %v", err)
 	}
 
+	// Recursively look for all product/board config files.
+	configurationFiles := f.FindMatching(".", findProductAndBoardConfigFiles)
+	err = dumpListToFile(ctx, config, configurationFiles, filepath.Join(dumpDir, "configuration.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export product/board configuration list: %v", err)
+	}
+
 	if config.Dist() {
 		f.WaitForDbDump()
 		// Dist the files.db plain text database.
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 5961c45..dab1a9b 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -141,6 +141,8 @@
 
 			// RBE client
 			"RBE_compare",
+			"RBE_num_local_reruns",
+			"RBE_num_remote_reruns",
 			"RBE_exec_root",
 			"RBE_exec_strategy",
 			"RBE_invocation_id",
@@ -167,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 81c500d..831a80f 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -91,6 +91,7 @@
 	"pstree":  Allowed,
 	"rsync":   Allowed,
 	"sh":      Allowed,
+	"stubby":  Allowed,
 	"tr":      Allowed,
 	"unzip":   Allowed,
 	"zip":     Allowed,
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index d74f262..8f9a699 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -19,6 +19,7 @@
 	"math/rand"
 	"os"
 	"path/filepath"
+	"runtime"
 	"syscall"
 	"time"
 
@@ -87,6 +88,13 @@
 		}
 		vars["RBE_server_address"] = fmt.Sprintf("unix://%v", name)
 	}
+
+	rf := 1.0
+	if config.Parallel() < runtime.NumCPU() {
+		rf = float64(config.Parallel()) / float64(runtime.NumCPU())
+	}
+	vars["RBE_local_resource_fraction"] = fmt.Sprintf("%.2f", rf)
+
 	k, v := config.rbeAuth()
 	vars[k] = v
 	return vars
diff --git a/ui/build/sandbox_config.go b/ui/build/sandbox_config.go
new file mode 100644
index 0000000..1b46459
--- /dev/null
+++ b/ui/build/sandbox_config.go
@@ -0,0 +1,36 @@
+// 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 build
+
+type SandboxConfig struct {
+	srcDirIsRO        bool
+	srcDirRWAllowlist []string
+}
+
+func (sc *SandboxConfig) SetSrcDirIsRO(ro bool) {
+	sc.srcDirIsRO = ro
+}
+
+func (sc *SandboxConfig) SrcDirIsRO() bool {
+	return sc.srcDirIsRO
+}
+
+func (sc *SandboxConfig) SetSrcDirRWAllowlist(allowlist []string) {
+	sc.srcDirRWAllowlist = allowlist
+}
+
+func (sc *SandboxConfig) SrcDirRWAllowlist() []string {
+	return sc.srcDirRWAllowlist
+}
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index dab0e75..5b2046e 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -97,8 +97,11 @@
 			"-u", "nobody",
 			"-g", sandboxConfig.group,
 			"-R", "/",
-			"-B", sandboxConfig.srcDir,
+			// Mount tmp before srcDir
+			// srcDir is /tmp/.* in integration tests, which is a child dir of /tmp
+			// nsjail throws an error if a child dir is mounted before its parent
 			"-B", "/tmp",
+			"-B", sandboxConfig.srcDir,
 			"-B", sandboxConfig.outDir,
 		}
 
@@ -145,6 +148,13 @@
 func (c *Cmd) wrapSandbox() {
 	wd, _ := os.Getwd()
 
+	var srcDirMountFlag string
+	if c.config.sandboxConfig.SrcDirIsRO() {
+		srcDirMountFlag = "-R"
+	} else {
+		srcDirMountFlag = "-B" //Read-Write
+	}
+
 	sandboxArgs := []string{
 		// The executable to run
 		"-x", c.Path,
@@ -184,8 +194,8 @@
 		// Mount a writable tmp dir
 		"-B", "/tmp",
 
-		// Mount source are read-write
-		"-B", sandboxConfig.srcDir,
+		// Mount source
+		srcDirMountFlag, sandboxConfig.srcDir,
 
 		//Mount out dir as read-write
 		"-B", sandboxConfig.outDir,
@@ -198,6 +208,18 @@
 		"-q",
 	}
 
+	// Mount srcDir RW allowlists as Read-Write
+	if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() {
+		errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary.
+			To recover, either
+			1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or
+			2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST`
+		c.ctx.Fatalln(errMsg)
+	}
+	for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() {
+		sandboxArgs = append(sandboxArgs, "-B", srcDirChild)
+	}
+
 	if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
 		//Mount dist dir as read-write if it already exists
 		sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir)
diff --git a/ui/build/sandbox_linux_test.go b/ui/build/sandbox_linux_test.go
new file mode 100644
index 0000000..7bfd750
--- /dev/null
+++ b/ui/build/sandbox_linux_test.go
@@ -0,0 +1,104 @@
+// 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 build
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMain(m *testing.M) {
+	// set src dir of sandbox
+	sandboxConfig.srcDir = "/my/src/dir"
+	os.Exit(m.Run())
+}
+
+func TestMountFlagsSrcDir(t *testing.T) {
+	testCases := []struct {
+		srcDirIsRO         bool
+		expectedSrcDirFlag string
+	}{
+		{
+			srcDirIsRO:         false,
+			expectedSrcDirFlag: "-B",
+		},
+		{
+			srcDirIsRO:         true,
+			expectedSrcDirFlag: "-R",
+		},
+	}
+	for _, testCase := range testCases {
+		c := testCmd()
+		c.config.sandboxConfig.SetSrcDirIsRO(testCase.srcDirIsRO)
+		c.wrapSandbox()
+		if !isExpectedMountFlag(c.Args, sandboxConfig.srcDir, testCase.expectedSrcDirFlag) {
+			t.Error("Mount flag of srcDir is not correct")
+		}
+	}
+}
+
+func TestMountFlagsSrcDirRWAllowlist(t *testing.T) {
+	testCases := []struct {
+		srcDirRWAllowlist []string
+	}{
+		{
+			srcDirRWAllowlist: []string{},
+		},
+		{
+			srcDirRWAllowlist: []string{"my/path"},
+		},
+		{
+			srcDirRWAllowlist: []string{"my/path1", "my/path2"},
+		},
+	}
+	for _, testCase := range testCases {
+		c := testCmd()
+		c.config.sandboxConfig.SetSrcDirIsRO(true)
+		c.config.sandboxConfig.SetSrcDirRWAllowlist(testCase.srcDirRWAllowlist)
+		c.wrapSandbox()
+		for _, allowlistPath := range testCase.srcDirRWAllowlist {
+			if !isExpectedMountFlag(c.Args, allowlistPath, "-B") {
+				t.Error("Mount flag of srcDirRWAllowlist is not correct, expect -B")
+			}
+		}
+	}
+}
+
+// utils for setting up test
+func testConfig() Config {
+	// create a minimal testConfig
+	env := Environment([]string{})
+	sandboxConfig := SandboxConfig{}
+	return Config{&configImpl{environ: &env,
+		sandboxConfig: &sandboxConfig}}
+}
+
+func testCmd() *Cmd {
+	return Command(testContext(), testConfig(), "sandbox_test", "path/to/nsjail")
+}
+
+func isExpectedMountFlag(cmdArgs []string, dirName string, expectedFlag string) bool {
+	indexOfSrcDir := index(cmdArgs, dirName)
+	return cmdArgs[indexOfSrcDir-1] == expectedFlag
+}
+
+func index(arr []string, target string) int {
+	for idx, element := range arr {
+		if element == target {
+			return idx
+		}
+	}
+	panic("element could not be located in input array")
+}
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 87818e3..c7f22f9 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,28 +15,41 @@
 package build
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strconv"
 
-	"android/soong/shared"
-	"github.com/google/blueprint/deptools"
-
+	"android/soong/ui/metrics"
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
+	"android/soong/ui/status"
+
+	"android/soong/shared"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
-
-	"github.com/golang/protobuf/proto"
+	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/microfactory"
 
-	"android/soong/ui/metrics"
-	"android/soong/ui/status"
+	"google.golang.org/protobuf/proto"
 )
 
 const (
 	availableEnvFile = "soong.environment.available"
 	usedEnvFile      = "soong.environment.used"
+
+	soongBuildTag      = "build"
+	bp2buildTag        = "bp2build"
+	jsonModuleGraphTag = "modulegraph"
+	queryviewTag       = "queryview"
+	soongDocsTag       = "soong_docs"
+
+	// bootstrapEpoch is used to determine if an incremental build is incompatible with the current
+	// version of bootstrap and needs cleaning before continuing the build.  Increment this for
+	// incompatible changes, for example when moving the location of the bpglob binary that is
+	// executed during bootstrap before the primary builder has had a chance to update the path.
+	bootstrapEpoch = 1
 )
 
 func writeEnvironmentFile(ctx Context, envFile string, envDeps map[string]string) error {
@@ -71,110 +84,274 @@
 // A tiny struct used to tell Blueprint that it's in bootstrap mode. It would
 // probably be nicer to use a flag in bootstrap.Args instead.
 type BlueprintConfig struct {
-	srcDir           string
-	buildDir         string
-	ninjaBuildDir    string
-	debugCompilation bool
+	toolDir                   string
+	soongOutDir               string
+	outDir                    string
+	runGoTests                bool
+	debugCompilation          bool
+	subninjas                 []string
+	primaryBuilderInvocations []bootstrap.PrimaryBuilderInvocation
 }
 
-func (c BlueprintConfig) SrcDir() string {
-	return "."
+func (c BlueprintConfig) HostToolDir() string {
+	return c.toolDir
 }
 
-func (c BlueprintConfig) BuildDir() string {
-	return c.buildDir
+func (c BlueprintConfig) SoongOutDir() string {
+	return c.soongOutDir
 }
 
-func (c BlueprintConfig) NinjaBuildDir() string {
-	return c.ninjaBuildDir
+func (c BlueprintConfig) OutDir() string {
+	return c.outDir
+}
+
+func (c BlueprintConfig) RunGoTests() bool {
+	return c.runGoTests
 }
 
 func (c BlueprintConfig) DebugCompilation() bool {
 	return c.debugCompilation
 }
 
-func environmentArgs(config Config, suffix string) []string {
+func (c BlueprintConfig) Subninjas() []string {
+	return c.subninjas
+}
+
+func (c BlueprintConfig) PrimaryBuilderInvocations() []bootstrap.PrimaryBuilderInvocation {
+	return c.primaryBuilderInvocations
+}
+
+func environmentArgs(config Config, tag string) []string {
 	return []string{
 		"--available_env", shared.JoinPath(config.SoongOutDir(), availableEnvFile),
-		"--used_env", shared.JoinPath(config.SoongOutDir(), usedEnvFile+suffix),
+		"--used_env", config.UsedEnvFile(tag),
 	}
 }
-func bootstrapBlueprint(ctx Context, config Config, integratedBp2Build bool) {
+
+func writeEmptyFile(ctx Context, path string) {
+	err := os.MkdirAll(filepath.Dir(path), 0777)
+	if err != nil {
+		ctx.Fatalf("Failed to create parent directories of empty file '%s': %s", path, err)
+	}
+
+	if exists, err := fileExists(path); err != nil {
+		ctx.Fatalf("Failed to check if file '%s' exists: %s", path, err)
+	} else if !exists {
+		err = ioutil.WriteFile(path, nil, 0666)
+		if err != nil {
+			ctx.Fatalf("Failed to create empty file '%s': %s", path, err)
+		}
+	}
+}
+
+func fileExists(path string) (bool, error) {
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		return false, nil
+	} else if err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
+func primaryBuilderInvocation(
+	config Config,
+	name string,
+	output string,
+	specificArgs []string,
+	description string) bootstrap.PrimaryBuilderInvocation {
+	commonArgs := make([]string, 0, 0)
+
+	if !config.skipSoongTests {
+		commonArgs = append(commonArgs, "-t")
+	}
+
+	commonArgs = append(commonArgs, "-l", filepath.Join(config.FileListDir(), "Android.bp.list"))
+	invocationEnv := make(map[string]string)
+	debugMode := os.Getenv("SOONG_DELVE") != ""
+
+	if debugMode {
+		commonArgs = append(commonArgs, "--delve_listen", os.Getenv("SOONG_DELVE"))
+		commonArgs = append(commonArgs, "--delve_path", shared.ResolveDelveBinary())
+		// GODEBUG=asyncpreemptoff=1 disables the preemption of goroutines. This
+		// is useful because the preemption happens by sending SIGURG to the OS
+		// thread hosting the goroutine in question and each signal results in
+		// work that needs to be done by Delve; it uses ptrace to debug the Go
+		// process and the tracer process must deal with every signal (it is not
+		// possible to selectively ignore SIGURG). This makes debugging slower,
+		// sometimes by an order of magnitude depending on luck.
+		// The original reason for adding async preemption to Go is here:
+		// https://github.com/golang/proposal/blob/master/design/24543-non-cooperative-preemption.md
+		invocationEnv["GODEBUG"] = "asyncpreemptoff=1"
+	}
+
+	allArgs := make([]string, 0, 0)
+	allArgs = append(allArgs, specificArgs...)
+	allArgs = append(allArgs,
+		"--globListDir", name,
+		"--globFile", config.NamedGlobFile(name))
+
+	allArgs = append(allArgs, commonArgs...)
+	allArgs = append(allArgs, environmentArgs(config, name)...)
+	allArgs = append(allArgs, "Android.bp")
+
+	return bootstrap.PrimaryBuilderInvocation{
+		Inputs:      []string{"Android.bp"},
+		Outputs:     []string{output},
+		Args:        allArgs,
+		Description: description,
+		// NB: Changing the value of this environment variable will not result in a
+		// rebuild. The bootstrap Ninja file will change, but apparently Ninja does
+		// not consider changing the pool specified in a statement a change that's
+		// worth rebuilding for.
+		Console: os.Getenv("SOONG_UNBUFFERED_OUTPUT") == "1",
+		Env:     invocationEnv,
+	}
+}
+
+// bootstrapEpochCleanup deletes files used by bootstrap during incremental builds across
+// incompatible changes.  Incompatible changes are marked by incrementing the bootstrapEpoch
+// constant.  A tree is considered out of date for the current epoch of the
+// .soong.bootstrap.epoch.<epoch> file doesn't exist.
+func bootstrapEpochCleanup(ctx Context, config Config) {
+	epochFile := fmt.Sprintf(".soong.bootstrap.epoch.%d", bootstrapEpoch)
+	epochPath := filepath.Join(config.SoongOutDir(), epochFile)
+	if exists, err := fileExists(epochPath); err != nil {
+		ctx.Fatalf("failed to check if bootstrap epoch file %q exists: %q", epochPath, err)
+	} else if !exists {
+		// The tree is out of date for the current epoch, delete files used by bootstrap
+		// and force the primary builder to rerun.
+		os.Remove(filepath.Join(config.SoongOutDir(), "build.ninja"))
+		for _, globFile := range bootstrapGlobFileList(config) {
+			os.Remove(globFile)
+		}
+
+		// Mark the tree as up to date with the current epoch by writing the epoch marker file.
+		writeEmptyFile(ctx, epochPath)
+	}
+}
+
+func bootstrapGlobFileList(config Config) []string {
+	return []string{
+		config.NamedGlobFile(soongBuildTag),
+		config.NamedGlobFile(bp2buildTag),
+		config.NamedGlobFile(jsonModuleGraphTag),
+		config.NamedGlobFile(queryviewTag),
+		config.NamedGlobFile(soongDocsTag),
+	}
+}
+
+func bootstrapBlueprint(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 	defer ctx.EndTrace()
 
-	var args bootstrap.Args
+	// Clean up some files for incremental builds across incompatible changes.
+	bootstrapEpochCleanup(ctx, config)
 
-	mainNinjaFile := shared.JoinPath(config.SoongOutDir(), "build.ninja")
-	globFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/soong-build-globs.ninja")
-	bootstrapGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.ninja")
-	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
-
-	args.RunGoTests = !config.skipSoongTests
-	args.UseValidations = true // Use validations to depend on tests
-	args.BuildDir = config.SoongOutDir()
-	args.NinjaBuildDir = config.OutDir()
-	args.TopFile = "Android.bp"
-	args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
-	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
-	args.GlobFile = globFile
-	args.GeneratingPrimaryBuilder = true
-	args.EmptyNinjaFile = config.EmptyNinjaFile()
-
-	args.DelveListen = os.Getenv("SOONG_DELVE")
-	if args.DelveListen != "" {
-		args.DelvePath = shared.ResolveDelveBinary()
+	mainSoongBuildExtraArgs := []string{"-o", config.SoongNinjaFile()}
+	if config.EmptyNinjaFile() {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 	}
 
-	commonArgs := bootstrap.PrimaryBuilderExtraFlags(args, bootstrapGlobFile, mainNinjaFile)
-	bp2BuildMarkerFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/bp2build_workspace_marker")
-	mainSoongBuildInputs := []string{"Android.bp"}
+	mainSoongBuildInvocation := primaryBuilderInvocation(
+		config,
+		soongBuildTag,
+		config.SoongNinjaFile(),
+		mainSoongBuildExtraArgs,
+		fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
+	)
 
-	if integratedBp2Build {
-		mainSoongBuildInputs = append(mainSoongBuildInputs, bp2BuildMarkerFile)
+	if config.bazelBuildMode() == mixedBuild {
+		// Mixed builds call Bazel from soong_build and they therefore need the
+		// Bazel workspace to be available. Make that so by adding a dependency on
+		// the bp2build marker file to the action that invokes soong_build .
+		mainSoongBuildInvocation.Inputs = append(mainSoongBuildInvocation.Inputs,
+			config.Bp2BuildMarkerFile())
 	}
 
-	soongBuildArgs := make([]string, 0)
-	soongBuildArgs = append(soongBuildArgs, commonArgs...)
-	soongBuildArgs = append(soongBuildArgs, environmentArgs(config, "")...)
-	soongBuildArgs = append(soongBuildArgs, "Android.bp")
+	bp2buildInvocation := primaryBuilderInvocation(
+		config,
+		bp2buildTag,
+		config.Bp2BuildMarkerFile(),
+		[]string{
+			"--bp2build_marker", config.Bp2BuildMarkerFile(),
+		},
+		fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
+	)
 
-	mainSoongBuildInvocation := bootstrap.PrimaryBuilderInvocation{
-		Inputs:  mainSoongBuildInputs,
-		Outputs: []string{mainNinjaFile},
-		Args:    soongBuildArgs,
+	jsonModuleGraphInvocation := primaryBuilderInvocation(
+		config,
+		jsonModuleGraphTag,
+		config.ModuleGraphFile(),
+		[]string{
+			"--module_graph_file", config.ModuleGraphFile(),
+			"--module_actions_file", config.ModuleActionsFile(),
+		},
+		fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
+	)
+
+	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
+	queryviewInvocation := primaryBuilderInvocation(
+		config,
+		queryviewTag,
+		config.QueryviewMarkerFile(),
+		[]string{
+			"--bazel_queryview_dir", queryviewDir,
+		},
+		fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
+	)
+
+	soongDocsInvocation := primaryBuilderInvocation(
+		config,
+		soongDocsTag,
+		config.SoongDocsHtml(),
+		[]string{
+			"--soong_docs", config.SoongDocsHtml(),
+		},
+		fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
+	)
+
+	globFiles := []string{
+		config.NamedGlobFile(soongBuildTag),
+		config.NamedGlobFile(bp2buildTag),
+		config.NamedGlobFile(jsonModuleGraphTag),
+		config.NamedGlobFile(queryviewTag),
+		config.NamedGlobFile(soongDocsTag),
 	}
 
-	if integratedBp2Build {
-		bp2buildArgs := []string{"--bp2build_marker", bp2BuildMarkerFile}
-		bp2buildArgs = append(bp2buildArgs, commonArgs...)
-		bp2buildArgs = append(bp2buildArgs, environmentArgs(config, ".bp2build")...)
-		bp2buildArgs = append(bp2buildArgs, "Android.bp")
-
-		bp2buildInvocation := bootstrap.PrimaryBuilderInvocation{
-			Inputs:  []string{"Android.bp"},
-			Outputs: []string{bp2BuildMarkerFile},
-			Args:    bp2buildArgs,
-		}
-		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{
-			bp2buildInvocation,
-			mainSoongBuildInvocation,
-		}
-	} else {
-		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{mainSoongBuildInvocation}
+	// The glob .ninja files are subninja'd. However, they are generated during
+	// the build itself so we write an empty file if the file does not exist yet
+	// so that the subninja doesn't fail on clean builds
+	for _, globFile := range bootstrapGlobFileList(config) {
+		writeEmptyFile(ctx, globFile)
 	}
 
+	var blueprintArgs bootstrap.Args
+
+	blueprintArgs.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
+	blueprintArgs.OutFile = shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja")
+	blueprintArgs.EmptyNinjaFile = false
+
 	blueprintCtx := blueprint.NewContext()
 	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 	blueprintConfig := BlueprintConfig{
-		srcDir:           os.Getenv("TOP"),
-		buildDir:         config.SoongOutDir(),
-		ninjaBuildDir:    config.OutDir(),
+		soongOutDir: config.SoongOutDir(),
+		toolDir:     config.HostToolDir(),
+		outDir:      config.OutDir(),
+		runGoTests:  !config.skipSoongTests,
+		// If we want to debug soong_build, we need to compile it for debugging
 		debugCompilation: os.Getenv("SOONG_DELVE") != "",
+		subninjas:        globFiles,
+		primaryBuilderInvocations: []bootstrap.PrimaryBuilderInvocation{
+			mainSoongBuildInvocation,
+			bp2buildInvocation,
+			jsonModuleGraphInvocation,
+			queryviewInvocation,
+			soongDocsInvocation},
 	}
 
-	bootstrapDeps := bootstrap.RunBlueprint(args, blueprintCtx, blueprintConfig)
-	err := deptools.WriteDepFile(bootstrapDepFile, args.OutFile, bootstrapDeps)
+	bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig)
+	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja.d")
+	err := deptools.WriteDepFile(bootstrapDepFile, blueprintArgs.OutFile, bootstrapDeps)
 	if err != nil {
 		ctx.Fatalf("Error writing depfile '%s': %s", bootstrapDepFile, err)
 	}
@@ -185,6 +362,7 @@
 		v, _ := currentEnv.Get(k)
 		return v
 	}
+
 	if stale, _ := shared.StaleEnvFile(envFile, getenv); stale {
 		os.Remove(envFile)
 	}
@@ -200,18 +378,11 @@
 	// unused variables were changed?
 	envFile := filepath.Join(config.SoongOutDir(), availableEnvFile)
 
-	for _, n := range []string{".bootstrap", ".minibootstrap"} {
-		dir := filepath.Join(config.SoongOutDir(), n)
-		if err := os.MkdirAll(dir, 0755); err != nil {
-			ctx.Fatalf("Cannot mkdir " + dir)
-		}
-	}
-
 	buildMode := config.bazelBuildMode()
-	integratedBp2Build := (buildMode == mixedBuild) || (buildMode == generateBuildFiles)
+	integratedBp2Build := buildMode == mixedBuild
 
 	// This is done unconditionally, but does not take a measurable amount of time
-	bootstrapBlueprint(ctx, config, integratedBp2Build)
+	bootstrapBlueprint(ctx, config)
 
 	soongBuildEnv := config.Environment().Copy()
 	soongBuildEnv.Set("TOP", os.Getenv("TOP"))
@@ -221,6 +392,7 @@
 	soongBuildEnv.Set("BAZEL_OUTPUT_BASE", filepath.Join(config.BazelOutDir(), "output"))
 	soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
 	soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir())
+	soongBuildEnv.Set("LOG_DIR", config.LogsDir())
 
 	// For Soong bootstrapping tests
 	if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
@@ -236,31 +408,29 @@
 		ctx.BeginTrace(metrics.RunSoong, "environment check")
 		defer ctx.EndTrace()
 
-		soongBuildEnvFile := filepath.Join(config.SoongOutDir(), usedEnvFile)
-		checkEnvironmentFile(soongBuildEnv, soongBuildEnvFile)
+		checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongBuildTag))
 
-		if integratedBp2Build {
-			bp2buildEnvFile := filepath.Join(config.SoongOutDir(), usedEnvFile+".bp2build")
-			checkEnvironmentFile(soongBuildEnv, bp2buildEnvFile)
+		if integratedBp2Build || config.Bp2Build() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(bp2buildTag))
+		}
+
+		if config.JsonModuleGraph() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
+		}
+
+		if config.Queryview() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(queryviewTag))
+		}
+
+		if config.SoongDocs() {
+			checkEnvironmentFile(soongBuildEnv, config.UsedEnvFile(soongDocsTag))
 		}
 	}()
 
-	var cfg microfactory.Config
-	cfg.Map("github.com/google/blueprint", "build/blueprint")
+	runMicrofactory(ctx, config, "bpglob", "github.com/google/blueprint/bootstrap/bpglob",
+		map[string]string{"github.com/google/blueprint": "build/blueprint"})
 
-	cfg.TrimPath = absPath(ctx, ".")
-
-	func() {
-		ctx.BeginTrace(metrics.RunSoong, "bpglob")
-		defer ctx.EndTrace()
-
-		bpglob := filepath.Join(config.SoongOutDir(), ".minibootstrap/bpglob")
-		if _, err := microfactory.Build(&cfg, bpglob, "github.com/google/blueprint/bootstrap/bpglob"); err != nil {
-			ctx.Fatalln("Failed to build bpglob:", err)
-		}
-	}()
-
-	ninja := func(name, file string) {
+	ninja := func(name, ninjaFile string, targets ...string) {
 		ctx.BeginTrace(metrics.RunSoong, name)
 		defer ctx.EndTrace()
 
@@ -268,8 +438,7 @@
 		nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
 		defer nr.Close()
 
-		cmd := Command(ctx, config, "soong "+name,
-			config.PrebuiltBuildTool("ninja"),
+		ninjaArgs := []string{
 			"-d", "keepdepfile",
 			"-d", "stats",
 			"-o", "usesphonyoutputs=yes",
@@ -279,7 +448,12 @@
 			"-w", "missingoutfile=err",
 			"-j", strconv.Itoa(config.Parallel()),
 			"--frontend_file", fifo,
-			"-f", filepath.Join(config.SoongOutDir(), file))
+			"-f", filepath.Join(config.SoongOutDir(), ninjaFile),
+		}
+
+		ninjaArgs = append(ninjaArgs, targets...)
+		cmd := Command(ctx, config, "soong "+name,
+			config.PrebuiltBuildTool("ninja"), ninjaArgs...)
 
 		var ninjaEnv Environment
 
@@ -291,8 +465,31 @@
 		cmd.Sandbox = soongSandbox
 		cmd.RunAndStreamOrFatal()
 	}
-	// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
-	ninja("bootstrap", ".bootstrap/build.ninja")
+
+	targets := make([]string, 0, 0)
+
+	if config.JsonModuleGraph() {
+		targets = append(targets, config.ModuleGraphFile())
+	}
+
+	if config.Bp2Build() {
+		targets = append(targets, config.Bp2BuildMarkerFile())
+	}
+
+	if config.Queryview() {
+		targets = append(targets, config.QueryviewMarkerFile())
+	}
+
+	if config.SoongDocs() {
+		targets = append(targets, config.SoongDocsHtml())
+	}
+
+	if config.SoongBuildInvocationNeeded() {
+		// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
+		targets = append(targets, config.SoongNinjaFile())
+	}
+
+	ninja("bootstrap", "bootstrap.ninja", targets...)
 
 	var soongBuildMetrics *soong_metrics_proto.SoongBuildMetrics
 	if shouldCollectBuildSoongMetrics(config) {
@@ -310,15 +507,37 @@
 	if shouldCollectBuildSoongMetrics(config) && ctx.Metrics != nil {
 		ctx.Metrics.SetSoongBuildMetrics(soongBuildMetrics)
 	}
+	if config.JsonModuleGraph() {
+		distGzipFile(ctx, config, config.ModuleGraphFile(), "soong")
+	}
+}
+
+func runMicrofactory(ctx Context, config Config, name string, pkg string, mapping map[string]string) {
+	ctx.BeginTrace(metrics.RunSoong, name)
+	defer ctx.EndTrace()
+	cfg := microfactory.Config{TrimPath: absPath(ctx, ".")}
+	for pkgPrefix, pathPrefix := range mapping {
+		cfg.Map(pkgPrefix, pathPrefix)
+	}
+
+	exePath := filepath.Join(config.SoongOutDir(), name)
+	dir := filepath.Dir(exePath)
+	if err := os.MkdirAll(dir, 0777); err != nil {
+		ctx.Fatalf("cannot create %s: %s", dir, err)
+	}
+	if _, err := microfactory.Build(&cfg, exePath, pkg); err != nil {
+		ctx.Fatalf("failed to build %s: %s", name, err)
+	}
 }
 
 func shouldCollectBuildSoongMetrics(config Config) bool {
-	// Do not collect metrics protobuf if the soong_build binary ran as the bp2build converter.
-	return config.bazelBuildMode() != generateBuildFiles
+	// Do not collect metrics protobuf if the soong_build binary ran as the
+	// bp2build converter or the JSON graph dump.
+	return config.SoongBuildInvocationNeeded()
 }
 
 func loadSoongBuildMetrics(ctx Context, config Config) *soong_metrics_proto.SoongBuildMetrics {
-	soongBuildMetricsFile := filepath.Join(config.OutDir(), "soong", "soong_build_metrics.pb")
+	soongBuildMetricsFile := filepath.Join(config.LogsDir(), "soong_build_metrics.pb")
 	buf, err := ioutil.ReadFile(soongBuildMetricsFile)
 	if err != nil {
 		ctx.Fatalf("Failed to load %s: %s", soongBuildMetricsFile, err)
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index a910c06..86c8568 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -51,7 +51,6 @@
 	executable := config.PrebuiltBuildTool("ninja")
 
 	commonArgs := []string{}
-	commonArgs = append(commonArgs, config.NinjaArgs()...)
 	commonArgs = append(commonArgs, "-f", config.CombinedNinjaFile())
 	args := append(commonArgs, "-t", "targets", "rule")
 
@@ -64,8 +63,6 @@
 	cmd.StartOrFatal()
 
 	outDir := config.OutDir()
-	bootstrapDir := filepath.Join(outDir, "soong", ".bootstrap")
-	miniBootstrapDir := filepath.Join(outDir, "soong", ".minibootstrap")
 	modulePathsDir := filepath.Join(outDir, ".module_paths")
 	variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
 
@@ -79,6 +76,9 @@
 	// out/build_date.txt is considered a "source file"
 	buildDatetimeFilePath := filepath.Join(outDir, "build_date.txt")
 
+	// bpglob is built explicitly using Microfactory
+	bpglob := filepath.Join(config.SoongOutDir(), "bpglob")
+
 	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
@@ -88,12 +88,11 @@
 			// Leaf node is not in the out directory.
 			continue
 		}
-		if strings.HasPrefix(line, bootstrapDir) ||
-			strings.HasPrefix(line, miniBootstrapDir) ||
-			strings.HasPrefix(line, modulePathsDir) ||
+		if strings.HasPrefix(line, modulePathsDir) ||
 			line == variablesFilePath ||
 			line == dexpreoptConfigFilePath ||
-			line == buildDatetimeFilePath {
+			line == buildDatetimeFilePath ||
+			line == bpglob {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
 			// full build rules in the primary build.ninja file.
 			continue
diff --git a/ui/build/upload.go b/ui/build/upload.go
index 55ca800..687f519 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -24,7 +24,8 @@
 	"time"
 
 	"android/soong/ui/metrics"
-	"github.com/golang/protobuf/proto"
+
+	"google.golang.org/protobuf/proto"
 
 	upload_proto "android/soong/ui/metrics/upload_proto"
 )
@@ -69,12 +70,11 @@
 	return metricsFiles
 }
 
-// UploadMetrics uploads a set of metrics files to a server for analysis. An
-// uploader full path is specified in ANDROID_ENABLE_METRICS_UPLOAD environment
-// variable in order to upload the set of metrics files. The metrics files are
-// first copied to a temporary directory and the uploader is then executed in
-// the background to allow the user/system to continue working. Soong communicates
-// to the uploader through the upload_proto raw protobuf file.
+// UploadMetrics uploads a set of metrics files to a server for analysis.
+// The metrics files are first copied to a temporary directory
+// and the uploader is then executed in the background to allow the user/system
+// to continue working. Soong communicates to the uploader through the
+// upload_proto raw protobuf file.
 func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, paths ...string) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index b740c11..764a1e1 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -80,13 +80,10 @@
 		createFiles bool
 		files       []string
 	}{{
-		description: "ANDROID_ENABLE_METRICS_UPLOAD not set",
-	}, {
-		description: "no metrics files to upload",
-		uploader:    "fake",
+		description: "no metrics uploader",
 	}, {
 		description: "non-existent metrics files no upload",
-		uploader:    "fake",
+		uploader:    "echo",
 		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3"},
 	}, {
 		description: "trigger upload",
@@ -137,9 +134,9 @@
 			config := Config{&configImpl{
 				environ: &Environment{
 					"OUT_DIR=" + outDir,
-					"ANDROID_ENABLE_METRICS_UPLOAD=" + tt.uploader,
 				},
-				buildDateTime: strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
+				buildDateTime:   strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
+				metricsUploader: tt.uploader,
 			}}
 
 			UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
@@ -192,9 +189,10 @@
 
 			config := Config{&configImpl{
 				environ: &Environment{
-					"ANDROID_ENABLE_METRICS_UPLOAD=fake",
 					"OUT_DIR=/bad",
-				}}}
+				},
+				metricsUploader: "echo",
+			}}
 
 			UploadMetrics(ctx, config, true, time.Now(), metricsFile)
 			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index c428ec4..05db1d7 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -21,9 +21,12 @@
     pkgPath: "android/soong/ui/metrics",
     deps: [
         "golang-protobuf-proto",
+        "soong-ui-bp2build_metrics_proto",
         "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
+        "soong-ui-mk_metrics_proto",
         "soong-ui-tracer",
+        "soong-shared",
     ],
     srcs: [
         "metrics.go",
@@ -37,7 +40,10 @@
 bootstrap_go_package {
     name: "soong-ui-metrics_proto",
     pkgPath: "android/soong/ui/metrics/metrics_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "metrics_proto/metrics.pb.go",
     ],
@@ -46,8 +52,35 @@
 bootstrap_go_package {
     name: "soong-ui-metrics_upload_proto",
     pkgPath: "android/soong/ui/metrics/upload_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "upload_proto/upload.pb.go",
     ],
 }
+
+bootstrap_go_package {
+    name: "soong-ui-bp2build_metrics_proto",
+    pkgPath: "android/soong/ui/metrics/bp2build_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "bp2build_metrics_proto/bp2build_metrics.pb.go",
+    ],
+}
+
+bootstrap_go_package {
+    name: "soong-ui-mk_metrics_proto",
+    pkgPath: "android/soong/ui/metrics/mk_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "mk_metrics_proto/mk_metrics.pb.go",
+    ],
+}
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
new file mode 100644
index 0000000..93f3471
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
@@ -0,0 +1,372 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.9.1
+// source: bp2build_metrics.proto
+
+package bp2build_metrics_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Bp2BuildMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Total number of Soong modules converted to generated targets
+	GeneratedModuleCount uint64 `protobuf:"varint,1,opt,name=generatedModuleCount,proto3" json:"generatedModuleCount,omitempty"`
+	// Total number of Soong modules converted to handcrafted targets
+	HandCraftedModuleCount uint64 `protobuf:"varint,2,opt,name=handCraftedModuleCount,proto3" json:"handCraftedModuleCount,omitempty"`
+	// Total number of unconverted Soong modules
+	UnconvertedModuleCount uint64 `protobuf:"varint,3,opt,name=unconvertedModuleCount,proto3" json:"unconvertedModuleCount,omitempty"`
+	// Counts of generated Bazel targets per Bazel rule class
+	RuleClassCount map[string]uint64 `protobuf:"bytes,4,rep,name=ruleClassCount,proto3" json:"ruleClassCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	// List of converted modules
+	ConvertedModules []string `protobuf:"bytes,5,rep,name=convertedModules,proto3" json:"convertedModules,omitempty"`
+	// Counts of converted modules by module type.
+	ConvertedModuleTypeCount map[string]uint64 `protobuf:"bytes,6,rep,name=convertedModuleTypeCount,proto3" json:"convertedModuleTypeCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	// Counts of total modules by module type.
+	TotalModuleTypeCount map[string]uint64 `protobuf:"bytes,7,rep,name=totalModuleTypeCount,proto3" json:"totalModuleTypeCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+	// List of traced runtime events of bp2build, useful for tracking bp2build
+	// runtime.
+	Events []*Event `protobuf:"bytes,8,rep,name=events,proto3" json:"events,omitempty"`
+}
+
+func (x *Bp2BuildMetrics) Reset() {
+	*x = Bp2BuildMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_bp2build_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Bp2BuildMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Bp2BuildMetrics) ProtoMessage() {}
+
+func (x *Bp2BuildMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_bp2build_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Bp2BuildMetrics.ProtoReflect.Descriptor instead.
+func (*Bp2BuildMetrics) Descriptor() ([]byte, []int) {
+	return file_bp2build_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Bp2BuildMetrics) GetGeneratedModuleCount() uint64 {
+	if x != nil {
+		return x.GeneratedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetHandCraftedModuleCount() uint64 {
+	if x != nil {
+		return x.HandCraftedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetUnconvertedModuleCount() uint64 {
+	if x != nil {
+		return x.UnconvertedModuleCount
+	}
+	return 0
+}
+
+func (x *Bp2BuildMetrics) GetRuleClassCount() map[string]uint64 {
+	if x != nil {
+		return x.RuleClassCount
+	}
+	return nil
+}
+
+func (x *Bp2BuildMetrics) GetConvertedModules() []string {
+	if x != nil {
+		return x.ConvertedModules
+	}
+	return nil
+}
+
+func (x *Bp2BuildMetrics) GetConvertedModuleTypeCount() map[string]uint64 {
+	if x != nil {
+		return x.ConvertedModuleTypeCount
+	}
+	return nil
+}
+
+func (x *Bp2BuildMetrics) GetTotalModuleTypeCount() map[string]uint64 {
+	if x != nil {
+		return x.TotalModuleTypeCount
+	}
+	return nil
+}
+
+func (x *Bp2BuildMetrics) GetEvents() []*Event {
+	if x != nil {
+		return x.Events
+	}
+	return nil
+}
+
+// Traced runtime event of bp2build.
+type Event struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The event name.
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	// The absolute start time of the event
+	// The number of nanoseconds elapsed since January 1, 1970 UTC.
+	StartTime uint64 `protobuf:"varint,2,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
+	// The real running time.
+	// The number of nanoseconds elapsed since start_time.
+	RealTime uint64 `protobuf:"varint,3,opt,name=real_time,json=realTime,proto3" json:"real_time,omitempty"`
+}
+
+func (x *Event) Reset() {
+	*x = Event{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_bp2build_metrics_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Event) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Event) ProtoMessage() {}
+
+func (x *Event) ProtoReflect() protoreflect.Message {
+	mi := &file_bp2build_metrics_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Event.ProtoReflect.Descriptor instead.
+func (*Event) Descriptor() ([]byte, []int) {
+	return file_bp2build_metrics_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Event) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *Event) GetStartTime() uint64 {
+	if x != nil {
+		return x.StartTime
+	}
+	return 0
+}
+
+func (x *Event) GetRealTime() uint64 {
+	if x != nil {
+		return x.RealTime
+	}
+	return 0
+}
+
+var File_bp2build_metrics_proto protoreflect.FileDescriptor
+
+var file_bp2build_metrics_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0xe9, 0x06, 0x0a, 0x0f, 0x42, 0x70, 0x32, 0x42, 0x75,
+	0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x67, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75,
+	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
+	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36,
+	0x0a, 0x16, 0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16,
+	0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76,
+	0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72,
+	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x69,
+	0x0a, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43,
+	0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e,
+	0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72,
+	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75,
+	0x6e, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42, 0x75, 0x69, 0x6c, 0x64,
+	0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65,
+	0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+	0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64,
+	0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
+	0x7b, 0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79,
+	0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e,
+	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32,
+	0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x54, 0x6f, 0x74,
+	0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e,
+	0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x6f, 0x64,
+	0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x06,
+	0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e,
+	0x74, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x41, 0x0a, 0x13, 0x52, 0x75, 0x6c,
+	0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79,
+	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
+	0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4b, 0x0a, 0x1d,
+	0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54,
+	0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
+	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x54, 0x6f, 0x74,
+	0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e,
+	0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
+	0x38, 0x01, 0x22, 0x57, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b,
+	0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f,
+	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_bp2build_metrics_proto_rawDescOnce sync.Once
+	file_bp2build_metrics_proto_rawDescData = file_bp2build_metrics_proto_rawDesc
+)
+
+func file_bp2build_metrics_proto_rawDescGZIP() []byte {
+	file_bp2build_metrics_proto_rawDescOnce.Do(func() {
+		file_bp2build_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_bp2build_metrics_proto_rawDescData)
+	})
+	return file_bp2build_metrics_proto_rawDescData
+}
+
+var file_bp2build_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_bp2build_metrics_proto_goTypes = []interface{}{
+	(*Bp2BuildMetrics)(nil), // 0: soong_build_bp2build_metrics.Bp2BuildMetrics
+	(*Event)(nil),           // 1: soong_build_bp2build_metrics.Event
+	nil,                     // 2: soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
+	nil,                     // 3: soong_build_bp2build_metrics.Bp2BuildMetrics.ConvertedModuleTypeCountEntry
+	nil,                     // 4: soong_build_bp2build_metrics.Bp2BuildMetrics.TotalModuleTypeCountEntry
+}
+var file_bp2build_metrics_proto_depIdxs = []int32{
+	2, // 0: soong_build_bp2build_metrics.Bp2BuildMetrics.ruleClassCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
+	3, // 1: soong_build_bp2build_metrics.Bp2BuildMetrics.convertedModuleTypeCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.ConvertedModuleTypeCountEntry
+	4, // 2: soong_build_bp2build_metrics.Bp2BuildMetrics.totalModuleTypeCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.TotalModuleTypeCountEntry
+	1, // 3: soong_build_bp2build_metrics.Bp2BuildMetrics.events:type_name -> soong_build_bp2build_metrics.Event
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_bp2build_metrics_proto_init() }
+func file_bp2build_metrics_proto_init() {
+	if File_bp2build_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_bp2build_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Bp2BuildMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_bp2build_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Event); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_bp2build_metrics_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_bp2build_metrics_proto_goTypes,
+		DependencyIndexes: file_bp2build_metrics_proto_depIdxs,
+		MessageInfos:      file_bp2build_metrics_proto_msgTypes,
+	}.Build()
+	File_bp2build_metrics_proto = out.File
+	file_bp2build_metrics_proto_rawDesc = nil
+	file_bp2build_metrics_proto_goTypes = nil
+	file_bp2build_metrics_proto_depIdxs = nil
+}
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
new file mode 100644
index 0000000..19a7827
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
@@ -0,0 +1,59 @@
+// Copyright 2021 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package soong_build_bp2build_metrics;
+option go_package = "android/soong/ui/metrics/bp2build_metrics_proto";
+
+message Bp2BuildMetrics {
+  // Total number of Soong modules converted to generated targets
+  uint64 generatedModuleCount = 1;
+
+  // Total number of Soong modules converted to handcrafted targets
+  uint64 handCraftedModuleCount = 2;
+
+  // Total number of unconverted Soong modules
+  uint64 unconvertedModuleCount = 3;
+
+  // Counts of generated Bazel targets per Bazel rule class
+  map<string, uint64> ruleClassCount = 4;
+
+  // List of converted modules
+  repeated string convertedModules = 5;
+
+  // Counts of converted modules by module type.
+  map<string, uint64> convertedModuleTypeCount = 6;
+
+  // Counts of total modules by module type.
+  map<string, uint64> totalModuleTypeCount = 7;
+
+  // List of traced runtime events of bp2build, useful for tracking bp2build
+  // runtime.
+  repeated Event events = 8;
+}
+
+// Traced runtime event of bp2build.
+message Event {
+  // The event name.
+  string name = 1;
+
+  // The absolute start time of the event
+  // The number of nanoseconds elapsed since January 1, 1970 UTC.
+  uint64 start_time = 2;
+
+  // The real running time.
+  // The number of nanoseconds elapsed since start_time.
+  uint64 real_time = 3;
+}
diff --git a/ui/metrics/bp2build_metrics_proto/regen.sh b/ui/metrics/bp2build_metrics_proto/regen.sh
new file mode 100755
index 0000000..bfe4294
--- /dev/null
+++ b/ui/metrics/bp2build_metrics_proto/regen.sh
@@ -0,0 +1,29 @@
+#!/bin/bash -e
+
+# Copyright 2021 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates the golang source file of bp2build_metrics.proto protobuf file.
+
+function die() { echo "ERROR: $1" >&2; exit 1; }
+
+readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
+
+if ! hash aprotoc &>/dev/null; then
+  die "could not find aprotoc. ${error_msg}"
+fi
+
+if ! aprotoc --go_out=paths=source_relative:. bp2build_metrics.proto; then
+  die "build failed. ${error_msg}"
+fi
diff --git a/ui/metrics/event.go b/ui/metrics/event.go
index 87c1b84..ebe664f 100644
--- a/ui/metrics/event.go
+++ b/ui/metrics/event.go
@@ -30,10 +30,10 @@
 	"syscall"
 	"time"
 
-	"android/soong/ui/metrics/metrics_proto"
+	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/tracer"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 )
 
 // _now wraps the time.Now() function. _now is declared for unit testing purpose.
@@ -69,7 +69,7 @@
 func (e event) perfInfo() soong_metrics_proto.PerfInfo {
 	realTime := uint64(_now().Sub(e.start).Nanoseconds())
 	return soong_metrics_proto.PerfInfo{
-		Desc:                  proto.String(e.desc),
+		Description:           proto.String(e.desc),
 		Name:                  proto.String(e.name),
 		StartTime:             proto.Uint64(uint64(e.start.UnixNano())),
 		RealTime:              proto.Uint64(realTime),
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index efb572c..0c62865 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -25,27 +25,24 @@
 // that captures the metrics and is them added as a perfInfo into the set
 // of the collected metrics. Finally, when soong_ui has finished the build,
 // the defer Dump function is invoked to store the collected metrics to the
-// raw protobuf file in the $OUT directory.
-//
-// There is one additional step that occurs after the raw protobuf file is written.
-// If the configuration environment variable ANDROID_ENABLE_METRICS_UPLOAD is
-// set with the path, the raw protobuf file is uploaded to the destination. See
-// ui/build/upload.go for more details. The filename of the raw protobuf file
-// and the list of files to be uploaded is defined in cmd/soong_ui/main.go.
-//
-// See ui/metrics/event.go for the explanation of what an event is and how
-// the metrics system is a stack based system.
+// raw protobuf file in the $OUT directory and this raw protobuf file will be
+// uploaded to the destination. See ui/build/upload.go for more details. The
+// filename of the raw protobuf file and the list of files to be uploaded is
+// defined in cmd/soong_ui/main.go. See ui/metrics/event.go for the explanation
+// of what an event is and how the metrics system is a stack based system.
 
 import (
-	"io/ioutil"
 	"os"
 	"runtime"
 	"strings"
 	"time"
 
-	"github.com/golang/protobuf/proto"
+	"android/soong/shared"
 
-	"android/soong/ui/metrics/metrics_proto"
+	"google.golang.org/protobuf/proto"
+
+	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
+	mk_metrics_proto "android/soong/ui/metrics/mk_metrics_proto"
 )
 
 const (
@@ -67,14 +64,22 @@
 	Total = "total"
 )
 
-// Metrics is a struct that stores collected metrics during the course
-// of a build which later is dumped to a MetricsBase protobuf file.
-// See ui/metrics/metrics_proto/metrics.proto for further details
-// on what information is collected.
+// Metrics is a struct that stores collected metrics during the course of a
+// build. It is later dumped to protobuf files. See underlying metrics protos
+// for further details on what information is collected.
 type Metrics struct {
-	// The protobuf message that is later written to the file.
+	// Protobuf containing various top-level build metrics. These include:
+	// 1. Build identifiers (ex: branch ID, requested product, hostname,
+	//    originating command)
+	// 2. Per-subprocess top-level metrics (ex: ninja process IO and runtime).
+	//    Note that, since these metrics are reported by soong_ui, there is little
+	//    insight that can be provided into performance breakdowns of individual
+	//    subprocesses.
 	metrics soong_metrics_proto.MetricsBase
 
+	// Protobuf containing metrics pertaining to number of makefiles in a build.
+	mkMetrics mk_metrics_proto.MkMetrics
+
 	// A list of pending build events.
 	EventTracer *EventTracer
 }
@@ -83,11 +88,24 @@
 func New() (metrics *Metrics) {
 	m := &Metrics{
 		metrics:     soong_metrics_proto.MetricsBase{},
+		mkMetrics:   mk_metrics_proto.MkMetrics{},
 		EventTracer: &EventTracer{},
 	}
 	return m
 }
 
+func (m *Metrics) SetTotalMakefiles(total int) {
+	m.mkMetrics.TotalMakefiles = uint32(total)
+}
+
+func (m *Metrics) SetToplevelMakefiles(total int) {
+	m.mkMetrics.ToplevelMakefiles = uint32(total)
+}
+
+func (m *Metrics) DumpMkMetrics(outPath string) {
+	shared.Save(&m.mkMetrics, outPath)
+}
+
 // SetTimeMetrics stores performance information from an executed block of
 // code.
 func (m *Metrics) SetTimeMetrics(perf soong_metrics_proto.PerfInfo) {
@@ -118,6 +136,11 @@
 	m.metrics.SystemResourceInfo = b
 }
 
+// ExpConfigFetcher stores information about the expconfigfetcher.
+func (m *Metrics) ExpConfigFetcher(b *soong_metrics_proto.ExpConfigFetcher) {
+	m.metrics.ExpConfigFetcher = b
+}
+
 // SetMetadataMetrics sets information about the build such as the target
 // product, host architecture and out directory.
 func (m *Metrics) SetMetadataMetrics(metadata map[string]string) {
@@ -201,7 +224,7 @@
 	}
 	m.metrics.HostOs = proto.String(runtime.GOOS)
 
-	return save(&m.metrics, out)
+	return shared.Save(&m.metrics, out)
 }
 
 // SetSoongBuildMetrics sets the metrics collected from the soong_build
@@ -233,25 +256,5 @@
 
 // Dump saves the collected CUJs metrics to the raw protobuf file.
 func (c *CriticalUserJourneysMetrics) Dump(filename string) (err error) {
-	return save(&c.cujs, filename)
-}
-
-// save takes a protobuf message, marshals to an array of bytes
-// and is then saved to a file.
-func save(pb proto.Message, filename string) (err error) {
-	data, err := proto.Marshal(pb)
-	if err != nil {
-		return err
-	}
-
-	tempFilename := filename + ".tmp"
-	if err := ioutil.WriteFile(tempFilename, []byte(data), 0644 /* rw-r--r-- */); err != nil {
-		return err
-	}
-
-	if err := os.Rename(tempFilename, filename); err != nil {
-		return err
-	}
-
-	return nil
+	return shared.Save(&c.cujs, filename)
 }
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index fc2cfa4..69f5689 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -1,24 +1,38 @@
+// 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.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.9.1
 // source: metrics.proto
 
-package soong_metrics_proto
+package metrics_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type MetricsBase_BuildVariant int32
 
@@ -28,17 +42,19 @@
 	MetricsBase_ENG       MetricsBase_BuildVariant = 2
 )
 
-var MetricsBase_BuildVariant_name = map[int32]string{
-	0: "USER",
-	1: "USERDEBUG",
-	2: "ENG",
-}
-
-var MetricsBase_BuildVariant_value = map[string]int32{
-	"USER":      0,
-	"USERDEBUG": 1,
-	"ENG":       2,
-}
+// Enum value maps for MetricsBase_BuildVariant.
+var (
+	MetricsBase_BuildVariant_name = map[int32]string{
+		0: "USER",
+		1: "USERDEBUG",
+		2: "ENG",
+	}
+	MetricsBase_BuildVariant_value = map[string]int32{
+		"USER":      0,
+		"USERDEBUG": 1,
+		"ENG":       2,
+	}
+)
 
 func (x MetricsBase_BuildVariant) Enum() *MetricsBase_BuildVariant {
 	p := new(MetricsBase_BuildVariant)
@@ -47,20 +63,34 @@
 }
 
 func (x MetricsBase_BuildVariant) String() string {
-	return proto.EnumName(MetricsBase_BuildVariant_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (x *MetricsBase_BuildVariant) UnmarshalJSON(data []byte) error {
-	value, err := proto.UnmarshalJSONEnum(MetricsBase_BuildVariant_value, data, "MetricsBase_BuildVariant")
+func (MetricsBase_BuildVariant) Descriptor() protoreflect.EnumDescriptor {
+	return file_metrics_proto_enumTypes[0].Descriptor()
+}
+
+func (MetricsBase_BuildVariant) Type() protoreflect.EnumType {
+	return &file_metrics_proto_enumTypes[0]
+}
+
+func (x MetricsBase_BuildVariant) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *MetricsBase_BuildVariant) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
-	*x = MetricsBase_BuildVariant(value)
+	*x = MetricsBase_BuildVariant(num)
 	return nil
 }
 
+// Deprecated: Use MetricsBase_BuildVariant.Descriptor instead.
 func (MetricsBase_BuildVariant) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{0, 0}
+	return file_metrics_proto_rawDescGZIP(), []int{0, 0}
 }
 
 type MetricsBase_Arch int32
@@ -73,21 +103,23 @@
 	MetricsBase_X86_64  MetricsBase_Arch = 4
 )
 
-var MetricsBase_Arch_name = map[int32]string{
-	0: "UNKNOWN",
-	1: "ARM",
-	2: "ARM64",
-	3: "X86",
-	4: "X86_64",
-}
-
-var MetricsBase_Arch_value = map[string]int32{
-	"UNKNOWN": 0,
-	"ARM":     1,
-	"ARM64":   2,
-	"X86":     3,
-	"X86_64":  4,
-}
+// Enum value maps for MetricsBase_Arch.
+var (
+	MetricsBase_Arch_name = map[int32]string{
+		0: "UNKNOWN",
+		1: "ARM",
+		2: "ARM64",
+		3: "X86",
+		4: "X86_64",
+	}
+	MetricsBase_Arch_value = map[string]int32{
+		"UNKNOWN": 0,
+		"ARM":     1,
+		"ARM64":   2,
+		"X86":     3,
+		"X86_64":  4,
+	}
+)
 
 func (x MetricsBase_Arch) Enum() *MetricsBase_Arch {
 	p := new(MetricsBase_Arch)
@@ -96,20 +128,34 @@
 }
 
 func (x MetricsBase_Arch) String() string {
-	return proto.EnumName(MetricsBase_Arch_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (x *MetricsBase_Arch) UnmarshalJSON(data []byte) error {
-	value, err := proto.UnmarshalJSONEnum(MetricsBase_Arch_value, data, "MetricsBase_Arch")
+func (MetricsBase_Arch) Descriptor() protoreflect.EnumDescriptor {
+	return file_metrics_proto_enumTypes[1].Descriptor()
+}
+
+func (MetricsBase_Arch) Type() protoreflect.EnumType {
+	return &file_metrics_proto_enumTypes[1]
+}
+
+func (x MetricsBase_Arch) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *MetricsBase_Arch) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
-	*x = MetricsBase_Arch(value)
+	*x = MetricsBase_Arch(num)
 	return nil
 }
 
+// Deprecated: Use MetricsBase_Arch.Descriptor instead.
 func (MetricsBase_Arch) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{0, 1}
+	return file_metrics_proto_rawDescGZIP(), []int{0, 1}
 }
 
 type ModuleTypeInfo_BuildSystem int32
@@ -120,17 +166,19 @@
 	ModuleTypeInfo_MAKE    ModuleTypeInfo_BuildSystem = 2
 )
 
-var ModuleTypeInfo_BuildSystem_name = map[int32]string{
-	0: "UNKNOWN",
-	1: "SOONG",
-	2: "MAKE",
-}
-
-var ModuleTypeInfo_BuildSystem_value = map[string]int32{
-	"UNKNOWN": 0,
-	"SOONG":   1,
-	"MAKE":    2,
-}
+// Enum value maps for ModuleTypeInfo_BuildSystem.
+var (
+	ModuleTypeInfo_BuildSystem_name = map[int32]string{
+		0: "UNKNOWN",
+		1: "SOONG",
+		2: "MAKE",
+	}
+	ModuleTypeInfo_BuildSystem_value = map[string]int32{
+		"UNKNOWN": 0,
+		"SOONG":   1,
+		"MAKE":    2,
+	}
+)
 
 func (x ModuleTypeInfo_BuildSystem) Enum() *ModuleTypeInfo_BuildSystem {
 	p := new(ModuleTypeInfo_BuildSystem)
@@ -139,23 +187,100 @@
 }
 
 func (x ModuleTypeInfo_BuildSystem) String() string {
-	return proto.EnumName(ModuleTypeInfo_BuildSystem_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (x *ModuleTypeInfo_BuildSystem) UnmarshalJSON(data []byte) error {
-	value, err := proto.UnmarshalJSONEnum(ModuleTypeInfo_BuildSystem_value, data, "ModuleTypeInfo_BuildSystem")
+func (ModuleTypeInfo_BuildSystem) Descriptor() protoreflect.EnumDescriptor {
+	return file_metrics_proto_enumTypes[2].Descriptor()
+}
+
+func (ModuleTypeInfo_BuildSystem) Type() protoreflect.EnumType {
+	return &file_metrics_proto_enumTypes[2]
+}
+
+func (x ModuleTypeInfo_BuildSystem) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *ModuleTypeInfo_BuildSystem) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
-	*x = ModuleTypeInfo_BuildSystem(value)
+	*x = ModuleTypeInfo_BuildSystem(num)
 	return nil
 }
 
+// Deprecated: Use ModuleTypeInfo_BuildSystem.Descriptor instead.
 func (ModuleTypeInfo_BuildSystem) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{5, 0}
+	return file_metrics_proto_rawDescGZIP(), []int{5, 0}
+}
+
+type ExpConfigFetcher_ConfigStatus int32
+
+const (
+	ExpConfigFetcher_NO_CONFIG ExpConfigFetcher_ConfigStatus = 0
+	ExpConfigFetcher_CONFIG    ExpConfigFetcher_ConfigStatus = 1
+	ExpConfigFetcher_ERROR     ExpConfigFetcher_ConfigStatus = 2
+)
+
+// Enum value maps for ExpConfigFetcher_ConfigStatus.
+var (
+	ExpConfigFetcher_ConfigStatus_name = map[int32]string{
+		0: "NO_CONFIG",
+		1: "CONFIG",
+		2: "ERROR",
+	}
+	ExpConfigFetcher_ConfigStatus_value = map[string]int32{
+		"NO_CONFIG": 0,
+		"CONFIG":    1,
+		"ERROR":     2,
+	}
+)
+
+func (x ExpConfigFetcher_ConfigStatus) Enum() *ExpConfigFetcher_ConfigStatus {
+	p := new(ExpConfigFetcher_ConfigStatus)
+	*p = x
+	return p
+}
+
+func (x ExpConfigFetcher_ConfigStatus) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ExpConfigFetcher_ConfigStatus) Descriptor() protoreflect.EnumDescriptor {
+	return file_metrics_proto_enumTypes[3].Descriptor()
+}
+
+func (ExpConfigFetcher_ConfigStatus) Type() protoreflect.EnumType {
+	return &file_metrics_proto_enumTypes[3]
+}
+
+func (x ExpConfigFetcher_ConfigStatus) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *ExpConfigFetcher_ConfigStatus) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = ExpConfigFetcher_ConfigStatus(num)
+	return nil
+}
+
+// Deprecated: Use ExpConfigFetcher_ConfigStatus.Descriptor instead.
+func (ExpConfigFetcher_ConfigStatus) EnumDescriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{9, 0}
 }
 
 type MetricsBase struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Timestamp generated when the build starts.
 	BuildDateTimestamp *int64 `protobuf:"varint,1,opt,name=build_date_timestamp,json=buildDateTimestamp" json:"build_date_timestamp,omitempty"`
 	// It is usually used to specify the branch name [and release candidate].
@@ -207,338 +332,403 @@
 	// The build command that the user entered to the build system.
 	BuildCommand *string `protobuf:"bytes,26,opt,name=build_command,json=buildCommand" json:"build_command,omitempty"`
 	// The metrics for calling Bazel.
-	BazelRuns            []*PerfInfo `protobuf:"bytes,27,rep,name=bazel_runs,json=bazelRuns" json:"bazel_runs,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
-	XXX_unrecognized     []byte      `json:"-"`
-	XXX_sizecache        int32       `json:"-"`
+	BazelRuns []*PerfInfo `protobuf:"bytes,27,rep,name=bazel_runs,json=bazelRuns" json:"bazel_runs,omitempty"`
+	// The metrics of the experiment config fetcher
+	ExpConfigFetcher *ExpConfigFetcher `protobuf:"bytes,28,opt,name=exp_config_fetcher,json=expConfigFetcher" json:"exp_config_fetcher,omitempty"`
 }
 
-func (m *MetricsBase) Reset()         { *m = MetricsBase{} }
-func (m *MetricsBase) String() string { return proto.CompactTextString(m) }
-func (*MetricsBase) ProtoMessage()    {}
+// Default values for MetricsBase fields.
+const (
+	Default_MetricsBase_TargetBuildVariant = MetricsBase_ENG
+	Default_MetricsBase_TargetArch         = MetricsBase_UNKNOWN
+	Default_MetricsBase_HostArch           = MetricsBase_UNKNOWN
+	Default_MetricsBase_Host_2NdArch       = MetricsBase_UNKNOWN
+)
+
+func (x *MetricsBase) Reset() {
+	*x = MetricsBase{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MetricsBase) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MetricsBase) ProtoMessage() {}
+
+func (x *MetricsBase) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MetricsBase.ProtoReflect.Descriptor instead.
 func (*MetricsBase) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{0}
+	return file_metrics_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *MetricsBase) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_MetricsBase.Unmarshal(m, b)
-}
-func (m *MetricsBase) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_MetricsBase.Marshal(b, m, deterministic)
-}
-func (m *MetricsBase) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_MetricsBase.Merge(m, src)
-}
-func (m *MetricsBase) XXX_Size() int {
-	return xxx_messageInfo_MetricsBase.Size(m)
-}
-func (m *MetricsBase) XXX_DiscardUnknown() {
-	xxx_messageInfo_MetricsBase.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_MetricsBase proto.InternalMessageInfo
-
-const Default_MetricsBase_TargetBuildVariant MetricsBase_BuildVariant = MetricsBase_ENG
-const Default_MetricsBase_TargetArch MetricsBase_Arch = MetricsBase_UNKNOWN
-const Default_MetricsBase_HostArch MetricsBase_Arch = MetricsBase_UNKNOWN
-const Default_MetricsBase_Host_2NdArch MetricsBase_Arch = MetricsBase_UNKNOWN
-
-func (m *MetricsBase) GetBuildDateTimestamp() int64 {
-	if m != nil && m.BuildDateTimestamp != nil {
-		return *m.BuildDateTimestamp
+func (x *MetricsBase) GetBuildDateTimestamp() int64 {
+	if x != nil && x.BuildDateTimestamp != nil {
+		return *x.BuildDateTimestamp
 	}
 	return 0
 }
 
-func (m *MetricsBase) GetBuildId() string {
-	if m != nil && m.BuildId != nil {
-		return *m.BuildId
+func (x *MetricsBase) GetBuildId() string {
+	if x != nil && x.BuildId != nil {
+		return *x.BuildId
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetPlatformVersionCodename() string {
-	if m != nil && m.PlatformVersionCodename != nil {
-		return *m.PlatformVersionCodename
+func (x *MetricsBase) GetPlatformVersionCodename() string {
+	if x != nil && x.PlatformVersionCodename != nil {
+		return *x.PlatformVersionCodename
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetTargetProduct() string {
-	if m != nil && m.TargetProduct != nil {
-		return *m.TargetProduct
+func (x *MetricsBase) GetTargetProduct() string {
+	if x != nil && x.TargetProduct != nil {
+		return *x.TargetProduct
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetTargetBuildVariant() MetricsBase_BuildVariant {
-	if m != nil && m.TargetBuildVariant != nil {
-		return *m.TargetBuildVariant
+func (x *MetricsBase) GetTargetBuildVariant() MetricsBase_BuildVariant {
+	if x != nil && x.TargetBuildVariant != nil {
+		return *x.TargetBuildVariant
 	}
 	return Default_MetricsBase_TargetBuildVariant
 }
 
-func (m *MetricsBase) GetTargetArch() MetricsBase_Arch {
-	if m != nil && m.TargetArch != nil {
-		return *m.TargetArch
+func (x *MetricsBase) GetTargetArch() MetricsBase_Arch {
+	if x != nil && x.TargetArch != nil {
+		return *x.TargetArch
 	}
 	return Default_MetricsBase_TargetArch
 }
 
-func (m *MetricsBase) GetTargetArchVariant() string {
-	if m != nil && m.TargetArchVariant != nil {
-		return *m.TargetArchVariant
+func (x *MetricsBase) GetTargetArchVariant() string {
+	if x != nil && x.TargetArchVariant != nil {
+		return *x.TargetArchVariant
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetTargetCpuVariant() string {
-	if m != nil && m.TargetCpuVariant != nil {
-		return *m.TargetCpuVariant
+func (x *MetricsBase) GetTargetCpuVariant() string {
+	if x != nil && x.TargetCpuVariant != nil {
+		return *x.TargetCpuVariant
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetHostArch() MetricsBase_Arch {
-	if m != nil && m.HostArch != nil {
-		return *m.HostArch
+func (x *MetricsBase) GetHostArch() MetricsBase_Arch {
+	if x != nil && x.HostArch != nil {
+		return *x.HostArch
 	}
 	return Default_MetricsBase_HostArch
 }
 
-func (m *MetricsBase) GetHost_2NdArch() MetricsBase_Arch {
-	if m != nil && m.Host_2NdArch != nil {
-		return *m.Host_2NdArch
+func (x *MetricsBase) GetHost_2NdArch() MetricsBase_Arch {
+	if x != nil && x.Host_2NdArch != nil {
+		return *x.Host_2NdArch
 	}
 	return Default_MetricsBase_Host_2NdArch
 }
 
-func (m *MetricsBase) GetHostOs() string {
-	if m != nil && m.HostOs != nil {
-		return *m.HostOs
+func (x *MetricsBase) GetHostOs() string {
+	if x != nil && x.HostOs != nil {
+		return *x.HostOs
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetHostOsExtra() string {
-	if m != nil && m.HostOsExtra != nil {
-		return *m.HostOsExtra
+func (x *MetricsBase) GetHostOsExtra() string {
+	if x != nil && x.HostOsExtra != nil {
+		return *x.HostOsExtra
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetHostCrossOs() string {
-	if m != nil && m.HostCrossOs != nil {
-		return *m.HostCrossOs
+func (x *MetricsBase) GetHostCrossOs() string {
+	if x != nil && x.HostCrossOs != nil {
+		return *x.HostCrossOs
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetHostCrossArch() string {
-	if m != nil && m.HostCrossArch != nil {
-		return *m.HostCrossArch
+func (x *MetricsBase) GetHostCrossArch() string {
+	if x != nil && x.HostCrossArch != nil {
+		return *x.HostCrossArch
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetHostCross_2NdArch() string {
-	if m != nil && m.HostCross_2NdArch != nil {
-		return *m.HostCross_2NdArch
+func (x *MetricsBase) GetHostCross_2NdArch() string {
+	if x != nil && x.HostCross_2NdArch != nil {
+		return *x.HostCross_2NdArch
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetOutDir() string {
-	if m != nil && m.OutDir != nil {
-		return *m.OutDir
+func (x *MetricsBase) GetOutDir() string {
+	if x != nil && x.OutDir != nil {
+		return *x.OutDir
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetSetupTools() []*PerfInfo {
-	if m != nil {
-		return m.SetupTools
+func (x *MetricsBase) GetSetupTools() []*PerfInfo {
+	if x != nil {
+		return x.SetupTools
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetKatiRuns() []*PerfInfo {
-	if m != nil {
-		return m.KatiRuns
+func (x *MetricsBase) GetKatiRuns() []*PerfInfo {
+	if x != nil {
+		return x.KatiRuns
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetSoongRuns() []*PerfInfo {
-	if m != nil {
-		return m.SoongRuns
+func (x *MetricsBase) GetSoongRuns() []*PerfInfo {
+	if x != nil {
+		return x.SoongRuns
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetNinjaRuns() []*PerfInfo {
-	if m != nil {
-		return m.NinjaRuns
+func (x *MetricsBase) GetNinjaRuns() []*PerfInfo {
+	if x != nil {
+		return x.NinjaRuns
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetTotal() *PerfInfo {
-	if m != nil {
-		return m.Total
+func (x *MetricsBase) GetTotal() *PerfInfo {
+	if x != nil {
+		return x.Total
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetSoongBuildMetrics() *SoongBuildMetrics {
-	if m != nil {
-		return m.SoongBuildMetrics
+func (x *MetricsBase) GetSoongBuildMetrics() *SoongBuildMetrics {
+	if x != nil {
+		return x.SoongBuildMetrics
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetBuildConfig() *BuildConfig {
-	if m != nil {
-		return m.BuildConfig
+func (x *MetricsBase) GetBuildConfig() *BuildConfig {
+	if x != nil {
+		return x.BuildConfig
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetHostname() string {
-	if m != nil && m.Hostname != nil {
-		return *m.Hostname
+func (x *MetricsBase) GetHostname() string {
+	if x != nil && x.Hostname != nil {
+		return *x.Hostname
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetSystemResourceInfo() *SystemResourceInfo {
-	if m != nil {
-		return m.SystemResourceInfo
+func (x *MetricsBase) GetSystemResourceInfo() *SystemResourceInfo {
+	if x != nil {
+		return x.SystemResourceInfo
 	}
 	return nil
 }
 
-func (m *MetricsBase) GetBuildCommand() string {
-	if m != nil && m.BuildCommand != nil {
-		return *m.BuildCommand
+func (x *MetricsBase) GetBuildCommand() string {
+	if x != nil && x.BuildCommand != nil {
+		return *x.BuildCommand
 	}
 	return ""
 }
 
-func (m *MetricsBase) GetBazelRuns() []*PerfInfo {
-	if m != nil {
-		return m.BazelRuns
+func (x *MetricsBase) GetBazelRuns() []*PerfInfo {
+	if x != nil {
+		return x.BazelRuns
+	}
+	return nil
+}
+
+func (x *MetricsBase) GetExpConfigFetcher() *ExpConfigFetcher {
+	if x != nil {
+		return x.ExpConfigFetcher
 	}
 	return nil
 }
 
 type BuildConfig struct {
-	UseGoma              *bool    `protobuf:"varint,1,opt,name=use_goma,json=useGoma" json:"use_goma,omitempty"`
-	UseRbe               *bool    `protobuf:"varint,2,opt,name=use_rbe,json=useRbe" json:"use_rbe,omitempty"`
-	ForceUseGoma         *bool    `protobuf:"varint,3,opt,name=force_use_goma,json=forceUseGoma" json:"force_use_goma,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	UseGoma      *bool `protobuf:"varint,1,opt,name=use_goma,json=useGoma" json:"use_goma,omitempty"`
+	UseRbe       *bool `protobuf:"varint,2,opt,name=use_rbe,json=useRbe" json:"use_rbe,omitempty"`
+	ForceUseGoma *bool `protobuf:"varint,3,opt,name=force_use_goma,json=forceUseGoma" json:"force_use_goma,omitempty"`
+	// Whether the Bazel is acting as the Ninja executor for this build.
+	BazelAsNinja *bool `protobuf:"varint,4,opt,name=bazel_as_ninja,json=bazelAsNinja" json:"bazel_as_ninja,omitempty"`
+	// Whether build is occurring in a mixed build mode, where Bazel maintains the
+	// definition and build of some modules in cooperation with Soong.
+	BazelMixedBuild *bool `protobuf:"varint,5,opt,name=bazel_mixed_build,json=bazelMixedBuild" json:"bazel_mixed_build,omitempty"`
+	// These are the targets soong passes to ninja, these targets include special
+	// targets such as droid as well as the regular build targets.
+	Targets []string `protobuf:"bytes,6,rep,name=targets" json:"targets,omitempty"`
 }
 
-func (m *BuildConfig) Reset()         { *m = BuildConfig{} }
-func (m *BuildConfig) String() string { return proto.CompactTextString(m) }
-func (*BuildConfig) ProtoMessage()    {}
+func (x *BuildConfig) Reset() {
+	*x = BuildConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildConfig) ProtoMessage() {}
+
+func (x *BuildConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildConfig.ProtoReflect.Descriptor instead.
 func (*BuildConfig) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{1}
+	return file_metrics_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *BuildConfig) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BuildConfig.Unmarshal(m, b)
-}
-func (m *BuildConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BuildConfig.Marshal(b, m, deterministic)
-}
-func (m *BuildConfig) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BuildConfig.Merge(m, src)
-}
-func (m *BuildConfig) XXX_Size() int {
-	return xxx_messageInfo_BuildConfig.Size(m)
-}
-func (m *BuildConfig) XXX_DiscardUnknown() {
-	xxx_messageInfo_BuildConfig.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BuildConfig proto.InternalMessageInfo
-
-func (m *BuildConfig) GetUseGoma() bool {
-	if m != nil && m.UseGoma != nil {
-		return *m.UseGoma
+func (x *BuildConfig) GetUseGoma() bool {
+	if x != nil && x.UseGoma != nil {
+		return *x.UseGoma
 	}
 	return false
 }
 
-func (m *BuildConfig) GetUseRbe() bool {
-	if m != nil && m.UseRbe != nil {
-		return *m.UseRbe
+func (x *BuildConfig) GetUseRbe() bool {
+	if x != nil && x.UseRbe != nil {
+		return *x.UseRbe
 	}
 	return false
 }
 
-func (m *BuildConfig) GetForceUseGoma() bool {
-	if m != nil && m.ForceUseGoma != nil {
-		return *m.ForceUseGoma
+func (x *BuildConfig) GetForceUseGoma() bool {
+	if x != nil && x.ForceUseGoma != nil {
+		return *x.ForceUseGoma
 	}
 	return false
 }
 
+func (x *BuildConfig) GetBazelAsNinja() bool {
+	if x != nil && x.BazelAsNinja != nil {
+		return *x.BazelAsNinja
+	}
+	return false
+}
+
+func (x *BuildConfig) GetBazelMixedBuild() bool {
+	if x != nil && x.BazelMixedBuild != nil {
+		return *x.BazelMixedBuild
+	}
+	return false
+}
+
+func (x *BuildConfig) GetTargets() []string {
+	if x != nil {
+		return x.Targets
+	}
+	return nil
+}
+
 type SystemResourceInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The total physical memory in bytes.
 	TotalPhysicalMemory *uint64 `protobuf:"varint,1,opt,name=total_physical_memory,json=totalPhysicalMemory" json:"total_physical_memory,omitempty"`
 	// The total of available cores for building
-	AvailableCpus        *int32   `protobuf:"varint,2,opt,name=available_cpus,json=availableCpus" json:"available_cpus,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	AvailableCpus *int32 `protobuf:"varint,2,opt,name=available_cpus,json=availableCpus" json:"available_cpus,omitempty"`
 }
 
-func (m *SystemResourceInfo) Reset()         { *m = SystemResourceInfo{} }
-func (m *SystemResourceInfo) String() string { return proto.CompactTextString(m) }
-func (*SystemResourceInfo) ProtoMessage()    {}
+func (x *SystemResourceInfo) Reset() {
+	*x = SystemResourceInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SystemResourceInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SystemResourceInfo) ProtoMessage() {}
+
+func (x *SystemResourceInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SystemResourceInfo.ProtoReflect.Descriptor instead.
 func (*SystemResourceInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{2}
+	return file_metrics_proto_rawDescGZIP(), []int{2}
 }
 
-func (m *SystemResourceInfo) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SystemResourceInfo.Unmarshal(m, b)
-}
-func (m *SystemResourceInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SystemResourceInfo.Marshal(b, m, deterministic)
-}
-func (m *SystemResourceInfo) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SystemResourceInfo.Merge(m, src)
-}
-func (m *SystemResourceInfo) XXX_Size() int {
-	return xxx_messageInfo_SystemResourceInfo.Size(m)
-}
-func (m *SystemResourceInfo) XXX_DiscardUnknown() {
-	xxx_messageInfo_SystemResourceInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SystemResourceInfo proto.InternalMessageInfo
-
-func (m *SystemResourceInfo) GetTotalPhysicalMemory() uint64 {
-	if m != nil && m.TotalPhysicalMemory != nil {
-		return *m.TotalPhysicalMemory
+func (x *SystemResourceInfo) GetTotalPhysicalMemory() uint64 {
+	if x != nil && x.TotalPhysicalMemory != nil {
+		return *x.TotalPhysicalMemory
 	}
 	return 0
 }
 
-func (m *SystemResourceInfo) GetAvailableCpus() int32 {
-	if m != nil && m.AvailableCpus != nil {
-		return *m.AvailableCpus
+func (x *SystemResourceInfo) GetAvailableCpus() int32 {
+	if x != nil && x.AvailableCpus != nil {
+		return *x.AvailableCpus
 	}
 	return 0
 }
 
 type PerfInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The description for the phase/action/part while the tool running.
-	Desc *string `protobuf:"bytes,1,opt,name=desc" json:"desc,omitempty"`
+	Description *string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
 	// The name for the running phase/action/part.
 	Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
 	// The absolute start time.
@@ -548,83 +738,93 @@
 	// The number of nanoseconds elapsed since start_time.
 	RealTime *uint64 `protobuf:"varint,4,opt,name=real_time,json=realTime" json:"real_time,omitempty"`
 	// The number of MB for memory use (deprecated as it is too generic).
-	MemoryUse *uint64 `protobuf:"varint,5,opt,name=memory_use,json=memoryUse" json:"memory_use,omitempty"` // Deprecated: Do not use.
+	//
+	// Deprecated: Do not use.
+	MemoryUse *uint64 `protobuf:"varint,5,opt,name=memory_use,json=memoryUse" json:"memory_use,omitempty"`
 	// The resource information of each executed process.
 	ProcessesResourceInfo []*ProcessResourceInfo `protobuf:"bytes,6,rep,name=processes_resource_info,json=processesResourceInfo" json:"processes_resource_info,omitempty"`
-	XXX_NoUnkeyedLiteral  struct{}               `json:"-"`
-	XXX_unrecognized      []byte                 `json:"-"`
-	XXX_sizecache         int32                  `json:"-"`
 }
 
-func (m *PerfInfo) Reset()         { *m = PerfInfo{} }
-func (m *PerfInfo) String() string { return proto.CompactTextString(m) }
-func (*PerfInfo) ProtoMessage()    {}
+func (x *PerfInfo) Reset() {
+	*x = PerfInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PerfInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PerfInfo) ProtoMessage() {}
+
+func (x *PerfInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[3]
+	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 PerfInfo.ProtoReflect.Descriptor instead.
 func (*PerfInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{3}
+	return file_metrics_proto_rawDescGZIP(), []int{3}
 }
 
-func (m *PerfInfo) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_PerfInfo.Unmarshal(m, b)
-}
-func (m *PerfInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_PerfInfo.Marshal(b, m, deterministic)
-}
-func (m *PerfInfo) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PerfInfo.Merge(m, src)
-}
-func (m *PerfInfo) XXX_Size() int {
-	return xxx_messageInfo_PerfInfo.Size(m)
-}
-func (m *PerfInfo) XXX_DiscardUnknown() {
-	xxx_messageInfo_PerfInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PerfInfo proto.InternalMessageInfo
-
-func (m *PerfInfo) GetDesc() string {
-	if m != nil && m.Desc != nil {
-		return *m.Desc
+func (x *PerfInfo) GetDescription() string {
+	if x != nil && x.Description != nil {
+		return *x.Description
 	}
 	return ""
 }
 
-func (m *PerfInfo) GetName() string {
-	if m != nil && m.Name != nil {
-		return *m.Name
+func (x *PerfInfo) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
 	}
 	return ""
 }
 
-func (m *PerfInfo) GetStartTime() uint64 {
-	if m != nil && m.StartTime != nil {
-		return *m.StartTime
+func (x *PerfInfo) GetStartTime() uint64 {
+	if x != nil && x.StartTime != nil {
+		return *x.StartTime
 	}
 	return 0
 }
 
-func (m *PerfInfo) GetRealTime() uint64 {
-	if m != nil && m.RealTime != nil {
-		return *m.RealTime
+func (x *PerfInfo) GetRealTime() uint64 {
+	if x != nil && x.RealTime != nil {
+		return *x.RealTime
 	}
 	return 0
 }
 
 // Deprecated: Do not use.
-func (m *PerfInfo) GetMemoryUse() uint64 {
-	if m != nil && m.MemoryUse != nil {
-		return *m.MemoryUse
+func (x *PerfInfo) GetMemoryUse() uint64 {
+	if x != nil && x.MemoryUse != nil {
+		return *x.MemoryUse
 	}
 	return 0
 }
 
-func (m *PerfInfo) GetProcessesResourceInfo() []*ProcessResourceInfo {
-	if m != nil {
-		return m.ProcessesResourceInfo
+func (x *PerfInfo) GetProcessesResourceInfo() []*ProcessResourceInfo {
+	if x != nil {
+		return x.ProcessesResourceInfo
 	}
 	return nil
 }
 
 type ProcessResourceInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The name of the process for identification.
 	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
 	// The amount of time spent executing in user space in microseconds.
@@ -644,257 +844,292 @@
 	// The number of voluntary context switches
 	VoluntaryContextSwitches *uint64 `protobuf:"varint,9,opt,name=voluntary_context_switches,json=voluntaryContextSwitches" json:"voluntary_context_switches,omitempty"`
 	// The number of involuntary context switches
-	InvoluntaryContextSwitches *uint64  `protobuf:"varint,10,opt,name=involuntary_context_switches,json=involuntaryContextSwitches" json:"involuntary_context_switches,omitempty"`
-	XXX_NoUnkeyedLiteral       struct{} `json:"-"`
-	XXX_unrecognized           []byte   `json:"-"`
-	XXX_sizecache              int32    `json:"-"`
+	InvoluntaryContextSwitches *uint64 `protobuf:"varint,10,opt,name=involuntary_context_switches,json=involuntaryContextSwitches" json:"involuntary_context_switches,omitempty"`
 }
 
-func (m *ProcessResourceInfo) Reset()         { *m = ProcessResourceInfo{} }
-func (m *ProcessResourceInfo) String() string { return proto.CompactTextString(m) }
-func (*ProcessResourceInfo) ProtoMessage()    {}
+func (x *ProcessResourceInfo) Reset() {
+	*x = ProcessResourceInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ProcessResourceInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ProcessResourceInfo) ProtoMessage() {}
+
+func (x *ProcessResourceInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[4]
+	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 ProcessResourceInfo.ProtoReflect.Descriptor instead.
 func (*ProcessResourceInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{4}
+	return file_metrics_proto_rawDescGZIP(), []int{4}
 }
 
-func (m *ProcessResourceInfo) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ProcessResourceInfo.Unmarshal(m, b)
-}
-func (m *ProcessResourceInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ProcessResourceInfo.Marshal(b, m, deterministic)
-}
-func (m *ProcessResourceInfo) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ProcessResourceInfo.Merge(m, src)
-}
-func (m *ProcessResourceInfo) XXX_Size() int {
-	return xxx_messageInfo_ProcessResourceInfo.Size(m)
-}
-func (m *ProcessResourceInfo) XXX_DiscardUnknown() {
-	xxx_messageInfo_ProcessResourceInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ProcessResourceInfo proto.InternalMessageInfo
-
-func (m *ProcessResourceInfo) GetName() string {
-	if m != nil && m.Name != nil {
-		return *m.Name
+func (x *ProcessResourceInfo) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
 	}
 	return ""
 }
 
-func (m *ProcessResourceInfo) GetUserTimeMicros() uint64 {
-	if m != nil && m.UserTimeMicros != nil {
-		return *m.UserTimeMicros
+func (x *ProcessResourceInfo) GetUserTimeMicros() uint64 {
+	if x != nil && x.UserTimeMicros != nil {
+		return *x.UserTimeMicros
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetSystemTimeMicros() uint64 {
-	if m != nil && m.SystemTimeMicros != nil {
-		return *m.SystemTimeMicros
+func (x *ProcessResourceInfo) GetSystemTimeMicros() uint64 {
+	if x != nil && x.SystemTimeMicros != nil {
+		return *x.SystemTimeMicros
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetMaxRssKb() uint64 {
-	if m != nil && m.MaxRssKb != nil {
-		return *m.MaxRssKb
+func (x *ProcessResourceInfo) GetMaxRssKb() uint64 {
+	if x != nil && x.MaxRssKb != nil {
+		return *x.MaxRssKb
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetMinorPageFaults() uint64 {
-	if m != nil && m.MinorPageFaults != nil {
-		return *m.MinorPageFaults
+func (x *ProcessResourceInfo) GetMinorPageFaults() uint64 {
+	if x != nil && x.MinorPageFaults != nil {
+		return *x.MinorPageFaults
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetMajorPageFaults() uint64 {
-	if m != nil && m.MajorPageFaults != nil {
-		return *m.MajorPageFaults
+func (x *ProcessResourceInfo) GetMajorPageFaults() uint64 {
+	if x != nil && x.MajorPageFaults != nil {
+		return *x.MajorPageFaults
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetIoInputKb() uint64 {
-	if m != nil && m.IoInputKb != nil {
-		return *m.IoInputKb
+func (x *ProcessResourceInfo) GetIoInputKb() uint64 {
+	if x != nil && x.IoInputKb != nil {
+		return *x.IoInputKb
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetIoOutputKb() uint64 {
-	if m != nil && m.IoOutputKb != nil {
-		return *m.IoOutputKb
+func (x *ProcessResourceInfo) GetIoOutputKb() uint64 {
+	if x != nil && x.IoOutputKb != nil {
+		return *x.IoOutputKb
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetVoluntaryContextSwitches() uint64 {
-	if m != nil && m.VoluntaryContextSwitches != nil {
-		return *m.VoluntaryContextSwitches
+func (x *ProcessResourceInfo) GetVoluntaryContextSwitches() uint64 {
+	if x != nil && x.VoluntaryContextSwitches != nil {
+		return *x.VoluntaryContextSwitches
 	}
 	return 0
 }
 
-func (m *ProcessResourceInfo) GetInvoluntaryContextSwitches() uint64 {
-	if m != nil && m.InvoluntaryContextSwitches != nil {
-		return *m.InvoluntaryContextSwitches
+func (x *ProcessResourceInfo) GetInvoluntaryContextSwitches() uint64 {
+	if x != nil && x.InvoluntaryContextSwitches != nil {
+		return *x.InvoluntaryContextSwitches
 	}
 	return 0
 }
 
 type ModuleTypeInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The build system, eg. 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.
 	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"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	NumOfModules *uint32 `protobuf:"varint,3,opt,name=num_of_modules,json=numOfModules" json:"num_of_modules,omitempty"`
 }
 
-func (m *ModuleTypeInfo) Reset()         { *m = ModuleTypeInfo{} }
-func (m *ModuleTypeInfo) String() string { return proto.CompactTextString(m) }
-func (*ModuleTypeInfo) ProtoMessage()    {}
+// Default values for ModuleTypeInfo fields.
+const (
+	Default_ModuleTypeInfo_BuildSystem = ModuleTypeInfo_UNKNOWN
+)
+
+func (x *ModuleTypeInfo) Reset() {
+	*x = ModuleTypeInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ModuleTypeInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ModuleTypeInfo) ProtoMessage() {}
+
+func (x *ModuleTypeInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[5]
+	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 ModuleTypeInfo.ProtoReflect.Descriptor instead.
 func (*ModuleTypeInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{5}
+	return file_metrics_proto_rawDescGZIP(), []int{5}
 }
 
-func (m *ModuleTypeInfo) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_ModuleTypeInfo.Unmarshal(m, b)
-}
-func (m *ModuleTypeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_ModuleTypeInfo.Marshal(b, m, deterministic)
-}
-func (m *ModuleTypeInfo) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_ModuleTypeInfo.Merge(m, src)
-}
-func (m *ModuleTypeInfo) XXX_Size() int {
-	return xxx_messageInfo_ModuleTypeInfo.Size(m)
-}
-func (m *ModuleTypeInfo) XXX_DiscardUnknown() {
-	xxx_messageInfo_ModuleTypeInfo.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ModuleTypeInfo proto.InternalMessageInfo
-
-const Default_ModuleTypeInfo_BuildSystem ModuleTypeInfo_BuildSystem = ModuleTypeInfo_UNKNOWN
-
-func (m *ModuleTypeInfo) GetBuildSystem() ModuleTypeInfo_BuildSystem {
-	if m != nil && m.BuildSystem != nil {
-		return *m.BuildSystem
+func (x *ModuleTypeInfo) GetBuildSystem() ModuleTypeInfo_BuildSystem {
+	if x != nil && x.BuildSystem != nil {
+		return *x.BuildSystem
 	}
 	return Default_ModuleTypeInfo_BuildSystem
 }
 
-func (m *ModuleTypeInfo) GetModuleType() string {
-	if m != nil && m.ModuleType != nil {
-		return *m.ModuleType
+func (x *ModuleTypeInfo) GetModuleType() string {
+	if x != nil && x.ModuleType != nil {
+		return *x.ModuleType
 	}
 	return ""
 }
 
-func (m *ModuleTypeInfo) GetNumOfModules() uint32 {
-	if m != nil && m.NumOfModules != nil {
-		return *m.NumOfModules
+func (x *ModuleTypeInfo) GetNumOfModules() uint32 {
+	if x != nil && x.NumOfModules != nil {
+		return *x.NumOfModules
 	}
 	return 0
 }
 
 type CriticalUserJourneyMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The name of a critical user journey test.
 	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
 	// The metrics produced when running the critical user journey test.
-	Metrics              *MetricsBase `protobuf:"bytes,2,opt,name=metrics" json:"metrics,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
-	XXX_unrecognized     []byte       `json:"-"`
-	XXX_sizecache        int32        `json:"-"`
+	Metrics *MetricsBase `protobuf:"bytes,2,opt,name=metrics" json:"metrics,omitempty"`
 }
 
-func (m *CriticalUserJourneyMetrics) Reset()         { *m = CriticalUserJourneyMetrics{} }
-func (m *CriticalUserJourneyMetrics) String() string { return proto.CompactTextString(m) }
-func (*CriticalUserJourneyMetrics) ProtoMessage()    {}
+func (x *CriticalUserJourneyMetrics) Reset() {
+	*x = CriticalUserJourneyMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CriticalUserJourneyMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CriticalUserJourneyMetrics) ProtoMessage() {}
+
+func (x *CriticalUserJourneyMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[6]
+	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 CriticalUserJourneyMetrics.ProtoReflect.Descriptor instead.
 func (*CriticalUserJourneyMetrics) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{6}
+	return file_metrics_proto_rawDescGZIP(), []int{6}
 }
 
-func (m *CriticalUserJourneyMetrics) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_CriticalUserJourneyMetrics.Unmarshal(m, b)
-}
-func (m *CriticalUserJourneyMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_CriticalUserJourneyMetrics.Marshal(b, m, deterministic)
-}
-func (m *CriticalUserJourneyMetrics) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_CriticalUserJourneyMetrics.Merge(m, src)
-}
-func (m *CriticalUserJourneyMetrics) XXX_Size() int {
-	return xxx_messageInfo_CriticalUserJourneyMetrics.Size(m)
-}
-func (m *CriticalUserJourneyMetrics) XXX_DiscardUnknown() {
-	xxx_messageInfo_CriticalUserJourneyMetrics.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_CriticalUserJourneyMetrics proto.InternalMessageInfo
-
-func (m *CriticalUserJourneyMetrics) GetName() string {
-	if m != nil && m.Name != nil {
-		return *m.Name
+func (x *CriticalUserJourneyMetrics) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
 	}
 	return ""
 }
 
-func (m *CriticalUserJourneyMetrics) GetMetrics() *MetricsBase {
-	if m != nil {
-		return m.Metrics
+func (x *CriticalUserJourneyMetrics) GetMetrics() *MetricsBase {
+	if x != nil {
+		return x.Metrics
 	}
 	return nil
 }
 
 type CriticalUserJourneysMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// A set of metrics from a run of the critical user journey tests.
-	Cujs                 []*CriticalUserJourneyMetrics `protobuf:"bytes,1,rep,name=cujs" json:"cujs,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}                      `json:"-"`
-	XXX_unrecognized     []byte                        `json:"-"`
-	XXX_sizecache        int32                         `json:"-"`
+	Cujs []*CriticalUserJourneyMetrics `protobuf:"bytes,1,rep,name=cujs" json:"cujs,omitempty"`
 }
 
-func (m *CriticalUserJourneysMetrics) Reset()         { *m = CriticalUserJourneysMetrics{} }
-func (m *CriticalUserJourneysMetrics) String() string { return proto.CompactTextString(m) }
-func (*CriticalUserJourneysMetrics) ProtoMessage()    {}
+func (x *CriticalUserJourneysMetrics) Reset() {
+	*x = CriticalUserJourneysMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CriticalUserJourneysMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CriticalUserJourneysMetrics) ProtoMessage() {}
+
+func (x *CriticalUserJourneysMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[7]
+	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 CriticalUserJourneysMetrics.ProtoReflect.Descriptor instead.
 func (*CriticalUserJourneysMetrics) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{7}
+	return file_metrics_proto_rawDescGZIP(), []int{7}
 }
 
-func (m *CriticalUserJourneysMetrics) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_CriticalUserJourneysMetrics.Unmarshal(m, b)
-}
-func (m *CriticalUserJourneysMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_CriticalUserJourneysMetrics.Marshal(b, m, deterministic)
-}
-func (m *CriticalUserJourneysMetrics) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_CriticalUserJourneysMetrics.Merge(m, src)
-}
-func (m *CriticalUserJourneysMetrics) XXX_Size() int {
-	return xxx_messageInfo_CriticalUserJourneysMetrics.Size(m)
-}
-func (m *CriticalUserJourneysMetrics) XXX_DiscardUnknown() {
-	xxx_messageInfo_CriticalUserJourneysMetrics.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_CriticalUserJourneysMetrics proto.InternalMessageInfo
-
-func (m *CriticalUserJourneysMetrics) GetCujs() []*CriticalUserJourneyMetrics {
-	if m != nil {
-		return m.Cujs
+func (x *CriticalUserJourneysMetrics) GetCujs() []*CriticalUserJourneyMetrics {
+	if x != nil {
+		return x.Cujs
 	}
 	return nil
 }
 
 type SoongBuildMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The number of modules handled by soong_build.
 	Modules *uint32 `protobuf:"varint,1,opt,name=modules" json:"modules,omitempty"`
 	// The total number of variants handled by soong_build.
@@ -904,178 +1139,594 @@
 	// The total size of allocations in soong_build in bytes.
 	TotalAllocSize *uint64 `protobuf:"varint,4,opt,name=total_alloc_size,json=totalAllocSize" json:"total_alloc_size,omitempty"`
 	// The approximate maximum size of the heap in soong_build in bytes.
-	MaxHeapSize          *uint64  `protobuf:"varint,5,opt,name=max_heap_size,json=maxHeapSize" json:"max_heap_size,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	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"`
 }
 
-func (m *SoongBuildMetrics) Reset()         { *m = SoongBuildMetrics{} }
-func (m *SoongBuildMetrics) String() string { return proto.CompactTextString(m) }
-func (*SoongBuildMetrics) ProtoMessage()    {}
+func (x *SoongBuildMetrics) Reset() {
+	*x = SoongBuildMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SoongBuildMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SoongBuildMetrics) ProtoMessage() {}
+
+func (x *SoongBuildMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[8]
+	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 SoongBuildMetrics.ProtoReflect.Descriptor instead.
 func (*SoongBuildMetrics) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{8}
+	return file_metrics_proto_rawDescGZIP(), []int{8}
 }
 
-func (m *SoongBuildMetrics) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_SoongBuildMetrics.Unmarshal(m, b)
-}
-func (m *SoongBuildMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_SoongBuildMetrics.Marshal(b, m, deterministic)
-}
-func (m *SoongBuildMetrics) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_SoongBuildMetrics.Merge(m, src)
-}
-func (m *SoongBuildMetrics) XXX_Size() int {
-	return xxx_messageInfo_SoongBuildMetrics.Size(m)
-}
-func (m *SoongBuildMetrics) XXX_DiscardUnknown() {
-	xxx_messageInfo_SoongBuildMetrics.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_SoongBuildMetrics proto.InternalMessageInfo
-
-func (m *SoongBuildMetrics) GetModules() uint32 {
-	if m != nil && m.Modules != nil {
-		return *m.Modules
+func (x *SoongBuildMetrics) GetModules() uint32 {
+	if x != nil && x.Modules != nil {
+		return *x.Modules
 	}
 	return 0
 }
 
-func (m *SoongBuildMetrics) GetVariants() uint32 {
-	if m != nil && m.Variants != nil {
-		return *m.Variants
+func (x *SoongBuildMetrics) GetVariants() uint32 {
+	if x != nil && x.Variants != nil {
+		return *x.Variants
 	}
 	return 0
 }
 
-func (m *SoongBuildMetrics) GetTotalAllocCount() uint64 {
-	if m != nil && m.TotalAllocCount != nil {
-		return *m.TotalAllocCount
+func (x *SoongBuildMetrics) GetTotalAllocCount() uint64 {
+	if x != nil && x.TotalAllocCount != nil {
+		return *x.TotalAllocCount
 	}
 	return 0
 }
 
-func (m *SoongBuildMetrics) GetTotalAllocSize() uint64 {
-	if m != nil && m.TotalAllocSize != nil {
-		return *m.TotalAllocSize
+func (x *SoongBuildMetrics) GetTotalAllocSize() uint64 {
+	if x != nil && x.TotalAllocSize != nil {
+		return *x.TotalAllocSize
 	}
 	return 0
 }
 
-func (m *SoongBuildMetrics) GetMaxHeapSize() uint64 {
-	if m != nil && m.MaxHeapSize != nil {
-		return *m.MaxHeapSize
+func (x *SoongBuildMetrics) GetMaxHeapSize() uint64 {
+	if x != nil && x.MaxHeapSize != nil {
+		return *x.MaxHeapSize
 	}
 	return 0
 }
 
-func init() {
-	proto.RegisterEnum("soong_build_metrics.MetricsBase_BuildVariant", MetricsBase_BuildVariant_name, MetricsBase_BuildVariant_value)
-	proto.RegisterEnum("soong_build_metrics.MetricsBase_Arch", MetricsBase_Arch_name, MetricsBase_Arch_value)
-	proto.RegisterEnum("soong_build_metrics.ModuleTypeInfo_BuildSystem", ModuleTypeInfo_BuildSystem_name, ModuleTypeInfo_BuildSystem_value)
-	proto.RegisterType((*MetricsBase)(nil), "soong_build_metrics.MetricsBase")
-	proto.RegisterType((*BuildConfig)(nil), "soong_build_metrics.BuildConfig")
-	proto.RegisterType((*SystemResourceInfo)(nil), "soong_build_metrics.SystemResourceInfo")
-	proto.RegisterType((*PerfInfo)(nil), "soong_build_metrics.PerfInfo")
-	proto.RegisterType((*ProcessResourceInfo)(nil), "soong_build_metrics.ProcessResourceInfo")
-	proto.RegisterType((*ModuleTypeInfo)(nil), "soong_build_metrics.ModuleTypeInfo")
-	proto.RegisterType((*CriticalUserJourneyMetrics)(nil), "soong_build_metrics.CriticalUserJourneyMetrics")
-	proto.RegisterType((*CriticalUserJourneysMetrics)(nil), "soong_build_metrics.CriticalUserJourneysMetrics")
-	proto.RegisterType((*SoongBuildMetrics)(nil), "soong_build_metrics.SoongBuildMetrics")
+func (x *SoongBuildMetrics) GetEvents() []*PerfInfo {
+	if x != nil {
+		return x.Events
+	}
+	return nil
 }
 
-func init() {
-	proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72)
+type ExpConfigFetcher struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The result of the call to expconfigfetcher
+	// NO_CONFIG - Not part of experiment
+	// CONFIG - Part of experiment, config copied successfully
+	// ERROR - expconfigfetcher failed
+	Status *ExpConfigFetcher_ConfigStatus `protobuf:"varint,1,opt,name=status,enum=soong_build_metrics.ExpConfigFetcher_ConfigStatus" json:"status,omitempty"`
+	// The output config filename
+	Filename *string `protobuf:"bytes,2,opt,name=filename" json:"filename,omitempty"`
+	// Time, in microseconds, taken by the expconfigfetcher
+	Micros *uint64 `protobuf:"varint,3,opt,name=micros" json:"micros,omitempty"`
 }
 
-var fileDescriptor_6039342a2ba47b72 = []byte{
-	// 1380 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xef, 0x52, 0x1b, 0x37,
-	0x10, 0x8f, 0xc1, 0x60, 0x7b, 0xfd, 0x07, 0x23, 0xa0, 0x5c, 0x48, 0xd2, 0x52, 0xb7, 0x49, 0x99,
-	0x4e, 0x43, 0x32, 0x34, 0xc3, 0x64, 0x98, 0x4c, 0xa7, 0xe0, 0xd0, 0x34, 0x65, 0xc0, 0x8c, 0x08,
-	0x69, 0xda, 0x7e, 0x50, 0xe5, 0xb3, 0x0c, 0x97, 0xdc, 0x9d, 0x6e, 0x24, 0x1d, 0xc5, 0x79, 0xb3,
-	0x7e, 0xee, 0x4b, 0xf4, 0x05, 0xfa, 0x04, 0x7d, 0x81, 0x8e, 0x56, 0x77, 0xe6, 0x20, 0x6e, 0xc2,
-	0xe4, 0xdb, 0xe9, 0xb7, 0xbf, 0xdf, 0x6a, 0xb5, 0xd2, 0xee, 0xda, 0xd0, 0x8c, 0x84, 0x51, 0x81,
-	0xaf, 0xd7, 0x13, 0x25, 0x8d, 0x24, 0x0b, 0x5a, 0xca, 0xf8, 0x84, 0xf5, 0xd3, 0x20, 0x1c, 0xb0,
-	0xcc, 0xd4, 0xf9, 0xbb, 0x01, 0xf5, 0x7d, 0xf7, 0xbd, 0xc3, 0xb5, 0x20, 0x0f, 0x61, 0xd1, 0x11,
-	0x06, 0xdc, 0x08, 0x66, 0x82, 0x48, 0x68, 0xc3, 0xa3, 0xc4, 0x2b, 0xad, 0x96, 0xd6, 0xa6, 0x29,
-	0x41, 0xdb, 0x53, 0x6e, 0xc4, 0x8b, 0xdc, 0x42, 0x6e, 0x42, 0xd5, 0x29, 0x82, 0x81, 0x37, 0xb5,
-	0x5a, 0x5a, 0xab, 0xd1, 0x0a, 0xae, 0x9f, 0x0f, 0xc8, 0x16, 0xdc, 0x4c, 0x42, 0x6e, 0x86, 0x52,
-	0x45, 0xec, 0x4c, 0x28, 0x1d, 0xc8, 0x98, 0xf9, 0x72, 0x20, 0x62, 0x1e, 0x09, 0x6f, 0x1a, 0xb9,
-	0xcb, 0x39, 0xe1, 0xa5, 0xb3, 0x77, 0x33, 0x33, 0xb9, 0x0b, 0x2d, 0xc3, 0xd5, 0x89, 0x30, 0x2c,
-	0x51, 0x72, 0x90, 0xfa, 0xc6, 0x2b, 0xa3, 0xa0, 0xe9, 0xd0, 0x43, 0x07, 0x92, 0x01, 0x2c, 0x66,
-	0x34, 0x17, 0xc4, 0x19, 0x57, 0x01, 0x8f, 0x8d, 0x37, 0xb3, 0x5a, 0x5a, 0x6b, 0x6d, 0xdc, 0x5f,
-	0x9f, 0x70, 0xe6, 0xf5, 0xc2, 0x79, 0xd7, 0x77, 0xac, 0xe5, 0xa5, 0x13, 0x6d, 0x4d, 0xef, 0x1e,
-	0x3c, 0xa3, 0xc4, 0xf9, 0x2b, 0x1a, 0x48, 0x0f, 0xea, 0xd9, 0x2e, 0x5c, 0xf9, 0xa7, 0xde, 0x2c,
-	0x3a, 0xbf, 0xfb, 0x41, 0xe7, 0xdb, 0xca, 0x3f, 0xdd, 0xaa, 0x1c, 0x1f, 0xec, 0x1d, 0xf4, 0x7e,
-	0x3e, 0xa0, 0xe0, 0x5c, 0x58, 0x90, 0xac, 0xc3, 0x42, 0xc1, 0xe1, 0x38, 0xea, 0x0a, 0x1e, 0x71,
-	0xfe, 0x82, 0x98, 0x07, 0xf0, 0x0d, 0x64, 0x61, 0x31, 0x3f, 0x49, 0xc7, 0xf4, 0x2a, 0xd2, 0xdb,
-	0xce, 0xd2, 0x4d, 0xd2, 0x9c, 0xbd, 0x07, 0xb5, 0x53, 0xa9, 0xb3, 0x60, 0x6b, 0x1f, 0x15, 0x6c,
-	0xd5, 0x3a, 0xc0, 0x50, 0x29, 0x34, 0xd1, 0xd9, 0x46, 0x3c, 0x70, 0x0e, 0xe1, 0xa3, 0x1c, 0xd6,
-	0xad, 0x93, 0x8d, 0x78, 0x80, 0x3e, 0x97, 0xa1, 0x82, 0x3e, 0xa5, 0xf6, 0xea, 0x78, 0x86, 0x59,
-	0xbb, 0xec, 0x69, 0xd2, 0xc9, 0x36, 0x93, 0x9a, 0x89, 0x73, 0xa3, 0xb8, 0xd7, 0x40, 0x73, 0xdd,
-	0x99, 0x77, 0x2d, 0x34, 0xe6, 0xf8, 0x4a, 0x6a, 0x6d, 0x5d, 0x34, 0x2f, 0x38, 0x5d, 0x8b, 0xf5,
-	0x34, 0xb9, 0x07, 0x73, 0x05, 0x0e, 0x86, 0xdd, 0x72, 0xcf, 0x67, 0xcc, 0xc2, 0x40, 0xee, 0xc3,
-	0x42, 0x81, 0x37, 0x3e, 0xe2, 0x9c, 0x4b, 0xec, 0x98, 0x5b, 0x88, 0x5b, 0xa6, 0x86, 0x0d, 0x02,
-	0xe5, 0xb5, 0x5d, 0xdc, 0x32, 0x35, 0x4f, 0x03, 0x45, 0xbe, 0x83, 0xba, 0x16, 0x26, 0x4d, 0x98,
-	0x91, 0x32, 0xd4, 0xde, 0xfc, 0xea, 0xf4, 0x5a, 0x7d, 0xe3, 0xce, 0xc4, 0x14, 0x1d, 0x0a, 0x35,
-	0x7c, 0x1e, 0x0f, 0x25, 0x05, 0x54, 0xbc, 0xb0, 0x02, 0xb2, 0x05, 0xb5, 0x37, 0xdc, 0x04, 0x4c,
-	0xa5, 0xb1, 0xf6, 0xc8, 0x75, 0xd4, 0x55, 0xcb, 0xa7, 0x69, 0xac, 0xc9, 0x13, 0x00, 0xc7, 0x44,
-	0xf1, 0xc2, 0x75, 0xc4, 0x35, 0xb4, 0xe6, 0xea, 0x38, 0x88, 0x5f, 0x73, 0xa7, 0x5e, 0xbc, 0x96,
-	0x1a, 0x05, 0xa8, 0xfe, 0x16, 0x66, 0x8c, 0x34, 0x3c, 0xf4, 0x96, 0x56, 0x4b, 0x1f, 0x16, 0x3a,
-	0x2e, 0x79, 0x09, 0x93, 0x5a, 0x91, 0xf7, 0x09, 0xba, 0xb8, 0x37, 0xd1, 0xc5, 0x91, 0xc5, 0xb0,
-	0x24, 0xb3, 0x17, 0x46, 0xe7, 0xf5, 0x55, 0x88, 0x74, 0xa1, 0xe1, 0x54, 0xbe, 0x8c, 0x87, 0xc1,
-	0x89, 0xb7, 0x8c, 0x0e, 0x57, 0x27, 0x3a, 0x44, 0x61, 0x17, 0x79, 0xb4, 0xde, 0xbf, 0x58, 0x90,
-	0x15, 0xc0, 0xa7, 0x8f, 0x2d, 0xca, 0xc3, 0x3b, 0x1e, 0xaf, 0xc9, 0x2f, 0xb0, 0xa8, 0x47, 0xda,
-	0x88, 0x88, 0x29, 0xa1, 0x65, 0xaa, 0x7c, 0xc1, 0x82, 0x78, 0x28, 0xbd, 0x9b, 0xb8, 0xd1, 0x57,
-	0x93, 0x23, 0x47, 0x01, 0xcd, 0xf8, 0x98, 0x06, 0xa2, 0xdf, 0xc1, 0xc8, 0x17, 0xd0, 0xcc, 0x63,
-	0x8f, 0x22, 0x1e, 0x0f, 0xbc, 0x15, 0xdc, 0xbb, 0x91, 0x85, 0x86, 0x98, 0xbd, 0xab, 0x3e, 0x7f,
-	0x2b, 0x42, 0x77, 0x57, 0xb7, 0xae, 0x75, 0x57, 0x28, 0xb0, 0x77, 0xd5, 0x79, 0x08, 0x8d, 0x4b,
-	0x4d, 0xad, 0x0a, 0xe5, 0xe3, 0xa3, 0x5d, 0xda, 0xbe, 0x41, 0x9a, 0x50, 0xb3, 0x5f, 0x4f, 0x77,
-	0x77, 0x8e, 0x9f, 0xb5, 0x4b, 0xa4, 0x02, 0xb6, 0x11, 0xb6, 0xa7, 0x3a, 0x4f, 0xa0, 0x8c, 0xcf,
-	0xbe, 0x0e, 0x79, 0x19, 0xb7, 0x6f, 0x58, 0xeb, 0x36, 0xdd, 0x6f, 0x97, 0x48, 0x0d, 0x66, 0xb6,
-	0xe9, 0xfe, 0xe6, 0xa3, 0xf6, 0x94, 0xc5, 0x5e, 0x3d, 0xde, 0x6c, 0x4f, 0x13, 0x80, 0xd9, 0x57,
-	0x8f, 0x37, 0xd9, 0xe6, 0xa3, 0x76, 0xb9, 0x73, 0x02, 0xf5, 0x42, 0x96, 0xed, 0x9c, 0x48, 0xb5,
-	0x60, 0x27, 0x32, 0xe2, 0x38, 0x4d, 0xaa, 0xb4, 0x92, 0x6a, 0xf1, 0x4c, 0x46, 0xdc, 0x96, 0x95,
-	0x35, 0xa9, 0xbe, 0xc0, 0x09, 0x52, 0xa5, 0xb3, 0xa9, 0x16, 0xb4, 0x2f, 0xc8, 0x97, 0xd0, 0x1a,
-	0x4a, 0x9b, 0xe6, 0xb1, 0x72, 0x1a, 0xed, 0x0d, 0x44, 0x8f, 0x9d, 0xbc, 0x23, 0x81, 0xbc, 0x9b,
-	0x65, 0xb2, 0x01, 0x4b, 0xf8, 0xdc, 0x58, 0x72, 0x3a, 0xd2, 0x81, 0xcf, 0x43, 0x16, 0x89, 0x48,
-	0xaa, 0x11, 0x6e, 0x5e, 0xa6, 0x0b, 0x68, 0x3c, 0xcc, 0x6c, 0xfb, 0x68, 0xb2, 0x43, 0x87, 0x9f,
-	0xf1, 0x20, 0xe4, 0xfd, 0x50, 0xd8, 0x4e, 0xab, 0x31, 0x9e, 0x19, 0xda, 0x1c, 0xa3, 0xdd, 0x24,
-	0xd5, 0x9d, 0x7f, 0x4b, 0x50, 0xcd, 0x33, 0x4c, 0x08, 0x94, 0x07, 0x42, 0xfb, 0xe8, 0xb6, 0x46,
-	0xf1, 0xdb, 0x62, 0xf8, 0x80, 0xdc, 0x3c, 0xc4, 0x6f, 0x72, 0x07, 0x40, 0x1b, 0xae, 0x0c, 0x0e,
-	0x55, 0x3c, 0x47, 0x99, 0xd6, 0x10, 0xb1, 0xb3, 0x94, 0xdc, 0x82, 0x9a, 0x12, 0x3c, 0x74, 0xd6,
-	0x32, 0x5a, 0xab, 0x16, 0x40, 0xe3, 0xe7, 0x00, 0x2e, 0x78, 0x9b, 0x08, 0x9c, 0x6d, 0xe5, 0x9d,
-	0x29, 0xaf, 0x44, 0x6b, 0x0e, 0x3d, 0xd6, 0x82, 0xfc, 0x0e, 0xcb, 0x89, 0x92, 0xbe, 0xd0, 0x5a,
-	0xe8, 0x2b, 0xcf, 0x73, 0x16, 0x1f, 0xca, 0xda, 0xe4, 0x87, 0xe2, 0x34, 0x97, 0xde, 0xe7, 0xd2,
-	0xd8, 0x51, 0x11, 0xee, 0xfc, 0x39, 0x0d, 0x0b, 0x13, 0xe8, 0xe3, 0xc3, 0x96, 0x0a, 0x87, 0x5d,
-	0x83, 0x76, 0xaa, 0x85, 0xc2, 0xd3, 0xb0, 0x28, 0xb0, 0xed, 0x15, 0x93, 0x51, 0xa6, 0x2d, 0x8b,
-	0xdb, 0x43, 0xed, 0x23, 0x6a, 0x27, 0x5b, 0x56, 0x53, 0x45, 0xae, 0x4b, 0x4f, 0xdb, 0x59, 0x0a,
-	0xec, 0xdb, 0x00, 0x11, 0x3f, 0x67, 0x4a, 0x6b, 0xf6, 0xa6, 0x9f, 0xa7, 0x29, 0xe2, 0xe7, 0x54,
-	0xeb, 0xbd, 0x3e, 0xf9, 0x1a, 0xe6, 0xa3, 0x20, 0x96, 0x8a, 0x25, 0xfc, 0x44, 0xb0, 0x21, 0x4f,
-	0x43, 0xa3, 0x5d, 0xb6, 0xe8, 0x1c, 0x1a, 0x0e, 0xf9, 0x89, 0xf8, 0x01, 0x61, 0xe4, 0xf2, 0xd7,
-	0x57, 0xb8, 0xb3, 0x19, 0xd7, 0x1a, 0x0a, 0xdc, 0x4f, 0xa1, 0x1e, 0x48, 0x16, 0xc4, 0x49, 0x6a,
-	0xec, 0xb6, 0x15, 0x77, 0x77, 0x81, 0x7c, 0x6e, 0x91, 0xbd, 0x3e, 0x59, 0x85, 0x46, 0x20, 0x99,
-	0x4c, 0x4d, 0x46, 0xa8, 0x22, 0x01, 0x02, 0xd9, 0x43, 0x68, 0xaf, 0x4f, 0x9e, 0xc0, 0xca, 0x99,
-	0x0c, 0xd3, 0xd8, 0x70, 0x35, 0xb2, 0xed, 0xc9, 0x88, 0x73, 0xc3, 0xf4, 0x1f, 0x81, 0xf1, 0x4f,
-	0x85, 0xc6, 0x11, 0x5d, 0xa6, 0xde, 0x98, 0xd1, 0x75, 0x84, 0xa3, 0xcc, 0x4e, 0xbe, 0x87, 0xdb,
-	0x41, 0xfc, 0x1e, 0x3d, 0xa0, 0x7e, 0xa5, 0xc0, 0xb9, 0xe2, 0xa1, 0xf3, 0x4f, 0x09, 0x5a, 0xfb,
-	0x72, 0x90, 0x86, 0xe2, 0xc5, 0x28, 0x71, 0xd7, 0xf6, 0x5b, 0xde, 0x2d, 0x5d, 0x92, 0xf1, 0xfa,
-	0x5a, 0x1b, 0x0f, 0x26, 0x8f, 0xf5, 0x4b, 0x52, 0xd7, 0x3c, 0x5d, 0xc9, 0x15, 0x06, 0x7c, 0xff,
-	0x02, 0x25, 0x9f, 0x41, 0x3d, 0x42, 0x0d, 0x33, 0xa3, 0x24, 0xaf, 0x03, 0x88, 0xc6, 0x6e, 0x6c,
-	0x65, 0xc7, 0x69, 0xc4, 0xe4, 0x90, 0x39, 0xd0, 0x5d, 0x79, 0x93, 0x36, 0xe2, 0x34, 0xea, 0x0d,
-	0xdd, 0x7e, 0xba, 0xf3, 0x20, 0x6b, 0x21, 0x99, 0xd7, 0x4b, 0x7d, 0xa8, 0x06, 0x33, 0x47, 0xbd,
-	0xde, 0x81, 0x6d, 0x58, 0x55, 0x28, 0xef, 0x6f, 0xef, 0xed, 0xb6, 0xa7, 0x3a, 0x21, 0xac, 0x74,
-	0x55, 0x60, 0x6c, 0x49, 0x1f, 0x6b, 0xa1, 0x7e, 0x92, 0xa9, 0x8a, 0xc5, 0x28, 0x1f, 0x10, 0x93,
-	0x5e, 0xea, 0x16, 0x54, 0xf2, 0x01, 0x34, 0xf5, 0x9e, 0x79, 0x51, 0xf8, 0x61, 0x43, 0x73, 0x41,
-	0xa7, 0x0f, 0xb7, 0x26, 0xec, 0xa6, 0x2f, 0xe6, 0x51, 0xd9, 0x4f, 0x5f, 0x6b, 0xaf, 0x84, 0xf5,
-	0x37, 0x39, 0xb3, 0xff, 0x1f, 0x2d, 0x45, 0x71, 0xe7, 0xaf, 0x12, 0xcc, 0xbf, 0x33, 0xfd, 0x88,
-	0x07, 0x95, 0x3c, 0x6f, 0x25, 0xcc, 0x5b, 0xbe, 0xb4, 0xf3, 0x2b, 0xfb, 0x79, 0xe8, 0x0e, 0xd4,
-	0xa4, 0xe3, 0xb5, 0x7d, 0xf3, 0xae, 0x25, 0xf2, 0x30, 0x94, 0x3e, 0xf3, 0x65, 0x1a, 0x9b, 0xac,
-	0xd4, 0xe6, 0xd0, 0xb0, 0x6d, 0xf1, 0xae, 0x85, 0x6d, 0x05, 0x17, 0xb9, 0x3a, 0x78, 0x9b, 0xb7,
-	0xa5, 0xd6, 0x05, 0xf5, 0x28, 0x78, 0x2b, 0xec, 0xef, 0x31, 0x5b, 0x93, 0xa7, 0x82, 0x27, 0x8e,
-	0xe6, 0x2a, 0xae, 0x1e, 0xf1, 0xf3, 0x1f, 0x05, 0x4f, 0x2c, 0x67, 0x67, 0xe9, 0xd7, 0x6c, 0xe4,
-	0x67, 0xe7, 0x66, 0xf8, 0x97, 0xe4, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcb, 0xfb, 0x8e, 0xf5,
-	0xa2, 0x0c, 0x00, 0x00,
+func (x *ExpConfigFetcher) Reset() {
+	*x = ExpConfigFetcher{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ExpConfigFetcher) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExpConfigFetcher) ProtoMessage() {}
+
+func (x *ExpConfigFetcher) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[9]
+	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 ExpConfigFetcher.ProtoReflect.Descriptor instead.
+func (*ExpConfigFetcher) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *ExpConfigFetcher) GetStatus() ExpConfigFetcher_ConfigStatus {
+	if x != nil && x.Status != nil {
+		return *x.Status
+	}
+	return ExpConfigFetcher_NO_CONFIG
+}
+
+func (x *ExpConfigFetcher) GetFilename() string {
+	if x != nil && x.Filename != nil {
+		return *x.Filename
+	}
+	return ""
+}
+
+func (x *ExpConfigFetcher) GetMicros() uint64 {
+	if x != nil && x.Micros != nil {
+		return *x.Micros
+	}
+	return 0
+}
+
+var File_metrics_proto protoreflect.FileDescriptor
+
+var file_metrics_proto_rawDesc = []byte{
+	0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+	0x13, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x22, 0xad, 0x0d, 0x0a, 0x0b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x42, 0x61, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61,
+	0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x12, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
+	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49,
+	0x64, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x5f, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x17, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a,
+	0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x72, 0x6f,
+	0x64, 0x75, 0x63, 0x74, 0x12, 0x64, 0x0a, 0x14, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x42, 0x61, 0x73, 0x65, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e,
+	0x74, 0x3a, 0x03, 0x45, 0x4e, 0x47, 0x52, 0x12, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x42, 0x75,
+	0x69, 0x6c, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x4f, 0x0a, 0x0b, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x25, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73,
+	0x65, 0x2e, 0x41, 0x72, 0x63, 0x68, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52,
+	0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x72, 0x63, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x74,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61,
+	0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
+	0x41, 0x72, 0x63, 0x68, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x74,
+	0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e,
+	0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43,
+	0x70, 0x75, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x09, 0x68, 0x6f, 0x73,
+	0x74, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73, 0x65, 0x2e, 0x41,
+	0x72, 0x63, 0x68, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x08, 0x68, 0x6f,
+	0x73, 0x74, 0x41, 0x72, 0x63, 0x68, 0x12, 0x52, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x32,
+	0x6e, 0x64, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e,
+	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73, 0x65, 0x2e,
+	0x41, 0x72, 0x63, 0x68, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x0b, 0x68,
+	0x6f, 0x73, 0x74, 0x32, 0x6e, 0x64, 0x41, 0x72, 0x63, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x6f,
+	0x73, 0x74, 0x5f, 0x6f, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73,
+	0x74, 0x4f, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6f, 0x73, 0x5f, 0x65,
+	0x78, 0x74, 0x72, 0x61, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74,
+	0x4f, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f,
+	0x63, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x6f, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
+	0x68, 0x6f, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x4f, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x68,
+	0x6f, 0x73, 0x74, 0x5f, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x0e,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x41,
+	0x72, 0x63, 0x68, 0x12, 0x2d, 0x0a, 0x13, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x72, 0x6f, 0x73,
+	0x73, 0x5f, 0x32, 0x6e, 0x64, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x10, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x32, 0x6e, 0x64, 0x41, 0x72,
+	0x63, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x10, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x44, 0x69, 0x72, 0x12, 0x3e, 0x0a, 0x0b, 0x73,
+	0x65, 0x74, 0x75, 0x70, 0x5f, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x11, 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,
+	0x0a, 0x73, 0x65, 0x74, 0x75, 0x70, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x12, 0x3a, 0x0a, 0x09, 0x6b,
+	0x61, 0x74, 0x69, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x12, 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, 0x08, 0x6b,
+	0x61, 0x74, 0x69, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x0a, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x13, 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, 0x09, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x0a, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x72,
+	0x75, 0x6e, 0x73, 0x18, 0x14, 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, 0x09, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x52,
+	0x75, 0x6e, 0x73, 0x12, 0x33, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x15, 0x20, 0x01,
+	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, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x56, 0x0a, 0x13, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18,
+	0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x53, 0x6f, 0x6f, 0x6e,
+	0x67, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x11, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x12, 0x43, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x75, 0x69,
+	0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
+	0x65, 0x12, 0x59, 0x0a, 0x14, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x27, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x1a, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
+	0x64, 0x12, 0x3c, 0x0a, 0x0a, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18,
+	0x1b, 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, 0x09, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x12,
+	0x53, 0x0a, 0x12, 0x65, 0x78, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x66, 0x65,
+	0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 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, 0x52, 0x10, 0x65, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74,
+	0x63, 0x68, 0x65, 0x72, 0x22, 0x30, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x56, 0x61, 0x72,
+	0x69, 0x61, 0x6e, 0x74, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x53, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0d,
+	0x0a, 0x09, 0x55, 0x53, 0x45, 0x52, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x07, 0x0a,
+	0x03, 0x45, 0x4e, 0x47, 0x10, 0x02, 0x22, 0x3c, 0x0a, 0x04, 0x41, 0x72, 0x63, 0x68, 0x12, 0x0b,
+	0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41,
+	0x52, 0x4d, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x52, 0x4d, 0x36, 0x34, 0x10, 0x02, 0x12,
+	0x07, 0x0a, 0x03, 0x58, 0x38, 0x36, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x58, 0x38, 0x36, 0x5f,
+	0x36, 0x34, 0x10, 0x04, 0x22, 0xd3, 0x01, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x75, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12,
+	0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x06, 0x75, 0x73, 0x65, 0x52, 0x62, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x63,
+	0x65, 0x5f, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x24,
+	0x0a, 0x0e, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x61, 0x73, 0x5f, 0x6e, 0x69, 0x6e, 0x6a, 0x61,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x41, 0x73, 0x4e,
+	0x69, 0x6e, 0x6a, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x69,
+	0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x0f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64,
+	0x12, 0x18, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x12, 0x53, 0x79,
+	0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f,
+	0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x68, 0x79, 0x73, 0x69, 0x63,
+	0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x4d, 0x65,
+	0x6d, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c,
+	0x65, 0x5f, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x76,
+	0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x70, 0x75, 0x73, 0x22, 0x81, 0x02, 0x0a, 0x08,
+	0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64,
+	0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d,
+	0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a,
+	0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x6d, 0x65,
+	0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02,
+	0x18, 0x01, 0x52, 0x09, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x65, 0x12, 0x60, 0x0a,
+	0x17, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28,
+	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
+	0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22,
+	0xb9, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x75,
+	0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d,
+	0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f,
+	0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x10, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63,
+	0x72, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b,
+	0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b,
+	0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f,
+	0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69,
+	0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a,
+	0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c,
+	0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50,
+	0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f,
+	0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09,
+	0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f,
+	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76,
+	0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
+	0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76,
+	0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
+	0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74,
+	0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x0e,
+	0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x5b,
+	0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53,
+	0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x0b,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x6d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e,
+	0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
+	0x65, 0x73, 0x22, 0x2f, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65,
+	0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09,
+	0x0a, 0x05, 0x53, 0x4f, 0x4f, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41, 0x4b,
+	0x45, 0x10, 0x02, 0x22, 0x6c, 0x0a, 0x1a, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55,
+	0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x22, 0x62, 0x0a, 0x1b, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65,
+	0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x12, 0x43, 0x0a, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f,
+	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65,
+	0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52,
+	0x04, 0x63, 0x75, 0x6a, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x42,
+	0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d,
+	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74,
+	0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74,
+	0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
+	0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x74, 0x6f,
+	0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a,
+	0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69, 0x7a,
+	0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c,
+	0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68,
+	0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
+	0x6d, 0x61, 0x78, 0x48, 0x65, 0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x65,
+	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,
+}
+
+var (
+	file_metrics_proto_rawDescOnce sync.Once
+	file_metrics_proto_rawDescData = file_metrics_proto_rawDesc
+)
+
+func file_metrics_proto_rawDescGZIP() []byte {
+	file_metrics_proto_rawDescOnce.Do(func() {
+		file_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_metrics_proto_rawDescData)
+	})
+	return file_metrics_proto_rawDescData
+}
+
+var file_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
+var file_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
+var file_metrics_proto_goTypes = []interface{}{
+	(MetricsBase_BuildVariant)(0),       // 0: soong_build_metrics.MetricsBase.BuildVariant
+	(MetricsBase_Arch)(0),               // 1: soong_build_metrics.MetricsBase.Arch
+	(ModuleTypeInfo_BuildSystem)(0),     // 2: soong_build_metrics.ModuleTypeInfo.BuildSystem
+	(ExpConfigFetcher_ConfigStatus)(0),  // 3: soong_build_metrics.ExpConfigFetcher.ConfigStatus
+	(*MetricsBase)(nil),                 // 4: soong_build_metrics.MetricsBase
+	(*BuildConfig)(nil),                 // 5: soong_build_metrics.BuildConfig
+	(*SystemResourceInfo)(nil),          // 6: soong_build_metrics.SystemResourceInfo
+	(*PerfInfo)(nil),                    // 7: soong_build_metrics.PerfInfo
+	(*ProcessResourceInfo)(nil),         // 8: soong_build_metrics.ProcessResourceInfo
+	(*ModuleTypeInfo)(nil),              // 9: soong_build_metrics.ModuleTypeInfo
+	(*CriticalUserJourneyMetrics)(nil),  // 10: soong_build_metrics.CriticalUserJourneyMetrics
+	(*CriticalUserJourneysMetrics)(nil), // 11: soong_build_metrics.CriticalUserJourneysMetrics
+	(*SoongBuildMetrics)(nil),           // 12: soong_build_metrics.SoongBuildMetrics
+	(*ExpConfigFetcher)(nil),            // 13: soong_build_metrics.ExpConfigFetcher
+}
+var file_metrics_proto_depIdxs = []int32{
+	0,  // 0: soong_build_metrics.MetricsBase.target_build_variant:type_name -> soong_build_metrics.MetricsBase.BuildVariant
+	1,  // 1: soong_build_metrics.MetricsBase.target_arch:type_name -> soong_build_metrics.MetricsBase.Arch
+	1,  // 2: soong_build_metrics.MetricsBase.host_arch:type_name -> soong_build_metrics.MetricsBase.Arch
+	1,  // 3: soong_build_metrics.MetricsBase.host_2nd_arch:type_name -> soong_build_metrics.MetricsBase.Arch
+	7,  // 4: soong_build_metrics.MetricsBase.setup_tools:type_name -> soong_build_metrics.PerfInfo
+	7,  // 5: soong_build_metrics.MetricsBase.kati_runs:type_name -> soong_build_metrics.PerfInfo
+	7,  // 6: soong_build_metrics.MetricsBase.soong_runs:type_name -> soong_build_metrics.PerfInfo
+	7,  // 7: soong_build_metrics.MetricsBase.ninja_runs:type_name -> soong_build_metrics.PerfInfo
+	7,  // 8: soong_build_metrics.MetricsBase.total:type_name -> soong_build_metrics.PerfInfo
+	12, // 9: soong_build_metrics.MetricsBase.soong_build_metrics:type_name -> soong_build_metrics.SoongBuildMetrics
+	5,  // 10: soong_build_metrics.MetricsBase.build_config:type_name -> soong_build_metrics.BuildConfig
+	6,  // 11: soong_build_metrics.MetricsBase.system_resource_info:type_name -> soong_build_metrics.SystemResourceInfo
+	7,  // 12: soong_build_metrics.MetricsBase.bazel_runs:type_name -> soong_build_metrics.PerfInfo
+	13, // 13: soong_build_metrics.MetricsBase.exp_config_fetcher:type_name -> soong_build_metrics.ExpConfigFetcher
+	8,  // 14: soong_build_metrics.PerfInfo.processes_resource_info:type_name -> soong_build_metrics.ProcessResourceInfo
+	2,  // 15: soong_build_metrics.ModuleTypeInfo.build_system:type_name -> soong_build_metrics.ModuleTypeInfo.BuildSystem
+	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
+}
+
+func init() { file_metrics_proto_init() }
+func file_metrics_proto_init() {
+	if File_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MetricsBase); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SystemResourceInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PerfInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ProcessResourceInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ModuleTypeInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CriticalUserJourneyMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CriticalUserJourneysMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SoongBuildMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExpConfigFetcher); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_metrics_proto_rawDesc,
+			NumEnums:      4,
+			NumMessages:   10,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_metrics_proto_goTypes,
+		DependencyIndexes: file_metrics_proto_depIdxs,
+		EnumInfos:         file_metrics_proto_enumTypes,
+		MessageInfos:      file_metrics_proto_msgTypes,
+	}.Build()
+	File_metrics_proto = out.File
+	file_metrics_proto_rawDesc = nil
+	file_metrics_proto_goTypes = nil
+	file_metrics_proto_depIdxs = nil
 }
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 91d8dd9..814eb67 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -15,7 +15,7 @@
 syntax = "proto2";
 
 package soong_build_metrics;
-option go_package = "soong_metrics_proto";
+option go_package = "android/soong/ui/metrics/metrics_proto";
 
 message MetricsBase {
   // Timestamp generated when the build starts.
@@ -108,6 +108,9 @@
 
   // The metrics for calling Bazel.
   repeated PerfInfo bazel_runs = 27;
+
+  // The metrics of the experiment config fetcher
+  optional ExpConfigFetcher exp_config_fetcher = 28;
 }
 
 message BuildConfig {
@@ -116,6 +119,17 @@
   optional bool use_rbe = 2;
 
   optional bool force_use_goma = 3;
+
+  // Whether the Bazel is acting as the Ninja executor for this build.
+  optional bool bazel_as_ninja = 4;
+
+  // Whether build is occurring in a mixed build mode, where Bazel maintains the
+  // definition and build of some modules in cooperation with Soong.
+  optional bool bazel_mixed_build = 5;
+
+  // These are the targets soong passes to ninja, these targets include special
+  // targets such as droid as well as the regular build targets.
+  repeated string targets = 6;
 }
 
 message SystemResourceInfo {
@@ -128,7 +142,7 @@
 
 message PerfInfo {
   // The description for the phase/action/part while the tool running.
-  optional string desc = 1;
+  optional string description = 1;
 
   // The name for the running phase/action/part.
   optional string name = 2;
@@ -224,4 +238,26 @@
 
   // The approximate maximum size of the heap in soong_build in bytes.
   optional uint64 max_heap_size = 5;
+
+  // Runtime metrics for soong_build execution.
+  repeated PerfInfo events = 6;
+}
+
+message ExpConfigFetcher {
+  enum ConfigStatus {
+    NO_CONFIG = 0;
+    CONFIG = 1;
+    ERROR = 2;
+  }
+  // The result of the call to expconfigfetcher
+  // NO_CONFIG - Not part of experiment
+  // CONFIG - Part of experiment, config copied successfully
+  // ERROR - expconfigfetcher failed
+  optional ConfigStatus status = 1;
+
+  // The output config filename
+  optional string filename = 2;
+
+  // Time, in microseconds, taken by the expconfigfetcher
+  optional uint64 micros = 3;
 }
diff --git a/ui/metrics/mk_metrics_proto/mk_metrics.pb.go b/ui/metrics/mk_metrics_proto/mk_metrics.pb.go
new file mode 100644
index 0000000..32e136a
--- /dev/null
+++ b/ui/metrics/mk_metrics_proto/mk_metrics.pb.go
@@ -0,0 +1,177 @@
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.9.1
+// source: mk_metrics.proto
+
+package mk_metrics_proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Contains metrics pertaining to makefiles.
+type MkMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Total number of mk files present in the workspace.
+	TotalMakefiles uint32 `protobuf:"varint,1,opt,name=totalMakefiles,proto3" json:"totalMakefiles,omitempty"`
+	// Number of top-level mk files present in the workspace.
+	// A mk file is "top level" if there are no mk files in its parent
+	// direrctories.
+	// This value is equivalent to the number of entries in Android.mk.list.
+	ToplevelMakefiles uint32 `protobuf:"varint,2,opt,name=toplevelMakefiles,proto3" json:"toplevelMakefiles,omitempty"`
+}
+
+func (x *MkMetrics) Reset() {
+	*x = MkMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mk_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MkMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MkMetrics) ProtoMessage() {}
+
+func (x *MkMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_mk_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MkMetrics.ProtoReflect.Descriptor instead.
+func (*MkMetrics) Descriptor() ([]byte, []int) {
+	return file_mk_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MkMetrics) GetTotalMakefiles() uint32 {
+	if x != nil {
+		return x.TotalMakefiles
+	}
+	return 0
+}
+
+func (x *MkMetrics) GetToplevelMakefiles() uint32 {
+	if x != nil {
+		return x.ToplevelMakefiles
+	}
+	return 0
+}
+
+var File_mk_metrics_proto protoreflect.FileDescriptor
+
+var file_mk_metrics_proto_rawDesc = []byte{
+	0x0a, 0x10, 0x6d, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x16, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x6d, 0x6b, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x61, 0x0a, 0x09, 0x4d, 0x6b,
+	0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c,
+	0x4d, 0x61, 0x6b, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
+	0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x61, 0x6b, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12,
+	0x2c, 0x0a, 0x11, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x4d, 0x61, 0x6b, 0x65, 0x66,
+	0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x74, 0x6f, 0x70, 0x6c,
+	0x65, 0x76, 0x65, 0x6c, 0x4d, 0x61, 0x6b, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x42, 0x2b, 0x5a,
+	0x29, 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, 0x6b, 0x5f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_mk_metrics_proto_rawDescOnce sync.Once
+	file_mk_metrics_proto_rawDescData = file_mk_metrics_proto_rawDesc
+)
+
+func file_mk_metrics_proto_rawDescGZIP() []byte {
+	file_mk_metrics_proto_rawDescOnce.Do(func() {
+		file_mk_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_mk_metrics_proto_rawDescData)
+	})
+	return file_mk_metrics_proto_rawDescData
+}
+
+var file_mk_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_mk_metrics_proto_goTypes = []interface{}{
+	(*MkMetrics)(nil), // 0: soong_build_mk_metrics.MkMetrics
+}
+var file_mk_metrics_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_mk_metrics_proto_init() }
+func file_mk_metrics_proto_init() {
+	if File_mk_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_mk_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MkMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_mk_metrics_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_mk_metrics_proto_goTypes,
+		DependencyIndexes: file_mk_metrics_proto_depIdxs,
+		MessageInfos:      file_mk_metrics_proto_msgTypes,
+	}.Build()
+	File_mk_metrics_proto = out.File
+	file_mk_metrics_proto_rawDesc = nil
+	file_mk_metrics_proto_goTypes = nil
+	file_mk_metrics_proto_depIdxs = nil
+}
diff --git a/ui/metrics/mk_metrics_proto/mk_metrics.proto b/ui/metrics/mk_metrics_proto/mk_metrics.proto
new file mode 100644
index 0000000..df7bca3
--- /dev/null
+++ b/ui/metrics/mk_metrics_proto/mk_metrics.proto
@@ -0,0 +1,30 @@
+// 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.
+
+syntax = "proto3";
+
+package soong_build_mk_metrics;
+option go_package = "android/soong/ui/metrics/mk_metrics_proto";
+
+// Contains metrics pertaining to makefiles.
+message MkMetrics {
+  // Total number of mk files present in the workspace.
+  uint32 totalMakefiles = 1;
+
+  // Number of top-level mk files present in the workspace.
+  // A mk file is "top level" if there are no mk files in its parent
+  // direrctories.
+  // This value is equivalent to the number of entries in Android.mk.list.
+  uint32 toplevelMakefiles = 2;
+}
diff --git a/ui/metrics/mk_metrics_proto/regen.sh b/ui/metrics/mk_metrics_proto/regen.sh
new file mode 100755
index 0000000..64018d4
--- /dev/null
+++ b/ui/metrics/mk_metrics_proto/regen.sh
@@ -0,0 +1,29 @@
+#!/bin/bash -e
+
+# 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.
+
+# Generates the golang source file of the mk_metrics.proto protobuf file.
+
+function die() { echo "ERROR: $1" >&2; exit 1; }
+
+readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
+
+if ! hash aprotoc &>/dev/null; then
+  die "could not find aprotoc. ${error_msg}"
+fi
+
+if ! aprotoc --go_out=paths=source_relative:. mk_metrics.proto; then
+  die "build failed. ${error_msg}"
+fi
diff --git a/ui/metrics/upload_proto/upload.pb.go b/ui/metrics/upload_proto/upload.pb.go
index 614d4c7..9c8fb06 100644
--- a/ui/metrics/upload_proto/upload.pb.go
+++ b/ui/metrics/upload_proto/upload.pb.go
@@ -1,26 +1,44 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: upload.proto
 
-package soong_metrics_upload_proto
+package upload_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type Upload struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// The timestamp in milliseconds that the build was created.
 	CreationTimestampMs *uint64 `protobuf:"varint,1,opt,name=creation_timestamp_ms,json=creationTimestampMs" json:"creation_timestamp_ms,omitempty"`
 	// The timestamp in milliseconds when the build was completed.
@@ -33,102 +51,169 @@
 	MetricsFiles []string `protobuf:"bytes,5,rep,name=metrics_files,json=metricsFiles" json:"metrics_files,omitempty"`
 	// A list of directories to delete after the copy of metrics files
 	// is completed for uploading.
-	DirectoriesToDelete  []string `protobuf:"bytes,6,rep,name=directories_to_delete,json=directoriesToDelete" json:"directories_to_delete,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	DirectoriesToDelete []string `protobuf:"bytes,6,rep,name=directories_to_delete,json=directoriesToDelete" json:"directories_to_delete,omitempty"`
 }
 
-func (m *Upload) Reset()         { *m = Upload{} }
-func (m *Upload) String() string { return proto.CompactTextString(m) }
-func (*Upload) ProtoMessage()    {}
+func (x *Upload) Reset() {
+	*x = Upload{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_upload_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Upload) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Upload) ProtoMessage() {}
+
+func (x *Upload) ProtoReflect() protoreflect.Message {
+	mi := &file_upload_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Upload.ProtoReflect.Descriptor instead.
 func (*Upload) Descriptor() ([]byte, []int) {
-	return fileDescriptor_91b94b655bd2a7e5, []int{0}
+	return file_upload_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *Upload) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Upload.Unmarshal(m, b)
-}
-func (m *Upload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Upload.Marshal(b, m, deterministic)
-}
-func (m *Upload) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Upload.Merge(m, src)
-}
-func (m *Upload) XXX_Size() int {
-	return xxx_messageInfo_Upload.Size(m)
-}
-func (m *Upload) XXX_DiscardUnknown() {
-	xxx_messageInfo_Upload.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Upload proto.InternalMessageInfo
-
-func (m *Upload) GetCreationTimestampMs() uint64 {
-	if m != nil && m.CreationTimestampMs != nil {
-		return *m.CreationTimestampMs
+func (x *Upload) GetCreationTimestampMs() uint64 {
+	if x != nil && x.CreationTimestampMs != nil {
+		return *x.CreationTimestampMs
 	}
 	return 0
 }
 
-func (m *Upload) GetCompletionTimestampMs() uint64 {
-	if m != nil && m.CompletionTimestampMs != nil {
-		return *m.CompletionTimestampMs
+func (x *Upload) GetCompletionTimestampMs() uint64 {
+	if x != nil && x.CompletionTimestampMs != nil {
+		return *x.CompletionTimestampMs
 	}
 	return 0
 }
 
-func (m *Upload) GetBranchName() string {
-	if m != nil && m.BranchName != nil {
-		return *m.BranchName
+func (x *Upload) GetBranchName() string {
+	if x != nil && x.BranchName != nil {
+		return *x.BranchName
 	}
 	return ""
 }
 
-func (m *Upload) GetTargetName() string {
-	if m != nil && m.TargetName != nil {
-		return *m.TargetName
+func (x *Upload) GetTargetName() string {
+	if x != nil && x.TargetName != nil {
+		return *x.TargetName
 	}
 	return ""
 }
 
-func (m *Upload) GetMetricsFiles() []string {
-	if m != nil {
-		return m.MetricsFiles
+func (x *Upload) GetMetricsFiles() []string {
+	if x != nil {
+		return x.MetricsFiles
 	}
 	return nil
 }
 
-func (m *Upload) GetDirectoriesToDelete() []string {
-	if m != nil {
-		return m.DirectoriesToDelete
+func (x *Upload) GetDirectoriesToDelete() []string {
+	if x != nil {
+		return x.DirectoriesToDelete
 	}
 	return nil
 }
 
-func init() {
-	proto.RegisterType((*Upload)(nil), "soong_metrics_upload.Upload")
+var File_upload_proto protoreflect.FileDescriptor
+
+var file_upload_proto_rawDesc = []byte{
+	0x0a, 0x0c, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14,
+	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x75, 0x70,
+	0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8f, 0x02, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12,
+	0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13,
+	0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
+	0x70, 0x4d, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f,
+	0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, 0x73, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x62,
+	0x72, 0x61, 0x6e, 0x63, 0x68, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
+	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a,
+	0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x46, 0x69, 0x6c,
+	0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65,
+	0x73, 0x5f, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x13, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x54, 0x6f,
+	0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x27, 0x5a, 0x25, 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, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
-func init() {
-	proto.RegisterFile("upload.proto", fileDescriptor_91b94b655bd2a7e5)
+var (
+	file_upload_proto_rawDescOnce sync.Once
+	file_upload_proto_rawDescData = file_upload_proto_rawDesc
+)
+
+func file_upload_proto_rawDescGZIP() []byte {
+	file_upload_proto_rawDescOnce.Do(func() {
+		file_upload_proto_rawDescData = protoimpl.X.CompressGZIP(file_upload_proto_rawDescData)
+	})
+	return file_upload_proto_rawDescData
 }
 
-var fileDescriptor_91b94b655bd2a7e5 = []byte{
-	// 230 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4a, 0x04, 0x31,
-	0x10, 0x86, 0xd9, 0xbb, 0xf3, 0xe0, 0xe2, 0xd9, 0xec, 0x79, 0x18, 0x44, 0x70, 0xd1, 0x66, 0x2b,
-	0x0b, 0x0b, 0x1f, 0x40, 0xc4, 0x4e, 0x8b, 0xe5, 0x6c, 0x6c, 0x86, 0x98, 0x1d, 0xd7, 0x40, 0x92,
-	0x09, 0xc9, 0xf8, 0x1c, 0xbe, 0xb2, 0x6c, 0xe2, 0xe2, 0x82, 0x76, 0xc3, 0xff, 0x7d, 0x7f, 0x31,
-	0xbf, 0xd8, 0x7e, 0x06, 0x4b, 0xaa, 0xbf, 0x09, 0x91, 0x98, 0xea, 0xd3, 0x44, 0xe4, 0x07, 0x70,
-	0xc8, 0xd1, 0xe8, 0x04, 0x85, 0x5d, 0x7d, 0x2d, 0xc4, 0xfa, 0x25, 0x9f, 0xf5, 0xad, 0xd8, 0xeb,
-	0x88, 0x8a, 0x0d, 0x79, 0x60, 0xe3, 0x30, 0xb1, 0x72, 0x01, 0x5c, 0x92, 0x55, 0x53, 0xb5, 0xab,
-	0x6e, 0x37, 0xc1, 0xc3, 0xc4, 0x9e, 0x52, 0x7d, 0x27, 0xce, 0x34, 0xb9, 0x60, 0xf1, 0x6f, 0x6b,
-	0x91, 0x5b, 0xfb, 0x5f, 0x3c, 0xef, 0x5d, 0x8a, 0xe3, 0xb7, 0xa8, 0xbc, 0xfe, 0x00, 0xaf, 0x1c,
-	0xca, 0x65, 0x53, 0xb5, 0x9b, 0x4e, 0x94, 0xe8, 0x59, 0x39, 0x1c, 0x05, 0x56, 0x71, 0x40, 0x2e,
-	0xc2, 0xaa, 0x08, 0x25, 0xca, 0xc2, 0xb5, 0x38, 0x99, 0x5e, 0x79, 0x37, 0x16, 0x93, 0x3c, 0x6a,
-	0x96, 0xed, 0xa6, 0xdb, 0xfe, 0x84, 0x8f, 0x63, 0x36, 0xbe, 0xd4, 0x9b, 0x88, 0x9a, 0x29, 0x1a,
-	0x4c, 0xc0, 0x04, 0x3d, 0x5a, 0x64, 0x94, 0xeb, 0x2c, 0xef, 0x66, 0xf0, 0x40, 0x0f, 0x19, 0xdd,
-	0x5f, 0xbc, 0x9e, 0xff, 0xb7, 0x14, 0xe4, 0x15, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x64, 0x04,
-	0xa8, 0xf4, 0x54, 0x01, 0x00, 0x00,
+var file_upload_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_upload_proto_goTypes = []interface{}{
+	(*Upload)(nil), // 0: soong_metrics_upload.Upload
+}
+var file_upload_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_upload_proto_init() }
+func file_upload_proto_init() {
+	if File_upload_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_upload_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Upload); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_upload_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_upload_proto_goTypes,
+		DependencyIndexes: file_upload_proto_depIdxs,
+		MessageInfos:      file_upload_proto_msgTypes,
+	}.Build()
+	File_upload_proto = out.File
+	file_upload_proto_rawDesc = nil
+	file_upload_proto_goTypes = nil
+	file_upload_proto_depIdxs = nil
 }
diff --git a/ui/metrics/upload_proto/upload.proto b/ui/metrics/upload_proto/upload.proto
index bcd0ab2..e621fd1 100644
--- a/ui/metrics/upload_proto/upload.proto
+++ b/ui/metrics/upload_proto/upload.proto
@@ -15,7 +15,7 @@
 syntax = "proto2";
 
 package soong_metrics_upload;
-option go_package = "soong_metrics_upload_proto";
+option go_package = "android/soong/ui/metrics/upload_proto";
 
 message Upload {
   // The timestamp in milliseconds that the build was created.
diff --git a/cmd/host_bionic_inject/Android.bp b/ui/signal/Android.bp
similarity index 73%
copy from cmd/host_bionic_inject/Android.bp
copy to ui/signal/Android.bp
index 16bc179..08933a1 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/ui/signal/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// 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.
@@ -16,9 +16,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+bootstrap_go_package {
+    name: "soong-ui-signal",
+    pkgPath: "android/soong/ui/signal",
+    srcs: [
+        "signal.go",
+    ],
+    deps: [
+        "soong-ui-logger",
+    ],
 }
diff --git a/ui/build/signal.go b/ui/signal/signal.go
similarity index 99%
rename from ui/build/signal.go
rename to ui/signal/signal.go
index 6643e2f..4929a7b 100644
--- a/ui/build/signal.go
+++ b/ui/signal/signal.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package build
+package signal
 
 import (
 	"os"
diff --git a/ui/status/Android.bp b/ui/status/Android.bp
index ac31390..a46a007 100644
--- a/ui/status/Android.bp
+++ b/ui/status/Android.bp
@@ -44,7 +44,10 @@
 bootstrap_go_package {
     name: "soong-ui-status-ninja_frontend",
     pkgPath: "android/soong/ui/status/ninja_frontend",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "ninja_frontend/frontend.pb.go",
     ],
@@ -53,7 +56,10 @@
 bootstrap_go_package {
     name: "soong-ui-status-build_error_proto",
     pkgPath: "android/soong/ui/status/build_error_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "build_error_proto/build_error.pb.go",
     ],
@@ -62,7 +68,10 @@
 bootstrap_go_package {
     name: "soong-ui-status-build_progress_proto",
     pkgPath: "android/soong/ui/status/build_progress_proto",
-    deps: ["golang-protobuf-proto"],
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
     srcs: [
         "build_progress_proto/build_progress.pb.go",
     ],
diff --git a/ui/status/build_error_proto/build_error.pb.go b/ui/status/build_error_proto/build_error.pb.go
index d4d0a6e..22125c2 100644
--- a/ui/status/build_error_proto/build_error.pb.go
+++ b/ui/status/build_error_proto/build_error.pb.go
@@ -1,71 +1,93 @@
+// 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.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: build_error.proto
 
-package soong_build_error_proto
+package build_error_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type BuildError struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// List of error messages of the overall build. The error messages
 	// are not associated with a build action.
 	ErrorMessages []string `protobuf:"bytes,1,rep,name=error_messages,json=errorMessages" json:"error_messages,omitempty"`
 	// List of build action errors.
-	ActionErrors         []*BuildActionError `protobuf:"bytes,2,rep,name=action_errors,json=actionErrors" json:"action_errors,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}            `json:"-"`
-	XXX_unrecognized     []byte              `json:"-"`
-	XXX_sizecache        int32               `json:"-"`
+	ActionErrors []*BuildActionError `protobuf:"bytes,2,rep,name=action_errors,json=actionErrors" json:"action_errors,omitempty"`
 }
 
-func (m *BuildError) Reset()         { *m = BuildError{} }
-func (m *BuildError) String() string { return proto.CompactTextString(m) }
-func (*BuildError) ProtoMessage()    {}
+func (x *BuildError) Reset() {
+	*x = BuildError{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_build_error_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildError) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildError) ProtoMessage() {}
+
+func (x *BuildError) ProtoReflect() protoreflect.Message {
+	mi := &file_build_error_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildError.ProtoReflect.Descriptor instead.
 func (*BuildError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_a2e15b05802a5501, []int{0}
+	return file_build_error_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *BuildError) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BuildError.Unmarshal(m, b)
-}
-func (m *BuildError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BuildError.Marshal(b, m, deterministic)
-}
-func (m *BuildError) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BuildError.Merge(m, src)
-}
-func (m *BuildError) XXX_Size() int {
-	return xxx_messageInfo_BuildError.Size(m)
-}
-func (m *BuildError) XXX_DiscardUnknown() {
-	xxx_messageInfo_BuildError.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BuildError proto.InternalMessageInfo
-
-func (m *BuildError) GetErrorMessages() []string {
-	if m != nil {
-		return m.ErrorMessages
+func (x *BuildError) GetErrorMessages() []string {
+	if x != nil {
+		return x.ErrorMessages
 	}
 	return nil
 }
 
-func (m *BuildError) GetActionErrors() []*BuildActionError {
-	if m != nil {
-		return m.ActionErrors
+func (x *BuildError) GetActionErrors() []*BuildActionError {
+	if x != nil {
+		return x.ActionErrors
 	}
 	return nil
 }
@@ -73,6 +95,10 @@
 // Build is composed of a list of build action. There can be a set of build
 // actions that can failed.
 type BuildActionError struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Description of the command.
 	Description *string `protobuf:"bytes,1,opt,name=description" json:"description,omitempty"`
 	// The command name that raised the error.
@@ -82,94 +108,177 @@
 	// List of artifacts (i.e. files) that was produced by the command.
 	Artifacts []string `protobuf:"bytes,4,rep,name=artifacts" json:"artifacts,omitempty"`
 	// The error string produced by the build action.
-	Error                *string  `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Error *string `protobuf:"bytes,5,opt,name=error" json:"error,omitempty"`
 }
 
-func (m *BuildActionError) Reset()         { *m = BuildActionError{} }
-func (m *BuildActionError) String() string { return proto.CompactTextString(m) }
-func (*BuildActionError) ProtoMessage()    {}
+func (x *BuildActionError) Reset() {
+	*x = BuildActionError{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_build_error_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildActionError) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildActionError) ProtoMessage() {}
+
+func (x *BuildActionError) ProtoReflect() protoreflect.Message {
+	mi := &file_build_error_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildActionError.ProtoReflect.Descriptor instead.
 func (*BuildActionError) Descriptor() ([]byte, []int) {
-	return fileDescriptor_a2e15b05802a5501, []int{1}
+	return file_build_error_proto_rawDescGZIP(), []int{1}
 }
 
-func (m *BuildActionError) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BuildActionError.Unmarshal(m, b)
-}
-func (m *BuildActionError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BuildActionError.Marshal(b, m, deterministic)
-}
-func (m *BuildActionError) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BuildActionError.Merge(m, src)
-}
-func (m *BuildActionError) XXX_Size() int {
-	return xxx_messageInfo_BuildActionError.Size(m)
-}
-func (m *BuildActionError) XXX_DiscardUnknown() {
-	xxx_messageInfo_BuildActionError.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BuildActionError proto.InternalMessageInfo
-
-func (m *BuildActionError) GetDescription() string {
-	if m != nil && m.Description != nil {
-		return *m.Description
+func (x *BuildActionError) GetDescription() string {
+	if x != nil && x.Description != nil {
+		return *x.Description
 	}
 	return ""
 }
 
-func (m *BuildActionError) GetCommand() string {
-	if m != nil && m.Command != nil {
-		return *m.Command
+func (x *BuildActionError) GetCommand() string {
+	if x != nil && x.Command != nil {
+		return *x.Command
 	}
 	return ""
 }
 
-func (m *BuildActionError) GetOutput() string {
-	if m != nil && m.Output != nil {
-		return *m.Output
+func (x *BuildActionError) GetOutput() string {
+	if x != nil && x.Output != nil {
+		return *x.Output
 	}
 	return ""
 }
 
-func (m *BuildActionError) GetArtifacts() []string {
-	if m != nil {
-		return m.Artifacts
+func (x *BuildActionError) GetArtifacts() []string {
+	if x != nil {
+		return x.Artifacts
 	}
 	return nil
 }
 
-func (m *BuildActionError) GetError() string {
-	if m != nil && m.Error != nil {
-		return *m.Error
+func (x *BuildActionError) GetError() string {
+	if x != nil && x.Error != nil {
+		return *x.Error
 	}
 	return ""
 }
 
-func init() {
-	proto.RegisterType((*BuildError)(nil), "soong_build_error.BuildError")
-	proto.RegisterType((*BuildActionError)(nil), "soong_build_error.BuildActionError")
+var File_build_error_proto protoreflect.FileDescriptor
+
+var file_build_error_proto_rawDesc = []byte{
+	0x0a, 0x11, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x11, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x7d, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x45,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x72,
+	0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x48, 0x0a, 0x0d, 0x61,
+	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x41, 0x63, 0x74, 0x69,
+	0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45,
+	0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x10, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x41,
+	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
+	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63,
+	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1c,
+	0x0a, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05,
+	0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72,
+	0x6f, 0x72, 0x42, 0x2b, 0x5a, 0x29, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f,
+	0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
-func init() { proto.RegisterFile("build_error.proto", fileDescriptor_a2e15b05802a5501) }
+var (
+	file_build_error_proto_rawDescOnce sync.Once
+	file_build_error_proto_rawDescData = file_build_error_proto_rawDesc
+)
 
-var fileDescriptor_a2e15b05802a5501 = []byte{
-	// 229 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xc1, 0x4a, 0xc3, 0x40,
-	0x10, 0x86, 0x49, 0x63, 0x95, 0x4c, 0xad, 0xd8, 0x41, 0x74, 0x04, 0x0f, 0xa1, 0x22, 0xe4, 0x94,
-	0x83, 0x6f, 0x60, 0x41, 0xf0, 0xe2, 0x25, 0x47, 0x2f, 0x61, 0xdd, 0xac, 0x65, 0xc1, 0x64, 0xc2,
-	0xce, 0xe6, 0xe8, 0x8b, 0xf8, 0xb4, 0x92, 0x69, 0xa5, 0xa5, 0x39, 0x7e, 0xdf, 0x3f, 0xfb, 0xef,
-	0xce, 0xc2, 0xea, 0x73, 0xf0, 0xdf, 0x4d, 0xed, 0x42, 0xe0, 0x50, 0xf6, 0x81, 0x23, 0xe3, 0x4a,
-	0x98, 0xbb, 0x6d, 0x7d, 0x14, 0xac, 0x7f, 0x00, 0x36, 0x23, 0xbe, 0x8e, 0x84, 0x4f, 0x70, 0xa5,
-	0xba, 0x6e, 0x9d, 0x88, 0xd9, 0x3a, 0xa1, 0x24, 0x4f, 0x8b, 0xac, 0x5a, 0xaa, 0x7d, 0xdf, 0x4b,
-	0x7c, 0x83, 0xa5, 0xb1, 0xd1, 0x73, 0xb7, 0x2b, 0x11, 0x9a, 0xe5, 0x69, 0xb1, 0x78, 0x7e, 0x2c,
-	0x27, 0xfd, 0xa5, 0x96, 0xbf, 0xe8, 0xb0, 0x5e, 0x51, 0x5d, 0x9a, 0x03, 0xc8, 0xfa, 0x37, 0x81,
-	0xeb, 0xd3, 0x11, 0xcc, 0x61, 0xd1, 0x38, 0xb1, 0xc1, 0xf7, 0xa3, 0xa3, 0x24, 0x4f, 0x8a, 0xac,
-	0x3a, 0x56, 0x48, 0x70, 0x61, 0xb9, 0x6d, 0x4d, 0xd7, 0xd0, 0x4c, 0xd3, 0x7f, 0xc4, 0x5b, 0x38,
-	0xe7, 0x21, 0xf6, 0x43, 0xa4, 0x54, 0x83, 0x3d, 0xe1, 0x03, 0x64, 0x26, 0x44, 0xff, 0x65, 0x6c,
-	0x14, 0x3a, 0xd3, 0xa5, 0x0e, 0x02, 0x6f, 0x60, 0xae, 0xcf, 0xa5, 0xb9, 0x1e, 0xda, 0xc1, 0xe6,
-	0xfe, 0xe3, 0x6e, 0xb2, 0x50, 0xad, 0x3f, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x18, 0x9e,
-	0x17, 0x5d, 0x01, 0x00, 0x00,
+func file_build_error_proto_rawDescGZIP() []byte {
+	file_build_error_proto_rawDescOnce.Do(func() {
+		file_build_error_proto_rawDescData = protoimpl.X.CompressGZIP(file_build_error_proto_rawDescData)
+	})
+	return file_build_error_proto_rawDescData
+}
+
+var file_build_error_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_build_error_proto_goTypes = []interface{}{
+	(*BuildError)(nil),       // 0: soong_build_error.BuildError
+	(*BuildActionError)(nil), // 1: soong_build_error.BuildActionError
+}
+var file_build_error_proto_depIdxs = []int32{
+	1, // 0: soong_build_error.BuildError.action_errors:type_name -> soong_build_error.BuildActionError
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_build_error_proto_init() }
+func file_build_error_proto_init() {
+	if File_build_error_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_build_error_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildError); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_build_error_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildActionError); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_build_error_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_build_error_proto_goTypes,
+		DependencyIndexes: file_build_error_proto_depIdxs,
+		MessageInfos:      file_build_error_proto_msgTypes,
+	}.Build()
+	File_build_error_proto = out.File
+	file_build_error_proto_rawDesc = nil
+	file_build_error_proto_goTypes = nil
+	file_build_error_proto_depIdxs = nil
 }
diff --git a/ui/status/build_error_proto/build_error.proto b/ui/status/build_error_proto/build_error.proto
index 9c8470d..ecd6074 100644
--- a/ui/status/build_error_proto/build_error.proto
+++ b/ui/status/build_error_proto/build_error.proto
@@ -15,7 +15,7 @@
 syntax = "proto2";
 
 package soong_build_error;
-option go_package = "soong_build_error_proto";
+option go_package = "android/soong/ui/status/build_error_proto";
 
 message BuildError {
   // List of error messages of the overall build. The error messages
diff --git a/ui/status/build_progress_proto/build_progress.pb.go b/ui/status/build_progress_proto/build_progress.pb.go
index f63c157..e243fe0 100644
--- a/ui/status/build_progress_proto/build_progress.pb.go
+++ b/ui/status/build_progress_proto/build_progress.pb.go
@@ -1,26 +1,44 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: build_progress.proto
 
-package soong_build_progress_proto
+package build_progress_proto
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type BuildProgress struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Total number of actions in a build. The total actions will increase
 	// and might decrease during the course of a build.
 	TotalActions *uint64 `protobuf:"varint,1,opt,name=total_actions,json=totalActions" json:"total_actions,omitempty"`
@@ -34,82 +52,150 @@
 	// build and current_actions + finished_actions <= total_actions.
 	CurrentActions *uint64 `protobuf:"varint,3,opt,name=current_actions,json=currentActions" json:"current_actions,omitempty"`
 	// Total number of actions that reported as a failure.
-	FailedActions        *uint64  `protobuf:"varint,4,opt,name=failed_actions,json=failedActions" json:"failed_actions,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	FailedActions *uint64 `protobuf:"varint,4,opt,name=failed_actions,json=failedActions" json:"failed_actions,omitempty"`
 }
 
-func (m *BuildProgress) Reset()         { *m = BuildProgress{} }
-func (m *BuildProgress) String() string { return proto.CompactTextString(m) }
-func (*BuildProgress) ProtoMessage()    {}
+func (x *BuildProgress) Reset() {
+	*x = BuildProgress{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_build_progress_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *BuildProgress) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BuildProgress) ProtoMessage() {}
+
+func (x *BuildProgress) ProtoReflect() protoreflect.Message {
+	mi := &file_build_progress_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use BuildProgress.ProtoReflect.Descriptor instead.
 func (*BuildProgress) Descriptor() ([]byte, []int) {
-	return fileDescriptor_a8a463f8e30dab2e, []int{0}
+	return file_build_progress_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *BuildProgress) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_BuildProgress.Unmarshal(m, b)
-}
-func (m *BuildProgress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_BuildProgress.Marshal(b, m, deterministic)
-}
-func (m *BuildProgress) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_BuildProgress.Merge(m, src)
-}
-func (m *BuildProgress) XXX_Size() int {
-	return xxx_messageInfo_BuildProgress.Size(m)
-}
-func (m *BuildProgress) XXX_DiscardUnknown() {
-	xxx_messageInfo_BuildProgress.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_BuildProgress proto.InternalMessageInfo
-
-func (m *BuildProgress) GetTotalActions() uint64 {
-	if m != nil && m.TotalActions != nil {
-		return *m.TotalActions
+func (x *BuildProgress) GetTotalActions() uint64 {
+	if x != nil && x.TotalActions != nil {
+		return *x.TotalActions
 	}
 	return 0
 }
 
-func (m *BuildProgress) GetFinishedActions() uint64 {
-	if m != nil && m.FinishedActions != nil {
-		return *m.FinishedActions
+func (x *BuildProgress) GetFinishedActions() uint64 {
+	if x != nil && x.FinishedActions != nil {
+		return *x.FinishedActions
 	}
 	return 0
 }
 
-func (m *BuildProgress) GetCurrentActions() uint64 {
-	if m != nil && m.CurrentActions != nil {
-		return *m.CurrentActions
+func (x *BuildProgress) GetCurrentActions() uint64 {
+	if x != nil && x.CurrentActions != nil {
+		return *x.CurrentActions
 	}
 	return 0
 }
 
-func (m *BuildProgress) GetFailedActions() uint64 {
-	if m != nil && m.FailedActions != nil {
-		return *m.FailedActions
+func (x *BuildProgress) GetFailedActions() uint64 {
+	if x != nil && x.FailedActions != nil {
+		return *x.FailedActions
 	}
 	return 0
 }
 
-func init() {
-	proto.RegisterType((*BuildProgress)(nil), "soong_build_progress.BuildProgress")
+var File_build_progress_proto protoreflect.FileDescriptor
+
+var file_build_progress_proto_rawDesc = []byte{
+	0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0xaf, 0x01, 0x0a,
+	0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x23,
+	0x0a, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x63, 0x74, 0x69,
+	0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f,
+	0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x66,
+	0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27,
+	0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
+	0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x65,
+	0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0d, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x2e,
+	0x5a, 0x2c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
+	0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
+	0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
-func init() { proto.RegisterFile("build_progress.proto", fileDescriptor_a8a463f8e30dab2e) }
+var (
+	file_build_progress_proto_rawDescOnce sync.Once
+	file_build_progress_proto_rawDescData = file_build_progress_proto_rawDesc
+)
 
-var fileDescriptor_a8a463f8e30dab2e = []byte{
-	// 165 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xcd, 0xcc,
-	0x49, 0x89, 0x2f, 0x28, 0xca, 0x4f, 0x2f, 0x4a, 0x2d, 0x2e, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9,
-	0x17, 0x12, 0x29, 0xce, 0xcf, 0xcf, 0x4b, 0x8f, 0x47, 0x95, 0x53, 0x5a, 0xcf, 0xc8, 0xc5, 0xeb,
-	0x04, 0x12, 0x0a, 0x80, 0x8a, 0x08, 0x29, 0x73, 0xf1, 0x96, 0xe4, 0x97, 0x24, 0xe6, 0xc4, 0x27,
-	0x26, 0x97, 0x64, 0xe6, 0xe7, 0x15, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0xb0, 0x04, 0xf1, 0x80, 0x05,
-	0x1d, 0x21, 0x62, 0x42, 0x9a, 0x5c, 0x02, 0x69, 0x99, 0x79, 0x99, 0xc5, 0x19, 0xa9, 0x29, 0x70,
-	0x75, 0x4c, 0x60, 0x75, 0xfc, 0x30, 0x71, 0x98, 0x52, 0x75, 0x2e, 0xfe, 0xe4, 0xd2, 0xa2, 0xa2,
-	0xd4, 0xbc, 0x12, 0xb8, 0x4a, 0x66, 0xb0, 0x4a, 0x3e, 0xa8, 0x30, 0x4c, 0xa1, 0x2a, 0x17, 0x5f,
-	0x5a, 0x62, 0x66, 0x0e, 0x92, 0x89, 0x2c, 0x60, 0x75, 0xbc, 0x10, 0x51, 0xa8, 0x32, 0x27, 0x99,
-	0x28, 0x29, 0x6c, 0x3e, 0x89, 0x07, 0xfb, 0x12, 0x10, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x6e, 0xc1,
-	0xef, 0xfc, 0x00, 0x00, 0x00,
+func file_build_progress_proto_rawDescGZIP() []byte {
+	file_build_progress_proto_rawDescOnce.Do(func() {
+		file_build_progress_proto_rawDescData = protoimpl.X.CompressGZIP(file_build_progress_proto_rawDescData)
+	})
+	return file_build_progress_proto_rawDescData
+}
+
+var file_build_progress_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_build_progress_proto_goTypes = []interface{}{
+	(*BuildProgress)(nil), // 0: soong_build_progress.BuildProgress
+}
+var file_build_progress_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_build_progress_proto_init() }
+func file_build_progress_proto_init() {
+	if File_build_progress_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_build_progress_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*BuildProgress); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_build_progress_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_build_progress_proto_goTypes,
+		DependencyIndexes: file_build_progress_proto_depIdxs,
+		MessageInfos:      file_build_progress_proto_msgTypes,
+	}.Build()
+	File_build_progress_proto = out.File
+	file_build_progress_proto_rawDesc = nil
+	file_build_progress_proto_goTypes = nil
+	file_build_progress_proto_depIdxs = nil
 }
diff --git a/ui/status/build_progress_proto/build_progress.proto b/ui/status/build_progress_proto/build_progress.proto
index d78060a..2672622 100644
--- a/ui/status/build_progress_proto/build_progress.proto
+++ b/ui/status/build_progress_proto/build_progress.proto
@@ -15,7 +15,7 @@
 syntax = "proto2";
 
 package soong_build_progress;
-option go_package = "soong_build_progress_proto";
+option go_package = "android/soong/ui/status/build_progress_proto";
 
 message BuildProgress {
   // Total number of actions in a build. The total actions will increase
diff --git a/ui/status/log.go b/ui/status/log.go
index 4a08acb..14df346 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -23,11 +23,11 @@
 	"os"
 	"strings"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 
 	"android/soong/ui/logger"
-	"android/soong/ui/status/build_error_proto"
-	"android/soong/ui/status/build_progress_proto"
+	soong_build_error_proto "android/soong/ui/status/build_error_proto"
+	soong_build_progress_proto "android/soong/ui/status/build_progress_proto"
 )
 
 type verboseLog struct {
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index 765679f..4d99621 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -19,10 +19,12 @@
 	"fmt"
 	"io"
 	"os"
+	"regexp"
+	"strings"
 	"syscall"
 	"time"
 
-	"github.com/golang/protobuf/proto"
+	"google.golang.org/protobuf/proto"
 
 	"android/soong/ui/logger"
 	"android/soong/ui/status/ninja_frontend"
@@ -158,9 +160,10 @@
 					err = fmt.Errorf("exited with code: %d", exitCode)
 				}
 
+				outputWithErrorHint := errorHintGenerator.GetOutputWithErrorHint(msg.EdgeFinished.GetOutput(), exitCode)
 				n.status.FinishAction(ActionResult{
 					Action: started,
-					Output: msg.EdgeFinished.GetOutput(),
+					Output: outputWithErrorHint,
 					Error:  err,
 					Stats: ActionResultStats{
 						UserTime:                   msg.EdgeFinished.GetUserTime(),
@@ -219,3 +222,53 @@
 
 	return ret, nil
 }
+
+// key is pattern in stdout/stderr
+// value is error hint
+var allErrorHints = map[string]string{
+	"Read-only file system": `\nWrite to a read-only file system detected. Possible fixes include
+1. Generate file directly to out/ which is ReadWrite, #recommend solution
+2. BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST := <my/path/1> <my/path/2> #discouraged, subset of source tree will be RW
+3. BUILD_BROKEN_SRC_DIR_IS_WRITABLE := true #highly discouraged, entire source tree will be RW
+`,
+}
+var errorHintGenerator = *newErrorHintGenerator(allErrorHints)
+
+type ErrorHintGenerator struct {
+	allErrorHints                map[string]string
+	allErrorHintPatternsCompiled *regexp.Regexp
+}
+
+func newErrorHintGenerator(allErrorHints map[string]string) *ErrorHintGenerator {
+	var allErrorHintPatterns []string
+	for errorHintPattern, _ := range allErrorHints {
+		allErrorHintPatterns = append(allErrorHintPatterns, errorHintPattern)
+	}
+	allErrorHintPatternsRegex := strings.Join(allErrorHintPatterns[:], "|")
+	re := regexp.MustCompile(allErrorHintPatternsRegex)
+	return &ErrorHintGenerator{
+		allErrorHints:                allErrorHints,
+		allErrorHintPatternsCompiled: re,
+	}
+}
+
+func (errorHintGenerator *ErrorHintGenerator) GetOutputWithErrorHint(rawOutput string, buildExitCode int) string {
+	if buildExitCode == 0 {
+		return rawOutput
+	}
+	errorHint := errorHintGenerator.getErrorHint(rawOutput)
+	if errorHint == nil {
+		return rawOutput
+	}
+	return rawOutput + *errorHint
+}
+
+// Returns the error hint corresponding to the FIRST match in raw output
+func (errorHintGenerator *ErrorHintGenerator) getErrorHint(rawOutput string) *string {
+	firstMatch := errorHintGenerator.allErrorHintPatternsCompiled.FindString(rawOutput)
+	if _, found := errorHintGenerator.allErrorHints[firstMatch]; found {
+		errorHint := errorHintGenerator.allErrorHints[firstMatch]
+		return &errorHint
+	}
+	return nil
+}
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
index 86e474b..bcadc67 100644
--- a/ui/status/ninja_frontend/frontend.pb.go
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -1,24 +1,38 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.26.0
+// 	protoc        v3.9.1
 // source: frontend.proto
 
 package ninja_frontend
 
 import (
-	fmt "fmt"
-	proto "github.com/golang/protobuf/proto"
-	math "math"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
 )
 
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
 
 type Status_Message_Level int32
 
@@ -29,19 +43,21 @@
 	Status_Message_DEBUG   Status_Message_Level = 3
 )
 
-var Status_Message_Level_name = map[int32]string{
-	0: "INFO",
-	1: "WARNING",
-	2: "ERROR",
-	3: "DEBUG",
-}
-
-var Status_Message_Level_value = map[string]int32{
-	"INFO":    0,
-	"WARNING": 1,
-	"ERROR":   2,
-	"DEBUG":   3,
-}
+// Enum value maps for Status_Message_Level.
+var (
+	Status_Message_Level_name = map[int32]string{
+		0: "INFO",
+		1: "WARNING",
+		2: "ERROR",
+		3: "DEBUG",
+	}
+	Status_Message_Level_value = map[string]int32{
+		"INFO":    0,
+		"WARNING": 1,
+		"ERROR":   2,
+		"DEBUG":   3,
+	}
+)
 
 func (x Status_Message_Level) Enum() *Status_Message_Level {
 	p := new(Status_Message_Level)
@@ -50,222 +66,271 @@
 }
 
 func (x Status_Message_Level) String() string {
-	return proto.EnumName(Status_Message_Level_name, int32(x))
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
 }
 
-func (x *Status_Message_Level) UnmarshalJSON(data []byte) error {
-	value, err := proto.UnmarshalJSONEnum(Status_Message_Level_value, data, "Status_Message_Level")
+func (Status_Message_Level) Descriptor() protoreflect.EnumDescriptor {
+	return file_frontend_proto_enumTypes[0].Descriptor()
+}
+
+func (Status_Message_Level) Type() protoreflect.EnumType {
+	return &file_frontend_proto_enumTypes[0]
+}
+
+func (x Status_Message_Level) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *Status_Message_Level) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
 	if err != nil {
 		return err
 	}
-	*x = Status_Message_Level(value)
+	*x = Status_Message_Level(num)
 	return nil
 }
 
+// Deprecated: Use Status_Message_Level.Descriptor instead.
 func (Status_Message_Level) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 5, 0}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 5, 0}
 }
 
 type Status struct {
-	TotalEdges           *Status_TotalEdges    `protobuf:"bytes,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
-	BuildStarted         *Status_BuildStarted  `protobuf:"bytes,2,opt,name=build_started,json=buildStarted" json:"build_started,omitempty"`
-	BuildFinished        *Status_BuildFinished `protobuf:"bytes,3,opt,name=build_finished,json=buildFinished" json:"build_finished,omitempty"`
-	EdgeStarted          *Status_EdgeStarted   `protobuf:"bytes,4,opt,name=edge_started,json=edgeStarted" json:"edge_started,omitempty"`
-	EdgeFinished         *Status_EdgeFinished  `protobuf:"bytes,5,opt,name=edge_finished,json=edgeFinished" json:"edge_finished,omitempty"`
-	Message              *Status_Message       `protobuf:"bytes,6,opt,name=message" json:"message,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}              `json:"-"`
-	XXX_unrecognized     []byte                `json:"-"`
-	XXX_sizecache        int32                 `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	TotalEdges    *Status_TotalEdges    `protobuf:"bytes,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
+	BuildStarted  *Status_BuildStarted  `protobuf:"bytes,2,opt,name=build_started,json=buildStarted" json:"build_started,omitempty"`
+	BuildFinished *Status_BuildFinished `protobuf:"bytes,3,opt,name=build_finished,json=buildFinished" json:"build_finished,omitempty"`
+	EdgeStarted   *Status_EdgeStarted   `protobuf:"bytes,4,opt,name=edge_started,json=edgeStarted" json:"edge_started,omitempty"`
+	EdgeFinished  *Status_EdgeFinished  `protobuf:"bytes,5,opt,name=edge_finished,json=edgeFinished" json:"edge_finished,omitempty"`
+	Message       *Status_Message       `protobuf:"bytes,6,opt,name=message" json:"message,omitempty"`
 }
 
-func (m *Status) Reset()         { *m = Status{} }
-func (m *Status) String() string { return proto.CompactTextString(m) }
-func (*Status) ProtoMessage()    {}
+func (x *Status) Reset() {
+	*x = Status{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status) ProtoMessage() {}
+
+func (x *Status) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Status.ProtoReflect.Descriptor instead.
 func (*Status) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0}
+	return file_frontend_proto_rawDescGZIP(), []int{0}
 }
 
-func (m *Status) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status.Unmarshal(m, b)
-}
-func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status.Marshal(b, m, deterministic)
-}
-func (m *Status) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status.Merge(m, src)
-}
-func (m *Status) XXX_Size() int {
-	return xxx_messageInfo_Status.Size(m)
-}
-func (m *Status) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status proto.InternalMessageInfo
-
-func (m *Status) GetTotalEdges() *Status_TotalEdges {
-	if m != nil {
-		return m.TotalEdges
+func (x *Status) GetTotalEdges() *Status_TotalEdges {
+	if x != nil {
+		return x.TotalEdges
 	}
 	return nil
 }
 
-func (m *Status) GetBuildStarted() *Status_BuildStarted {
-	if m != nil {
-		return m.BuildStarted
+func (x *Status) GetBuildStarted() *Status_BuildStarted {
+	if x != nil {
+		return x.BuildStarted
 	}
 	return nil
 }
 
-func (m *Status) GetBuildFinished() *Status_BuildFinished {
-	if m != nil {
-		return m.BuildFinished
+func (x *Status) GetBuildFinished() *Status_BuildFinished {
+	if x != nil {
+		return x.BuildFinished
 	}
 	return nil
 }
 
-func (m *Status) GetEdgeStarted() *Status_EdgeStarted {
-	if m != nil {
-		return m.EdgeStarted
+func (x *Status) GetEdgeStarted() *Status_EdgeStarted {
+	if x != nil {
+		return x.EdgeStarted
 	}
 	return nil
 }
 
-func (m *Status) GetEdgeFinished() *Status_EdgeFinished {
-	if m != nil {
-		return m.EdgeFinished
+func (x *Status) GetEdgeFinished() *Status_EdgeFinished {
+	if x != nil {
+		return x.EdgeFinished
 	}
 	return nil
 }
 
-func (m *Status) GetMessage() *Status_Message {
-	if m != nil {
-		return m.Message
+func (x *Status) GetMessage() *Status_Message {
+	if x != nil {
+		return x.Message
 	}
 	return nil
 }
 
 type Status_TotalEdges struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// New value for total edges in the build.
-	TotalEdges           *uint32  `protobuf:"varint,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	TotalEdges *uint32 `protobuf:"varint,1,opt,name=total_edges,json=totalEdges" json:"total_edges,omitempty"`
 }
 
-func (m *Status_TotalEdges) Reset()         { *m = Status_TotalEdges{} }
-func (m *Status_TotalEdges) String() string { return proto.CompactTextString(m) }
-func (*Status_TotalEdges) ProtoMessage()    {}
+func (x *Status_TotalEdges) Reset() {
+	*x = Status_TotalEdges{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_TotalEdges) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_TotalEdges) ProtoMessage() {}
+
+func (x *Status_TotalEdges) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Status_TotalEdges.ProtoReflect.Descriptor instead.
 func (*Status_TotalEdges) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 0}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 0}
 }
 
-func (m *Status_TotalEdges) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_TotalEdges.Unmarshal(m, b)
-}
-func (m *Status_TotalEdges) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_TotalEdges.Marshal(b, m, deterministic)
-}
-func (m *Status_TotalEdges) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_TotalEdges.Merge(m, src)
-}
-func (m *Status_TotalEdges) XXX_Size() int {
-	return xxx_messageInfo_Status_TotalEdges.Size(m)
-}
-func (m *Status_TotalEdges) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_TotalEdges.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_TotalEdges proto.InternalMessageInfo
-
-func (m *Status_TotalEdges) GetTotalEdges() uint32 {
-	if m != nil && m.TotalEdges != nil {
-		return *m.TotalEdges
+func (x *Status_TotalEdges) GetTotalEdges() uint32 {
+	if x != nil && x.TotalEdges != nil {
+		return *x.TotalEdges
 	}
 	return 0
 }
 
 type Status_BuildStarted struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Number of jobs Ninja will run in parallel.
 	Parallelism *uint32 `protobuf:"varint,1,opt,name=parallelism" json:"parallelism,omitempty"`
 	// Verbose value passed to ninja.
-	Verbose              *bool    `protobuf:"varint,2,opt,name=verbose" json:"verbose,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Verbose *bool `protobuf:"varint,2,opt,name=verbose" json:"verbose,omitempty"`
 }
 
-func (m *Status_BuildStarted) Reset()         { *m = Status_BuildStarted{} }
-func (m *Status_BuildStarted) String() string { return proto.CompactTextString(m) }
-func (*Status_BuildStarted) ProtoMessage()    {}
+func (x *Status_BuildStarted) Reset() {
+	*x = Status_BuildStarted{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_BuildStarted) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_BuildStarted) ProtoMessage() {}
+
+func (x *Status_BuildStarted) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Status_BuildStarted.ProtoReflect.Descriptor instead.
 func (*Status_BuildStarted) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 1}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 1}
 }
 
-func (m *Status_BuildStarted) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_BuildStarted.Unmarshal(m, b)
-}
-func (m *Status_BuildStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_BuildStarted.Marshal(b, m, deterministic)
-}
-func (m *Status_BuildStarted) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_BuildStarted.Merge(m, src)
-}
-func (m *Status_BuildStarted) XXX_Size() int {
-	return xxx_messageInfo_Status_BuildStarted.Size(m)
-}
-func (m *Status_BuildStarted) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_BuildStarted.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_BuildStarted proto.InternalMessageInfo
-
-func (m *Status_BuildStarted) GetParallelism() uint32 {
-	if m != nil && m.Parallelism != nil {
-		return *m.Parallelism
+func (x *Status_BuildStarted) GetParallelism() uint32 {
+	if x != nil && x.Parallelism != nil {
+		return *x.Parallelism
 	}
 	return 0
 }
 
-func (m *Status_BuildStarted) GetVerbose() bool {
-	if m != nil && m.Verbose != nil {
-		return *m.Verbose
+func (x *Status_BuildStarted) GetVerbose() bool {
+	if x != nil && x.Verbose != nil {
+		return *x.Verbose
 	}
 	return false
 }
 
 type Status_BuildFinished struct {
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
 }
 
-func (m *Status_BuildFinished) Reset()         { *m = Status_BuildFinished{} }
-func (m *Status_BuildFinished) String() string { return proto.CompactTextString(m) }
-func (*Status_BuildFinished) ProtoMessage()    {}
+func (x *Status_BuildFinished) Reset() {
+	*x = Status_BuildFinished{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_BuildFinished) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_BuildFinished) ProtoMessage() {}
+
+func (x *Status_BuildFinished) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[3]
+	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 Status_BuildFinished.ProtoReflect.Descriptor instead.
 func (*Status_BuildFinished) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 2}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 2}
 }
 
-func (m *Status_BuildFinished) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_BuildFinished.Unmarshal(m, b)
-}
-func (m *Status_BuildFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_BuildFinished.Marshal(b, m, deterministic)
-}
-func (m *Status_BuildFinished) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_BuildFinished.Merge(m, src)
-}
-func (m *Status_BuildFinished) XXX_Size() int {
-	return xxx_messageInfo_Status_BuildFinished.Size(m)
-}
-func (m *Status_BuildFinished) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_BuildFinished.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_BuildFinished proto.InternalMessageInfo
-
 type Status_EdgeStarted struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Edge identification number, unique to a Ninja run.
 	Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
 	// Edge start time in milliseconds since Ninja started.
@@ -279,87 +344,95 @@
 	// Command field from the edge.
 	Command *string `protobuf:"bytes,6,opt,name=command" json:"command,omitempty"`
 	// Edge uses console.
-	Console              *bool    `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Console *bool `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
 }
 
-func (m *Status_EdgeStarted) Reset()         { *m = Status_EdgeStarted{} }
-func (m *Status_EdgeStarted) String() string { return proto.CompactTextString(m) }
-func (*Status_EdgeStarted) ProtoMessage()    {}
+func (x *Status_EdgeStarted) Reset() {
+	*x = Status_EdgeStarted{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_EdgeStarted) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_EdgeStarted) ProtoMessage() {}
+
+func (x *Status_EdgeStarted) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[4]
+	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 Status_EdgeStarted.ProtoReflect.Descriptor instead.
 func (*Status_EdgeStarted) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 3}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 3}
 }
 
-func (m *Status_EdgeStarted) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_EdgeStarted.Unmarshal(m, b)
-}
-func (m *Status_EdgeStarted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_EdgeStarted.Marshal(b, m, deterministic)
-}
-func (m *Status_EdgeStarted) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_EdgeStarted.Merge(m, src)
-}
-func (m *Status_EdgeStarted) XXX_Size() int {
-	return xxx_messageInfo_Status_EdgeStarted.Size(m)
-}
-func (m *Status_EdgeStarted) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_EdgeStarted.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_EdgeStarted proto.InternalMessageInfo
-
-func (m *Status_EdgeStarted) GetId() uint32 {
-	if m != nil && m.Id != nil {
-		return *m.Id
+func (x *Status_EdgeStarted) GetId() uint32 {
+	if x != nil && x.Id != nil {
+		return *x.Id
 	}
 	return 0
 }
 
-func (m *Status_EdgeStarted) GetStartTime() uint32 {
-	if m != nil && m.StartTime != nil {
-		return *m.StartTime
+func (x *Status_EdgeStarted) GetStartTime() uint32 {
+	if x != nil && x.StartTime != nil {
+		return *x.StartTime
 	}
 	return 0
 }
 
-func (m *Status_EdgeStarted) GetInputs() []string {
-	if m != nil {
-		return m.Inputs
+func (x *Status_EdgeStarted) GetInputs() []string {
+	if x != nil {
+		return x.Inputs
 	}
 	return nil
 }
 
-func (m *Status_EdgeStarted) GetOutputs() []string {
-	if m != nil {
-		return m.Outputs
+func (x *Status_EdgeStarted) GetOutputs() []string {
+	if x != nil {
+		return x.Outputs
 	}
 	return nil
 }
 
-func (m *Status_EdgeStarted) GetDesc() string {
-	if m != nil && m.Desc != nil {
-		return *m.Desc
+func (x *Status_EdgeStarted) GetDesc() string {
+	if x != nil && x.Desc != nil {
+		return *x.Desc
 	}
 	return ""
 }
 
-func (m *Status_EdgeStarted) GetCommand() string {
-	if m != nil && m.Command != nil {
-		return *m.Command
+func (x *Status_EdgeStarted) GetCommand() string {
+	if x != nil && x.Command != nil {
+		return *x.Command
 	}
 	return ""
 }
 
-func (m *Status_EdgeStarted) GetConsole() bool {
-	if m != nil && m.Console != nil {
-		return *m.Console
+func (x *Status_EdgeStarted) GetConsole() bool {
+	if x != nil && x.Console != nil {
+		return *x.Console
 	}
 	return false
 }
 
 type Status_EdgeFinished struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Edge identification number, unique to a Ninja run.
 	Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
 	// Edge end time in milliseconds since Ninja started.
@@ -385,237 +458,434 @@
 	// Voluntary context switches
 	VoluntaryContextSwitches *uint64 `protobuf:"varint,12,opt,name=voluntary_context_switches,json=voluntaryContextSwitches" json:"voluntary_context_switches,omitempty"`
 	// Involuntary context switches
-	InvoluntaryContextSwitches *uint64  `protobuf:"varint,13,opt,name=involuntary_context_switches,json=involuntaryContextSwitches" json:"involuntary_context_switches,omitempty"`
-	XXX_NoUnkeyedLiteral       struct{} `json:"-"`
-	XXX_unrecognized           []byte   `json:"-"`
-	XXX_sizecache              int32    `json:"-"`
+	InvoluntaryContextSwitches *uint64 `protobuf:"varint,13,opt,name=involuntary_context_switches,json=involuntaryContextSwitches" json:"involuntary_context_switches,omitempty"`
 }
 
-func (m *Status_EdgeFinished) Reset()         { *m = Status_EdgeFinished{} }
-func (m *Status_EdgeFinished) String() string { return proto.CompactTextString(m) }
-func (*Status_EdgeFinished) ProtoMessage()    {}
+func (x *Status_EdgeFinished) Reset() {
+	*x = Status_EdgeFinished{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_EdgeFinished) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_EdgeFinished) ProtoMessage() {}
+
+func (x *Status_EdgeFinished) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[5]
+	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 Status_EdgeFinished.ProtoReflect.Descriptor instead.
 func (*Status_EdgeFinished) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 4}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 4}
 }
 
-func (m *Status_EdgeFinished) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_EdgeFinished.Unmarshal(m, b)
-}
-func (m *Status_EdgeFinished) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_EdgeFinished.Marshal(b, m, deterministic)
-}
-func (m *Status_EdgeFinished) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_EdgeFinished.Merge(m, src)
-}
-func (m *Status_EdgeFinished) XXX_Size() int {
-	return xxx_messageInfo_Status_EdgeFinished.Size(m)
-}
-func (m *Status_EdgeFinished) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_EdgeFinished.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_EdgeFinished proto.InternalMessageInfo
-
-func (m *Status_EdgeFinished) GetId() uint32 {
-	if m != nil && m.Id != nil {
-		return *m.Id
+func (x *Status_EdgeFinished) GetId() uint32 {
+	if x != nil && x.Id != nil {
+		return *x.Id
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetEndTime() uint32 {
-	if m != nil && m.EndTime != nil {
-		return *m.EndTime
+func (x *Status_EdgeFinished) GetEndTime() uint32 {
+	if x != nil && x.EndTime != nil {
+		return *x.EndTime
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetStatus() int32 {
-	if m != nil && m.Status != nil {
-		return *m.Status
+func (x *Status_EdgeFinished) GetStatus() int32 {
+	if x != nil && x.Status != nil {
+		return *x.Status
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetOutput() string {
-	if m != nil && m.Output != nil {
-		return *m.Output
+func (x *Status_EdgeFinished) GetOutput() string {
+	if x != nil && x.Output != nil {
+		return *x.Output
 	}
 	return ""
 }
 
-func (m *Status_EdgeFinished) GetUserTime() uint32 {
-	if m != nil && m.UserTime != nil {
-		return *m.UserTime
+func (x *Status_EdgeFinished) GetUserTime() uint32 {
+	if x != nil && x.UserTime != nil {
+		return *x.UserTime
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetSystemTime() uint32 {
-	if m != nil && m.SystemTime != nil {
-		return *m.SystemTime
+func (x *Status_EdgeFinished) GetSystemTime() uint32 {
+	if x != nil && x.SystemTime != nil {
+		return *x.SystemTime
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetMaxRssKb() uint64 {
-	if m != nil && m.MaxRssKb != nil {
-		return *m.MaxRssKb
+func (x *Status_EdgeFinished) GetMaxRssKb() uint64 {
+	if x != nil && x.MaxRssKb != nil {
+		return *x.MaxRssKb
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetMinorPageFaults() uint64 {
-	if m != nil && m.MinorPageFaults != nil {
-		return *m.MinorPageFaults
+func (x *Status_EdgeFinished) GetMinorPageFaults() uint64 {
+	if x != nil && x.MinorPageFaults != nil {
+		return *x.MinorPageFaults
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetMajorPageFaults() uint64 {
-	if m != nil && m.MajorPageFaults != nil {
-		return *m.MajorPageFaults
+func (x *Status_EdgeFinished) GetMajorPageFaults() uint64 {
+	if x != nil && x.MajorPageFaults != nil {
+		return *x.MajorPageFaults
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetIoInputKb() uint64 {
-	if m != nil && m.IoInputKb != nil {
-		return *m.IoInputKb
+func (x *Status_EdgeFinished) GetIoInputKb() uint64 {
+	if x != nil && x.IoInputKb != nil {
+		return *x.IoInputKb
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetIoOutputKb() uint64 {
-	if m != nil && m.IoOutputKb != nil {
-		return *m.IoOutputKb
+func (x *Status_EdgeFinished) GetIoOutputKb() uint64 {
+	if x != nil && x.IoOutputKb != nil {
+		return *x.IoOutputKb
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetVoluntaryContextSwitches() uint64 {
-	if m != nil && m.VoluntaryContextSwitches != nil {
-		return *m.VoluntaryContextSwitches
+func (x *Status_EdgeFinished) GetVoluntaryContextSwitches() uint64 {
+	if x != nil && x.VoluntaryContextSwitches != nil {
+		return *x.VoluntaryContextSwitches
 	}
 	return 0
 }
 
-func (m *Status_EdgeFinished) GetInvoluntaryContextSwitches() uint64 {
-	if m != nil && m.InvoluntaryContextSwitches != nil {
-		return *m.InvoluntaryContextSwitches
+func (x *Status_EdgeFinished) GetInvoluntaryContextSwitches() uint64 {
+	if x != nil && x.InvoluntaryContextSwitches != nil {
+		return *x.InvoluntaryContextSwitches
 	}
 	return 0
 }
 
 type Status_Message struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
 	// Message priority level (DEBUG, INFO, WARNING, ERROR).
 	Level *Status_Message_Level `protobuf:"varint,1,opt,name=level,enum=ninja.Status_Message_Level,def=0" json:"level,omitempty"`
 	// Info/warning/error message from Ninja.
-	Message              *string  `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
 }
 
-func (m *Status_Message) Reset()         { *m = Status_Message{} }
-func (m *Status_Message) String() string { return proto.CompactTextString(m) }
-func (*Status_Message) ProtoMessage()    {}
+// Default values for Status_Message fields.
+const (
+	Default_Status_Message_Level = Status_Message_INFO
+)
+
+func (x *Status_Message) Reset() {
+	*x = Status_Message{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_frontend_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Status_Message) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Status_Message) ProtoMessage() {}
+
+func (x *Status_Message) ProtoReflect() protoreflect.Message {
+	mi := &file_frontend_proto_msgTypes[6]
+	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 Status_Message.ProtoReflect.Descriptor instead.
 func (*Status_Message) Descriptor() ([]byte, []int) {
-	return fileDescriptor_eca3873955a29cfe, []int{0, 5}
+	return file_frontend_proto_rawDescGZIP(), []int{0, 5}
 }
 
-func (m *Status_Message) XXX_Unmarshal(b []byte) error {
-	return xxx_messageInfo_Status_Message.Unmarshal(m, b)
-}
-func (m *Status_Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	return xxx_messageInfo_Status_Message.Marshal(b, m, deterministic)
-}
-func (m *Status_Message) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_Status_Message.Merge(m, src)
-}
-func (m *Status_Message) XXX_Size() int {
-	return xxx_messageInfo_Status_Message.Size(m)
-}
-func (m *Status_Message) XXX_DiscardUnknown() {
-	xxx_messageInfo_Status_Message.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_Status_Message proto.InternalMessageInfo
-
-const Default_Status_Message_Level Status_Message_Level = Status_Message_INFO
-
-func (m *Status_Message) GetLevel() Status_Message_Level {
-	if m != nil && m.Level != nil {
-		return *m.Level
+func (x *Status_Message) GetLevel() Status_Message_Level {
+	if x != nil && x.Level != nil {
+		return *x.Level
 	}
 	return Default_Status_Message_Level
 }
 
-func (m *Status_Message) GetMessage() string {
-	if m != nil && m.Message != nil {
-		return *m.Message
+func (x *Status_Message) GetMessage() string {
+	if x != nil && x.Message != nil {
+		return *x.Message
 	}
 	return ""
 }
 
-func init() {
-	proto.RegisterEnum("ninja.Status_Message_Level", Status_Message_Level_name, Status_Message_Level_value)
-	proto.RegisterType((*Status)(nil), "ninja.Status")
-	proto.RegisterType((*Status_TotalEdges)(nil), "ninja.Status.TotalEdges")
-	proto.RegisterType((*Status_BuildStarted)(nil), "ninja.Status.BuildStarted")
-	proto.RegisterType((*Status_BuildFinished)(nil), "ninja.Status.BuildFinished")
-	proto.RegisterType((*Status_EdgeStarted)(nil), "ninja.Status.EdgeStarted")
-	proto.RegisterType((*Status_EdgeFinished)(nil), "ninja.Status.EdgeFinished")
-	proto.RegisterType((*Status_Message)(nil), "ninja.Status.Message")
+var File_frontend_proto protoreflect.FileDescriptor
+
+var file_frontend_proto_rawDesc = []byte{
+	0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xb4, 0x0a, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65,
+	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
+	0x73, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x73, 0x12, 0x3f, 0x0a,
+	0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64,
+	0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x42,
+	0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e, 0x53,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73,
+	0x68, 0x65, 0x64, 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68,
+	0x65, 0x64, 0x12, 0x3c, 0x0a, 0x0c, 0x65, 0x64, 0x67, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74,
+	0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61,
+	0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72,
+	0x74, 0x65, 0x64, 0x52, 0x0b, 0x65, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64,
+	0x12, 0x3f, 0x0a, 0x0d, 0x65, 0x64, 0x67, 0x65, 0x5f, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
+	0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x64, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73,
+	0x68, 0x65, 0x64, 0x52, 0x0c, 0x65, 0x64, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
+	0x64, 0x12, 0x2f, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x1a, 0x2d, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x73,
+	0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
+	0x73, 0x1a, 0x4a, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65,
+	0x64, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c,
+	0x69, 0x73, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x1a, 0x0f, 0x0a,
+	0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x1a, 0xb6,
+	0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x0e,
+	0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d,
+	0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a,
+	0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x69,
+	0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12,
+	0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64,
+	0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x06,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a,
+	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
+	0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x1a, 0xdf, 0x03, 0x0a, 0x0c, 0x45, 0x64, 0x67, 0x65,
+	0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f,
+	0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54,
+	0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f,
+	0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65,
+	0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d,
+	0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62, 0x18,
+	0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62, 0x12,
+	0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61,
+	0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x6f,
+	0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d,
+	0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73,
+	0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61, 0x67,
+	0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69, 0x6e,
+	0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6f,
+	0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69,
+	0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f, 0x6c,
+	0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73,
+	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x76,
+	0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53,
+	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f, 0x6c,
+	0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73,
+	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x69,
+	0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x1a, 0x92, 0x01, 0x0a, 0x07, 0x4d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x4c, 0x65, 0x76, 0x65,
+	0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18,
+	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65,
+	0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x57,
+	0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f,
+	0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x03, 0x42, 0x2a,
+	0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x6e, 0x69, 0x6e, 0x6a,
+	0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64,
 }
 
-func init() {
-	proto.RegisterFile("frontend.proto", fileDescriptor_eca3873955a29cfe)
+var (
+	file_frontend_proto_rawDescOnce sync.Once
+	file_frontend_proto_rawDescData = file_frontend_proto_rawDesc
+)
+
+func file_frontend_proto_rawDescGZIP() []byte {
+	file_frontend_proto_rawDescOnce.Do(func() {
+		file_frontend_proto_rawDescData = protoimpl.X.CompressGZIP(file_frontend_proto_rawDescData)
+	})
+	return file_frontend_proto_rawDescData
 }
 
-var fileDescriptor_eca3873955a29cfe = []byte{
-	// 678 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xff, 0x4e, 0xd4, 0x40,
-	0x10, 0xc7, 0xbd, 0xdf, 0xd7, 0xe9, 0xdd, 0x71, 0x6c, 0xa2, 0x29, 0x05, 0xe5, 0xc2, 0x5f, 0xc4,
-	0xc4, 0x33, 0x31, 0x26, 0x46, 0x43, 0xa2, 0x9e, 0x02, 0x22, 0x0a, 0x66, 0xc1, 0x98, 0xf8, 0x4f,
-	0xb3, 0xbd, 0x2e, 0xb0, 0xd8, 0x76, 0x2f, 0xdd, 0x2d, 0xc2, 0x6b, 0xf8, 0x2c, 0xc6, 0xd7, 0xf1,
-	0x55, 0xcc, 0xce, 0xb6, 0x47, 0x0f, 0x88, 0xff, 0x75, 0x66, 0x3e, 0xf3, 0x9d, 0xd9, 0x99, 0xed,
-	0xc2, 0xe0, 0x24, 0x93, 0xa9, 0xe6, 0x69, 0x34, 0x9e, 0x65, 0x52, 0x4b, 0xd2, 0x4a, 0x45, 0x7a,
-	0xce, 0x36, 0x7e, 0x03, 0xb4, 0x8f, 0x34, 0xd3, 0xb9, 0x22, 0x2f, 0xc1, 0xd5, 0x52, 0xb3, 0x38,
-	0xe0, 0xd1, 0x29, 0x57, 0x5e, 0x6d, 0x54, 0xdb, 0x74, 0x9f, 0x79, 0x63, 0xe4, 0xc6, 0x96, 0x19,
-	0x1f, 0x1b, 0x60, 0xdb, 0xc4, 0x29, 0xe8, 0xf9, 0x37, 0x79, 0x0d, 0xfd, 0x30, 0x17, 0x71, 0x14,
-	0x28, 0xcd, 0x32, 0xcd, 0x23, 0xaf, 0x8e, 0xc9, 0xfe, 0x62, 0xf2, 0xc4, 0x20, 0x47, 0x96, 0xa0,
-	0xbd, 0xb0, 0x62, 0x91, 0x09, 0x0c, 0xac, 0xc0, 0x89, 0x48, 0x85, 0x3a, 0xe3, 0x91, 0xd7, 0x40,
-	0x85, 0xd5, 0x3b, 0x14, 0x76, 0x0a, 0x84, 0xda, 0x9a, 0xa5, 0x49, 0xb6, 0xa0, 0x67, 0x3a, 0x9f,
-	0xf7, 0xd0, 0x44, 0x85, 0x95, 0x45, 0x05, 0xd3, 0x6f, 0xd9, 0x82, 0xcb, 0xaf, 0x0d, 0x73, 0x04,
-	0xcc, 0x9e, 0x37, 0xd0, 0xba, 0xeb, 0x08, 0x26, 0x7d, 0x5e, 0x1f, 0xcb, 0xcd, 0xcb, 0x3f, 0x85,
-	0x4e, 0xc2, 0x95, 0x62, 0xa7, 0xdc, 0x6b, 0x63, 0xea, 0xfd, 0xc5, 0xd4, 0xcf, 0x36, 0x48, 0x4b,
-	0xca, 0x7f, 0x02, 0x70, 0x3d, 0x4e, 0xb2, 0x7e, 0x7b, 0xfa, 0xfd, 0xea, 0x8c, 0xfd, 0x8f, 0xd0,
-	0xab, 0x0e, 0x90, 0x8c, 0xc0, 0x9d, 0xb1, 0x8c, 0xc5, 0x31, 0x8f, 0x85, 0x4a, 0x8a, 0x84, 0xaa,
-	0x8b, 0x78, 0xd0, 0xb9, 0xe0, 0x59, 0x28, 0x15, 0xc7, 0x7d, 0x74, 0x69, 0x69, 0xfa, 0x4b, 0xd0,
-	0x5f, 0x18, 0xa5, 0xff, 0xa7, 0x06, 0x6e, 0x65, 0x34, 0x64, 0x00, 0x75, 0x11, 0x15, 0x9a, 0x75,
-	0x11, 0x91, 0x87, 0x00, 0x38, 0xd6, 0x40, 0x8b, 0xc4, 0xaa, 0xf5, 0xa9, 0x83, 0x9e, 0x63, 0x91,
-	0x70, 0xf2, 0x00, 0xda, 0x22, 0x9d, 0xe5, 0x5a, 0x79, 0x8d, 0x51, 0x63, 0xd3, 0xa1, 0x85, 0x65,
-	0x3a, 0x90, 0xb9, 0xc6, 0x40, 0x13, 0x03, 0xa5, 0x49, 0x08, 0x34, 0x23, 0xae, 0xa6, 0x38, 0x65,
-	0x87, 0xe2, 0xb7, 0xa1, 0xa7, 0x32, 0x49, 0x58, 0x1a, 0xe1, 0x04, 0x1d, 0x5a, 0x9a, 0x36, 0x92,
-	0x2a, 0x19, 0x73, 0xaf, 0x63, 0x4f, 0x52, 0x98, 0xfe, 0xdf, 0x06, 0xf4, 0xaa, 0x4b, 0xb9, 0xd5,
-	0xf9, 0x0a, 0x74, 0x79, 0x1a, 0x55, 0xfb, 0xee, 0xf0, 0x34, 0x2a, 0xbb, 0x56, 0xb8, 0x1b, 0xbc,
-	0x6c, 0xcb, 0xb4, 0xb0, 0x8c, 0xdf, 0xb6, 0x89, 0x57, 0xc8, 0xa1, 0x85, 0x45, 0x56, 0xc1, 0xc9,
-	0x15, 0xcf, 0xac, 0x56, 0x0b, 0xb5, 0xba, 0xc6, 0x81, 0x62, 0xeb, 0xe0, 0xaa, 0x2b, 0xa5, 0x79,
-	0x62, 0xc3, 0x6d, 0xbb, 0x3f, 0xeb, 0x42, 0x60, 0x0d, 0x20, 0x61, 0x97, 0x41, 0xa6, 0x54, 0xf0,
-	0x23, 0xc4, 0x63, 0x34, 0x69, 0x37, 0x61, 0x97, 0x54, 0xa9, 0xfd, 0x90, 0x3c, 0x86, 0xe5, 0x44,
-	0xa4, 0x32, 0x0b, 0x66, 0xcc, 0x5c, 0x42, 0x96, 0xc7, 0x5a, 0x79, 0x5d, 0x84, 0x96, 0x30, 0xf0,
-	0x85, 0x9d, 0xf2, 0x1d, 0x74, 0x23, 0xcb, 0xce, 0x6f, 0xb0, 0x4e, 0xc1, 0x9a, 0x40, 0x85, 0x7d,
-	0x04, 0xae, 0x90, 0x01, 0xae, 0xc3, 0x94, 0x05, 0xa4, 0x1c, 0x21, 0xf7, 0x8c, 0x67, 0x3f, 0x24,
-	0x23, 0xe8, 0x09, 0x19, 0xd8, 0x03, 0x1a, 0xc0, 0x45, 0x00, 0x84, 0x3c, 0x44, 0xd7, 0x7e, 0x48,
-	0xb6, 0xc0, 0xbf, 0x90, 0x71, 0x9e, 0x6a, 0x96, 0x5d, 0x05, 0x53, 0xf3, 0x86, 0x5c, 0xea, 0x40,
-	0xfd, 0x14, 0x7a, 0x7a, 0xc6, 0x95, 0xd7, 0x43, 0xde, 0x9b, 0x13, 0xef, 0x2c, 0x70, 0x54, 0xc4,
-	0xc9, 0x1b, 0x58, 0x13, 0xe9, 0x7f, 0xf2, 0xfb, 0x98, 0xef, 0x57, 0x98, 0x1b, 0x0a, 0xfe, 0xaf,
-	0x1a, 0x74, 0x8a, 0x7f, 0x87, 0xbc, 0x80, 0x56, 0xcc, 0x2f, 0x78, 0x8c, 0xfb, 0x1d, 0xdc, 0x7c,
-	0x1d, 0x0a, 0x6a, 0xfc, 0xc9, 0x20, 0xaf, 0x9a, 0x7b, 0x07, 0x3b, 0x87, 0xd4, 0xf2, 0xe6, 0x02,
-	0x95, 0x3f, 0x67, 0xdd, 0x5e, 0xad, 0xc2, 0xdc, 0x78, 0x0e, 0x2d, 0xe4, 0x49, 0x17, 0x30, 0x63,
-	0x78, 0x8f, 0xb8, 0xd0, 0xf9, 0xf6, 0x96, 0x1e, 0xec, 0x1d, 0xec, 0x0e, 0x6b, 0xc4, 0x81, 0xd6,
-	0x36, 0xa5, 0x87, 0x74, 0x58, 0x37, 0x9f, 0xef, 0xb7, 0x27, 0x5f, 0x77, 0x87, 0x8d, 0x09, 0xf9,
-	0xd0, 0xf8, 0x3e, 0xc0, 0xe2, 0x41, 0xf9, 0xae, 0xfe, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x7a,
-	0x33, 0x13, 0x62, 0x05, 0x00, 0x00,
+var file_frontend_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_frontend_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_frontend_proto_goTypes = []interface{}{
+	(Status_Message_Level)(0),    // 0: ninja.Status.Message.Level
+	(*Status)(nil),               // 1: ninja.Status
+	(*Status_TotalEdges)(nil),    // 2: ninja.Status.TotalEdges
+	(*Status_BuildStarted)(nil),  // 3: ninja.Status.BuildStarted
+	(*Status_BuildFinished)(nil), // 4: ninja.Status.BuildFinished
+	(*Status_EdgeStarted)(nil),   // 5: ninja.Status.EdgeStarted
+	(*Status_EdgeFinished)(nil),  // 6: ninja.Status.EdgeFinished
+	(*Status_Message)(nil),       // 7: ninja.Status.Message
+}
+var file_frontend_proto_depIdxs = []int32{
+	2, // 0: ninja.Status.total_edges:type_name -> ninja.Status.TotalEdges
+	3, // 1: ninja.Status.build_started:type_name -> ninja.Status.BuildStarted
+	4, // 2: ninja.Status.build_finished:type_name -> ninja.Status.BuildFinished
+	5, // 3: ninja.Status.edge_started:type_name -> ninja.Status.EdgeStarted
+	6, // 4: ninja.Status.edge_finished:type_name -> ninja.Status.EdgeFinished
+	7, // 5: ninja.Status.message:type_name -> ninja.Status.Message
+	0, // 6: ninja.Status.Message.level:type_name -> ninja.Status.Message.Level
+	7, // [7:7] is the sub-list for method output_type
+	7, // [7:7] is the sub-list for method input_type
+	7, // [7:7] is the sub-list for extension type_name
+	7, // [7:7] is the sub-list for extension extendee
+	0, // [0:7] is the sub-list for field type_name
+}
+
+func init() { file_frontend_proto_init() }
+func file_frontend_proto_init() {
+	if File_frontend_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_frontend_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_TotalEdges); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_BuildStarted); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_BuildFinished); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_EdgeStarted); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_EdgeFinished); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_frontend_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Status_Message); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_frontend_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   7,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_frontend_proto_goTypes,
+		DependencyIndexes: file_frontend_proto_depIdxs,
+		EnumInfos:         file_frontend_proto_enumTypes,
+		MessageInfos:      file_frontend_proto_msgTypes,
+	}.Build()
+	File_frontend_proto = out.File
+	file_frontend_proto_rawDesc = nil
+	file_frontend_proto_goTypes = nil
+	file_frontend_proto_depIdxs = nil
 }
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
index e5e5d9f..5730388 100644
--- a/ui/status/ninja_frontend/frontend.proto
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -17,7 +17,7 @@
 option optimize_for = LITE_RUNTIME;
 
 package ninja;
-option go_package = "ninja_frontend";
+option go_package = "android/soong/ui/status/ninja_frontend";
 
 message Status {
   message TotalEdges {
diff --git a/ui/status/ninja_test.go b/ui/status/ninja_test.go
index c400c97..f3638b3 100644
--- a/ui/status/ninja_test.go
+++ b/ui/status/ninja_test.go
@@ -43,3 +43,53 @@
 		t.Errorf("nr.Close timed out, %s > %s", g, w)
 	}
 }
+
+// Test that error hint is added to output if available
+func TestNinjaReader_CorrectErrorHint(t *testing.T) {
+	errorPattern1 := "pattern-1 in input"
+	errorHint1 := "\n Fix by doing task 1"
+	errorPattern2 := "pattern-2 in input"
+	errorHint2 := "\n Fix by doing task 2"
+	mockErrorHints := make(map[string]string)
+	mockErrorHints[errorPattern1] = errorHint1
+	mockErrorHints[errorPattern2] = errorHint2
+
+	errorHintGenerator := *newErrorHintGenerator(mockErrorHints)
+	testCases := []struct {
+		rawOutput            string
+		buildExitCode        int
+		expectedFinalOutput  string
+		testCaseErrorMessage string
+	}{
+		{
+			rawOutput:            "ninja build was successful",
+			buildExitCode:        0,
+			expectedFinalOutput:  "ninja build was successful",
+			testCaseErrorMessage: "raw output changed when build was successful",
+		},
+		{
+			rawOutput:            "ninja build failed",
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed",
+			testCaseErrorMessage: "raw output changed even when no error hint pattern was found",
+		},
+		{
+			rawOutput:            "ninja build failed: " + errorPattern1 + "some footnotes",
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed: " + errorPattern1 + "some footnotes" + errorHint1,
+			testCaseErrorMessage: "error hint not added despite pattern match",
+		},
+		{
+			rawOutput:            "ninja build failed: " + errorPattern2 + errorPattern1,
+			buildExitCode:        1,
+			expectedFinalOutput:  "ninja build failed: " + errorPattern2 + errorPattern1 + errorHint2,
+			testCaseErrorMessage: "error hint should be added for first pattern match in raw output",
+		},
+	}
+	for _, testCase := range testCases {
+		actualFinalOutput := errorHintGenerator.GetOutputWithErrorHint(testCase.rawOutput, testCase.buildExitCode)
+		if actualFinalOutput != testCase.expectedFinalOutput {
+			t.Errorf(testCase.testCaseErrorMessage+"\nexpected: %s\ngot: %s", testCase.expectedFinalOutput, actualFinalOutput)
+		}
+	}
+}
diff --git a/ui/terminal/simple_status.go b/ui/terminal/simple_status.go
index 4e8c568..3157813 100644
--- a/ui/terminal/simple_status.go
+++ b/ui/terminal/simple_status.go
@@ -22,30 +22,41 @@
 )
 
 type simpleStatusOutput struct {
-	writer    io.Writer
-	formatter formatter
+	writer      io.Writer
+	formatter   formatter
+	keepANSI    bool
+	outputLevel status.MsgLevel
 }
 
 // NewSimpleStatusOutput returns a StatusOutput that represents the
 // current build status similarly to Ninja's built-in terminal
 // output.
-func NewSimpleStatusOutput(w io.Writer, formatter formatter) status.StatusOutput {
+func NewSimpleStatusOutput(w io.Writer, formatter formatter, keepANSI bool, quietBuild bool) status.StatusOutput {
+	level := status.StatusLvl
+	if quietBuild {
+		level = status.PrintLvl
+	}
 	return &simpleStatusOutput{
-		writer:    w,
-		formatter: formatter,
+		writer:      w,
+		formatter:   formatter,
+		keepANSI:    keepANSI,
+		outputLevel: level,
 	}
 }
 
 func (s *simpleStatusOutput) Message(level status.MsgLevel, message string) {
-	if level >= status.StatusLvl {
+	if level >= s.outputLevel {
 		fmt.Fprintln(s.writer, s.formatter.message(level, message))
 	}
 }
 
-func (s *simpleStatusOutput) StartAction(action *status.Action, counts status.Counts) {
+func (s *simpleStatusOutput) StartAction(_ *status.Action, _ status.Counts) {
 }
 
 func (s *simpleStatusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
+	if s.outputLevel > status.StatusLvl {
+		return
+	}
 	str := result.Description
 	if str == "" {
 		str = result.Command
@@ -54,7 +65,9 @@
 	progress := s.formatter.progress(counts) + str
 
 	output := s.formatter.result(result)
-	output = string(stripAnsiEscapes([]byte(output)))
+	if !s.keepANSI {
+		output = string(stripAnsiEscapes([]byte(output)))
+	}
 
 	if output != "" {
 		fmt.Fprint(s.writer, progress, "\n", output)
diff --git a/ui/terminal/smart_status.go b/ui/terminal/smart_status.go
index 6bdf140..06a4064 100644
--- a/ui/terminal/smart_status.go
+++ b/ui/terminal/smart_status.go
@@ -77,7 +77,12 @@
 		s.requestedTableHeight = h
 	}
 
-	s.updateTermSize()
+	if w, h, ok := termSize(s.writer); ok {
+		s.termWidth, s.termHeight = w, h
+		s.computeTableHeight()
+	} else {
+		s.tableMode = false
+	}
 
 	if s.tableMode {
 		// Add empty lines at the bottom of the screen to scroll back the existing history
@@ -296,40 +301,44 @@
 	close(s.sigwinch)
 }
 
+// computeTableHeight recomputes s.tableHeight based on s.termHeight and s.requestedTableHeight.
+func (s *smartStatusOutput) computeTableHeight() {
+	tableHeight := s.requestedTableHeight
+	if tableHeight == 0 {
+		tableHeight = s.termHeight / 4
+		if tableHeight < 1 {
+			tableHeight = 1
+		} else if tableHeight > 10 {
+			tableHeight = 10
+		}
+	}
+	if tableHeight > s.termHeight-1 {
+		tableHeight = s.termHeight - 1
+	}
+	s.tableHeight = tableHeight
+}
+
+// updateTermSize recomputes the table height after a SIGWINCH and pans any existing text if
+// necessary.
 func (s *smartStatusOutput) updateTermSize() {
 	if w, h, ok := termSize(s.writer); ok {
-		firstUpdate := s.termHeight == 0 && s.termWidth == 0
 		oldScrollingHeight := s.termHeight - s.tableHeight
 
 		s.termWidth, s.termHeight = w, h
 
 		if s.tableMode {
-			tableHeight := s.requestedTableHeight
-			if tableHeight == 0 {
-				tableHeight = s.termHeight / 4
-				if tableHeight < 1 {
-					tableHeight = 1
-				} else if tableHeight > 10 {
-					tableHeight = 10
-				}
-			}
-			if tableHeight > s.termHeight-1 {
-				tableHeight = s.termHeight - 1
-			}
-			s.tableHeight = tableHeight
+			s.computeTableHeight()
 
 			scrollingHeight := s.termHeight - s.tableHeight
 
-			if !firstUpdate {
-				// If the scrolling region has changed, attempt to pan the existing text so that it is
-				// not overwritten by the table.
-				if scrollingHeight < oldScrollingHeight {
-					pan := oldScrollingHeight - scrollingHeight
-					if pan > s.tableHeight {
-						pan = s.tableHeight
-					}
-					fmt.Fprint(s.writer, ansi.panDown(pan))
+			// If the scrolling region has changed, attempt to pan the existing text so that it is
+			// not overwritten by the table.
+			if scrollingHeight < oldScrollingHeight {
+				pan := oldScrollingHeight - scrollingHeight
+				if pan > s.tableHeight {
+					pan = s.tableHeight
 				}
+				fmt.Fprint(s.writer, ansi.panDown(pan))
 			}
 		}
 	}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index d8e7392..ff0af47 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -26,12 +26,12 @@
 //
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild bool) status.StatusOutput {
+func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
 	formatter := newFormatter(statusFormat, quietBuild)
 
-	if !forceSimpleOutput && isSmartTerminal(w) {
-		return NewSmartStatusOutput(w, formatter)
+	if forceSimpleOutput || quietBuild || !isSmartTerminal(w) {
+		return NewSimpleStatusOutput(w, formatter, forceKeepANSI, quietBuild)
 	} else {
-		return NewSimpleStatusOutput(w, formatter)
+		return NewSmartStatusOutput(w, formatter)
 	}
 }
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index aa69dff..810e31d 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -94,7 +94,7 @@
 
 			t.Run("smart", func(t *testing.T) {
 				smart := &fakeSmartTerminal{termWidth: 40}
-				stat := NewStatusOutput(smart, "", false, false)
+				stat := NewStatusOutput(smart, "", false, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -105,7 +105,7 @@
 
 			t.Run("simple", func(t *testing.T) {
 				simple := &bytes.Buffer{}
-				stat := NewStatusOutput(simple, "", false, false)
+				stat := NewStatusOutput(simple, "", false, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -116,7 +116,7 @@
 
 			t.Run("force simple", func(t *testing.T) {
 				smart := &fakeSmartTerminal{termWidth: 40}
-				stat := NewStatusOutput(smart, "", true, false)
+				stat := NewStatusOutput(smart, "", true, false, false)
 				tt.calls(stat)
 				stat.Flush()
 
@@ -269,7 +269,7 @@
 	os.Setenv(tableHeightEnVar, "")
 
 	smart := &fakeSmartTerminal{termWidth: 40}
-	stat := NewStatusOutput(smart, "", false, false)
+	stat := NewStatusOutput(smart, "", false, false, false)
 	smartStat := stat.(*smartStatusOutput)
 	smartStat.sigwinchHandled = make(chan bool)