Merge "Support paths for init_rc from Soong."
diff --git a/android/bazel.go b/android/bazel.go
index b2170be..b54ae64 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -174,6 +174,18 @@
 		"liblinker_debuggerd_stub",      // ruperts@, cc_library_static, depends on //system/libbase
 		"libbionic_tests_headers_posix", // ruperts@, cc_library_static
 		"libc_dns",                      // ruperts@, cc_library_static
+
+		"note_memtag_heap_async", // jingwen@, b/185079815, features.h includes not found
+		"note_memtag_heap_sync",  // jingwen@, b/185079815, features.h includes not found
+
+		// List of all full_cc_libraries in //bionic, with their immediate failures
+		"libc",              // jingwen@, cc_library, depends on //external/gwp_asan
+		"libc_malloc_debug", // jingwen@, cc_library, fatal error: 'assert.h' file not found
+		"libc_malloc_hooks", // jingwen@, cc_library, fatal error: 'errno.h' file not found
+		"libdl",             // jingwen@, cc_library, ld.lld: error: no input files
+		"libm",              // jingwen@, cc_library, fatal error: 'freebsd-compat.h' file not found
+		"libseccomp_policy", // jingwen@, cc_library, fatal error: 'seccomp_policy.h' file not found
+		"libstdc++",         // jingwen@, cc_library, depends on //external/gwp_asan
 	}
 
 	// Used for quicker lookups
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 5b9d3bf..97eec30 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -34,10 +34,26 @@
 	"android/soong/shared"
 )
 
+type cqueryRequest interface {
+	// Name returns a string name for this request type. Such request type names must be unique,
+	// and must only consist of alphanumeric characters.
+	Name() string
+
+	// 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.
+	StarlarkFunctionBody() string
+}
+
 // Map key to describe bazel cquery requests.
 type cqueryKey struct {
 	label       string
-	requestType cquery.RequestType
+	requestType cqueryRequest
 	archType    ArchType
 }
 
@@ -53,6 +69,10 @@
 	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
 	GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool)
 
+	// GetPrebuiltCcStaticLibraryFiles returns paths to prebuilt cc static libraries, and whether the
+	// results were available
+	GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool)
+
 	// ** End cquery methods
 
 	// Issues commands to Bazel to receive results for all cquery requests
@@ -97,17 +117,26 @@
 
 // A bazel context to use for tests.
 type MockBazelContext struct {
-	AllFiles map[string][]string
+	OutputBaseDir string
+
+	LabelToOutputFiles                 map[string][]string
+	LabelToOutputFilesAndCcObjectFiles map[string]cquery.GetOutputFilesAndCcObjectFiles_Result
+	LabelToCcStaticLibraryFiles        map[string][]string
 }
 
 func (m MockBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
-	result, ok := m.AllFiles[label]
+	result, ok := m.LabelToOutputFiles[label]
 	return result, ok
 }
 
 func (m MockBazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
-	result, ok := m.AllFiles[label]
-	return result, result, ok
+	result, ok := m.LabelToOutputFilesAndCcObjectFiles[label]
+	return result.OutputFiles, result.CcObjectFiles, ok
+}
+
+func (m MockBazelContext) GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool) {
+	result, ok := m.LabelToCcStaticLibraryFiles[label]
+	return result, ok
 }
 
 func (m MockBazelContext) InvokeBazel() error {
@@ -118,9 +147,7 @@
 	return true
 }
 
-func (m MockBazelContext) OutputBase() string {
-	return "outputbase"
-}
+func (m MockBazelContext) OutputBase() string { return m.OutputBaseDir }
 
 func (m MockBazelContext) BuildStatementsToRegister() []bazel.BuildStatement {
 	return []bazel.BuildStatement{}
@@ -133,7 +160,7 @@
 	var ret []string
 	if ok {
 		bazelOutput := strings.TrimSpace(rawString)
-		ret = cquery.GetOutputFiles.ParseResult(bazelOutput).([]string)
+		ret = cquery.GetOutputFiles.ParseResult(bazelOutput)
 	}
 	return ret, ok
 }
@@ -145,7 +172,7 @@
 	result, ok := bazelCtx.cquery(label, cquery.GetOutputFilesAndCcObjectFiles, archType)
 	if ok {
 		bazelOutput := strings.TrimSpace(result)
-		returnResult := cquery.GetOutputFilesAndCcObjectFiles.ParseResult(bazelOutput).(cquery.GetOutputFilesAndCcObjectFiles_Result)
+		returnResult := cquery.GetOutputFilesAndCcObjectFiles.ParseResult(bazelOutput)
 		outputFiles = returnResult.OutputFiles
 		ccObjects = returnResult.CcObjectFiles
 	}
@@ -153,6 +180,19 @@
 	return outputFiles, ccObjects, ok
 }
 
+// GetPrebuiltCcStaticLibraryFiles returns a slice of prebuilt static libraries for the given
+// label/archType if there are query results; otherwise, it enqueues the query and returns false.
+func (bazelCtx *bazelContext) GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool) {
+	result, ok := bazelCtx.cquery(label, cquery.GetPrebuiltCcStaticLibraryFiles, archType)
+	if !ok {
+		return nil, false
+	}
+
+	bazelOutput := strings.TrimSpace(result)
+	ret := cquery.GetPrebuiltCcStaticLibraryFiles.ParseResult(bazelOutput)
+	return ret, ok
+}
+
 func (n noopBazelContext) GetOutputFiles(label string, archType ArchType) ([]string, bool) {
 	panic("unimplemented")
 }
@@ -161,6 +201,10 @@
 	panic("unimplemented")
 }
 
+func (n noopBazelContext) GetPrebuiltCcStaticLibraryFiles(label string, archType ArchType) ([]string, bool) {
+	panic("unimplemented")
+}
+
 func (n noopBazelContext) InvokeBazel() error {
 	panic("unimplemented")
 }
@@ -231,7 +275,7 @@
 // If the given request was already made (and the results are available), then
 // returns (result, true). If the request is queued but no results are available,
 // then returns ("", false).
-func (context *bazelContext) cquery(label string, requestType cquery.RequestType,
+func (context *bazelContext) cquery(label string, requestType cqueryRequest,
 	archType ArchType) (string, bool) {
 	key := cqueryKey{label, requestType, archType}
 	if result, ok := context.results[key]; ok {
@@ -310,15 +354,20 @@
 # This file is generated by soong_build. Do not edit.
 local_repository(
     name = "sourceroot",
-    path = "%s",
+    path = "%[1]s",
 )
 
 local_repository(
     name = "rules_cc",
-    path = "%s/build/bazel/rules_cc",
+    path = "%[1]s/build/bazel/rules_cc",
+)
+
+local_repository(
+    name = "bazel_skylib",
+    path = "%[1]s/build/bazel/bazel_skylib",
 )
 `
-	return []byte(fmt.Sprintf(formatString, context.workspaceDir, context.workspaceDir))
+	return []byte(fmt.Sprintf(formatString, context.workspaceDir))
 }
 
 func (context *bazelContext) mainBzlFileContents() []byte {
@@ -449,7 +498,7 @@
 // and grouped by their request type. The data retrieved for each label depends on its
 // request type.
 func (context *bazelContext) cqueryStarlarkFileContents() []byte {
-	requestTypeToCqueryIdEntries := map[cquery.RequestType][]string{}
+	requestTypeToCqueryIdEntries := map[cqueryRequest][]string{}
 	for val, _ := range context.requests {
 		cqueryId := getCqueryId(val)
 		mapEntryString := fmt.Sprintf("%q : True", cqueryId)
diff --git a/android/config.go b/android/config.go
index cfbc37f..80651bb 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1498,6 +1498,10 @@
 	return c.config.productVariables.BuildBrokenTrebleSyspropNeverallow
 }
 
+func (c *deviceConfig) BuildDebugfsRestrictionsEnabled() bool {
+	return c.config.productVariables.BuildDebugfsRestrictionsEnabled
+}
+
 func (c *deviceConfig) BuildBrokenVendorPropertyNamespace() bool {
 	return c.config.productVariables.BuildBrokenVendorPropertyNamespace
 }
diff --git a/android/paths.go b/android/paths.go
index ba1ab11..37b04dd 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -421,6 +421,9 @@
 // bazel-compatible labels.  Properties passed as the paths or excludes argument must have been
 // annotated with struct tag `android:"path"` so that dependencies on other modules will have
 // already been handled by the path_properties mutator.
+//
+// With expanded globs, we can catch package boundaries problem instead of
+// silently failing to potentially missing files from Bazel's globs.
 func BazelLabelForModuleSrc(ctx BazelConversionPathContext, paths []string) bazel.LabelList {
 	return BazelLabelForModuleSrcExcludes(ctx, paths, []string(nil))
 }
@@ -431,6 +434,9 @@
 // passed as the paths or excludes argument must have been annotated with struct tag
 // `android:"path"` so that dependencies on other modules will have already been handled by the
 // path_properties mutator.
+//
+// With expanded globs, we can catch package boundaries problem instead of
+// silently failing to potentially missing files from Bazel's globs.
 func BazelLabelForModuleSrcExcludes(ctx BazelConversionPathContext, paths, excludes []string) bazel.LabelList {
 	excludeLabels := expandSrcsForBazel(ctx, excludes, []string(nil))
 	excluded := make([]string, 0, len(excludeLabels.Includes))
diff --git a/android/variable.go b/android/variable.go
index dff48c2..f25143d 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -385,6 +385,8 @@
 	BuildBrokenTrebleSyspropNeverallow bool `json:",omitempty"`
 	BuildBrokenVendorPropertyNamespace bool `json:",omitempty"`
 
+	BuildDebugfsRestrictionsEnabled bool `json:",omitempty"`
+
 	RequiresInsecureExecmemForSwiftshader bool `json:",omitempty"`
 
 	SelinuxIgnoreNeverallows bool `json:",omitempty"`
diff --git a/apex/Android.bp b/apex/Android.bp
index 1890b89..7b52402 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -31,6 +31,7 @@
     testSrcs: [
         "apex_test.go",
         "boot_image_test.go",
+        "platform_bootclasspath_test.go",
         "vndk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/apex/apex.go b/apex/apex.go
index bad382a..880028f 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1927,6 +1927,10 @@
 						filesInfo = append(filesInfo, af)
 						return true // track transitive dependencies
 					}
+				} else if rust.IsRlibDepTag(depTag) {
+					// Rlib is statically linked, but it might have shared lib
+					// dependencies. Track them.
+					return true
 				} else if java.IsbootImageContentDepTag(depTag) {
 					// Add the contents of the boot image to the apex.
 					switch child.(type) {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 54aead7..ee4255e 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -392,6 +392,15 @@
 			srcs: ["foo.rs"],
 			crate_name: "foo",
 			apex_available: ["myapex"],
+			shared_libs: ["libfoo.shared_from_rust"],
+		}
+
+		cc_library_shared {
+			name: "libfoo.shared_from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: ["myapex"],
 		}
 
 		rust_library_dylib {
@@ -539,6 +548,7 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libbar.ffi"), "android_arm64_armv8-a_shared_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.shared_from_rust"), "android_arm64_armv8-a_shared_apex10000")
 
 	// Ensure that both direct and indirect deps are copied into apex
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
@@ -548,6 +558,7 @@
 	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.ffi.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/libbar.ffi.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
 	// .. but not for java libs
 	ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
 	ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")
@@ -4605,6 +4616,40 @@
 `)
 	})
 
+	t.Run("apex_set only", func(t *testing.T) {
+		bp := `
+		apex_set {
+			name: "myapex",
+			set: "myapex.apks",
+			exported_java_libs: ["libfoo", "libbar"],
+		}
+
+		java_import {
+			name: "libfoo",
+			jars: ["libfoo.jar"],
+			apex_available: ["myapex"],
+		}
+
+		java_sdk_library_import {
+			name: "libbar",
+			public: {
+				jars: ["libbar.jar"],
+			},
+			apex_available: ["myapex"],
+		}
+	`
+
+		ctx := testDexpreoptWithApexes(t, bp, "", transform)
+		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
+
+		// Make sure that the dex file from the apex_set contributes to the hiddenapi index file.
+		checkHiddenAPIIndexInputs(t, ctx, `
+.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
+.intermediates/libfoo/android_common_myapex/hiddenapi/index.csv
+`)
+	})
+
 	t.Run("prebuilt with source library preferred", func(t *testing.T) {
 		bp := `
 		prebuilt_apex {
@@ -6368,8 +6413,7 @@
 }
 
 func TestAppSetBundlePrebuilt(t *testing.T) {
-	ctx := testApex(t, "", android.FixtureModifyMockFS(func(fs android.MockFS) {
-		bp := `
+	bp := `
 		apex_set {
 			name: "myapex",
 			filename: "foo_v2.apex",
@@ -6377,24 +6421,23 @@
 				none: { set: "myapex.apks", },
 				hwaddress: { set: "myapex.hwasan.apks", },
 			},
-		}`
-		fs["Android.bp"] = []byte(bp)
-	}),
-		prepareForTestWithSantitizeHwaddress,
-	)
+		}
+	`
+	ctx := testApex(t, bp, prepareForTestWithSantitizeHwaddress)
 
-	m := ctx.ModuleForTests("myapex", "android_common")
-	extractedApex := m.Output("out/soong/.intermediates/myapex/android_common/foo_v2.apex")
+	// Check that the extractor produces the correct output file from the correct input file.
+	extractorOutput := "out/soong/.intermediates/myapex.apex.extractor/android_common/extracted/myapex.hwasan.apks"
 
-	actual := extractedApex.Inputs
-	if len(actual) != 1 {
-		t.Errorf("expected a single input")
-	}
+	m := ctx.ModuleForTests("myapex.apex.extractor", "android_common")
+	extractedApex := m.Output(extractorOutput)
 
-	expected := "myapex.hwasan.apks"
-	if actual[0].String() != expected {
-		t.Errorf("expected %s, got %s", expected, actual[0].String())
-	}
+	android.AssertArrayString(t, "extractor input", []string{"myapex.hwasan.apks"}, extractedApex.Inputs.Strings())
+
+	// Ditto for the apex.
+	m = ctx.ModuleForTests("myapex", "android_common")
+	copiedApex := m.Output("out/soong/.intermediates/myapex/android_common/foo_v2.apex")
+
+	android.AssertStringEquals(t, "myapex input", extractorOutput, copiedApex.Input.String())
 }
 
 func testNoUpdatableJarsInBootImage(t *testing.T, errmsg string, transformDexpreoptConfig func(*dexpreopt.GlobalConfig)) {
@@ -7030,10 +7073,10 @@
 		}),
 	)
 
-	m := ctx.ModuleForTests("myapex", "android_common")
+	m := ctx.ModuleForTests("myapex.apex.extractor", "android_common")
 
 	// Check extract_apks tool parameters.
-	extractedApex := m.Output("out/soong/.intermediates/myapex/android_common/foo_v2.apex")
+	extractedApex := m.Output("extracted/myapex.apks")
 	actual := extractedApex.Args["abis"]
 	expected := "ARMEABI_V7A,ARM64_V8A"
 	if actual != expected {
@@ -7045,6 +7088,7 @@
 		t.Errorf("Unexpected abis parameter - expected %q vs actual %q", expected, actual)
 	}
 
+	m = ctx.ModuleForTests("myapex", "android_common")
 	a := m.Module().(*ApexSet)
 	expectedOverrides := []string{"foo"}
 	actualOverrides := android.AndroidMkEntriesForTest(t, ctx, a)[0].EntryMap["LOCAL_OVERRIDES_MODULES"]
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
new file mode 100644
index 0000000..2ea6401
--- /dev/null
+++ b/apex/platform_bootclasspath_test.go
@@ -0,0 +1,177 @@
+// 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 apex
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+	"android/soong/java"
+	"github.com/google/blueprint"
+)
+
+// Contains tests for platform_bootclasspath logic from java/platform_bootclasspath.go that requires
+// apexes.
+
+var prepareForTestWithPlatformBootclasspath = android.GroupFixturePreparers(
+	java.PrepareForTestWithDexpreopt,
+	PrepareForTestWithApexBuildComponents,
+)
+
+func TestPlatformBootclasspathDependencies(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithArtApex,
+		prepareForTestWithMyapex,
+		// Configure some libraries in the art and framework boot images.
+		dexpreopt.FixtureSetArtBootJars("com.android.art:baz", "com.android.art:quuz"),
+		dexpreopt.FixtureSetBootJars("platform:foo"),
+		dexpreopt.FixtureSetUpdatableBootJars("myapex:bar"),
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "com.android.art",
+			key: "com.android.art.key",
+ 			bootclasspath_fragments: [
+				"art-bootclasspath-fragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "com.android.art.key",
+			public_key: "com.android.art.avbpubkey",
+			private_key: "com.android.art.pem",
+		}
+
+		bootclasspath_fragment {
+			name: "art-bootclasspath-fragment",
+			apex_available: [
+				"com.android.art",
+			],
+			contents: [
+				"baz",
+				"quuz",
+			],
+		}
+
+		java_library {
+			name: "baz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		// Add a java_import that is not preferred and so won't have an appropriate apex variant created
+		// for it to make sure that the platform_bootclasspath doesn't try and add a dependency onto it.
+		java_import {
+			name: "baz",
+			apex_available: [
+				"com.android.art",
+			],
+			jars: ["b.jar"],
+		}
+
+		java_library {
+			name: "quuz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: [
+				"bar",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["bar"],
+		}
+
+		platform_bootclasspath {
+			name: "myplatform-bootclasspath",
+
+			fragments: [
+				{
+					apex: "com.android.art",
+					module: "art-bootclasspath-fragment",
+				},
+			],
+		}
+`,
+	)
+
+	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
+		"com.android.art:baz",
+		"com.android.art:quuz",
+		"platform:foo",
+		"myapex:bar",
+	})
+
+	java.CheckPlatformBootclasspathFragments(t, result, "myplatform-bootclasspath", []string{
+		`com.android.art:art-bootclasspath-fragment`,
+	})
+
+	// Make sure that the myplatform-bootclasspath has the correct dependencies.
+	CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
+		`platform:dex2oatd`,
+		`com.android.art:baz`,
+		`com.android.art:quuz`,
+		`platform:foo`,
+		`myapex:bar`,
+		`com.android.art:art-bootclasspath-fragment`,
+	})
+}
+
+// CheckModuleDependencies checks the dependencies of the selected module against the expected list.
+//
+// The expected list must be a list of strings of the form "<apex>:<module>", where <apex> is the
+// name of the apex, or platform is it is not part of an apex and <module> is the module name.
+func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
+	t.Helper()
+	module := ctx.ModuleForTests(name, variant).Module()
+	modules := []android.Module{}
+	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
+		modules = append(modules, m.(android.Module))
+	})
+
+	pairs := java.ApexNamePairsFromModules(ctx, modules)
+	android.AssertDeepEquals(t, "module dependencies", expected, pairs)
+}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 9d84281..10a70a3 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -48,6 +48,9 @@
 type prebuiltCommon struct {
 	prebuilt   android.Prebuilt
 	properties prebuiltCommonProperties
+
+	deapexerProperties     DeapexerProperties
+	selectedApexProperties SelectedApexProperties
 }
 
 type sanitizedPrebuilt interface {
@@ -91,6 +94,96 @@
 	return false
 }
 
+func (p *prebuiltCommon) deapexerDeps(ctx android.BottomUpMutatorContext) {
+	// Add dependencies onto the java modules that represent the java libraries that are provided by
+	// and exported from this prebuilt apex.
+	for _, lib := range p.deapexerProperties.Exported_java_libs {
+		dep := prebuiltApexExportedModuleName(ctx, lib)
+		ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), exportedJavaLibTag, dep)
+	}
+}
+
+// apexInfoMutator marks any modules for which this apex exports a file as requiring an apex
+// specific variant and checks that they are supported.
+//
+// The apexMutator will ensure that the ApexInfo objects passed to BuildForApex(ApexInfo) are
+// associated with the apex specific variant using the ApexInfoProvider for later retrieval.
+//
+// Unlike the source apex module type the prebuilt_apex module type cannot share compatible variants
+// across prebuilt_apex modules. That is because there is no way to determine whether two
+// prebuilt_apex modules that export files for the same module are compatible. e.g. they could have
+// been built from different source at different times or they could have been built with different
+// build options that affect the libraries.
+//
+// While it may be possible to provide sufficient information to determine whether two prebuilt_apex
+// modules were compatible it would be a lot of work and would not provide much benefit for a couple
+// of reasons:
+// * The number of prebuilt_apex modules that will be exporting files for the same module will be
+//   low as the prebuilt_apex only exports files for the direct dependencies that require it and
+//   very few modules are direct dependencies of multiple prebuilt_apex modules, e.g. there are a
+//   few com.android.art* apex files that contain the same contents and could export files for the
+//   same modules but only one of them needs to do so. Contrast that with source apex modules which
+//   need apex specific variants for every module that contributes code to the apex, whether direct
+//   or indirect.
+// * The build cost of a prebuilt_apex variant is generally low as at worst it will involve some
+//   extra copying of files. Contrast that with source apex modules that has to build each variant
+//   from source.
+func (p *prebuiltCommon) apexInfoMutator(mctx android.TopDownMutatorContext) {
+
+	// Collect direct dependencies into contents.
+	contents := make(map[string]android.ApexMembership)
+
+	// Collect the list of dependencies.
+	var dependencies []android.ApexModule
+	mctx.VisitDirectDeps(func(m android.Module) {
+		tag := mctx.OtherModuleDependencyTag(m)
+		if tag == exportedJavaLibTag {
+			depName := mctx.OtherModuleName(m)
+
+			// It is an error if the other module is not a prebuilt.
+			if _, ok := m.(android.PrebuiltInterface); !ok {
+				mctx.PropertyErrorf("exported_java_libs", "%q is not a prebuilt module", depName)
+				return
+			}
+
+			// It is an error if the other module is not an ApexModule.
+			if _, ok := m.(android.ApexModule); !ok {
+				mctx.PropertyErrorf("exported_java_libs", "%q is not usable within an apex", depName)
+				return
+			}
+
+			// Strip off the prebuilt_ prefix if present before storing content to ensure consistent
+			// behavior whether there is a corresponding source module present or not.
+			depName = android.RemoveOptionalPrebuiltPrefix(depName)
+
+			// Remember that this module was added as a direct dependency.
+			contents[depName] = contents[depName].Add(true)
+
+			// Add the module to the list of dependencies that need to have an APEX variant.
+			dependencies = append(dependencies, m.(android.ApexModule))
+		}
+	})
+
+	// Create contents for the prebuilt_apex and store it away for later use.
+	apexContents := android.NewApexContents(contents)
+	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
+		Contents: apexContents,
+	})
+
+	// Create an ApexInfo for the prebuilt_apex.
+	apexInfo := android.ApexInfo{
+		ApexVariationName: mctx.ModuleName(),
+		InApexes:          []string{mctx.ModuleName()},
+		ApexContents:      []*android.ApexContents{apexContents},
+		ForPrebuiltApex:   true,
+	}
+
+	// Mark the dependencies of this module as requiring a variant for this module.
+	for _, am := range dependencies {
+		am.BuildForApex(apexInfo)
+	}
+}
+
 // prebuiltApexSelectorModule is a private module type that is only created by the prebuilt_apex
 // module. It selects the apex to use and makes it available for use by prebuilt_apex and the
 // deapexer.
@@ -197,7 +290,6 @@
 
 type PrebuiltProperties struct {
 	ApexFileProperties
-	DeapexerProperties
 
 	Installable *bool
 	// Optional name for the installed apex. If unspecified, name of the
@@ -267,7 +359,7 @@
 //
 func PrebuiltFactory() android.Module {
 	module := &Prebuilt{}
-	module.AddProperties(&module.properties, &module.selectedApexProperties)
+	module.AddProperties(&module.properties, &module.deapexerProperties, &module.selectedApexProperties)
 	android.InitSingleSourcePrebuiltModule(module, &module.selectedApexProperties, "Selected_apex")
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 
@@ -278,8 +370,8 @@
 		createApexSelectorModule(ctx, apexSelectorModuleName, &module.properties.ApexFileProperties)
 
 		apexFileSource := ":" + apexSelectorModuleName
-		if len(module.properties.Exported_java_libs) != 0 {
-			createDeapexerModule(ctx, deapexerModuleName(baseModuleName), apexFileSource, &module.properties.DeapexerProperties)
+		if len(module.deapexerProperties.Exported_java_libs) != 0 {
+			createDeapexerModule(ctx, deapexerModuleName(baseModuleName), apexFileSource, &module.deapexerProperties)
 		}
 
 		// Add a source reference to retrieve the selected apex from the selector module.
@@ -363,95 +455,13 @@
 )
 
 func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) {
-	// Add dependencies onto the java modules that represent the java libraries that are provided by
-	// and exported from this prebuilt apex.
-	for _, lib := range p.properties.Exported_java_libs {
-		dep := prebuiltApexExportedModuleName(ctx, lib)
-		ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), exportedJavaLibTag, dep)
-	}
+	p.deapexerDeps(ctx)
 }
 
 var _ ApexInfoMutator = (*Prebuilt)(nil)
 
-// ApexInfoMutator marks any modules for which this apex exports a file as requiring an apex
-// specific variant and checks that they are supported.
-//
-// The apexMutator will ensure that the ApexInfo objects passed to BuildForApex(ApexInfo) are
-// associated with the apex specific variant using the ApexInfoProvider for later retrieval.
-//
-// Unlike the source apex module type the prebuilt_apex module type cannot share compatible variants
-// across prebuilt_apex modules. That is because there is no way to determine whether two
-// prebuilt_apex modules that export files for the same module are compatible. e.g. they could have
-// been built from different source at different times or they could have been built with different
-// build options that affect the libraries.
-//
-// While it may be possible to provide sufficient information to determine whether two prebuilt_apex
-// modules were compatible it would be a lot of work and would not provide much benefit for a couple
-// of reasons:
-// * The number of prebuilt_apex modules that will be exporting files for the same module will be
-//   low as the prebuilt_apex only exports files for the direct dependencies that require it and
-//   very few modules are direct dependencies of multiple prebuilt_apex modules, e.g. there are a
-//   few com.android.art* apex files that contain the same contents and could export files for the
-//   same modules but only one of them needs to do so. Contrast that with source apex modules which
-//   need apex specific variants for every module that contributes code to the apex, whether direct
-//   or indirect.
-// * The build cost of a prebuilt_apex variant is generally low as at worst it will involve some
-//   extra copying of files. Contrast that with source apex modules that has to build each variant
-//   from source.
 func (p *Prebuilt) ApexInfoMutator(mctx android.TopDownMutatorContext) {
-
-	// Collect direct dependencies into contents.
-	contents := make(map[string]android.ApexMembership)
-
-	// Collect the list of dependencies.
-	var dependencies []android.ApexModule
-	mctx.VisitDirectDeps(func(m android.Module) {
-		tag := mctx.OtherModuleDependencyTag(m)
-		if tag == exportedJavaLibTag {
-			depName := mctx.OtherModuleName(m)
-
-			// It is an error if the other module is not a prebuilt.
-			if _, ok := m.(android.PrebuiltInterface); !ok {
-				mctx.PropertyErrorf("exported_java_libs", "%q is not a prebuilt module", depName)
-				return
-			}
-
-			// It is an error if the other module is not an ApexModule.
-			if _, ok := m.(android.ApexModule); !ok {
-				mctx.PropertyErrorf("exported_java_libs", "%q is not usable within an apex", depName)
-				return
-			}
-
-			// Strip off the prebuilt_ prefix if present before storing content to ensure consistent
-			// behavior whether there is a corresponding source module present or not.
-			depName = android.RemoveOptionalPrebuiltPrefix(depName)
-
-			// Remember that this module was added as a direct dependency.
-			contents[depName] = contents[depName].Add(true)
-
-			// Add the module to the list of dependencies that need to have an APEX variant.
-			dependencies = append(dependencies, m.(android.ApexModule))
-		}
-	})
-
-	// Create contents for the prebuilt_apex and store it away for later use.
-	apexContents := android.NewApexContents(contents)
-	mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{
-		Contents: apexContents,
-	})
-
-	// Create an ApexInfo for the prebuilt_apex.
-	apexInfo := android.ApexInfo{
-		ApexVariationName: mctx.ModuleName(),
-		InApexes:          []string{mctx.ModuleName()},
-		ApexContents:      []*android.ApexContents{apexContents},
-		ForPrebuiltApex:   true,
-	}
-
-	// Mark the dependencies of this module as requiring a variant for this module.
-	for _, am := range dependencies {
-		am.BuildForApex(apexInfo)
-	}
+	p.apexInfoMutator(mctx)
 }
 
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -505,6 +515,49 @@
 	}}
 }
 
+// prebuiltApexExtractorModule is a private module type that is only created by the prebuilt_apex
+// module. It extracts the correct apex to use and makes it available for use by apex_set.
+type prebuiltApexExtractorModule struct {
+	android.ModuleBase
+
+	properties ApexExtractorProperties
+
+	extractedApex android.WritablePath
+}
+
+func privateApexExtractorModuleFactory() android.Module {
+	module := &prebuiltApexExtractorModule{}
+	module.AddProperties(
+		&module.properties,
+	)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (p *prebuiltApexExtractorModule) Srcs() android.Paths {
+	return android.Paths{p.extractedApex}
+}
+
+func (p *prebuiltApexExtractorModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	srcsSupplier := func(ctx android.BaseModuleContext, prebuilt android.Module) []string {
+		return p.properties.prebuiltSrcs(ctx)
+	}
+	apexSet := android.SingleSourcePathFromSupplier(ctx, srcsSupplier, "set")
+	p.extractedApex = android.PathForModuleOut(ctx, "extracted", apexSet.Base())
+	ctx.Build(pctx,
+		android.BuildParams{
+			Rule:        extractMatchingApex,
+			Description: "Extract an apex from an apex set",
+			Inputs:      android.Paths{apexSet},
+			Output:      p.extractedApex,
+			Args: map[string]string{
+				"abis":              strings.Join(java.SupportedAbis(ctx), ","),
+				"allow-prereleased": strconv.FormatBool(proptools.Bool(p.properties.Prerelease)),
+				"sdk-version":       ctx.Config().PlatformSdkVersion().String(),
+			},
+		})
+}
+
 type ApexSet struct {
 	android.ModuleBase
 	prebuiltCommon
@@ -523,7 +576,7 @@
 	postInstallCommands []string
 }
 
-type ApexSetProperties struct {
+type ApexExtractorProperties struct {
 	// the .apks file path that contains prebuilt apex files to be extracted.
 	Set *string
 
@@ -539,6 +592,37 @@
 		}
 	}
 
+	// apexes in this set use prerelease SDK version
+	Prerelease *bool
+}
+
+func (e *ApexExtractorProperties) prebuiltSrcs(ctx android.BaseModuleContext) []string {
+	var srcs []string
+	if e.Set != nil {
+		srcs = append(srcs, *e.Set)
+	}
+
+	var sanitizers []string
+	if ctx.Host() {
+		sanitizers = ctx.Config().SanitizeHost()
+	} else {
+		sanitizers = ctx.Config().SanitizeDevice()
+	}
+
+	if android.InList("address", sanitizers) && e.Sanitized.Address.Set != nil {
+		srcs = append(srcs, *e.Sanitized.Address.Set)
+	} else if android.InList("hwaddress", sanitizers) && e.Sanitized.Hwaddress.Set != nil {
+		srcs = append(srcs, *e.Sanitized.Hwaddress.Set)
+	} else if e.Sanitized.None.Set != nil {
+		srcs = append(srcs, *e.Sanitized.None.Set)
+	}
+
+	return srcs
+}
+
+type ApexSetProperties struct {
+	ApexExtractorProperties
+
 	// whether the extracted apex file installable.
 	Installable *bool
 
@@ -552,33 +636,6 @@
 	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
 	// from PRODUCT_PACKAGES.
 	Overrides []string
-
-	// apexes in this set use prerelease SDK version
-	Prerelease *bool
-}
-
-func (a *ApexSet) prebuiltSrcs(ctx android.BaseModuleContext) []string {
-	var srcs []string
-	if a.properties.Set != nil {
-		srcs = append(srcs, *a.properties.Set)
-	}
-
-	var sanitizers []string
-	if ctx.Host() {
-		sanitizers = ctx.Config().SanitizeHost()
-	} else {
-		sanitizers = ctx.Config().SanitizeDevice()
-	}
-
-	if android.InList("address", sanitizers) && a.properties.Sanitized.Address.Set != nil {
-		srcs = append(srcs, *a.properties.Sanitized.Address.Set)
-	} else if android.InList("hwaddress", sanitizers) && a.properties.Sanitized.Hwaddress.Set != nil {
-		srcs = append(srcs, *a.properties.Sanitized.Hwaddress.Set)
-	} else if a.properties.Sanitized.None.Set != nil {
-		srcs = append(srcs, *a.properties.Sanitized.None.Set)
-	}
-
-	return srcs
 }
 
 func (a *ApexSet) hasSanitizedSource(sanitizer string) bool {
@@ -611,15 +668,54 @@
 // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
 func apexSetFactory() android.Module {
 	module := &ApexSet{}
-	module.AddProperties(&module.properties)
+	module.AddProperties(&module.properties, &module.selectedApexProperties, &module.deapexerProperties)
 
-	srcsSupplier := func(ctx android.BaseModuleContext, _ android.Module) []string {
-		return module.prebuiltSrcs(ctx)
+	android.InitSingleSourcePrebuiltModule(module, &module.selectedApexProperties, "Selected_apex")
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		baseModuleName := module.BaseModuleName()
+
+		apexExtractorModuleName := apexExtractorModuleName(baseModuleName)
+		createApexExtractorModule(ctx, apexExtractorModuleName, &module.properties.ApexExtractorProperties)
+
+		apexFileSource := ":" + apexExtractorModuleName
+		if len(module.deapexerProperties.Exported_java_libs) != 0 {
+			createDeapexerModule(ctx, deapexerModuleName(baseModuleName), apexFileSource, &module.deapexerProperties)
+		}
+
+		// After passing the arch specific src properties to the creating the apex selector module
+		module.selectedApexProperties.Selected_apex = proptools.StringPtr(apexFileSource)
+	})
+
+	return module
+}
+
+func createApexExtractorModule(ctx android.LoadHookContext, name string, apexExtractorProperties *ApexExtractorProperties) {
+	props := struct {
+		Name *string
+	}{
+		Name: proptools.StringPtr(name),
 	}
 
-	android.InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "set")
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-	return module
+	ctx.CreateModule(privateApexExtractorModuleFactory,
+		&props,
+		apexExtractorProperties,
+	)
+}
+
+func apexExtractorModuleName(baseModuleName string) string {
+	return baseModuleName + ".apex.extractor"
+}
+
+func (a *ApexSet) DepsMutator(ctx android.BottomUpMutatorContext) {
+	a.deapexerDeps(ctx)
+}
+
+var _ ApexInfoMutator = (*ApexSet)(nil)
+
+func (a *ApexSet) ApexInfoMutator(mctx android.TopDownMutatorContext) {
+	a.apexInfoMutator(mctx)
 }
 
 func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -628,20 +724,13 @@
 		ctx.ModuleErrorf("filename should end in %s for apex_set", imageApexSuffix)
 	}
 
-	apexSet := a.prebuiltCommon.prebuilt.SingleSourcePath(ctx)
+	inputApex := android.OptionalPathForModuleSrc(ctx, a.selectedApexProperties.Selected_apex).Path()
 	a.outputApex = android.PathForModuleOut(ctx, a.installFilename)
-	ctx.Build(pctx,
-		android.BuildParams{
-			Rule:        extractMatchingApex,
-			Description: "Extract an apex from an apex set",
-			Inputs:      android.Paths{apexSet},
-			Output:      a.outputApex,
-			Args: map[string]string{
-				"abis":              strings.Join(java.SupportedAbis(ctx), ","),
-				"allow-prereleased": strconv.FormatBool(proptools.Bool(a.properties.Prerelease)),
-				"sdk-version":       ctx.Config().PlatformSdkVersion().String(),
-			},
-		})
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   android.Cp,
+		Input:  inputApex,
+		Output: a.outputApex,
+	})
 
 	if a.prebuiltCommon.checkForceDisable(ctx) {
 		a.HideFromMake()
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index affb5ce..b88da2f 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -5,8 +5,9 @@
 )
 
 var (
-	GetOutputFiles                 RequestType = &getOutputFilesRequestType{}
-	GetOutputFilesAndCcObjectFiles RequestType = &getOutputFilesAndCcObjectFilesType{}
+	GetOutputFiles                  = &getOutputFilesRequestType{}
+	GetOutputFilesAndCcObjectFiles  = &getOutputFilesAndCcObjectFilesType{}
+	GetPrebuiltCcStaticLibraryFiles = &getPrebuiltCcStaticLibraryFiles{}
 )
 
 type GetOutputFilesAndCcObjectFiles_Result struct {
@@ -14,49 +15,49 @@
 	CcObjectFiles []string
 }
 
-type RequestType interface {
-	// Name returns a string name for this request type. Such request type names must be unique,
-	// and must only consist of alphanumeric characters.
-	Name() string
-
-	// StarlarkFunctionBody returns a straark 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.
-	StarlarkFunctionBody() string
-
-	// 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.
-	// The type of this value depends on the request type; it is up to the caller to
-	// cast to the correct type.
-	ParseResult(rawString string) interface{}
-}
-
 type getOutputFilesRequestType 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 {
 	return "getOutputFiles"
 }
 
+// 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 getOutputFilesRequestType) StarlarkFunctionBody() string {
 	return "return ', '.join([f.path for f in target.files.to_list()])"
 }
 
-func (g getOutputFilesRequestType) ParseResult(rawString string) interface{} {
+// 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 getOutputFilesRequestType) ParseResult(rawString string) []string {
 	return strings.Split(rawString, ", ")
 }
 
 type getOutputFilesAndCcObjectFilesType 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 getOutputFilesAndCcObjectFilesType) Name() string {
 	return "getOutputFilesAndCcObjectFiles"
 }
 
+// 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 getOutputFilesAndCcObjectFilesType) StarlarkFunctionBody() string {
 	return `
 outputFiles = [f.path for f in target.files.to_list()]
@@ -71,7 +72,10 @@
 return ', '.join(outputFiles) + "|" + ', '.join(ccObjectFiles)`
 }
 
-func (g getOutputFilesAndCcObjectFilesType) ParseResult(rawString string) interface{} {
+// 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 getOutputFilesAndCcObjectFilesType) ParseResult(rawString string) GetOutputFilesAndCcObjectFiles_Result {
 	var outputFiles []string
 	var ccObjects []string
 
@@ -83,6 +87,34 @@
 	return GetOutputFilesAndCcObjectFiles_Result{outputFiles, ccObjects}
 }
 
+type getPrebuiltCcStaticLibraryFiles struct{}
+
+// Name returns the name of the starlark function to get prebuilt cc static library files
+func (g getPrebuiltCcStaticLibraryFiles) Name() string {
+	return "getPrebuiltCcStaticLibraryFiles"
+}
+
+// StarlarkFunctionBody returns the unindented body of a starlark function for extracting the static
+// library paths from a cc_import module.
+func (g getPrebuiltCcStaticLibraryFiles) StarlarkFunctionBody() string {
+	return `
+linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
+
+static_libraries = []
+for linker_input in linker_inputs:
+  for library in linker_input.libraries:
+    static_libraries.append(library.static_library.path)
+
+return ', '.join(static_libraries)`
+}
+
+// ParseResult returns a slice of bazel output paths to static libraries if any exist for the given
+// rawString corresponding to the string output which was created by evaluating the
+// StarlarkFunctionBody.
+func (g getPrebuiltCcStaticLibraryFiles) ParseResult(rawString string) []string {
+	return strings.Split(rawString, ", ")
+}
+
 // splitOrEmpty is a modification of strings.Split() that returns an empty list
 // if the given string is empty.
 func splitOrEmpty(s string, sep string) []string {
diff --git a/bazel/properties.go b/bazel/properties.go
index 2440ca1..4bb2391 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"regexp"
 	"sort"
 )
@@ -47,6 +48,57 @@
 	Excludes []Label
 }
 
+// GlobsInDir returns a list of glob expressions for a list of extensions
+// (optionally recursive) within a directory.
+func GlobsInDir(dir string, recursive bool, extensions []string) []string {
+	globs := []string{}
+
+	globInfix := ""
+	if dir == "." {
+		if recursive {
+			// e.g "**/*.h"
+			globInfix = "**/"
+		} // else e.g. "*.h"
+		for _, ext := range extensions {
+			globs = append(globs, globInfix+"*"+ext)
+		}
+	} else {
+		if recursive {
+			// e.g. "foo/bar/**/*.h"
+			dir += "/**"
+		} // else e.g. "foo/bar/*.h"
+		for _, ext := range extensions {
+			globs = append(globs, dir+"/*"+ext)
+		}
+	}
+	return globs
+}
+
+// LooseHdrsGlobs returns the list of non-recursive header globs for each parent directory of
+// each source file in this LabelList's Includes.
+func (ll *LabelList) LooseHdrsGlobs(exts []string) []string {
+	var globs []string
+	for _, parentDir := range ll.uniqueParentDirectories() {
+		globs = append(globs,
+			GlobsInDir(parentDir, false, exts)...)
+	}
+	return globs
+}
+
+// uniqueParentDirectories returns a list of the unique parent directories for
+// all files in ll.Includes.
+func (ll *LabelList) uniqueParentDirectories() []string {
+	dirMap := map[string]bool{}
+	for _, label := range ll.Includes {
+		dirMap[filepath.Dir(label.Label)] = true
+	}
+	dirs := []string{}
+	for dir := range dirMap {
+		dirs = append(dirs, dir)
+	}
+	return dirs
+}
+
 // 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 {
@@ -79,6 +131,63 @@
 	return uniqueLabelList
 }
 
+// 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
+	}
+	for _, s := range needle {
+		delete(remainder, s)
+	}
+
+	var strings []string
+	for s, _ := range remainder {
+		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)
+	}
+
+	var labels []Label
+	for label, _ := range remainder {
+		labels = append(labels, label)
+	}
+
+	sort.SliceStable(labels, func(i, j int) bool {
+		return labels[i].Label < labels[j].Label
+	})
+
+	return labels
+}
+
+// Subtract needle from haystack
+func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
+	var result LabelList
+	result.Includes = SubtractBazelLabels(haystack.Includes, needle.Includes)
+	// NOTE: Excludes are intentionally not subtracted
+	result.Excludes = haystack.Excludes
+	return result
+}
+
 const (
 	// ArchType names in arch.go
 	ARCH_ARM    = "arm"
@@ -167,6 +276,26 @@
 	return LabelListAttribute{Value: UniqueBazelLabelList(value)}
 }
 
+// Append appends 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 {
@@ -257,6 +386,12 @@
 	OsValues stringListOsValues
 }
 
+// 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}
+}
+
 // 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 {
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 0fcb904..56840ef 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -46,6 +46,82 @@
 	}
 }
 
+func TestSubtractStrings(t *testing.T) {
+	testCases := []struct {
+		haystack       []string
+		needle         []string
+		expectedResult []string
+	}{
+		{
+			haystack: []string{
+				"a",
+				"b",
+				"c",
+			},
+			needle: []string{
+				"a",
+			},
+			expectedResult: []string{
+				"b", "c",
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualResult := SubtractStrings(tc.haystack, tc.needle)
+		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
+			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
+		}
+	}
+}
+
+func TestSubtractBazelLabelList(t *testing.T) {
+	testCases := []struct {
+		haystack       LabelList
+		needle         LabelList
+		expectedResult LabelList
+	}{
+		{
+			haystack: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+			needle: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+				},
+				Excludes: []Label{
+					{Label: "z"},
+				},
+			},
+			// NOTE: Excludes are intentionally not subtracted
+			expectedResult: LabelList{
+				Includes: []Label{
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualResult := SubtractBazelLabelList(tc.haystack, tc.needle)
+		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
+			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
+		}
+	}
+}
 func TestUniqueBazelLabelList(t *testing.T) {
 	testCases := []struct {
 		originalLabelList       LabelList
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 653c489..764cded 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -23,7 +23,7 @@
 )
 
 const bloatyDescriptorExt = ".bloaty.csv"
-const protoFilename = "binary_sizes.pb"
+const protoFilename = "binary_sizes.pb.gz"
 
 var (
 	fileSizeMeasurerKey blueprint.ProviderKey
diff --git a/bloaty/bloaty_merger.py b/bloaty/bloaty_merger.py
index c873fb8..1034462 100644
--- a/bloaty/bloaty_merger.py
+++ b/bloaty/bloaty_merger.py
@@ -16,12 +16,13 @@
 Merges a list of .csv files from Bloaty into a protobuf.  It takes the list as
 a first argument and the output as second. For instance:
 
-    $ bloaty_merger binary_sizes.lst binary_sizes.pb
+    $ bloaty_merger binary_sizes.lst binary_sizes.pb.gz
 
 """
 
 import argparse
 import csv
+import gzip
 
 import ninja_rsp
 
@@ -57,7 +58,8 @@
   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.
+    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)
@@ -65,7 +67,7 @@
     file_proto = parse_csv(csv_path)
     if file_proto:
       metrics.files.append(file_proto)
-  with open(output_proto, "wb") as output:
+  with gzip.open(output_proto, "wb") as output:
     output.write(metrics.SerializeToString())
 
 def main():
diff --git a/bloaty/bloaty_merger_test.py b/bloaty/bloaty_merger_test.py
index 0e3641d..9de049a 100644
--- a/bloaty/bloaty_merger_test.py
+++ b/bloaty/bloaty_merger_test.py
@@ -11,6 +11,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.
+import gzip
 import unittest
 
 from pyfakefs import fake_filesystem_unittest
@@ -53,10 +54,10 @@
     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")
+    bloaty_merger.create_file_size_metrics("files.lst", "output.pb.gz")
 
     metrics = file_sections_pb2.FileSizeMetrics()
-    with open("output.pb", "rb") as output:
+    with gzip.open("output.pb.gz", "rb") as output:
       metrics.ParseFromString(output.read())
 
 
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index cc616f2..d2a8729 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -26,6 +26,7 @@
     testSrcs: [
         "build_conversion_test.go",
         "bzl_conversion_test.go",
+        "cc_library_conversion_test.go",
         "cc_library_headers_conversion_test.go",
         "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index dd14c7d..b7a2810 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -374,16 +374,9 @@
 		// value>)" to set the default value of unset attributes. In the cases
 		// where the bp2build converter didn't set the default value within the
 		// mutator when creating the BazelTargetModule, this would be a zero
-		// value. For those cases, we return a non-surprising default value so
-		// generated BUILD files are syntactically correct.
-		switch propertyValue.Kind() {
-		case reflect.Slice:
-			return "[]", nil
-		case reflect.Map:
-			return "{}", nil
-		default:
-			return "", nil
-		}
+		// value. For those cases, we return an empty string so we don't
+		// unnecessarily generate empty values.
+		return "", nil
 	}
 
 	var ret string
@@ -397,21 +390,38 @@
 	case reflect.Ptr:
 		return prettyPrint(propertyValue.Elem(), indent)
 	case reflect.Slice:
-		ret = "[\n"
-		for i := 0; i < propertyValue.Len(); i++ {
-			indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
+		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)
 			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 indexedValue != "" {
+					ret += makeIndent(indent + 1)
+					ret += indexedValue
+					ret += ",\n"
+				}
 			}
+			ret += makeIndent(indent)
+			ret += "]"
 		}
-		ret += makeIndent(indent)
-		ret += "]"
+
 	case reflect.Struct:
 		// Special cases where the bp2build sends additional information to the codegenerator
 		// by wrapping the attributes in a custom struct type.
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 49897b3..1ede442 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -27,9 +27,7 @@
 		expectedBazelTarget string
 	}{
 		{
-			bp: `custom {
-	name: "foo",
-}
+			bp: `custom { name: "foo" }
 		`,
 			expectedBazelTarget: `soong_module(
     name = "foo",
@@ -85,9 +83,7 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
-    required = [
-        "bar",
-    ],
+    required = ["bar"],
 )`,
 		},
 		{
@@ -116,12 +112,10 @@
 		targets: ["goal_foo"],
 		tag: ".foo",
 	},
-	dists: [
-		{
-			targets: ["goal_bar"],
-			tag: ".bar",
-		},
-	],
+	dists: [{
+		targets: ["goal_bar"],
+		tag: ".bar",
+	}],
 }
 		`,
 			expectedBazelTarget: `soong_module(
@@ -133,18 +127,12 @@
     ],
     dist = {
         "tag": ".foo",
-        "targets": [
-            "goal_foo",
-        ],
+        "targets": ["goal_foo"],
     },
-    dists = [
-        {
-            "tag": ".bar",
-            "targets": [
-                "goal_bar",
-            ],
-        },
-    ],
+    dists = [{
+        "tag": ".bar",
+        "targets": ["goal_bar"],
+    }],
 )`,
 		},
 		{
@@ -169,19 +157,13 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
-    dists = [
-        {
-            "tag": ".tag",
-            "targets": [
-                "my_goal",
-            ],
-        },
-    ],
+    dists = [{
+        "tag": ".tag",
+        "targets": ["my_goal"],
+    }],
     owner = "custom_owner",
     ramdisk = True,
-    required = [
-        "bar",
-    ],
+    required = ["bar"],
     target_required = [
         "qux",
         "bazqux",
@@ -553,9 +535,7 @@
 }`,
 			expectedBazelTargets: []string{`filegroup(
     name = "fg_foo",
-    srcs = [
-        "b",
-    ],
+    srcs = ["b"],
 )`,
 			},
 		},
@@ -625,7 +605,7 @@
 			bp: `filegroup {
     name: "foobar",
     srcs: [
-      ":foo",
+        ":foo",
         "c",
     ],
     bazel_module: { bp2build_available: true },
@@ -671,25 +651,15 @@
 				`genrule(
     name = "foo",
     cmd = "$(location :foo.tool) --genDir=$(GENDIR) arg $(SRCS) $(OUTS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
-    tools = [
-        ":foo.tool",
-    ],
+    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",
-    ],
+    outs = ["foo_tool.out"],
+    srcs = ["foo_tool.in"],
 )`,
 			},
 		},
@@ -718,15 +688,9 @@
 			expectedBazelTargets: []string{`genrule(
     name = "foo",
     cmd = "$(locations :foo.tools) -s $(OUTS) $(SRCS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
-    tools = [
-        ":foo.tools",
-    ],
+    outs = ["foo.out"],
+    srcs = ["foo.in"],
+    tools = [":foo.tools"],
 )`,
 				`genrule(
     name = "foo.tools",
@@ -735,9 +699,7 @@
         "foo_tool.out",
         "foo_tool2.out",
     ],
-    srcs = [
-        "foo_tool.in",
-    ],
+    srcs = ["foo_tool.in"],
 )`,
 			},
 		},
@@ -758,15 +720,9 @@
 			expectedBazelTargets: []string{`genrule(
     name = "foo",
     cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
-    tools = [
-        "//other:foo.tool",
-    ],
+    outs = ["foo.out"],
+    srcs = ["foo.in"],
+    tools = ["//other:foo.tool"],
 )`,
 			},
 			fs: otherGenruleBp,
@@ -788,15 +744,9 @@
 			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",
-    ],
+    outs = ["foo.out"],
+    srcs = ["//other:other.tool"],
+    tools = ["//other:foo.tool"],
 )`,
 			},
 			fs: otherGenruleBp,
@@ -818,12 +768,8 @@
 			expectedBazelTargets: []string{`genrule(
     name = "foo",
     cmd = "$(location //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
+    outs = ["foo.out"],
+    srcs = ["foo.in"],
     tools = [
         "//other:foo.tool",
         "//other:other.tool",
@@ -849,12 +795,8 @@
 			expectedBazelTargets: []string{`genrule(
     name = "foo",
     cmd = "$(locations //other:foo.tool) -s $(OUTS) $(SRCS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
+    outs = ["foo.out"],
+    srcs = ["foo.in"],
     tools = [
         "//other:foo.tool",
         "//other:other.tool",
@@ -879,12 +821,8 @@
 			expectedBazelTargets: []string{`genrule(
     name = "foo",
     cmd = "cp $(SRCS) $(OUTS)",
-    outs = [
-        "foo.out",
-    ],
-    srcs = [
-        "foo.in",
-    ],
+    outs = ["foo.out"],
+    srcs = ["foo.in"],
 )`,
 			},
 		},
@@ -988,12 +926,8 @@
 			expectedBazelTarget: `genrule(
     name = "gen",
     cmd = "do-something $(SRCS) $(OUTS)",
-    outs = [
-        "out",
-    ],
-    srcs = [
-        "in1",
-    ],
+    outs = ["out"],
+    srcs = ["in1"],
 )`,
 			description: "genrule applies properties from a genrule_defaults dependency if not specified",
 		},
@@ -1062,12 +996,8 @@
 			expectedBazelTarget: `genrule(
     name = "gen",
     cmd = "cp $(SRCS) $(OUTS)",
-    outs = [
-        "out",
-    ],
-    srcs = [
-        "in1",
-    ],
+    outs = ["out"],
+    srcs = ["in1"],
 )`,
 			description: "genrule applies properties from list of genrule_defaults",
 		},
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
new file mode 100644
index 0000000..783af2e
--- /dev/null
+++ b/bp2build/cc_library_conversion_test.go
@@ -0,0 +1,271 @@
+// 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"
+	"strings"
+	"testing"
+)
+
+const (
+	// See cc/testing.go for more context
+	soongCcLibraryPreamble = `
+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: "",
+}`
+)
+
+func TestCcLibraryBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		bp                                 string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+		dir                                string
+	}{
+		{
+			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" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    cflags: ["-Wall"],
+    header_libs: ["some-headers"],
+    export_include_dirs: ["foo-dir"],
+    ldflags: ["-Wl,--exclude-libs=bar.a"],
+    arch: {
+        x86: {
+            ldflags: ["-Wl,--exclude-libs=baz.a"],
+            srcs: ["x86.cpp"],
+        },
+        x86_64: {
+            ldflags: ["-Wl,--exclude-libs=qux.a"],
+            srcs: ["x86_64.cpp"],
+        },
+    },
+    target: {
+        android: {
+            srcs: ["android.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["linux.cpp"],
+        },
+        darwin: {
+            srcs: ["darwin.cpp"],
+        },
+    },
+}
+`,
+			expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = ["-Wall"],
+    deps = [":some-headers"],
+    hdrs = [
+        "header.h",
+        "header.hh",
+        "header.hpp",
+        "header.hxx",
+        "header.h++",
+        "header.inl",
+        "header.inc",
+        "header.ipp",
+        "header.h.generic",
+        "foo-dir/a.h",
+    ],
+    includes = ["foo-dir"],
+    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({
+        "//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:darwin": ["darwin.cpp"],
+        "//build/bazel/platforms/os:linux": ["linux.cpp"],
+        "//conditions:default": [],
+    }),
+)`},
+		},
+		{
+			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" }
+cc_library {
+    name: "fake-ld-android",
+    srcs: ["ld_android.cpp"],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Werror",
+    ],
+    header_libs: ["libc_headers"],
+    ldflags: [
+        "-Wl,--exclude-libs=libgcc.a",
+        "-Wl,--exclude-libs=libgcc_stripped.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-i686-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
+    ],
+    arch: {
+        x86: {
+            ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
+        },
+        x86_64: {
+            ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
+        },
+    },
+}
+`,
+			expectedBazelTargets: []string{`cc_library(
+    name = "fake-ld-android",
+    copts = [
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Werror",
+    ],
+    deps = [":libc_headers"],
+    hdrs = [
+        "linked_list.h",
+        "linker.h",
+        "linker_block_allocator.h",
+        "linker_cfi.h",
+    ],
+    linkopts = [
+        "-Wl,--exclude-libs=libgcc.a",
+        "-Wl,--exclude-libs=libgcc_stripped.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-i686-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
+    ] + select({
+        "//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"],
+)`},
+		},
+	}
+
+	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)
+
+		cc.RegisterCCBuildComponents(ctx)
+		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
+		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,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index dbea37a..c59241f 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -23,7 +23,7 @@
 
 const (
 	// See cc/testing.go for more context
-	soongCcLibraryPreamble = `
+	soongCcLibraryHeadersPreamble = `
 cc_defaults {
 	name: "linux_bionic_supported",
 }
@@ -86,16 +86,19 @@
 			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": "",
+				"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: soongCcLibraryPreamble + `
+			bp: soongCcLibraryHeadersPreamble + `
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
@@ -111,6 +114,19 @@
     export_include_dirs: ["dir-1", "dir-2"],
     header_libs: ["lib-1", "lib-2"],
 
+    arch: {
+        arm64: {
+	    // 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: {
+            export_include_dirs: ["arch_x86_exported_include_dir"],
+        },
+        x86_64: {
+            export_include_dirs: ["arch_x86_64_exported_include_dir"],
+        },
+    },
+
     // TODO: Also support export_header_lib_headers
 }`,
 			expectedBazelTargets: []string{`cc_library_headers(
@@ -124,29 +140,35 @@
         "dir-1/dir1b.h",
         "dir-2/dir2a.h",
         "dir-2/dir2b.h",
-    ],
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": ["arch_arm64_exported_include_dir/a.h"],
+        "//build/bazel/platforms/arch:x86": ["arch_x86_exported_include_dir/b.h"],
+        "//build/bazel/platforms/arch:x86_64": ["arch_x86_64_exported_include_dir/c.h"],
+        "//conditions:default": [],
+    }),
     includes = [
         "dir-1",
         "dir-2",
-    ],
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": ["arch_arm64_exported_include_dir"],
+        "//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",
     hdrs = [
         "lib-1/lib1a.h",
         "lib-1/lib1b.h",
     ],
-    includes = [
-        "lib-1",
-    ],
+    includes = ["lib-1"],
 )`, `cc_library_headers(
     name = "lib-2",
     hdrs = [
         "lib-2/lib2a.h",
         "lib-2/lib2b.h",
     ],
-    includes = [
-        "lib-2",
-    ],
+    includes = ["lib-2"],
 )`},
 		},
 		{
@@ -185,27 +207,13 @@
     name = "darwin-lib",
 )`, `cc_library_headers(
     name = "foo_headers",
-    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",
-        ],
+    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(
@@ -240,7 +248,7 @@
     name = "exported-lib",
 )`, `cc_library_headers(
     name = "foo_headers",
-    deps = [] + select({
+    deps = select({
         "//build/bazel/platforms/os:android": [
             ":android-lib",
             ":exported-lib",
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 467b0b2..427aed3 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -101,6 +101,9 @@
 				"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 + `
 cc_library_headers {
@@ -191,6 +194,8 @@
         ":whole_static_lib_2",
     ],
     hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "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",
@@ -203,35 +208,74 @@
         "include_dir_2",
         "local_include_dir_1",
         "local_include_dir_2",
+        ".",
     ],
     linkstatic = True,
     srcs = [
         "foo_static1.cc",
         "foo_static2.cc",
+        "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",
+        "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",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "static_lib_1",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
+    includes = ["."],
     linkstatic = True,
     srcs = [
         "static_lib_1.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "static_lib_2",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
+    includes = ["."],
     linkstatic = True,
     srcs = [
         "static_lib_2.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_1",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
+    includes = ["."],
     linkstatic = True,
     srcs = [
         "whole_static_lib_1.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_2",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
+    includes = ["."],
     linkstatic = True,
     srcs = [
         "whole_static_lib_2.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`},
 		},
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 4f3babe..a9d24ac 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -52,7 +52,6 @@
         "-Werror",
     ],
     srcs: [
-        "a/b/*.h",
         "a/b/*.c"
     ],
     exclude_srcs: ["a/b/exclude.c"],
@@ -68,15 +67,15 @@
         "-Wall",
         "-Werror",
     ],
+    hdrs = [
+        "a/b/bar.h",
+        "a/b/foo.h",
+    ],
     local_include_dirs = [
         "include",
         ".",
     ],
-    srcs = [
-        "a/b/bar.h",
-        "a/b/c.c",
-        "a/b/foo.h",
-    ],
+    srcs = ["a/b/c.c"],
 )`,
 			},
 		},
@@ -123,9 +122,7 @@
         "include",
         ".",
     ],
-    srcs = [
-        "a/b/c.c",
-    ],
+    srcs = ["a/b/c.c"],
 )`,
 			},
 		},
@@ -155,29 +152,15 @@
 `,
 			expectedBazelTargets: []string{`cc_object(
     name = "bar",
-    copts = [
-        "-fno-addrsig",
-    ],
-    local_include_dirs = [
-        ".",
-    ],
-    srcs = [
-        "x/y/z.c",
-    ],
+    copts = ["-fno-addrsig"],
+    local_include_dirs = ["."],
+    srcs = ["x/y/z.c"],
 )`, `cc_object(
     name = "foo",
-    copts = [
-        "-fno-addrsig",
-    ],
-    deps = [
-        ":bar",
-    ],
-    local_include_dirs = [
-        ".",
-    ],
-    srcs = [
-        "a/b/c.c",
-    ],
+    copts = ["-fno-addrsig"],
+    deps = [":bar"],
+    local_include_dirs = ["."],
+    srcs = ["a/b/c.c"],
 )`,
 			},
 		},
@@ -200,12 +183,8 @@
 `,
 			expectedBazelTargets: []string{`cc_object(
     name = "foo",
-    copts = [
-        "-fno-addrsig",
-    ],
-    srcs = [
-        "a/b/c.c",
-    ],
+    copts = ["-fno-addrsig"],
+    srcs = ["a/b/c.c"],
 )`,
 			},
 		},
@@ -228,12 +207,8 @@
 `,
 			expectedBazelTargets: []string{`cc_object(
     name = "foo",
-    asflags = [
-        "-DPLATFORM_SDK_VERSION={Platform_sdk_version}",
-    ],
-    copts = [
-        "-fno-addrsig",
-    ],
+    asflags = ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
+    copts = ["-fno-addrsig"],
 )`,
 			},
 		},
@@ -321,23 +296,13 @@
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    copts = [
-        "-fno-addrsig",
-    ] + select({
-        "//build/bazel/platforms/arch:x86": [
-            "-fPIC",
-        ],
+    copts = ["-fno-addrsig"] + select({
+        "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//conditions:default": [],
     }),
-    local_include_dirs = [
-        ".",
-    ],
-    srcs = [
-        "a.cpp",
-    ] + select({
-        "//build/bazel/platforms/arch:arm": [
-            "arch/arm/file.S",
-        ],
+    local_include_dirs = ["."],
+    srcs = ["a.cpp"] + select({
+        "//build/bazel/platforms/arch:arm": ["arch/arm/file.S"],
         "//conditions:default": [],
     }),
 )`,
@@ -375,41 +340,19 @@
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    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",
-        ],
+    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": [],
     }),
-    local_include_dirs = [
-        ".",
-    ],
-    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",
-        ],
+    local_include_dirs = ["."],
+    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": [],
     }),
 )`,
@@ -440,26 +383,14 @@
 			expectedBazelTargets: []string{
 				`cc_object(
     name = "foo",
-    copts = [
-        "-fno-addrsig",
-    ] + select({
-        "//build/bazel/platforms/os:android": [
-            "-fPIC",
-        ],
-        "//build/bazel/platforms/os:darwin": [
-            "-Wall",
-        ],
-        "//build/bazel/platforms/os:windows": [
-            "-fPIC",
-        ],
+    copts = ["-fno-addrsig"] + select({
+        "//build/bazel/platforms/os:android": ["-fPIC"],
+        "//build/bazel/platforms/os:darwin": ["-Wall"],
+        "//build/bazel/platforms/os:windows": ["-fPIC"],
         "//conditions:default": [],
     }),
-    local_include_dirs = [
-        ".",
-    ],
-    srcs = [
-        "base.cpp",
-    ],
+    local_include_dirs = ["."],
+    srcs = ["base.cpp"],
 )`,
 			},
 		},
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index b2b3379..97729df 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -69,20 +69,26 @@
 		return ret, err
 	}
 
-	// Create the selects for arch specific values.
-	selectMap, err := prettyPrintSelectMap(archSelects, "[]", indent)
+	// 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)
+		if err != nil {
+			return "", err
+		}
+		if s != "" && selectMap != "" {
+			s += " + "
+		}
+		s += selectMap
+
+		return s, nil
+	}
+
+	ret, err = appendSelects(archSelects, "[]", ret)
 	if err != nil {
 		return "", err
 	}
-	ret += selectMap
 
-	// Create the selects for target os specific values.
-	selectMap, err = prettyPrintSelectMap(osSelects, "[]", indent)
-	if err != nil {
-		return "", err
-	}
-	ret += selectMap
-
+	ret, err = appendSelects(osSelects, "[]", ret)
 	return ret, err
 }
 
@@ -113,7 +119,7 @@
 	}
 
 	// Create the map.
-	ret := " + select({\n"
+	ret := "select({\n"
 	ret += selects
 	// default condition comes last.
 	ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 7600e36..2054e06 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -33,24 +33,15 @@
 			blueprint: `python_binary_host {
     name: "foo",
     main: "a.py",
-    srcs: [
-        "**/*.py"
-    ],
-    exclude_srcs: [
-        "b/e.py"
-    ],
-    data: [
-        "files/data.txt",
-    ],
-
+    srcs: ["**/*.py"],
+    exclude_srcs: ["b/e.py"],
+    data: ["files/data.txt",],
     bazel_module: { bp2build_available: true },
 }
 `,
 			expectedBazelTargets: []string{`py_binary(
     name = "foo",
-    data = [
-        "files/data.txt",
-    ],
+    data = ["files/data.txt"],
     main = "a.py",
     srcs = [
         "a.py",
@@ -83,9 +74,7 @@
 			expectedBazelTargets: []string{`py_binary(
     name = "foo",
     python_version = "PY2",
-    srcs = [
-        "a.py",
-    ],
+    srcs = ["a.py"],
 )`,
 			},
 		},
@@ -113,9 +102,7 @@
 				// python_version is PY3 by default.
 				`py_binary(
     name = "foo",
-    srcs = [
-        "a.py",
-    ],
+    srcs = ["a.py"],
 )`,
 			},
 		},
diff --git a/bp2build/sh_conversion_test.go b/bp2build/sh_conversion_test.go
index 2aa373c..37f542e 100644
--- a/bp2build/sh_conversion_test.go
+++ b/bp2build/sh_conversion_test.go
@@ -74,9 +74,7 @@
 }`,
 			expectedBazelTargets: []string{`sh_binary(
     name = "foo",
-    srcs = [
-        "foo.sh",
-    ],
+    srcs = ["foo.sh"],
 )`},
 		},
 	}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 497d227..0bca30a 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -16,6 +16,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel"
+	"strings"
 )
 
 // bp2build functions and helpers for converting cc_* modules to Bazel.
@@ -58,74 +59,168 @@
 	ctx.AddDependency(module, nil, android.SortedUniqueStrings(allDeps)...)
 }
 
-// bp2buildParseCflags creates a label list attribute containing the cflags of a module, including
-func bp2BuildParseCflags(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
-	var ret bazel.StringListAttribute
+// Convenience struct to hold all attributes parsed from compiler properties.
+type compilerAttributes struct {
+	copts bazel.StringListAttribute
+	srcs  bazel.LabelListAttribute
+	hdrs  bazel.LabelListAttribute
+}
+
+// bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
+func bp2BuildParseCompilerProps(ctx android.TopDownMutatorContext, module *Module) compilerAttributes {
+	var hdrs, srcs bazel.LabelListAttribute
+	var copts bazel.StringListAttribute
+
+	hdrsAndSrcs := func(baseCompilerProps *BaseCompilerProperties) (bazel.LabelList, bazel.LabelList) {
+		srcsList := android.BazelLabelForModuleSrcExcludes(
+			ctx, baseCompilerProps.Srcs, baseCompilerProps.Exclude_srcs)
+		hdrsList := android.BazelLabelForModuleSrc(ctx, srcsList.LooseHdrsGlobs(headerExts))
+		return hdrsList, srcsList
+	}
+
 	for _, props := range module.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			ret.Value = baseCompilerProps.Cflags
+			hdrs.Value, srcs.Value = hdrsAndSrcs(baseCompilerProps)
+			copts.Value = baseCompilerProps.Cflags
 			break
 		}
 	}
 
 	for arch, props := range module.GetArchProperties(&BaseCompilerProperties{}) {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			ret.SetValueForArch(arch.Name, baseCompilerProps.Cflags)
+			hdrsList, srcsList := hdrsAndSrcs(baseCompilerProps)
+			hdrs.SetValueForArch(arch.Name, bazel.SubtractBazelLabelList(hdrsList, hdrs.Value))
+			srcs.SetValueForArch(arch.Name, srcsList)
+			copts.SetValueForArch(arch.Name, baseCompilerProps.Cflags)
 		}
 	}
 
 	for os, props := range module.GetTargetProperties(&BaseCompilerProperties{}) {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			ret.SetValueForOS(os.Name, baseCompilerProps.Cflags)
+			hdrsList, srcsList := hdrsAndSrcs(baseCompilerProps)
+			hdrs.SetValueForOS(os.Name, bazel.SubtractBazelLabelList(hdrsList, hdrs.Value))
+			srcs.SetValueForOS(os.Name, srcsList)
+			copts.SetValueForOS(os.Name, baseCompilerProps.Cflags)
 		}
 	}
 
-	return ret
+	return compilerAttributes{
+		hdrs:  hdrs,
+		srcs:  srcs,
+		copts: copts,
+	}
 }
 
-// bp2BuildParseHeaderLibs creates a label list attribute containing the header library deps of a module, including
+// Convenience struct to hold all attributes parsed from linker properties.
+type linkerAttributes struct {
+	deps     bazel.LabelListAttribute
+	linkopts bazel.StringListAttribute
+}
+
+// bp2BuildParseLinkerProps creates a label list attribute containing the header library deps of a module, including
 // configurable attribute values.
-func bp2BuildParseHeaderLibs(ctx android.TopDownMutatorContext, module *Module) bazel.LabelListAttribute {
-	var ret bazel.LabelListAttribute
+func bp2BuildParseLinkerProps(ctx android.TopDownMutatorContext, module *Module) linkerAttributes {
+
+	var deps bazel.LabelListAttribute
+	var linkopts bazel.StringListAttribute
+
 	for _, linkerProps := range module.linker.linkerProps() {
 		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
 			libs := baseLinkerProps.Header_libs
 			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			ret = bazel.MakeLabelListAttribute(
+			deps = bazel.MakeLabelListAttribute(
 				android.BazelLabelForModuleDeps(ctx, android.SortedUniqueStrings(libs)))
+			linkopts.Value = baseLinkerProps.Ldflags
 			break
 		}
 	}
 
+	for arch, p := range module.GetArchProperties(&BaseLinkerProperties{}) {
+		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
+			libs := baseLinkerProps.Header_libs
+			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
+			libs = android.SortedUniqueStrings(libs)
+			deps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, libs))
+			linkopts.SetValueForArch(arch.Name, baseLinkerProps.Ldflags)
+		}
+	}
+
 	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 = android.SortedUniqueStrings(libs)
-			ret.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
+			deps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
+			linkopts.SetValueForOS(os.Name, baseLinkerProps.Ldflags)
 		}
 	}
 
-	return ret
+	return linkerAttributes{
+		deps:     deps,
+		linkopts: linkopts,
+	}
+}
+
+func bp2BuildListHeadersInDir(ctx android.TopDownMutatorContext, includeDir string) bazel.LabelList {
+	globs := bazel.GlobsInDir(includeDir, includeDir != ".", headerExts)
+	return android.BazelLabelForModuleSrc(ctx, globs)
+}
+
+// Bazel wants include paths to be relative to the module
+func bp2BuildMakePathsRelativeToModule(ctx android.TopDownMutatorContext, paths []string) []string {
+	var relativePaths []string
+	for _, path := range paths {
+		relativePath := strings.TrimPrefix(path, ctx.ModuleDir()+"/")
+		relativePaths = append(relativePaths, relativePath)
+	}
+	return relativePaths
 }
 
 // bp2BuildParseExportedIncludes creates a label list attribute contains the
 // exported included directories of a module.
-func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) (bazel.LabelListAttribute, bazel.LabelListAttribute) {
+func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) (bazel.StringListAttribute, bazel.LabelListAttribute) {
 	libraryDecorator := module.linker.(*libraryDecorator)
 
 	includeDirs := libraryDecorator.flagExporter.Properties.Export_system_include_dirs
 	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
+	includeDirs = bp2BuildMakePathsRelativeToModule(ctx, includeDirs)
+	includeDirsAttribute := bazel.MakeStringListAttribute(includeDirs)
 
-	includeDirsLabels := android.BazelLabelForModuleSrc(ctx, includeDirs)
-
-	var includeDirGlobs []string
+	var headersAttribute bazel.LabelListAttribute
+	var headers bazel.LabelList
 	for _, includeDir := range includeDirs {
-		includeDirGlobs = append(includeDirGlobs, includeDir+"/**/*.h")
-		includeDirGlobs = append(includeDirGlobs, includeDir+"/**/*.inc")
-		includeDirGlobs = append(includeDirGlobs, includeDir+"/**/*.hpp")
+		headers.Append(bp2BuildListHeadersInDir(ctx, includeDir))
+	}
+	headers = bazel.UniqueBazelLabelList(headers)
+	headersAttribute.Value = headers
+
+	for arch, props := range module.GetArchProperties(&FlagExporterProperties{}) {
+		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
+			archIncludeDirs := flagExporterProperties.Export_system_include_dirs
+			archIncludeDirs = append(archIncludeDirs, flagExporterProperties.Export_include_dirs...)
+			archIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, archIncludeDirs)
+
+			// To avoid duplicate includes when base includes + arch includes are combined
+			archIncludeDirs = bazel.SubtractStrings(archIncludeDirs, includeDirs)
+
+			if len(archIncludeDirs) > 0 {
+				includeDirsAttribute.SetValueForArch(arch.Name, archIncludeDirs)
+			}
+
+			var archHeaders bazel.LabelList
+			for _, archIncludeDir := range archIncludeDirs {
+				archHeaders.Append(bp2BuildListHeadersInDir(ctx, archIncludeDir))
+			}
+			archHeaders = bazel.UniqueBazelLabelList(archHeaders)
+
+			// To avoid duplicate headers when base headers + arch headers are combined
+			archHeaders = bazel.SubtractBazelLabelList(archHeaders, headers)
+
+			if len(archHeaders.Includes) > 0 || len(archHeaders.Excludes) > 0 {
+				headersAttribute.SetValueForArch(arch.Name, archHeaders)
+			}
+		}
 	}
 
-	headersLabels := android.BazelLabelForModuleSrc(ctx, includeDirGlobs)
-	return bazel.MakeLabelListAttribute(includeDirsLabels), bazel.MakeLabelListAttribute(headersLabels)
+	return includeDirsAttribute, headersAttribute
 }
diff --git a/cc/builder.go b/cc/builder.go
index 8c9743f..da8501c 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -132,7 +132,7 @@
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
 			Deps:        blueprint.DepsGCC,
-			Command:     "CROSS_COMPILE=$crossCompile XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
+			Command:     "XZ=$xzCmd CLANG_BIN=${config.ClangBin} $stripPath ${args} -i ${in} -o ${out} -d ${out}.d",
 			CommandDeps: []string{"$stripPath", "$xzCmd"},
 			Pool:        darwinStripPool,
 		},
diff --git a/cc/cc.go b/cc/cc.go
index 1ce83a9..9176bc3 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -56,8 +56,8 @@
 		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("hwasan_deps", sanitizerDepsMutator(Hwasan))
+		ctx.BottomUp("hwasan", sanitizerMutator(Hwasan)).Parallel()
 
 		ctx.TopDown("fuzzer_deps", sanitizerDepsMutator(Fuzzer))
 		ctx.BottomUp("fuzzer", sanitizerMutator(Fuzzer)).Parallel()
diff --git a/cc/library.go b/cc/library.go
index 11b6737..738b45f 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -206,6 +206,7 @@
 	RegisterLibraryBuildComponents(android.InitRegistrationContext)
 
 	android.RegisterBp2BuildMutator("cc_library_static", CcLibraryStaticBp2Build)
+	android.RegisterBp2BuildMutator("cc_library", CcLibraryBp2Build)
 }
 
 func RegisterLibraryBuildComponents(ctx android.RegistrationContext) {
@@ -216,6 +217,67 @@
 	ctx.RegisterModuleType("cc_library_host_shared", LibraryHostSharedFactory)
 }
 
+// For bp2build conversion.
+type bazelCcLibraryAttributes struct {
+	Srcs            bazel.LabelListAttribute
+	Hdrs            bazel.LabelListAttribute
+	Copts           bazel.StringListAttribute
+	Linkopts        bazel.StringListAttribute
+	Deps            bazel.LabelListAttribute
+	User_link_flags bazel.StringListAttribute
+	Includes        bazel.StringListAttribute
+}
+
+type bazelCcLibrary struct {
+	android.BazelTargetModuleBase
+	bazelCcLibraryAttributes
+}
+
+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
+	}
+
+	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
+	linkerAttrs := bp2BuildParseLinkerProps(ctx, m)
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, m)
+	compilerAttrs.hdrs.Append(exportedIncludesHeaders)
+
+	attrs := &bazelCcLibraryAttributes{
+		Srcs:     compilerAttrs.srcs,
+		Hdrs:     compilerAttrs.hdrs,
+		Copts:    compilerAttrs.copts,
+		Linkopts: linkerAttrs.linkopts,
+		Deps:     linkerAttrs.deps,
+		Includes: exportedIncludes,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library",
+		Bzl_load_location: "//build/bazel/rules:full_cc_library.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(BazelCcLibraryFactory, m.Name(), props, attrs)
+}
+
 // cc_library creates both static and/or shared libraries for a device and/or
 // host. By default, a cc_library has a single variant that targets the device.
 // Specifying `host_supported: true` also creates a library that targets the
@@ -2058,11 +2120,12 @@
 }
 
 type bazelCcLibraryStaticAttributes struct {
-	Copts      []string
+	Copts      bazel.StringListAttribute
 	Srcs       bazel.LabelListAttribute
 	Deps       bazel.LabelListAttribute
+	Linkopts   bazel.StringListAttribute
 	Linkstatic bool
-	Includes   bazel.LabelListAttribute
+	Includes   bazel.StringListAttribute
 	Hdrs       bazel.LabelListAttribute
 }
 
@@ -2091,24 +2154,37 @@
 		return
 	}
 
-	var copts []string
-	var srcs []string
+	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
+
 	var includeDirs []string
 	var localIncludeDirs []string
 	for _, props := range module.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			copts = baseCompilerProps.Cflags
-			srcs = baseCompilerProps.Srcs
-			includeDirs = baseCompilerProps.Include_dirs
-			localIncludeDirs = baseCompilerProps.Local_include_dirs
+			// TODO: these should be arch and os specific.
+			includeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
+			localIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Local_include_dirs)
 			break
 		}
 	}
-	srcsLabels := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, srcs))
+
+	// Soong implicitly includes headers from the module's directory.
+	// For Bazel builds to work we have to make these header includes explicit.
+	if module.compiler.(*libraryDecorator).includeBuildDirectory() {
+		localIncludeDirs = append(localIncludeDirs, ".")
+	}
+
+	// For Bazel, be more explicit about headers - list all header files in include dirs as srcs
+	for _, includeDir := range includeDirs {
+		compilerAttrs.srcs.Value.Append(bp2BuildListHeadersInDir(ctx, includeDir))
+	}
+	for _, localIncludeDir := range localIncludeDirs {
+		compilerAttrs.srcs.Value.Append(bp2BuildListHeadersInDir(ctx, localIncludeDir))
+	}
 
 	var staticLibs []string
 	var wholeStaticLibs []string
 	for _, props := range module.linker.linkerProps() {
+		// TODO: move this into bp2buildParseLinkerProps
 		if baseLinkerProperties, ok := props.(*BaseLinkerProperties); ok {
 			staticLibs = baseLinkerProperties.Static_libs
 			wholeStaticLibs = baseLinkerProperties.Whole_static_libs
@@ -2122,25 +2198,26 @@
 
 	depsLabels := android.BazelLabelForModuleDeps(ctx, allDeps)
 
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
+
 	// FIXME: Unify absolute vs relative paths
 	// FIXME: Use -I copts instead of setting includes= ?
-	allIncludes := includeDirs
-	allIncludes = append(allIncludes, localIncludeDirs...)
-	includesLabels := android.BazelLabelForModuleSrc(ctx, allIncludes)
+	allIncludes := exportedIncludes
+	allIncludes.Value = append(allIncludes.Value, includeDirs...)
+	allIncludes.Value = append(allIncludes.Value, localIncludeDirs...)
 
-	exportedIncludesLabels, exportedIncludesHeadersLabels := bp2BuildParseExportedIncludes(ctx, module)
-	includesLabels.Append(exportedIncludesLabels.Value)
+	compilerAttrs.hdrs.Append(exportedIncludesHeaders)
 
-	headerLibsLabels := bp2BuildParseHeaderLibs(ctx, module)
-	depsLabels.Append(headerLibsLabels.Value)
+	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
+	depsLabels.Append(linkerAttrs.deps.Value)
 
 	attrs := &bazelCcLibraryStaticAttributes{
-		Copts:      copts,
-		Srcs:       srcsLabels,
+		Copts:      compilerAttrs.copts,
+		Srcs:       compilerAttrs.srcs,
 		Deps:       bazel.MakeLabelListAttribute(depsLabels),
 		Linkstatic: true,
-		Includes:   bazel.MakeLabelListAttribute(includesLabels),
-		Hdrs:       exportedIncludesHeadersLabels,
+		Includes:   allIncludes,
+		Hdrs:       compilerAttrs.hdrs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 82af16a..076ce80 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -64,7 +64,7 @@
 type bazelCcLibraryHeadersAttributes struct {
 	Copts    bazel.StringListAttribute
 	Hdrs     bazel.LabelListAttribute
-	Includes bazel.LabelListAttribute
+	Includes bazel.StringListAttribute
 	Deps     bazel.LabelListAttribute
 }
 
@@ -95,15 +95,15 @@
 		return
 	}
 
-	exportedIncludesLabels, exportedIncludesHeadersLabels := bp2BuildParseExportedIncludes(ctx, module)
-
-	headerLibsLabels := bp2BuildParseHeaderLibs(ctx, module)
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
+	compilerAttrs := bp2BuildParseCompilerProps(ctx, module)
+	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
 
 	attrs := &bazelCcLibraryHeadersAttributes{
-		Copts:    bp2BuildParseCflags(ctx, module),
-		Includes: exportedIncludesLabels,
-		Hdrs:     exportedIncludesHeadersLabels,
-		Deps:     headerLibsLabels,
+		Copts:    compilerAttrs.copts,
+		Includes: exportedIncludes,
+		Hdrs:     exportedIncludesHeaders,
+		Deps:     linkerAttrs.deps,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/object.go b/cc/object.go
index 4f8797d..9bb279a 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -113,6 +113,7 @@
 // For bp2build conversion.
 type bazelObjectAttributes struct {
 	Srcs               bazel.LabelListAttribute
+	Hdrs               bazel.LabelListAttribute
 	Deps               bazel.LabelListAttribute
 	Copts              bazel.StringListAttribute
 	Asflags            []string
@@ -156,16 +157,11 @@
 	}
 
 	// Set arch-specific configurable attributes
-	var srcs bazel.LabelListAttribute
+	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
 	var localIncludeDirs []string
 	var asFlags []string
 	for _, props := range m.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			srcs = bazel.MakeLabelListAttribute(
-				android.BazelLabelForModuleSrcExcludes(
-					ctx,
-					baseCompilerProps.Srcs,
-					baseCompilerProps.Exclude_srcs))
 			localIncludeDirs = baseCompilerProps.Local_include_dirs
 			break
 		}
@@ -200,16 +196,11 @@
 	}
 	// TODO(b/183595872) warn/error if we're not handling product variables
 
-	for arch, p := range m.GetArchProperties(&BaseCompilerProperties{}) {
-		if cProps, ok := p.(*BaseCompilerProperties); ok {
-			srcs.SetValueForArch(arch.Name, android.BazelLabelForModuleSrcExcludes(ctx, cProps.Srcs, cProps.Exclude_srcs))
-		}
-	}
-
 	attrs := &bazelObjectAttributes{
-		Srcs:               srcs,
+		Srcs:               compilerAttrs.srcs,
+		Hdrs:               compilerAttrs.hdrs,
 		Deps:               deps,
-		Copts:              bp2BuildParseCflags(ctx, m),
+		Copts:              compilerAttrs.copts,
 		Asflags:            asFlags,
 		Local_include_dirs: localIncludeDirs,
 	}
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 6b9a3d5..3829b1e 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -305,6 +305,7 @@
 func NewPrebuiltStaticLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
 	module, library := NewPrebuiltLibrary(hod)
 	library.BuildOnlyStatic()
+	module.bazelHandler = &prebuiltStaticLibraryBazelHandler{module: module, library: library}
 	return module, library
 }
 
@@ -319,6 +320,52 @@
 	properties prebuiltObjectProperties
 }
 
+type prebuiltStaticLibraryBazelHandler struct {
+	bazelHandler
+
+	module  *Module
+	library *libraryDecorator
+}
+
+func (h *prebuiltStaticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	staticLibs, ok := bazelCtx.GetPrebuiltCcStaticLibraryFiles(label, ctx.Arch().ArchType)
+	if !ok {
+		return false
+	}
+	if len(staticLibs) > 1 {
+		ctx.ModuleErrorf("expected 1 static library from bazel target %q, got %s", label, staticLibs)
+		return false
+	}
+
+	// TODO(b/184543518): cc_prebuilt_library_static 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(staticLibs) == 0 {
+		h.module.outputFile = android.OptionalPath{}
+		return true
+	}
+
+	out := android.PathForBazelOut(ctx, staticLibs[0])
+	h.module.outputFile = android.OptionalPathForPath(out)
+
+	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(out).Build()
+	ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
+		StaticLibrary: out,
+
+		TransitiveStaticLibrariesForOrdering: depSet,
+	})
+
+	return true
+}
+
 func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
 	return &p.Prebuilt
 }
diff --git a/cc/sanitize.go b/cc/sanitize.go
index e1ac9f0..397121e 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -83,7 +83,7 @@
 
 const (
 	Asan SanitizerType = iota + 1
-	hwasan
+	Hwasan
 	tsan
 	intOverflow
 	cfi
@@ -97,7 +97,7 @@
 	switch t {
 	case Asan:
 		return "asan"
-	case hwasan:
+	case Hwasan:
 		return "hwasan"
 	case tsan:
 		return "tsan"
@@ -121,7 +121,7 @@
 	switch t {
 	case Asan:
 		return "address"
-	case hwasan:
+	case Hwasan:
 		return "hwaddress"
 	case memtag_heap:
 		return "memtag_heap"
@@ -144,7 +144,7 @@
 	switch t {
 	case Asan:
 		return true
-	case hwasan:
+	case Hwasan:
 		return true
 	case tsan:
 		return true
@@ -163,7 +163,7 @@
 
 // incompatibleWithCfi returns true if a sanitizer is incompatible with CFI.
 func (t SanitizerType) incompatibleWithCfi() bool {
-	return t == Asan || t == Fuzzer || t == hwasan
+	return t == Asan || t == Fuzzer || t == Hwasan
 }
 
 type SanitizeUserProps struct {
@@ -745,7 +745,7 @@
 	switch t {
 	case Asan:
 		return sanitize.Properties.Sanitize.Address
-	case hwasan:
+	case Hwasan:
 		return sanitize.Properties.Sanitize.Hwaddress
 	case tsan:
 		return sanitize.Properties.Sanitize.Thread
@@ -767,7 +767,7 @@
 // isUnsanitizedVariant returns true if no sanitizers are enabled.
 func (sanitize *sanitize) isUnsanitizedVariant() bool {
 	return !sanitize.isSanitizerEnabled(Asan) &&
-		!sanitize.isSanitizerEnabled(hwasan) &&
+		!sanitize.isSanitizerEnabled(Hwasan) &&
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(cfi) &&
 		!sanitize.isSanitizerEnabled(scs) &&
@@ -778,7 +778,7 @@
 // isVariantOnProductionDevice returns true if variant is for production devices (no non-production sanitizers enabled).
 func (sanitize *sanitize) isVariantOnProductionDevice() bool {
 	return !sanitize.isSanitizerEnabled(Asan) &&
-		!sanitize.isSanitizerEnabled(hwasan) &&
+		!sanitize.isSanitizerEnabled(Hwasan) &&
 		!sanitize.isSanitizerEnabled(tsan) &&
 		!sanitize.isSanitizerEnabled(Fuzzer)
 }
@@ -787,7 +787,7 @@
 	switch t {
 	case Asan:
 		sanitize.Properties.Sanitize.Address = boolPtr(b)
-	case hwasan:
+	case Hwasan:
 		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
 	case tsan:
 		sanitize.Properties.Sanitize.Thread = boolPtr(b)
@@ -902,7 +902,7 @@
 					if d, ok := child.(PlatformSanitizeable); ok && d.SanitizePropDefined() &&
 						!d.SanitizeNever() &&
 						!d.IsSanitizerExplicitlyDisabled(t) {
-						if t == cfi || t == hwasan || t == scs {
+						if t == cfi || t == Hwasan || t == scs {
 							if d.StaticallyLinked() && d.SanitizerSupported(t) {
 								// Rust does not support some of these sanitizers, so we need to check if it's
 								// supported before setting this true.
@@ -1080,6 +1080,12 @@
 			if Bool(c.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(SnapshotInfoProvider).(SnapshotInfo)
+			if lib, ok := snapshot.StaticLibs[noteDep]; ok {
+				noteDep = lib
+			}
 			depTag := libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: true}
 			variations := append(mctx.Target().Variations(),
 				blueprint.Variation{Mutator: "link", Variation: "static"})
@@ -1280,7 +1286,7 @@
 					// For cfi/scs/hwasan, we can export both sanitized and un-sanitized variants
 					// to Make, because the sanitized version has a different suffix in name.
 					// For other types of sanitizers, suppress the variation that is disabled.
-					if t != cfi && t != scs && t != hwasan {
+					if t != cfi && t != scs && t != Hwasan {
 						if isSanitizerEnabled {
 							modules[0].(PlatformSanitizeable).SetPreventInstall()
 							modules[0].(PlatformSanitizeable).SetHideFromMake()
@@ -1294,7 +1300,7 @@
 					if c.StaticallyLinked() && c.ExportedToMake() {
 						if t == cfi {
 							cfiStaticLibs(mctx.Config()).add(c, c.Module().Name())
-						} else if t == hwasan {
+						} else if t == Hwasan {
 							hwasanStaticLibs(mctx.Config()).add(c, c.Module().Name())
 						}
 					}
@@ -1411,7 +1417,7 @@
 
 func hwasanStaticLibs(config android.Config) *sanitizerStaticLibsMap {
 	return config.Once(hwasanStaticLibsKey, func() interface{} {
-		return newSanitizerStaticLibsMap(hwasan)
+		return newSanitizerStaticLibsMap(Hwasan)
 	}).(*sanitizerStaticLibsMap)
 }
 
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index bbb8896..af05102 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -337,7 +337,8 @@
 		for _, name := range names {
 			snapshotMap[name] = name +
 				getSnapshotNameSuffix(snapshotSuffix+moduleSuffix,
-					s.baseSnapshot.version(), ctx.Arch().ArchType.Name)
+					s.baseSnapshot.version(),
+					ctx.DeviceConfig().Arches()[0].ArchType.String())
 		}
 		return snapshotMap
 	}
@@ -396,7 +397,7 @@
 	Target_arch string
 
 	// Suffix to be added to the module name when exporting to Android.mk, e.g. ".vendor".
-	Androidmk_suffix string
+	Androidmk_suffix string `blueprint:"mutated"`
 
 	// Suffix to be added to the module name, e.g., vendor_shared,
 	// recovery_shared, etc.
@@ -417,6 +418,7 @@
 // will be seen as "libbase.vendor_static.30.arm64" by Soong.
 type baseSnapshotDecorator struct {
 	baseProperties baseSnapshotDecoratorProperties
+	image          snapshotImage
 }
 
 func (p *baseSnapshotDecorator) Name(name string) string {
@@ -447,10 +449,21 @@
 	return p.baseProperties.Androidmk_suffix
 }
 
+func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext) {
+	if ctx.OtherModuleDependencyVariantExists([]blueprint.Variation{
+		{Mutator: "image", Variation: android.CoreVariation},
+	}, ctx.Module().(*Module).BaseModuleName()) {
+		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+	} else {
+		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, snapshotSuffix, moduleSuffix string) {
-	p.baseProperties.ModuleSuffix = snapshotSuffix + moduleSuffix
+func (p *baseSnapshotDecorator) init(m *Module, image snapshotImage, moduleSuffix string) {
+	p.image = image
+	p.baseProperties.ModuleSuffix = image.moduleNameSuffix() + moduleSuffix
 	m.AddProperties(&p.baseProperties)
 	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
 		vendorSnapshotLoadHook(ctx, p)
@@ -532,6 +545,8 @@
 // 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)
+
 	if p.header() {
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
 	}
@@ -614,7 +629,7 @@
 	}
 }
 
-func snapshotLibraryFactory(snapshotSuffix, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+func snapshotLibraryFactory(image snapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
@@ -637,7 +652,7 @@
 	module.linker = prebuilt
 	module.installer = prebuilt
 
-	prebuilt.init(module, snapshotSuffix, moduleSuffix)
+	prebuilt.init(module, image, moduleSuffix)
 	module.AddProperties(
 		&prebuilt.properties,
 		&prebuilt.sanitizerProperties,
@@ -651,7 +666,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.moduleNameSuffix(), snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -661,7 +676,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.moduleNameSuffix(), snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -671,7 +686,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.moduleNameSuffix(), snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -681,7 +696,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.moduleNameSuffix(), snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -691,7 +706,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.moduleNameSuffix(), snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -701,7 +716,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.moduleNameSuffix(), snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -739,6 +754,8 @@
 // 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)
+
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
@@ -767,17 +784,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.moduleNameSuffix(), 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.moduleNameSuffix(), snapshotBinarySuffix)
+	return snapshotBinaryFactory(recoverySnapshotImageSingleton, snapshotBinarySuffix)
 }
 
-func snapshotBinaryFactory(snapshotSuffix, 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)
@@ -796,7 +813,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, snapshotSuffix, moduleSuffix)
+	prebuilt.init(module, image, moduleSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -832,6 +849,8 @@
 // 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)
+
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
@@ -856,7 +875,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotObjectSuffix)
+	prebuilt.init(module, vendorSnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -874,7 +893,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotObjectSuffix)
+	prebuilt.init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
diff --git a/cc/stl.go b/cc/stl.go
index 594231d..4f8865f 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -140,6 +140,17 @@
 }
 
 func staticUnwinder(ctx android.BaseModuleContext) string {
+	vndkVersion := ctx.Module().(*Module).VndkVersion()
+
+	// Modules using R vndk use different unwinder
+	if vndkVersion == "30" {
+		if ctx.Arch().ArchType == android.Arm {
+			return "libunwind_llvm"
+		} else {
+			return "libgcc_stripped"
+		}
+	}
+
 	return "libunwind"
 }
 
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 4014fe0..3437d77 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -196,7 +196,7 @@
 		if m.sanitize != nil {
 			// scs and hwasan export both sanitized and unsanitized variants for static and header
 			// Always use unsanitized variants of them.
-			for _, t := range []SanitizerType{scs, hwasan} {
+			for _, t := range []SanitizerType{scs, Hwasan} {
 				if !l.shared() && m.sanitize.isSanitizerEnabled(t) {
 					return false
 				}
@@ -238,7 +238,6 @@
 type snapshotJsonFlags struct {
 	ModuleName          string `json:",omitempty"`
 	RelativeInstallPath string `json:",omitempty"`
-	AndroidMkSuffix     string `json:",omitempty"`
 
 	// library flags
 	ExportedDirs       []string `json:",omitempty"`
@@ -352,7 +351,6 @@
 		} else {
 			prop.RelativeInstallPath = m.RelativeInstallPath()
 		}
-		prop.AndroidMkSuffix = m.Properties.SubName
 		prop.RuntimeLibs = m.Properties.SnapshotRuntimeLibs
 		prop.Required = m.RequiredModuleNames()
 		for _, path := range m.InitRc() {
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index 20cd031..1c3f1b4 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -271,7 +271,6 @@
 			enabled: true,
 		},
 		nocrt: true,
-		compile_multilib: "64",
 	}
 
 	cc_library {
@@ -281,7 +280,6 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
 	}
 
 	cc_library {
@@ -291,6 +289,25 @@
 		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",
 	}
 
@@ -301,14 +318,23 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
+	}
+
+	cc_binary {
+		name: "bin32",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "32",
 	}
 `
 
 	vndkBp := `
 	vndk_prebuilt_shared {
 		name: "libvndk",
-		version: "28",
+		version: "30",
 		target_arch: "arm64",
 		vendor_available: true,
 		product_available: true,
@@ -320,6 +346,10 @@
 				srcs: ["libvndk.so"],
 				export_include_dirs: ["include/libvndk"],
 			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
 		},
 	}
 
@@ -338,6 +368,28 @@
 				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"],
+			},
 		},
 	}
 `
@@ -350,7 +402,6 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
 	}
 
 	cc_library_shared {
@@ -362,7 +413,14 @@
 		system_shared_libs: [],
 		shared_libs: ["libvndk", "libvendor_available"],
 		static_libs: ["libvendor", "libvendor_without_snapshot"],
-		compile_multilib: "64",
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
 		srcs: ["client.cpp"],
 	}
 
@@ -371,52 +429,83 @@
 		vendor: true,
 		nocrt: true,
 		no_libcrt: true,
-		stl: "none",
+		stl: "libc++_static",
 		system_shared_libs: [],
 		static_libs: ["libvndk"],
-		compile_multilib: "64",
 		srcs: ["bin.cpp"],
 	}
 
 	vendor_snapshot {
 		name: "vendor_snapshot",
-		compile_multilib: "first",
-		version: "28",
-		vndk_libs: [
-			"libvndk",
-		],
-		static_libs: [
-			"libvendor",
-			"libvendor_available",
-			"libvndk",
-		],
-		shared_libs: [
-			"libvendor",
-			"libvendor_available",
-		],
-		binaries: [
-			"bin",
-		],
+		version: "30",
+		arch: {
+			arm64: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libc++_static",
+					"libc++demangle",
+					"libgcc_stripped",
+					"libvendor",
+					"libvendor_available",
+					"libvndk",
+					"lib64",
+				],
+				shared_libs: [
+					"libvendor",
+					"libvendor_available",
+					"lib64",
+				],
+				binaries: [
+					"bin",
+				],
+			},
+			arm: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvendor_available",
+					"libvndk",
+					"lib32",
+				],
+				shared_libs: [
+					"libvendor",
+					"libvendor_available",
+					"lib32",
+				],
+				binaries: [
+					"bin32",
+				],
+			},
+		}
 	}
 
 	vendor_snapshot_static {
 		name: "libvndk",
-		version: "28",
+		version: "30",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvndk.a",
 				export_include_dirs: ["include/libvndk"],
 			},
+			arm: {
+				src: "libvndk.a",
+				export_include_dirs: ["include/libvndk"],
+			},
 		},
 	}
 
 	vendor_snapshot_shared {
 		name: "libvendor",
-		version: "28",
+		version: "30",
 		target_arch: "arm64",
-		compile_multilib: "64",
+		compile_multilib: "both",
 		vendor: true,
 		shared_libs: [
 			"libvendor_without_snapshot",
@@ -428,54 +517,163 @@
 				src: "libvendor.so",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "lib32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.a",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.so",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "lib64",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.a",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib64",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.so",
+			},
 		},
 	}
 
 	vendor_snapshot_static {
 		name: "libvendor",
-		version: "28",
+		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",
-		androidmk_suffix: ".vendor",
-		version: "28",
+		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_static {
 		name: "libvendor_available",
-		androidmk_suffix: ".vendor",
-		version: "28",
+		version: "30",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvendor_available.a",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libc++_static",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libc++_static.a",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libc++demangle",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libc++demangle.a",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libgcc_stripped",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libgcc_stripped.a",
+			},
 		},
 	}
 
 	vendor_snapshot_binary {
 		name: "bin",
-		version: "28",
+		version: "30",
 		target_arch: "arm64",
+		compile_multilib: "64",
 		vendor: true,
 		arch: {
 			arm64: {
@@ -484,11 +682,39 @@
 		},
 	}
 
+	vendor_snapshot_binary {
+		name: "bin32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "bin32",
+			},
+		},
+	}
+
 	// 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: {
@@ -502,23 +728,32 @@
 	mockFS := map[string][]byte{
 		"deps/Android.bp":              []byte(depsBp),
 		"framework/Android.bp":         []byte(frameworkBp),
+		"framework/symbol.txt":         nil,
 		"vendor/Android.bp":            []byte(vendorProprietaryBp),
 		"vendor/bin":                   nil,
+		"vendor/bin32":                 nil,
 		"vendor/bin.cpp":               nil,
 		"vendor/client.cpp":            nil,
 		"vendor/include/libvndk/a.h":   nil,
 		"vendor/include/libvendor/b.h": nil,
+		"vendor/libc++_static.a":       nil,
+		"vendor/libc++demangle.a":      nil,
+		"vendor/libgcc_striped.a":      nil,
 		"vendor/libvndk.a":             nil,
 		"vendor/libvendor.a":           nil,
 		"vendor/libvendor.so":          nil,
+		"vendor/lib32.a":               nil,
+		"vendor/lib32.so":              nil,
+		"vendor/lib64.a":               nil,
+		"vendor/lib64.so":              nil,
 		"vndk/Android.bp":              []byte(vndkBp),
 		"vndk/include/libvndk/a.h":     nil,
 		"vndk/libvndk.so":              nil,
 	}
 
 	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
-	config.TestProductVariables.DeviceVndkVersion = StringPtr("28")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("30")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("31")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -527,11 +762,14 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	android.FailIfErrored(t, errs)
 
-	sharedVariant := "android_vendor.28_arm64_armv8-a_shared"
-	staticVariant := "android_vendor.28_arm64_armv8-a_static"
-	binaryVariant := "android_vendor.28_arm64_armv8-a"
+	sharedVariant := "android_vendor.30_arm64_armv8-a_shared"
+	staticVariant := "android_vendor.30_arm64_armv8-a_static"
+	binaryVariant := "android_vendor.30_arm64_armv8-a"
 
-	// libclient uses libvndk.vndk.28.arm64, libvendor.vendor_static.28.arm64, libvendor_without_snapshot
+	shared32Variant := "android_vendor.30_arm_armv7-a-neon_shared"
+	binary32Variant := "android_vendor.30_arm_armv7-a-neon"
+
+	// libclient uses libvndk.vndk.30.arm64, libvendor.vendor_static.30.arm64, libvendor_without_snapshot
 	libclientCcFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("cc").Args["cFlags"]
 	for _, includeFlags := range []string{
 		"-Ivndk/include/libvndk",     // libvndk
@@ -545,8 +783,8 @@
 
 	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("ld").Args["libFlags"]
 	for _, input := range [][]string{
-		[]string{sharedVariant, "libvndk.vndk.28.arm64"},
-		[]string{staticVariant, "libvendor.vendor_static.28.arm64"},
+		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
+		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
 		[]string{staticVariant, "libvendor_without_snapshot"},
 	} {
 		outputPaths := getOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
@@ -556,7 +794,7 @@
 	}
 
 	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
-	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor"}; !reflect.DeepEqual(g, w) {
+	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64"}; !reflect.DeepEqual(g, w) {
 		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
 	}
 
@@ -565,7 +803,12 @@
 		t.Errorf("wanted libclient AndroidMkStaticLibs %q, got %q", w, g)
 	}
 
-	// bin_without_snapshot uses libvndk.vendor_static.28.arm64
+	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	// bin_without_snapshot uses libvndk.vendor_static.30.arm64
 	binWithoutSnapshotCcFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("cc").Args["cFlags"]
 	if !strings.Contains(binWithoutSnapshotCcFlags, "-Ivendor/include/libvndk") {
 		t.Errorf("flags for bin_without_snapshot must contain %#v, but was %#v.",
@@ -573,28 +816,37 @@
 	}
 
 	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("ld").Args["libFlags"]
-	libVndkStaticOutputPaths := getOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.28.arm64"})
+	libVndkStaticOutputPaths := 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)
 	}
 
-	// libvendor.so is installed by libvendor.vendor_shared.28.arm64
-	ctx.ModuleForTests("libvendor.vendor_shared.28.arm64", sharedVariant).Output("libvendor.so")
+	// libvendor.so is installed by libvendor.vendor_shared.30.arm64
+	ctx.ModuleForTests("libvendor.vendor_shared.30.arm64", sharedVariant).Output("libvendor.so")
 
-	// libvendor_available.so is installed by libvendor_available.vendor_shared.28.arm64
-	ctx.ModuleForTests("libvendor_available.vendor_shared.28.arm64", sharedVariant).Output("libvendor_available.so")
+	// lib64.so is installed by lib64.vendor_shared.30.arm64
+	ctx.ModuleForTests("lib64.vendor_shared.30.arm64", sharedVariant).Output("lib64.so")
+
+	// lib32.so is installed by lib32.vendor_shared.30.arm64
+	ctx.ModuleForTests("lib32.vendor_shared.30.arm64", shared32Variant).Output("lib32.so")
+
+	// libvendor_available.so is installed by libvendor_available.vendor_shared.30.arm64
+	ctx.ModuleForTests("libvendor_available.vendor_shared.30.arm64", sharedVariant).Output("libvendor_available.so")
 
 	// libvendor_without_snapshot.so is installed by libvendor_without_snapshot
 	ctx.ModuleForTests("libvendor_without_snapshot", sharedVariant).Output("libvendor_without_snapshot.so")
 
-	// bin is installed by bin.vendor_binary.28.arm64
-	ctx.ModuleForTests("bin.vendor_binary.28.arm64", binaryVariant).Output("bin")
+	// 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.28 variant
+	// libvendor, libvendor_available and bin don't have vendor.30 variant
 	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
 	if inList(sharedVariant, libvendorVariants) {
 		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
@@ -613,6 +865,18 @@
 
 func TestVendorSnapshotSanitizer(t *testing.T) {
 	bp := `
+	vendor_snapshot {
+		name: "vendor_snapshot",
+		version: "28",
+		arch: {
+			arm64: {
+				static_libs: [
+					"libsnapshot",
+					"note_memtag_heap_sync",
+				],
+			},
+		},
+	}
 	vendor_snapshot_static {
 		name: "libsnapshot",
 		vendor: true,
@@ -627,8 +891,41 @@
 			},
 		},
 	}
+
+	vendor_snapshot_static {
+		name: "note_memtag_heap_sync",
+		vendor: true,
+		target_arch: "arm64",
+		version: "28",
+		arch: {
+			arm64: {
+				src: "note_memtag_heap_sync.a",
+			},
+		},
+	}
+
+	cc_test {
+		name: "vstest",
+		gtest: false,
+		vendor: true,
+		compile_multilib: "64",
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		static_libs: ["libsnapshot"],
+		system_shared_libs: [],
+	}
 `
-	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+
+	mockFS := map[string][]byte{
+		"vendor/Android.bp":              []byte(bp),
+		"vendor/libc++demangle.a":        nil,
+		"vendor/libsnapshot.a":           nil,
+		"vendor/libsnapshot.cfi.a":       nil,
+		"vendor/note_memtag_heap_sync.a": nil,
+	}
+
+	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("28")
 	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 3f1e9f3..3ce4f85 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -696,7 +696,8 @@
 	result := android.GroupFixturePreparers(
 		prepareForGenRuleTest, android.FixtureModifyConfig(func(config android.Config) {
 			config.BazelContext = android.MockBazelContext{
-				AllFiles: map[string][]string{
+				OutputBaseDir: "outputbase",
+				LabelToOutputFiles: map[string][]string{
 					"//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}}
 		})).RunTestWithBp(t, testGenruleBp()+bp)
 
diff --git a/java/Android.bp b/java/Android.bp
index 8334b85..f8ba1b6 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -42,6 +42,7 @@
         "gen.go",
         "genrule.go",
         "hiddenapi.go",
+        "hiddenapi_modular.go",
         "hiddenapi_singleton.go",
         "jacoco.go",
         "java.go",
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
new file mode 100644
index 0000000..bed11fe
--- /dev/null
+++ b/java/hiddenapi_modular.go
@@ -0,0 +1,172 @@
+// 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 java
+
+import (
+	"android/soong/android"
+)
+
+// Contains support for processing hiddenAPI in a modular fashion.
+
+// HiddenAPIAugmentationProperties contains paths to the files that can be used to augment the information
+// obtained from annotations within the source code in order to create the complete set of flags
+// that should be applied to the dex implementation jars on the bootclasspath.
+//
+// Each property contains a list of paths. With the exception of the Unsupported_packages the paths
+// of each property reference a plain text file that contains a java signature per line. The flags
+// for each of those signatures will be updated in a property specific way.
+//
+// The Unsupported_packages property contains a list of paths, each of which is a plain text file
+// with one Java package per line. All members of all classes within that package (but not nested
+// packages) will be updated in a property specific way.
+type HiddenAPIAugmentationProperties struct {
+	// Marks each signature in the referenced files as being unsupported.
+	Unsupported []string `android:"path"`
+
+	// Marks each signature in the referenced files as being unsupported because it has been removed.
+	// Any conflicts with other flags are ignored.
+	Removed []string `android:"path"`
+
+	// Marks each signature in the referenced files as being supported only for targetSdkVersion <= R
+	// and low priority.
+	Max_target_r_low_priority []string `android:"path"`
+
+	// Marks each signature in the referenced files as being supported only for targetSdkVersion <= Q.
+	Max_target_q []string `android:"path"`
+
+	// Marks each signature in the referenced files as being supported only for targetSdkVersion <= P.
+	Max_target_p []string `android:"path"`
+
+	// Marks each signature in the referenced files as being supported only for targetSdkVersion <= O
+	// and low priority. Any conflicts with other flags are ignored.
+	Max_target_o_low_priority []string `android:"path"`
+
+	// Marks each signature in the referenced files as being blocked.
+	Blocked []string `android:"path"`
+
+	// Marks each signature in every package in the referenced files as being unsupported.
+	Unsupported_packages []string `android:"path"`
+}
+
+func (p *HiddenAPIAugmentationProperties) hiddenAPIAugmentationInfo(ctx android.ModuleContext) hiddenAPIAugmentationInfo {
+	paths := func(paths []string) android.Paths { return android.PathsForModuleSrc(ctx, paths) }
+	return hiddenAPIAugmentationInfo{
+		Unsupported:               paths(p.Unsupported),
+		Removed:                   paths(p.Removed),
+		Max_target_r_low_priority: paths(p.Max_target_r_low_priority),
+		Max_target_q:              paths(p.Max_target_q),
+		Max_target_p:              paths(p.Max_target_p),
+		Max_target_o_low_priority: paths(p.Max_target_o_low_priority),
+		Blocked:                   paths(p.Blocked),
+		Unsupported_packages:      paths(p.Unsupported_packages),
+	}
+}
+
+// hiddenAPIAugmentationInfo contains paths resolved from HiddenAPIAugmentationProperties
+type hiddenAPIAugmentationInfo struct {
+	// See HiddenAPIAugmentationProperties.Unsupported
+	Unsupported android.Paths
+
+	// See HiddenAPIAugmentationProperties.Removed
+	Removed android.Paths
+
+	// See HiddenAPIAugmentationProperties.Max_target_r_low_priority
+	Max_target_r_low_priority android.Paths
+
+	// See HiddenAPIAugmentationProperties.Max_target_q
+	Max_target_q android.Paths
+
+	// See HiddenAPIAugmentationProperties.Max_target_p
+	Max_target_p android.Paths
+
+	// See HiddenAPIAugmentationProperties.Max_target_o_low_priority
+	Max_target_o_low_priority android.Paths
+
+	// See HiddenAPIAugmentationProperties.Blocked
+	Blocked android.Paths
+
+	// See HiddenAPIAugmentationProperties.Unsupported_packages
+	Unsupported_packages android.Paths
+}
+
+// ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
+// flags from all the modules, the stub flags, augmented with some additional configuration files.
+//
+// baseFlagsPath is the path to the flags file containing all the information from the stubs plus
+// an entry for every single member in the dex implementation jars of the individual modules. Every
+// signature in any of the other files MUST be included in this file.
+//
+// moduleSpecificFlagsPaths are the paths to the flags files generated by each module using
+// information from the baseFlagsPath as well as from annotations within the source.
+//
+// augmentationInfo is a struct containing paths to files that augment the information provided by
+// the moduleSpecificFlagsPaths.
+// ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
+// flags from all the modules, the stub flags, augmented with some additional configuration files.
+//
+// baseFlagsPath is the path to the flags file containing all the information from the stubs plus
+// an entry for every single member in the dex implementation jars of the individual modules. Every
+// signature in any of the other files MUST be included in this file.
+//
+// moduleSpecificFlagsPaths are the paths to the flags files generated by each module using
+// information from the baseFlagsPath as well as from annotations within the source.
+//
+// augmentationInfo is a struct containing paths to files that augment the information provided by
+// the moduleSpecificFlagsPaths.
+func ruleToGenerateHiddenApiFlags(ctx android.BuilderContext, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, augmentationInfo hiddenAPIAugmentationInfo) {
+	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
+	rule := android.NewRuleBuilder(pctx, ctx)
+	command := rule.Command().
+		BuiltTool("generate_hiddenapi_lists").
+		FlagWithInput("--csv ", baseFlagsPath).
+		Inputs(moduleSpecificFlagsPaths).
+		FlagWithOutput("--output ", tempPath)
+
+	for _, path := range augmentationInfo.Unsupported {
+		command.FlagWithInput("--unsupported ", path)
+	}
+
+	for _, path := range augmentationInfo.Removed {
+		command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
+	}
+
+	for _, path := range augmentationInfo.Max_target_r_low_priority {
+		command.FlagWithInput("--max-target-r ", path).FlagWithArg("--tag ", "lo-prio")
+	}
+
+	for _, path := range augmentationInfo.Max_target_q {
+		command.FlagWithInput("--max-target-q ", path)
+	}
+
+	for _, path := range augmentationInfo.Max_target_p {
+		command.FlagWithInput("--max-target-p ", path)
+	}
+
+	for _, path := range augmentationInfo.Max_target_o_low_priority {
+		command.FlagWithInput("--max-target-o ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio")
+	}
+
+	for _, path := range augmentationInfo.Blocked {
+		command.FlagWithInput("--blocked ", path)
+	}
+
+	for _, path := range augmentationInfo.Unsupported_packages {
+		command.FlagWithInput("--unsupported ", path).Flag("--packages ")
+	}
+
+	commitChangeForRestat(rule, tempPath, outputPath)
+
+	rule.Build("hiddenAPIFlagsFile", "hiddenapi flags")
+}
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 7e9477b..641e19f 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -18,7 +18,6 @@
 	"fmt"
 
 	"android/soong/android"
-	"android/soong/genrule"
 )
 
 func init() {
@@ -321,63 +320,10 @@
 	return outputPath
 }
 
-// flagsRule creates a rule to build hiddenapi-flags.csv out of flags.csv files generated for boot image modules and
-// the unsupported API.
+// flagsRule is a placeholder that simply returns the location of the file, the generation of the
+// ninja rules is done in generateHiddenAPIBuildActions.
 func flagsRule(ctx android.SingletonContext) android.Path {
-	var flagsCSV android.Paths
-	var combinedRemovedApis android.Path
-
-	ctx.VisitAllModules(func(module android.Module) {
-		if h, ok := module.(hiddenAPIIntf); ok {
-			if csv := h.flagsCSV(); csv != nil {
-				flagsCSV = append(flagsCSV, csv)
-			}
-		} else if g, ok := module.(*genrule.Module); ok {
-			if ctx.ModuleName(module) == "combined-removed-dex" {
-				if len(g.GeneratedSourceFiles()) != 1 || combinedRemovedApis != nil {
-					ctx.Errorf("Expected 1 combined-removed-dex module that generates 1 output file.")
-				}
-				combinedRemovedApis = g.GeneratedSourceFiles()[0]
-			}
-		}
-	})
-
-	if combinedRemovedApis == nil {
-		ctx.Errorf("Failed to find combined-removed-dex.")
-	}
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-
 	outputPath := hiddenAPISingletonPaths(ctx).flags
-	tempPath := android.PathForOutput(ctx, outputPath.Rel()+".tmp")
-
-	stubFlags := hiddenAPISingletonPaths(ctx).stubFlags
-
-	rule.Command().
-		BuiltTool("generate_hiddenapi_lists").
-		FlagWithInput("--csv ", stubFlags).
-		Inputs(flagsCSV).
-		FlagWithInput("--unsupported ",
-			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-unsupported.txt")).
-		FlagWithInput("--unsupported ", combinedRemovedApis).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed").
-		FlagWithInput("--max-target-r ",
-			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt")).FlagWithArg("--tag ", "lo-prio").
-		FlagWithInput("--max-target-q ",
-			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-q.txt")).
-		FlagWithInput("--max-target-p ",
-			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-p.txt")).
-		FlagWithInput("--max-target-o ", android.PathForSource(
-			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
-		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-force-blocked.txt")).
-		FlagWithInput("--unsupported ", android.PathForSource(
-			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
-		FlagWithOutput("--output ", tempPath)
-
-	commitChangeForRestat(rule, tempPath, outputPath)
-
-	rule.Build("hiddenAPIFlagsFile", "hiddenapi flags")
-
 	return outputPath
 }
 
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 5507077..e292d80 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -17,6 +17,8 @@
 import (
 	"android/soong/android"
 	"android/soong/dexpreopt"
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -25,14 +27,73 @@
 
 func registerPlatformBootclasspathBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("platform_bootclasspath", platformBootclasspathFactory)
+
+	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("platform_bootclasspath_deps", platformBootclasspathDepsMutator)
+	})
 }
 
+type platformBootclasspathDependencyTag struct {
+	blueprint.BaseDependencyTag
+
+	name string
+}
+
+// Avoid having to make platform bootclasspath content visible to the platform bootclasspath.
+//
+// This is a temporary workaround to make it easier to migrate to platform bootclasspath with proper
+// dependencies.
+// TODO(b/177892522): Remove this and add needed visibility.
+func (t platformBootclasspathDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+// The tag used for the dependency between the platform bootclasspath and any configured boot jars.
+var platformBootclasspathModuleDepTag = platformBootclasspathDependencyTag{name: "module"}
+
+// The tag used for the dependency between the platform bootclasspath and bootclasspath_fragments.
+var platformBootclasspathFragmentDepTag = platformBootclasspathDependencyTag{name: "fragment"}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = platformBootclasspathDependencyTag{}
+
 type platformBootclasspathModule struct {
 	android.ModuleBase
+
+	properties platformBootclasspathProperties
+
+	// The apex:module pairs obtained from the configured modules.
+	//
+	// Currently only for testing.
+	configuredModules []android.Module
+
+	// The apex:module pairs obtained from the fragments.
+	//
+	// Currently only for testing.
+	fragments []android.Module
+}
+
+// ApexVariantReference specifies a particular apex variant of a module.
+type ApexVariantReference struct {
+	// 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
+	// added to the module's platform variant.
+	Apex *string
+
+	// The name of the module.
+	Module *string
+}
+
+type platformBootclasspathProperties struct {
+	// The names of the bootclasspath_fragment modules that form part of this
+	// platform_bootclasspath.
+	Fragments []ApexVariantReference
+
+	Hidden_api HiddenAPIAugmentationProperties
 }
 
 func platformBootclasspathFactory() android.Module {
 	m := &platformBootclasspathModule{}
+	m.AddProperties(&m.properties)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
@@ -47,7 +108,92 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
+func platformBootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
+	m := ctx.Module()
+	if p, ok := m.(*platformBootclasspathModule); ok {
+		// Add dependencies on all the modules configured in the "art" boot image.
+		artImageConfig := genBootImageConfigs(ctx)[artBootImageName]
+		addDependenciesOntoBootImageModules(ctx, artImageConfig.modules)
+
+		// Add dependencies on all the modules configured in the "boot" boot image. That does not
+		// include modules configured in the "art" boot image.
+		bootImageConfig := p.getImageConfig(ctx)
+		addDependenciesOntoBootImageModules(ctx, bootImageConfig.modules)
+
+		// Add dependencies on all the updatable modules.
+		updatableModules := dexpreopt.GetGlobalConfig(ctx).UpdatableBootJars
+		addDependenciesOntoBootImageModules(ctx, updatableModules)
+
+		// Add dependencies on all the fragments.
+		addDependencyOntoApexVariants(ctx, "fragments", p.properties.Fragments, platformBootclasspathFragmentDepTag)
+	}
+}
+
+func addDependencyOntoApexVariants(ctx android.BottomUpMutatorContext, propertyName string, refs []ApexVariantReference, tag blueprint.DependencyTag) {
+	for i, ref := range refs {
+		apex := proptools.StringDefault(ref.Apex, "platform")
+
+		if ref.Module == nil {
+			ctx.PropertyErrorf(propertyName, "missing module name at position %d", i)
+			continue
+		}
+		name := proptools.String(ref.Module)
+
+		addDependencyOntoApexModulePair(ctx, apex, name, tag)
+	}
+}
+
+func addDependencyOntoApexModulePair(ctx android.BottomUpMutatorContext, apex string, name string, tag blueprint.DependencyTag) {
+	var variations []blueprint.Variation
+	if apex != "platform" {
+		// Pick the correct apex variant.
+		variations = []blueprint.Variation{
+			{Mutator: "apex", Variation: apex},
+		}
+	}
+
+	addedDep := false
+	if ctx.OtherModuleDependencyVariantExists(variations, name) {
+		ctx.AddFarVariationDependencies(variations, tag, name)
+		addedDep = true
+	}
+
+	// Add a dependency on the prebuilt module if it exists.
+	prebuiltName := android.PrebuiltNameFromSource(name)
+	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
+		ctx.AddVariationDependencies(variations, tag, prebuiltName)
+		addedDep = true
+	}
+
+	// If no appropriate variant existing for this, so no dependency could be added, then it is an
+	// error, unless missing dependencies are allowed. The simplest way to handle that is to add a
+	// dependency that will not be satisfied and the default behavior will handle it.
+	if !addedDep {
+		ctx.AddFarVariationDependencies(variations, tag, name)
+	}
+}
+
+func addDependenciesOntoBootImageModules(ctx android.BottomUpMutatorContext, modules android.ConfiguredJarList) {
+	for i := 0; i < modules.Len(); i++ {
+		apex := modules.Apex(i)
+		name := modules.Jar(i)
+
+		addDependencyOntoApexModulePair(ctx, apex, name, platformBootclasspathModuleDepTag)
+	}
+}
+
 func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		if tag == platformBootclasspathModuleDepTag {
+			b.configuredModules = append(b.configuredModules, module)
+		} else if tag == platformBootclasspathFragmentDepTag {
+			b.fragments = append(b.fragments, module)
+		}
+	})
+
+	b.generateHiddenAPIBuildActions(ctx, b.configuredModules)
+
 	// Nothing to do if skipping the dexpreopt of boot image jars.
 	if SkipDexpreoptBootJars(ctx) {
 		return
@@ -72,3 +218,24 @@
 func (b *platformBootclasspathModule) getImageConfig(ctx android.EarlyModuleContext) *bootImageConfig {
 	return defaultBootImageConfig(ctx)
 }
+
+// generateHiddenAPIBuildActions generates all the hidden API related build rules.
+func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module) {
+
+	moduleSpecificFlagsPaths := android.Paths{}
+	for _, module := range modules {
+		if h, ok := module.(hiddenAPIIntf); ok {
+			if csv := h.flagsCSV(); csv != nil {
+				moduleSpecificFlagsPaths = append(moduleSpecificFlagsPaths, csv)
+			}
+		} else {
+			ctx.ModuleErrorf("module %s of type %s does not implement hiddenAPIIntf", module, ctx.OtherModuleType(module))
+		}
+	}
+
+	augmentationInfo := b.properties.Hidden_api.hiddenAPIAugmentationInfo(ctx)
+
+	outputPath := hiddenAPISingletonPaths(ctx).flags
+	baseFlagsPath := hiddenAPISingletonPaths(ctx).stubFlags
+	ruleToGenerateHiddenApiFlags(ctx, outputPath, baseFlagsPath, moduleSpecificFlagsPaths, augmentationInfo)
+}
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 1c81cfd..ebbe3a5 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -29,10 +29,105 @@
 )
 
 func TestPlatformBootclasspath(t *testing.T) {
-	prepareForTestWithPlatformBootclasspath.
-		RunTestWithBp(t, `
+	preparer := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"),
+		android.FixtureWithRootAndroidBp(`
 			platform_bootclasspath {
 				name: "platform-bootclasspath",
 			}
-		`)
+
+			java_library {
+				name: "bar",
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+			}
+		`),
+	)
+
+	var addSourceBootclassPathModule = android.FixtureAddTextFile("source/Android.bp", `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			system_modules: "none",
+			sdk_version: "none",
+			compile_dex: true,
+		}
+	`)
+
+	var addPrebuiltBootclassPathModule = android.FixtureAddTextFile("prebuilt/Android.bp", `
+		java_import {
+			name: "foo",
+			jars: ["a.jar"],
+			compile_dex: true,
+			prefer: false,
+		}
+	`)
+
+	var addPrebuiltPreferredBootclassPathModule = android.FixtureAddTextFile("prebuilt/Android.bp", `
+		java_import {
+			name: "foo",
+			jars: ["a.jar"],
+			compile_dex: true,
+			prefer: true,
+		}
+	`)
+
+	t.Run("missing", func(t *testing.T) {
+		preparer.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"platform-bootclasspath" depends on undefined module "foo"`)).
+			RunTest(t)
+	})
+
+	t.Run("source", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			addSourceBootclassPathModule,
+		).RunTest(t)
+
+		CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{
+			"platform:foo",
+			"platform:bar",
+		})
+	})
+
+	t.Run("prebuilt", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			addPrebuiltBootclassPathModule,
+		).RunTest(t)
+
+		CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{
+			"platform:prebuilt_foo",
+			"platform:bar",
+		})
+	})
+
+	t.Run("source+prebuilt - source preferred", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			addSourceBootclassPathModule,
+			addPrebuiltBootclassPathModule,
+		).RunTest(t)
+
+		CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{
+			"platform:foo",
+			"platform:bar",
+		})
+	})
+
+	t.Run("source+prebuilt - prebuilt preferred", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			addSourceBootclassPathModule,
+			addPrebuiltPreferredBootclassPathModule,
+		).RunTest(t)
+
+		CheckPlatformBootclasspathModules(t, result, "platform-bootclasspath", []string{
+			"platform:prebuilt_foo",
+			"platform:bar",
+		})
+	})
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index eb9ba9b..96135c3 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -201,8 +201,12 @@
 	return scope
 }
 
+func (scope *apiScope) stubsLibraryModuleNameSuffix() string {
+	return ".stubs" + scope.moduleSuffix
+}
+
 func (scope *apiScope) stubsLibraryModuleName(baseName string) string {
-	return baseName + ".stubs" + scope.moduleSuffix
+	return baseName + scope.stubsLibraryModuleNameSuffix()
 }
 
 func (scope *apiScope) stubsSourceModuleName(baseName string) string {
@@ -1684,16 +1688,20 @@
 func moduleStubLinkType(name string) (stub bool, ret sdkLinkType) {
 	// This suffix-based approach is fragile and could potentially mis-trigger.
 	// TODO(b/155164730): Clean this up when modules no longer reference sdk_lib stubs directly.
-	if strings.HasSuffix(name, ".stubs.public") || strings.HasSuffix(name, "-stubs-publicapi") {
+	if strings.HasSuffix(name, apiScopePublic.stubsLibraryModuleNameSuffix()) {
+		if name == "hwbinder.stubs" || name == "libcore_private.stubs" {
+			// Due to a previous bug, these modules were not considered stubs, so we retain that.
+			return false, javaPlatform
+		}
 		return true, javaSdk
 	}
-	if strings.HasSuffix(name, ".stubs.system") || strings.HasSuffix(name, "-stubs-systemapi") {
+	if strings.HasSuffix(name, apiScopeSystem.stubsLibraryModuleNameSuffix()) {
 		return true, javaSystem
 	}
-	if strings.HasSuffix(name, ".stubs.module_lib") || strings.HasSuffix(name, "-stubs-module_libs_api") {
+	if strings.HasSuffix(name, apiScopeModuleLib.stubsLibraryModuleNameSuffix()) {
 		return true, javaModule
 	}
-	if strings.HasSuffix(name, ".stubs.test") {
+	if strings.HasSuffix(name, apiScopeTest.stubsLibraryModuleNameSuffix()) {
 		return true, javaSystem
 	}
 	return false, javaPlatform
diff --git a/java/testing.go b/java/testing.go
index 80c107d..6ebc747 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -300,6 +300,46 @@
 	}
 }
 
+// CheckPlatformBootclasspathModules returns the apex:module pair for the modules depended upon by
+// the platform-bootclasspath module.
+func CheckPlatformBootclasspathModules(t *testing.T, result *android.TestResult, name string, expected []string) {
+	t.Helper()
+	platformBootclasspath := result.Module(name, "android_common").(*platformBootclasspathModule)
+	pairs := ApexNamePairsFromModules(result.TestContext, platformBootclasspath.configuredModules)
+	android.AssertDeepEquals(t, fmt.Sprintf("%s modules", "platform-bootclasspath"), expected, pairs)
+}
+
+// ApexNamePairsFromModules returns the apex:module pair for the supplied modules.
+func ApexNamePairsFromModules(ctx *android.TestContext, modules []android.Module) []string {
+	pairs := []string{}
+	for _, module := range modules {
+		pairs = append(pairs, apexNamePairFromModule(ctx, module))
+	}
+	return pairs
+}
+
+func apexNamePairFromModule(ctx *android.TestContext, module android.Module) string {
+	name := module.Name()
+	var apex string
+	apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
+	if apexInfo.IsForPlatform() {
+		apex = "platform"
+	} else {
+		apex = apexInfo.InApexes[0]
+	}
+
+	return fmt.Sprintf("%s:%s", apex, name)
+}
+
+// CheckPlatformBootclasspathFragments returns the apex:module pair for the fragments depended upon
+// by the platform-bootclasspath module.
+func CheckPlatformBootclasspathFragments(t *testing.T, result *android.TestResult, name string, expected []string) {
+	t.Helper()
+	platformBootclasspath := result.Module(name, "android_common").(*platformBootclasspathModule)
+	pairs := ApexNamePairsFromModules(result.TestContext, platformBootclasspath.fragments)
+	android.AssertDeepEquals(t, fmt.Sprintf("%s fragments", "platform-bootclasspath"), expected, pairs)
+}
+
 func CheckHiddenAPIRuleInputs(t *testing.T, expected string, hiddenAPIRule android.TestingBuildParams) {
 	t.Helper()
 	actual := strings.TrimSpace(strings.Join(android.NormalizePathsForTesting(hiddenAPIRule.Implicits), "\n"))
diff --git a/rust/androidmk.go b/rust/androidmk.go
index b0e6967..dea32a3 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -70,6 +70,11 @@
 		// If the compiler is disabled, this is a SourceProvider.
 		mod.SubAndroidMk(&ret, mod.sourceProvider)
 	}
+
+	if mod.sanitize != nil {
+		mod.SubAndroidMk(&ret, mod.sanitize)
+	}
+
 	ret.SubName += mod.Properties.SubName
 
 	return []android.AndroidMkEntries{ret}
diff --git a/rust/builder.go b/rust/builder.go
index 9d462d4..197c703 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -51,9 +51,12 @@
 			Command: "$envVars $clippyCmd " +
 				// Because clippy-driver uses rustc as backend, we need to have some output even during the linting.
 				// Use the metadata output as it has the smallest footprint.
-				"--emit metadata -o $out $in ${libFlags} " +
-				"$rustcFlags $clippyFlags",
+				"--emit metadata -o $out --emit dep-info=$out.d.raw $in ${libFlags} " +
+				"$rustcFlags $clippyFlags" +
+				" && grep \"^$out:\" $out.d.raw > $out.d",
 			CommandDeps: []string{"$clippyCmd"},
+			Deps:        blueprint.DepsGCC,
+			Depfile:     "$out.d",
 		},
 		"rustcFlags", "libFlags", "clippyFlags", "envVars")
 
diff --git a/rust/compiler.go b/rust/compiler.go
index 2e85f5a..aaa1924 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -76,7 +76,7 @@
 	// errors). The default value is "default".
 	Lints *string
 
-	// flags to pass to rustc
+	// flags to pass to rustc. To enable configuration options or features, use the "cfgs" or "features" properties.
 	Flags []string `android:"path,arch_variant"`
 
 	// flags to pass to the linker
@@ -125,6 +125,9 @@
 	// list of features to enable for this crate
 	Features []string `android:"arch_variant"`
 
+	// list of configuration options to enable for this crate. To enable features, use the "features" property.
+	Cfgs []string `android:"arch_variant"`
+
 	// specific rust edition that should be used if the default version is not desired
 	Edition *string `android:"arch_variant"`
 
@@ -210,9 +213,17 @@
 	return []interface{}{&compiler.Properties}
 }
 
-func (compiler *baseCompiler) featuresToFlags(features []string) []string {
+func (compiler *baseCompiler) cfgsToFlags() []string {
 	flags := []string{}
-	for _, feature := range features {
+	for _, cfg := range compiler.Properties.Cfgs {
+		flags = append(flags, "--cfg '"+cfg+"'")
+	}
+	return flags
+}
+
+func (compiler *baseCompiler) featuresToFlags() []string {
+	flags := []string{}
+	for _, feature := range compiler.Properties.Features {
 		flags = append(flags, "--cfg 'feature=\""+feature+"\"'")
 	}
 	return flags
@@ -226,7 +237,8 @@
 	}
 	flags.RustFlags = append(flags.RustFlags, lintFlags)
 	flags.RustFlags = append(flags.RustFlags, compiler.Properties.Flags...)
-	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags(compiler.Properties.Features)...)
+	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
+	flags.RustFlags = append(flags.RustFlags, compiler.featuresToFlags()...)
 	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
 	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index c752762..5ca9e7f 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -42,6 +42,27 @@
 	}
 }
 
+// Test that cfgs flags are being correctly generated.
+func TestCfgsToFlags(t *testing.T) {
+	ctx := testRust(t, `
+		rust_library_host {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			cfgs: [
+				"std",
+				"cfg1=\"one\""
+			],
+		}`)
+
+	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+
+	if !strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'std'") ||
+		!strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'cfg1=\"one\"'") {
+		t.Fatalf("missing std and cfg1 flags for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
+	}
+}
+
 // Test that we reject multiple source files.
 func TestEnforceSingleSourceFile(t *testing.T) {
 
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 1c8e43e..394fcc5 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -23,6 +23,7 @@
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
 		"system/hardware/interfaces/keystore2",
+		"system/logging/rust",
 		"system/security",
 		"system/tools/aidl",
 	}
diff --git a/rust/config/global.go b/rust/config/global.go
index 9208ddb..18776ab 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.50.0"
+	RustDefaultVersion = "1.51.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2018"
 	Stdlibs            = []string{
diff --git a/rust/config/lints.go b/rust/config/lints.go
index 7c05e4f..ef6b315 100644
--- a/rust/config/lints.go
+++ b/rust/config/lints.go
@@ -54,6 +54,7 @@
 		"-A clippy::type-complexity",
 		"-A clippy::unnecessary-wraps",
 		"-A clippy::unusual-byte-groupings",
+		"-A clippy::upper-case-acronyms",
 	}
 
 	// Rust lints for vendor code.
diff --git a/rust/rust.go b/rust/rust.go
index 566ad37..34e197a 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -798,6 +798,11 @@
 	return ok && tag == dylibDepTag
 }
 
+func IsRlibDepTag(depTag blueprint.DependencyTag) bool {
+	tag, ok := depTag.(dependencyTag)
+	return ok && tag == rlibDepTag
+}
+
 type autoDep struct {
 	variation string
 	depTag    dependencyTag
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 2498aa1..ae3eff0 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -23,11 +23,12 @@
 )
 
 type SanitizeProperties struct {
-	// enable AddressSanitizer, ThreadSanitizer, or UndefinedBehaviorSanitizer
+	// enable AddressSanitizer, HWAddressSanitizer, and others.
 	Sanitize struct {
-		Address *bool `android:"arch_variant"`
-		Fuzzer  *bool `android:"arch_variant"`
-		Never   *bool `android:"arch_variant"`
+		Address   *bool `android:"arch_variant"`
+		Hwaddress *bool `android:"arch_variant"`
+		Fuzzer    *bool `android:"arch_variant"`
+		Never     *bool `android:"arch_variant"`
 	}
 	SanitizerEnabled bool `blueprint:"mutated"`
 	SanitizeDep      bool `blueprint:"mutated"`
@@ -43,7 +44,6 @@
 	"-C llvm-args=-sanitizer-coverage-level=3",
 	"-C llvm-args=-sanitizer-coverage-trace-compares",
 	"-C llvm-args=-sanitizer-coverage-inline-8bit-counters",
-	"-C llvm-args=-sanitizer-coverage-stack-depth",
 	"-C llvm-args=-sanitizer-coverage-trace-geps",
 	"-C llvm-args=-sanitizer-coverage-prune-blocks=0",
 	"-Z sanitizer=address",
@@ -57,6 +57,11 @@
 	"-Z sanitizer=address",
 }
 
+var hwasanFlags = []string{
+	"-Z sanitizer=hwaddress",
+	"-C target-feature=+tagged-globals",
+}
+
 func boolPtr(v bool) *bool {
 	if v {
 		return &v
@@ -83,6 +88,15 @@
 	if ctx.Os() == android.Android && Bool(s.Address) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
+
+	// HWASan requires AArch64 hardware feature (top-byte-ignore).
+	if ctx.Arch().ArchType != android.Arm64 {
+		s.Hwaddress = nil
+	}
+
+	if ctx.Os() == android.Android && Bool(s.Hwaddress) {
+		sanitize.Properties.SanitizerEnabled = true
+	}
 }
 
 type sanitize struct {
@@ -99,6 +113,9 @@
 	if Bool(sanitize.Properties.Sanitize.Address) {
 		flags.RustFlags = append(flags.RustFlags, asanFlags...)
 	}
+	if Bool(sanitize.Properties.Sanitize.Hwaddress) {
+		flags.RustFlags = append(flags.RustFlags, hwasanFlags...)
+	}
 	return flags, deps
 }
 
@@ -111,11 +128,38 @@
 		if !mod.Enabled() {
 			return
 		}
+
+		variations := mctx.Target().Variations()
+		var depTag blueprint.DependencyTag
+		var deps []string
+
 		if Bool(mod.sanitize.Properties.Sanitize.Fuzzer) || Bool(mod.sanitize.Properties.Sanitize.Address) {
-			mctx.AddFarVariationDependencies(append(mctx.Target().Variations(), []blueprint.Variation{
-				{Mutator: "link", Variation: "shared"},
-			}...), cc.SharedDepTag(), config.LibclangRuntimeLibrary(mod.toolchain(mctx), "asan"))
+			variations = append(variations,
+				blueprint.Variation{Mutator: "link", Variation: "shared"})
+			depTag = cc.SharedDepTag()
+			deps = []string{config.LibclangRuntimeLibrary(mod.toolchain(mctx), "asan")}
+		} else if mod.IsSanitizerEnabled(cc.Hwasan) {
+			// 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) {
+					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")}
+			}
 		}
+
+		mctx.AddFarVariationDependencies(variations, depTag, deps...)
 	}
 }
 
@@ -128,6 +172,9 @@
 	case cc.Asan:
 		sanitize.Properties.Sanitize.Address = boolPtr(b)
 		sanitizerSet = true
+	case cc.Hwasan:
+		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
+		sanitizerSet = true
 	default:
 		panic(fmt.Errorf("setting unsupported sanitizerType %d", t))
 	}
@@ -169,11 +216,23 @@
 		return sanitize.Properties.Sanitize.Fuzzer
 	case cc.Asan:
 		return sanitize.Properties.Sanitize.Address
+	case cc.Hwasan:
+		return sanitize.Properties.Sanitize.Hwaddress
 	default:
 		return nil
 	}
 }
 
+func (sanitize *sanitize) AndroidMk(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+	// Add a suffix for hwasan rlib libraries to allow surfacing both the sanitized and
+	// non-sanitized variants to make without a name conflict.
+	if entries.Class == "RLIB_LIBRARIES" || entries.Class == "STATIC_LIBRARIES" {
+		if sanitize.isSanitizerEnabled(cc.Hwasan) {
+			entries.SubName += ".hwasan"
+		}
+	}
+}
+
 func (mod *Module) SanitizerSupported(t cc.SanitizerType) bool {
 	if mod.Host() {
 		return false
@@ -183,6 +242,8 @@
 		return true
 	case cc.Asan:
 		return true
+	case cc.Hwasan:
+		return true
 	default:
 		return false
 	}
diff --git a/scripts/strip.sh b/scripts/strip.sh
index 43e6cbf..e3e5273 100755
--- a/scripts/strip.sh
+++ b/scripts/strip.sh
@@ -18,7 +18,6 @@
 # Inputs:
 #  Environment:
 #   CLANG_BIN: path to the clang bin directory
-#   CROSS_COMPILE: prefix added to readelf, objcopy tools
 #   XZ: path to the xz binary
 #  Arguments:
 #   -i ${file}: input file (required)
@@ -69,7 +68,7 @@
 
     KEEP_SYMBOLS="--strip-unneeded-symbol=* --keep-symbols="
     KEEP_SYMBOLS+="${outfile}.symbolList"
-    "${CROSS_COMPILE}objcopy" -w "${infile}" "${outfile}.tmp" ${KEEP_SYMBOLS}
+    "${CLANG_BIN}/llvm-objcopy" -w "${infile}" "${outfile}.tmp" ${KEEP_SYMBOLS}
 }
 
 do_strip_keep_mini_debug_info() {
@@ -78,18 +77,13 @@
     "${CLANG_BIN}/llvm-strip" --strip-all --keep-section=.ARM.attributes --remove-section=.comment "${infile}" -o "${outfile}.tmp" || fail=true
 
     if [ -z $fail ]; then
-        # Current prebult llvm-objcopy does not support --only-keep-debug flag,
-        # and cannot process object files that are produced with the flag. Use
-        # GNU objcopy instead for now. (b/141010852)
-        "${CROSS_COMPILE}objcopy" --only-keep-debug "${infile}" "${outfile}.debug"
+        "${CLANG_BIN}/llvm-objcopy" --only-keep-debug "${infile}" "${outfile}.debug"
         "${CLANG_BIN}/llvm-nm" -D "${infile}" --format=posix --defined-only 2> /dev/null | awk '{ print $1 }' | sort >"${outfile}.dynsyms"
         "${CLANG_BIN}/llvm-nm" "${infile}" --format=posix --defined-only | awk '{ if ($2 == "T" || $2 == "t" || $2 == "D") print $1 }' | sort > "${outfile}.funcsyms"
         comm -13 "${outfile}.dynsyms" "${outfile}.funcsyms" > "${outfile}.keep_symbols"
         echo >> "${outfile}.keep_symbols" # Ensure that the keep_symbols file is not empty.
-        "${CROSS_COMPILE}objcopy" --rename-section .debug_frame=saved_debug_frame "${outfile}.debug" "${outfile}.mini_debuginfo"
-        "${CROSS_COMPILE}objcopy" -S --remove-section .gdb_index --remove-section .comment --remove-section .rustc --keep-symbols="${outfile}.keep_symbols" "${outfile}.mini_debuginfo"
-        "${CROSS_COMPILE}objcopy" --rename-section saved_debug_frame=.debug_frame "${outfile}.mini_debuginfo"
-        "${XZ}" --block-size=64k --threads=0 "${outfile}.mini_debuginfo"
+        "${CLANG_BIN}/llvm-objcopy" -S --keep-section .debug_frame --keep-symbols="${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo"
+        "${XZ}" --keep --block-size=64k --threads=0 "${outfile}.mini_debuginfo"
 
         "${CLANG_BIN}/llvm-objcopy" --add-section .gnu_debugdata="${outfile}.mini_debuginfo.xz" "${outfile}.tmp"
         rm -f "${outfile}.dynsyms" "${outfile}.funcsyms" "${outfile}.keep_symbols" "${outfile}.debug" "${outfile}.mini_debuginfo" "${outfile}.mini_debuginfo.xz"
@@ -196,7 +190,6 @@
 cat <<EOF > "${depsfile}"
 ${outfile}: \
   ${infile} \
-  ${CROSS_COMPILE}objcopy \
   ${CLANG_BIN}/llvm-nm \
   ${CLANG_BIN}/llvm-objcopy \
   ${CLANG_BIN}/llvm-readelf \
diff --git a/bootstrap_test.sh b/tests/bootstrap_test.sh
similarity index 77%
rename from bootstrap_test.sh
rename to tests/bootstrap_test.sh
index 9d87697..b2f7b2b 100755
--- a/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -3,106 +3,13 @@
 # This test exercises the bootstrapping process of the build system
 # in a source tree that only contains enough files for Bazel and Soong to work.
 
-HARDWIRED_MOCK_TOP=
-# Uncomment this to be able to view the source tree after a test is run
-# HARDWIRED_MOCK_TOP=/tmp/td
-
-REAL_TOP="$(readlink -f "$(dirname "$0")"/../..)"
-
-function fail {
-  echo ERROR: $1
-  exit 1
-}
-
-function copy_directory() {
-  local dir="$1"
-  local parent="$(dirname "$dir")"
-
-  mkdir -p "$MOCK_TOP/$parent"
-  cp -R "$REAL_TOP/$dir" "$MOCK_TOP/$parent"
-}
-
-function symlink_file() {
-  local file="$1"
-
-  mkdir -p "$MOCK_TOP/$(dirname "$file")"
-  ln -s "$REAL_TOP/$file" "$MOCK_TOP/$file"
-}
-
-function symlink_directory() {
-  local dir="$1"
-
-  mkdir -p "$MOCK_TOP/$dir"
-  # We need to symlink the contents of the directory individually instead of
-  # using one symlink for the whole directory because finder.go doesn't follow
-  # symlinks when looking for Android.bp files
-  for i in $(ls "$REAL_TOP/$dir"); do
-    local target="$MOCK_TOP/$dir/$i"
-    local source="$REAL_TOP/$dir/$i"
-
-    if [[ -e "$target" ]]; then
-      if [[ ! -d "$source" || ! -d "$target" ]]; then
-        fail "Trying to symlink $dir twice"
-      fi
-    else
-      ln -s "$REAL_TOP/$dir/$i" "$MOCK_TOP/$dir/$i";
-    fi
-  done
-}
-
-function setup_bazel() {
-  copy_directory build/bazel
-
-  symlink_directory prebuilts/bazel
-  symlink_directory prebuilts/jdk
-
-  symlink_file WORKSPACE
-  symlink_file tools/bazel
-}
-
-function setup() {
-  if [[ ! -z "$HARDWIRED_MOCK_TOP" ]]; then
-    MOCK_TOP="$HARDWIRED_MOCK_TOP"
-    rm -fr "$MOCK_TOP"
-    mkdir -p "$MOCK_TOP"
-  else
-    MOCK_TOP=$(mktemp -t -d st.XXXXX)
-    trap 'echo cd / && echo rm -fr "$MOCK_TOP"' EXIT
-  fi
-
-  echo "Test case: ${FUNCNAME[1]}, mock top path: $MOCK_TOP"
-  cd "$MOCK_TOP"
-
-  copy_directory build/blueprint
-  copy_directory build/soong
-
-  symlink_directory prebuilts/go
-  symlink_directory prebuilts/build-tools
-  symlink_directory external/golang-protobuf
-
-  touch "$MOCK_TOP/Android.bp"
-
-  export ALLOW_MISSING_DEPENDENCIES=true
-
-  mkdir -p out/soong
-}
-
-function run_soong() {
-  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests
-}
+source "$(dirname "$0")/lib.sh"
 
 function test_smoke {
   setup
   run_soong
 }
 
-function test_bazel_smoke {
-  setup
-  setup_bazel
-
-  tools/bazel info
-
-}
 function test_null_build() {
   setup
   run_soong
@@ -410,7 +317,6 @@
   fi
 }
 
-test_bazel_smoke
 test_smoke
 test_null_build
 test_null_build_after_docs
diff --git a/tests/lib.sh b/tests/lib.sh
new file mode 100644
index 0000000..3c97e14
--- /dev/null
+++ b/tests/lib.sh
@@ -0,0 +1,79 @@
+#!/bin/bash -eu
+
+HARDWIRED_MOCK_TOP=
+# Uncomment this to be able to view the source tree after a test is run
+# HARDWIRED_MOCK_TOP=/tmp/td
+
+REAL_TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+
+function fail {
+  echo ERROR: $1
+  exit 1
+}
+
+function copy_directory() {
+  local dir="$1"
+  local parent="$(dirname "$dir")"
+
+  mkdir -p "$MOCK_TOP/$parent"
+  cp -R "$REAL_TOP/$dir" "$MOCK_TOP/$parent"
+}
+
+function symlink_file() {
+  local file="$1"
+
+  mkdir -p "$MOCK_TOP/$(dirname "$file")"
+  ln -s "$REAL_TOP/$file" "$MOCK_TOP/$file"
+}
+
+function symlink_directory() {
+  local dir="$1"
+
+  mkdir -p "$MOCK_TOP/$dir"
+  # We need to symlink the contents of the directory individually instead of
+  # using one symlink for the whole directory because finder.go doesn't follow
+  # symlinks when looking for Android.bp files
+  for i in $(ls "$REAL_TOP/$dir"); do
+    local target="$MOCK_TOP/$dir/$i"
+    local source="$REAL_TOP/$dir/$i"
+
+    if [[ -e "$target" ]]; then
+      if [[ ! -d "$source" || ! -d "$target" ]]; then
+        fail "Trying to symlink $dir twice"
+      fi
+    else
+      ln -s "$REAL_TOP/$dir/$i" "$MOCK_TOP/$dir/$i";
+    fi
+  done
+}
+
+function setup() {
+  if [[ ! -z "$HARDWIRED_MOCK_TOP" ]]; then
+    MOCK_TOP="$HARDWIRED_MOCK_TOP"
+    rm -fr "$MOCK_TOP"
+    mkdir -p "$MOCK_TOP"
+  else
+    MOCK_TOP=$(mktemp -t -d st.XXXXX)
+    trap 'echo cd / && echo rm -fr "$MOCK_TOP"' EXIT
+  fi
+
+  echo "Test case: ${FUNCNAME[1]}, mock top path: $MOCK_TOP"
+  cd "$MOCK_TOP"
+
+  copy_directory build/blueprint
+  copy_directory build/soong
+
+  symlink_directory prebuilts/go
+  symlink_directory prebuilts/build-tools
+  symlink_directory external/golang-protobuf
+
+  touch "$MOCK_TOP/Android.bp"
+
+  export ALLOW_MISSING_DEPENDENCIES=true
+
+  mkdir -p out/soong
+}
+
+function run_soong() {
+  build/soong/soong_ui.bash --make-mode --skip-ninja --skip-make --skip-soong-tests
+}
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
new file mode 100755
index 0000000..54f0689
--- /dev/null
+++ b/tests/mixed_mode_test.sh
@@ -0,0 +1,28 @@
+#!/bin/bash -eu
+
+# This test exercises mixed builds where Soong and Bazel cooperate in building
+# Android.
+#
+# When the execroot is deleted, the Bazel server process will automatically
+# terminate itself.
+
+source "$(dirname "$0")/lib.sh"
+
+function setup_bazel() {
+  copy_directory build/bazel
+
+  symlink_directory prebuilts/bazel
+  symlink_directory prebuilts/jdk
+
+  symlink_file WORKSPACE
+  symlink_file tools/bazel
+}
+
+function test_bazel_smoke {
+  setup
+  setup_bazel
+
+  tools/bazel info
+}
+
+test_bazel_smoke
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
new file mode 100755
index 0000000..db24037
--- /dev/null
+++ b/tests/run_integration_tests.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+TOP="$(readlink -f "$(dirname "$0")"/../../..)"
+"$TOP/build/soong/tests/bootstrap_test.sh"
+"$TOP/build/soong/tests/mixed_mode_test.sh"
+