Merge "Handle simple prebuilt static libraries from bazel"
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/prebuilt.go b/android/prebuilt.go
index 8176299..2fc4782 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -99,22 +99,24 @@
 	return proptools.Bool(p.properties.Prefer)
 }
 
-// The below source-related functions and the srcs, src fields are based on an assumption that
-// prebuilt modules have a static source property at the moment. Currently there is only one
-// exception, android_app_import, which chooses a source file depending on the product's DPI
-// preference configs. We'll want to add native support for dynamic source cases if we end up having
-// more modules like this.
-func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
-	if p.srcsSupplier != nil {
-		srcs := p.srcsSupplier(ctx, ctx.Module())
+// SingleSourcePathFromSupplier invokes the supplied supplier for the current module in the
+// supplied context to retrieve a list of file paths, ensures that the returned list of file paths
+// contains a single value and then assumes that is a module relative file path and converts it to
+// a Path accordingly.
+//
+// Any issues, such as nil supplier or not exactly one file path will be reported as errors on the
+// supplied context and this will return nil.
+func SingleSourcePathFromSupplier(ctx ModuleContext, srcsSupplier PrebuiltSrcsSupplier, srcsPropertyName string) Path {
+	if srcsSupplier != nil {
+		srcs := srcsSupplier(ctx, ctx.Module())
 
 		if len(srcs) == 0 {
-			ctx.PropertyErrorf(p.srcsPropertyName, "missing prebuilt source file")
+			ctx.PropertyErrorf(srcsPropertyName, "missing prebuilt source file")
 			return nil
 		}
 
 		if len(srcs) > 1 {
-			ctx.PropertyErrorf(p.srcsPropertyName, "multiple prebuilt source files")
+			ctx.PropertyErrorf(srcsPropertyName, "multiple prebuilt source files")
 			return nil
 		}
 
@@ -128,6 +130,15 @@
 	}
 }
 
+// The below source-related functions and the srcs, src fields are based on an assumption that
+// prebuilt modules have a static source property at the moment. Currently there is only one
+// exception, android_app_import, which chooses a source file depending on the product's DPI
+// preference configs. We'll want to add native support for dynamic source cases if we end up having
+// more modules like this.
+func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
+	return SingleSourcePathFromSupplier(ctx, p.srcsSupplier, p.srcsPropertyName)
+}
+
 func (p *Prebuilt) UsePrebuilt() bool {
 	return p.properties.UsePrebuilt
 }
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 87d551b..5439265 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")
@@ -4316,6 +4327,14 @@
 	}
 }
 
+func TestPrebuiltMissingSrc(t *testing.T) {
+	testApexError(t, `module "myapex" variant "android_common".*: prebuilt_apex does not support "arm64_armv8-a"`, `
+		prebuilt_apex {
+			name: "myapex",
+		}
+	`)
+}
+
 func TestPrebuiltFilenameOverride(t *testing.T) {
 	ctx := testApex(t, `
 		prebuilt_apex {
@@ -4597,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 {
@@ -6360,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",
@@ -6369,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)) {
@@ -7022,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 {
@@ -7037,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/boot_image_test.go b/apex/boot_image_test.go
index 574166a..aa0d9c4 100644
--- a/apex/boot_image_test.go
+++ b/apex/boot_image_test.go
@@ -305,6 +305,7 @@
 	`)
 
 	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common", []string{
+		`com.android.art.apex.selector`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 	})
diff --git a/apex/deapexer.go b/apex/deapexer.go
index 46ce41f..1db13f9 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -49,35 +49,31 @@
 	Exported_java_libs []string
 }
 
+type SelectedApexProperties struct {
+	// The path to the apex selected for use by this module.
+	//
+	// Is tagged as `android:"path"` because it will usually contain a string of the form ":<module>"
+	// and is tagged as "`blueprint:"mutate"` because it is only initialized in a LoadHook not an
+	// Android.bp file.
+	Selected_apex *string `android:"path" blueprint:"mutated"`
+}
+
 type Deapexer struct {
 	android.ModuleBase
-	prebuilt android.Prebuilt
 
-	properties         DeapexerProperties
-	apexFileProperties ApexFileProperties
+	properties             DeapexerProperties
+	selectedApexProperties SelectedApexProperties
 
 	inputApex android.Path
 }
 
 func privateDeapexerFactory() android.Module {
 	module := &Deapexer{}
-	module.AddProperties(
-		&module.properties,
-		&module.apexFileProperties,
-	)
-	android.InitPrebuiltModuleWithSrcSupplier(module, module.apexFileProperties.prebuiltApexSelector, "src")
+	module.AddProperties(&module.properties, &module.selectedApexProperties)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
 
-func (p *Deapexer) Prebuilt() *android.Prebuilt {
-	return &p.prebuilt
-}
-
-func (p *Deapexer) Name() string {
-	return p.prebuilt.Name(p.ModuleBase.Name())
-}
-
 func (p *Deapexer) DepsMutator(ctx android.BottomUpMutatorContext) {
 	// Add dependencies from the java modules to which this exports files from the `.apex` file onto
 	// this module so that they can access the `DeapexerInfo` object that this provides.
@@ -88,7 +84,7 @@
 }
 
 func (p *Deapexer) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
+	p.inputApex = android.OptionalPathForModuleSrc(ctx, p.selectedApexProperties.Selected_apex).Path()
 
 	// Create and remember the directory into which the .apex file's contents will be unpacked.
 	deapexerOutput := android.PathForModuleOut(ctx, "deapexer")
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 68f2859..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,208 +94,16 @@
 	return false
 }
 
-type Prebuilt struct {
-	android.ModuleBase
-	prebuiltCommon
-
-	properties PrebuiltProperties
-
-	inputApex       android.Path
-	installDir      android.InstallPath
-	installFilename string
-	outputApex      android.WritablePath
-
-	// list of commands to create symlinks for backward compatibility.
-	// these commands will be attached as LOCAL_POST_INSTALL_CMD
-	compatSymlinks []string
-}
-
-type ApexFileProperties struct {
-	// the path to the prebuilt .apex file to import.
-	//
-	// This cannot be marked as `android:"arch_variant"` because the `prebuilt_apex` is only mutated
-	// for android_common. That is so that it will have the same arch variant as, and so be compatible
-	// with, the source `apex` module type that it replaces.
-	Src  *string
-	Arch struct {
-		Arm struct {
-			Src *string
-		}
-		Arm64 struct {
-			Src *string
-		}
-		X86 struct {
-			Src *string
-		}
-		X86_64 struct {
-			Src *string
-		}
-	}
-}
-
-// prebuiltApexSelector selects the correct prebuilt APEX file for the build target.
-//
-// The ctx parameter can be for any module not just the prebuilt module so care must be taken not
-// to use methods on it that are specific to the current module.
-//
-// See the ApexFileProperties.Src property.
-func (p *ApexFileProperties) prebuiltApexSelector(ctx android.BaseModuleContext, prebuilt android.Module) []string {
-	multiTargets := prebuilt.MultiTargets()
-	if len(multiTargets) != 1 {
-		ctx.OtherModuleErrorf(prebuilt, "compile_multilib shouldn't be \"both\" for prebuilt_apex")
-		return nil
-	}
-	var src string
-	switch multiTargets[0].Arch.ArchType {
-	case android.Arm:
-		src = String(p.Arch.Arm.Src)
-	case android.Arm64:
-		src = String(p.Arch.Arm64.Src)
-	case android.X86:
-		src = String(p.Arch.X86.Src)
-	case android.X86_64:
-		src = String(p.Arch.X86_64.Src)
-	default:
-		ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
-		return nil
-	}
-	if src == "" {
-		src = String(p.Src)
-	}
-
-	return []string{src}
-}
-
-type PrebuiltProperties struct {
-	ApexFileProperties
-	DeapexerProperties
-
-	Installable *bool
-	// Optional name for the installed apex. If unspecified, name of the
-	// module is used as the file name
-	Filename *string
-
-	// Names of modules to be overridden. Listed modules can only be other binaries
-	// (in Make or Soong).
-	// This does not completely prevent installation of the overridden binaries, but if both
-	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
-	// from PRODUCT_PACKAGES.
-	Overrides []string
-}
-
-func (a *Prebuilt) hasSanitizedSource(sanitizer string) bool {
-	return false
-}
-
-func (p *Prebuilt) installable() bool {
-	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
-}
-
-func (p *Prebuilt) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{p.outputApex}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (p *Prebuilt) InstallFilename() string {
-	return proptools.StringDefault(p.properties.Filename, p.BaseModuleName()+imageApexSuffix)
-}
-
-func (p *Prebuilt) Name() string {
-	return p.prebuiltCommon.prebuilt.Name(p.ModuleBase.Name())
-}
-
-// prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
-//
-// If this needs to make files from within a `.apex` file available for use by other Soong modules,
-// e.g. make dex implementation jars available for java_import modules isted in exported_java_libs,
-// it does so as follows:
-//
-// 1. It creates a `deapexer` module that actually extracts the files from the `.apex` file and
-//    makes them available for use by other modules, at both Soong and ninja levels.
-//
-// 2. It adds a dependency onto those modules and creates an apex specific variant similar to what
-//    an `apex` module does. That ensures that code which looks for specific apex variant, e.g.
-//    dexpreopt, will work the same way from source and prebuilt.
-//
-// 3. The `deapexer` module adds a dependency from the modules that require the exported files onto
-//    itself so that they can retrieve the file paths to those files.
-//
-func PrebuiltFactory() android.Module {
-	module := &Prebuilt{}
-	module.AddProperties(&module.properties)
-	android.InitPrebuiltModuleWithSrcSupplier(module, module.properties.prebuiltApexSelector, "src")
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-
-	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		props := struct {
-			Name *string
-		}{
-			Name: proptools.StringPtr(module.BaseModuleName() + ".deapexer"),
-		}
-		ctx.CreateModule(privateDeapexerFactory,
-			&props,
-			&module.properties.ApexFileProperties,
-			&module.properties.DeapexerProperties,
-		)
-	})
-
-	return module
-}
-
-func prebuiltApexExportedModuleName(ctx android.BottomUpMutatorContext, name string) string {
-	// The prebuilt_apex should be depending on prebuilt modules but as this runs after
-	// prebuilt_rename the prebuilt module may or may not be using the prebuilt_ prefixed named. So,
-	// check to see if the prefixed name is in use first, if it is then use that, otherwise assume
-	// the unprefixed name is the one to use. If the unprefixed one turns out to be a source module
-	// and not a renamed prebuilt module then that will be detected and reported as an error when
-	// processing the dependency in ApexInfoMutator().
-	prebuiltName := android.PrebuiltNameFromSource(name)
-	if ctx.OtherModuleExists(prebuiltName) {
-		name = prebuiltName
-	}
-	return name
-}
-
-type exportedDependencyTag struct {
-	blueprint.BaseDependencyTag
-	name string
-}
-
-// Mark this tag so dependencies that use it are excluded from visibility enforcement.
-//
-// This does allow any prebuilt_apex to reference any module which does open up a small window for
-// restricted visibility modules to be referenced from the wrong prebuilt_apex. However, doing so
-// avoids opening up a much bigger window by widening the visibility of modules that need files
-// provided by the prebuilt_apex to include all the possible locations they may be defined, which
-// could include everything below vendor/.
-//
-// A prebuilt_apex that references a module via this tag will have to contain the appropriate files
-// corresponding to that module, otherwise it will fail when attempting to retrieve the files from
-// the .apex file. It will also have to be included in the module's apex_available property too.
-// That makes it highly unlikely that a prebuilt_apex would reference a restricted module
-// incorrectly.
-func (t exportedDependencyTag) ExcludeFromVisibilityEnforcement() {}
-
-var (
-	exportedJavaLibTag = exportedDependencyTag{name: "exported_java_lib"}
-)
-
-func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) {
+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.properties.Exported_java_libs {
+	for _, lib := range p.deapexerProperties.Exported_java_libs {
 		dep := prebuiltApexExportedModuleName(ctx, lib)
 		ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), exportedJavaLibTag, dep)
 	}
 }
 
-var _ ApexInfoMutator = (*Prebuilt)(nil)
-
-// ApexInfoMutator marks any modules for which this apex exports a file as requiring an apex
+// 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
@@ -317,7 +128,7 @@
 // * 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) {
+func (p *prebuiltCommon) apexInfoMutator(mctx android.TopDownMutatorContext) {
 
 	// Collect direct dependencies into contents.
 	contents := make(map[string]android.ApexMembership)
@@ -373,9 +184,289 @@
 	}
 }
 
+// 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.
+type prebuiltApexSelectorModule struct {
+	android.ModuleBase
+
+	apexFileProperties ApexFileProperties
+
+	inputApex android.Path
+}
+
+func privateApexSelectorModuleFactory() android.Module {
+	module := &prebuiltApexSelectorModule{}
+	module.AddProperties(
+		&module.apexFileProperties,
+	)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (p *prebuiltApexSelectorModule) Srcs() android.Paths {
+	return android.Paths{p.inputApex}
+}
+
+func (p *prebuiltApexSelectorModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.inputApex = android.SingleSourcePathFromSupplier(ctx, p.apexFileProperties.prebuiltApexSelector, "src")
+}
+
+type Prebuilt struct {
+	android.ModuleBase
+	prebuiltCommon
+
+	properties             PrebuiltProperties
+	selectedApexProperties SelectedApexProperties
+
+	inputApex       android.Path
+	installDir      android.InstallPath
+	installFilename string
+	outputApex      android.WritablePath
+
+	// list of commands to create symlinks for backward compatibility.
+	// these commands will be attached as LOCAL_POST_INSTALL_CMD
+	compatSymlinks []string
+}
+
+type ApexFileProperties struct {
+	// the path to the prebuilt .apex file to import.
+	//
+	// This cannot be marked as `android:"arch_variant"` because the `prebuilt_apex` is only mutated
+	// for android_common. That is so that it will have the same arch variant as, and so be compatible
+	// with, the source `apex` module type that it replaces.
+	Src  *string `android:"path"`
+	Arch struct {
+		Arm struct {
+			Src *string `android:"path"`
+		}
+		Arm64 struct {
+			Src *string `android:"path"`
+		}
+		X86 struct {
+			Src *string `android:"path"`
+		}
+		X86_64 struct {
+			Src *string `android:"path"`
+		}
+	}
+}
+
+// prebuiltApexSelector selects the correct prebuilt APEX file for the build target.
+//
+// The ctx parameter can be for any module not just the prebuilt module so care must be taken not
+// to use methods on it that are specific to the current module.
+//
+// See the ApexFileProperties.Src property.
+func (p *ApexFileProperties) prebuiltApexSelector(ctx android.BaseModuleContext, prebuilt android.Module) []string {
+	multiTargets := prebuilt.MultiTargets()
+	if len(multiTargets) != 1 {
+		ctx.OtherModuleErrorf(prebuilt, "compile_multilib shouldn't be \"both\" for prebuilt_apex")
+		return nil
+	}
+	var src string
+	switch multiTargets[0].Arch.ArchType {
+	case android.Arm:
+		src = String(p.Arch.Arm.Src)
+	case android.Arm64:
+		src = String(p.Arch.Arm64.Src)
+	case android.X86:
+		src = String(p.Arch.X86.Src)
+	case android.X86_64:
+		src = String(p.Arch.X86_64.Src)
+	}
+	if src == "" {
+		src = String(p.Src)
+	}
+
+	if src == "" {
+		ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
+		// Drop through to return an empty string as the src (instead of nil) to avoid the prebuilt
+		// logic from reporting a more general, less useful message.
+	}
+
+	return []string{src}
+}
+
+type PrebuiltProperties struct {
+	ApexFileProperties
+
+	Installable *bool
+	// Optional name for the installed apex. If unspecified, name of the
+	// module is used as the file name
+	Filename *string
+
+	// Names of modules to be overridden. Listed modules can only be other binaries
+	// (in Make or Soong).
+	// This does not completely prevent installation of the overridden binaries, but if both
+	// binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed
+	// from PRODUCT_PACKAGES.
+	Overrides []string
+}
+
+func (a *Prebuilt) hasSanitizedSource(sanitizer string) bool {
+	return false
+}
+
+func (p *Prebuilt) installable() bool {
+	return p.properties.Installable == nil || proptools.Bool(p.properties.Installable)
+}
+
+func (p *Prebuilt) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{p.outputApex}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+func (p *Prebuilt) InstallFilename() string {
+	return proptools.StringDefault(p.properties.Filename, p.BaseModuleName()+imageApexSuffix)
+}
+
+func (p *Prebuilt) Name() string {
+	return p.prebuiltCommon.prebuilt.Name(p.ModuleBase.Name())
+}
+
+// prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
+//
+// If this needs to make files from within a `.apex` file available for use by other Soong modules,
+// e.g. make dex implementation jars available for java_import modules isted in exported_java_libs,
+// it does so as follows:
+//
+// 1. It creates a `deapexer` module that actually extracts the files from the `.apex` file and
+//    makes them available for use by other modules, at both Soong and ninja levels.
+//
+// 2. It adds a dependency onto those modules and creates an apex specific variant similar to what
+//    an `apex` module does. That ensures that code which looks for specific apex variant, e.g.
+//    dexpreopt, will work the same way from source and prebuilt.
+//
+// 3. The `deapexer` module adds a dependency from the modules that require the exported files onto
+//    itself so that they can retrieve the file paths to those files.
+//
+// It also creates a child module `selector` that is responsible for selecting the appropriate
+// input apex for both the prebuilt_apex and the deapexer. That is needed for a couple of reasons:
+// 1. To dedup the selection logic so it only runs in one module.
+// 2. To allow the deapexer to be wired up to a different source for the input apex, e.g. an
+//    `apex_set`.
+//
+//                     prebuilt_apex
+//                    /      |      \
+//                 /         |         \
+//              V            |            V
+//       selector  <---  deapexer  <---  exported java lib
+//
+func PrebuiltFactory() android.Module {
+	module := &Prebuilt{}
+	module.AddProperties(&module.properties, &module.deapexerProperties, &module.selectedApexProperties)
+	android.InitSingleSourcePrebuiltModule(module, &module.selectedApexProperties, "Selected_apex")
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		baseModuleName := module.BaseModuleName()
+
+		apexSelectorModuleName := apexSelectorModuleName(baseModuleName)
+		createApexSelectorModule(ctx, apexSelectorModuleName, &module.properties.ApexFileProperties)
+
+		apexFileSource := ":" + apexSelectorModuleName
+		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.
+		module.selectedApexProperties.Selected_apex = proptools.StringPtr(apexFileSource)
+	})
+
+	return module
+}
+
+func createApexSelectorModule(ctx android.LoadHookContext, name string, apexFileProperties *ApexFileProperties) {
+	props := struct {
+		Name *string
+	}{
+		Name: proptools.StringPtr(name),
+	}
+
+	ctx.CreateModule(privateApexSelectorModuleFactory,
+		&props,
+		apexFileProperties,
+	)
+}
+
+func createDeapexerModule(ctx android.LoadHookContext, deapexerName string, apexFileSource string, deapexerProperties *DeapexerProperties) {
+	props := struct {
+		Name          *string
+		Selected_apex *string
+	}{
+		Name:          proptools.StringPtr(deapexerName),
+		Selected_apex: proptools.StringPtr(apexFileSource),
+	}
+	ctx.CreateModule(privateDeapexerFactory,
+		&props,
+		deapexerProperties,
+	)
+}
+
+func deapexerModuleName(baseModuleName string) string {
+	return baseModuleName + ".deapexer"
+}
+
+func apexSelectorModuleName(baseModuleName string) string {
+	return baseModuleName + ".apex.selector"
+}
+
+func prebuiltApexExportedModuleName(ctx android.BottomUpMutatorContext, name string) string {
+	// The prebuilt_apex should be depending on prebuilt modules but as this runs after
+	// prebuilt_rename the prebuilt module may or may not be using the prebuilt_ prefixed named. So,
+	// check to see if the prefixed name is in use first, if it is then use that, otherwise assume
+	// the unprefixed name is the one to use. If the unprefixed one turns out to be a source module
+	// and not a renamed prebuilt module then that will be detected and reported as an error when
+	// processing the dependency in ApexInfoMutator().
+	prebuiltName := android.PrebuiltNameFromSource(name)
+	if ctx.OtherModuleExists(prebuiltName) {
+		name = prebuiltName
+	}
+	return name
+}
+
+type exportedDependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+}
+
+// Mark this tag so dependencies that use it are excluded from visibility enforcement.
+//
+// This does allow any prebuilt_apex to reference any module which does open up a small window for
+// restricted visibility modules to be referenced from the wrong prebuilt_apex. However, doing so
+// avoids opening up a much bigger window by widening the visibility of modules that need files
+// provided by the prebuilt_apex to include all the possible locations they may be defined, which
+// could include everything below vendor/.
+//
+// A prebuilt_apex that references a module via this tag will have to contain the appropriate files
+// corresponding to that module, otherwise it will fail when attempting to retrieve the files from
+// the .apex file. It will also have to be included in the module's apex_available property too.
+// That makes it highly unlikely that a prebuilt_apex would reference a restricted module
+// incorrectly.
+func (t exportedDependencyTag) ExcludeFromVisibilityEnforcement() {}
+
+var (
+	exportedJavaLibTag = exportedDependencyTag{name: "exported_java_lib"}
+)
+
+func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) {
+	p.deapexerDeps(ctx)
+}
+
+var _ ApexInfoMutator = (*Prebuilt)(nil)
+
+func (p *Prebuilt) ApexInfoMutator(mctx android.TopDownMutatorContext) {
+	p.apexInfoMutator(mctx)
+}
+
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// TODO(jungjw): Check the key validity.
-	p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
+	p.inputApex = android.OptionalPathForModuleSrc(ctx, p.selectedApexProperties.Selected_apex).Path()
 	p.installDir = android.PathForModuleInstall(ctx, "apex")
 	p.installFilename = p.InstallFilename()
 	if !strings.HasSuffix(p.installFilename, imageApexSuffix) {
@@ -424,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
@@ -442,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
 
@@ -458,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
 
@@ -471,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 {
@@ -530,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) {
@@ -547,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/properties.go b/bazel/properties.go
index 2440ca1..148386f 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -79,6 +79,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"
@@ -257,6 +314,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/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index dbea37a..74226ae 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -86,14 +86,17 @@
 			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 + `
 cc_library_headers {
@@ -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,11 +140,33 @@
         "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 = [
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 467b0b2..ef528e9 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 {
@@ -203,34 +206,65 @@
         "include_dir_2",
         "local_include_dir_1",
         "local_include_dir_2",
+        ".",
     ],
     linkstatic = True,
     srcs = [
         "foo_static1.cc",
         "foo_static2.cc",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+        "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",
     ],
 )`, `cc_library_static(
     name = "static_lib_1",
+    includes = [
+        ".",
+    ],
     linkstatic = True,
     srcs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "static_lib_1.cc",
     ],
 )`, `cc_library_static(
     name = "static_lib_2",
+    includes = [
+        ".",
+    ],
     linkstatic = True,
     srcs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "static_lib_2.cc",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_1",
+    includes = [
+        ".",
+    ],
     linkstatic = True,
     srcs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "whole_static_lib_1.cc",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_2",
+    includes = [
+        ".",
+    ],
     linkstatic = True,
     srcs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "whole_static_lib_2.cc",
     ],
 )`},
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 497d227..cffeb24 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.
@@ -109,23 +110,78 @@
 	return ret
 }
 
+func bp2BuildListHeadersInDir(ctx android.TopDownMutatorContext, includeDir string) bazel.LabelList {
+	var globInfix string
+
+	if includeDir == "." {
+		globInfix = ""
+	} else {
+		globInfix = "/**"
+	}
+
+	var includeDirGlobs []string
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.h")
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.inc")
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.hpp")
+
+	return android.BazelLabelForModuleSrc(ctx, includeDirGlobs)
+}
+
+// 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/library.go b/cc/library.go
index 11b6737..18f9fae 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -2062,7 +2062,7 @@
 	Srcs       bazel.LabelListAttribute
 	Deps       bazel.LabelListAttribute
 	Linkstatic bool
-	Includes   bazel.LabelListAttribute
+	Includes   bazel.StringListAttribute
 	Hdrs       bazel.LabelListAttribute
 }
 
@@ -2099,12 +2099,27 @@
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
 			copts = baseCompilerProps.Cflags
 			srcs = baseCompilerProps.Srcs
-			includeDirs = baseCompilerProps.Include_dirs
-			localIncludeDirs = baseCompilerProps.Local_include_dirs
+			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, ".")
+	}
+
+	srcsLabels := android.BazelLabelForModuleSrc(ctx, srcs)
+
+	// For Bazel, be more explicit about headers - list all header files in include dirs as srcs
+	for _, includeDir := range includeDirs {
+		srcsLabels.Append(bp2BuildListHeadersInDir(ctx, includeDir))
+	}
+	for _, localIncludeDir := range localIncludeDirs {
+		srcsLabels.Append(bp2BuildListHeadersInDir(ctx, localIncludeDir))
+	}
 
 	var staticLibs []string
 	var wholeStaticLibs []string
@@ -2122,25 +2137,24 @@
 
 	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)
-
-	exportedIncludesLabels, exportedIncludesHeadersLabels := bp2BuildParseExportedIncludes(ctx, module)
-	includesLabels.Append(exportedIncludesLabels.Value)
+	allIncludes := exportedIncludes
+	allIncludes.Value = append(allIncludes.Value, includeDirs...)
+	allIncludes.Value = append(allIncludes.Value, localIncludeDirs...)
 
 	headerLibsLabels := bp2BuildParseHeaderLibs(ctx, module)
 	depsLabels.Append(headerLibsLabels.Value)
 
 	attrs := &bazelCcLibraryStaticAttributes{
 		Copts:      copts,
-		Srcs:       srcsLabels,
+		Srcs:       bazel.MakeLabelListAttribute(srcsLabels),
 		Deps:       bazel.MakeLabelListAttribute(depsLabels),
 		Linkstatic: true,
-		Includes:   bazel.MakeLabelListAttribute(includesLabels),
-		Hdrs:       exportedIncludesHeadersLabels,
+		Includes:   allIncludes,
+		Hdrs:       exportedIncludesHeaders,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 82af16a..d35748b 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)
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
 
-	headerLibsLabels := bp2BuildParseHeaderLibs(ctx, module)
+	headerLibs := bp2BuildParseHeaderLibs(ctx, module)
 
 	attrs := &bazelCcLibraryHeadersAttributes{
 		Copts:    bp2BuildParseCflags(ctx, module),
-		Includes: exportedIncludesLabels,
-		Hdrs:     exportedIncludesHeadersLabels,
-		Deps:     headerLibsLabels,
+		Includes: exportedIncludes,
+		Hdrs:     exportedIncludesHeaders,
+		Deps:     headerLibs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/sanitize.go b/cc/sanitize.go
index e1ac9f0..8f17e4e 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -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"})
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..3d31be4 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -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/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 0e9b6c5..7e9477b 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -358,20 +358,20 @@
 		FlagWithInput("--csv ", stubFlags).
 		Inputs(flagsCSV).
 		FlagWithInput("--unsupported ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-unsupported.txt")).
+			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/config/hiddenapi-max-target-r-loprio.txt")).FlagWithArg("--tag ", "lo-prio").
+			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/config/hiddenapi-max-target-q.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-q.txt")).
 		FlagWithInput("--max-target-p ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-p.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-p.txt")).
 		FlagWithInput("--max-target-o ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
+			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-force-blocked.txt")).
 		FlagWithInput("--unsupported ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
+			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
 
 	commitChangeForRestat(rule, tempPath, outputPath)
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 5507077..95d19b9 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,72 @@
 
 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
 }
 
 func platformBootclasspathFactory() android.Module {
 	m := &platformBootclasspathModule{}
+	m.AddProperties(&m.properties)
 	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	return m
 }
@@ -47,7 +107,90 @@
 	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)
+		}
+	})
+
 	// Nothing to do if skipping the dexpreopt of boot image jars.
 	if SkipDexpreoptBootJars(ctx) {
 		return
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 0f9a17d..b0e6967 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -50,7 +50,7 @@
 	}
 
 	ret := android.AndroidMkEntries{
-		OutputFile: mod.outputFile,
+		OutputFile: mod.unstrippedOutputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
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 41b7371..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...)
@@ -272,6 +284,10 @@
 	return false
 }
 
+func (compiler *baseCompiler) strippedOutputFilePath() android.OptionalPath {
+	return compiler.strippedOutputFile
+}
+
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
 	deps.Dylibs = append(deps.Dylibs, compiler.Properties.Dylibs...)
@@ -337,10 +353,7 @@
 }
 
 func (compiler *baseCompiler) install(ctx ModuleContext) {
-	path := ctx.RustModule().outputFile
-	if compiler.strippedOutputFile.Valid() {
-		path = compiler.strippedOutputFile
-	}
+	path := ctx.RustModule().OutputFile()
 	compiler.path = ctx.InstallFile(compiler.installDir(ctx), path.Path().Base(), path.Path())
 }
 
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/rust.go b/rust/rust.go
index f0d0e36..34e197a 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -114,7 +114,10 @@
 	sourceProvider   SourceProvider
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
-	outputFile android.OptionalPath
+	// Unstripped output. This is usually used when this module is linked to another module
+	// as a library. The stripped output which is used for installation can be found via
+	// compiler.strippedOutputFile if it exists.
+	unstrippedOutputFile android.OptionalPath
 
 	hideApexVariantFromMake bool
 }
@@ -163,8 +166,8 @@
 		if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
 			return mod.sourceProvider.Srcs(), nil
 		} else {
-			if mod.outputFile.Valid() {
-				return android.Paths{mod.outputFile.Path()}, nil
+			if mod.OutputFile().Valid() {
+				return android.Paths{mod.OutputFile().Path()}, nil
 			}
 			return android.Paths{}, nil
 		}
@@ -346,6 +349,8 @@
 
 	stdLinkage(ctx *depsContext) RustLinkage
 	isDependencyRoot() bool
+
+	strippedOutputFilePath() android.OptionalPath
 }
 
 type exportedFlagsProducer interface {
@@ -523,7 +528,10 @@
 }
 
 func (mod *Module) OutputFile() android.OptionalPath {
-	return mod.outputFile
+	if mod.compiler != nil && mod.compiler.strippedOutputFilePath().Valid() {
+		return mod.compiler.strippedOutputFilePath()
+	}
+	return mod.unstrippedOutputFile
 }
 
 func (mod *Module) CoverageFiles() android.Paths {
@@ -540,7 +548,7 @@
 		return false
 	}
 
-	return mod.outputFile.Valid() && !mod.Properties.PreventInstall
+	return mod.OutputFile().Valid() && !mod.Properties.PreventInstall
 }
 
 var _ cc.LinkableInterface = (*Module)(nil)
@@ -721,9 +729,9 @@
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
-		outputFile := mod.compiler.compile(ctx, flags, deps)
+		unstrippedOutputFile := mod.compiler.compile(ctx, flags, deps)
 
-		mod.outputFile = android.OptionalPathForPath(outputFile)
+		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if mod.installable(apexInfo) {
@@ -790,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
@@ -882,7 +895,7 @@
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
-				linkFile := rustDep.outputFile
+				linkFile := rustDep.unstrippedOutputFile
 				if !linkFile.Valid() {
 					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q",
 						depName, ctx.ModuleName())
@@ -978,15 +991,15 @@
 
 	var rlibDepFiles RustLibraries
 	for _, dep := range directRlibDeps {
-		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 	var dylibDepFiles RustLibraries
 	for _, dep := range directDylibDeps {
-		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 	var procMacroDepFiles RustLibraries
 	for _, dep := range directProcMacroDeps {
-		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 
 	var staticLibDepFiles android.Paths
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"
+