Merge "Remove jungjw from OWNERS."
diff --git a/Android.bp b/Android.bp
index 8f7f3e2..45e661e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -92,7 +92,7 @@
 }
 
 cc_genrule {
-    name: "host_bionic_linker_flags",
+    name: "host_bionic_linker_script",
     host_supported: true,
     device_supported: false,
     target: {
@@ -107,9 +107,9 @@
         },
     },
     tools: ["extract_linker"],
-    cmd: "$(location) -f $(out) $(in)",
+    cmd: "$(location) -T $(out) $(in)",
     srcs: [":linker"],
-    out: ["linker.flags"],
+    out: ["linker.script"],
 }
 
 // Instantiate the dex_bootjars singleton module.
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..9f386ca
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "packages/modules/SdkExtensions"
+    }
+  ]
+}
diff --git a/android/Android.bp b/android/Android.bp
index 6124654..5d0f2b9 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -42,6 +42,7 @@
         "image.go",
         "license.go",
         "license_kind.go",
+        "license_sdk_member.go",
         "licenses.go",
         "makefile_goal.go",
         "makevars.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 590eceb..557e7ba 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -501,7 +501,7 @@
 	a.SetString("LOCAL_MODULE", name+a.SubName)
 	a.AddStrings("LOCAL_LICENSE_KINDS", amod.commonProperties.Effective_license_kinds...)
 	a.AddStrings("LOCAL_LICENSE_CONDITIONS", amod.commonProperties.Effective_license_conditions...)
-	a.AddStrings("LOCAL_NOTICE_FILE", amod.commonProperties.Effective_license_text...)
+	a.AddStrings("LOCAL_NOTICE_FILE", amod.commonProperties.Effective_license_text.Strings()...)
 	// TODO(b/151177513): Does this code need to set LOCAL_MODULE_IS_CONTAINER ?
 	if amod.commonProperties.Effective_package_name != nil {
 		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *amod.commonProperties.Effective_package_name)
diff --git a/android/apex.go b/android/apex.go
index 60da45b..4618fe9 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -36,11 +36,16 @@
 // Accessible via `ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)`
 type ApexInfo struct {
 	// Name of the apex variation that this module (i.e. the apex variant of the module) is
-	// mutated into, or "" for a platform (i.e. non-APEX) variant. Note that a module can be
-	// included in multiple APEXes, in which case, the module is mutated into one or more
-	// variants, each of which is for an APEX. The variants then can later be deduped if they
-	// don't need to be compiled differently. This is an optimization done in
-	// mergeApexVariations.
+	// mutated into, or "" for a platform (i.e. non-APEX) variant. Note that this name and the
+	// Soong module name of the APEX can be different. That happens when there is
+	// `override_apex` that overrides `apex`. In that case, both Soong modules have the same
+	// apex variation name which usually is `com.android.foo`. This name is also the `name`
+	// in the path `/apex/<name>` where this apex is activated on at runtime.
+	//
+	// Also note that a module can be included in multiple APEXes, in which case, the module is
+	// mutated into one or more variants, each of which is for an APEX. The variants then can
+	// later be deduped if they don't need to be compiled differently. This is an optimization
+	// done in mergeApexVariations.
 	ApexVariationName string
 
 	// ApiLevel that this module has to support at minimum.
@@ -52,11 +57,19 @@
 	// The list of SDK modules that the containing apexBundle depends on.
 	RequiredSdks SdkRefs
 
-	// List of apexBundles that this apex variant of the module is associated with. Initially,
-	// the size of this list is one because one apex variant is associated with one apexBundle.
-	// When multiple apex variants are merged in mergeApexVariations, ApexInfo struct of the
-	// merged variant holds the list of apexBundles that are merged together.
-	InApexes []string
+	// List of Apex variant names that this module is associated with. This initially is the
+	// same as the `ApexVariationName` field.  Then when multiple apex variants are merged in
+	// mergeApexVariations, ApexInfo struct of the merged variant holds the list of apexBundles
+	// that are merged together.
+	InApexVariants []string
+
+	// List of APEX Soong module names that this module is part of. Note that the list includes
+	// different variations of the same APEX. For example, if module `foo` is included in the
+	// apex `com.android.foo`, and also if there is an override_apex module
+	// `com.mycompany.android.foo` overriding `com.android.foo`, then this list contains both
+	// `com.android.foo` and `com.mycompany.android.foo`.  If the APEX Soong module is a
+	// prebuilt, the name here doesn't have the `prebuilt_` prefix.
+	InApexModules []string
 
 	// Pointers to the ApexContents struct each of which is for apexBundle modules that this
 	// module is part of. The ApexContents gives information about which modules the apexBundle
@@ -93,23 +106,20 @@
 	return i.ApexVariationName == ""
 }
 
-// InApex tells whether this apex variant of the module is part of the given apexBundle or not.
-func (i ApexInfo) InApex(apex string) bool {
-	for _, a := range i.InApexes {
-		if a == apex {
+// InApexVariant tells whether this apex variant of the module is part of the given apexVariant or
+// not.
+func (i ApexInfo) InApexVariant(apexVariant string) bool {
+	for _, a := range i.InApexVariants {
+		if a == apexVariant {
 			return true
 		}
 	}
 	return false
 }
 
-// InApexByBaseName tells whether this apex variant of the module is part of the given APEX or not,
-// where the APEX is specified by its canonical base name, i.e. typically beginning with
-// "com.android.". In particular this function doesn't differentiate between source and prebuilt
-// APEXes, where the latter may have "prebuilt_" prefixes.
-func (i ApexInfo) InApexByBaseName(apex string) bool {
-	for _, a := range i.InApexes {
-		if RemoveOptionalPrebuiltPrefix(a) == apex {
+func (i ApexInfo) InApexModule(apexModuleName string) bool {
+	for _, a := range i.InApexModules {
+		if a == apexModuleName {
 			return true
 		}
 	}
@@ -203,6 +213,12 @@
 	// apex_available property of the module.
 	AvailableFor(what string) bool
 
+	// AlwaysRequiresPlatformApexVariant allows the implementing module to determine whether an
+	// APEX mutator should always be created for it.
+	//
+	// Returns false by default.
+	AlwaysRequiresPlatformApexVariant() bool
+
 	// Returns true if this module is not available to platform (i.e. apex_available property
 	// doesn't have "//apex_available:platform"), or shouldn't be available to platform, which
 	// is the case when this module depends on other module that isn't available to platform.
@@ -339,8 +355,21 @@
 func (m *ApexModuleBase) BuildForApex(apex ApexInfo) {
 	m.apexInfosLock.Lock()
 	defer m.apexInfosLock.Unlock()
-	for _, v := range m.apexInfos {
+	for i, v := range m.apexInfos {
 		if v.ApexVariationName == apex.ApexVariationName {
+			if len(apex.InApexModules) != 1 {
+				panic(fmt.Errorf("Newly created apexInfo must be for a single APEX"))
+			}
+			// Even when the ApexVariantNames are the same, the given ApexInfo might
+			// actually be for different APEX. This can happen when an APEX is
+			// overridden via override_apex. For example, there can be two apexes
+			// `com.android.foo` (from the `apex` module type) and
+			// `com.mycompany.android.foo` (from the `override_apex` module type), both
+			// of which has the same ApexVariantName `com.android.foo`. Add the apex
+			// name to the list so that it's not lost.
+			if !InList(apex.InApexModules[0], v.InApexModules) {
+				m.apexInfos[i].InApexModules = append(m.apexInfos[i].InApexModules, apex.InApexModules[0])
+			}
 			return
 		}
 	}
@@ -424,6 +453,11 @@
 }
 
 // Implements ApexModule
+func (m *ApexModuleBase) AlwaysRequiresPlatformApexVariant() bool {
+	return false
+}
+
+// Implements ApexModule
 func (m *ApexModuleBase) NotAvailableForPlatform() bool {
 	return m.ApexProperties.NotAvailableForPlatform
 }
@@ -485,21 +519,23 @@
 		// Merge the ApexInfo together. If a compatible ApexInfo exists then merge the information from
 		// this one into it, otherwise create a new merged ApexInfo from this one and save it away so
 		// other ApexInfo instances can be merged into it.
-		apexName := apexInfo.ApexVariationName
+		variantName := apexInfo.ApexVariationName
 		mergedName := apexInfo.mergedName(ctx)
 		if index, exists := seen[mergedName]; exists {
 			// Variants having the same mergedName are deduped
-			merged[index].InApexes = append(merged[index].InApexes, apexName)
+			merged[index].InApexVariants = append(merged[index].InApexVariants, variantName)
+			merged[index].InApexModules = append(merged[index].InApexModules, apexInfo.InApexModules...)
 			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
 			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
 		} else {
 			seen[mergedName] = len(merged)
 			apexInfo.ApexVariationName = mergedName
-			apexInfo.InApexes = CopyOf(apexInfo.InApexes)
+			apexInfo.InApexVariants = CopyOf(apexInfo.InApexVariants)
+			apexInfo.InApexModules = CopyOf(apexInfo.InApexModules)
 			apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
 			merged = append(merged, apexInfo)
 		}
-		aliases = append(aliases, [2]string{apexName, mergedName})
+		aliases = append(aliases, [2]string{variantName, mergedName})
 	}
 	return merged, aliases
 }
@@ -572,15 +608,15 @@
 // in the same APEX have unique APEX variations so that the module can link against the right
 // variant.
 func UpdateUniqueApexVariationsForDeps(mctx BottomUpMutatorContext, am ApexModule) {
-	// anyInSameApex returns true if the two ApexInfo lists contain any values in an InApexes
-	// list in common. It is used instead of DepIsInSameApex because it needs to determine if
-	// the dep is in the same APEX due to being directly included, not only if it is included
-	// _because_ it is a dependency.
+	// anyInSameApex returns true if the two ApexInfo lists contain any values in an
+	// InApexVariants list in common. It is used instead of DepIsInSameApex because it needs to
+	// determine if the dep is in the same APEX due to being directly included, not only if it
+	// is included _because_ it is a dependency.
 	anyInSameApex := func(a, b []ApexInfo) bool {
 		collectApexes := func(infos []ApexInfo) []string {
 			var ret []string
 			for _, info := range infos {
-				ret = append(ret, info.InApexes...)
+				ret = append(ret, info.InApexVariants...)
 			}
 			return ret
 		}
@@ -620,8 +656,8 @@
 	mctx.VisitDirectDeps(func(dep Module) {
 		if _, ok := mctx.OtherModuleDependencyTag(dep).(CopyDirectlyInAnyApexTag); ok {
 			depBase := dep.(ApexModule).apexModuleBase()
-			base.ApexProperties.DirectlyInAnyApex = depBase.ApexProperties.DirectlyInAnyApex
-			base.ApexProperties.InAnyApex = depBase.ApexProperties.InAnyApex
+			depBase.ApexProperties.DirectlyInAnyApex = base.ApexProperties.DirectlyInAnyApex
+			depBase.ApexProperties.InAnyApex = base.ApexProperties.InAnyApex
 		}
 	})
 
diff --git a/android/apex_test.go b/android/apex_test.go
index 109b1c8..e112369 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -33,10 +33,10 @@
 		{
 			name: "single",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"foo", "apex10000"},
@@ -45,11 +45,11 @@
 		{
 			name: "merge",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, nil, false}},
+				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, false}},
 			wantAliases: [][2]string{
 				{"bar", "apex10000_baz_1"},
 				{"foo", "apex10000_baz_1"},
@@ -58,12 +58,12 @@
 		{
 			name: "don't merge version",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex30", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex30", uncheckedFinalApiLevel(30), false, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex30"},
@@ -73,11 +73,11 @@
 		{
 			name: "merge updatable",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
@@ -87,12 +87,12 @@
 		{
 			name: "don't merge sdks",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000_baz_2", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex},
-				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"apex10000_baz_2", FutureApiLevel, false, SdkRefs{{"baz", "2"}}, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"apex10000_baz_1", FutureApiLevel, false, SdkRefs{{"baz", "1"}}, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000_baz_2"},
@@ -102,15 +102,15 @@
 		{
 			name: "don't merge when for prebuilt_apex",
 			in: []ApexInfo{
-				{"foo", FutureApiLevel, false, nil, []string{"foo"}, nil, NotForPrebuiltApex},
-				{"bar", FutureApiLevel, true, nil, []string{"bar"}, nil, NotForPrebuiltApex},
+				{"foo", FutureApiLevel, false, nil, []string{"foo"}, []string{"foo"}, nil, NotForPrebuiltApex},
+				{"bar", FutureApiLevel, true, nil, []string{"bar"}, []string{"bar"}, nil, NotForPrebuiltApex},
 				// This one should not be merged in with the others because it is for
 				// a prebuilt_apex.
-				{"baz", FutureApiLevel, true, nil, []string{"baz"}, nil, ForPrebuiltApex},
+				{"baz", FutureApiLevel, true, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantMerged: []ApexInfo{
-				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
-				{"baz", FutureApiLevel, true, nil, []string{"baz"}, nil, ForPrebuiltApex},
+				{"apex10000", FutureApiLevel, true, nil, []string{"bar", "foo"}, []string{"bar", "foo"}, nil, NotForPrebuiltApex},
+				{"baz", FutureApiLevel, true, nil, []string{"baz"}, []string{"baz"}, nil, ForPrebuiltApex},
 			},
 			wantAliases: [][2]string{
 				{"bar", "apex10000"},
diff --git a/android/api_levels.go b/android/api_levels.go
index 9bc7e83..84ab27c 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -158,6 +158,21 @@
 // The first version that introduced 64-bit ABIs.
 var FirstLp64Version = uncheckedFinalApiLevel(21)
 
+// Android has had various kinds of packed relocations over the years
+// (http://b/187907243).
+//
+// API level 30 is where the now-standard SHT_RELR is available.
+var FirstShtRelrVersion = uncheckedFinalApiLevel(30)
+
+// API level 28 introduced SHT_RELR when it was still Android-only, and used an
+// Android-specific relocation.
+var FirstAndroidRelrVersion = uncheckedFinalApiLevel(28)
+
+// API level 23 was when we first had the Chrome relocation packer, which is
+// obsolete and has been removed, but lld can now generate compatible packed
+// relocations itself.
+var FirstPackedRelocationsVersion = uncheckedFinalApiLevel(23)
+
 // The first API level that does not require NDK code to link
 // libandroid_support.
 var FirstNonLibAndroidSupportVersion = uncheckedFinalApiLevel(21)
diff --git a/android/arch.go b/android/arch.go
index c1b2c33..1fbba19 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"android/soong/bazel"
 	"encoding"
 	"fmt"
 	"reflect"
@@ -897,7 +898,7 @@
 
 			// Add the OS/Arch combinations, e.g. "android_arm64".
 			for _, archType := range osArchTypeMap[os] {
-				targets = append(targets, os.Field+"_"+archType.Name)
+				targets = append(targets, GetCompoundTargetField(os, archType))
 
 				// Also add the special "linux_<arch>" and "bionic_<arch>" property structs.
 				if os.Linux() {
@@ -1025,7 +1026,7 @@
 }
 
 // Merges the property struct in srcValue into dst.
-func mergePropertyStruct(ctx BaseMutatorContext, dst interface{}, srcValue reflect.Value) {
+func mergePropertyStruct(ctx ArchVariantContext, dst interface{}, srcValue reflect.Value) {
 	src := maybeBlueprintEmbed(srcValue).Interface()
 
 	// order checks the `android:"variant_prepend"` tag to handle properties where the
@@ -1054,25 +1055,29 @@
 
 // Returns the immediate child of the input property struct that corresponds to
 // the sub-property "field".
-func getChildPropertyStruct(ctx BaseMutatorContext,
-	src reflect.Value, field, userFriendlyField string) reflect.Value {
+func getChildPropertyStruct(ctx ArchVariantContext,
+	src reflect.Value, field, userFriendlyField string) (reflect.Value, bool) {
 
 	// Step into non-nil pointers to structs in the src value.
 	if src.Kind() == reflect.Ptr {
 		if src.IsNil() {
-			return src
+			return reflect.Value{}, false
 		}
 		src = src.Elem()
 	}
 
 	// Find the requested field in the src struct.
-	src = src.FieldByName(proptools.FieldNameForProperty(field))
-	if !src.IsValid() {
+	child := src.FieldByName(proptools.FieldNameForProperty(field))
+	if !child.IsValid() {
 		ctx.ModuleErrorf("field %q does not exist", userFriendlyField)
-		return src
+		return reflect.Value{}, false
 	}
 
-	return src
+	if child.IsZero() {
+		return reflect.Value{}, false
+	}
+
+	return child, true
 }
 
 // Squash the appropriate OS-specific property structs into the matching top level property structs
@@ -1099,8 +1104,9 @@
 			if os.Class == Host {
 				field := "Host"
 				prefix := "target.host"
-				hostProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, hostProperties)
+				if hostProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, hostProperties)
+				}
 			}
 
 			// Handle target OS generalities of the form:
@@ -1112,15 +1118,17 @@
 			if os.Linux() {
 				field := "Linux"
 				prefix := "target.linux"
-				linuxProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, linuxProperties)
+				if linuxProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, linuxProperties)
+				}
 			}
 
 			if os.Bionic() {
 				field := "Bionic"
 				prefix := "target.bionic"
-				bionicProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, bionicProperties)
+				if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, bionicProperties)
+				}
 			}
 
 			// Handle target OS properties in the form:
@@ -1137,14 +1145,16 @@
 			// },
 			field := os.Field
 			prefix := "target." + os.Name
-			osProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-			mergePropertyStruct(ctx, genProps, osProperties)
+			if osProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+				mergePropertyStruct(ctx, genProps, osProperties)
+			}
 
 			if os.Class == Host && os != Windows {
 				field := "Not_windows"
 				prefix := "target.not_windows"
-				notWindowsProperties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-				mergePropertyStruct(ctx, genProps, notWindowsProperties)
+				if notWindowsProperties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+					mergePropertyStruct(ctx, genProps, notWindowsProperties)
+				}
 			}
 
 			// Handle 64-bit device properties in the form:
@@ -1164,13 +1174,15 @@
 				if ctx.Config().Android64() {
 					field := "Android64"
 					prefix := "target.android64"
-					android64Properties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-					mergePropertyStruct(ctx, genProps, android64Properties)
+					if android64Properties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+						mergePropertyStruct(ctx, genProps, android64Properties)
+					}
 				} else {
 					field := "Android32"
 					prefix := "target.android32"
-					android32Properties := getChildPropertyStruct(ctx, targetProp, field, prefix)
-					mergePropertyStruct(ctx, genProps, android32Properties)
+					if android32Properties, ok := getChildPropertyStruct(ctx, targetProp, field, prefix); ok {
+						mergePropertyStruct(ctx, genProps, android32Properties)
+					}
 				}
 			}
 		}
@@ -1186,12 +1198,11 @@
 // },
 // This struct will also contain sub-structs containing to the architecture/CPU
 // variants and features that themselves contain properties specific to those.
-func getArchTypeStruct(ctx BaseMutatorContext, archProperties interface{}, archType ArchType) reflect.Value {
+func getArchTypeStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) (reflect.Value, bool) {
 	archPropValues := reflect.ValueOf(archProperties).Elem()
 	archProp := archPropValues.FieldByName("Arch").Elem()
 	prefix := "arch." + archType.Name
-	archStruct := getChildPropertyStruct(ctx, archProp, archType.Name, prefix)
-	return archStruct
+	return getChildPropertyStruct(ctx, archProp, archType.Name, prefix)
 }
 
 // Returns the struct containing the properties specific to a given multilib
@@ -1201,11 +1212,14 @@
 //         key: value,
 //     },
 // },
-func getMultilibStruct(ctx BaseMutatorContext, archProperties interface{}, archType ArchType) reflect.Value {
+func getMultilibStruct(ctx ArchVariantContext, archProperties interface{}, archType ArchType) (reflect.Value, bool) {
 	archPropValues := reflect.ValueOf(archProperties).Elem()
 	multilibProp := archPropValues.FieldByName("Multilib").Elem()
-	multilibProperties := getChildPropertyStruct(ctx, multilibProp, archType.Multilib, "multilib."+archType.Multilib)
-	return multilibProperties
+	return getChildPropertyStruct(ctx, multilibProp, archType.Multilib, "multilib."+archType.Multilib)
+}
+
+func GetCompoundTargetField(os OsType, arch ArchType) string {
+	return os.Field + "_" + arch.Name
 }
 
 // Returns the structs corresponding to the properties specific to the given
@@ -1219,58 +1233,64 @@
 	archType := arch.ArchType
 
 	if arch.ArchType != Common {
-		archStruct := getArchTypeStruct(ctx, archProperties, arch.ArchType)
-		result = append(result, archStruct)
+		archStruct, ok := getArchTypeStruct(ctx, archProperties, arch.ArchType)
+		if ok {
+			result = append(result, archStruct)
 
-		// Handle arch-variant-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         variant: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		v := variantReplacer.Replace(arch.ArchVariant)
-		if v != "" {
-			prefix := "arch." + archType.Name + "." + v
-			variantProperties := getChildPropertyStruct(ctx, archStruct, v, prefix)
-			result = append(result, variantProperties)
-		}
+			// Handle arch-variant-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         variant: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			v := variantReplacer.Replace(arch.ArchVariant)
+			if v != "" {
+				prefix := "arch." + archType.Name + "." + v
+				if variantProperties, ok := getChildPropertyStruct(ctx, archStruct, v, prefix); ok {
+					result = append(result, variantProperties)
+				}
+			}
 
-		// Handle cpu-variant-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         variant: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		if arch.CpuVariant != arch.ArchVariant {
-			c := variantReplacer.Replace(arch.CpuVariant)
-			if c != "" {
-				prefix := "arch." + archType.Name + "." + c
-				cpuVariantProperties := getChildPropertyStruct(ctx, archStruct, c, prefix)
-				result = append(result, cpuVariantProperties)
+			// Handle cpu-variant-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         variant: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			if arch.CpuVariant != arch.ArchVariant {
+				c := variantReplacer.Replace(arch.CpuVariant)
+				if c != "" {
+					prefix := "arch." + archType.Name + "." + c
+					if cpuVariantProperties, ok := getChildPropertyStruct(ctx, archStruct, c, prefix); ok {
+						result = append(result, cpuVariantProperties)
+					}
+				}
+			}
+
+			// Handle arch-feature-specific properties in the form:
+			// arch: {
+			//     arm: {
+			//         feature: {
+			//             key: value,
+			//         },
+			//     },
+			// },
+			for _, feature := range arch.ArchFeatures {
+				prefix := "arch." + archType.Name + "." + feature
+				if featureProperties, ok := getChildPropertyStruct(ctx, archStruct, feature, prefix); ok {
+					result = append(result, featureProperties)
+				}
 			}
 		}
 
-		// Handle arch-feature-specific properties in the form:
-		// arch: {
-		//     arm: {
-		//         feature: {
-		//             key: value,
-		//         },
-		//     },
-		// },
-		for _, feature := range arch.ArchFeatures {
-			prefix := "arch." + archType.Name + "." + feature
-			featureProperties := getChildPropertyStruct(ctx, archStruct, feature, prefix)
-			result = append(result, featureProperties)
+		if multilibProperties, ok := getMultilibStruct(ctx, archProperties, archType); ok {
+			result = append(result, multilibProperties)
 		}
 
-		multilibProperties := getMultilibStruct(ctx, archProperties, archType)
-		result = append(result, multilibProperties)
-
 		// Handle combined OS-feature and arch specific properties in the form:
 		// target: {
 		//     bionic_x86: {
@@ -1280,15 +1300,17 @@
 		if os.Linux() {
 			field := "Linux_" + arch.ArchType.Name
 			userFriendlyField := "target.linux_" + arch.ArchType.Name
-			linuxProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, linuxProperties)
+			if linuxProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, linuxProperties)
+			}
 		}
 
 		if os.Bionic() {
 			field := "Bionic_" + archType.Name
 			userFriendlyField := "target.bionic_" + archType.Name
-			bionicProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, bionicProperties)
+			if bionicProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, bionicProperties)
+			}
 		}
 
 		// Handle combined OS and arch specific properties in the form:
@@ -1306,10 +1328,11 @@
 		//         key: value,
 		//     },
 		// },
-		field := os.Field + "_" + archType.Name
+		field := GetCompoundTargetField(os, archType)
 		userFriendlyField := "target." + os.Name + "_" + archType.Name
-		osArchProperties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-		result = append(result, osArchProperties)
+		if osArchProperties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+			result = append(result, osArchProperties)
+		}
 	}
 
 	// Handle arm on x86 properties in the form:
@@ -1326,21 +1349,24 @@
 			hasArmAndroidArch(ctx.Config().Targets[Android])) {
 			field := "Arm_on_x86"
 			userFriendlyField := "target.arm_on_x86"
-			armOnX86Properties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, armOnX86Properties)
+			if armOnX86Properties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, armOnX86Properties)
+			}
 		}
 		if arch.ArchType == X86_64 && (hasArmAbi(arch) ||
 			hasArmAndroidArch(ctx.Config().Targets[Android])) {
 			field := "Arm_on_x86_64"
 			userFriendlyField := "target.arm_on_x86_64"
-			armOnX8664Properties := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField)
-			result = append(result, armOnX8664Properties)
+			if armOnX8664Properties, ok := getChildPropertyStruct(ctx, targetProp, field, userFriendlyField); ok {
+				result = append(result, armOnX8664Properties)
+			}
 		}
 		if os == Android && nativeBridgeEnabled {
 			userFriendlyField := "Native_bridge"
 			prefix := "target.native_bridge"
-			nativeBridgeProperties := getChildPropertyStruct(ctx, targetProp, userFriendlyField, prefix)
-			result = append(result, nativeBridgeProperties)
+			if nativeBridgeProperties, ok := getChildPropertyStruct(ctx, targetProp, userFriendlyField, prefix); ok {
+				result = append(result, nativeBridgeProperties)
+			}
 		}
 	}
 
@@ -1540,7 +1566,10 @@
 	abi         []string
 }
 
-// getNdkAbisConfig returns a list of archConfigs for the ABIs supported by the NDK.
+// getNdkAbisConfig returns the list of archConfigs that are used for bulding
+// the API stubs and static libraries that are included in the NDK. These are
+// built *without Neon*, because non-Neon is still supported and building these
+// with Neon will break those users.
 func getNdkAbisConfig() []archConfig {
 	return []archConfig{
 		{"arm", "armv7-a", "", []string{"armeabi-v7a"}},
@@ -1851,25 +1880,44 @@
 	return reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
 }
 
-// GetArchProperties returns a map of architectures to the values of the
-// properties of the 'propertySet' struct that are specific to that architecture.
+// ArchVariantContext defines the limited context necessary to retrieve arch_variant properties.
+type ArchVariantContext interface {
+	ModuleErrorf(fmt string, args ...interface{})
+	PropertyErrorf(property, fmt string, args ...interface{})
+}
+
+// ArchVariantProperties represents a map of arch-variant config strings to a property interface{}.
+type ArchVariantProperties map[string]interface{}
+
+// ConfigurationAxisToArchVariantProperties represents a map of bazel.ConfigurationAxis to
+// ArchVariantProperties, such that each independent arch-variant axis maps to the
+// configs/properties for that axis.
+type ConfigurationAxisToArchVariantProperties map[bazel.ConfigurationAxis]ArchVariantProperties
+
+// GetArchVariantProperties returns a ConfigurationAxisToArchVariantProperties where the
+// arch-variant properties correspond to the values of the properties of the 'propertySet' struct
+// that are specific to that axis/configuration. Each axis is independent, containing
+// non-overlapping configs that correspond to the various "arch-variant" support, at this time:
+//    arches (including multilib)
+//    oses
+//    arch+os combinations
 //
-// For example, passing a struct { Foo bool, Bar string } will return an
-// interface{} that can be type asserted back into the same struct, containing
-// the arch specific property value specified by the module if defined.
+// For example, passing a struct { Foo bool, Bar string } will return an interface{} that can be
+// type asserted back into the same struct, containing the config-specific property value specified
+// by the module if defined.
 //
 // Arch-specific properties may come from an arch stanza or a multilib stanza; properties
 // in these stanzas are combined.
 // For example: `arch: { x86: { Foo: ["bar"] } }, multilib: { lib32: {` Foo: ["baz"] } }`
 // will result in `Foo: ["bar", "baz"]` being returned for architecture x86, if the given
 // propertyset contains `Foo []string`.
-func (m *ModuleBase) GetArchProperties(ctx BaseMutatorContext, propertySet interface{}) map[ArchType]interface{} {
+func (m *ModuleBase) GetArchVariantProperties(ctx ArchVariantContext, propertySet interface{}) ConfigurationAxisToArchVariantProperties {
 	// Return value of the arch types to the prop values for that arch.
-	archToProp := map[ArchType]interface{}{}
+	axisToProps := ConfigurationAxisToArchVariantProperties{}
 
 	// Nothing to do for non-arch-specific modules.
 	if !m.ArchSpecific() {
-		return archToProp
+		return axisToProps
 	}
 
 	dstType := reflect.ValueOf(propertySet).Type()
@@ -1887,9 +1935,10 @@
 
 	if archProperties == nil {
 		// This module does not have the property set requested
-		return archToProp
+		return axisToProps
 	}
 
+	archToProp := ArchVariantProperties{}
 	// For each arch type (x86, arm64, etc.)
 	for _, arch := range ArchTypeList() {
 		// Arch properties are sometimes sharded (see createArchPropTypeDesc() ).
@@ -1897,9 +1946,14 @@
 		// input one that contains the data specific to that arch.
 		propertyStructs := make([]reflect.Value, 0)
 		for _, archProperty := range archProperties {
-			archTypeStruct := getArchTypeStruct(ctx, archProperty, arch)
-			multilibStruct := getMultilibStruct(ctx, archProperty, arch)
-			propertyStructs = append(propertyStructs, archTypeStruct, multilibStruct)
+			archTypeStruct, ok := getArchTypeStruct(ctx, archProperty, arch)
+			if ok {
+				propertyStructs = append(propertyStructs, archTypeStruct)
+			}
+			multilibStruct, ok := getMultilibStruct(ctx, archProperty, arch)
+			if ok {
+				propertyStructs = append(propertyStructs, multilibStruct)
+			}
 		}
 
 		// Create a new instance of the requested property set
@@ -1910,95 +1964,64 @@
 			mergePropertyStruct(ctx, value, propertyStruct)
 		}
 
-		archToProp[arch] = value
+		archToProp[arch.Name] = value
 	}
+	axisToProps[bazel.ArchConfigurationAxis] = archToProp
 
-	return archToProp
-}
-
-// GetTargetProperties returns a map of OS target (e.g. android, windows) to the
-// values of the properties of the 'dst' struct that are specific to that OS
-// target.
-//
-// For example, passing a struct { Foo bool, Bar string } will return an
-// interface{} that can be type asserted back into the same struct, containing
-// the os-specific property value specified by the module if defined.
-//
-// While this looks similar to GetArchProperties, the internal representation of
-// the properties have a slightly different layout to warrant a standalone
-// lookup function.
-func (m *ModuleBase) GetTargetProperties(dst interface{}) map[OsType]interface{} {
-	// Return value of the arch types to the prop values for that arch.
-	osToProp := map[OsType]interface{}{}
-
-	// Nothing to do for non-OS/arch-specific modules.
-	if !m.ArchSpecific() {
-		return osToProp
-	}
-
-	// archProperties has the type of [][]interface{}. Looks complicated, so
-	// let's explain this step by step.
-	//
-	// Loop over the outer index, which determines the property struct that
-	// contains a matching set of properties in dst that we're interested in.
-	// For example, BaseCompilerProperties or BaseLinkerProperties.
-	for i := range m.archProperties {
-		if m.archProperties[i] == nil {
+	osToProp := ArchVariantProperties{}
+	archOsToProp := ArchVariantProperties{}
+	// For android, linux, ...
+	for _, os := range osTypeList {
+		if os == CommonOS {
+			// It looks like this OS value is not used in Blueprint files
 			continue
 		}
-
-		// Iterate over the supported OS types
-		for _, os := range osTypeList {
-			// e.g android, linux_bionic
-			field := os.Field
-
-			// If it's not nil, loop over the inner index, which determines the arch variant
-			// of the prop type. In an Android.bp file, this is like looping over:
-			//
-			// target: { android: { key: value, ... }, linux_bionic: { key: value, ... } }
-			for _, archProperties := range m.archProperties[i] {
-				archPropValues := reflect.ValueOf(archProperties).Elem()
-
-				// This is the archPropRoot struct. Traverse into the Targetnested struct.
-				src := archPropValues.FieldByName("Target").Elem()
-
-				// Step into non-nil pointers to structs in the src value.
-				if src.Kind() == reflect.Ptr {
-					if src.IsNil() {
-						continue
-					}
-					src = src.Elem()
-				}
-
-				// Find the requested field (e.g. android, linux_bionic) in the src struct.
-				src = src.FieldByName(field)
-
-				// Validation steps. We want valid non-nil pointers to structs.
-				if !src.IsValid() || src.IsNil() {
-					continue
-				}
-
-				if src.Kind() != reflect.Ptr || src.Elem().Kind() != reflect.Struct {
-					continue
-				}
-
-				// Clone the destination prop, since we want a unique prop struct per arch.
-				dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()
-
-				// Copy the located property struct into the cloned destination property struct.
-				err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
-				if err != nil {
-					// This is fine, it just means the src struct doesn't match.
-					continue
-				}
-
-				// Found the prop for the os, you have.
-				osToProp[os] = dstClone
-
-				// Go to the next prop.
-				break
-			}
+		osToProp[os.Name] = getTargetStruct(ctx, propertySet, archProperties, os.Field)
+		// For arm, x86, ...
+		for _, arch := range osArchTypeMap[os] {
+			targetField := GetCompoundTargetField(os, arch)
+			targetName := fmt.Sprintf("%s_%s", os.Name, arch.Name)
+			archOsToProp[targetName] = getTargetStruct(ctx, propertySet, archProperties, targetField)
 		}
 	}
-	return osToProp
+	axisToProps[bazel.OsConfigurationAxis] = osToProp
+	axisToProps[bazel.OsArchConfigurationAxis] = archOsToProp
+
+	return axisToProps
+}
+
+// Returns a struct matching the propertySet interface, containing properties specific to the targetName
+// For example, given these arguments:
+//    propertySet = BaseCompilerProperties
+//    targetName = "android_arm"
+// And given this Android.bp fragment:
+//    target:
+//       android_arm: {
+//          srcs: ["foo.c"],
+//       }
+//       android_arm64: {
+//          srcs: ["bar.c"],
+//      }
+//    }
+// This would return a BaseCompilerProperties with BaseCompilerProperties.Srcs = ["foo.c"]
+func getTargetStruct(ctx ArchVariantContext, propertySet interface{}, archProperties []interface{}, targetName string) interface{} {
+	propertyStructs := make([]reflect.Value, 0)
+	for _, archProperty := range archProperties {
+		archPropValues := reflect.ValueOf(archProperty).Elem()
+		targetProp := archPropValues.FieldByName("Target").Elem()
+		targetStruct, ok := getChildPropertyStruct(ctx, targetProp, targetName, targetName)
+		if ok {
+			propertyStructs = append(propertyStructs, targetStruct)
+		}
+	}
+
+	// Create a new instance of the requested property set
+	value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
+
+	// Merge all the structs together
+	for _, propertyStruct := range propertyStructs {
+		mergePropertyStruct(ctx, value, propertyStruct)
+	}
+
+	return value
 }
diff --git a/android/bazel.go b/android/bazel.go
index 6c476a7..992d8aa 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -126,40 +126,20 @@
 )
 
 var (
-	// Do not write BUILD files for these directories
-	// NOTE: this is not recursive
-	bp2buildDoNotWriteBuildFileList = []string{
-		// Don't generate these BUILD files - because external BUILD files already exist
-		"external/boringssl",
-		"external/brotli",
-		"external/dagger2",
-		"external/flatbuffers",
-		"external/gflags",
-		"external/google-fruit",
-		"external/grpc-grpc",
-		"external/grpc-grpc/test/core/util",
-		"external/grpc-grpc/test/cpp/common",
-		"external/grpc-grpc/third_party/address_sorting",
-		"external/nanopb-c",
-		"external/nos/host/generic",
-		"external/nos/host/generic/libnos",
-		"external/nos/host/generic/libnos/generator",
-		"external/nos/host/generic/libnos_datagram",
-		"external/nos/host/generic/libnos_transport",
-		"external/nos/host/generic/nugget/proto",
-		"external/perfetto",
-		"external/protobuf",
-		"external/rust/cxx",
-		"external/rust/cxx/demo",
-		"external/ruy",
-		"external/tensorflow",
-		"external/tensorflow/tensorflow/lite",
-		"external/tensorflow/tensorflow/lite/java",
-		"external/tensorflow/tensorflow/lite/kernels",
-		"external/tflite-support",
-		"external/tinyalsa_new",
-		"external/wycheproof",
-		"external/libyuv",
+	// Keep any existing BUILD files (and do not generate new BUILD files) for these directories
+	bp2buildKeepExistingBuildFile = map[string]bool{
+		// This is actually build/bazel/build.BAZEL symlinked to ./BUILD
+		".":/*recrusive = */ false,
+
+		"build/bazel":/* recursive = */ true,
+		"build/pesto":/* recursive = */ true,
+
+		// external/bazelbuild-rules_android/... is needed by mixed builds, otherwise mixed builds analysis fails
+		// e.g. ERROR: Analysis of target '@soong_injection//mixed_builds:buildroot' failed
+		"external/bazelbuild-rules_android":/* recursive = */ true,
+
+		"prebuilts/sdk":/* recursive = */ false,
+		"prebuilts/sdk/tools":/* recursive = */ false,
 	}
 
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
@@ -173,19 +153,13 @@
 		"external/jemalloc_new":           Bp2BuildDefaultTrueRecursively,
 		"external/fmtlib":                 Bp2BuildDefaultTrueRecursively,
 		"external/arm-optimized-routines": Bp2BuildDefaultTrueRecursively,
+		"external/scudo":                  Bp2BuildDefaultTrueRecursively,
+		"prebuilts/clang/host/linux-x86":  Bp2BuildDefaultTrueRecursively,
 	}
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
 		// Things that transitively depend on unconverted libc_* modules.
-		"libc_nopthread", // http://b/186821550, cc_library_static, depends on //bionic/libc:libc_bionic_ndk (http://b/186822256)
-		//                                                     also depends on //bionic/libc:libc_tzcode (http://b/186822591)
-		//                                                     also depends on //bionic/libc:libstdc++ (http://b/186822597)
-		"libc_common",        // http://b/186821517, cc_library_static, depends on //bionic/libc:libc_nopthread (http://b/186821550)
-		"libc_common_static", // http://b/186824119, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-		"libc_common_shared", // http://b/186824118, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-		"libc_nomalloc",      // http://b/186825031, cc_library_static, depends on //bionic/libc:libc_common (http://b/186821517)
-
 		"libbionic_spawn_benchmark", // http://b/186824595, cc_library_static, depends on //external/google-benchmark (http://b/186822740)
 		//                                                                also depends on //system/logging/liblog:liblog (http://b/186822772)
 
@@ -204,13 +178,9 @@
 		"liblinker_malloc", // http://b/186826466, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
 		//                                                       also depends on //system/libziparchive:libziparchive (http://b/186823656)
 		//                                                       also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"libc_jemalloc_wrapper", // http://b/187012490, cc_library_static, depends on //external/jemalloc_new:libjemalloc5 (http://b/186828626)
-		"libc_ndk",              // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
-		"libc",                  // http://b/183064430, cc_library, depends on //external/jemalloc_new:libjemalloc5 (http://b/186828626)
-		"libc_tzcode",           // http://b/186822591, cc_library_static, localtime.c:84:46: error: expected expression
-		"libc_bionic_ndk",       // http://b/186822256, cc_library_static, signal.cpp:186:52: error: ISO C++ requires field designators to be specified in declaration order
-		"libc_malloc_hooks",     // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
-		"libm",                  // http://b/183064661, cc_library, math.h:25:16: error: unexpected token in argument list
+		"libc_ndk",          // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
+		"libc_malloc_hooks", // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
+		"libm",              // http://b/183064661, cc_library, math.h:25:16: error: unexpected token in argument list
 
 		// http://b/186823769: Needs C++ STL support, includes from unconverted standard libraries in //external/libcxx
 		// c++_static
@@ -218,6 +188,7 @@
 		// libcxx
 		"libBionicBenchmarksUtils", // cc_library_static, fatal error: 'map' file not found, from libcxx
 		"fmtlib",                   // cc_library_static, fatal error: 'cassert' file not found, from libcxx
+		"fmtlib_ndk",               // cc_library_static, fatal error: 'cassert' file not found
 		"libbase",                  // http://b/186826479, cc_library, fatal error: 'memory' file not found, from libcxx
 
 		// http://b/186024507: Includes errors because of the system_shared_libs default value.
@@ -229,6 +200,8 @@
 		"note_memtag_heap_async", // http://b/185127353: cc_library_static, error: feature.h not found
 		"note_memtag_heap_sync",  // http://b/185127353: cc_library_static, error: feature.h not found
 
+		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
+
 		// Tests. Handle later.
 		"libbionic_tests_headers_posix", // http://b/186024507, cc_library_static, sched.h, time.h not found
 		"libjemalloc5_integrationtest",
@@ -239,32 +212,21 @@
 	// Per-module denylist of cc_library modules to only generate the static
 	// variant if their shared variant isn't ready or buildable by Bazel.
 	bp2buildCcLibraryStaticOnlyList = []string{
-		"libstdc++", // http://b/186822597, cc_library, ld.lld: error: undefined symbol: __errno
+		"libstdc++",    // http://b/186822597, cc_library, ld.lld: error: undefined symbol: __errno
+		"libjemalloc5", // http://b/188503688, cc_library, `target: { android: { enabled: false } }` for android targets.
 	}
 
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
-	mixedBuildsDisabledList = []string{
-		"libc_netbsd",                      // lberki@, cc_library_static, version script assignment of 'LIBC_PRIVATE' to symbol 'SHA1Final' failed: symbol not defined
-		"libc_openbsd",                     // ruperts@, cc_library_static, OK for bp2build but error: duplicate symbol: strcpy for mixed builds
-		"libsystemproperties",              // cparsons@, cc_library_static, wrong include paths
-		"libpropertyinfoparser",            // cparsons@, cc_library_static, wrong include paths
-		"libarm-optimized-routines-string", // jingwen@, cc_library_static, OK for bp2build but b/186615213 (asflags not handled in  bp2build), version script assignment of 'LIBC' to symbol 'memcmp' failed: symbol not defined (also for memrchr, strnlen)
-		"fmtlib_ndk",                       // http://b/187040371, cc_library_static, OK for bp2build but format-inl.h:11:10: fatal error: 'cassert' file not found for mixed builds
-	}
+	mixedBuildsDisabledList = []string{}
 
 	// Used for quicker lookups
-	bp2buildDoNotWriteBuildFile = map[string]bool{}
 	bp2buildModuleDoNotConvert  = map[string]bool{}
 	bp2buildCcLibraryStaticOnly = map[string]bool{}
 	mixedBuildsDisabled         = map[string]bool{}
 )
 
 func init() {
-	for _, moduleName := range bp2buildDoNotWriteBuildFileList {
-		bp2buildDoNotWriteBuildFile[moduleName] = true
-	}
-
 	for _, moduleName := range bp2buildModuleDoNotConvertList {
 		bp2buildModuleDoNotConvert[moduleName] = true
 	}
@@ -282,12 +244,21 @@
 	return bp2buildCcLibraryStaticOnly[ctx.Module().Name()]
 }
 
-func ShouldWriteBuildFileForDir(dir string) bool {
-	if _, ok := bp2buildDoNotWriteBuildFile[dir]; ok {
-		return false
-	} else {
+func ShouldKeepExistingBuildFileForDir(dir string) bool {
+	if _, ok := bp2buildKeepExistingBuildFile[dir]; ok {
+		// Exact dir match
 		return true
 	}
+	// Check if subtree match
+	for prefix, recursive := range bp2buildKeepExistingBuildFile {
+		if recursive {
+			if strings.HasPrefix(dir, prefix+"/") {
+				return true
+			}
+		}
+	}
+	// Default
+	return false
 }
 
 // MixedBuildsEnabled checks that a module is ready to be replaced by a
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 4598995..b11b474 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -333,7 +333,7 @@
 	// The actual platform values here may be overridden by configuration
 	// transitions from the buildroot.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_x86_64"))
+		fmt.Sprintf("--platforms=%s", "//build/bazel/platforms:android_arm"))
 	cmdFlags = append(cmdFlags,
 		fmt.Sprintf("--extra_toolchains=%s", "//prebuilts/clang/host/linux-x86:all"))
 	// This should be parameterized on the host OS, but let's restrict to linux
@@ -347,7 +347,13 @@
 
 	bazelCmd := exec.Command(paths.bazelPath, cmdFlags...)
 	bazelCmd.Dir = absolutePath(paths.syntheticWorkspaceDir())
-	bazelCmd.Env = append(os.Environ(), "HOME="+paths.homeDir, pwdPrefix(),
+	bazelCmd.Env = append(os.Environ(),
+		"HOME="+paths.homeDir,
+		pwdPrefix(),
+		"BUILD_DIR="+absolutePath(paths.buildDir),
+		// Make OUT_DIR absolute here so tools/bazel.sh uses the correct
+		// OUT_DIR at <root>/out, instead of <root>/out/soong/workspace/out.
+		"OUT_DIR="+absolutePath(paths.outDir()),
 		// Disables local host detection of gcc; toolchain information is defined
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
@@ -536,10 +542,13 @@
   platform_name = build_options(target)["//command_line_option:platforms"][0].name
   if platform_name == "host":
     return "HOST"
-  elif not platform_name.startswith("android_"):
-    fail("expected platform name of the form 'android_<arch>', but was " + str(platforms))
+  elif platform_name.startswith("android_"):
+    return platform_name[len("android_"):]
+  elif platform_name.startswith("linux_"):
+    return platform_name[len("linux_"):]
+  else:
+    fail("expected platform name of the form 'android_<arch>' or 'linux_<arch>', but was " + str(platforms))
     return "UNKNOWN"
-  return platform_name[len("android_"):]
 
 def format(target):
   id_string = str(target.label) + "|" + get_arch(target)
@@ -564,7 +573,7 @@
 // Returns the path where the contents of the @soong_injection repository live.
 // It is used by Soong to tell Bazel things it cannot over the command line.
 func (p *bazelPaths) injectedFilesDir() string {
-	return filepath.Join(p.buildDir, "soong_injection")
+	return filepath.Join(p.buildDir, bazel.SoongInjectionDirName)
 }
 
 // Returns the path of the synthetic Bazel workspace that contains a symlink
@@ -573,6 +582,11 @@
 	return filepath.Join(p.buildDir, "workspace")
 }
 
+// Returns the path to the top level out dir ($OUT_DIR).
+func (p *bazelPaths) outDir() string {
+	return filepath.Dir(p.buildDir)
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *bazelContext) InvokeBazel() error {
@@ -583,8 +597,9 @@
 	var err error
 
 	soongInjectionPath := absolutePath(context.paths.injectedFilesDir())
-	if _, err := os.Stat(soongInjectionPath); os.IsNotExist(err) {
-		err = os.Mkdir(soongInjectionPath, 0777)
+	mixedBuildsPath := filepath.Join(soongInjectionPath, "mixed_builds")
+	if _, err := os.Stat(mixedBuildsPath); os.IsNotExist(err) {
+		err = os.MkdirAll(mixedBuildsPath, 0777)
 	}
 	if err != nil {
 		return err
@@ -596,14 +611,14 @@
 	}
 
 	err = ioutil.WriteFile(
-		filepath.Join(soongInjectionPath, "main.bzl"),
+		filepath.Join(mixedBuildsPath, "main.bzl"),
 		context.mainBzlFileContents(), 0666)
 	if err != nil {
 		return err
 	}
 
 	err = ioutil.WriteFile(
-		filepath.Join(soongInjectionPath, "BUILD.bazel"),
+		filepath.Join(mixedBuildsPath, "BUILD.bazel"),
 		context.mainBuildFileContents(), 0666)
 	if err != nil {
 		return err
@@ -615,7 +630,7 @@
 	if err != nil {
 		return err
 	}
-	buildrootLabel := "@soong_injection//:buildroot"
+	buildrootLabel := "@soong_injection//mixed_builds:buildroot"
 	cqueryOutput, cqueryErr, err = context.issueBazelCommand(
 		context.paths,
 		bazel.CqueryBuildRootRunName,
@@ -676,7 +691,7 @@
 	_, _, err = context.issueBazelCommand(
 		context.paths,
 		bazel.BazelBuildPhonyRootRunName,
-		bazelCommand{"build", "@soong_injection//:phonyroot"})
+		bazelCommand{"build", "@soong_injection//mixed_builds:phonyroot"})
 
 	if err != nil {
 		return err
@@ -730,8 +745,17 @@
 		}
 		rule := NewRuleBuilder(pctx, ctx)
 		cmd := rule.Command()
-		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ && %s",
-			ctx.Config().BazelContext.OutputBase(), buildStatement.Command))
+
+		// cd into Bazel's execution root, which is the action cwd.
+		cmd.Text(fmt.Sprintf("cd %s/execroot/__main__ && ", ctx.Config().BazelContext.OutputBase()))
+
+		for _, pair := range buildStatement.Env {
+			// Set per-action env variables, if any.
+			cmd.Flag(pair.Key + "=" + pair.Value)
+		}
+
+		// The actual Bazel action.
+		cmd.Text(" " + buildStatement.Command)
 
 		for _, outputPath := range buildStatement.OutputPaths {
 			cmd.ImplicitOutput(PathForBazelOut(ctx, outputPath))
@@ -744,6 +768,10 @@
 			cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
 		}
 
+		for _, symlinkPath := range buildStatement.SymlinkPaths {
+			cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath))
+		}
+
 		// This is required to silence warnings pertaining to unexpected timestamps. Particularly,
 		// some Bazel builtins (such as files in the bazel_tools directory) have far-future
 		// timestamps. Without restat, Ninja would emit warnings that the input files of a
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index cb25fee..f1fabec 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -11,7 +11,7 @@
 	label := "//foo:bar"
 	arch := Arm64
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "cquery", expression: "kind(rule, deps(@soong_injection//:buildroot))"}: `//foo:bar|arm64>>out/foo/bar.txt`,
+		bazelCommand{command: "cquery", expression: "kind(rule, deps(@soong_injection//mixed_builds:buildroot))"}: `//foo:bar|arm64>>out/foo/bar.txt`,
 	})
 	g, ok := bazelContext.GetOutputFiles(label, arch)
 	if ok {
@@ -35,13 +35,13 @@
 	if err != nil {
 		t.Fatalf("Did not expect error invoking Bazel, but got %s", err)
 	}
-	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "main.bzl")); os.IsNotExist(err) {
+	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "main.bzl")); os.IsNotExist(err) {
 		t.Errorf("Expected main.bzl to exist, but it does not")
 	} else if err != nil {
 		t.Errorf("Unexpected error stating main.bzl %s", err)
 	}
 
-	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "BUILD.bazel")); os.IsNotExist(err) {
+	if _, err := os.Stat(filepath.Join(baseDir, "soong_injection", "mixed_builds", "BUILD.bazel")); os.IsNotExist(err) {
 		t.Errorf("Expected BUILD.bazel to exist, but it does not")
 	} else if err != nil {
 		t.Errorf("Unexpected error stating BUILD.bazel %s", err)
@@ -56,7 +56,7 @@
 
 func TestInvokeBazelPopulatesBuildStatements(t *testing.T) {
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{
-		bazelCommand{command: "aquery", expression: "deps(@soong_injection//:buildroot)"}: `
+		bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"}: `
 {
   "artifacts": [{
     "id": 1,
@@ -105,7 +105,7 @@
 		outputBase:   "outputbase",
 		workspaceDir: "workspace_dir",
 	}
-	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(@soong_injection//:buildroot)"}
+	aqueryCommand := bazelCommand{command: "aquery", expression: "deps(@soong_injection//mixed_builds:buildroot)"}
 	if _, exists := bazelCommandResults[aqueryCommand]; !exists {
 		bazelCommandResults[aqueryCommand] = "{}\n"
 	}
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index f4b2a7c..f74fed1 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -83,6 +83,36 @@
 // or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
 // module within the given ctx.
 func BazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
+	return bazelLabelForModuleDeps(ctx, modules, false)
+}
+
+// BazelLabelForModuleWholeDeps expects a list of references to other modules, ("<module>"
+// or ":<module>") and returns a Bazel-compatible label which corresponds to dependencies on the
+// module within the given ctx, where prebuilt dependencies will be appended with _alwayslink so
+// they can be handled as whole static libraries.
+func BazelLabelForModuleWholeDeps(ctx BazelConversionPathContext, modules []string) bazel.LabelList {
+	return bazelLabelForModuleDeps(ctx, modules, true)
+}
+
+// BazelLabelForModuleDepsExcludes expects two lists: modules (containing modules to include in the
+// list), and excludes (modules to exclude from the list). Both of these should contain references
+// to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label list which
+// corresponds to dependencies on the module within the given ctx, and the excluded dependencies.
+func BazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, false)
+}
+
+// BazelLabelForModuleWholeDepsExcludes expects two lists: modules (containing modules to include in
+// the list), and excludes (modules to exclude from the list). Both of these should contain
+// references to other modules, ("<module>" or ":<module>"). It returns a Bazel-compatible label
+// list which corresponds to dependencies on the module within the given ctx, and the excluded
+// dependencies.  Prebuilt dependencies will be appended with _alwayslink so they can be handled as
+// whole static libraries.
+func BazelLabelForModuleWholeDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string) bazel.LabelList {
+	return bazelLabelForModuleDepsExcludes(ctx, modules, excludes, true)
+}
+
+func bazelLabelForModuleDeps(ctx BazelConversionPathContext, modules []string, isWholeLibs bool) bazel.LabelList {
 	var labels bazel.LabelList
 	for _, module := range modules {
 		bpText := module
@@ -90,7 +120,7 @@
 			module = ":" + module
 		}
 		if m, t := SrcIsModuleWithTag(module); m != "" {
-			l := getOtherModuleLabel(ctx, m, t)
+			l := getOtherModuleLabel(ctx, m, t, isWholeLibs)
 			l.OriginalModuleName = bpText
 			labels.Includes = append(labels.Includes, l)
 		} else {
@@ -100,6 +130,18 @@
 	return labels
 }
 
+func bazelLabelForModuleDepsExcludes(ctx BazelConversionPathContext, modules, excludes []string, isWholeLibs bool) bazel.LabelList {
+	moduleLabels := bazelLabelForModuleDeps(ctx, RemoveListFromList(modules, excludes), isWholeLibs)
+	if len(excludes) == 0 {
+		return moduleLabels
+	}
+	excludeLabels := bazelLabelForModuleDeps(ctx, excludes, isWholeLibs)
+	return bazel.LabelList{
+		Includes: moduleLabels.Includes,
+		Excludes: excludeLabels.Includes,
+	}
+}
+
 func BazelLabelForModuleSrcSingle(ctx BazelConversionPathContext, path string) bazel.Label {
 	return BazelLabelForModuleSrcExcludes(ctx, []string{path}, []string(nil)).Includes[0]
 }
@@ -257,7 +299,7 @@
 
 	for _, p := range paths {
 		if m, tag := SrcIsModuleWithTag(p); m != "" {
-			l := getOtherModuleLabel(ctx, m, tag)
+			l := getOtherModuleLabel(ctx, m, tag, false)
 			if !InList(l.Label, expandedExcludes) {
 				l.OriginalModuleName = fmt.Sprintf(":%s", m)
 				labels.Includes = append(labels.Includes, l)
@@ -288,7 +330,7 @@
 // getOtherModuleLabel returns a bazel.Label for the given dependency/tag combination for the
 // module. The label will be relative to the current directory if appropriate. The dependency must
 // already be resolved by either deps mutator or path deps mutator.
-func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string) bazel.Label {
+func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string, isWholeLibs bool) bazel.Label {
 	m, _ := ctx.GetDirectDep(dep)
 	if m == nil {
 		panic(fmt.Errorf(`Cannot get direct dep %q of %q.
@@ -297,6 +339,11 @@
 	}
 	otherLabel := bazelModuleLabel(ctx, m, tag)
 	label := bazelModuleLabel(ctx, ctx.Module(), "")
+	if isWholeLibs {
+		if m, ok := m.(Module); ok && IsModulePrebuilt(m) {
+			otherLabel += "_alwayslink"
+		}
+	}
 	if samePackage(label, otherLabel) {
 		otherLabel = bazelShortLabel(otherLabel)
 	}
diff --git a/android/config.go b/android/config.go
index 79917ad..ed90c31 100644
--- a/android/config.go
+++ b/android/config.go
@@ -35,6 +35,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android/soongconfig"
+	"android/soong/bazel"
 	"android/soong/remoteexec"
 )
 
@@ -169,7 +170,7 @@
 
 // loadFromConfigFile loads and decodes configuration options from a JSON file
 // in the current working directory.
-func loadFromConfigFile(configurable jsonConfigurable, filename string) error {
+func loadFromConfigFile(configurable *productVariables, filename string) error {
 	// Try to open the file
 	configFileReader, err := os.Open(filename)
 	defer configFileReader.Close()
@@ -194,13 +195,20 @@
 		}
 	}
 
-	// No error
-	return nil
+	if Bool(configurable.GcovCoverage) && Bool(configurable.ClangCoverage) {
+		return fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
+	}
+
+	configurable.Native_coverage = proptools.BoolPtr(
+		Bool(configurable.GcovCoverage) ||
+			Bool(configurable.ClangCoverage))
+
+	return saveToBazelConfigFile(configurable, filepath.Dir(filename))
 }
 
 // atomically writes the config file in case two copies of soong_build are running simultaneously
 // (for example, docs generation and ninja manifest generation)
-func saveToConfigFile(config jsonConfigurable, filename string) error {
+func saveToConfigFile(config *productVariables, filename string) error {
 	data, err := json.MarshalIndent(&config, "", "    ")
 	if err != nil {
 		return fmt.Errorf("cannot marshal config data: %s", err.Error())
@@ -229,6 +237,35 @@
 	return nil
 }
 
+func saveToBazelConfigFile(config *productVariables, outDir string) error {
+	dir := filepath.Join(outDir, bazel.SoongInjectionDirName, "product_config")
+	err := createDirIfNonexistent(dir, os.ModePerm)
+	if err != nil {
+		return fmt.Errorf("Could not create dir %s: %s", dir, err)
+	}
+
+	data, err := json.MarshalIndent(&config, "", "    ")
+	if err != nil {
+		return fmt.Errorf("cannot marshal config data: %s", err.Error())
+	}
+
+	bzl := []string{
+		bazel.GeneratedBazelFileWarning,
+		fmt.Sprintf(`_product_vars = json.decode("""%s""")`, data),
+		"product_vars = _product_vars\n",
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "product_variables.bzl"), []byte(strings.Join(bzl, "\n")), 0644)
+	if err != nil {
+		return fmt.Errorf("Could not write .bzl config file %s", err)
+	}
+	err = ioutil.WriteFile(filepath.Join(dir, "BUILD"), []byte(bazel.GeneratedBazelFileWarning), 0644)
+	if err != nil {
+		return fmt.Errorf("Could not write BUILD config file %s", err)
+	}
+
+	return nil
+}
+
 // NullConfig returns a mostly empty Config for use by standalone tools like dexpreopt_gen that
 // use the android package.
 func NullConfig(buildDir string) Config {
@@ -448,14 +485,6 @@
 		config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	if Bool(config.productVariables.GcovCoverage) && Bool(config.productVariables.ClangCoverage) {
-		return Config{}, fmt.Errorf("GcovCoverage and ClangCoverage cannot both be set")
-	}
-
-	config.productVariables.Native_coverage = proptools.BoolPtr(
-		Bool(config.productVariables.GcovCoverage) ||
-			Bool(config.productVariables.ClangCoverage))
-
 	config.BazelContext, err = NewBazelContext(config)
 	config.bp2buildPackageConfig = bp2buildDefaultConfig
 	config.bp2buildModuleTypeConfig = make(map[string]bool)
@@ -1618,6 +1647,21 @@
 	return ConfiguredJarList{apexes, jars}
 }
 
+// Filter keeps the entries if a jar appears in the given list of jars to keep; returns a new list.
+func (l *ConfiguredJarList) Filter(jarsToKeep []string) ConfiguredJarList {
+	var apexes []string
+	var jars []string
+
+	for i, jar := range l.jars {
+		if InList(jar, jarsToKeep) {
+			apexes = append(apexes, l.apexes[i])
+			jars = append(jars, jar)
+		}
+	}
+
+	return ConfiguredJarList{apexes, jars}
+}
+
 // CopyOfJars returns a copy of the list of strings containing jar module name
 // components.
 func (l *ConfiguredJarList) CopyOfJars() []string {
@@ -1646,6 +1690,16 @@
 	return paths
 }
 
+// BuildPathsByModule returns a map from module name to build paths based on the given directory
+// prefix.
+func (l *ConfiguredJarList) BuildPathsByModule(ctx PathContext, dir OutputPath) map[string]WritablePath {
+	paths := map[string]WritablePath{}
+	for _, jar := range l.jars {
+		paths[jar] = dir.Join(ctx, ModuleStem(jar)+".jar")
+	}
+	return paths
+}
+
 // UnmarshalJSON converts JSON configuration from raw bytes into a
 // ConfiguredJarList structure.
 func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
diff --git a/android/defaults.go b/android/defaults.go
index aacfbac..be80cf1 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -104,6 +104,7 @@
 	EarlyModuleContext
 
 	CreateModule(ModuleFactory, ...interface{}) Module
+	AddMissingDependencies(missingDeps []string)
 }
 
 type DefaultableHook func(ctx DefaultableHookContext)
diff --git a/android/filegroup.go b/android/filegroup.go
index fc6850e..e207412 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -63,7 +63,10 @@
 		Srcs: srcs,
 	}
 
-	props := bazel.BazelTargetModuleProperties{Rule_class: "filegroup"}
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "filegroup",
+		Bzl_load_location: "//build/bazel/rules:filegroup.bzl",
+	}
 
 	ctx.CreateBazelTargetModule(BazelFileGroupFactory, fg.Name(), props, attrs)
 }
diff --git a/android/image.go b/android/image.go
index 66101be..bc6b8cd 100644
--- a/android/image.go
+++ b/android/image.go
@@ -43,10 +43,9 @@
 	// its variation.
 	ExtraImageVariations(ctx BaseModuleContext) []string
 
-	// SetImageVariation will be passed a newly created recovery variant of the module.  ModuleBase implements
-	// SetImageVariation, most module types will not need to override it, and those that do must call the
-	// overridden method.  Implementors of SetImageVariation must be careful to modify the module argument
-	// and not the receiver.
+	// SetImageVariation is called for each newly created image variant. The receiver is the original
+	// module, "variation" is the name of the newly created variant and "module" is the newly created
+	// variant itself.
 	SetImageVariation(ctx BaseModuleContext, variation string, module Module)
 }
 
diff --git a/android/license.go b/android/license.go
index cb375a2..8bfd3ba 100644
--- a/android/license.go
+++ b/android/license.go
@@ -61,7 +61,17 @@
 }
 
 func (m *licenseModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	// Nothing to do.
+	// license modules have no licenses, but license_kinds must refer to license_kind modules
+	mergeStringProps(&m.base().commonProperties.Effective_licenses, ctx.ModuleName())
+	mergePathProps(&m.base().commonProperties.Effective_license_text, PathsForModuleSrc(ctx, m.properties.License_text)...)
+	for _, module := range ctx.GetDirectDepsWithTag(licenseKindTag) {
+		if lk, ok := module.(*licenseKindModule); ok {
+			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, lk.properties.Conditions...)
+			mergeStringProps(&m.base().commonProperties.Effective_license_kinds, ctx.OtherModuleName(module))
+		} else {
+			ctx.ModuleErrorf("license_kinds property %q is not a license_kind module", ctx.OtherModuleName(module))
+		}
+	}
 }
 
 func LicenseFactory() Module {
diff --git a/android/license_sdk_member.go b/android/license_sdk_member.go
new file mode 100644
index 0000000..cd36ed6
--- /dev/null
+++ b/android/license_sdk_member.go
@@ -0,0 +1,118 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"path/filepath"
+
+	"github.com/google/blueprint"
+)
+
+// Contains support for adding license modules to an sdk.
+
+func init() {
+	RegisterSdkMemberType(LicenseModuleSdkMemberType)
+}
+
+// licenseSdkMemberType determines how a license module is added to the sdk.
+type licenseSdkMemberType struct {
+	SdkMemberTypeBase
+}
+
+func (l *licenseSdkMemberType) AddDependencies(mctx BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
+	// Add dependencies onto the license module from the sdk module.
+	mctx.AddDependency(mctx.Module(), dependencyTag, names...)
+}
+
+func (l *licenseSdkMemberType) IsInstance(module Module) bool {
+	// Verify that the module being added is compatible with this module type.
+	_, ok := module.(*licenseModule)
+	return ok
+}
+
+func (l *licenseSdkMemberType) AddPrebuiltModule(ctx SdkMemberContext, member SdkMember) BpModule {
+	// Add the basics of a prebuilt module.
+	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "license")
+}
+
+func (l *licenseSdkMemberType) CreateVariantPropertiesStruct() SdkMemberProperties {
+	// Create the structure into which the properties of the license module that need to be output to
+	// the snapshot will be placed. The structure may be populated with information from a variant or
+	// may be used as the destination for properties that are common to a set of variants.
+	return &licenseSdkMemberProperties{}
+}
+
+// LicenseModuleSdkMemberType is the instance of licenseSdkMemberType
+var LicenseModuleSdkMemberType = &licenseSdkMemberType{
+	SdkMemberTypeBase{
+		PropertyName: "licenses",
+
+		// This should never be added directly to an sdk/module_exports, all license modules should be
+		// added indirectly as transitive dependencies of other sdk members.
+		BpPropertyNotRequired: true,
+
+		SupportsSdk: true,
+
+		// The snapshot of the license module is just another license module (not a prebuilt). They are
+		// internal modules only so will have an sdk specific name that will not clash with the
+		// originating source module.
+		UseSourceModuleTypeInSnapshot: true,
+	},
+}
+
+var _ SdkMemberType = (*licenseSdkMemberType)(nil)
+
+// licenseSdkMemberProperties is the set of properties that need to be added to the license module
+// in the snapshot.
+type licenseSdkMemberProperties struct {
+	SdkMemberPropertiesBase
+
+	// The kinds of licenses provided by the module.
+	License_kinds []string
+
+	// The source paths to the files containing license text.
+	License_text Paths
+}
+
+func (p *licenseSdkMemberProperties) PopulateFromVariant(_ SdkMemberContext, variant Module) {
+	// Populate the properties from the variant.
+	l := variant.(*licenseModule)
+	p.License_kinds = l.properties.License_kinds
+	p.License_text = l.base().commonProperties.Effective_license_text
+}
+
+func (p *licenseSdkMemberProperties) AddToPropertySet(ctx SdkMemberContext, propertySet BpPropertySet) {
+	// Just pass any specified license_kinds straight through.
+	if len(p.License_kinds) > 0 {
+		propertySet.AddProperty("license_kinds", p.License_kinds)
+	}
+
+	// Copy any license test files to the snapshot into a module specific location.
+	if len(p.License_text) > 0 {
+		dests := []string{}
+		for _, path := range p.License_text {
+			// The destination path only uses the path of the license file in the source not the license
+			// module name. That ensures that if the same license file is used by multiple license modules
+			// that it only gets copied once as the snapshot builder will dedup copies where the source
+			// and destination match.
+			dest := filepath.Join("licenses", path.String())
+			dests = append(dests, dest)
+			ctx.SnapshotBuilder().CopyToSnapshot(path, dest)
+		}
+		propertySet.AddProperty("license_text", dests)
+	}
+}
+
+var _ SdkMemberProperties = (*licenseSdkMemberProperties)(nil)
diff --git a/android/license_test.go b/android/license_test.go
index 2b09a4f..26b33c3 100644
--- a/android/license_test.go
+++ b/android/license_test.go
@@ -8,7 +8,7 @@
 var prepareForLicenseTest = GroupFixturePreparers(
 	// General preparers in alphabetical order.
 	PrepareForTestWithDefaults,
-	prepareForTestWithLicenses,
+	PrepareForTestWithLicenses,
 	PrepareForTestWithOverrides,
 	PrepareForTestWithPackageModule,
 	PrepareForTestWithPrebuilts,
diff --git a/android/licenses.go b/android/licenses.go
index 2838f5d..464ba49 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -32,8 +32,23 @@
 	blueprint.BaseDependencyTag
 }
 
+func (l licensesDependencyTag) SdkMemberType(Module) SdkMemberType {
+	// Add the supplied module to the sdk as a license module.
+	return LicenseModuleSdkMemberType
+}
+
+func (l licensesDependencyTag) ExportMember() bool {
+	// The license module will only every be referenced from within the sdk. This will ensure that it
+	// gets a unique name and so avoid clashing with the original license module.
+	return false
+}
+
 var (
 	licensesTag = licensesDependencyTag{}
+
+	// License modules, i.e. modules depended upon via a licensesTag, must be automatically added to
+	// any sdk/module_exports to which their referencing module is a member.
+	_ SdkMemberTypeDependencyTag = licensesTag
 )
 
 // Describes the property provided by a module to reference applicable licenses.
@@ -140,7 +155,6 @@
 	}
 
 	licenses := getLicenses(ctx, m)
-
 	ctx.AddVariationDependencies(nil, licensesTag, licenses...)
 }
 
@@ -187,34 +201,21 @@
 		return
 	}
 
-	// license modules have no licenses, but license_kinds must refer to license_kind modules
-	if l, ok := m.(*licenseModule); ok {
-		mergeProps(&m.base().commonProperties.Effective_licenses, ctx.ModuleName())
-		mergeProps(&m.base().commonProperties.Effective_license_text, PathsForModuleSrc(ctx, l.properties.License_text).Strings()...)
-		for _, module := range ctx.GetDirectDepsWithTag(licenseKindTag) {
-			if lk, ok := module.(*licenseKindModule); ok {
-				mergeProps(&m.base().commonProperties.Effective_license_conditions, lk.properties.Conditions...)
-				mergeProps(&m.base().commonProperties.Effective_license_kinds, ctx.OtherModuleName(module))
-			} else {
-				ctx.ModuleErrorf("license_kinds property %q is not a license_kind module", ctx.OtherModuleName(module))
-			}
-		}
-		return
-	}
-
 	if exemptFromRequiredApplicableLicensesProperty(m) {
 		return
 	}
 
+	var licenses []string
 	for _, module := range ctx.GetDirectDepsWithTag(licensesTag) {
 		if l, ok := module.(*licenseModule); ok {
+			licenses = append(licenses, ctx.OtherModuleName(module))
 			if m.base().commonProperties.Effective_package_name == nil && l.properties.Package_name != nil {
 				m.base().commonProperties.Effective_package_name = l.properties.Package_name
 			}
-			mergeProps(&m.base().commonProperties.Effective_licenses, module.base().commonProperties.Effective_licenses...)
-			mergeProps(&m.base().commonProperties.Effective_license_text, module.base().commonProperties.Effective_license_text...)
-			mergeProps(&m.base().commonProperties.Effective_license_kinds, module.base().commonProperties.Effective_license_kinds...)
-			mergeProps(&m.base().commonProperties.Effective_license_conditions, module.base().commonProperties.Effective_license_conditions...)
+			mergeStringProps(&m.base().commonProperties.Effective_licenses, module.base().commonProperties.Effective_licenses...)
+			mergePathProps(&m.base().commonProperties.Effective_license_text, module.base().commonProperties.Effective_license_text...)
+			mergeStringProps(&m.base().commonProperties.Effective_license_kinds, module.base().commonProperties.Effective_license_kinds...)
+			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, module.base().commonProperties.Effective_license_conditions...)
 		} else {
 			propertyName := "licenses"
 			primaryProperty := m.base().primaryLicensesProperty
@@ -224,19 +225,24 @@
 			ctx.ModuleErrorf("%s property %q is not a license module", propertyName, ctx.OtherModuleName(module))
 		}
 	}
+
+	// Make the license information available for other modules.
+	licenseInfo := LicenseInfo{
+		Licenses: licenses,
+	}
+	ctx.SetProvider(LicenseInfoProvider, licenseInfo)
 }
 
 // Update a property string array with a distinct union of its values and a list of new values.
-func mergeProps(prop *[]string, values ...string) {
-	s := make(map[string]bool)
-	for _, v := range *prop {
-		s[v] = true
-	}
-	for _, v := range values {
-		s[v] = true
-	}
-	*prop = []string{}
-	*prop = append(*prop, SortedStringKeys(s)...)
+func mergeStringProps(prop *[]string, values ...string) {
+	*prop = append(*prop, values...)
+	*prop = SortedUniqueStrings(*prop)
+}
+
+// Update a property Path array with a distinct union of its values and a list of new values.
+func mergePathProps(prop *Paths, values ...Path) {
+	*prop = append(*prop, values...)
+	*prop = SortedUniquePaths(*prop)
 }
 
 // Get the licenses property falling back to the package default.
@@ -293,3 +299,12 @@
 	}
 	return true
 }
+
+// LicenseInfo contains information about licenses for a specific module.
+type LicenseInfo struct {
+	// The list of license modules this depends upon, either explicitly or through default package
+	// configuration.
+	Licenses []string
+}
+
+var LicenseInfoProvider = blueprint.NewProvider(LicenseInfo{})
diff --git a/android/licenses_test.go b/android/licenses_test.go
index 913dc88..8503310 100644
--- a/android/licenses_test.go
+++ b/android/licenses_test.go
@@ -6,18 +6,6 @@
 	"github.com/google/blueprint"
 )
 
-var prepareForTestWithLicenses = GroupFixturePreparers(
-	FixtureRegisterWithContext(RegisterLicenseKindBuildComponents),
-	FixtureRegisterWithContext(RegisterLicenseBuildComponents),
-	FixtureRegisterWithContext(registerLicenseMutators),
-)
-
-func registerLicenseMutators(ctx RegistrationContext) {
-	ctx.PreArchMutators(RegisterLicensesPackageMapper)
-	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
-	ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
-}
-
 var licensesTests = []struct {
 	name                       string
 	fs                         MockFS
@@ -670,7 +658,7 @@
 		if base == nil {
 			return
 		}
-		actualNotices[m.Name()] = base.commonProperties.Effective_license_text
+		actualNotices[m.Name()] = base.commonProperties.Effective_license_text.Strings()
 	})
 
 	for moduleName, expectedNotices := range effectiveNotices {
diff --git a/android/module.go b/android/module.go
index 99606d1..f745a4a 100644
--- a/android/module.go
+++ b/android/module.go
@@ -165,13 +165,20 @@
 	// OtherModuleDependencyVariantExists returns true if a module with the
 	// specified name and variant exists. The variant must match the given
 	// variations. It must also match all the non-local variations of the current
-	// module. In other words, it checks for the module AddVariationDependencies
+	// module. In other words, it checks for the module that AddVariationDependencies
 	// would add a dependency on with the same arguments.
 	OtherModuleDependencyVariantExists(variations []blueprint.Variation, name string) bool
 
+	// OtherModuleFarDependencyVariantExists returns true if a module with the
+	// specified name and variant exists. The variant must match the given
+	// variations, but not the non-local variations of the current module. In
+	// other words, it checks for the module that AddFarVariationDependencies
+	// would add a dependency on with the same arguments.
+	OtherModuleFarDependencyVariantExists(variations []blueprint.Variation, name string) bool
+
 	// OtherModuleReverseDependencyVariantExists returns true if a module with the
 	// specified name exists with the same variations as the current module. In
-	// other words, it checks for the module AddReverseDependency would add a
+	// other words, it checks for the module that AddReverseDependency would add a
 	// dependency on with the same argument.
 	OtherModuleReverseDependencyVariantExists(name string) bool
 
@@ -688,7 +695,7 @@
 	// Override of module name when reporting licenses
 	Effective_package_name *string `blueprint:"mutated"`
 	// Notice files
-	Effective_license_text []string `blueprint:"mutated"`
+	Effective_license_text Paths `blueprint:"mutated"`
 	// License names
 	Effective_license_kinds []string `blueprint:"mutated"`
 	// License conditions
@@ -1500,7 +1507,7 @@
 	var installDeps []*installPathsDepSet
 	var packagingSpecs []*packagingSpecsDepSet
 	ctx.VisitDirectDeps(func(dep Module) {
-		if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(dep)) {
+		if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(dep)) && !dep.IsHideFromMake() {
 			installDeps = append(installDeps, dep.base().installFilesDepSet)
 			packagingSpecs = append(packagingSpecs, dep.base().packagingSpecsDepSet)
 		}
@@ -1809,8 +1816,8 @@
 	if m.Enabled() {
 		// ensure all direct android.Module deps are enabled
 		ctx.VisitDirectDepsBlueprint(func(bm blueprint.Module) {
-			if _, ok := bm.(Module); ok {
-				ctx.validateAndroidModule(bm, ctx.baseModuleContext.strictVisitDeps)
+			if m, ok := bm.(Module); ok {
+				ctx.validateAndroidModule(bm, ctx.OtherModuleDependencyTag(m), ctx.baseModuleContext.strictVisitDeps)
 			}
 		})
 
@@ -2022,6 +2029,9 @@
 func (b *baseModuleContext) OtherModuleDependencyVariantExists(variations []blueprint.Variation, name string) bool {
 	return b.bp.OtherModuleDependencyVariantExists(variations, name)
 }
+func (b *baseModuleContext) OtherModuleFarDependencyVariantExists(variations []blueprint.Variation, name string) bool {
+	return b.bp.OtherModuleFarDependencyVariantExists(variations, name)
+}
 func (b *baseModuleContext) OtherModuleReverseDependencyVariantExists(name string) bool {
 	return b.bp.OtherModuleReverseDependencyVariantExists(name)
 }
@@ -2234,7 +2244,12 @@
 	}
 }
 
-func (b *baseModuleContext) validateAndroidModule(module blueprint.Module, strict bool) Module {
+type AllowDisabledModuleDependency interface {
+	blueprint.DependencyTag
+	AllowDisabledModuleDependency(target Module) bool
+}
+
+func (b *baseModuleContext) validateAndroidModule(module blueprint.Module, tag blueprint.DependencyTag, strict bool) Module {
 	aModule, _ := module.(Module)
 
 	if !strict {
@@ -2247,10 +2262,12 @@
 	}
 
 	if !aModule.Enabled() {
-		if b.Config().AllowMissingDependencies() {
-			b.AddMissingDependencies([]string{b.OtherModuleName(aModule)})
-		} else {
-			b.ModuleErrorf("depends on disabled module %q", b.OtherModuleName(aModule))
+		if t, ok := tag.(AllowDisabledModuleDependency); !ok || !t.AllowDisabledModuleDependency(aModule) {
+			if b.Config().AllowMissingDependencies() {
+				b.AddMissingDependencies([]string{b.OtherModuleName(aModule)})
+			} else {
+				b.ModuleErrorf("depends on disabled module %q", b.OtherModuleName(aModule))
+			}
 		}
 		return nil
 	}
@@ -2343,7 +2360,7 @@
 
 func (b *baseModuleContext) VisitDirectDeps(visit func(Module)) {
 	b.bp.VisitDirectDeps(func(module blueprint.Module) {
-		if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil {
+		if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 			visit(aModule)
 		}
 	})
@@ -2351,7 +2368,7 @@
 
 func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) {
 	b.bp.VisitDirectDeps(func(module blueprint.Module) {
-		if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil {
+		if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 			if b.bp.OtherModuleDependencyTag(aModule) == tag {
 				visit(aModule)
 			}
@@ -2363,7 +2380,7 @@
 	b.bp.VisitDirectDepsIf(
 		// pred
 		func(module blueprint.Module) bool {
-			if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil {
+			if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 				return pred(aModule)
 			} else {
 				return false
@@ -2377,7 +2394,7 @@
 
 func (b *baseModuleContext) VisitDepsDepthFirst(visit func(Module)) {
 	b.bp.VisitDepsDepthFirst(func(module blueprint.Module) {
-		if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil {
+		if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 			visit(aModule)
 		}
 	})
@@ -2387,7 +2404,7 @@
 	b.bp.VisitDepsDepthFirstIf(
 		// pred
 		func(module blueprint.Module) bool {
-			if aModule := b.validateAndroidModule(module, b.strictVisitDeps); aModule != nil {
+			if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
 				return pred(aModule)
 			} else {
 				return false
diff --git a/android/override_module.go b/android/override_module.go
index 0a7e294..e72cb78 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -213,7 +213,6 @@
 // to keep them in this order and not put any order mutators between them.
 func RegisterOverridePostDepsMutators(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("override_deps", overrideModuleDepsMutator).Parallel()
-	ctx.TopDown("register_override", registerOverrideMutator).Parallel()
 	ctx.BottomUp("perform_override", performOverrideMutator).Parallel()
 	// overridableModuleDepsMutator calls OverridablePropertiesDepsMutator so that overridable modules can
 	// add deps from overridable properties.
@@ -253,20 +252,11 @@
 				return
 			}
 		})
-		ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)
-	}
-}
-
-// Visits the base module added as a dependency above, checks the module type, and registers the
-// overriding module.
-func registerOverrideMutator(ctx TopDownMutatorContext) {
-	ctx.VisitDirectDepsWithTag(overrideBaseDepTag, func(base Module) {
-		if o, ok := base.(OverridableModule); ok {
+		baseModule := ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)[0]
+		if o, ok := baseModule.(OverridableModule); ok {
 			o.addOverride(ctx.Module().(OverrideModule))
-		} else {
-			ctx.PropertyErrorf("base", "unsupported base module type")
 		}
-	})
+	}
 }
 
 // Now, goes through all overridable modules, finds all modules overriding them, creates a local
diff --git a/android/packaging.go b/android/packaging.go
index 72c0c17..9065826 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -198,8 +198,8 @@
 	}
 }
 
-// See PackageModule.CopyDepsToZip
-func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
+// Returns transitive PackagingSpecs from deps
+func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
 	ctx.VisitDirectDeps(func(child Module) {
 		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
@@ -211,7 +211,12 @@
 			}
 		}
 	})
+	return m
+}
 
+// See PackageModule.CopyDepsToZip
+func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut WritablePath) (entries []string) {
+	m := p.GatherPackagingSpecs(ctx)
 	builder := NewRuleBuilder(pctx, ctx)
 
 	dir := PathForModuleOut(ctx, ".zip")
diff --git a/android/paths.go b/android/paths.go
index 5d458cb..128ec12 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -287,6 +287,17 @@
 	return p.path
 }
 
+// AsPaths converts the OptionalPath into Paths.
+//
+// It returns nil if this is not valid, or a single length slice containing the Path embedded in
+// this OptionalPath.
+func (p OptionalPath) AsPaths() Paths {
+	if !p.valid {
+		return nil
+	}
+	return Paths{p.path}
+}
+
 // RelativeToTop returns an OptionalPath with the path that was embedded having been replaced by the
 // result of calling Path.RelativeToTop on it.
 func (p OptionalPath) RelativeToTop() OptionalPath {
@@ -1580,6 +1591,18 @@
 // PathForModuleInstall returns a Path representing the install path for the
 // module appended with paths...
 func PathForModuleInstall(ctx ModuleInstallPathContext, pathComponents ...string) InstallPath {
+	os, arch := osAndArch(ctx)
+	partition := modulePartition(ctx, os)
+	return makePathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
+}
+
+// PathForModuleInPartitionInstall is similar to PathForModuleInstall but partition is provided by the caller
+func PathForModuleInPartitionInstall(ctx ModuleInstallPathContext, partition string, pathComponents ...string) InstallPath {
+	os, arch := osAndArch(ctx)
+	return makePathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
+}
+
+func osAndArch(ctx ModuleInstallPathContext) (OsType, ArchType) {
 	os := ctx.Os()
 	arch := ctx.Arch().ArchType
 	forceOS, forceArch := ctx.InstallForceOS()
@@ -1589,14 +1612,14 @@
 	if forceArch != nil {
 		arch = *forceArch
 	}
-	partition := modulePartition(ctx, os)
+	return os, arch
+}
 
-	ret := pathForInstall(ctx, os, arch, partition, ctx.Debug(), pathComponents...)
-
+func makePathForInstall(ctx ModuleInstallPathContext, os OsType, arch ArchType, partition string, debug bool, pathComponents ...string) InstallPath {
+	ret := pathForInstall(ctx, os, arch, partition, debug, pathComponents...)
 	if ctx.InstallBypassMake() && ctx.Config().KatiEnabled() {
 		ret = ret.ToMakePath()
 	}
-
 	return ret
 }
 
@@ -1979,6 +2002,10 @@
 
 func CreateOutputDirIfNonexistent(path WritablePath, perm os.FileMode) error {
 	dir := absolutePath(path.String())
+	return createDirIfNonexistent(dir, perm)
+}
+
+func createDirIfNonexistent(dir string, perm os.FileMode) error {
 	if _, err := os.Stat(dir); os.IsNotExist(err) {
 		return os.MkdirAll(dir, os.ModePerm)
 	} else {
@@ -1986,6 +2013,9 @@
 	}
 }
 
+// absolutePath is deliberately private so that Soong's Go plugins can't use it to find and
+// read arbitrary files without going through the methods in the current package that track
+// dependencies.
 func absolutePath(path string) string {
 	if filepath.IsAbs(path) {
 		return path
diff --git a/android/paths_test.go b/android/paths_test.go
index f8ccc77..6f5d79e 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -141,6 +141,9 @@
 
 	path = OptionalPathForPath(nil)
 	checkInvalidOptionalPath(t, path)
+
+	path = OptionalPathForPath(PathForTesting("path"))
+	checkValidOptionalPath(t, path, "path")
 }
 
 func checkInvalidOptionalPath(t *testing.T, path OptionalPath) {
@@ -151,6 +154,10 @@
 	if path.String() != "" {
 		t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String())
 	}
+	paths := path.AsPaths()
+	if len(paths) != 0 {
+		t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths)
+	}
 	defer func() {
 		if r := recover(); r == nil {
 			t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath")
@@ -159,6 +166,21 @@
 	path.Path()
 }
 
+func checkValidOptionalPath(t *testing.T, path OptionalPath, expectedString string) {
+	t.Helper()
+	if !path.Valid() {
+		t.Errorf("Initialized OptionalPath should not be invalid")
+	}
+	if path.String() != expectedString {
+		t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
+	}
+	paths := path.AsPaths()
+	if len(paths) != 1 {
+		t.Errorf("Initialized OptionalPath AsPaths() should return Paths with length 1, not %q", paths)
+	}
+	path.Path()
+}
+
 func check(t *testing.T, testType, testString string,
 	got interface{}, err []error,
 	expected interface{}, expectedErr []error) {
diff --git a/android/testing.go b/android/testing.go
index ce27fca..b36f62c 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -74,6 +74,42 @@
 	ctx.PostDepsMutators(RegisterOverridePostDepsMutators)
 })
 
+var PrepareForTestWithLicenses = GroupFixturePreparers(
+	FixtureRegisterWithContext(RegisterLicenseKindBuildComponents),
+	FixtureRegisterWithContext(RegisterLicenseBuildComponents),
+	FixtureRegisterWithContext(registerLicenseMutators),
+)
+
+func registerLicenseMutators(ctx RegistrationContext) {
+	ctx.PreArchMutators(RegisterLicensesPackageMapper)
+	ctx.PreArchMutators(RegisterLicensesPropertyGatherer)
+	ctx.PostDepsMutators(RegisterLicensesDependencyChecker)
+}
+
+var PrepareForTestWithLicenseDefaultModules = GroupFixturePreparers(
+	FixtureAddTextFile("build/soong/licenses/Android.bp", `
+		license {
+				name: "Android-Apache-2.0",
+				package_name: "Android",
+				license_kinds: ["SPDX-license-identifier-Apache-2.0"],
+				copyright_notice: "Copyright (C) The Android Open Source Project",
+				license_text: ["LICENSE"],
+		}
+
+		license_kind {
+				name: "SPDX-license-identifier-Apache-2.0",
+				conditions: ["notice"],
+				url: "https://spdx.org/licenses/Apache-2.0.html",
+		}
+
+		license_kind {
+				name: "legacy_unencumbered",
+				conditions: ["unencumbered"],
+		}
+	`),
+	FixtureAddFile("build/soong/licenses/LICENSE", nil),
+)
+
 // Test fixture preparer that will register most java build components.
 //
 // Singletons and mutators should only be added here if they are needed for a majority of java
@@ -677,9 +713,11 @@
 
 func (b baseTestingComponent) maybeBuildParamsFromRule(rule string) (TestingBuildParams, []string) {
 	var searchedRules []string
-	for _, p := range b.provider.BuildParamsForTests() {
-		searchedRules = append(searchedRules, p.Rule.String())
-		if strings.Contains(p.Rule.String(), rule) {
+	buildParams := b.provider.BuildParamsForTests()
+	for _, p := range buildParams {
+		ruleAsString := p.Rule.String()
+		searchedRules = append(searchedRules, ruleAsString)
+		if strings.Contains(ruleAsString, rule) {
 			return b.newTestingBuildParams(p), searchedRules
 		}
 	}
@@ -689,7 +727,7 @@
 func (b baseTestingComponent) buildParamsFromRule(rule string) TestingBuildParams {
 	p, searchRules := b.maybeBuildParamsFromRule(rule)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find rule %q.\nall rules: %v", rule, searchRules))
+		panic(fmt.Errorf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n")))
 	}
 	return p
 }
diff --git a/android/variable.go b/android/variable.go
index e830845..c766120 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -282,7 +282,7 @@
 	NativeCoverageExcludePaths []string `json:",omitempty"`
 
 	// Set by NewConfig
-	Native_coverage *bool
+	Native_coverage *bool `json:",omitempty"`
 
 	SanitizeHost       []string `json:",omitempty"`
 	SanitizeDevice     []string `json:",omitempty"`
@@ -458,16 +458,17 @@
 // with the appropriate ProductConfigVariable.
 type ProductConfigProperty struct {
 	ProductConfigVariable string
+	FullConfig            string
 	Property              interface{}
 }
 
 // ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that
 // all it all product variable-specific versions of a property are easily accessed together
-type ProductConfigProperties map[string][]ProductConfigProperty
+type ProductConfigProperties map[string]map[string]ProductConfigProperty
 
 // ProductVariableProperties returns a ProductConfigProperties containing only the properties which
 // have been set for the module in the given context.
-func ProductVariableProperties(ctx ProductConfigContext) ProductConfigProperties {
+func ProductVariableProperties(ctx BaseMutatorContext) ProductConfigProperties {
 	module := ctx.Module()
 	moduleBase := module.base()
 
@@ -477,7 +478,24 @@
 		return productConfigProperties
 	}
 
-	variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName("Product_variables")
+	productVariableValues(moduleBase.variableProperties, "", &productConfigProperties)
+
+	for _, configToProps := range moduleBase.GetArchVariantProperties(ctx, moduleBase.variableProperties) {
+		for config, props := range configToProps {
+			// GetArchVariantProperties is creating an instance of the requested type
+			// and productVariablesValues expects an interface, so no need to cast
+			productVariableValues(props, config, &productConfigProperties)
+		}
+	}
+
+	return productConfigProperties
+}
+
+func productVariableValues(variableProps interface{}, suffix string, productConfigProperties *ProductConfigProperties) {
+	if suffix != "" {
+		suffix = "-" + suffix
+	}
+	variableValues := reflect.ValueOf(variableProps).Elem().FieldByName("Product_variables")
 	for i := 0; i < variableValues.NumField(); i++ {
 		variableValue := variableValues.Field(i)
 		// Check if any properties were set for the module
@@ -495,15 +513,17 @@
 
 			// e.g. Asflags, Cflags, Enabled, etc.
 			propertyName := variableValue.Type().Field(j).Name
-			productConfigProperties[propertyName] = append(productConfigProperties[propertyName],
-				ProductConfigProperty{
-					ProductConfigVariable: productVariableName,
-					Property:              property.Interface(),
-				})
+			if (*productConfigProperties)[propertyName] == nil {
+				(*productConfigProperties)[propertyName] = make(map[string]ProductConfigProperty)
+			}
+			config := productVariableName + suffix
+			(*productConfigProperties)[propertyName][config] = ProductConfigProperty{
+				ProductConfigVariable: productVariableName,
+				FullConfig:            config,
+				Property:              property.Interface(),
+			}
 		}
 	}
-
-	return productConfigProperties
 }
 
 func VariableMutator(mctx BottomUpMutatorContext) {
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 5316d7b..08616a9 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -104,6 +104,7 @@
 			"LOCAL_NDK_STL_VARIANT":         "stl",
 			"LOCAL_JAR_MANIFEST":            "manifest",
 			"LOCAL_CERTIFICATE":             "certificate",
+			"LOCAL_CERTIFICATE_LINEAGE":     "lineage",
 			"LOCAL_PACKAGE_NAME":            "name",
 			"LOCAL_MODULE_RELATIVE_PATH":    "relative_install_path",
 			"LOCAL_PROTOC_OPTIMIZE_TYPE":    "proto.type",
diff --git a/androidmk/androidmk/androidmk_test.go b/androidmk/androidmk/androidmk_test.go
index 439f45d..067dcba 100644
--- a/androidmk/androidmk/androidmk_test.go
+++ b/androidmk/androidmk/androidmk_test.go
@@ -1463,6 +1463,22 @@
 }
 `,
 	},
+	{
+		desc: "LOCAL_CERTIFICATE_LINEAGE",
+		in: `
+include $(CLEAR_VARS)
+LOCAL_MODULE := foo
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE_LINEAGE := lineage
+include $(BUILD_PACKAGE)
+`,
+		expected: `
+android_test {
+    name: "foo",
+    lineage: "lineage",
+}
+`,
+	},
 }
 
 func TestEndToEnd(t *testing.T) {
diff --git a/apex/Android.bp b/apex/Android.bp
index e234181..14c8771 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -32,6 +32,7 @@
         "apex_test.go",
         "bootclasspath_fragment_test.go",
         "platform_bootclasspath_test.go",
+        "systemserver_classpath_fragment_test.go",
         "vndk_test.go",
     ],
     pluginFor: ["soong_build"],
diff --git a/apex/apex.go b/apex/apex.go
index 2c0df27..33b83c0 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -69,10 +69,12 @@
 	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
 	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
 	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
+	// Run mark_platform_availability before the apexMutator as the apexMutator needs to know whether
+	// it should create a platform variant.
+	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
 	ctx.BottomUp("apex", apexMutator).Parallel()
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
-	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
 }
 
 type apexBundleProperties struct {
@@ -100,6 +102,9 @@
 	// List of bootclasspath fragments that are embedded inside this APEX bundle.
 	Bootclasspath_fragments []string
 
+	// List of systemserverclasspath fragments that are embedded inside this APEX bundle.
+	Systemserverclasspath_fragments []string
+
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
 
@@ -165,9 +170,10 @@
 	// Default is false.
 	Ignore_system_library_special_case *bool
 
-	// Whenever apex_payload.img of the APEX should include dm-verity hashtree. Should be only
-	// used in tests.
-	Test_only_no_hashtree *bool
+	// Whenever apex_payload.img of the APEX should include dm-verity hashtree.
+	// Default value is false.
+	// TODO(b/190621617): change default value to true.
+	Generate_hashtree *bool
 
 	// Whenever apex_payload.img of the APEX should not be dm-verity signed. Should be only
 	// used in tests.
@@ -573,6 +579,7 @@
 	executableTag   = dependencyTag{name: "executable", payload: true}
 	fsTag           = dependencyTag{name: "filesystem", payload: true}
 	bcpfTag         = dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true}
+	sscpfTag        = dependencyTag{name: "systemserverclasspathFragment", payload: true, sourceOnly: true}
 	compatConfigTag = dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
 	javaLibTag      = dependencyTag{name: "javaLib", payload: true}
 	jniLibTag       = dependencyTag{name: "jniLib", payload: true}
@@ -735,24 +742,27 @@
 		}
 	}
 
-	// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
-	// regardless of the TARGET_PREFER_* setting. See b/144532908
-	archForPrebuiltEtc := config.Arches()[0]
-	for _, arch := range config.Arches() {
-		// Prefer 64-bit arch if there is any
-		if arch.ArchType.Multilib == "lib64" {
-			archForPrebuiltEtc = arch
-			break
+	if prebuilts := a.properties.Prebuilts; len(prebuilts) > 0 {
+		// For prebuilt_etc, use the first variant (64 on 64/32bit device, 32 on 32bit device)
+		// regardless of the TARGET_PREFER_* setting. See b/144532908
+		archForPrebuiltEtc := config.Arches()[0]
+		for _, arch := range config.Arches() {
+			// Prefer 64-bit arch if there is any
+			if arch.ArchType.Multilib == "lib64" {
+				archForPrebuiltEtc = arch
+				break
+			}
 		}
+		ctx.AddFarVariationDependencies([]blueprint.Variation{
+			{Mutator: "os", Variation: ctx.Os().String()},
+			{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
+		}, prebuiltTag, prebuilts...)
 	}
-	ctx.AddFarVariationDependencies([]blueprint.Variation{
-		{Mutator: "os", Variation: ctx.Os().String()},
-		{Mutator: "arch", Variation: archForPrebuiltEtc.String()},
-	}, prebuiltTag, a.properties.Prebuilts...)
 
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments...)
+	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.Systemserverclasspath_fragments...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
@@ -899,12 +909,16 @@
 
 	// This is the main part of this mutator. Mark the collected dependencies that they need to
 	// be built for this apexBundle.
+
+	// Note that there are many different names.
+	// ApexVariationName: this is the name of the apex variation
 	apexInfo := android.ApexInfo{
-		ApexVariationName: mctx.ModuleName(),
+		ApexVariationName: mctx.ModuleName(), // could be com.android.foo
 		MinSdkVersion:     minSdkVersion,
 		RequiredSdks:      a.RequiredSdks(),
 		Updatable:         a.Updatable(),
-		InApexes:          []string{mctx.ModuleName()},
+		InApexVariants:    []string{mctx.ModuleName()}, // could be com.android.foo
+		InApexModules:     []string{a.Name()},          // could be com.mycompany.android.foo
 		ApexContents:      []*android.ApexContents{apexContents},
 	}
 	mctx.WalkDeps(func(child, parent android.Module) bool {
@@ -1013,9 +1027,8 @@
 		}
 	})
 
-	// Exception 1: stub libraries and native bridge libraries are always available to platform
-	if cc, ok := mctx.Module().(*cc.Module); ok &&
-		(cc.IsStubs() || cc.Target().NativeBridge == android.NativeBridgeEnabled) {
+	// Exception 1: check to see if the module always requires it.
+	if am.AlwaysRequiresPlatformApexVariant() {
 		availableToPlatform = true
 	}
 
@@ -1256,7 +1269,7 @@
 }
 
 // Implements cc.Coverage
-func (a *apexBundle) PreventInstall() {
+func (a *apexBundle) SetPreventInstall() {
 	a.properties.PreventInstall = true
 }
 
@@ -1305,9 +1318,9 @@
 	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
 }
 
-// See the test_only_no_hashtree property
-func (a *apexBundle) testOnlyShouldSkipHashtreeGeneration() bool {
-	return proptools.Bool(a.properties.Test_only_no_hashtree)
+// See the generate_hashtree property
+func (a *apexBundle) shouldGenerateHashtree() bool {
+	return proptools.BoolDefault(a.properties.Generate_hashtree, false)
 }
 
 // See the test_only_unsigned_payload property
@@ -1598,7 +1611,7 @@
 		}
 
 		ai := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
-		externalDep := !android.InList(ctx.ModuleName(), ai.InApexes)
+		externalDep := !android.InList(ctx.ModuleName(), ai.InApexVariants)
 
 		// Visit actually
 		return do(ctx, parent, am, externalDep)
@@ -1714,6 +1727,15 @@
 					filesInfo = append(filesInfo, filesToAdd...)
 					return true
 				}
+			case sscpfTag:
+				{
+					if _, ok := child.(*java.SystemServerClasspathModule); !ok {
+						ctx.PropertyErrorf("systemserverclasspath_fragments", "%q is not a systemserverclasspath_fragment module", depName)
+						return false
+					}
+					filesInfo = append(filesInfo, apexClasspathFragmentProtoFile(ctx, child))
+					return true
+				}
 			case javaLibTag:
 				switch child.(type) {
 				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport, *java.Import:
@@ -1940,7 +1962,16 @@
 					default:
 						ctx.PropertyErrorf("bootclasspath_fragments", "bootclasspath_fragment content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
 					}
-
+				} else if java.IsSystemServerClasspathFragmentContentDepTag(depTag) {
+					// Add the contents of the systemserverclasspath fragment to the apex.
+					switch child.(type) {
+					case *java.Library, *java.SdkLibrary:
+						af := apexFileForJavaModule(ctx, child.(javaModule))
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					default:
+						ctx.PropertyErrorf("systemserverclasspath_fragments", "systemserverclasspath_fragment content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
+					}
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
 					// nothing
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
@@ -1979,7 +2010,9 @@
 
 	// Sort to have consistent build rules
 	sort.Slice(filesInfo, func(i, j int) bool {
-		return filesInfo[i].builtFile.String() < filesInfo[j].builtFile.String()
+		// Sort by destination path so as to ensure consistent ordering even if the source of the files
+		// changes.
+		return filesInfo[i].path() < filesInfo[j].path()
 	})
 
 	////////////////////////////////////////////////////////////////////////////////////////////
@@ -1988,7 +2021,7 @@
 	a.filesInfo = filesInfo
 
 	// Set suffix and primaryApexType depending on the ApexType
-	buildFlattenedAsDefault := ctx.Config().FlattenApex() && !ctx.Config().UnbundledBuildApps()
+	buildFlattenedAsDefault := ctx.Config().FlattenApex()
 	switch a.properties.ApexType {
 	case imageApex:
 		if buildFlattenedAsDefault {
@@ -2103,13 +2136,19 @@
 	}
 
 	// Add classpaths.proto config.
-	classpathProtoOutput := bootclasspathFragmentInfo.ClasspathFragmentProtoOutput
-	classpathProto := newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), bootclasspathFragmentInfo.ClasspathFragmentProtoInstallDir.Rel(), etc, nil)
-	filesToAdd = append(filesToAdd, classpathProto)
+	filesToAdd = append(filesToAdd, apexClasspathFragmentProtoFile(ctx, module))
 
 	return filesToAdd
 }
 
+// apexClasspathFragmentProtoFile returns apexFile structure defining the classpath.proto config that
+// the module contributes to the apex.
+func apexClasspathFragmentProtoFile(ctx android.ModuleContext, module blueprint.Module) apexFile {
+	fragmentInfo := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo)
+	classpathProtoOutput := fragmentInfo.ClasspathFragmentProtoOutput
+	return newApexFile(ctx, classpathProtoOutput, classpathProtoOutput.Base(), fragmentInfo.ClasspathFragmentProtoInstallDir.Rel(), etc, nil)
+}
+
 // apexFileForBootclasspathFragmentContentModule creates an apexFile for a bootclasspath_fragment
 // content module, i.e. a library that is part of the bootclasspath.
 func apexFileForBootclasspathFragmentContentModule(ctx android.ModuleContext, fragmentModule blueprint.Module, javaModule javaModule) apexFile {
@@ -2117,7 +2156,10 @@
 
 	// Get the dexBootJar from the bootclasspath_fragment as that is responsible for performing the
 	// hidden API encpding.
-	dexBootJar := bootclasspathFragmentInfo.DexBootJarPathForContentModule(javaModule)
+	dexBootJar, err := bootclasspathFragmentInfo.DexBootJarPathForContentModule(javaModule)
+	if err != nil {
+		ctx.ModuleErrorf("%s", err)
+	}
 
 	// Create an apexFile as for a normal java module but with the dex boot jar provided by the
 	// bootclasspath_fragment.
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 08d82e9..1bfe7e9 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -709,6 +709,79 @@
 	}
 }
 
+func TestApexManifestMinSdkVersion(t *testing.T) {
+	ctx := testApex(t, `
+		apex_defaults {
+			name: "my_defaults",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: ":my-file-contexts",
+			updatable: false,
+		}
+		apex {
+			name: "myapex_30",
+			min_sdk_version: "30",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_current",
+			min_sdk_version: "current",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_none",
+			defaults: ["my_defaults"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		filegroup {
+			name: "my-file-contexts",
+			srcs: ["product_specific_file_contexts"],
+		}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}), android.FixtureModifyProductVariables(
+		func(variables android.FixtureProductVariables) {
+			variables.Unbundled_build = proptools.BoolPtr(true)
+			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(false)
+		}), android.FixtureMergeEnv(map[string]string{
+		"UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT": "true",
+	}))
+
+	testCases := []struct {
+		module        string
+		minSdkVersion string
+	}{
+		{
+			module:        "myapex_30",
+			minSdkVersion: "30",
+		},
+		{
+			module:        "myapex_current",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+		{
+			module:        "myapex_none",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+	}
+	for _, tc := range testCases {
+		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module+"_image")
+		args := module.Rule("apexRule").Args
+		optFlags := args["opt_flags"]
+		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
+			t.Errorf("%s: Expected min_sdk_version=%s, got: %s", tc.module, tc.minSdkVersion, optFlags)
+		}
+	}
+}
+
 func TestBasicZipApex(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
@@ -4542,11 +4615,16 @@
 }
 
 func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
-	preparer := java.FixtureConfigureBootJars("myapex:libfoo", "myapex:libbar")
+	preparer := android.GroupFixturePreparers(
+		java.FixtureConfigureBootJars("myapex:libfoo", "myapex:libbar"),
+		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
+		// is disabled.
+		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+	)
 
 	checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, stem string, bootDexJarPath string) {
 		t.Helper()
-		s := ctx.SingletonForTests("dex_bootjars")
+		s := ctx.ModuleForTests("platform-bootclasspath", "android_common")
 		foundLibfooJar := false
 		base := stem + ".jar"
 		for _, output := range s.AllOutputs() {
@@ -4564,7 +4642,7 @@
 	checkHiddenAPIIndexInputs := func(t *testing.T, ctx *android.TestContext, expectedInputs string) {
 		t.Helper()
 		platformBootclasspath := ctx.ModuleForTests("platform-bootclasspath", "android_common")
-		indexRule := platformBootclasspath.Rule("platform-bootclasspath-monolithic-hiddenapi-index")
+		indexRule := platformBootclasspath.Rule("monolithic_hidden_API_index")
 		java.CheckHiddenAPIRuleInputs(t, expectedInputs, indexRule)
 	}
 
@@ -4602,10 +4680,10 @@
 		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 prebuilt_apex contributes to the hiddenapi index file.
+		// Verify the correct module jars contribute to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
-.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
-.intermediates/libfoo/android_common_myapex/hiddenapi/index.csv
+.intermediates/libbar.stubs/android_common/combined/libbar.stubs.jar
+.intermediates/libfoo/android_common_myapex/combined/libfoo.jar
 `)
 	})
 
@@ -4636,10 +4714,10 @@
 		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.
+		// Verify the correct module jars contribute to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
-.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
-.intermediates/libfoo/android_common_myapex/hiddenapi/index.csv
+.intermediates/libbar.stubs/android_common/combined/libbar.stubs.jar
+.intermediates/libfoo/android_common_myapex/combined/libfoo.jar
 `)
 	})
 
@@ -4691,7 +4769,7 @@
 		// prebuilt_apex module always depends on the prebuilt, and so it doesn't
 		// find the dex boot jar in it. We either need to disable the source libfoo
 		// or make the prebuilt libfoo preferred.
-		testDexpreoptWithApexes(t, bp, "failed to find a dex jar path for module 'libfoo'", preparer)
+		testDexpreoptWithApexes(t, bp, "module libfoo does not provide a dex boot jar", preparer)
 	})
 
 	t.Run("prebuilt library preferred with source", func(t *testing.T) {
@@ -4743,10 +4821,10 @@
 		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 prebuilt_apex contributes to the hiddenapi index file.
+		// Verify the correct module jars contribute to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
-.intermediates/prebuilt_libbar/android_common_myapex/hiddenapi/index.csv
-.intermediates/prebuilt_libfoo/android_common_myapex/hiddenapi/index.csv
+.intermediates/prebuilt_libbar.stubs/android_common/combined/libbar.stubs.jar
+.intermediates/prebuilt_libfoo/android_common_myapex/combined/libfoo.jar
 `)
 	})
 
@@ -4810,10 +4888,10 @@
 		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
 
-		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
+		// Verify the correct module jars contribute to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
-.intermediates/libbar/android_common_myapex/hiddenapi/index.csv
-.intermediates/libfoo/android_common_apex10000/hiddenapi/index.csv
+.intermediates/libbar/android_common_myapex/javac/libbar.jar
+.intermediates/libfoo/android_common_apex10000/javac/libfoo.jar
 `)
 	})
 
@@ -4879,10 +4957,10 @@
 		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 prebuilt_apex contributes to the hiddenapi index file.
+		// Verify the correct module jars contribute to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
-.intermediates/prebuilt_libbar/android_common_myapex/hiddenapi/index.csv
-.intermediates/prebuilt_libfoo/android_common_myapex/hiddenapi/index.csv
+.intermediates/prebuilt_libbar.stubs/android_common/combined/libbar.stubs.jar
+.intermediates/prebuilt_libfoo/android_common_myapex/combined/libfoo.jar
 `)
 	})
 }
@@ -7584,6 +7662,102 @@
 	}
 }
 
+func TestHostApexInHostOnlyBuild(t *testing.T) {
+	testApex(t, `
+		apex {
+			name: "myapex",
+			host_supported: true,
+			key: "myapex.key",
+			updatable: false,
+			payload_type: "zip",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`,
+		android.FixtureModifyConfig(func(config android.Config) {
+			// We may not have device targets in all builds, e.g. in
+			// prebuilts/build-tools/build-prebuilts.sh
+			config.Targets[android.Android] = []android.Target{}
+		}))
+}
+
+func TestApexJavaCoverage(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["mylib"],
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			systemserverclasspath_fragments: ["mysystemserverclasspathfragment"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "mylib",
+			srcs: ["mylib.java"],
+			apex_available: ["myapex"],
+			compile_dex: true,
+		}
+
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["mybootclasspathlib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "mybootclasspathlib",
+			srcs: ["mybootclasspathlib.java"],
+			apex_available: ["myapex"],
+			compile_dex: true,
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			contents: ["mysystemserverclasspathlib"],
+			apex_available: ["myapex"],
+		}
+
+		java_library {
+			name: "mysystemserverclasspathlib",
+			srcs: ["mysystemserverclasspathlib.java"],
+			apex_available: ["myapex"],
+			compile_dex: true,
+		}
+	`
+
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithApexBuildComponents,
+		prepareForTestWithMyapex,
+		java.PrepareForTestWithJavaDefaultModules,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.FixtureWithRootAndroidBp(bp),
+		android.FixtureMergeEnv(map[string]string{
+			"EMMA_INSTRUMENT": "true",
+		}),
+	).RunTest(t)
+
+	// Make sure jacoco ran on both mylib and mybootclasspathlib
+	if result.ModuleForTests("mylib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+		t.Errorf("Failed to find jacoco rule for mylib")
+	}
+	if result.ModuleForTests("mybootclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+		t.Errorf("Failed to find jacoco rule for mybootclasspathlib")
+	}
+	if result.ModuleForTests("mysystemserverclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+		t.Errorf("Failed to find jacoco rule for mysystemserverclasspathlib")
+	}
+}
+
 func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 7bb3ff6..7aecff6 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -62,6 +62,7 @@
 		apex {
 			name: "com.android.art",
 			key: "com.android.art.key",
+			bootclasspath_fragments: ["art-bootclasspath-fragment"],
  			java_libs: [
 				"baz",
 				"quuz",
@@ -100,32 +101,12 @@
 				"com.android.art",
 			],
 		}
-
-		bootclasspath_fragment {
-			name: "framework-bootclasspath-fragment",
-			image_name: "boot",
-		}
 `,
 	)
 
-	// Make sure that the framework-bootclasspath-fragment is using the correct configuration.
-	checkBootclasspathFragment(t, result, "framework-bootclasspath-fragment", "platform:foo,platform:bar", `
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.art
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.oat
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.vdex
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.art
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.oat
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.vdex
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.art
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.oat
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.vdex
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.art
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.oat
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.vdex
-`)
-
 	// Make sure that the art-bootclasspath-fragment is using the correct configuration.
-	checkBootclasspathFragment(t, result, "art-bootclasspath-fragment", "com.android.art:baz,com.android.art:quuz", `
+	checkBootclasspathFragment(t, result, "art-bootclasspath-fragment", "android_common_apex10000",
+		"com.android.art:baz,com.android.art:quuz", `
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.art
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.oat
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.vdex
@@ -141,10 +122,132 @@
 `)
 }
 
-func checkBootclasspathFragment(t *testing.T, result *android.TestResult, moduleName string, expectedConfiguredModules string, expectedBootclasspathFragmentFiles string) {
+func TestBootclasspathFragments_FragmentDependency(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootclasspathFragment,
+		// Configure some libraries in the art bootclasspath_fragment and platform_bootclasspath.
+		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "platform:foo", "platform:bar"),
+		prepareForTestWithArtApex,
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo", "baz"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		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",
+		}
+
+		java_sdk_library {
+			name: "baz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+			test: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "quuz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			compile_dex: true,
+		}
+
+		bootclasspath_fragment {
+			name: "art-bootclasspath-fragment",
+			image_name: "art",
+			// Must match the "com.android.art:" entries passed to FixtureConfigureBootJars above.
+			contents: ["baz", "quuz"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		bootclasspath_fragment {
+			name: "other-bootclasspath-fragment",
+			contents: ["foo", "bar"],
+			fragments: [
+					{
+							apex: "com.android.art",
+							module: "art-bootclasspath-fragment",
+					},
+			],
+		}
+`,
+	)
+
+	checkSdkKindStubs := func(message string, info java.HiddenAPIInfo, kind android.SdkKind, expectedPaths ...string) {
+		t.Helper()
+		android.AssertPathsRelativeToTopEquals(t, fmt.Sprintf("%s %s", message, kind), expectedPaths, info.TransitiveStubDexJarsByKind[kind])
+	}
+
+	// Check stub dex paths exported by art.
+	artFragment := result.Module("art-bootclasspath-fragment", "android_common")
+	artInfo := result.ModuleProvider(artFragment, java.HiddenAPIInfoProvider).(java.HiddenAPIInfo)
+
+	bazPublicStubs := "out/soong/.intermediates/baz.stubs/android_common/dex/baz.stubs.jar"
+	bazSystemStubs := "out/soong/.intermediates/baz.stubs.system/android_common/dex/baz.stubs.system.jar"
+	bazTestStubs := "out/soong/.intermediates/baz.stubs.test/android_common/dex/baz.stubs.test.jar"
+
+	checkSdkKindStubs("art", artInfo, android.SdkPublic, bazPublicStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkSystem, bazSystemStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkTest, bazTestStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkCorePlatform)
+
+	// Check stub dex paths exported by other.
+	otherFragment := result.Module("other-bootclasspath-fragment", "android_common")
+	otherInfo := result.ModuleProvider(otherFragment, java.HiddenAPIInfoProvider).(java.HiddenAPIInfo)
+
+	fooPublicStubs := "out/soong/.intermediates/foo.stubs/android_common/dex/foo.stubs.jar"
+	fooSystemStubs := "out/soong/.intermediates/foo.stubs.system/android_common/dex/foo.stubs.system.jar"
+
+	checkSdkKindStubs("other", otherInfo, android.SdkPublic, bazPublicStubs, fooPublicStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkSystem, bazSystemStubs, fooSystemStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkTest, bazTestStubs, fooSystemStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkCorePlatform)
+}
+
+func checkBootclasspathFragment(t *testing.T, result *android.TestResult, moduleName, variantName string, expectedConfiguredModules string, expectedBootclasspathFragmentFiles string) {
 	t.Helper()
 
-	bootclasspathFragment := result.ModuleForTests(moduleName, "android_common").Module().(*java.BootclasspathFragmentModule)
+	bootclasspathFragment := result.ModuleForTests(moduleName, variantName).Module().(*java.BootclasspathFragmentModule)
 
 	bootclasspathFragmentInfo := result.ModuleProvider(bootclasspathFragment, java.BootclasspathFragmentApexContentInfoProvider).(java.BootclasspathFragmentApexContentInfo)
 	modules := bootclasspathFragmentInfo.Modules()
@@ -279,7 +382,7 @@
 		).RunTest(t)
 
 		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
-			"etc/classpaths/mybootclasspathfragment.pb",
+			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
 			"javalib/arm/boot.vdex",
@@ -433,6 +536,14 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithMyapex,
+		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
+		java.FixtureConfigureBootJars("myapex:foo", "myapex:bar"),
+		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
+		// is disabled.
+		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -449,10 +560,11 @@
 			private_key: "testkey.pem",
 		}
 
-		java_library {
+		java_sdk_library {
 			name: "foo",
 			srcs: ["b.java"],
-			installable: true,
+			shared_library: false,
+			public: {enabled: true},
 			apex_available: [
 				"myapex",
 			],
@@ -482,7 +594,7 @@
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		// This does not include art, oat or vdex files as they are only included for the art boot
 		// image.
-		"etc/classpaths/mybootclasspathfragment.pb",
+		"etc/classpaths/bootclasspath.pb",
 		"javalib/bar.jar",
 		"javalib/foo.jar",
 	})
@@ -491,6 +603,30 @@
 		`myapex.key`,
 		`mybootclasspathfragment`,
 	})
+
+	apex := result.ModuleForTests("myapex", "android_common_myapex_image")
+	apexRule := apex.Rule("apexRule")
+	copyCommands := apexRule.Args["copy_commands"]
+
+	// Make sure that the fragment provides the hidden API encoded dex jars to the APEX.
+	fragment := result.Module("mybootclasspathfragment", "android_common_apex10000")
+
+	info := result.ModuleProvider(fragment, java.BootclasspathFragmentApexContentInfoProvider).(java.BootclasspathFragmentApexContentInfo)
+
+	checkFragmentExportedDexJar := func(name string, expectedDexJar string) {
+		module := result.Module(name, "android_common_apex10000")
+		dexJar, err := info.DexBootJarPathForContentModule(module)
+		if err != nil {
+			t.Error(err)
+		}
+		android.AssertPathRelativeToTopEquals(t, name+" dex", expectedDexJar, dexJar)
+
+		expectedCopyCommand := fmt.Sprintf("&& cp -f %s out/soong/.intermediates/myapex/android_common_myapex_image/image.apex/javalib/%s.jar", expectedDexJar, name)
+		android.AssertStringDoesContain(t, name+" apex copy command", copyCommands, expectedCopyCommand)
+	}
+
+	checkFragmentExportedDexJar("foo", "out/soong/.intermediates/mybootclasspathfragment/android_common_apex10000/hiddenapi-modular/encoded/foo.jar")
+	checkFragmentExportedDexJar("bar", "out/soong/.intermediates/mybootclasspathfragment/android_common_apex10000/hiddenapi-modular/encoded/bar.jar")
 }
 
 // TODO(b/177892522) - add test for host apex.
diff --git a/apex/builder.go b/apex/builder.go
index da8841c..24c049b 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -602,6 +602,11 @@
 		// codename
 		if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
+
+			if java.UseApiFingerprint(ctx) {
+				minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
+				implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+			}
 		}
 		// apex module doesn't have a concept of target_sdk_version, hence for the time
 		// being targetSdkVersion == default targetSdkVersion of the branch.
@@ -611,10 +616,6 @@
 			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
 			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
 		}
-		if java.UseApiFingerprint(ctx) {
-			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
-			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
-		}
 		optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
 		optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
 
@@ -629,11 +630,7 @@
 			optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.mergedNotices.HtmlGzOutput.String()))
 		}
 
-		if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && ctx.ModuleDir() != "system/apex/shim/build" && a.testOnlyShouldSkipHashtreeGeneration() {
-			ctx.PropertyErrorf("test_only_no_hashtree", "not available")
-			return
-		}
-		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) || a.testOnlyShouldSkipHashtreeGeneration()) && !compressionEnabled {
+		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
 			// Apexes which are supposed to be installed in builtin dirs(/system, etc)
 			// don't need hashtree for activation. Therefore, by removing hashtree from
 			// apex bundle (filesystem image in it, to be specific), we can save storage.
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 52b1689..7297926 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -15,11 +15,14 @@
 package apex
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/java"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // Contains tests for platform_bootclasspath logic from java/platform_bootclasspath.go that requires
@@ -30,6 +33,139 @@
 	PrepareForTestWithApexBuildComponents,
 )
 
+func TestPlatformBootclasspath_Fragments(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithMyapex,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
+		java.FixtureConfigureBootJars("myapex:bar"),
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "myapex",
+						module:"bar-fragment",
+					},
+				],
+				hidden_api: {
+					unsupported: [
+							"unsupported.txt",
+					],
+					removed: [
+							"removed.txt",
+					],
+					max_target_r_low_priority: [
+							"max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"max-target-q.txt",
+					],
+					max_target_p: [
+							"max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"blocked.txt",
+					],
+					unsupported_packages: [
+							"unsupported-packages.txt",
+					],
+				},
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: [
+					"bar-fragment",
+				],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "bar-fragment",
+				contents: ["bar"],
+				apex_available: ["myapex"],
+				api: {
+					stub_libs: ["foo"],
+				},
+				hidden_api: {
+					unsupported: [
+							"bar-unsupported.txt",
+					],
+					removed: [
+							"bar-removed.txt",
+					],
+					max_target_r_low_priority: [
+							"bar-max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"bar-max-target-q.txt",
+					],
+					max_target_p: [
+							"bar-max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"bar-max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"bar-blocked.txt",
+					],
+					unsupported_packages: [
+							"bar-unsupported-packages.txt",
+					],
+				},
+			}
+
+			java_library {
+				name: "bar",
+				apex_available: ["myapex"],
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+				permitted_packages: ["bar"],
+			}
+
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java"],
+				public: {
+					enabled: true,
+				},
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	pbcp := result.Module("platform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, java.MonolithicHiddenAPIInfoProvider).(java.MonolithicHiddenAPIInfo)
+
+	for _, category := range java.HiddenAPIFlagFileCategories {
+		name := category.PropertyName
+		message := fmt.Sprintf("category %s", name)
+		filename := strings.ReplaceAll(name, "_", "-")
+		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
+		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
+	}
+
+	android.AssertPathsRelativeToTopEquals(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/stub-flags.csv"}, info.StubFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
+	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/index.csv"}, info.IndexPaths)
+	android.AssertPathsRelativeToTopEquals(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/all-flags.csv"}, info.AllFlagsPaths)
+}
+
 func TestPlatformBootclasspathDependencies(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
@@ -174,6 +310,141 @@
 	})
 }
 
+// TestPlatformBootclasspath_AlwaysUsePrebuiltSdks verifies that the build does not fail when
+// AlwaysUsePrebuiltSdk() returns true. The structure of the modules in this test matches what
+// currently exists in some places in the Android build but it is not the intended structure. It is
+// in fact an invalid structure that should cause build failures. However, fixing that structure
+// will take too long so in the meantime this tests the workarounds to avoid build breakages.
+//
+// The main issues with this structure are:
+// 1. There is no prebuilt_bootclasspath_fragment referencing the "foo" java_sdk_library_import.
+// 2. There is no prebuilt_apex/apex_set which makes the dex implementation jar available to the
+//    prebuilt_bootclasspath_fragment and the "foo" java_sdk_library_import.
+//
+// Together these cause the following symptoms:
+// 1. The "foo" java_sdk_library_import does not have a dex implementation jar.
+// 2. The "foo" java_sdk_library_import does not have a myapex variant.
+//
+// TODO(b/179354495): Fix the structure in this test once the main Android build has been fixed.
+func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithMyapex,
+		// Configure two libraries, the first is a java_sdk_library whose prebuilt will be used because
+		// of AlwaysUsePrebuiltsSdk() but does not have an appropriate apex variant and does not provide
+		// a boot dex jar. The second is a normal library that is unaffected. The order matters because
+		// if the dependency on myapex:foo is filtered out because of either of those conditions then
+		// the dependencies resolved by the platform_bootclasspath will not match the configured list
+		// and so will fail the test.
+		java.FixtureConfigureUpdatableBootJars("myapex:foo", "myapex:bar"),
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
+		}),
+		java.FixtureWithPrebuiltApis(map[string][]string{
+			"current": {},
+			"30":      {"foo"},
+		}),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspath-fragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["bar"],
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
+		}
+
+		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
+		// because AlwaysUsePrebuiltSdks() is true.
+		java_sdk_library_import {
+			name: "foo",
+			prefer: false,
+			shared_library: false,
+			public: {
+				jars: ["sdk_library/public/foo-stubs.jar"],
+				stub_srcs: ["sdk_library/public/foo_stub_sources"],
+				current_api: "sdk_library/public/foo.txt",
+				removed_api: "sdk_library/public/foo-removed.txt",
+				sdk_version: "current",
+			},
+			apex_available: ["myapex"],
+		}
+
+		// This always depends on the source foo module, its dependencies are not affected by the
+		// AlwaysUsePrebuiltSdks().
+		bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo", "bar",
+			],
+		}
+
+		platform_bootclasspath {
+			name: "myplatform-bootclasspath",
+		}
+`,
+	)
+
+	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
+		// The configured contents of BootJars.
+		"platform:prebuilt_foo", // Note: This is the platform not myapex variant.
+		"myapex:bar",
+	})
+
+	// Make sure that the myplatform-bootclasspath has the correct dependencies.
+	CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
+		// The following are stubs.
+		"platform:prebuilt_sdk_public_current_android",
+		"platform:prebuilt_sdk_system_current_android",
+		"platform:prebuilt_sdk_test_current_android",
+
+		// Not a prebuilt as no prebuilt existed when it was added.
+		"platform:legacy.core.platform.api.stubs",
+
+		// Needed for generating the boot image.
+		`platform:dex2oatd`,
+
+		// The platform_bootclasspath intentionally adds dependencies on both source and prebuilt
+		// modules when available as it does not know which one will be preferred.
+		//
+		// The source module has an APEX variant but the prebuilt does not.
+		"myapex:foo",
+		"platform:prebuilt_foo",
+
+		// Only a source module exists.
+		"myapex:bar",
+	})
+}
+
 // 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
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 9d632a9..4e89883 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -47,21 +47,47 @@
 }
 
 type prebuiltCommon struct {
+	android.ModuleBase
 	prebuilt android.Prebuilt
 
 	// Properties common to both prebuilt_apex and apex_set.
-	prebuiltCommonProperties prebuiltCommonProperties
+	prebuiltCommonProperties *PrebuiltCommonProperties
+
+	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
+
+	hostRequired        []string
+	postInstallCommands []string
 }
 
 type sanitizedPrebuilt interface {
 	hasSanitizedSource(sanitizer string) bool
 }
 
-type prebuiltCommonProperties struct {
+type PrebuiltCommonProperties struct {
 	SelectedApexProperties
 
 	ForceDisable bool `blueprint:"mutated"`
 
+	// whether the extracted apex file is installable.
+	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
+
 	// List of java libraries that are embedded inside this prebuilt APEX bundle and for which this
 	// APEX bundle will create an APEX variant and provide dex implementation jars for use by
 	// dexpreopt and boot jars package check.
@@ -72,6 +98,14 @@
 	Exported_bootclasspath_fragments []string
 }
 
+// initPrebuiltCommon initializes the prebuiltCommon structure and performs initialization of the
+// module that is common to Prebuilt and ApexSet.
+func (p *prebuiltCommon) initPrebuiltCommon(module android.Module, properties *PrebuiltCommonProperties) {
+	p.prebuiltCommonProperties = properties
+	android.InitSingleSourcePrebuiltModule(module.(android.PrebuiltInterface), properties, "Selected_apex")
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+}
+
 func (p *prebuiltCommon) Prebuilt() *android.Prebuilt {
 	return &p.prebuilt
 }
@@ -105,6 +139,46 @@
 	return false
 }
 
+func (p *prebuiltCommon) InstallFilename() string {
+	return proptools.StringDefault(p.prebuiltCommonProperties.Filename, p.BaseModuleName()+imageApexSuffix)
+}
+
+func (p *prebuiltCommon) Name() string {
+	return p.prebuilt.Name(p.ModuleBase.Name())
+}
+
+func (p *prebuiltCommon) Overrides() []string {
+	return p.prebuiltCommonProperties.Overrides
+}
+
+func (p *prebuiltCommon) installable() bool {
+	return proptools.BoolDefault(p.prebuiltCommonProperties.Installable, true)
+}
+
+func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{
+		{
+			Class:         "ETC",
+			OutputFile:    android.OptionalPathForPath(p.outputApex),
+			Include:       "$(BUILD_PREBUILT)",
+			Host_required: p.hostRequired,
+			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+					entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
+					entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
+					entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
+					entries.AddStrings("LOCAL_OVERRIDES_MODULES", p.prebuiltCommonProperties.Overrides...)
+					postInstallCommands := append([]string{}, p.postInstallCommands...)
+					postInstallCommands = append(postInstallCommands, p.compatSymlinks...)
+					if len(postInstallCommands) > 0 {
+						entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(postInstallCommands, " && "))
+					}
+				},
+			},
+		},
+	}
+}
+
 // prebuiltApexModuleCreator defines the methods that need to be implemented by prebuilt_apex and
 // apex_set in order to create the modules needed to provide access to the prebuilt .apex file.
 type prebuiltApexModuleCreator interface {
@@ -228,9 +302,11 @@
 	})
 
 	// Create an ApexInfo for the prebuilt_apex.
+	apexVariationName := android.RemoveOptionalPrebuiltPrefix(mctx.ModuleName())
 	apexInfo := android.ApexInfo{
-		ApexVariationName: android.RemoveOptionalPrebuiltPrefix(mctx.ModuleName()),
-		InApexes:          []string{mctx.ModuleName()},
+		ApexVariationName: apexVariationName,
+		InApexVariants:    []string{apexVariationName},
+		InApexModules:     []string{apexVariationName},
 		ApexContents:      []*android.ApexContents{apexContents},
 		ForPrebuiltApex:   true,
 	}
@@ -270,19 +346,11 @@
 }
 
 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
+	inputApex android.Path
 }
 
 type ApexFileProperties struct {
@@ -347,27 +415,13 @@
 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
+	PrebuiltCommonProperties
 }
 
 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 "":
@@ -377,20 +431,11 @@
 	}
 }
 
-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.
 func PrebuiltFactory() android.Module {
 	module := &Prebuilt{}
-	module.AddProperties(&module.properties, &module.prebuiltCommonProperties)
-	android.InitSingleSourcePrebuiltModule(module, &module.prebuiltCommonProperties, "Selected_apex")
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	module.AddProperties(&module.properties)
+	module.initPrebuiltCommon(module, &module.properties.PrebuiltCommonProperties)
 
 	return module
 }
@@ -413,7 +458,7 @@
 // A deapexer module is only needed when the prebuilt apex specifies one or more modules in either
 // the `exported_java_libs` or `exported_bootclasspath_fragments` properties as that indicates that
 // the listed modules need access to files from within the prebuilt .apex file.
-func createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string, properties *prebuiltCommonProperties) {
+func createDeapexerModuleIfNeeded(ctx android.TopDownMutatorContext, deapexerName string, apexFileSource string, properties *PrebuiltCommonProperties) {
 	// Only create the deapexer module if it is needed.
 	if len(properties.Exported_java_libs)+len(properties.Exported_bootclasspath_fragments) == 0 {
 		return
@@ -554,7 +599,7 @@
 	createApexSelectorModule(ctx, apexSelectorModuleName, &p.properties.ApexFileProperties)
 
 	apexFileSource := ":" + apexSelectorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, &p.prebuiltCommonProperties)
+	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, p.prebuiltCommonProperties)
 
 	// Add a source reference to retrieve the selected apex from the selector module.
 	p.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -597,30 +642,11 @@
 	// in case that prebuilt_apex replaces source apex (using prefer: prop)
 	p.compatSymlinks = makeCompatSymlinks(p.BaseModuleName(), ctx)
 	// or that prebuilt_apex overrides other apexes (using overrides: prop)
-	for _, overridden := range p.properties.Overrides {
+	for _, overridden := range p.prebuiltCommonProperties.Overrides {
 		p.compatSymlinks = append(p.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
 	}
 }
 
-func (p *Prebuilt) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
-		Class:      "ETC",
-		OutputFile: android.OptionalPathForPath(p.inputApex),
-		Include:    "$(BUILD_PREBUILT)",
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDir.ToMakePath().String())
-				entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
-				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
-				entries.AddStrings("LOCAL_OVERRIDES_MODULES", p.properties.Overrides...)
-				if len(p.compatSymlinks) > 0 {
-					entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(p.compatSymlinks, " && "))
-				}
-			},
-		},
-	}}
-}
-
 // 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 {
@@ -665,21 +691,9 @@
 }
 
 type ApexSet struct {
-	android.ModuleBase
 	prebuiltCommon
 
 	properties ApexSetProperties
-
-	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
-
-	hostRequired        []string
-	postInstallCommands []string
 }
 
 type ApexExtractorProperties struct {
@@ -729,19 +743,7 @@
 type ApexSetProperties struct {
 	ApexExtractorProperties
 
-	// whether the extracted apex file installable.
-	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
+	PrebuiltCommonProperties
 }
 
 func (a *ApexSet) hasSanitizedSource(sanitizer string) bool {
@@ -755,29 +757,11 @@
 	return false
 }
 
-func (a *ApexSet) installable() bool {
-	return a.properties.Installable == nil || proptools.Bool(a.properties.Installable)
-}
-
-func (a *ApexSet) InstallFilename() string {
-	return proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+imageApexSuffix)
-}
-
-func (a *ApexSet) Name() string {
-	return a.prebuiltCommon.prebuilt.Name(a.ModuleBase.Name())
-}
-
-func (a *ApexSet) Overrides() []string {
-	return a.properties.Overrides
-}
-
 // 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.prebuiltCommonProperties)
-
-	android.InitSingleSourcePrebuiltModule(module, &module.prebuiltCommonProperties, "Selected_apex")
-	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	module.AddProperties(&module.properties)
+	module.initPrebuiltCommon(module, &module.properties.PrebuiltCommonProperties)
 
 	return module
 }
@@ -815,7 +799,7 @@
 	createApexExtractorModule(ctx, apexExtractorModuleName, &a.properties.ApexExtractorProperties)
 
 	apexFileSource := ":" + apexExtractorModuleName
-	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, &a.prebuiltCommonProperties)
+	createDeapexerModuleIfNeeded(ctx, deapexerModuleName(baseModuleName), apexFileSource, a.prebuiltCommonProperties)
 
 	// After passing the arch specific src properties to the creating the apex selector module
 	a.prebuiltCommonProperties.Selected_apex = proptools.StringPtr(apexFileSource)
@@ -858,7 +842,7 @@
 	// in case that apex_set replaces source apex (using prefer: prop)
 	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
 	// or that apex_set overrides other apexes (using overrides: prop)
-	for _, overridden := range a.properties.Overrides {
+	for _, overridden := range a.prebuiltCommonProperties.Overrides {
 		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
 	}
 
@@ -881,25 +865,3 @@
 func (*systemExtContext) SystemExtSpecific() bool {
 	return true
 }
-
-func (a *ApexSet) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
-		Class:         "ETC",
-		OutputFile:    android.OptionalPathForPath(a.outputApex),
-		Include:       "$(BUILD_PREBUILT)",
-		Host_required: a.hostRequired,
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", a.installDir.ToMakePath().String())
-				entries.SetString("LOCAL_MODULE_STEM", a.installFilename)
-				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !a.installable())
-				entries.AddStrings("LOCAL_OVERRIDES_MODULES", a.properties.Overrides...)
-				postInstallCommands := append([]string{}, a.postInstallCommands...)
-				postInstallCommands = append(postInstallCommands, a.compatSymlinks...)
-				if len(postInstallCommands) > 0 {
-					entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(postInstallCommands, " && "))
-				}
-			},
-		},
-	}}
-}
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
new file mode 100644
index 0000000..95b6e23
--- /dev/null
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -0,0 +1,78 @@
+// 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/java"
+)
+
+var prepareForTestWithSystemserverclasspathFragment = android.GroupFixturePreparers(
+	java.PrepareForTestWithDexpreopt,
+	PrepareForTestWithApexBuildComponents,
+)
+
+func TestSystemserverclasspathFragmentContents(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithSystemserverclasspathFragment,
+		prepareForTestWithMyapex,
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			systemserverclasspath_fragments: [
+				"mysystemserverclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
+		systemserverclasspath_fragment {
+			name: "mysystemserverclasspathfragment",
+			contents: [
+				"foo",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+	`)
+
+	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		"etc/classpaths/systemserverclasspath.pb",
+		"javalib/foo.jar",
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		`myapex.key`,
+		`mysystemserverclasspathfragment`,
+	})
+}
diff --git a/bazel/Android.bp b/bazel/Android.bp
index b7c185a..b68d65b 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -7,6 +7,7 @@
     pkgPath: "android/soong/bazel",
     srcs: [
         "aquery.go",
+        "configurability.go",
         "constants.go",
         "properties.go",
     ],
diff --git a/bazel/aquery.go b/bazel/aquery.go
index 555f1dc..8741afb 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -73,31 +73,39 @@
 // BuildStatement contains information to register a build statement corresponding (one to one)
 // with a Bazel action from Bazel's action graph.
 type BuildStatement struct {
-	Command     string
-	Depfile     *string
-	OutputPaths []string
-	InputPaths  []string
-	Env         []KeyValuePair
-	Mnemonic    string
+	Command      string
+	Depfile      *string
+	OutputPaths  []string
+	InputPaths   []string
+	SymlinkPaths []string
+	Env          []KeyValuePair
+	Mnemonic     string
 }
 
-// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
-// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
-// aquery invocation).
-func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
-	buildStatements := []BuildStatement{}
+// A helper type for aquery processing which facilitates retrieval of path IDs from their
+// less readable Bazel structures (depset and path fragment).
+type aqueryArtifactHandler struct {
+	// Maps middleman artifact Id to input artifact depset ID.
+	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
+	// if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
+	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
+	// that action instead.
+	middlemanIdToDepsetIds map[int][]int
+	// Maps depset Id to depset struct.
+	depsetIdToDepset map[int]depSetOfFiles
+	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
+	// may be an expensive operation.
+	depsetIdToArtifactIdsCache map[int][]int
+	// Maps artifact Id to fully expanded path.
+	artifactIdToPath map[int]string
+}
 
-	var aqueryResult actionGraphContainer
-	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
-
-	if err != nil {
-		return nil, err
-	}
-
+func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
 	pathFragments := map[int]pathFragment{}
 	for _, pathFragment := range aqueryResult.PathFragments {
 		pathFragments[pathFragment.Id] = pathFragment
 	}
+
 	artifactIdToPath := map[int]string{}
 	for _, artifact := range aqueryResult.Artifacts {
 		artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
@@ -112,22 +120,87 @@
 		depsetIdToDepset[depset.Id] = depset
 	}
 
-	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
-	// may be an expensive operation.
-	depsetIdToArtifactIdsCache := map[int][]int{}
-
 	// Do a pass through all actions to identify which artifacts are middleman artifacts.
-	// These will be omitted from the inputs of other actions.
-	// TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
-	// headers may cause build failures.
-	middlemanArtifactIds := map[int]bool{}
+	middlemanIdToDepsetIds := map[int][]int{}
 	for _, actionEntry := range aqueryResult.Actions {
 		if actionEntry.Mnemonic == "Middleman" {
 			for _, outputId := range actionEntry.OutputIds {
-				middlemanArtifactIds[outputId] = true
+				middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
 			}
 		}
 	}
+	return &aqueryArtifactHandler{
+		middlemanIdToDepsetIds:     middlemanIdToDepsetIds,
+		depsetIdToDepset:           depsetIdToDepset,
+		depsetIdToArtifactIdsCache: map[int][]int{},
+		artifactIdToPath:           artifactIdToPath,
+	}, nil
+}
+
+func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
+	inputPaths := []string{}
+
+	for _, inputDepSetId := range depsetIds {
+		inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
+		if err != nil {
+			return nil, err
+		}
+		for _, inputId := range inputArtifacts {
+			if middlemanInputDepsetIds, isMiddlemanArtifact := a.middlemanIdToDepsetIds[inputId]; isMiddlemanArtifact {
+				// Add all inputs from middleman actions which created middleman artifacts which are
+				// in the inputs for this action.
+				swappedInputPaths, err := a.getInputPaths(middlemanInputDepsetIds)
+				if err != nil {
+					return nil, err
+				}
+				inputPaths = append(inputPaths, swappedInputPaths...)
+			} else {
+				inputPath, exists := a.artifactIdToPath[inputId]
+				if !exists {
+					return nil, fmt.Errorf("undefined input artifactId %d", inputId)
+				}
+				inputPaths = append(inputPaths, inputPath)
+			}
+		}
+	}
+	return inputPaths, nil
+}
+
+func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
+	if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
+		return result, nil
+	}
+	if depset, exists := a.depsetIdToDepset[depsetId]; exists {
+		result := depset.DirectArtifactIds
+		for _, childId := range depset.TransitiveDepSetIds {
+			childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
+			if err != nil {
+				return nil, err
+			}
+			result = append(result, childArtifactIds...)
+		}
+		a.depsetIdToArtifactIdsCache[depsetId] = result
+		return result, nil
+	} else {
+		return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+	}
+}
+
+// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
+// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
+// aquery invocation).
+func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
+	buildStatements := []BuildStatement{}
+
+	var aqueryResult actionGraphContainer
+	err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
+	if err != nil {
+		return nil, err
+	}
+	aqueryHandler, err := newAqueryHandler(aqueryResult)
+	if err != nil {
+		return nil, err
+	}
 
 	for _, actionEntry := range aqueryResult.Actions {
 		if shouldSkipAction(actionEntry) {
@@ -136,7 +209,7 @@
 		outputPaths := []string{}
 		var depfile *string
 		for _, outputId := range actionEntry.OutputIds {
-			outputPath, exists := artifactIdToPath[outputId]
+			outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
 			if !exists {
 				return nil, fmt.Errorf("undefined outputId %d", outputId)
 			}
@@ -151,35 +224,32 @@
 				outputPaths = append(outputPaths, outputPath)
 			}
 		}
-		inputPaths := []string{}
-		for _, inputDepSetId := range actionEntry.InputDepSetIds {
-			inputArtifacts, err :=
-				artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, inputDepSetId)
-			if err != nil {
-				return nil, err
-			}
-			for _, inputId := range inputArtifacts {
-				if _, isMiddlemanArtifact := middlemanArtifactIds[inputId]; isMiddlemanArtifact {
-					// Omit middleman artifacts.
-					continue
-				}
-				inputPath, exists := artifactIdToPath[inputId]
-				if !exists {
-					return nil, fmt.Errorf("undefined input artifactId %d", inputId)
-				}
-				inputPaths = append(inputPaths, inputPath)
-			}
+		inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
+		if err != nil {
+			return nil, err
 		}
+
 		buildStatement := BuildStatement{
 			Command:     strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
 			Depfile:     depfile,
 			OutputPaths: outputPaths,
 			InputPaths:  inputPaths,
 			Env:         actionEntry.EnvironmentVariables,
-			Mnemonic:    actionEntry.Mnemonic}
-		if len(actionEntry.Arguments) < 1 {
+			Mnemonic:    actionEntry.Mnemonic,
+		}
+
+		if isSymlinkAction(actionEntry) {
+			if len(inputPaths) != 1 || len(outputPaths) != 1 {
+				return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
+			}
+			out := outputPaths[0]
+			outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
+			out = proptools.ShellEscapeIncludingSpaces(out)
+			in := proptools.ShellEscapeIncludingSpaces(inputPaths[0])
+			buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in)
+			buildStatement.SymlinkPaths = outputPaths[:]
+		} else if len(actionEntry.Arguments) < 1 {
 			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
-			continue
 		}
 		buildStatements = append(buildStatements, buildStatement)
 	}
@@ -187,13 +257,18 @@
 	return buildStatements, nil
 }
 
+func isSymlinkAction(a action) bool {
+	return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
+}
+
 func shouldSkipAction(a action) bool {
-	// TODO(b/180945121): Handle symlink actions.
-	if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" {
+	// TODO(b/180945121): Handle complex symlink actions.
+	if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
 		return true
 	}
-	// TODO(b/180945500): Handle middleman actions; without proper handling, depending on generated
-	// headers may cause build failures.
+	// Middleman actions are not handled like other actions; they are handled separately as a
+	// preparatory step so that their inputs may be relayed to actions depending on middleman
+	// artifacts.
 	if a.Mnemonic == "Middleman" {
 		return true
 	}
@@ -209,28 +284,6 @@
 	return false
 }
 
-func artifactIdsFromDepsetId(depsetIdToDepset map[int]depSetOfFiles,
-	depsetIdToArtifactIdsCache map[int][]int, depsetId int) ([]int, error) {
-	if result, exists := depsetIdToArtifactIdsCache[depsetId]; exists {
-		return result, nil
-	}
-	if depset, exists := depsetIdToDepset[depsetId]; exists {
-		result := depset.DirectArtifactIds
-		for _, childId := range depset.TransitiveDepSetIds {
-			childArtifactIds, err :=
-				artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, childId)
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, childArtifactIds...)
-		}
-		depsetIdToArtifactIdsCache[depsetId] = result
-		return result, nil
-	} else {
-		return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
-	}
-}
-
 func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
 	labels := []string{}
 	currId := id
@@ -241,6 +294,9 @@
 			return "", fmt.Errorf("undefined path fragment id %d", currId)
 		}
 		labels = append([]string{currFragment.Label}, labels...)
+		if currId == currFragment.ParentId {
+			return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
+		}
 		currId = currFragment.ParentId
 	}
 	return filepath.Join(labels...), nil
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index fa8810f..43e4155 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -718,17 +718,316 @@
 	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
 }
 
+func TestMiddlemenAction(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }, {
+    "id": 4,
+    "pathFragmentId": 4
+  }, {
+    "id": 5,
+    "pathFragmentId": 5
+  }, {
+    "id": 6,
+    "pathFragmentId": 6
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "middleinput_one"
+  }, {
+    "id": 2,
+    "label": "middleinput_two"
+  }, {
+    "id": 3,
+    "label": "middleman_artifact"
+  }, {
+    "id": 4,
+    "label": "maininput_one"
+  }, {
+    "id": 5,
+    "label": "maininput_two"
+  }, {
+    "id": 6,
+    "label": "output"
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1, 2]
+  }, {
+    "id": 2,
+    "directArtifactIds": [3, 4, 5]
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Middleman",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [1],
+    "outputIds": [3],
+    "primaryOutputId": 3
+  }, {
+    "targetId": 2,
+    "actionKey": "y",
+    "mnemonic": "Main action",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [2],
+    "outputIds": [6],
+    "primaryOutputId": 6
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+	if expected := 1; len(actual) != expected {
+		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
+	}
+
+	bs := actual[0]
+	expectedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
+	if !reflect.DeepEqual(bs.InputPaths, expectedInputs) {
+		t.Errorf("Expected main action inputs %q, but got %q", expectedInputs, bs.InputPaths)
+	}
+
+	expectedOutputs := []string{"output"}
+	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
+		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
+	}
+}
+
+func TestSimpleSymlink(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 3
+  }, {
+    "id": 2,
+    "pathFragmentId": 5
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "file_subdir",
+    "parentId": 1
+  }, {
+    "id": 3,
+    "label": "file",
+    "parentId": 2
+  }, {
+    "id": 4,
+    "label": "symlink_subdir",
+    "parentId": 1
+  }, {
+    "id": 5,
+    "label": "symlink",
+    "parentId": 4
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "mkdir -p one/symlink_subdir && " +
+				"rm -f one/symlink_subdir/symlink && " +
+				"ln -rsf one/file_subdir/file one/symlink_subdir/symlink",
+			InputPaths:   []string{"one/file_subdir/file"},
+			OutputPaths:  []string{"one/symlink_subdir/symlink"},
+			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
+			Mnemonic:     "Symlink",
+		},
+	}
+	assertBuildStatements(t, actual, expectedBuildStatements)
+}
+
+func TestSymlinkQuotesPaths(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 3
+  }, {
+    "id": 2,
+    "pathFragmentId": 5
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "SolibSymlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "file subdir",
+    "parentId": 1
+  }, {
+    "id": 3,
+    "label": "file",
+    "parentId": 2
+  }, {
+    "id": 4,
+    "label": "symlink subdir",
+    "parentId": 1
+  }, {
+    "id": 5,
+    "label": "symlink",
+    "parentId": 4
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+
+	expectedBuildStatements := []BuildStatement{
+		BuildStatement{
+			Command: "mkdir -p 'one/symlink subdir' && " +
+				"rm -f 'one/symlink subdir/symlink' && " +
+				"ln -rsf 'one/file subdir/file' 'one/symlink subdir/symlink'",
+			InputPaths:   []string{"one/file subdir/file"},
+			OutputPaths:  []string{"one/symlink subdir/symlink"},
+			SymlinkPaths: []string{"one/symlink subdir/symlink"},
+			Mnemonic:     "SolibSymlink",
+		},
+	}
+	assertBuildStatements(t, actual, expectedBuildStatements)
+}
+
+func TestSymlinkMultipleInputs(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [3],
+    "primaryOutputId": 3
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1,2]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "file"
+  }, {
+    "id": 2,
+    "label": "other_file"
+  }, {
+    "id": 3,
+    "label": "symlink"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`)
+}
+
+func TestSymlinkMultipleOutputs(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "Symlink",
+    "inputDepSetIds": [1],
+    "outputIds": [2,3],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "file"
+  }, {
+    "id": 2,
+    "label": "symlink"
+  }, {
+    "id": 3,
+    "label": "other_symlink"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
+}
+
 func assertError(t *testing.T, err error, expected string) {
+	t.Helper()
 	if err == nil {
 		t.Errorf("expected error '%s', but got no error", expected)
 	} else if err.Error() != expected {
-		t.Errorf("expected error '%s', but got: %s", expected, err.Error())
+		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
 	}
 }
 
 // Asserts that the given actual build statements match the given expected build statements.
 // Build statement equivalence is determined using buildStatementEquals.
 func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
+	t.Helper()
 	if len(expected) != len(actual) {
 		t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
 			len(expected), len(actual), expected, actual)
@@ -765,6 +1064,12 @@
 	if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) {
 		return false
 	}
+	if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) {
+		return false
+	}
+	if first.Depfile != second.Depfile {
+		return false
+	}
 	return true
 }
 
diff --git a/bazel/configurability.go b/bazel/configurability.go
new file mode 100644
index 0000000..282c606
--- /dev/null
+++ b/bazel/configurability.go
@@ -0,0 +1,213 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bazel
+
+import (
+	"fmt"
+	"strings"
+)
+
+const (
+	// ArchType names in arch.go
+	archArm    = "arm"
+	archArm64  = "arm64"
+	archX86    = "x86"
+	archX86_64 = "x86_64"
+
+	// OsType names in arch.go
+	osAndroid     = "android"
+	osDarwin      = "darwin"
+	osFuchsia     = "fuchsia"
+	osLinux       = "linux_glibc"
+	osLinuxBionic = "linux_bionic"
+	osWindows     = "windows"
+
+	// Targets in arch.go
+	osArchAndroidArm        = "android_arm"
+	osArchAndroidArm64      = "android_arm64"
+	osArchAndroidX86        = "android_x86"
+	osArchAndroidX86_64     = "android_x86_64"
+	osArchDarwinX86_64      = "darwin_x86_64"
+	osArchFuchsiaArm64      = "fuchsia_arm64"
+	osArchFuchsiaX86_64     = "fuchsia_x86_64"
+	osArchLinuxX86          = "linux_glibc_x86"
+	osArchLinuxX86_64       = "linux_glibc_x86_64"
+	osArchLinuxBionicArm64  = "linux_bionic_arm64"
+	osArchLinuxBionicX86_64 = "linux_bionic_x86_64"
+	osArchWindowsX86        = "windows_x86"
+	osArchWindowsX86_64     = "windows_x86_64"
+
+	// This is the string representation of the default condition wherever a
+	// configurable attribute is used in a select statement, i.e.
+	// //conditions:default for Bazel.
+	//
+	// This is consistently named "conditions_default" to mirror the Soong
+	// config variable default key in an Android.bp file, although there's no
+	// integration with Soong config variables (yet).
+	conditionsDefault = "conditions_default"
+
+	ConditionsDefaultSelectKey = "//conditions:default"
+
+	productVariableBazelPackage = "//build/bazel/product_variables"
+)
+
+var (
+	// These are the list of OSes and architectures with a Bazel config_setting
+	// and constraint value equivalent. These exist in arch.go, but the android
+	// package depends on the bazel package, so a cyclic dependency prevents
+	// using those variables here.
+
+	// A map of architectures to the Bazel label of the constraint_value
+	// for the @platforms//cpu:cpu constraint_setting
+	platformArchMap = map[string]string{
+		archArm:           "//build/bazel/platforms/arch:arm",
+		archArm64:         "//build/bazel/platforms/arch:arm64",
+		archX86:           "//build/bazel/platforms/arch:x86",
+		archX86_64:        "//build/bazel/platforms/arch:x86_64",
+		conditionsDefault: ConditionsDefaultSelectKey, // The default condition of as arch select map.
+	}
+
+	// A map of target operating systems to the Bazel label of the
+	// constraint_value for the @platforms//os:os constraint_setting
+	platformOsMap = map[string]string{
+		osAndroid:         "//build/bazel/platforms/os:android",
+		osDarwin:          "//build/bazel/platforms/os:darwin",
+		osFuchsia:         "//build/bazel/platforms/os:fuchsia",
+		osLinux:           "//build/bazel/platforms/os:linux",
+		osLinuxBionic:     "//build/bazel/platforms/os:linux_bionic",
+		osWindows:         "//build/bazel/platforms/os:windows",
+		conditionsDefault: ConditionsDefaultSelectKey, // The default condition of an os select map.
+	}
+
+	platformOsArchMap = map[string]string{
+		osArchAndroidArm:        "//build/bazel/platforms/os_arch:android_arm",
+		osArchAndroidArm64:      "//build/bazel/platforms/os_arch:android_arm64",
+		osArchAndroidX86:        "//build/bazel/platforms/os_arch:android_x86",
+		osArchAndroidX86_64:     "//build/bazel/platforms/os_arch:android_x86_64",
+		osArchDarwinX86_64:      "//build/bazel/platforms/os_arch:darwin_x86_64",
+		osArchFuchsiaArm64:      "//build/bazel/platforms/os_arch:fuchsia_arm64",
+		osArchFuchsiaX86_64:     "//build/bazel/platforms/os_arch:fuchsia_x86_64",
+		osArchLinuxX86:          "//build/bazel/platforms/os_arch:linux_glibc_x86",
+		osArchLinuxX86_64:       "//build/bazel/platforms/os_arch:linux_glibc_x86_64",
+		osArchLinuxBionicArm64:  "//build/bazel/platforms/os_arch:linux_bionic_arm64",
+		osArchLinuxBionicX86_64: "//build/bazel/platforms/os_arch:linux_bionic_x86_64",
+		osArchWindowsX86:        "//build/bazel/platforms/os_arch:windows_x86",
+		osArchWindowsX86_64:     "//build/bazel/platforms/os_arch:windows_x86_64",
+		conditionsDefault:       ConditionsDefaultSelectKey, // The default condition of an os select map.
+	}
+)
+
+// basic configuration types
+type configurationType int
+
+const (
+	noConfig configurationType = iota
+	arch
+	os
+	osArch
+	productVariables
+)
+
+func (ct configurationType) String() string {
+	return map[configurationType]string{
+		noConfig:         "no_config",
+		arch:             "arch",
+		os:               "os",
+		osArch:           "arch_os",
+		productVariables: "product_variables",
+	}[ct]
+}
+
+func (ct configurationType) validateConfig(config string) {
+	switch ct {
+	case noConfig:
+		if config != "" {
+			panic(fmt.Errorf("Cannot specify config with %s, but got %s", ct, config))
+		}
+	case arch:
+		if _, ok := platformArchMap[config]; !ok {
+			panic(fmt.Errorf("Unknown arch: %s", config))
+		}
+	case os:
+		if _, ok := platformOsMap[config]; !ok {
+			panic(fmt.Errorf("Unknown os: %s", config))
+		}
+	case osArch:
+		if _, ok := platformOsArchMap[config]; !ok {
+			panic(fmt.Errorf("Unknown os+arch: %s", config))
+		}
+	case productVariables:
+		// do nothing
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
+	}
+}
+
+// SelectKey returns the Bazel select key for a given configurationType and config string.
+func (ct configurationType) SelectKey(config string) string {
+	ct.validateConfig(config)
+	switch ct {
+	case noConfig:
+		panic(fmt.Errorf("SelectKey is unnecessary for noConfig ConfigurationType "))
+	case arch:
+		return platformArchMap[config]
+	case os:
+		return platformOsMap[config]
+	case osArch:
+		return platformOsArchMap[config]
+	case productVariables:
+		if config == conditionsDefault {
+			return ConditionsDefaultSelectKey
+		}
+		return fmt.Sprintf("%s:%s", productVariableBazelPackage, strings.ToLower(config))
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationType %d", ct))
+	}
+}
+
+var (
+	// Indicating there is no configuration axis
+	NoConfigAxis = ConfigurationAxis{configurationType: noConfig}
+	// An axis for architecture-specific configurations
+	ArchConfigurationAxis = ConfigurationAxis{configurationType: arch}
+	// An axis for os-specific configurations
+	OsConfigurationAxis = ConfigurationAxis{configurationType: os}
+	// An axis for arch+os-specific configurations
+	OsArchConfigurationAxis = ConfigurationAxis{configurationType: osArch}
+)
+
+// ProductVariableConfigurationAxis returns an axis for the given product variable
+func ProductVariableConfigurationAxis(variable string) ConfigurationAxis {
+	return ConfigurationAxis{
+		configurationType: productVariables,
+		subType:           variable,
+	}
+}
+
+// ConfigurationAxis is an independent axis for configuration, there should be no overlap between
+// elements within an axis.
+type ConfigurationAxis struct {
+	configurationType
+	// some configuration types (e.g. productVariables) have multiple independent axes, subType helps
+	// distinguish between them without needing to list all 17 product variables.
+	subType string
+}
+
+func (ca *ConfigurationAxis) less(other ConfigurationAxis) bool {
+	if ca.configurationType < other.configurationType {
+		return true
+	}
+	return ca.subType < other.subType
+}
diff --git a/bazel/constants.go b/bazel/constants.go
index 15c75cf..6beb496 100644
--- a/bazel/constants.go
+++ b/bazel/constants.go
@@ -18,6 +18,10 @@
 
 	// Run bazel as a ninja executer
 	BazelNinjaExecRunName = RunName("bazel-ninja-exec")
+
+	SoongInjectionDirName = "soong_injection"
+
+	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT"
 )
 
 // String returns the name of the run.
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index c30abeb..52c6c2f 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -16,6 +16,14 @@
 	CcStaticLibraryFiles []string
 	Includes             []string
 	SystemIncludes       []string
+	// Archives owned by the current target (not by its dependencies). These will
+	// be a subset of OutputFiles. (or static libraries, this will be equal to OutputFiles,
+	// but general cc_library will also have dynamic libraries in output files).
+	RootStaticArchives []string
+	// Dynamic libraries (.so files) created by the current target. These will
+	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
+	// but general cc_library will also have dynamic libraries in output files).
+	RootDynamicLibraries []string
 }
 
 type getOutputFilesRequestType struct{}
@@ -70,6 +78,7 @@
 
 ccObjectFiles = []
 staticLibraries = []
+rootStaticArchives = []
 linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
 
 for linker_input in linker_inputs:
@@ -78,6 +87,15 @@
       ccObjectFiles += [object.path]
     if library.static_library:
       staticLibraries.append(library.static_library.path)
+      if linker_input.owner == target.label:
+        rootStaticArchives.append(library.static_library.path)
+
+rootDynamicLibraries = []
+
+if "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo" in providers(target):
+  shared_info = providers(target)["@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"]
+  for lib in shared_info.linker_input.libraries:
+    rootDynamicLibraries += [lib.dynamic_library.path]
 
 returns = [
   outputFiles,
@@ -85,6 +103,8 @@
   ccObjectFiles,
   includes,
   system_includes,
+  rootStaticArchives,
+  rootDynamicLibraries
 ]
 
 return "|".join([", ".join(r) for r in returns])`
@@ -98,7 +118,7 @@
 	var ccObjects []string
 
 	splitString := strings.Split(rawString, "|")
-	if expectedLen := 5; len(splitString) != expectedLen {
+	if expectedLen := 7; len(splitString) != expectedLen {
 		return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString)
 	}
 	outputFilesString := splitString[0]
@@ -109,12 +129,16 @@
 	ccObjects = splitOrEmpty(ccObjectsString, ", ")
 	includes := splitOrEmpty(splitString[3], ", ")
 	systemIncludes := splitOrEmpty(splitString[4], ", ")
+	rootStaticArchives := splitOrEmpty(splitString[5], ", ")
+	rootDynamicLibraries := splitOrEmpty(splitString[6], ", ")
 	return CcInfo{
 		OutputFiles:          outputFiles,
 		CcObjectFiles:        ccObjects,
 		CcStaticLibraryFiles: ccStaticLibraries,
 		Includes:             includes,
 		SystemIncludes:       systemIncludes,
+		RootStaticArchives:   rootStaticArchives,
+		RootDynamicLibraries: rootDynamicLibraries,
 	}, nil
 }
 
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 6369999..035544e 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -46,48 +46,54 @@
 	}{
 		{
 			description: "no result",
-			input:       "||||",
+			input:       "||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{},
 				CcObjectFiles:        []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
 			},
 		},
 		{
 			description: "only output",
-			input:       "test||||",
+			input:       "test||||||",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"test"},
 				CcObjectFiles:        []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
 			},
 		},
 		{
 			description: "all items set",
-			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir",
+			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|rootstaticarchive1|rootdynamiclibrary1",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
 				CcObjectFiles:        []string{"object1", "object2"},
 				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
 				Includes:             []string{".", "dir/subdir"},
 				SystemIncludes:       []string{"system/dir", "system/other/dir"},
+				RootStaticArchives:   []string{"rootstaticarchive1"},
+				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
 			},
 		},
 		{
 			description:          "too few result splits",
 			input:                "|",
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 5, []string{"", ""}),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, []string{"", ""}),
 		},
 		{
 			description:          "too many result splits",
 			input:                strings.Repeat("|", 8),
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 5, make([]string, 9)),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 7, make([]string, 9)),
 		},
 	}
 	for _, tc := range testCases {
diff --git a/bazel/properties.go b/bazel/properties.go
index a71b12b..7093d6c 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -64,6 +64,17 @@
 	Excludes []Label
 }
 
+func (ll *LabelList) IsNil() bool {
+	return ll.Includes == nil && ll.Excludes == nil
+}
+
+func (ll *LabelList) deepCopy() LabelList {
+	return LabelList{
+		Includes: ll.Includes[:],
+		Excludes: ll.Excludes[:],
+	}
+}
+
 // uniqueParentDirectories returns a list of the unique parent directories for
 // all files in ll.Includes.
 func (ll *LabelList) uniqueParentDirectories() []string {
@@ -105,7 +116,27 @@
 	return uniqueLabels
 }
 
-func UniqueBazelLabelList(originalLabelList LabelList) LabelList {
+func FirstUniqueBazelLabels(originalLabels []Label) []Label {
+	var labels []Label
+	found := make(map[Label]bool, len(originalLabels))
+	for _, l := range originalLabels {
+		if _, ok := found[l]; ok {
+			continue
+		}
+		labels = append(labels, l)
+		found[l] = true
+	}
+	return labels
+}
+
+func FirstUniqueBazelLabelList(originalLabelList LabelList) LabelList {
+	var uniqueLabelList LabelList
+	uniqueLabelList.Includes = FirstUniqueBazelLabels(originalLabelList.Includes)
+	uniqueLabelList.Excludes = FirstUniqueBazelLabels(originalLabelList.Excludes)
+	return uniqueLabelList
+}
+
+func UniqueSortedBazelLabelList(originalLabelList LabelList) LabelList {
 	var uniqueLabelList LabelList
 	uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
 	uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
@@ -136,6 +167,76 @@
 	return strings
 }
 
+// Map a function over all labels in a LabelList.
+func MapLabelList(mapOver LabelList, mapFn func(string) string) LabelList {
+	var includes []Label
+	for _, inc := range mapOver.Includes {
+		mappedLabel := Label{Label: mapFn(inc.Label), OriginalModuleName: inc.OriginalModuleName}
+		includes = append(includes, mappedLabel)
+	}
+	// mapFn is not applied over excludes, but they are propagated as-is.
+	return LabelList{Includes: includes, Excludes: mapOver.Excludes}
+}
+
+// Map a function over all Labels in a LabelListAttribute
+func MapLabelListAttribute(mapOver LabelListAttribute, mapFn func(string) string) LabelListAttribute {
+	var result LabelListAttribute
+
+	result.Value = MapLabelList(mapOver.Value, mapFn)
+
+	for axis, configToLabels := range mapOver.ConfigurableValues {
+		for config, value := range configToLabels {
+			result.SetSelectValue(axis, config, MapLabelList(value, mapFn))
+		}
+	}
+
+	return result
+}
+
+// Return all needles in a given haystack, where needleFn is true for needles.
+func FilterLabelList(haystack LabelList, needleFn func(string) bool) LabelList {
+	var includes []Label
+	for _, inc := range haystack.Includes {
+		if needleFn(inc.Label) {
+			includes = append(includes, inc)
+		}
+	}
+	// needleFn is not applied over excludes, but they are propagated as-is.
+	return LabelList{Includes: includes, Excludes: haystack.Excludes}
+}
+
+// Return all needles in a given haystack, where needleFn is true for needles.
+func FilterLabelListAttribute(haystack LabelListAttribute, needleFn func(string) bool) LabelListAttribute {
+	result := MakeLabelListAttribute(FilterLabelList(haystack.Value, needleFn))
+
+	for config, selects := range haystack.ConfigurableValues {
+		newSelects := make(labelListSelectValues, len(selects))
+		for k, v := range selects {
+			newSelects[k] = FilterLabelList(v, needleFn)
+		}
+		result.ConfigurableValues[config] = newSelects
+	}
+
+	return result
+}
+
+// Subtract needle from haystack
+func SubtractBazelLabelListAttribute(haystack LabelListAttribute, needle LabelListAttribute) LabelListAttribute {
+	result := MakeLabelListAttribute(SubtractBazelLabelList(haystack.Value, needle.Value))
+
+	for config, selects := range haystack.ConfigurableValues {
+		newSelects := make(labelListSelectValues, len(selects))
+		needleSelects := needle.ConfigurableValues[config]
+
+		for k, v := range selects {
+			newSelects[k] = SubtractBazelLabelList(v, needleSelects[k])
+		}
+		result.ConfigurableValues[config] = newSelects
+	}
+
+	return result
+}
+
 // Subtract needle from haystack
 func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
 	// This is really a set
@@ -160,6 +261,14 @@
 	return labels
 }
 
+// Appends two LabelLists, returning the combined list.
+func AppendBazelLabelLists(a LabelList, b LabelList) LabelList {
+	var result LabelList
+	result.Includes = append(a.Includes, b.Includes...)
+	result.Excludes = append(a.Excludes, b.Excludes...)
+	return result
+}
+
 // Subtract needle from haystack
 func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
 	var result LabelList
@@ -169,247 +278,312 @@
 	return result
 }
 
-const (
-	// ArchType names in arch.go
-	ARCH_ARM    = "arm"
-	ARCH_ARM64  = "arm64"
-	ARCH_X86    = "x86"
-	ARCH_X86_64 = "x86_64"
-
-	// OsType names in arch.go
-	OS_ANDROID      = "android"
-	OS_DARWIN       = "darwin"
-	OS_FUCHSIA      = "fuchsia"
-	OS_LINUX        = "linux_glibc"
-	OS_LINUX_BIONIC = "linux_bionic"
-	OS_WINDOWS      = "windows"
-
-	// This is the string representation of the default condition wherever a
-	// configurable attribute is used in a select statement, i.e.
-	// //conditions:default for Bazel.
-	//
-	// This is consistently named "conditions_default" to mirror the Soong
-	// config variable default key in an Android.bp file, although there's no
-	// integration with Soong config variables (yet).
-	CONDITIONS_DEFAULT = "conditions_default"
-)
-
-var (
-	// These are the list of OSes and architectures with a Bazel config_setting
-	// and constraint value equivalent. These exist in arch.go, but the android
-	// package depends on the bazel package, so a cyclic dependency prevents
-	// using those variables here.
-
-	// A map of architectures to the Bazel label of the constraint_value
-	// for the @platforms//cpu:cpu constraint_setting
-	PlatformArchMap = map[string]string{
-		ARCH_ARM:           "//build/bazel/platforms/arch:arm",
-		ARCH_ARM64:         "//build/bazel/platforms/arch:arm64",
-		ARCH_X86:           "//build/bazel/platforms/arch:x86",
-		ARCH_X86_64:        "//build/bazel/platforms/arch:x86_64",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of as arch select map.
-	}
-
-	// A map of target operating systems to the Bazel label of the
-	// constraint_value for the @platforms//os:os constraint_setting
-	PlatformOsMap = map[string]string{
-		OS_ANDROID:         "//build/bazel/platforms/os:android",
-		OS_DARWIN:          "//build/bazel/platforms/os:darwin",
-		OS_FUCHSIA:         "//build/bazel/platforms/os:fuchsia",
-		OS_LINUX:           "//build/bazel/platforms/os:linux",
-		OS_LINUX_BIONIC:    "//build/bazel/platforms/os:linux_bionic",
-		OS_WINDOWS:         "//build/bazel/platforms/os:windows",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of an os select map.
-	}
-)
-
 type Attribute interface {
 	HasConfigurableValues() bool
 }
 
+type labelSelectValues map[string]*Label
+
+type configurableLabels map[ConfigurationAxis]labelSelectValues
+
+func (cl configurableLabels) setValueForAxis(axis ConfigurationAxis, config string, value *Label) {
+	if cl[axis] == nil {
+		cl[axis] = make(labelSelectValues)
+	}
+	cl[axis][config] = value
+}
+
 // Represents an attribute whose value is a single label
 type LabelAttribute struct {
-	Value  Label
-	X86    Label
-	X86_64 Label
-	Arm    Label
-	Arm64  Label
+	Value *Label
+
+	ConfigurableValues configurableLabels
 }
 
-func (attr *LabelAttribute) GetValueForArch(arch string) Label {
-	switch arch {
-	case ARCH_ARM:
-		return attr.Arm
-	case ARCH_ARM64:
-		return attr.Arm64
-	case ARCH_X86:
-		return attr.X86
-	case ARCH_X86_64:
-		return attr.X86_64
-	case CONDITIONS_DEFAULT:
-		return attr.Value
-	default:
-		panic("Invalid arch type")
-	}
+// HasConfigurableValues returns whether there are configurable values set for this label.
+func (la LabelAttribute) HasConfigurableValues() bool {
+	return len(la.ConfigurableValues) > 0
 }
 
-func (attr *LabelAttribute) SetValueForArch(arch string, value Label) {
-	switch arch {
-	case ARCH_ARM:
-		attr.Arm = value
-	case ARCH_ARM64:
-		attr.Arm64 = value
-	case ARCH_X86:
-		attr.X86 = value
-	case ARCH_X86_64:
-		attr.X86_64 = value
-	default:
-		panic("Invalid arch type")
-	}
+// SetValue sets the base, non-configured value for the Label
+func (la *LabelAttribute) SetValue(value Label) {
+	la.SetSelectValue(NoConfigAxis, "", value)
 }
 
-func (attr LabelAttribute) HasConfigurableValues() bool {
-	return attr.Arm.Label != "" || attr.Arm64.Label != "" || attr.X86.Label != "" || attr.X86_64.Label != ""
-}
-
-// Arch-specific label_list typed Bazel attribute values. This should correspond
-// to the types of architectures supported for compilation in arch.go.
-type labelListArchValues struct {
-	X86    LabelList
-	X86_64 LabelList
-	Arm    LabelList
-	Arm64  LabelList
-	Common LabelList
-
-	ConditionsDefault LabelList
-}
-
-type labelListOsValues struct {
-	Android     LabelList
-	Darwin      LabelList
-	Fuchsia     LabelList
-	Linux       LabelList
-	LinuxBionic LabelList
-	Windows     LabelList
-
-	ConditionsDefault LabelList
-}
-
-// LabelListAttribute is used to represent a list of Bazel labels as an
-// attribute.
-type LabelListAttribute struct {
-	// The non-arch specific attribute label list Value. Required.
-	Value LabelList
-
-	// The arch-specific attribute label list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-arch specific
-	// label list Value.
-	ArchValues labelListArchValues
-
-	// The os-specific attribute label list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-os specific
-	// label list Value.
-	OsValues labelListOsValues
-}
-
-// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
-func MakeLabelListAttribute(value LabelList) LabelListAttribute {
-	return LabelListAttribute{Value: UniqueBazelLabelList(value)}
-}
-
-// Append all values, including os and arch specific ones, from another
-// LabelListAttribute to this LabelListAttribute.
-func (attrs *LabelListAttribute) Append(other LabelListAttribute) {
-	for arch := range PlatformArchMap {
-		this := attrs.GetValueForArch(arch)
-		that := other.GetValueForArch(arch)
-		this.Append(that)
-		attrs.SetValueForArch(arch, this)
-	}
-
-	for os := range PlatformOsMap {
-		this := attrs.GetValueForOS(os)
-		that := other.GetValueForOS(os)
-		this.Append(that)
-		attrs.SetValueForOS(os, this)
-	}
-
-	attrs.Value.Append(other.Value)
-}
-
-// HasArchSpecificValues returns true if the attribute contains
-// architecture-specific label_list values.
-func (attrs LabelListAttribute) HasConfigurableValues() bool {
-	for arch := range PlatformArchMap {
-		if len(attrs.GetValueForArch(arch).Includes) > 0 {
-			return true
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (la *LabelAttribute) SetSelectValue(axis ConfigurationAxis, config string, value Label) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		la.Value = &value
+	case arch, os, osArch, productVariables:
+		if la.ConfigurableValues == nil {
+			la.ConfigurableValues = make(configurableLabels)
 		}
+		la.ConfigurableValues.setValueForAxis(axis, config, &value)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (la *LabelAttribute) SelectValue(axis ConfigurationAxis, config string) Label {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return *la.Value
+	case arch, os, osArch, productVariables:
+		return *la.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(la.ConfigurableValues))
+	for k := range la.ConfigurableValues {
+		keys = append(keys, k)
 	}
 
-	for os := range PlatformOsMap {
-		if len(attrs.GetValueForOS(os).Includes) > 0 {
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
+type configToBools map[string]bool
+
+func (ctb configToBools) setValue(config string, value *bool) {
+	if value == nil {
+		if _, ok := ctb[config]; ok {
+			delete(ctb, config)
+		}
+		return
+	}
+	ctb[config] = *value
+}
+
+type configurableBools map[ConfigurationAxis]configToBools
+
+func (cb configurableBools) setValueForAxis(axis ConfigurationAxis, config string, value *bool) {
+	if cb[axis] == nil {
+		cb[axis] = make(configToBools)
+	}
+	cb[axis].setValue(config, value)
+}
+
+// BoolAttribute represents an attribute whose value is a single bool but may be configurable..
+type BoolAttribute struct {
+	Value *bool
+
+	ConfigurableValues configurableBools
+}
+
+// HasConfigurableValues returns whether there are configurable values for this attribute.
+func (ba BoolAttribute) HasConfigurableValues() bool {
+	return len(ba.ConfigurableValues) > 0
+}
+
+// SetSelectValue sets value for the given axis/config.
+func (ba *BoolAttribute) SetSelectValue(axis ConfigurationAxis, config string, value *bool) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		ba.Value = value
+	case arch, os, osArch, productVariables:
+		if ba.ConfigurableValues == nil {
+			ba.ConfigurableValues = make(configurableBools)
+		}
+		ba.ConfigurableValues.setValueForAxis(axis, config, value)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SelectValue gets the value for the given axis/config.
+func (ba BoolAttribute) SelectValue(axis ConfigurationAxis, config string) *bool {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return ba.Value
+	case arch, os, osArch, productVariables:
+		if v, ok := ba.ConfigurableValues[axis][config]; ok {
+			return &v
+		} else {
+			return nil
+		}
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (ba *BoolAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(ba.ConfigurableValues))
+	for k := range ba.ConfigurableValues {
+		keys = append(keys, k)
+	}
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
+// labelListSelectValues supports config-specific label_list typed Bazel attribute values.
+type labelListSelectValues map[string]LabelList
+
+func (ll labelListSelectValues) appendSelects(other labelListSelectValues) {
+	for k, v := range other {
+		l := ll[k]
+		(&l).Append(v)
+		ll[k] = l
+	}
+}
+
+// HasConfigurableValues returns whether there are configurable values within this set of selects.
+func (ll labelListSelectValues) HasConfigurableValues() bool {
+	for _, v := range ll {
+		if len(v.Includes) > 0 {
 			return true
 		}
 	}
 	return false
 }
 
-func (attrs *LabelListAttribute) archValuePtrs() map[string]*LabelList {
-	return map[string]*LabelList{
-		ARCH_X86:           &attrs.ArchValues.X86,
-		ARCH_X86_64:        &attrs.ArchValues.X86_64,
-		ARCH_ARM:           &attrs.ArchValues.Arm,
-		ARCH_ARM64:         &attrs.ArchValues.Arm64,
-		CONDITIONS_DEFAULT: &attrs.ArchValues.ConditionsDefault,
+// LabelListAttribute is used to represent a list of Bazel labels as an
+// attribute.
+type LabelListAttribute struct {
+	// The non-configured attribute label list Value. Required.
+	Value LabelList
+
+	// The configured attribute label list Values. Optional
+	// a map of independent configurability axes
+	ConfigurableValues configurableLabelLists
+}
+
+type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
+
+func (cll configurableLabelLists) setValueForAxis(axis ConfigurationAxis, config string, list LabelList) {
+	if list.IsNil() {
+		if _, ok := cll[axis][config]; ok {
+			delete(cll[axis], config)
+		}
+		return
+	}
+	if cll[axis] == nil {
+		cll[axis] = make(labelListSelectValues)
+	}
+
+	cll[axis][config] = list
+}
+
+func (cll configurableLabelLists) Append(other configurableLabelLists) {
+	for axis, otherSelects := range other {
+		selects := cll[axis]
+		if selects == nil {
+			selects = make(labelListSelectValues, len(otherSelects))
+		}
+		selects.appendSelects(otherSelects)
+		cll[axis] = selects
 	}
 }
 
-// GetValueForArch returns the label_list attribute value for an architecture.
-func (attrs *LabelListAttribute) GetValueForArch(arch string) LabelList {
-	var v *LabelList
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	return *v
-}
-
-// SetValueForArch sets the label_list attribute value for an architecture.
-func (attrs *LabelListAttribute) SetValueForArch(arch string, value LabelList) {
-	var v *LabelList
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	*v = value
-}
-
-func (attrs *LabelListAttribute) osValuePtrs() map[string]*LabelList {
-	return map[string]*LabelList{
-		OS_ANDROID:         &attrs.OsValues.Android,
-		OS_DARWIN:          &attrs.OsValues.Darwin,
-		OS_FUCHSIA:         &attrs.OsValues.Fuchsia,
-		OS_LINUX:           &attrs.OsValues.Linux,
-		OS_LINUX_BIONIC:    &attrs.OsValues.LinuxBionic,
-		OS_WINDOWS:         &attrs.OsValues.Windows,
-		CONDITIONS_DEFAULT: &attrs.OsValues.ConditionsDefault,
+// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
+func MakeLabelListAttribute(value LabelList) LabelListAttribute {
+	return LabelListAttribute{
+		Value:              value,
+		ConfigurableValues: make(configurableLabelLists),
 	}
 }
 
-// GetValueForOS returns the label_list attribute value for an OS target.
-func (attrs *LabelListAttribute) GetValueForOS(os string) LabelList {
-	var v *LabelList
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
-	}
-	return *v
+func (lla *LabelListAttribute) SetValue(list LabelList) {
+	lla.SetSelectValue(NoConfigAxis, "", list)
 }
 
-// SetValueForArch sets the label_list attribute value for an OS target.
-func (attrs *LabelListAttribute) SetValueForOS(os string, value LabelList) {
-	var v *LabelList
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (lla *LabelListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list LabelList) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		lla.Value = list
+	case arch, os, osArch, productVariables:
+		if lla.ConfigurableValues == nil {
+			lla.ConfigurableValues = make(configurableLabelLists)
+		}
+		lla.ConfigurableValues.setValueForAxis(axis, config, list)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
 	}
-	*v = value
+}
+
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (lla *LabelListAttribute) SelectValue(axis ConfigurationAxis, config string) LabelList {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return lla.Value
+	case arch, os, osArch, productVariables:
+		return lla.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(lla.ConfigurableValues))
+	for k := range lla.ConfigurableValues {
+		keys = append(keys, k)
+	}
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
+// Append all values, including os and arch specific ones, from another
+// LabelListAttribute to this LabelListAttribute.
+func (lla *LabelListAttribute) Append(other LabelListAttribute) {
+	lla.Value.Append(other.Value)
+	if lla.ConfigurableValues == nil {
+		lla.ConfigurableValues = make(configurableLabelLists)
+	}
+	lla.ConfigurableValues.Append(other.ConfigurableValues)
+}
+
+// HasConfigurableValues returns true if the attribute contains axis-specific label list values.
+func (lla LabelListAttribute) HasConfigurableValues() bool {
+	return len(lla.ConfigurableValues) > 0
+}
+
+// ResolveExcludes handles excludes across the various axes, ensuring that items are removed from
+// the base value and included in default values as appropriate.
+func (lla *LabelListAttribute) ResolveExcludes() {
+	for axis, configToLabels := range lla.ConfigurableValues {
+		baseLabels := lla.Value.deepCopy()
+		for config, val := range configToLabels {
+			// Exclude config-specific excludes from base value
+			lla.Value = SubtractBazelLabelList(lla.Value, LabelList{Includes: val.Excludes})
+
+			// add base values to config specific to add labels excluded by others in this axis
+			// then remove all config-specific excludes
+			allLabels := baseLabels.deepCopy()
+			allLabels.Append(val)
+			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: val.Excludes})
+		}
+
+		// After going through all configs, delete the duplicates in the config
+		// values that are already in the base Value.
+		for config, val := range configToLabels {
+			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(val, lla.Value)
+		}
+
+		// Now that the Value list is finalized for this axis, compare it with the original
+		// list, and put the difference into the default condition for the axis.
+		lla.ConfigurableValues[axis][conditionsDefault] = SubtractBazelLabelList(baseLabels, lla.Value)
+
+		// if everything ends up without includes, just delete the axis
+		if !lla.ConfigurableValues[axis].HasConfigurableValues() {
+			delete(lla.ConfigurableValues, axis)
+		}
+	}
 }
 
 // StringListAttribute corresponds to the string_list Bazel attribute type with
@@ -418,139 +592,110 @@
 	// The base value of the string list attribute.
 	Value []string
 
-	// The arch-specific attribute string list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-arch specific
-	// label list Value.
-	ArchValues stringListArchValues
-
-	// The os-specific attribute string list values. Optional. If used, these
-	// are generated in a select statement and appended to the non-os specific
-	// label list Value.
-	OsValues stringListOsValues
+	// The configured attribute label list Values. Optional
+	// a map of independent configurability axes
+	ConfigurableValues configurableStringLists
 }
 
-// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
-func MakeStringListAttribute(value []string) StringListAttribute {
-	// NOTE: These strings are not necessarily unique or sorted.
-	return StringListAttribute{Value: value}
-}
+type configurableStringLists map[ConfigurationAxis]stringListSelectValues
 
-// Arch-specific string_list typed Bazel attribute values. This should correspond
-// to the types of architectures supported for compilation in arch.go.
-type stringListArchValues struct {
-	X86    []string
-	X86_64 []string
-	Arm    []string
-	Arm64  []string
-	Common []string
-
-	ConditionsDefault []string
-}
-
-type stringListOsValues struct {
-	Android     []string
-	Darwin      []string
-	Fuchsia     []string
-	Linux       []string
-	LinuxBionic []string
-	Windows     []string
-
-	ConditionsDefault []string
-}
-
-// HasConfigurableValues returns true if the attribute contains
-// architecture-specific string_list values.
-func (attrs StringListAttribute) HasConfigurableValues() bool {
-	for arch := range PlatformArchMap {
-		if len(attrs.GetValueForArch(arch)) > 0 {
-			return true
+func (csl configurableStringLists) Append(other configurableStringLists) {
+	for axis, otherSelects := range other {
+		selects := csl[axis]
+		if selects == nil {
+			selects = make(stringListSelectValues, len(otherSelects))
 		}
+		selects.appendSelects(otherSelects)
+		csl[axis] = selects
 	}
+}
 
-	for os := range PlatformOsMap {
-		if len(attrs.GetValueForOS(os)) > 0 {
+func (csl configurableStringLists) setValueForAxis(axis ConfigurationAxis, config string, list []string) {
+	if csl[axis] == nil {
+		csl[axis] = make(stringListSelectValues)
+	}
+	csl[axis][config] = list
+}
+
+type stringListSelectValues map[string][]string
+
+func (sl stringListSelectValues) appendSelects(other stringListSelectValues) {
+	for k, v := range other {
+		sl[k] = append(sl[k], v...)
+	}
+}
+
+func (sl stringListSelectValues) hasConfigurableValues(other stringListSelectValues) bool {
+	for _, val := range sl {
+		if len(val) > 0 {
 			return true
 		}
 	}
 	return false
 }
 
-func (attrs *StringListAttribute) archValuePtrs() map[string]*[]string {
-	return map[string]*[]string{
-		ARCH_X86:           &attrs.ArchValues.X86,
-		ARCH_X86_64:        &attrs.ArchValues.X86_64,
-		ARCH_ARM:           &attrs.ArchValues.Arm,
-		ARCH_ARM64:         &attrs.ArchValues.Arm64,
-		CONDITIONS_DEFAULT: &attrs.ArchValues.ConditionsDefault,
+// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
+func MakeStringListAttribute(value []string) StringListAttribute {
+	// NOTE: These strings are not necessarily unique or sorted.
+	return StringListAttribute{
+		Value:              value,
+		ConfigurableValues: make(configurableStringLists),
 	}
 }
 
-// GetValueForArch returns the string_list attribute value for an architecture.
-func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
-	var v *[]string
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	return *v
-}
-
-// SetValueForArch sets the string_list attribute value for an architecture.
-func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
-	var v *[]string
-	if v = attrs.archValuePtrs()[arch]; v == nil {
-		panic(fmt.Errorf("Unknown arch: %s", arch))
-	}
-	*v = value
-}
-
-func (attrs *StringListAttribute) osValuePtrs() map[string]*[]string {
-	return map[string]*[]string{
-		OS_ANDROID:         &attrs.OsValues.Android,
-		OS_DARWIN:          &attrs.OsValues.Darwin,
-		OS_FUCHSIA:         &attrs.OsValues.Fuchsia,
-		OS_LINUX:           &attrs.OsValues.Linux,
-		OS_LINUX_BIONIC:    &attrs.OsValues.LinuxBionic,
-		OS_WINDOWS:         &attrs.OsValues.Windows,
-		CONDITIONS_DEFAULT: &attrs.OsValues.ConditionsDefault,
-	}
-}
-
-// GetValueForOS returns the string_list attribute value for an OS target.
-func (attrs *StringListAttribute) GetValueForOS(os string) []string {
-	var v *[]string
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
-	}
-	return *v
-}
-
-// SetValueForArch sets the string_list attribute value for an OS target.
-func (attrs *StringListAttribute) SetValueForOS(os string, value []string) {
-	var v *[]string
-	if v = attrs.osValuePtrs()[os]; v == nil {
-		panic(fmt.Errorf("Unknown os: %s", os))
-	}
-	*v = value
+// HasConfigurableValues returns true if the attribute contains axis-specific string_list values.
+func (sla StringListAttribute) HasConfigurableValues() bool {
+	return len(sla.ConfigurableValues) > 0
 }
 
 // Append appends all values, including os and arch specific ones, from another
 // StringListAttribute to this StringListAttribute
-func (attrs *StringListAttribute) Append(other StringListAttribute) {
-	for arch := range PlatformArchMap {
-		this := attrs.GetValueForArch(arch)
-		that := other.GetValueForArch(arch)
-		this = append(this, that...)
-		attrs.SetValueForArch(arch, this)
+func (sla *StringListAttribute) Append(other StringListAttribute) {
+	sla.Value = append(sla.Value, other.Value...)
+	if sla.ConfigurableValues == nil {
+		sla.ConfigurableValues = make(configurableStringLists)
+	}
+	sla.ConfigurableValues.Append(other.ConfigurableValues)
+}
+
+// SetSelectValue set a value for a bazel select for the given axis, config and value.
+func (sla *StringListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list []string) {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		sla.Value = list
+	case arch, os, osArch, productVariables:
+		if sla.ConfigurableValues == nil {
+			sla.ConfigurableValues = make(configurableStringLists)
+		}
+		sla.ConfigurableValues.setValueForAxis(axis, config, list)
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SelectValue gets a value for a bazel select for the given axis and config.
+func (sla *StringListAttribute) SelectValue(axis ConfigurationAxis, config string) []string {
+	axis.validateConfig(config)
+	switch axis.configurationType {
+	case noConfig:
+		return sla.Value
+	case arch, os, osArch, productVariables:
+		return sla.ConfigurableValues[axis][config]
+	default:
+		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
+	}
+}
+
+// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
+func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(sla.ConfigurableValues))
+	for k := range sla.ConfigurableValues {
+		keys = append(keys, k)
 	}
 
-	for os := range PlatformOsMap {
-		this := attrs.GetValueForOS(os)
-		that := other.GetValueForOS(os)
-		this = append(this, that...)
-		attrs.SetValueForOS(os, this)
-	}
-
-	attrs.Value = append(attrs.Value, other.Value...)
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
 }
 
 // TryVariableSubstitution, replace string substitution formatting within each string in slice with
@@ -569,6 +714,6 @@
 // TryVariableSubstitution, replace string substitution formatting within s with Starlark
 // string.format compatible tag for productVariable.
 func TryVariableSubstitution(s string, productVariable string) (string, bool) {
-	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "{"+productVariable+"}")
+	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
 	return sub, s != sub
 }
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 229a4aa..9464245 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -122,7 +122,7 @@
 		}
 	}
 }
-func TestUniqueBazelLabelList(t *testing.T) {
+func TestFirstUniqueBazelLabelList(t *testing.T) {
 	testCases := []struct {
 		originalLabelList       LabelList
 		expectedUniqueLabelList LabelList
@@ -157,9 +157,139 @@
 		},
 	}
 	for _, tc := range testCases {
-		actualUniqueLabelList := UniqueBazelLabelList(tc.originalLabelList)
+		actualUniqueLabelList := FirstUniqueBazelLabelList(tc.originalLabelList)
 		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
 			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
 		}
 	}
 }
+
+func TestUniqueSortedBazelLabelList(t *testing.T) {
+	testCases := []struct {
+		originalLabelList       LabelList
+		expectedUniqueLabelList LabelList
+	}{
+		{
+			originalLabelList: LabelList{
+				Includes: []Label{
+					{Label: "c"},
+					{Label: "a"},
+					{Label: "a"},
+					{Label: "b"},
+				},
+				Excludes: []Label{
+					{Label: "y"},
+					{Label: "z"},
+					{Label: "x"},
+					{Label: "x"},
+				},
+			},
+			expectedUniqueLabelList: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualUniqueLabelList := UniqueSortedBazelLabelList(tc.originalLabelList)
+		if !reflect.DeepEqual(tc.expectedUniqueLabelList, actualUniqueLabelList) {
+			t.Fatalf("Expected %v, got %v", tc.expectedUniqueLabelList, actualUniqueLabelList)
+		}
+	}
+}
+
+func makeLabels(labels ...string) []Label {
+	var ret []Label
+	for _, l := range labels {
+		ret = append(ret, Label{Label: l})
+	}
+	return ret
+}
+
+func makeLabelList(includes, excludes []string) LabelList {
+	return LabelList{
+		Includes: makeLabels(includes...),
+		Excludes: makeLabels(excludes...),
+	}
+}
+
+func TestResolveExcludes(t *testing.T) {
+	attr := LabelListAttribute{
+		Value: makeLabelList(
+			[]string{
+				"all_include",
+				"arm_exclude",
+				"android_exclude",
+			},
+			[]string{"all_exclude"},
+		),
+		ConfigurableValues: configurableLabelLists{
+			ArchConfigurationAxis: labelListSelectValues{
+				"arm": makeLabelList([]string{}, []string{"arm_exclude"}),
+				"x86": makeLabelList([]string{"x86_include"}, []string{}),
+			},
+			OsConfigurationAxis: labelListSelectValues{
+				"android": makeLabelList([]string{}, []string{"android_exclude"}),
+				"linux":   makeLabelList([]string{"linux_include"}, []string{}),
+			},
+			OsArchConfigurationAxis: labelListSelectValues{
+				"linux_x86": makeLabelList([]string{"linux_x86_include"}, []string{}),
+			},
+			ProductVariableConfigurationAxis("a"): labelListSelectValues{
+				"a": makeLabelList([]string{}, []string{"not_in_value"}),
+			},
+		},
+	}
+
+	attr.ResolveExcludes()
+
+	expectedBaseIncludes := []Label{Label{Label: "all_include"}}
+	if !reflect.DeepEqual(expectedBaseIncludes, attr.Value.Includes) {
+		t.Errorf("Expected Value includes %q, got %q", attr.Value.Includes, expectedBaseIncludes)
+	}
+	var nilLabels []Label
+	expectedConfiguredIncludes := map[ConfigurationAxis]map[string][]Label{
+		ArchConfigurationAxis: map[string][]Label{
+			"arm":                nilLabels,
+			"x86":                makeLabels("arm_exclude", "x86_include"),
+			"conditions_default": makeLabels("arm_exclude"),
+		},
+		OsConfigurationAxis: map[string][]Label{
+			"android":            nilLabels,
+			"linux":              makeLabels("android_exclude", "linux_include"),
+			"conditions_default": makeLabels("android_exclude"),
+		},
+		OsArchConfigurationAxis: map[string][]Label{
+			"linux_x86":          makeLabels("linux_x86_include"),
+			"conditions_default": nilLabels,
+		},
+	}
+	for _, axis := range attr.SortedConfigurationAxes() {
+		if _, ok := expectedConfiguredIncludes[axis]; !ok {
+			t.Errorf("Found unexpected axis %s", axis)
+			continue
+		}
+		expectedForAxis := expectedConfiguredIncludes[axis]
+		gotForAxis := attr.ConfigurableValues[axis]
+		if len(expectedForAxis) != len(gotForAxis) {
+			t.Errorf("Expected %d configs for %s, got %d: %s", len(expectedForAxis), axis, len(gotForAxis), gotForAxis)
+		}
+		for config, value := range gotForAxis {
+			if expected, ok := expectedForAxis[config]; ok {
+				if !reflect.DeepEqual(expected, value.Includes) {
+					t.Errorf("For %s, expected: %#v, got %#v", axis, expected, value.Includes)
+				}
+			} else {
+				t.Errorf("Got unexpected config %q for %s", config, axis)
+			}
+		}
+	}
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index abd79f5..3abbc4c 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -20,6 +20,7 @@
         "soong-android",
         "soong-bazel",
         "soong-cc",
+        "soong-cc-config",
         "soong-genrule",
         "soong-python",
         "soong-sh",
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index cf6994f..ee36982 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/bazel"
 	"fmt"
 	"os"
 )
@@ -24,22 +25,16 @@
 // writing .bzl files that are equivalent to Android.bp files that are capable
 // of being built with Bazel.
 func Codegen(ctx *CodegenContext) CodegenMetrics {
-	outputDir := android.PathForOutput(ctx, "bp2build")
-	android.RemoveAllOutputDir(outputDir)
+	// This directory stores BUILD files that could be eventually checked-in.
+	bp2buildDir := android.PathForOutput(ctx, "bp2build")
+	android.RemoveAllOutputDir(bp2buildDir)
 
 	buildToTargets, metrics := GenerateBazelTargets(ctx, true)
+	bp2buildFiles := CreateBazelFiles(nil, buildToTargets, ctx.mode)
+	writeFiles(ctx, bp2buildDir, bp2buildFiles)
 
-	filesToWrite := CreateBazelFiles(nil, buildToTargets, ctx.mode)
-
-	generatedBuildFiles := []string{}
-	for _, f := range filesToWrite {
-		p := getOrCreateOutputDir(outputDir, ctx, f.Dir).Join(ctx, f.Basename)
-		if err := writeFile(ctx, p, f.Contents); err != nil {
-			panic(fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err))
-		}
-		// if these generated files are modified, regenerate on next run.
-		generatedBuildFiles = append(generatedBuildFiles, p.String())
-	}
+	soongInjectionDir := android.PathForOutput(ctx, bazel.SoongInjectionDirName)
+	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionFiles())
 
 	return metrics
 }
@@ -51,6 +46,16 @@
 	return dirPath
 }
 
+// writeFiles materializes a list of BazelFile rooted at outputDir.
+func writeFiles(ctx android.PathContext, outputDir android.OutputPath, files []BazelFile) {
+	for _, f := range files {
+		p := getOrCreateOutputDir(outputDir, ctx, f.Dir).Join(ctx, f.Basename)
+		if err := writeFile(ctx, p, f.Contents); err != nil {
+			panic(fmt.Errorf("Failed to write %q (dir %q) due to %q", f.Basename, f.Dir, err))
+		}
+	}
+}
+
 func writeFile(ctx android.PathContext, pathToFile android.OutputPath, content string) error {
 	// These files are made editable to allow users to modify and iterate on them
 	// in the source tree.
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index bddc524..a1e0424 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -19,6 +19,7 @@
 	"android/soong/bazel"
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -34,6 +35,7 @@
 	content         string
 	ruleClass       string
 	bzlLoadLocation string
+	handcrafted     bool
 }
 
 // IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
@@ -45,12 +47,47 @@
 // BazelTargets is a typedef for a slice of BazelTarget objects.
 type BazelTargets []BazelTarget
 
+// HasHandcraftedTargetsreturns true if a set of bazel targets contain
+// handcrafted ones.
+func (targets BazelTargets) hasHandcraftedTargets() bool {
+	for _, target := range targets {
+		if target.handcrafted {
+			return true
+		}
+	}
+	return false
+}
+
+// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
+func (targets BazelTargets) sort() {
+	sort.Slice(targets, func(i, j int) bool {
+		if targets[i].handcrafted != targets[j].handcrafted {
+			// Handcrafted targets will be generated after the bp2build generated targets.
+			return targets[j].handcrafted
+		}
+		// This will cover all bp2build generated targets.
+		return targets[i].name < targets[j].name
+	})
+}
+
 // String returns the string representation of BazelTargets, without load
 // statements (use LoadStatements for that), since the targets are usually not
 // adjacent to the load statements at the top of the BUILD file.
 func (targets BazelTargets) String() string {
 	var res string
 	for i, target := range targets {
+		// There is only at most 1 handcrafted "target", because its contents
+		// represent the entire BUILD file content from the tree. See
+		// build_conversion.go#getHandcraftedBuildContent for more information.
+		//
+		// Add a header to make it easy to debug where the handcrafted targets
+		// are in a generated BUILD file.
+		if target.handcrafted {
+			res += "# -----------------------------\n"
+			res += "# Section: Handcrafted targets. \n"
+			res += "# -----------------------------\n\n"
+		}
+
 		res += target.content
 		if i != len(targets)-1 {
 			res += "\n\n"
@@ -267,7 +304,8 @@
 	}
 	// TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
 	return BazelTarget{
-		content: c,
+		content:     c,
+		handcrafted: true,
 	}, nil
 }
 
@@ -294,6 +332,7 @@
 			targetName,
 			attributes,
 		),
+		handcrafted: false,
 	}
 }
 
@@ -406,7 +445,7 @@
 		return prettyPrint(propertyValue.Elem(), indent)
 	case reflect.Slice:
 		if propertyValue.Len() == 0 {
-			return "", nil
+			return "[]", nil
 		}
 
 		if propertyValue.Len() == 1 {
@@ -449,6 +488,9 @@
 		ret = "{\n"
 		// Sort and print the struct props by the key.
 		structProps := extractStructProperties(propertyValue, indent)
+		if len(structProps) == 0 {
+			return "", nil
+		}
 		for _, k := range android.SortedStringKeys(structProps) {
 			ret += makeIndent(indent + 1)
 			ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
@@ -528,7 +570,14 @@
 		} else {
 			return true
 		}
+	// Always print bools, if you want a bool attribute to be able to take the default value, use a
+	// bool pointer instead
+	case reflect.Bool:
+		return false
 	default:
+		if !value.IsValid() {
+			return true
+		}
 		zeroValue := reflect.Zero(value.Type())
 		result := value.Interface() == zeroValue.Interface()
 		return result
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 63a6c2e..33609af 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -23,12 +23,14 @@
 
 func TestGenerateSoongModuleTargets(t *testing.T) {
 	testCases := []struct {
+		description         string
 		bp                  string
 		expectedBazelTarget string
 	}{
 		{
+			description: "only name",
 			bp: `custom { name: "foo" }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -36,14 +38,16 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
 )`,
 		},
 		{
+			description: "handles bool",
 			bp: `custom {
-	name: "foo",
-	ramdisk: true,
+  name: "foo",
+  bool_prop: true,
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -51,15 +55,16 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
-    ramdisk = True,
+    bool_prop = True,
 )`,
 		},
 		{
+			description: "string escaping",
 			bp: `custom {
-	name: "foo",
-	owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
+  name: "foo",
+  owner: "a_string_with\"quotes\"_and_\\backslashes\\\\",
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -67,15 +72,17 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     owner = "a_string_with\"quotes\"_and_\\backslashes\\\\",
 )`,
 		},
 		{
+			description: "single item string list",
 			bp: `custom {
-	name: "foo",
-	required: ["bar"],
+  name: "foo",
+  required: ["bar"],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -83,15 +90,17 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     required = ["bar"],
 )`,
 		},
 		{
+			description: "list of strings",
 			bp: `custom {
-	name: "foo",
-	target_required: ["qux", "bazqux"],
+  name: "foo",
+  target_required: ["qux", "bazqux"],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -99,6 +108,7 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     target_required = [
         "qux",
         "bazqux",
@@ -106,18 +116,19 @@
 )`,
 		},
 		{
+			description: "dist/dists",
 			bp: `custom {
-	name: "foo",
-	dist: {
-		targets: ["goal_foo"],
-		tag: ".foo",
-	},
-	dists: [{
-		targets: ["goal_bar"],
-		tag: ".bar",
-	}],
+  name: "foo",
+  dist: {
+    targets: ["goal_foo"],
+    tag: ".foo",
+  },
+  dists: [{
+    targets: ["goal_bar"],
+    tag: ".bar",
+  }],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -125,6 +136,7 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = False,
     dist = {
         "tag": ".foo",
         "targets": ["goal_foo"],
@@ -136,20 +148,21 @@
 )`,
 		},
 		{
+			description: "put it together",
 			bp: `custom {
-	name: "foo",
-	required: ["bar"],
-	target_required: ["qux", "bazqux"],
-	ramdisk: true,
-	owner: "custom_owner",
-	dists: [
-		{
-			tag: ".tag",
-			targets: ["my_goal"],
-		},
-	],
+  name: "foo",
+  required: ["bar"],
+  target_required: ["qux", "bazqux"],
+  bool_prop: true,
+  owner: "custom_owner",
+  dists: [
+    {
+      tag: ".tag",
+      targets: ["my_goal"],
+    },
+  ],
 }
-		`,
+    `,
 			expectedBazelTarget: `soong_module(
     name = "foo",
     soong_module_name = "foo",
@@ -157,12 +170,12 @@
     soong_module_variant = "",
     soong_module_deps = [
     ],
+    bool_prop = True,
     dists = [{
         "tag": ".tag",
         "targets": ["my_goal"],
     }],
     owner = "custom_owner",
-    ramdisk = True,
     required = ["bar"],
     target_required = [
         "qux",
@@ -174,31 +187,33 @@
 
 	dir := "."
 	for _, testCase := range testCases {
-		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
-		ctx := android.NewTestContext(config)
+		t.Run(testCase.description, func(t *testing.T) {
+			config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+			ctx := android.NewTestContext(config)
 
-		ctx.RegisterModuleType("custom", customModuleFactory)
-		ctx.Register()
+			ctx.RegisterModuleType("custom", customModuleFactory)
+			ctx.Register()
 
-		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		android.FailIfErrored(t, errs)
-		_, errs = ctx.PrepareBuildActions(config)
-		android.FailIfErrored(t, errs)
+			_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+			android.FailIfErrored(t, errs)
+			_, errs = ctx.PrepareBuildActions(config)
+			android.FailIfErrored(t, errs)
 
-		codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
-			t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
-		}
+			codegenCtx := NewCodegenContext(config, *ctx.Context, QueryView)
+			bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
+			if actualCount, expectedCount := len(bazelTargets), 1; actualCount != expectedCount {
+				t.Fatalf("Expected %d bazel target, got %d", expectedCount, actualCount)
+			}
 
-		actualBazelTarget := bazelTargets[0]
-		if actualBazelTarget.content != testCase.expectedBazelTarget {
-			t.Errorf(
-				"Expected generated Bazel target to be '%s', got '%s'",
-				testCase.expectedBazelTarget,
-				actualBazelTarget.content,
-			)
-		}
+			actualBazelTarget := bazelTargets[0]
+			if actualBazelTarget.content != testCase.expectedBazelTarget {
+				t.Errorf(
+					"Expected generated Bazel target to be '%s', got '%s'",
+					testCase.expectedBazelTarget,
+					actualBazelTarget.content,
+				)
+			}
+		})
 	}
 }
 
@@ -324,11 +339,11 @@
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
-		if Errored(t, "", errs) {
+		if errored(t, "", errs) {
 			continue
 		}
 		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, "", errs) {
+		if errored(t, "", errs) {
 			continue
 		}
 
@@ -925,11 +940,11 @@
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
+		if errored(t, testCase.description, errs) {
 			continue
 		}
 		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
+		if errored(t, testCase.description, errs) {
 			continue
 		}
 
@@ -957,17 +972,6 @@
 	}
 }
 
-func Errored(t *testing.T, desc string, errs []error) bool {
-	t.Helper()
-	if len(errs) > 0 {
-		for _, err := range errs {
-			t.Errorf("%s: %s", desc, err)
-		}
-		return true
-	}
-	return false
-}
-
 type bp2buildMutator = func(android.TopDownMutatorContext)
 
 func TestBp2BuildInlinesDefaults(t *testing.T) {
@@ -1120,8 +1124,8 @@
         "out",
     ],
     srcs = [
-        "in1",
         "srcs-from-3",
+        "in1",
     ],
 )`,
 			description: "genrule applies properties from genrule_defaults transitively",
@@ -1463,53 +1467,61 @@
 
 	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
+		t.Run(testCase.description, func(t *testing.T) {
+			fs := make(map[string][]byte)
+			toParse := []string{
+				"Android.bp",
 			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+			for f, content := range testCase.fs {
+				if strings.HasSuffix(f, "Android.bp") {
+					toParse = append(toParse, f)
+				}
+				fs[f] = []byte(content)
+			}
+			config := android.TestConfig(buildDir, nil, testCase.bp, fs)
+			ctx := android.NewTestContext(config)
+			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+			for _, m := range testCase.depsMutators {
+				ctx.DepsBp2BuildMutators(m)
+			}
+			ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+			ctx.RegisterForBazelConversion()
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
+			_, errs := ctx.ParseFileList(dir, toParse)
+			if errored(t, testCase.description, errs) {
+				return
+			}
+			_, errs = ctx.ResolveDependencies(config)
+			if errored(t, testCase.description, errs) {
+				return
+			}
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
-		} else {
+			checkDir := dir
+			if testCase.dir != "" {
+				checkDir = testCase.dir
+			}
+			bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
+			bazelTargets.sort()
+			actualCount := len(bazelTargets)
+			expectedCount := len(testCase.expectedBazelTargets)
+			if actualCount != expectedCount {
+				t.Errorf("Expected %d bazel target, got %d\n%s", expectedCount, actualCount, bazelTargets)
+			}
+			if !strings.Contains(bazelTargets.String(), "Section: Handcrafted targets. ") {
+				t.Errorf("Expected string representation of bazelTargets to contain handcrafted section header.")
+			}
 			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+				actualContent := target.content
+				expectedContent := testCase.expectedBazelTargets[i]
+				if expectedContent != actualContent {
 					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
+						"Expected generated Bazel target to be '%s', got '%s'",
+						expectedContent,
+						actualContent,
 					)
 				}
 			}
-		}
+		})
 	}
 }
 
@@ -1538,10 +1550,10 @@
 			expectedBazelTargets: []string{`filegroup(
     name = "fg_foo",
     srcs = [
-        "//dir:e.txt",
-        "//dir:f.txt",
         "a.txt",
         "b.txt",
+        "//dir:e.txt",
+        "//dir:f.txt",
     ],
 )`,
 			},
@@ -1578,9 +1590,9 @@
 			expectedBazelTargets: []string{`filegroup(
     name = "fg_foo",
     srcs = [
+        "a.txt",
         "//dir/subdir:e.txt",
         "//dir/subdir:f.txt",
-        "a.txt",
     ],
 )`,
 			},
@@ -1606,11 +1618,11 @@
 		ctx.RegisterForBazelConversion()
 
 		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
+		if errored(t, testCase.description, errs) {
 			continue
 		}
 		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
+		if errored(t, testCase.description, errs) {
 			continue
 		}
 
diff --git a/bp2build/bzl_conversion_test.go b/bp2build/bzl_conversion_test.go
index 32b12e4..204c519 100644
--- a/bp2build/bzl_conversion_test.go
+++ b/bp2build/bzl_conversion_test.go
@@ -22,8 +22,6 @@
 	"testing"
 )
 
-var buildDir string
-
 func setUp() {
 	var err error
 	buildDir, err = ioutil.TempDir("", "bazel_queryview_test")
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 407073a..285677a 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -25,58 +25,117 @@
 	// See cc/testing.go for more context
 	soongCcLibraryPreamble = `
 cc_defaults {
-	name: "linux_bionic_supported",
+  name: "linux_bionic_supported",
 }
 
 toolchain_library {
-	name: "libclang_rt.builtins-x86_64-android",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
+  name: "libclang_rt.builtins-x86_64-android",
+  defaults: ["linux_bionic_supported"],
+  vendor_available: true,
+  vendor_ramdisk_available: true,
+  product_available: true,
+  recovery_available: true,
+  native_bridge_supported: true,
+  src: "",
 }`
 )
 
-func TestCcLibraryBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-		depsMutators                       []android.RegisterMutatorFunc
-	}{
-		{
-			description:                        "cc_library - simple example",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			filesystem: map[string]string{
-				"android.cpp": "",
-				"darwin.cpp":  "",
-				// Refer to cc.headerExts for the supported header extensions in Soong.
-				"header.h":         "",
-				"header.hh":        "",
-				"header.hpp":       "",
-				"header.hxx":       "",
-				"header.h++":       "",
-				"header.inl":       "",
-				"header.inc":       "",
-				"header.ipp":       "",
-				"header.h.generic": "",
-				"impl.cpp":         "",
-				"linux.cpp":        "",
-				"x86.cpp":          "",
-				"x86_64.cpp":       "",
-				"foo-dir/a.h":      "",
-			},
-			bp: soongCcLibraryPreamble + `
+func runCcLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibraryModuleTypes, tc)
+}
+
+func registerCcLibraryModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
+	ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
+	ctx.RegisterModuleType("cc_prebuilt_library_static", cc.PrebuiltStaticLibraryFactory)
+	ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+}
+
+func runBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc bp2buildTestCase) {
+	t.Helper()
+	dir := "."
+	filesystem := make(map[string][]byte)
+	toParse := []string{
+		"Android.bp",
+	}
+	for f, content := range tc.filesystem {
+		if strings.HasSuffix(f, "Android.bp") {
+			toParse = append(toParse, f)
+		}
+		filesystem[f] = []byte(content)
+	}
+	config := android.TestConfig(buildDir, nil, tc.blueprint, filesystem)
+	ctx := android.NewTestContext(config)
+
+	registerModuleTypes(ctx)
+	ctx.RegisterModuleType(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestFactory)
+	ctx.RegisterBp2BuildConfig(bp2buildConfig)
+	for _, m := range tc.depsMutators {
+		ctx.DepsBp2BuildMutators(m)
+	}
+	ctx.RegisterBp2BuildMutator(tc.moduleTypeUnderTest, tc.moduleTypeUnderTestBp2BuildMutator)
+	ctx.RegisterForBazelConversion()
+
+	_, errs := ctx.ParseFileList(dir, toParse)
+	if errored(t, tc.description, errs) {
+		return
+	}
+	_, errs = ctx.ResolveDependencies(config)
+	if errored(t, tc.description, errs) {
+		return
+	}
+
+	checkDir := dir
+	if tc.dir != "" {
+		checkDir = tc.dir
+	}
+	codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+	bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
+	if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount {
+		t.Errorf("%s: Expected %d bazel target, got %d", tc.description, expectedCount, actualCount)
+	} else {
+		for i, target := range bazelTargets {
+			if w, g := tc.expectedBazelTargets[i], target.content; w != g {
+				t.Errorf(
+					"%s: Expected generated Bazel target to be '%s', got '%s'",
+					tc.description,
+					w,
+					g,
+				)
+			}
+		}
+	}
+}
+
+func TestCcLibrarySimple(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library - simple example",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"android.cpp": "",
+			"darwin.cpp":  "",
+			// Refer to cc.headerExts for the supported header extensions in Soong.
+			"header.h":         "",
+			"header.hh":        "",
+			"header.hpp":       "",
+			"header.hxx":       "",
+			"header.h++":       "",
+			"header.inl":       "",
+			"header.inc":       "",
+			"header.ipp":       "",
+			"header.h.generic": "",
+			"impl.cpp":         "",
+			"linux.cpp":        "",
+			"x86.cpp":          "",
+			"x86_64.cpp":       "",
+			"foo-dir/a.h":      "",
+		},
+		blueprint: soongCcLibraryPreamble + `
 cc_library_headers { name: "some-headers" }
 cc_library {
     name: "foo-lib",
@@ -108,13 +167,14 @@
     },
 }
 `,
-			expectedBazelTargets: []string{`cc_library(
+		expectedBazelTargets: []string{`cc_library(
     name = "foo-lib",
     copts = [
         "-Wall",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
-    deps = [":some-headers"],
+    implementation_deps = [":some-headers"],
     includes = ["foo-dir"],
     linkopts = ["-Wl,--exclude-libs=bar.a"] + select({
         "//build/bazel/platforms/arch:x86": ["-Wl,--exclude-libs=baz.a"],
@@ -131,21 +191,23 @@
         "//build/bazel/platforms/os:linux": ["linux.cpp"],
         "//conditions:default": [],
     }),
-)`},
+)`}})
+}
+
+func TestCcLibraryTrimmedLdAndroid(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library - trimmed example of //bionic/linker:ld-android",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"ld-android.cpp":           "",
+			"linked_list.h":            "",
+			"linker.h":                 "",
+			"linker_block_allocator.h": "",
+			"linker_cfi.h":             "",
 		},
-		{
-			description:                        "cc_library - trimmed example of //bionic/linker:ld-android",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			filesystem: map[string]string{
-				"ld-android.cpp":           "",
-				"linked_list.h":            "",
-				"linker.h":                 "",
-				"linker_block_allocator.h": "",
-				"linker_cfi.h":             "",
-			},
-			bp: soongCcLibraryPreamble + `
+		blueprint: soongCcLibraryPreamble + `
 cc_library_headers { name: "libc_headers" }
 cc_library {
     name: "fake-ld-android",
@@ -175,7 +237,7 @@
     },
 }
 `,
-			expectedBazelTargets: []string{`cc_library(
+		expectedBazelTargets: []string{`cc_library(
     name = "fake-ld-android",
     copts = [
         "-Wall",
@@ -183,8 +245,9 @@
         "-Wunused",
         "-Werror",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
-    deps = [":libc_headers"],
+    implementation_deps = [":libc_headers"],
     linkopts = [
         "-Wl,--exclude-libs=libgcc.a",
         "-Wl,--exclude-libs=libgcc_stripped.a",
@@ -199,20 +262,23 @@
     }),
     srcs = ["ld_android.cpp"],
 )`},
-		},
-		{
-			description:                        "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			dir:                                "external",
-			filesystem: map[string]string{
-				"external/math/cosf.c":      "",
-				"external/math/erf.c":       "",
-				"external/math/erf_data.c":  "",
-				"external/math/erff.c":      "",
-				"external/math/erff_data.c": "",
-				"external/Android.bp": `
+	})
+}
+
+func TestCcLibraryExcludeSrcs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library exclude_srcs - trimmed example of //external/arm-optimized-routines:libarm-optimized-routines-math",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		dir:                                "external",
+		filesystem: map[string]string{
+			"external/math/cosf.c":      "",
+			"external/math/erf.c":       "",
+			"external/math/erf_data.c":  "",
+			"external/math/erff.c":      "",
+			"external/math/erff_data.c": "",
+			"external/Android.bp": `
 cc_library {
     name: "fake-libarm-optimized-routines-math",
     exclude_srcs: [
@@ -238,29 +304,35 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
     name = "fake-libarm-optimized-routines-math",
-    copts = ["-Iexternal"] + select({
+    copts = [
+        "-Iexternal",
+        "-I$(BINDIR)/external",
+    ] + select({
         "//build/bazel/platforms/arch:arm64": ["-DHAVE_FAST_FMA=1"],
         "//conditions:default": [],
     }),
-    srcs = ["math/cosf.c"],
+    srcs_c = ["math/cosf.c"],
 )`},
-		},
-		{
-			description:                        "cc_library shared/static props",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/both.cpp":       "",
-				"foo/bar/sharedonly.cpp": "",
-				"foo/bar/staticonly.cpp": "",
-				"foo/bar/Android.bp": `
+	})
+}
+
+func TestCcLibrarySharedStaticProps(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library shared/static props",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/both.cpp":       "",
+			"foo/bar/sharedonly.cpp": "",
+			"foo/bar/staticonly.cpp": "",
+			"foo/bar/Android.bp": `
 cc_library {
     name: "a",
     srcs: ["both.cpp"],
@@ -303,39 +375,369 @@
 
 cc_library { name: "shared_dep_for_both" }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
     name = "a",
     copts = [
         "bothflag",
         "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
     ],
-    deps = [":static_dep_for_both"],
     dynamic_deps = [":shared_dep_for_both"],
-    dynamic_deps_for_shared = [":shared_dep_for_shared"],
-    dynamic_deps_for_static = [":shared_dep_for_static"],
-    shared_copts = ["sharedflag"],
-    shared_srcs = ["sharedonly.cpp"],
+    implementation_deps = [":static_dep_for_both"],
+    shared = {
+        "copts": ["sharedflag"],
+        "dynamic_deps": [":shared_dep_for_shared"],
+        "srcs": ["sharedonly.cpp"],
+        "static_deps": [":static_dep_for_shared"],
+        "whole_archive_deps": [":whole_static_lib_for_shared"],
+    },
     srcs = ["both.cpp"],
-    static_copts = ["staticflag"],
-    static_deps_for_shared = [":static_dep_for_shared"],
-    static_deps_for_static = [":static_dep_for_static"],
-    static_srcs = ["staticonly.cpp"],
+    static = {
+        "copts": ["staticflag"],
+        "dynamic_deps": [":shared_dep_for_static"],
+        "srcs": ["staticonly.cpp"],
+        "static_deps": [":static_dep_for_static"],
+        "whole_archive_deps": [":whole_static_lib_for_static"],
+    },
     whole_archive_deps = [":whole_static_lib_for_both"],
-    whole_archive_deps_for_shared = [":whole_static_lib_for_shared"],
-    whole_archive_deps_for_static = [":whole_static_lib_for_static"],
 )`},
+	})
+}
+
+func TestCcLibraryWholeStaticLibsAlwaysLink(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    whole_static_libs: ["whole_static_lib_for_both"],
+    static: {
+        whole_static_libs: ["whole_static_lib_for_static"],
+    },
+    shared: {
+        whole_static_libs: ["whole_static_lib_for_shared"],
+    },
+    bazel_module: { bp2build_available: true },
+}
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_shared" }
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_static" }
+
+cc_prebuilt_library_static { name: "whole_static_lib_for_both" }
+`,
 		},
-		{
-			description:                        "cc_library non-configured version script",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    shared = {
+        "whole_archive_deps": [":whole_static_lib_for_shared_alwayslink"],
+    },
+    static = {
+        "whole_archive_deps": [":whole_static_lib_for_static_alwayslink"],
+    },
+    whole_archive_deps = [":whole_static_lib_for_both_alwayslink"],
+)`},
+	})
+}
+
+func TestCcLibrarySharedStaticPropsInArch(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library shared/static props in arch",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/arm.cpp":        "",
+			"foo/bar/x86.cpp":        "",
+			"foo/bar/sharedonly.cpp": "",
+			"foo/bar/staticonly.cpp": "",
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    arch: {
+        arm: {
+            shared: {
+                srcs: ["arm_shared.cpp"],
+                cflags: ["-DARM_SHARED"],
+                static_libs: ["arm_static_dep_for_shared"],
+                whole_static_libs: ["arm_whole_static_dep_for_shared"],
+                shared_libs: ["arm_shared_dep_for_shared"],
+            },
+        },
+        x86: {
+            static: {
+                srcs: ["x86_static.cpp"],
+                cflags: ["-DX86_STATIC"],
+                static_libs: ["x86_dep_for_static"],
+            },
+        },
+    },
+    target: {
+        android: {
+            shared: {
+                srcs: ["android_shared.cpp"],
+                cflags: ["-DANDROID_SHARED"],
+                static_libs: ["android_dep_for_shared"],
+            },
+        },
+        android_arm: {
+            shared: {
+                cflags: ["-DANDROID_ARM_SHARED"],
+            },
+        },
+    },
+    srcs: ["both.cpp"],
+    cflags: ["bothflag"],
+    static_libs: ["static_dep_for_both"],
+    static: {
+        srcs: ["staticonly.cpp"],
+        cflags: ["staticflag"],
+        static_libs: ["static_dep_for_static"],
+    },
+    shared: {
+        srcs: ["sharedonly.cpp"],
+        cflags: ["sharedflag"],
+        static_libs: ["static_dep_for_shared"],
+    },
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_static { name: "static_dep_for_shared" }
+cc_library_static { name: "static_dep_for_static" }
+cc_library_static { name: "static_dep_for_both" }
+
+cc_library_static { name: "arm_static_dep_for_shared" }
+cc_library_static { name: "arm_whole_static_dep_for_shared" }
+cc_library_static { name: "arm_shared_dep_for_shared" }
+
+cc_library_static { name: "x86_dep_for_static" }
+
+cc_library_static { name: "android_dep_for_shared" }
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "bothflag",
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    implementation_deps = [":static_dep_for_both"],
+    shared = {
+        "copts": ["sharedflag"] + select({
+            "//build/bazel/platforms/arch:arm": ["-DARM_SHARED"],
+            "//conditions:default": [],
+        }) + select({
+            "//build/bazel/platforms/os:android": ["-DANDROID_SHARED"],
+            "//conditions:default": [],
+        }) + select({
+            "//build/bazel/platforms/os_arch:android_arm": ["-DANDROID_ARM_SHARED"],
+            "//conditions:default": [],
+        }),
+        "dynamic_deps": select({
+            "//build/bazel/platforms/arch:arm": [":arm_shared_dep_for_shared"],
+            "//conditions:default": [],
+        }),
+        "srcs": ["sharedonly.cpp"] + select({
+            "//build/bazel/platforms/arch:arm": ["arm_shared.cpp"],
+            "//conditions:default": [],
+        }) + select({
+            "//build/bazel/platforms/os:android": ["android_shared.cpp"],
+            "//conditions:default": [],
+        }),
+        "static_deps": [":static_dep_for_shared"] + select({
+            "//build/bazel/platforms/arch:arm": [":arm_static_dep_for_shared"],
+            "//conditions:default": [],
+        }) + select({
+            "//build/bazel/platforms/os:android": [":android_dep_for_shared"],
+            "//conditions:default": [],
+        }),
+        "whole_archive_deps": select({
+            "//build/bazel/platforms/arch:arm": [":arm_whole_static_dep_for_shared"],
+            "//conditions:default": [],
+        }),
+    },
+    srcs = ["both.cpp"],
+    static = {
+        "copts": ["staticflag"] + select({
+            "//build/bazel/platforms/arch:x86": ["-DX86_STATIC"],
+            "//conditions:default": [],
+        }),
+        "srcs": ["staticonly.cpp"] + select({
+            "//build/bazel/platforms/arch:x86": ["x86_static.cpp"],
+            "//conditions:default": [],
+        }),
+        "static_deps": [":static_dep_for_static"] + select({
+            "//build/bazel/platforms/arch:x86": [":x86_dep_for_static"],
+            "//conditions:default": [],
+        }),
+    },
+)`},
+	})
+}
+
+func TestCcLibrarySharedStaticPropsWithMixedSources(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library shared/static props with c/cpp/s mixed sources",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/both_source.cpp":   "",
+			"foo/bar/both_source.cc":    "",
+			"foo/bar/both_source.c":     "",
+			"foo/bar/both_source.s":     "",
+			"foo/bar/both_source.S":     "",
+			"foo/bar/shared_source.cpp": "",
+			"foo/bar/shared_source.cc":  "",
+			"foo/bar/shared_source.c":   "",
+			"foo/bar/shared_source.s":   "",
+			"foo/bar/shared_source.S":   "",
+			"foo/bar/static_source.cpp": "",
+			"foo/bar/static_source.cc":  "",
+			"foo/bar/static_source.c":   "",
+			"foo/bar/static_source.s":   "",
+			"foo/bar/static_source.S":   "",
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    srcs: [
+    "both_source.cpp",
+    "both_source.cc",
+    "both_source.c",
+    "both_source.s",
+    "both_source.S",
+        ":both_filegroup",
+  ],
+    static: {
+    srcs: [
+      "static_source.cpp",
+      "static_source.cc",
+      "static_source.c",
+      "static_source.s",
+      "static_source.S",
+      ":static_filegroup",
+    ],
+    },
+    shared: {
+    srcs: [
+      "shared_source.cpp",
+      "shared_source.cc",
+      "shared_source.c",
+      "shared_source.s",
+      "shared_source.S",
+      ":shared_filegroup",
+    ],
+    },
+    bazel_module: { bp2build_available: true },
+}
+
+filegroup {
+    name: "both_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+
+filegroup {
+    name: "shared_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+
+filegroup {
+    name: "static_filegroup",
+    srcs: [
+        // Not relevant, handled by filegroup macro
+  ],
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    shared = {
+        "srcs": [
+            ":shared_filegroup_cpp_srcs",
+            "shared_source.cc",
+            "shared_source.cpp",
+        ],
+        "srcs_as": [
+            "shared_source.s",
+            "shared_source.S",
+            ":shared_filegroup_as_srcs",
+        ],
+        "srcs_c": [
+            "shared_source.c",
+            ":shared_filegroup_c_srcs",
+        ],
+    },
+    srcs = [
+        ":both_filegroup_cpp_srcs",
+        "both_source.cc",
+        "both_source.cpp",
+    ],
+    srcs_as = [
+        "both_source.s",
+        "both_source.S",
+        ":both_filegroup_as_srcs",
+    ],
+    srcs_c = [
+        "both_source.c",
+        ":both_filegroup_c_srcs",
+    ],
+    static = {
+        "srcs": [
+            ":static_filegroup_cpp_srcs",
+            "static_source.cc",
+            "static_source.cpp",
+        ],
+        "srcs_as": [
+            "static_source.s",
+            "static_source.S",
+            ":static_filegroup_as_srcs",
+        ],
+        "srcs_c": [
+            "static_source.c",
+            ":static_filegroup_c_srcs",
+        ],
+    },
+)`},
+	})
+}
+
+func TestCcLibraryNonConfiguredVersionScript(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library non-configured version script",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
 cc_library {
     name: "a",
     srcs: ["a.cpp"],
@@ -343,24 +745,73 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
     name = "a",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
     srcs = ["a.cpp"],
     version_script = "v.map",
 )`},
+	})
+}
+
+func TestCcLibraryConfiguredVersionScript(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library configured version script",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+    cc_library {
+       name: "a",
+       srcs: ["a.cpp"],
+       arch: {
+         arm: {
+           version_script: "arm.map",
+         },
+         arm64: {
+           version_script: "arm64.map",
+         },
+       },
+
+       bazel_module: { bp2build_available: true },
+    }
+    `,
 		},
-		{
-			description:                        "cc_library shared_libs",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    srcs = ["a.cpp"],
+    version_script = select({
+        "//build/bazel/platforms/arch:arm": "arm.map",
+        "//build/bazel/platforms/arch:arm64": "arm64.map",
+        "//conditions:default": None,
+    }),
+)`},
+	})
+}
+
+func TestCcLibrarySharedLibs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library shared_libs",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
 cc_library {
     name: "mylib",
     bazel_module: { bp2build_available: true },
@@ -372,26 +823,35 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
     name = "a",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
     dynamic_deps = [":mylib"],
 )`, `cc_library(
     name = "mylib",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
 )`},
-		},
-		{
-			description:                        "cc_library pack_relocations test",
-			moduleTypeUnderTest:                "cc_library",
-			moduleTypeUnderTestFactory:         cc.LibraryFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			dir:                                "foo/bar",
-			filesystem: map[string]string{
-				"foo/bar/Android.bp": `
+	})
+}
+
+func TestCcLibraryPackRelocations(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library pack_relocations test",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
 cc_library {
     name: "a",
     srcs: ["a.cpp"],
@@ -404,8 +864,8 @@
     srcs: ["b.cpp"],
     arch: {
         x86_64: {
-		pack_relocations: false,
-	},
+    pack_relocations: false,
+  },
     },
     bazel_module: { bp2build_available: true },
 }
@@ -415,21 +875,27 @@
     srcs: ["c.cpp"],
     target: {
         darwin: {
-		pack_relocations: false,
-	},
+    pack_relocations: false,
+  },
     },
     bazel_module: { bp2build_available: true },
 }`,
-			},
-			bp: soongCcLibraryPreamble,
-			expectedBazelTargets: []string{`cc_library(
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
     name = "a",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
     linkopts = ["-Wl,--pack-dyn-relocs=none"],
     srcs = ["a.cpp"],
 )`, `cc_library(
     name = "b",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
     linkopts = select({
         "//build/bazel/platforms/arch:x86_64": ["-Wl,--pack-dyn-relocs=none"],
         "//conditions:default": [],
@@ -437,71 +903,560 @@
     srcs = ["b.cpp"],
 )`, `cc_library(
     name = "c",
-    copts = ["-Ifoo/bar"],
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
     linkopts = select({
         "//build/bazel/platforms/os:darwin": ["-Wl,--pack-dyn-relocs=none"],
         "//conditions:default": [],
     }),
     srcs = ["c.cpp"],
 )`},
+	})
+}
+
+func TestCcLibrarySpacesInCopts(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library spaces in copts",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "a",
+    cflags: ["-include header.h",],
+    bazel_module: { bp2build_available: true },
+}
+`,
 		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-include",
+        "header.h",
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+)`},
+	})
+}
+
+func TestCcLibraryCppFlagsGoesIntoCopts(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library cppflags usage",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `cc_library {
+    name: "a",
+    srcs: ["a.cpp"],
+    cflags: [
+    "-Wall",
+  ],
+    cppflags: [
+        "-fsigned-char",
+        "-pedantic",
+  ],
+    arch: {
+        arm64: {
+            cppflags: ["-DARM64=1"],
+    },
+  },
+    target: {
+        android: {
+            cppflags: ["-DANDROID=1"],
+    },
+  },
+    bazel_module: { bp2build_available: true  },
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-Wall",
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    cppflags = [
+        "-fsigned-char",
+        "-pedantic",
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": ["-DARM64=1"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": ["-DANDROID=1"],
+        "//conditions:default": [],
+    }),
+    srcs = ["a.cpp"],
+)`},
+	})
+}
+
+func TestCcLibraryLabelAttributeGetTargetProperties(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library GetTargetProperties on a LabelAttribute",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+    cc_library {
+       name: "a",
+       srcs: ["a.cpp"],
+       target: {
+         android_arm: {
+           version_script: "android_arm.map",
+         },
+         linux_bionic_arm64: {
+           version_script: "linux_bionic_arm64.map",
+         },
+       },
+
+       bazel_module: { bp2build_available: true },
+    }
+    `,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "a",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    srcs = ["a.cpp"],
+    version_script = select({
+        "//build/bazel/platforms/os_arch:android_arm": "android_arm.map",
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": "linux_bionic_arm64.map",
+        "//conditions:default": None,
+    }),
+)`},
+	})
+}
+
+func TestCcLibraryExcludeLibs(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library {
+    name: "foo_static",
+    srcs: ["common.c"],
+    whole_static_libs: [
+        "arm_whole_static_lib_excludes",
+        "malloc_not_svelte_whole_static_lib_excludes"
+    ],
+    static_libs: [
+        "arm_static_lib_excludes",
+        "malloc_not_svelte_static_lib_excludes"
+    ],
+    shared_libs: [
+        "arm_shared_lib_excludes",
+    ],
+    arch: {
+        arm: {
+            exclude_shared_libs: [
+                 "arm_shared_lib_excludes",
+            ],
+            exclude_static_libs: [
+                "arm_static_lib_excludes",
+                "arm_whole_static_lib_excludes",
+            ],
+        },
+    },
+    product_variables: {
+        malloc_not_svelte: {
+            shared_libs: ["malloc_not_svelte_shared_lib"],
+            whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
+            exclude_static_libs: [
+                "malloc_not_svelte_static_lib_excludes",
+                "malloc_not_svelte_whole_static_lib_excludes",
+            ],
+        },
+    },
+}
+
+cc_library {
+    name: "arm_whole_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_whole_static_lib",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_whole_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "arm_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_static_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "arm_shared_lib_excludes",
+    bazel_module: { bp2build_available: false },
+}
+
+cc_library {
+    name: "malloc_not_svelte_shared_lib",
+    bazel_module: { bp2build_available: false },
+}
+`,
+		expectedBazelTargets: []string{
+			`cc_library(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    dynamic_deps = select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_shared_lib_excludes"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_shared_lib"],
+        "//conditions:default": [],
+    }),
+    implementation_deps = select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_static_lib_excludes"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [],
+        "//conditions:default": [":malloc_not_svelte_static_lib_excludes"],
+    }),
+    srcs_c = ["common.c"],
+    whole_archive_deps = select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": [":arm_whole_static_lib_excludes"],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": [":malloc_not_svelte_whole_static_lib"],
+        "//conditions:default": [":malloc_not_svelte_whole_static_lib_excludes"],
+    }),
+)`,
+		},
+	})
+}
+
+func TestCCLibraryNoCrtTrue(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library - simple example",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: true,
+}
+`,
+		expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    srcs = ["impl.cpp"],
+    use_libcrt = False,
+)`}})
+}
+
+func TestCCLibraryNoCrtFalse(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: false,
+}
+`,
+		expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    srcs = ["impl.cpp"],
+    use_libcrt = True,
+)`}})
+}
+
+func TestCCLibraryNoCrtArchVariant(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    arch: {
+        arm: {
+            no_libcrt: true,
+        },
+        x86: {
+            no_libcrt: true,
+        },
+    },
+}
+`,
+		expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    srcs = ["impl.cpp"],
+    use_libcrt = select({
+        "//build/bazel/platforms/arch:arm": False,
+        "//build/bazel/platforms/arch:x86": False,
+        "//conditions:default": None,
+    }),
+)`}})
+}
+
+func TestCCLibraryNoCrtArchVariantWithDefault(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem: map[string]string{
+			"impl.cpp": "",
+		},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    no_libcrt: false,
+    arch: {
+        arm: {
+            no_libcrt: true,
+        },
+        x86: {
+            no_libcrt: true,
+        },
+    },
+}
+`,
+		expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    srcs = ["impl.cpp"],
+    use_libcrt = select({
+        "//build/bazel/platforms/arch:arm": False,
+        "//build/bazel/platforms/arch:x86": False,
+        "//conditions:default": True,
+    }),
+)`}})
+}
+
+func TestCcLibraryStrip(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library strip args",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "nothing",
+    bazel_module: { bp2build_available: true },
+}
+cc_library {
+    name: "keep_symbols",
+    bazel_module: { bp2build_available: true },
+    strip: {
+		keep_symbols: true,
 	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("cc_library_static", cc.LibraryStaticFactory)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig) // TODO(jingwen): make this the default for all tests
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
+}
+cc_library {
+    name: "keep_symbols_and_debug_frame",
+    bazel_module: { bp2build_available: true },
+    strip: {
+		keep_symbols_and_debug_frame: true,
 	}
 }
+cc_library {
+    name: "none",
+    bazel_module: { bp2build_available: true },
+    strip: {
+		none: true,
+	}
+}
+cc_library {
+    name: "keep_symbols_list",
+    bazel_module: { bp2build_available: true },
+    strip: {
+		keep_symbols_list: ["symbol"],
+	}
+}
+cc_library {
+    name: "all",
+    bazel_module: { bp2build_available: true },
+    strip: {
+		all: true,
+	}
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "all",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "all": True,
+    },
+)`, `cc_library(
+    name = "keep_symbols",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "keep_symbols": True,
+    },
+)`, `cc_library(
+    name = "keep_symbols_and_debug_frame",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "keep_symbols_and_debug_frame": True,
+    },
+)`, `cc_library(
+    name = "keep_symbols_list",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "keep_symbols_list": ["symbol"],
+    },
+)`, `cc_library(
+    name = "none",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "none": True,
+    },
+)`, `cc_library(
+    name = "nothing",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+)`},
+	})
+}
+
+func TestCcLibraryStripWithArch(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library strip args",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		dir:                                "foo/bar",
+		filesystem: map[string]string{
+			"foo/bar/Android.bp": `
+cc_library {
+    name: "multi-arch",
+    bazel_module: { bp2build_available: true },
+    target: {
+        darwin: {
+            strip: {
+                keep_symbols_list: ["foo", "bar"]
+            }
+        },
+    },
+    arch: {
+        arm: {
+            strip: {
+                keep_symbols_and_debug_frame: true,
+            },
+        },
+        arm64: {
+            strip: {
+                keep_symbols: true,
+            },
+        },
+    }
+}
+`,
+		},
+		blueprint: soongCcLibraryPreamble,
+		expectedBazelTargets: []string{`cc_library(
+    name = "multi-arch",
+    copts = [
+        "-Ifoo/bar",
+        "-I$(BINDIR)/foo/bar",
+    ],
+    strip = {
+        "keep_symbols": select({
+            "//build/bazel/platforms/arch:arm64": True,
+            "//conditions:default": None,
+        }),
+        "keep_symbols_and_debug_frame": select({
+            "//build/bazel/platforms/arch:arm": True,
+            "//conditions:default": None,
+        }),
+        "keep_symbols_list": select({
+            "//build/bazel/platforms/os:darwin": [
+                "foo",
+                "bar",
+            ],
+            "//conditions:default": [],
+        }),
+    },
+)`},
+	})
+}
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index 0905aba..db344de 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -15,10 +15,10 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/cc"
-	"strings"
-	"testing"
 )
 
 const (
@@ -40,6 +40,18 @@
 }`
 )
 
+type bp2buildTestCase struct {
+	description                        string
+	moduleTypeUnderTest                string
+	moduleTypeUnderTestFactory         android.ModuleFactory
+	moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+	depsMutators                       []android.RegisterMutatorFunc
+	blueprint                          string
+	expectedBazelTargets               []string
+	filesystem                         map[string]string
+	dir                                string
+}
+
 func TestCcLibraryHeadersLoadStatement(t *testing.T) {
 	testCases := []struct {
 		bazelTargets           BazelTargets
@@ -64,41 +76,38 @@
 			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
 		}
 	}
-
 }
 
-func TestCcLibraryHeadersBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "cc_library_headers test",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			filesystem: map[string]string{
-				"lib-1/lib1a.h":                        "",
-				"lib-1/lib1b.h":                        "",
-				"lib-2/lib2a.h":                        "",
-				"lib-2/lib2b.h":                        "",
-				"dir-1/dir1a.h":                        "",
-				"dir-1/dir1b.h":                        "",
-				"dir-2/dir2a.h":                        "",
-				"dir-2/dir2b.h":                        "",
-				"arch_arm64_exported_include_dir/a.h":  "",
-				"arch_x86_exported_include_dir/b.h":    "",
-				"arch_x86_64_exported_include_dir/c.h": "",
-			},
-			bp: soongCcLibraryHeadersPreamble + `
+func registerCcLibraryHeadersModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+}
+
+func runCcLibraryHeadersTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibraryHeadersModuleTypes, tc)
+}
+
+func TestCcLibraryHeadersSimple(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_headers test",
+		moduleTypeUnderTest:                "cc_library_headers",
+		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		filesystem: map[string]string{
+			"lib-1/lib1a.h":                        "",
+			"lib-1/lib1b.h":                        "",
+			"lib-2/lib2a.h":                        "",
+			"lib-2/lib2b.h":                        "",
+			"dir-1/dir1a.h":                        "",
+			"dir-1/dir1b.h":                        "",
+			"dir-2/dir2a.h":                        "",
+			"dir-2/dir2b.h":                        "",
+			"arch_arm64_exported_include_dir/a.h":  "",
+			"arch_x86_exported_include_dir/b.h":    "",
+			"arch_x86_64_exported_include_dir/c.h": "",
+		},
+		blueprint: soongCcLibraryHeadersPreamble + `
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
@@ -129,10 +138,13 @@
 
     // TODO: Also support export_header_lib_headers
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
+		expectedBazelTargets: []string{`cc_library_headers(
     name = "foo_headers",
-    copts = ["-I."],
-    deps = [
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = [
         ":lib-1",
         ":lib-2",
     ],
@@ -147,22 +159,31 @@
     }),
 )`, `cc_library_headers(
     name = "lib-1",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     includes = ["lib-1"],
 )`, `cc_library_headers(
     name = "lib-2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     includes = ["lib-2"],
 )`},
-		},
-		{
-			description:                        "cc_library_headers test with os-specific header_libs props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `
+	})
+}
+
+func TestCcLibraryHeadersOSSpecificHeader(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_headers test with os-specific header_libs props",
+		moduleTypeUnderTest:                "cc_library_headers",
+		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
 cc_library_headers { name: "android-lib" }
 cc_library_headers { name: "base-lib" }
 cc_library_headers { name: "darwin-lib" }
@@ -183,19 +204,31 @@
     },
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
+		expectedBazelTargets: []string{`cc_library_headers(
     name = "android-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "base-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "darwin-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "foo_headers",
-    copts = ["-I."],
-    deps = [":base-lib"] + select({
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = [":base-lib"] + select({
         "//build/bazel/platforms/os:android": [":android-lib"],
         "//build/bazel/platforms/os:darwin": [":darwin-lib"],
         "//build/bazel/platforms/os:fuchsia": [":fuchsia-lib"],
@@ -206,26 +239,41 @@
     }),
 )`, `cc_library_headers(
     name = "fuchsia-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "linux-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "linux_bionic-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "windows-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`},
-		},
-		{
-			description:                        "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `
+	})
+}
+
+func TestCcLibraryHeadersOsSpecficHeaderLibsExportHeaderLibHeaders(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
+		moduleTypeUnderTest:                "cc_library_headers",
+		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
 cc_library_headers { name: "android-lib" }
 cc_library_headers { name: "exported-lib" }
 cc_library_headers {
@@ -234,32 +282,45 @@
         android: { header_libs: ["android-lib"], export_header_lib_headers: ["exported-lib"] },
     },
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
+		expectedBazelTargets: []string{`cc_library_headers(
     name = "android-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "exported-lib",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
 )`, `cc_library_headers(
     name = "foo_headers",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     deps = select({
-        "//build/bazel/platforms/os:android": [
-            ":android-lib",
-            ":exported-lib",
-        ],
+        "//build/bazel/platforms/os:android": [":exported-lib"],
+        "//conditions:default": [],
+    }),
+    implementation_deps = select({
+        "//build/bazel/platforms/os:android": [":android-lib"],
         "//conditions:default": [],
     }),
 )`},
-		},
-		{
-			description:                        "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
-			moduleTypeUnderTest:                "cc_library_headers",
-			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryPreamble + `cc_library_headers {
+	})
+}
+
+func TestCcLibraryHeadersArchAndTargetExportSystemIncludes(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_headers test with arch-specific and target-specific export_system_include_dirs props",
+		moduleTypeUnderTest:                "cc_library_headers",
+		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryPreamble + `cc_library_headers {
     name: "foo_headers",
     export_system_include_dirs: [
 	"shared_include_dir",
@@ -294,9 +355,12 @@
         },
     },
 }`,
-			expectedBazelTargets: []string{`cc_library_headers(
+		expectedBazelTargets: []string{`cc_library_headers(
     name = "foo_headers",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     includes = ["shared_include_dir"] + select({
         "//build/bazel/platforms/arch:arm": ["arm_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["x86_64_include_dir"],
@@ -308,65 +372,41 @@
         "//conditions:default": [],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryHeadersNoCrtIgnored(t *testing.T) {
+	runCcLibraryHeadersTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_headers test",
+		moduleTypeUnderTest:                "cc_library_headers",
+		moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+		filesystem: map[string]string{
+			"lib-1/lib1a.h":                        "",
+			"lib-1/lib1b.h":                        "",
+			"lib-2/lib2a.h":                        "",
+			"lib-2/lib2b.h":                        "",
+			"dir-1/dir1a.h":                        "",
+			"dir-1/dir1b.h":                        "",
+			"dir-2/dir2a.h":                        "",
+			"dir-2/dir2b.h":                        "",
+			"arch_arm64_exported_include_dir/a.h":  "",
+			"arch_x86_exported_include_dir/b.h":    "",
+			"arch_x86_64_exported_include_dir/c.h": "",
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-
-		// TODO(jingwen): make this default for all bp2build tests
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+		blueprint: soongCcLibraryHeadersPreamble + `
+cc_library_headers {
+    name: "lib-1",
+    export_include_dirs: ["lib-1"],
+    no_libcrt: true,
+}`,
+		expectedBazelTargets: []string{`cc_library_headers(
+    name = "lib-1",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    includes = ["lib-1"],
+)`},
+	})
 }
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index d082db1..40edec8 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -17,7 +17,8 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
-	"strings"
+	"android/soong/genrule"
+
 	"testing"
 )
 
@@ -67,45 +68,45 @@
 
 }
 
-func TestCcLibraryStaticBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "cc_library_static test",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
-				"include_dir_1/include_dir_1_a.h": "",
-				"include_dir_1/include_dir_1_b.h": "",
-				"include_dir_2/include_dir_2_a.h": "",
-				"include_dir_2/include_dir_2_b.h": "",
-				// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
-				"local_include_dir_1/local_include_dir_1_a.h": "",
-				"local_include_dir_1/local_include_dir_1_b.h": "",
-				"local_include_dir_2/local_include_dir_2_a.h": "",
-				"local_include_dir_2/local_include_dir_2_b.h": "",
-				// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
-				"export_include_dir_1/export_include_dir_1_a.h": "",
-				"export_include_dir_1/export_include_dir_1_b.h": "",
-				"export_include_dir_2/export_include_dir_2_a.h": "",
-				"export_include_dir_2/export_include_dir_2_b.h": "",
-				// NOTE: Soong implicitly includes headers in the current directory
-				"implicit_include_1.h": "",
-				"implicit_include_2.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+func registerCcLibraryStaticModuleTypes(ctx android.RegistrationContext) {
+	cc.RegisterCCBuildComponents(ctx)
+	ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+	ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+}
+
+func runCcLibraryStaticTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcLibraryStaticModuleTypes, tc)
+}
+
+func TestCcLibraryStaticSimple(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static test",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+			"include_dir_1/include_dir_1_a.h": "",
+			"include_dir_1/include_dir_1_b.h": "",
+			"include_dir_2/include_dir_2_a.h": "",
+			"include_dir_2/include_dir_2_b.h": "",
+			// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+			"local_include_dir_1/local_include_dir_1_a.h": "",
+			"local_include_dir_1/local_include_dir_1_b.h": "",
+			"local_include_dir_2/local_include_dir_2_a.h": "",
+			"local_include_dir_2/local_include_dir_2_b.h": "",
+			// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+			"export_include_dir_1/export_include_dir_1_a.h": "",
+			"export_include_dir_1/export_include_dir_1_b.h": "",
+			"export_include_dir_2/export_include_dir_2_a.h": "",
+			"export_include_dir_2/export_include_dir_2_b.h": "",
+			// NOTE: Soong implicitly includes headers in the current directory
+			"implicit_include_1.h": "",
+			"implicit_include_2.h": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_headers {
     name: "header_lib_1",
     export_include_dirs: ["header_lib_1"],
@@ -163,8 +164,8 @@
         "local_include_dir_2",
     ],
     export_include_dirs: [
-    "export_include_dir_1",
-    "export_include_dir_2"
+        "export_include_dir_1",
+        "export_include_dir_2"
     ],
     header_libs: [
         "header_lib_1",
@@ -173,18 +174,23 @@
 
     // TODO: Also support export_header_lib_headers
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Dflag1",
         "-Dflag2",
         "-Iinclude_dir_1",
+        "-I$(BINDIR)/include_dir_1",
         "-Iinclude_dir_2",
+        "-I$(BINDIR)/include_dir_2",
         "-Ilocal_include_dir_1",
+        "-I$(BINDIR)/local_include_dir_1",
         "-Ilocal_include_dir_2",
+        "-I$(BINDIR)/local_include_dir_2",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
-    deps = [
+    implementation_deps = [
         ":header_lib_1",
         ":header_lib_2",
         ":static_lib_1",
@@ -205,46 +211,61 @@
     ],
 )`, `cc_library_static(
     name = "static_lib_1",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
     srcs = ["static_lib_1.cc"],
 )`, `cc_library_static(
     name = "static_lib_2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
     srcs = ["static_lib_2.cc"],
 )`, `cc_library_static(
     name = "whole_static_lib_1",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
     srcs = ["whole_static_lib_1.cc"],
 )`, `cc_library_static(
     name = "whole_static_lib_2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
     srcs = ["whole_static_lib_2.cc"],
 )`},
+	})
+}
+
+func TestCcLibraryStaticSubpackage(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static subpackage test",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
+			// subsubpackage with subdirectory
+			"subpackage/subsubpackage/Android.bp":                         "",
+			"subpackage/subsubpackage/subsubpackage_header.h":             "",
+			"subpackage/subsubpackage/subdirectory/subdirectory_header.h": "",
+			// subsubsubpackage with subdirectory
+			"subpackage/subsubpackage/subsubsubpackage/Android.bp":                         "",
+			"subpackage/subsubpackage/subsubsubpackage/subsubsubpackage_header.h":          "",
+			"subpackage/subsubpackage/subsubsubpackage/subdirectory/subdirectory_header.h": "",
 		},
-		{
-			description:                        "cc_library_static subpackage test",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-				// subsubpackage with subdirectory
-				"subpackage/subsubpackage/Android.bp":                         "",
-				"subpackage/subsubpackage/subsubpackage_header.h":             "",
-				"subpackage/subsubpackage/subdirectory/subdirectory_header.h": "",
-				// subsubsubpackage with subdirectory
-				"subpackage/subsubpackage/subsubsubpackage/Android.bp":                         "",
-				"subpackage/subsubpackage/subsubsubpackage/subsubsubpackage_header.h":          "",
-				"subpackage/subsubpackage/subsubsubpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: [
@@ -253,70 +274,87 @@
 	"subpackage",
     ],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Isubpackage",
+        "-I$(BINDIR)/subpackage",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticExportIncludeDir(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static export include dir",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
 		},
-		{
-			description:                        "cc_library_static export include dir",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     export_include_dirs: ["subpackage"],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     includes = ["subpackage"],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticExportSystemIncludeDir(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static export system include dir",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
 		},
-		{
-			description:                        "cc_library_static export system include dir",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     export_system_include_dirs: ["subpackage"],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     includes = ["subpackage"],
     linkstatic = True,
 )`},
-		},
-		{
-			description:                        "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			dir:                                "subpackage",
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp": `
+	})
+}
+
+func TestCcLibraryStaticManyIncludeDirs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static include_dirs, local_include_dirs, export_include_dirs (b/183742505)",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		dir:                                "subpackage",
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp": `
 cc_library_static {
     name: "foo_static",
     // include_dirs are workspace/root relative
@@ -330,101 +368,123 @@
     include_build_directory: true,
     bazel_module: { bp2build_available: true },
 }`,
-				"subpackage/subsubpackage/header.h":          "",
-				"subpackage/subsubpackage2/header.h":         "",
-				"subpackage/exported_subsubpackage/header.h": "",
-				"subpackage2/header.h":                       "",
-				"subpackage3/subsubpackage/header.h":         "",
-			},
-			bp: soongCcLibraryStaticPreamble,
-			expectedBazelTargets: []string{`cc_library_static(
+			"subpackage/subsubpackage/header.h":          "",
+			"subpackage/subsubpackage2/header.h":         "",
+			"subpackage/exported_subsubpackage/header.h": "",
+			"subpackage2/header.h":                       "",
+			"subpackage3/subsubpackage/header.h":         "",
+		},
+		blueprint: soongCcLibraryStaticPreamble,
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Isubpackage/subsubpackage",
+        "-I$(BINDIR)/subpackage/subsubpackage",
         "-Isubpackage2",
+        "-I$(BINDIR)/subpackage2",
         "-Isubpackage3/subsubpackage",
+        "-I$(BINDIR)/subpackage3/subsubpackage",
         "-Isubpackage/subsubpackage2",
+        "-I$(BINDIR)/subpackage/subsubpackage2",
         "-Isubpackage",
+        "-I$(BINDIR)/subpackage",
     ],
     includes = ["./exported_subsubpackage"],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticIncludeBuildDirectoryDisabled(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static include_build_directory disabled",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
 		},
-		{
-			description:                        "cc_library_static include_build_directory disabled",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
     local_include_dirs: ["subpackage2"],
     include_build_directory: false,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Isubpackage",
+        "-I$(BINDIR)/subpackage",
         "-Isubpackage2",
+        "-I$(BINDIR)/subpackage2",
     ],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticIncludeBuildDirectoryEnabled(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static include_build_directory enabled",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		filesystem: map[string]string{
+			// subpackage with subdirectory
+			"subpackage/Android.bp":                         "",
+			"subpackage/subpackage_header.h":                "",
+			"subpackage2/Android.bp":                        "",
+			"subpackage2/subpackage2_header.h":              "",
+			"subpackage/subdirectory/subdirectory_header.h": "",
 		},
-		{
-			description:                        "cc_library_static include_build_directory enabled",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			filesystem: map[string]string{
-				// subpackage with subdirectory
-				"subpackage/Android.bp":                         "",
-				"subpackage/subpackage_header.h":                "",
-				"subpackage2/Android.bp":                        "",
-				"subpackage2/subpackage2_header.h":              "",
-				"subpackage/subdirectory/subdirectory_header.h": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     include_dirs: ["subpackage"], // still used, but local_include_dirs is recommended
     local_include_dirs: ["subpackage2"],
     include_build_directory: true,
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
     copts = [
         "-Isubpackage",
+        "-I$(BINDIR)/subpackage",
         "-Isubpackage2",
+        "-I$(BINDIR)/subpackage2",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     linkstatic = True,
 )`},
-		},
-		{
-			description:                        "cc_library_static arch-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticArchSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch-specific static_libs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static { name: "static_dep" }
 cc_library_static { name: "static_dep2" }
 cc_library_static {
     name: "foo_static",
     arch: { arm64: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
-    deps = select({
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = select({
         "//build/bazel/platforms/arch:arm64": [":static_dep"],
         "//conditions:default": [],
     }),
@@ -435,32 +495,44 @@
     }),
 )`, `cc_library_static(
     name = "static_dep",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`},
-		},
-		{
-			description:                        "cc_library_static os-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticOsSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static os-specific static_libs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static { name: "static_dep" }
 cc_library_static { name: "static_dep2" }
 cc_library_static {
     name: "foo_static",
     target: { android: { static_libs: ["static_dep"], whole_static_libs: ["static_dep2"] } },
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
-    deps = select({
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = select({
         "//build/bazel/platforms/os:android": [":static_dep"],
         "//conditions:default": [],
     }),
@@ -471,22 +543,31 @@
     }),
 )`, `cc_library_static(
     name = "static_dep",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`},
-		},
-		{
-			description:                        "cc_library_static base, arch and os-specific static_libs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
+	})
+}
+
+func TestCcLibraryStaticBaseArchOsSpecificStaticLib(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static base, arch and os-specific static_libs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static { name: "static_dep" }
 cc_library_static { name: "static_dep2" }
 cc_library_static { name: "static_dep3" }
@@ -498,10 +579,13 @@
     target: { android: { static_libs: ["static_dep3"] } },
     arch: { arm64: { static_libs: ["static_dep4"] } },
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
-    deps = [":static_dep"] + select({
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = [":static_dep"] + select({
         "//build/bazel/platforms/arch:arm64": [":static_dep4"],
         "//conditions:default": [],
     }) + select({
@@ -512,88 +596,115 @@
     whole_archive_deps = [":static_dep2"],
 )`, `cc_library_static(
     name = "static_dep",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep3",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep4",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticSimpleExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static simple exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":       "",
+			"foo-a.c":        "",
+			"foo-excluded.c": "",
 		},
-		{
-			description:                        "cc_library_static simple exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":       "",
-				"foo-a.c":        "",
-				"foo-excluded.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "foo-*.c"],
     exclude_srcs: ["foo-excluded.c"],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = [
+    srcs_c = [
         "common.c",
         "foo-a.c",
     ],
 )`},
+	})
+}
+
+func TestCcLibraryStaticOneArchSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static one arch specific srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":  "",
+			"foo-arm.c": "",
 		},
-		{
-			description:                        "cc_library_static one arch specific srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":  "",
-				"foo-arm.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c"],
     arch: { arm: { srcs: ["foo-arm.c"] } }
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["foo-arm.c"],
         "//conditions:default": [],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryStaticOneArchSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static one arch specific srcs and exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":           "",
+			"for-arm.c":          "",
+			"not-for-arm.c":      "",
+			"not-for-anything.c": "",
 		},
-		{
-			description:                        "cc_library_static one arch specific srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":           "",
-				"for-arm.c":          "",
-				"not-for-arm.c":      "",
-				"not-for-anything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -602,30 +713,36 @@
         arm: { srcs: ["for-arm.c"], exclude_srcs: ["not-for-arm.c"] },
     },
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-arm.c"],
         "//conditions:default": ["not-for-arm.c"],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryStaticTwoArchExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch specific exclude_srcs for 2 architectures",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":      "",
+			"for-arm.c":     "",
+			"for-x86.c":     "",
+			"not-for-arm.c": "",
+			"not-for-x86.c": "",
 		},
-		{
-			description:                        "cc_library_static arch specific exclude_srcs for 2 architectures",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":      "",
-				"for-arm.c":     "",
-				"for-x86.c":     "",
-				"not-for-arm.c": "",
-				"not-for-x86.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -635,11 +752,14 @@
         x86: { srcs: ["for-x86.c"], exclude_srcs: ["not-for-x86.c"] },
     },
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
             "not-for-x86.c",
@@ -654,26 +774,28 @@
         ],
     }),
 )`},
+	})
+}
+func TestCcLibraryStaticFourArchExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch specific exclude_srcs for 4 architectures",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":             "",
+			"for-arm.c":            "",
+			"for-arm64.c":          "",
+			"for-x86.c":            "",
+			"for-x86_64.c":         "",
+			"not-for-arm.c":        "",
+			"not-for-arm64.c":      "",
+			"not-for-x86.c":        "",
+			"not-for-x86_64.c":     "",
+			"not-for-everything.c": "",
 		},
-		{
-			description:                        "cc_library_static arch specific exclude_srcs for 4 architectures",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":             "",
-				"for-arm.c":            "",
-				"for-arm64.c":          "",
-				"for-x86.c":            "",
-				"for-x86_64.c":         "",
-				"not-for-arm.c":        "",
-				"not-for-arm64.c":      "",
-				"not-for-x86.c":        "",
-				"not-for-x86_64.c":     "",
-				"not-for-everything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -685,11 +807,14 @@
         x86_64: { srcs: ["for-x86_64.c"], exclude_srcs: ["not-for-x86_64.c"] },
 	},
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
             "not-for-arm64.c",
@@ -722,43 +847,133 @@
         ],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryStaticOneArchEmpty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static one arch empty",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.cc":       "",
+			"foo-no-arm.cc":   "",
+			"foo-excluded.cc": "",
 		},
-		{
-			description:                        "cc_library_static multiple dep same name panic",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem:                         map[string]string{},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.cc", "foo-*.cc"],
+    exclude_srcs: ["foo-excluded.cc"],
+    arch: {
+        arm: { exclude_srcs: ["foo-no-arm.cc"] },
+    },
+}`,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs = ["common.cc"] + select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//conditions:default": ["foo-no-arm.cc"],
+    }),
+)`},
+	})
+}
+
+func TestCcLibraryStaticOneArchEmptyOtherSet(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static one arch empty other set",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.cc":       "",
+			"foo-no-arm.cc":   "",
+			"x86-only.cc":     "",
+			"foo-excluded.cc": "",
+		},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.cc", "foo-*.cc"],
+    exclude_srcs: ["foo-excluded.cc"],
+    arch: {
+        arm: { exclude_srcs: ["foo-no-arm.cc"] },
+        x86: { srcs: ["x86-only.cc"] },
+    },
+}`,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs = ["common.cc"] + select({
+        "//build/bazel/platforms/arch:arm": [],
+        "//build/bazel/platforms/arch:x86": [
+            "foo-no-arm.cc",
+            "x86-only.cc",
+        ],
+        "//conditions:default": ["foo-no-arm.cc"],
+    }),
+)`},
+	})
+}
+
+func TestCcLibraryStaticMultipleDepSameName(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static multiple dep same name panic",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static { name: "static_dep" }
 cc_library_static {
     name: "foo_static",
     static_libs: ["static_dep", "static_dep"],
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
-    deps = [":static_dep"],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    implementation_deps = [":static_dep"],
     linkstatic = True,
 )`, `cc_library_static(
     name = "static_dep",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
 )`},
+	})
+}
+
+func TestCcLibraryStaticOneMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static 1 multilib srcs and exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":        "",
+			"for-lib32.c":     "",
+			"not-for-lib32.c": "",
 		},
-		{
-			description:                        "cc_library_static 1 multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":        "",
-				"for-lib32.c":     "",
-				"not-for-lib32.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static",
     srcs: ["common.c", "not-for-*.c"],
@@ -766,31 +981,37 @@
         lib32: { srcs: ["for-lib32.c"], exclude_srcs: ["not-for-lib32.c"] },
     },
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": ["for-lib32.c"],
         "//build/bazel/platforms/arch:x86": ["for-lib32.c"],
         "//conditions:default": ["not-for-lib32.c"],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryStaticTwoMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static 2 multilib srcs and exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":        "",
+			"for-lib32.c":     "",
+			"for-lib64.c":     "",
+			"not-for-lib32.c": "",
+			"not-for-lib64.c": "",
 		},
-		{
-			description:                        "cc_library_static 2 multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":        "",
-				"for-lib32.c":     "",
-				"for-lib64.c":     "",
-				"not-for-lib32.c": "",
-				"not-for-lib64.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
     name: "foo_static2",
     srcs: ["common.c", "not-for-*.c"],
@@ -799,11 +1020,14 @@
         lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
     },
 } `,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static2",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-lib32.c",
             "not-for-lib64.c",
@@ -826,30 +1050,33 @@
         ],
     }),
 )`},
+	})
+}
+
+func TestCcLibrarySTaticArchMultilibSrcsExcludeSrcs(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch and multilib srcs and exclude_srcs",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.c":             "",
+			"for-arm.c":            "",
+			"for-arm64.c":          "",
+			"for-x86.c":            "",
+			"for-x86_64.c":         "",
+			"for-lib32.c":          "",
+			"for-lib64.c":          "",
+			"not-for-arm.c":        "",
+			"not-for-arm64.c":      "",
+			"not-for-x86.c":        "",
+			"not-for-x86_64.c":     "",
+			"not-for-lib32.c":      "",
+			"not-for-lib64.c":      "",
+			"not-for-everything.c": "",
 		},
-		{
-			description:                        "cc_library_static arch and multilib srcs and exclude_srcs",
-			moduleTypeUnderTest:                "cc_library_static",
-			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
-			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
-			filesystem: map[string]string{
-				"common.c":             "",
-				"for-arm.c":            "",
-				"for-arm64.c":          "",
-				"for-x86.c":            "",
-				"for-x86_64.c":         "",
-				"for-lib32.c":          "",
-				"for-lib64.c":          "",
-				"not-for-arm.c":        "",
-				"not-for-arm64.c":      "",
-				"not-for-x86.c":        "",
-				"not-for-x86_64.c":     "",
-				"not-for-lib32.c":      "",
-				"not-for-lib64.c":      "",
-				"not-for-everything.c": "",
-			},
-			bp: soongCcLibraryStaticPreamble + `
+		blueprint: soongCcLibraryStaticPreamble + `
 cc_library_static {
    name: "foo_static3",
    srcs: ["common.c", "not-for-*.c"],
@@ -865,11 +1092,14 @@
        lib64: { srcs: ["for-lib64.c"], exclude_srcs: ["not-for-lib64.c"] },
    },
 }`,
-			expectedBazelTargets: []string{`cc_library_static(
+		expectedBazelTargets: []string{`cc_library_static(
     name = "foo_static3",
-    copts = ["-I."],
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
     linkstatic = True,
-    srcs = ["common.c"] + select({
+    srcs_c = ["common.c"] + select({
         "//build/bazel/platforms/arch:arm": [
             "for-arm.c",
             "for-lib32.c",
@@ -912,64 +1142,305 @@
         ],
     }),
 )`},
+	})
+}
+
+func TestCcLibraryStaticArchSrcsExcludeSrcsGeneratedFiles(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch srcs/exclude_srcs with generated files",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem: map[string]string{
+			"common.cpp":             "",
+			"for-x86.cpp":            "",
+			"not-for-x86.cpp":        "",
+			"not-for-everything.cpp": "",
+			"dep/Android.bp": `
+genrule {
+	name: "generated_src_other_pkg",
+	out: ["generated_src_other_pkg.cpp"],
+	cmd: "nothing to see here",
+}
+
+genrule {
+	name: "generated_hdr_other_pkg",
+	out: ["generated_hdr_other_pkg.cpp"],
+	cmd: "nothing to see here",
+}
+
+genrule {
+	name: "generated_hdr_other_pkg_x86",
+	out: ["generated_hdr_other_pkg_x86.cpp"],
+	cmd: "nothing to see here",
+}`,
 		},
-	}
+		blueprint: soongCcLibraryStaticPreamble + `
+genrule {
+    name: "generated_src",
+    out: ["generated_src.cpp"],
+    cmd: "nothing to see here",
+}
 
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
+genrule {
+    name: "generated_src_x86",
+    out: ["generated_src_x86.cpp"],
+    cmd: "nothing to see here",
+}
 
-		cc.RegisterCCBuildComponents(ctx)
-		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
-		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+genrule {
+    name: "generated_hdr",
+    out: ["generated_hdr.h"],
+    cmd: "nothing to see here",
+}
 
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
+cc_library_static {
+   name: "foo_static3",
+   srcs: ["common.cpp", "not-for-*.cpp"],
+   exclude_srcs: ["not-for-everything.cpp"],
+   generated_sources: ["generated_src", "generated_src_other_pkg"],
+   generated_headers: ["generated_hdr", "generated_hdr_other_pkg"],
+   arch: {
+       x86: {
+           srcs: ["for-x86.cpp"],
+           exclude_srcs: ["not-for-x86.cpp"],
+           generated_sources: ["generated_src_x86"],
+           generated_headers: ["generated_hdr_other_pkg_x86"],
+       },
+   },
+}
+`,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static3",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs = [
+        "//dep:generated_hdr_other_pkg",
+        "//dep:generated_src_other_pkg",
+        ":generated_hdr",
+        ":generated_src",
+        "common.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [
+            "//dep:generated_hdr_other_pkg_x86",
+            ":generated_src_x86",
+            "for-x86.cpp",
+        ],
+        "//conditions:default": ["not-for-x86.cpp"],
+    }),
+)`},
+	})
+}
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
+func TestCcLibraryStaticGetTargetProperties(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
+		description:                        "cc_library_static complex GetTargetProperties",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    target: {
+        android: {
+            srcs: ["android_src.c"],
+        },
+        android_arm: {
+            srcs: ["android_arm_src.c"],
+        },
+        android_arm64: {
+            srcs: ["android_arm64_src.c"],
+        },
+        android_x86: {
+            srcs: ["android_x86_src.c"],
+        },
+        android_x86_64: {
+            srcs: ["android_x86_64_src.c"],
+        },
+        linux_bionic_arm64: {
+            srcs: ["linux_bionic_arm64_src.c"],
+        },
+        linux_bionic_x86_64: {
+            srcs: ["linux_bionic_x86_64_src.c"],
+        },
+    },
+}`,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs_c = select({
+        "//build/bazel/platforms/os:android": ["android_src.c"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os_arch:android_arm": ["android_arm_src.c"],
+        "//build/bazel/platforms/os_arch:android_arm64": ["android_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86": ["android_x86_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86_64": ["android_x86_64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": ["linux_bionic_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_x86_64": ["linux_bionic_x86_64_src.c"],
+        "//conditions:default": [],
+    }),
+)`},
+	})
+}
+
+func TestCcLibraryStaticProductVariableSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+      malloc_zero_contents: {
+        cflags: ["-Wmalloc_zero_contents"],
+      },
+      binder32bit: {
+        cflags: ["-Wbinder32bit"],
+      },
+    },
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ] + select({
+        "//build/bazel/product_variables:binder32bit": ["-Wbinder32bit"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+    srcs_c = ["common.c"],
+)`},
+	})
+}
+
+func TestCcLibraryStaticProductVariableArchSpecificSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch-specific product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+    },
+		arch: {
+				arm64: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Warm64_malloc_not_svelte"],
+								},
+						},
+				},
+		},
+		multilib: {
+				lib32: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Wlib32_malloc_not_svelte"],
+								},
+						},
+				},
+		},
+		target: {
+				android: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Wandroid_malloc_not_svelte"],
+								},
+						},
 				}
-			}
-		}
-	}
+		},
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ] + select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-android": ["-Wandroid_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm64": ["-Warm64_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+    srcs_c = ["common.c"],
+)`},
+	})
+}
+
+func TestCcLibraryStaticProductVariableStringReplacement(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      platform_sdk_version: {
+          asflags: ["-DPLATFORM_SDK_VERSION=%d"],
+      },
+    },
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    asflags = select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//conditions:default": [],
+    }),
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs_c = ["common.c"],
+)`},
+	})
 }
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 9efdb53..57f75ea 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -15,35 +15,35 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/cc"
-	"fmt"
-	"strings"
-	"testing"
 )
 
-func TestCcObjectBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "simple cc_object generates cc_object with include header dep",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/foo.h":     "",
-				"a/b/bar.h":     "",
-				"a/b/exclude.c": "",
-				"a/b/c.c":       "",
-			},
-			blueprint: `cc_object {
+func registerCcObjectModuleTypes(ctx android.RegistrationContext) {
+	// Always register cc_defaults module factory
+	ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+}
+
+func runCcObjectTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, registerCcObjectModuleTypes, tc)
+}
+
+func TestCcObjectSimple(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "simple cc_object generates cc_object with include header dep",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		filesystem: map[string]string{
+			"a/b/foo.h":     "",
+			"a/b/bar.h":     "",
+			"a/b/exclude.c": "",
+			"a/b/c.c":       "",
+		},
+		blueprint: `cc_object {
     name: "foo",
     local_include_dirs: ["include"],
     cflags: [
@@ -57,7 +57,7 @@
     exclude_srcs: ["a/b/exclude.c"],
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
+		expectedBazelTargets: []string{`cc_object(
     name = "foo",
     copts = [
         "-fno-addrsig",
@@ -65,18 +65,23 @@
         "-Wall",
         "-Werror",
         "-Iinclude",
+        "-I$(BINDIR)/include",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     srcs = ["a/b/c.c"],
 )`,
-			},
 		},
-		{
-			description:                        "simple cc_object with defaults",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectDefaults(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "simple cc_object with defaults",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		blueprint: `cc_object {
     name: "foo",
     local_include_dirs: ["include"],
     srcs: [
@@ -101,7 +106,7 @@
     ],
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
+		expectedBazelTargets: []string{`cc_object(
     name = "foo",
     copts = [
         "-Wno-gcc-compat",
@@ -109,22 +114,26 @@
         "-Werror",
         "-fno-addrsig",
         "-Iinclude",
+        "-I$(BINDIR)/include",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     srcs = ["a/b/c.c"],
 )`,
-			},
+		}})
+}
+
+func TestCcObjectCcObjetDepsInObjs(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object with cc_object deps in objs props",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		filesystem: map[string]string{
+			"a/b/c.c": "",
+			"x/y/z.c": "",
 		},
-		{
-			description:                        "cc_object with cc_object deps in objs props",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/c.c": "",
-				"x/y/z.c": "",
-			},
-			blueprint: `cc_object {
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["a/b/c.c"],
     objs: ["bar"],
@@ -135,11 +144,12 @@
     srcs: ["x/y/z.c"],
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
+		expectedBazelTargets: []string{`cc_object(
     name = "bar",
     copts = [
         "-fno-addrsig",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     srcs = ["x/y/z.c"],
 )`, `cc_object(
@@ -147,40 +157,47 @@
     copts = [
         "-fno-addrsig",
         "-I.",
+        "-I$(BINDIR)/.",
     ],
     deps = [":bar"],
     srcs = ["a/b/c.c"],
 )`,
-			},
 		},
-		{
-			description:                        "cc_object with include_build_dir: false",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			filesystem: map[string]string{
-				"a/b/c.c": "",
-				"x/y/z.c": "",
-			},
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectIncludeBuildDirFalse(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object with include_build_dir: false",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		filesystem: map[string]string{
+			"a/b/c.c": "",
+			"x/y/z.c": "",
+		},
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["a/b/c.c"],
     include_build_directory: false,
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
+		expectedBazelTargets: []string{`cc_object(
     name = "foo",
     copts = ["-fno-addrsig"],
     srcs = ["a/b/c.c"],
 )`,
-			},
 		},
-		{
-			description:                        "cc_object with product variable",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectProductVariable(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object with product variable",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		blueprint: `cc_object {
     name: "foo",
     include_build_directory: false,
     product_variables: {
@@ -190,82 +207,25 @@
     },
 }
 `,
-			expectedBazelTargets: []string{`cc_object(
+		expectedBazelTargets: []string{`cc_object(
     name = "foo",
-    asflags = ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
+    asflags = select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//conditions:default": [],
+    }),
     copts = ["-fno-addrsig"],
 )`,
-			},
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-		// Always register cc_defaults module factory
-		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
 
-func TestCcObjectConfigurableAttributesBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "cc_object setting cflags for one arch",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+func TestCcObjectCflagsOneArch(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object setting cflags for one arch",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["a.cpp"],
     arch: {
@@ -278,12 +238,13 @@
     },
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
+		expectedBazelTargets: []string{
+			`cc_object(
     name = "foo",
     copts = [
         "-fno-addrsig",
         "-I.",
+        "-I$(BINDIR)/.",
     ] + select({
         "//build/bazel/platforms/arch:x86": ["-fPIC"],
         "//conditions:default": [],
@@ -293,14 +254,17 @@
         "//conditions:default": [],
     }),
 )`,
-			},
 		},
-		{
-			description:                        "cc_object setting cflags for 4 architectures",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectCflagsFourArch(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object setting cflags for 4 architectures",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["base.cpp"],
     arch: {
@@ -323,12 +287,13 @@
     },
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
+		expectedBazelTargets: []string{
+			`cc_object(
     name = "foo",
     copts = [
         "-fno-addrsig",
         "-I.",
+        "-I$(BINDIR)/.",
     ] + select({
         "//build/bazel/platforms/arch:arm": ["-Wall"],
         "//build/bazel/platforms/arch:arm64": ["-Wall"],
@@ -344,14 +309,17 @@
         "//conditions:default": [],
     }),
 )`,
-			},
 		},
-		{
-			description:                        "cc_object setting cflags for multiple OSes",
-			moduleTypeUnderTest:                "cc_object",
-			moduleTypeUnderTestFactory:         cc.ObjectFactory,
-			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
-			blueprint: `cc_object {
+	})
+}
+
+func TestCcObjectCflagsMultiOs(t *testing.T) {
+	runCcObjectTestCase(t, bp2buildTestCase{
+		description:                        "cc_object setting cflags for multiple OSes",
+		moduleTypeUnderTest:                "cc_object",
+		moduleTypeUnderTestFactory:         cc.ObjectFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+		blueprint: `cc_object {
     name: "foo",
     srcs: ["base.cpp"],
     target: {
@@ -367,12 +335,13 @@
     },
 }
 `,
-			expectedBazelTargets: []string{
-				`cc_object(
+		expectedBazelTargets: []string{
+			`cc_object(
     name = "foo",
     copts = [
         "-fno-addrsig",
         "-I.",
+        "-I$(BINDIR)/.",
     ] + select({
         "//build/bazel/platforms/os:android": ["-fPIC"],
         "//build/bazel/platforms/os:darwin": ["-Wall"],
@@ -381,51 +350,6 @@
     }),
     srcs = ["base.cpp"],
 )`,
-			},
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-		// Always register cc_defaults module factory
-		ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterBp2BuildConfig(bp2buildConfig)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 95a2747..c8105eb 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -11,69 +11,131 @@
 
 type selects map[string]reflect.Value
 
-func getStringListValues(list bazel.StringListAttribute) (reflect.Value, selects, selects) {
+func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value)
 	if !list.HasConfigurableValues() {
-		return value, nil, nil
+		return value, []selects{}
 	}
 
-	archSelects := map[string]reflect.Value{}
-	for arch, selectKey := range bazel.PlatformArchMap {
-		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch))
+	var ret []selects
+	for _, axis := range list.SortedConfigurationAxes() {
+		configToLists := list.ConfigurableValues[axis]
+		archSelects := map[string]reflect.Value{}
+		for config, labels := range configToLists {
+			selectKey := axis.SelectKey(config)
+			archSelects[selectKey] = reflect.ValueOf(labels)
+		}
+		if len(archSelects) > 0 {
+			ret = append(ret, archSelects)
+		}
 	}
 
-	osSelects := map[string]reflect.Value{}
-	for os, selectKey := range bazel.PlatformOsMap {
-		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os))
-	}
-
-	return value, archSelects, osSelects
+	return value, ret
 }
 
-func getLabelValue(label bazel.LabelAttribute) (reflect.Value, selects, selects) {
+func getLabelValue(label bazel.LabelAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(label.Value)
-	return value, nil, nil
+	if !label.HasConfigurableValues() {
+		return value, []selects{}
+	}
+
+	ret := selects{}
+	for _, axis := range label.SortedConfigurationAxes() {
+		configToLabels := label.ConfigurableValues[axis]
+		for config, labels := range configToLabels {
+			selectKey := axis.SelectKey(config)
+			ret[selectKey] = reflect.ValueOf(labels)
+		}
+	}
+
+	return value, []selects{ret}
 }
 
-func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, selects, selects) {
+func getBoolValue(boolAttr bazel.BoolAttribute) (reflect.Value, []selects) {
+	value := reflect.ValueOf(boolAttr.Value)
+	if !boolAttr.HasConfigurableValues() {
+		return value, []selects{}
+	}
+
+	ret := selects{}
+	for _, axis := range boolAttr.SortedConfigurationAxes() {
+		configToBools := boolAttr.ConfigurableValues[axis]
+		for config, bools := range configToBools {
+			selectKey := axis.SelectKey(config)
+			ret[selectKey] = reflect.ValueOf(bools)
+		}
+	}
+	// if there is a select, use the base value as the conditions default value
+	if len(ret) > 0 {
+		ret[bazel.ConditionsDefaultSelectKey] = value
+		value = reflect.Zero(value.Type())
+	}
+
+	return value, []selects{ret}
+}
+func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value.Includes)
-	if !list.HasConfigurableValues() {
-		return value, nil, nil
+	var ret []selects
+	for _, axis := range list.SortedConfigurationAxes() {
+		configToLabels := list.ConfigurableValues[axis]
+		if !configToLabels.HasConfigurableValues() {
+			continue
+		}
+		archSelects := map[string]reflect.Value{}
+		for config, labels := range configToLabels {
+			selectKey := axis.SelectKey(config)
+			if use, value := labelListSelectValue(selectKey, labels); use {
+				archSelects[selectKey] = value
+			}
+		}
+		if len(archSelects) > 0 {
+			ret = append(ret, archSelects)
+		}
 	}
 
-	archSelects := map[string]reflect.Value{}
-	for arch, selectKey := range bazel.PlatformArchMap {
-		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch).Includes)
-	}
-
-	osSelects := map[string]reflect.Value{}
-	for os, selectKey := range bazel.PlatformOsMap {
-		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os).Includes)
-	}
-
-	return value, archSelects, osSelects
+	return value, ret
 }
 
+func labelListSelectValue(selectKey string, list bazel.LabelList) (bool, reflect.Value) {
+	if selectKey == bazel.ConditionsDefaultSelectKey || len(list.Includes) > 0 {
+		return true, reflect.ValueOf(list.Includes)
+	} else if len(list.Excludes) > 0 {
+		// if there is still an excludes -- we need to have an empty list for this select & use the
+		// value in conditions default Includes
+		return true, reflect.ValueOf([]string{})
+	}
+	return false, reflect.Zero(reflect.TypeOf([]string{}))
+}
+
+var (
+	emptyBazelList = "[]"
+	bazelNone      = "None"
+)
+
 // prettyPrintAttribute converts an Attribute to its Bazel syntax. May contain
 // select statements.
 func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
 	var value reflect.Value
-	var archSelects, osSelects selects
-	var defaultSelectValue string
+	var configurableAttrs []selects
+	var defaultSelectValue *string
 	switch list := v.(type) {
 	case bazel.StringListAttribute:
-		value, archSelects, osSelects = getStringListValues(list)
-		defaultSelectValue = "[]"
+		value, configurableAttrs = getStringListValues(list)
+		defaultSelectValue = &emptyBazelList
 	case bazel.LabelListAttribute:
-		value, archSelects, osSelects = getLabelListValues(list)
-		defaultSelectValue = "[]"
+		value, configurableAttrs = getLabelListValues(list)
+		defaultSelectValue = &emptyBazelList
 	case bazel.LabelAttribute:
-		value, archSelects, osSelects = getLabelValue(list)
-		defaultSelectValue = "None"
+		value, configurableAttrs = getLabelValue(list)
+		defaultSelectValue = &bazelNone
+	case bazel.BoolAttribute:
+		value, configurableAttrs = getBoolValue(list)
+		defaultSelectValue = &bazelNone
 	default:
 		return "", fmt.Errorf("Not a supported Bazel attribute type: %s", v)
 	}
 
+	var err error
 	ret := ""
 	if value.Kind() != reflect.Invalid {
 		s, err := prettyPrint(value, indent)
@@ -84,7 +146,7 @@
 		ret += s
 	}
 	// Convenience function to append selects components to an attribute value.
-	appendSelects := func(selectsData selects, defaultValue, s string) (string, error) {
+	appendSelects := func(selectsData selects, defaultValue *string, s string) (string, error) {
 		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent)
 		if err != nil {
 			return "", err
@@ -97,28 +159,26 @@
 		return s, nil
 	}
 
-	ret, err := appendSelects(archSelects, defaultSelectValue, ret)
-	if err != nil {
-		return "", err
+	for _, configurableAttr := range configurableAttrs {
+		ret, err = appendSelects(configurableAttr, defaultSelectValue, ret)
+		if err != nil {
+			return "", err
+		}
 	}
 
-	ret, err = appendSelects(osSelects, defaultSelectValue, ret)
-	return ret, err
+	return ret, nil
 }
 
 // prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
 // to construct a select map for any kind of attribute type.
-func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue string, indent int) (string, error) {
+func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue *string, indent int) (string, error) {
 	if selectMap == nil {
 		return "", nil
 	}
 
-	// addConditionsDefault := false
-	conditionsDefaultKey := bazel.PlatformArchMap[bazel.CONDITIONS_DEFAULT]
-
 	var selects string
 	for _, selectKey := range android.SortedStringKeys(selectMap) {
-		if selectKey == conditionsDefaultKey {
+		if selectKey == bazel.ConditionsDefaultSelectKey {
 			// Handle default condition later.
 			continue
 		}
@@ -148,18 +208,18 @@
 	ret += selects
 
 	// Handle the default condition
-	s, err := prettyPrintSelectEntry(selectMap[conditionsDefaultKey], conditionsDefaultKey, indent)
+	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent)
 	if err != nil {
 		return "", err
 	}
-	if s == "" {
-		// Print an explicit empty list (the default value) even if the value is
-		// empty, to avoid errors about not finding a configuration that matches.
-		ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)
-	} else {
+	if s != "" {
 		// Print the custom default value.
 		ret += s
 		ret += ",\n"
+	} else if defaultValue != nil {
+		// Print an explicit empty list (the default value) even if the value is
+		// empty, to avoid errors about not finding a configuration that matches.
+		ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), bazel.ConditionsDefaultSelectKey, *defaultValue)
 	}
 
 	ret += makeIndent(indent)
diff --git a/bp2build/constants.go b/bp2build/constants.go
index 70f320e..4870dff 100644
--- a/bp2build/constants.go
+++ b/bp2build/constants.go
@@ -19,7 +19,10 @@
 	// be preferred for use within a Bazel build.
 
 	// The file name used for automatically generated files.
-	GeneratedBuildFileName = "BUILD"
+	GeneratedBuildFileName = "BUILD.bazel"
+
 	// The file name used for hand-crafted build targets.
+	// NOTE: It is okay that this matches GeneratedBuildFileName, since we generate BUILD files in a different directory to source files
+	// FIXME: Because there are hundreds of existing BUILD.bazel files in the AOSP tree, we should pick another name here, like BUILD.android
 	HandcraftedBuildFileName = "BUILD.bazel"
 )
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index d67ab3d..bced4c1 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -2,9 +2,9 @@
 
 import (
 	"android/soong/android"
+	"android/soong/cc/config"
 	"fmt"
 	"reflect"
-	"sort"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -16,6 +16,15 @@
 	Contents string
 }
 
+func CreateSoongInjectionFiles() []BazelFile {
+	var files []BazelFile
+
+	files = append(files, newFile("cc_toolchain", "BUILD", "")) // Creates a //cc_toolchain package.
+	files = append(files, newFile("cc_toolchain", "constants.bzl", config.BazelCcToolchainVars()))
+
+	return files
+}
+
 func CreateBazelFiles(
 	ruleShims map[string]RuleShim,
 	buildToTargets map[string]BazelTargets,
@@ -49,27 +58,33 @@
 func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
 	files := make([]BazelFile, 0, len(buildToTargets))
 	for _, dir := range android.SortedStringKeys(buildToTargets) {
-		if mode == Bp2Build && !android.ShouldWriteBuildFileForDir(dir) {
+		if mode == Bp2Build && android.ShouldKeepExistingBuildFileForDir(dir) {
 			fmt.Printf("[bp2build] Not writing generated BUILD file for dir: '%s'\n", dir)
 			continue
 		}
 		targets := buildToTargets[dir]
-		sort.Slice(targets, func(i, j int) bool {
-			// this will cover all bp2build generated targets
-			if targets[i].name < targets[j].name {
-				return true
-			}
-			// give a strict ordering to content from hand-crafted targets
-			return targets[i].content < targets[j].content
-		})
-		content := soongModuleLoad
+		targets.sort()
+
+		var content string
 		if mode == Bp2Build {
-			content = `# This file was automatically generated by bp2build for the Bazel migration project.
-# Feel free to edit or test it, but do *not* check it into your version control system.`
-			content += "\n\n"
-			content += "package(default_visibility = [\"//visibility:public\"])"
-			content += "\n\n"
+			content = `# READ THIS FIRST:
+# This file was automatically generated by bp2build for the Bazel migration project.
+# Feel free to edit or test it, but do *not* check it into your version control system.
+`
+			if targets.hasHandcraftedTargets() {
+				// For BUILD files with both handcrafted and generated targets,
+				// don't hardcode actual content, like package() declarations.
+				// Leave that responsibility to the checked-in BUILD file
+				// instead.
+				content += `# This file contains generated targets and handcrafted targets that are manually managed in the source tree.`
+			} else {
+				// For fully-generated BUILD files, hardcode the default visibility.
+				content += "package(default_visibility = [\"//visibility:public\"])"
+			}
+			content += "\n"
 			content += targets.LoadStatements()
+		} else if mode == QueryView {
+			content = soongModuleLoad
 		}
 		if content != "" {
 			// If there are load statements, add a couple of newlines.
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 262a488..0931ff7 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -29,7 +29,7 @@
 	expectedFilePaths := []bazelFilepath{
 		{
 			dir:      "",
-			basename: "BUILD",
+			basename: "BUILD.bazel",
 		},
 		{
 			dir:      "",
@@ -37,7 +37,7 @@
 		},
 		{
 			dir:      bazelRulesSubDir,
-			basename: "BUILD",
+			basename: "BUILD.bazel",
 		},
 		{
 			dir:      bazelRulesSubDir,
@@ -69,7 +69,7 @@
 
 		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
 			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
-		} else if actualFile.Basename == "BUILD" || actualFile.Basename == "WORKSPACE" {
+		} else if actualFile.Basename == "BUILD.bazel" || actualFile.Basename == "WORKSPACE" {
 			if actualFile.Contents != "" {
 				t.Errorf("Expected %s to have no content.", actualFile)
 			}
@@ -79,9 +79,33 @@
 	}
 }
 
-func TestCreateBazelFiles_Bp2Build_CreatesNoFilesWithNoTargets(t *testing.T) {
-	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, Bp2Build)
-	if len(files) != 0 {
-		t.Errorf("Expected no files, got %d", len(files))
+func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
+	files := CreateSoongInjectionFiles()
+
+	expectedFilePaths := []bazelFilepath{
+		{
+			dir:      "cc_toolchain",
+			basename: "BUILD",
+		},
+		{
+			dir:      "cc_toolchain",
+			basename: "constants.bzl",
+		},
+	}
+
+	if len(files) != len(expectedFilePaths) {
+		t.Errorf("Expected %d file, got %d", len(expectedFilePaths), len(files))
+	}
+
+	for i := range files {
+		actualFile, expectedFile := files[i], expectedFilePaths[i]
+
+		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
+			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
+		}
+
+		if expectedFile.basename != "BUILD" && actualFile.Contents == "" {
+			t.Errorf("Contents of %s unexpected empty.", actualFile)
+		}
 	}
 }
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 2054e06..7bedf71 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -1,36 +1,31 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/python"
-	"fmt"
-	"strings"
-	"testing"
 )
 
-func TestPythonBinaryHost(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		blueprint                          string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-	}{
-		{
-			description:                        "simple python_binary_host converts to a native py_binary",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			filesystem: map[string]string{
-				"a.py":           "",
-				"b/c.py":         "",
-				"b/d.py":         "",
-				"b/e.py":         "",
-				"files/data.txt": "",
-			},
-			blueprint: `python_binary_host {
+func runPythonTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestPythonBinaryHostSimple(t *testing.T) {
+	runPythonTestCase(t, bp2buildTestCase{
+		description:                        "simple python_binary_host converts to a native py_binary",
+		moduleTypeUnderTest:                "python_binary_host",
+		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		filesystem: map[string]string{
+			"a.py":           "",
+			"b/c.py":         "",
+			"b/d.py":         "",
+			"b/e.py":         "",
+			"files/data.txt": "",
+		},
+		blueprint: `python_binary_host {
     name: "foo",
     main: "a.py",
     srcs: ["**/*.py"],
@@ -39,7 +34,7 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			expectedBazelTargets: []string{`py_binary(
+		expectedBazelTargets: []string{`py_binary(
     name = "foo",
     data = ["files/data.txt"],
     main = "a.py",
@@ -49,14 +44,17 @@
         "b/d.py",
     ],
 )`,
-			},
 		},
-		{
-			description:                        "py2 python_binary_host",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			blueprint: `python_binary_host {
+	})
+}
+
+func TestPythonBinaryHostPy2(t *testing.T) {
+	runPythonTestCase(t, bp2buildTestCase{
+		description:                        "py2 python_binary_host",
+		moduleTypeUnderTest:                "python_binary_host",
+		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -71,19 +69,22 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			expectedBazelTargets: []string{`py_binary(
+		expectedBazelTargets: []string{`py_binary(
     name = "foo",
     python_version = "PY2",
     srcs = ["a.py"],
 )`,
-			},
 		},
-		{
-			description:                        "py3 python_binary_host",
-			moduleTypeUnderTest:                "python_binary_host",
-			moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
-			moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
-			blueprint: `python_binary_host {
+	})
+}
+
+func TestPythonBinaryHostPy3(t *testing.T) {
+	runPythonTestCase(t, bp2buildTestCase{
+		description:                        "py3 python_binary_host",
+		moduleTypeUnderTest:                "python_binary_host",
+		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		blueprint: `python_binary_host {
     name: "foo",
     srcs: ["a.py"],
     version: {
@@ -98,60 +99,12 @@
     bazel_module: { bp2build_available: true },
 }
 `,
-			expectedBazelTargets: []string{
-				// python_version is PY3 by default.
-				`py_binary(
+		expectedBazelTargets: []string{
+			// python_version is PY3 by default.
+			`py_binary(
     name = "foo",
     srcs = ["a.py"],
 )`,
-			},
 		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.blueprint, filesystem)
-		ctx := android.NewTestContext(config)
-
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, dir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			fmt.Println(bazelTargets)
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
diff --git a/bp2build/sh_conversion_test.go b/bp2build/sh_conversion_test.go
index 37f542e..82e0a14 100644
--- a/bp2build/sh_conversion_test.go
+++ b/bp2build/sh_conversion_test.go
@@ -15,10 +15,10 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/sh"
-	"strings"
-	"testing"
 )
 
 func TestShBinaryLoadStatement(t *testing.T) {
@@ -46,88 +46,27 @@
 			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
 		}
 	}
-
 }
 
-func TestShBinaryBp2Build(t *testing.T) {
-	testCases := []struct {
-		description                        string
-		moduleTypeUnderTest                string
-		moduleTypeUnderTestFactory         android.ModuleFactory
-		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
-		preArchMutators                    []android.RegisterMutatorFunc
-		depsMutators                       []android.RegisterMutatorFunc
-		bp                                 string
-		expectedBazelTargets               []string
-		filesystem                         map[string]string
-		dir                                string
-	}{
-		{
-			description:                        "sh_binary test",
-			moduleTypeUnderTest:                "sh_binary",
-			moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
-			moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
-			bp: `sh_binary {
+func runShBinaryTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, tc)
+}
+
+func TestShBinarySimple(t *testing.T) {
+	runShBinaryTestCase(t, bp2buildTestCase{
+		description:                        "sh_binary test",
+		moduleTypeUnderTest:                "sh_binary",
+		moduleTypeUnderTestFactory:         sh.ShBinaryFactory,
+		moduleTypeUnderTestBp2BuildMutator: sh.ShBinaryBp2Build,
+		blueprint: `sh_binary {
     name: "foo",
     src: "foo.sh",
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{`sh_binary(
+		expectedBazelTargets: []string{`sh_binary(
     name = "foo",
     srcs = ["foo.sh"],
 )`},
-		},
-	}
-
-	dir := "."
-	for _, testCase := range testCases {
-		filesystem := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.filesystem {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
-			}
-			filesystem[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
-
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if Errored(t, testCase.description, errs) {
-			continue
-		}
-
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
-		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
-		} else {
-			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
-					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
-					)
-				}
-			}
-		}
-	}
+	})
 }
diff --git a/bp2build/testing.go b/bp2build/testing.go
index b925682..861f7d2 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -1,6 +1,8 @@
 package bp2build
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/bazel"
 )
@@ -10,6 +12,8 @@
 	bp2buildConfig = android.Bp2BuildConfig{
 		android.BP2BUILD_TOPLEVEL: android.Bp2BuildDefaultTrueRecursively,
 	}
+
+	buildDir string
 )
 
 type nestedProps struct {
@@ -39,6 +43,17 @@
 	props customProps
 }
 
+func errored(t *testing.T, desc string, errs []error) bool {
+	t.Helper()
+	if len(errs) > 0 {
+		for _, err := range errs {
+			t.Errorf("%s: %s", desc, err)
+		}
+		return true
+	}
+	return false
+}
+
 // OutputFiles is needed because some instances of this module use dist with a
 // tag property which requires the module implements OutputFileProducer.
 func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
@@ -142,9 +157,11 @@
 
 		paths := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, m.props.Arch_paths))
 
-		for arch, props := range m.GetArchProperties(ctx, &customProps{}) {
-			if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil {
-				paths.SetValueForArch(arch.Name, android.BazelLabelForModuleSrc(ctx, archProps.Arch_paths))
+		for axis, configToProps := range m.GetArchVariantProperties(ctx, &customProps{}) {
+			for config, props := range configToProps {
+				if archProps, ok := props.(*customProps); ok && archProps.Arch_paths != nil {
+					paths.SetSelectValue(axis, config, android.BazelLabelForModuleSrc(ctx, archProps.Arch_paths))
+				}
 			}
 		}
 
diff --git a/build_kzip.bash b/build_kzip.bash
index a09335e..5655067 100755
--- a/build_kzip.bash
+++ b/build_kzip.bash
@@ -6,6 +6,8 @@
 # The following environment variables affect the result:
 #   BUILD_NUMBER          build number, used to generate unique ID (will use UUID if not set)
 #   SUPERPROJECT_SHA      superproject sha, used to generate unique id (will use BUILD_NUMBER if not set)
+#   SUPERPROJECT_REVISION superproject revision, used for unique id if defined as a sha
+#   KZIP_NAME             name of the output file (will use SUPERPROJECT_REVISION|SUPERPROJECT_SHA|BUILD_NUMBER|UUID if not set)
 #   DIST_DIR              where the resulting all.kzip will be placed
 #   KYTHE_KZIP_ENCODING   proto or json (proto is default)
 #   KYTHE_JAVA_SOURCE_BATCH_SIZE maximum number of the Java source files in a compilation unit
@@ -14,8 +16,16 @@
 #   TARGET_PRODUCT        target device name, e.g., 'aosp_blueline'
 #   XREF_CORPUS           source code repository URI, e.g., 'android.googlesource.com/platform/superproject'
 
-: ${BUILD_NUMBER:=$(uuidgen)}
-: ${SUPERPROJECT_SHA:=$BUILD_NUMBER}
+# If the SUPERPROJECT_REVISION is defined as a sha, use this as the default value if no
+# SUPERPROJECT_SHA is specified.
+if [[ ${SUPERPROJECT_REVISION:-} =~ [0-9a-f]{40} ]]; then
+  : ${KZIP_NAME:=${SUPERPROJECT_REVISION:-}}
+fi
+
+: ${KZIP_NAME:=${SUPERPROJECT_SHA:-}}
+: ${KZIP_NAME:=${BUILD_NUMBER:-}}
+: ${KZIP_NAME:=$(uuidgen)}
+
 : ${KYTHE_JAVA_SOURCE_BATCH_SIZE:=500}
 : ${KYTHE_KZIP_ENCODING:=proto}
 : ${XREF_CORPUS:?should be set}
@@ -50,6 +60,6 @@
 
 # Pack
 # TODO(asmundak): this should be done by soong.
-declare -r allkzip="$SUPERPROJECT_SHA.kzip"
+declare -r allkzip="$KZIP_NAME.kzip"
 "$out/soong/host/linux-x86/bin/merge_zips" "$DIST_DIR/$allkzip" @<(find "$out" -name '*.kzip')
 
diff --git a/build_test.bash b/build_test.bash
index 3230f2d..b039285 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -49,8 +49,12 @@
 esac
 
 echo
+echo "Free disk space:"
+df -h
+
+echo
 echo "Running Bazel smoke test..."
-"${TOP}/tools/bazel" --batch --max_idle_secs=1 info
+STANDALONE_BAZEL=true "${TOP}/tools/bazel" --batch --max_idle_secs=1 info
 
 echo
 echo "Running Soong test..."
diff --git a/cc/Android.bp b/cc/Android.bp
index c32cca8..46740dc 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -65,9 +65,10 @@
         "test.go",
         "toolchain_library.go",
 
-        "ndk_prebuilt.go",
+        "ndk_abi.go",
         "ndk_headers.go",
         "ndk_library.go",
+        "ndk_prebuilt.go",
         "ndk_sysroot.go",
 
         "llndk_library.go",
@@ -92,6 +93,7 @@
         "object_test.go",
         "prebuilt_test.go",
         "proto_test.go",
+        "sanitize_test.go",
         "test_data_test.go",
         "vendor_public_library_test.go",
         "vendor_snapshot_test.go",
diff --git a/cc/binary.go b/cc/binary.go
index 999b82c..3aa3fdf 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -149,11 +149,11 @@
 	if ctx.toolchain().Bionic() {
 		if !Bool(binary.baseLinker.Properties.Nocrt) {
 			if binary.static() {
-				deps.CrtBegin = "crtbegin_static"
+				deps.CrtBegin = []string{"crtbegin_static"}
 			} else {
-				deps.CrtBegin = "crtbegin_dynamic"
+				deps.CrtBegin = []string{"crtbegin_dynamic"}
 			}
-			deps.CrtEnd = "crtend_android"
+			deps.CrtEnd = []string{"crtend_android"}
 		}
 
 		if binary.static() {
@@ -178,7 +178,7 @@
 		// the kernel before jumping to the embedded linker.
 		if ctx.Os() == android.LinuxBionic && !binary.static() {
 			deps.DynamicLinker = "linker"
-			deps.LinkerFlagsFile = "host_bionic_linker_flags"
+			deps.CrtBegin = append(deps.CrtBegin, "host_bionic_linker_script")
 		}
 	}
 
@@ -345,12 +345,6 @@
 
 	var linkerDeps android.Paths
 
-	// Add flags from linker flags file.
-	if deps.LinkerFlagsFile.Valid() {
-		flags.Local.LdFlags = append(flags.Local.LdFlags, "$$(cat "+deps.LinkerFlagsFile.String()+")")
-		linkerDeps = append(linkerDeps, deps.LinkerFlagsFile.Path())
-	}
-
 	if flags.DynamicLinker != "" {
 		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-dynamic-linker,"+flags.DynamicLinker)
 	} else if ctx.toolchain().Bionic() && !binary.static() {
@@ -401,16 +395,18 @@
 		}
 	}
 
+	var validations android.WritablePaths
+
 	// Handle host bionic linker symbols.
 	if ctx.Os() == android.LinuxBionic && !binary.static() {
-		injectedOutputFile := outputFile
-		outputFile = android.PathForModuleOut(ctx, "prelinker", fileName)
+		verifyFile := android.PathForModuleOut(ctx, "host_bionic_verify.stamp")
 
 		if !deps.DynamicLinker.Valid() {
 			panic("Non-static host bionic modules must have a dynamic linker")
 		}
 
-		binary.injectHostBionicLinkerSymbols(ctx, outputFile, deps.DynamicLinker.Path(), injectedOutputFile)
+		binary.verifyHostBionicLinker(ctx, outputFile, deps.DynamicLinker.Path(), verifyFile)
+		validations = append(validations, verifyFile)
 	}
 
 	var sharedLibs android.Paths
@@ -430,7 +426,7 @@
 	// Register link action.
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
-		builderFlags, outputFile, nil)
+		builderFlags, outputFile, nil, validations)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -532,19 +528,19 @@
 }
 
 func init() {
-	pctx.HostBinToolVariable("hostBionicSymbolsInjectCmd", "host_bionic_inject")
+	pctx.HostBinToolVariable("verifyHostBionicCmd", "host_bionic_verify")
 }
 
-var injectHostBionicSymbols = pctx.AndroidStaticRule("injectHostBionicSymbols",
+var verifyHostBionic = pctx.AndroidStaticRule("verifyHostBionic",
 	blueprint.RuleParams{
-		Command:     "$hostBionicSymbolsInjectCmd -i $in -l $linker -o $out",
-		CommandDeps: []string{"$hostBionicSymbolsInjectCmd"},
+		Command:     "$verifyHostBionicCmd -i $in -l $linker && touch $out",
+		CommandDeps: []string{"$verifyHostBionicCmd"},
 	}, "linker")
 
-func (binary *binaryDecorator) injectHostBionicLinkerSymbols(ctx ModuleContext, in, linker android.Path, out android.WritablePath) {
+func (binary *binaryDecorator) verifyHostBionicLinker(ctx ModuleContext, in, linker android.Path, out android.WritablePath) {
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        injectHostBionicSymbols,
-		Description: "inject host bionic symbols",
+		Rule:        verifyHostBionic,
+		Description: "verify host bionic",
 		Input:       in,
 		Implicit:    linker,
 		Output:      out,
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 7d01986..76c5f3b 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -14,9 +14,14 @@
 package cc
 
 import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
 	"android/soong/android"
 	"android/soong/bazel"
-	"path/filepath"
+
+	"github.com/google/blueprint/proptools"
 )
 
 // bp2build functions and helpers for converting cc_* modules to Bazel.
@@ -48,135 +53,318 @@
 
 	var allDeps []string
 
-	for _, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
-		// arch specific linker props
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
-			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
+	for _, configToProps := range module.GetArchVariantProperties(ctx, &BaseCompilerProperties{}) {
+		for _, props := range configToProps {
+			if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+				allDeps = append(allDeps, baseCompilerProps.Generated_headers...)
+				allDeps = append(allDeps, baseCompilerProps.Generated_sources...)
+			}
 		}
 	}
 
-	for _, p := range module.GetArchProperties(ctx, &BaseLinkerProperties{}) {
-		// arch specific linker props
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
-			allDeps = append(allDeps, baseLinkerProps.Static_libs...)
-			allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
+	for _, configToProps := range module.GetArchVariantProperties(ctx, &BaseLinkerProperties{}) {
+		for _, props := range configToProps {
+			if baseLinkerProps, ok := props.(*BaseLinkerProperties); ok {
+				allDeps = append(allDeps, baseLinkerProps.Header_libs...)
+				allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
+				allDeps = append(allDeps, baseLinkerProps.Static_libs...)
+				allDeps = append(allDeps, baseLinkerProps.Exclude_static_libs...)
+				allDeps = append(allDeps, baseLinkerProps.Whole_static_libs...)
+				allDeps = append(allDeps, baseLinkerProps.Shared_libs...)
+				allDeps = append(allDeps, baseLinkerProps.Exclude_shared_libs...)
+			}
 		}
 	}
 
 	// Deps in the static: { .. } and shared: { .. } props of a cc_library.
 	if lib, ok := module.compiler.(*libraryDecorator); ok {
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Static_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Whole_static_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.Shared_libs...)
-		allDeps = append(allDeps, lib.SharedProperties.Shared.System_shared_libs...)
+		appendDeps := func(deps []string, p StaticOrSharedProperties) []string {
+			deps = append(deps, p.Static_libs...)
+			deps = append(deps, p.Whole_static_libs...)
+			deps = append(deps, p.Shared_libs...)
+			return deps
+		}
 
-		allDeps = append(allDeps, lib.StaticProperties.Static.Static_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.Whole_static_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.Shared_libs...)
-		allDeps = append(allDeps, lib.StaticProperties.Static.System_shared_libs...)
+		allDeps = appendDeps(allDeps, lib.SharedProperties.Shared)
+		allDeps = appendDeps(allDeps, lib.StaticProperties.Static)
+
+		// TODO(b/186024507, b/186489250): Temporarily exclude adding
+		// system_shared_libs deps until libc and libm builds.
+		// allDeps = append(allDeps, lib.SharedProperties.Shared.System_shared_libs...)
+		// allDeps = append(allDeps, lib.StaticProperties.Static.System_shared_libs...)
+
+		// Deps in the target/arch nested static: { .. } and shared: { .. } props of a cc_library.
+		// target: { <target>: shared: { ... } }
+		for _, configToProps := range module.GetArchVariantProperties(ctx, &SharedProperties{}) {
+			for _, props := range configToProps {
+				if p, ok := props.(*SharedProperties); ok {
+					allDeps = appendDeps(allDeps, p.Shared)
+				}
+			}
+		}
+
+		for _, configToProps := range module.GetArchVariantProperties(ctx, &StaticProperties{}) {
+			for _, props := range configToProps {
+				if p, ok := props.(*StaticProperties); ok {
+					allDeps = appendDeps(allDeps, p.Static)
+				}
+			}
+		}
+	}
+
+	// product variables only support a limited set of fields, this is the full list of field names
+	// related to cc module dependency management that are supported.
+	productVariableDepFields := [4]string{
+		"Shared_libs",
+		"Static_libs",
+		"Exclude_static_libs",
+		"Whole_static_libs",
+	}
+
+	productVariableProps := android.ProductVariableProperties(ctx)
+	for _, name := range productVariableDepFields {
+		props, exists := productVariableProps[name]
+		if !exists {
+			continue
+		}
+		for _, prop := range props {
+			if p, ok := prop.Property.([]string); !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", name)
+			} else {
+				allDeps = append(allDeps, p...)
+			}
+		}
 	}
 
 	ctx.AddDependency(module, nil, android.SortedUniqueStrings(allDeps)...)
 }
 
-type sharedAttributes struct {
-	copts            bazel.StringListAttribute
-	srcs             bazel.LabelListAttribute
-	staticDeps       bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
+// staticOrSharedAttributes are the Bazel-ified versions of StaticOrSharedProperties --
+// properties which apply to either the shared or static version of a cc_library module.
+type staticOrSharedAttributes struct {
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+	Copts   bazel.StringListAttribute
+
+	Static_deps        bazel.LabelListAttribute
+	Dynamic_deps       bazel.LabelListAttribute
+	Whole_archive_deps bazel.LabelListAttribute
+}
+
+func groupSrcsByExtension(ctx android.TopDownMutatorContext, srcs bazel.LabelListAttribute) (cppSrcs, cSrcs, asSrcs bazel.LabelListAttribute) {
+	// Branch srcs into three language-specific groups.
+	// C++ is the "catch-all" group, and comprises generated sources because we don't
+	// know the language of these sources until the genrule is executed.
+	// TODO(b/190006308): Handle language detection of sources in a Bazel rule.
+	isCSrcOrFilegroup := func(s string) bool {
+		return strings.HasSuffix(s, ".c") || strings.HasSuffix(s, "_c_srcs")
+	}
+
+	isAsmSrcOrFilegroup := func(s string) bool {
+		return strings.HasSuffix(s, ".S") || strings.HasSuffix(s, ".s") || strings.HasSuffix(s, "_as_srcs")
+	}
+
+	// Check that a module is a filegroup type named <label>.
+	isFilegroupNamed := func(m android.Module, fullLabel string) bool {
+		if ctx.OtherModuleType(m) != "filegroup" {
+			return false
+		}
+		labelParts := strings.Split(fullLabel, ":")
+		if len(labelParts) > 2 {
+			// There should not be more than one colon in a label.
+			panic(fmt.Errorf("%s is not a valid Bazel label for a filegroup", fullLabel))
+		} else {
+			return m.Name() == labelParts[len(labelParts)-1]
+		}
+	}
+
+	// Convert the filegroup dependencies into the extension-specific filegroups
+	// filtered in the filegroup.bzl macro.
+	cppFilegroup := func(label string) string {
+		ctx.VisitDirectDeps(func(m android.Module) {
+			if isFilegroupNamed(m, label) {
+				label = label + "_cpp_srcs"
+				return
+			}
+		})
+		return label
+	}
+	cFilegroup := func(label string) string {
+		ctx.VisitDirectDeps(func(m android.Module) {
+			if isFilegroupNamed(m, label) {
+				label = label + "_c_srcs"
+				return
+			}
+		})
+		return label
+	}
+	asFilegroup := func(label string) string {
+		ctx.VisitDirectDeps(func(m android.Module) {
+			if isFilegroupNamed(m, label) {
+				label = label + "_as_srcs"
+				return
+			}
+		})
+		return label
+	}
+
+	cSrcs = bazel.MapLabelListAttribute(srcs, cFilegroup)
+	cSrcs = bazel.FilterLabelListAttribute(cSrcs, isCSrcOrFilegroup)
+
+	asSrcs = bazel.MapLabelListAttribute(srcs, asFilegroup)
+	asSrcs = bazel.FilterLabelListAttribute(asSrcs, isAsmSrcOrFilegroup)
+
+	cppSrcs = bazel.MapLabelListAttribute(srcs, cppFilegroup)
+	cppSrcs = bazel.SubtractBazelLabelListAttribute(cppSrcs, cSrcs)
+	cppSrcs = bazel.SubtractBazelLabelListAttribute(cppSrcs, asSrcs)
+	return
 }
 
 // bp2buildParseSharedProps returns the attributes for the shared variant of a cc_library.
-func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) sharedAttributes {
+func bp2BuildParseSharedProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
 	lib, ok := module.compiler.(*libraryDecorator)
 	if !ok {
-		return sharedAttributes{}
+		return staticOrSharedAttributes{}
 	}
 
-	copts := bazel.StringListAttribute{Value: lib.SharedProperties.Shared.Cflags}
-
-	srcs := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleSrc(ctx, lib.SharedProperties.Shared.Srcs)}
-
-	staticDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Static_libs)}
-
-	dynamicDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Shared_libs)}
-
-	wholeArchiveDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.SharedProperties.Shared.Whole_static_libs)}
-
-	return sharedAttributes{
-		copts:            copts,
-		srcs:             srcs,
-		staticDeps:       staticDeps,
-		dynamicDeps:      dynamicDeps,
-		wholeArchiveDeps: wholeArchiveDeps,
-	}
-}
-
-type staticAttributes struct {
-	copts            bazel.StringListAttribute
-	srcs             bazel.LabelListAttribute
-	staticDeps       bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
+	return bp2buildParseStaticOrSharedProps(ctx, module, lib, false)
 }
 
 // bp2buildParseStaticProps returns the attributes for the static variant of a cc_library.
-func bp2BuildParseStaticProps(ctx android.TopDownMutatorContext, module *Module) staticAttributes {
+func bp2BuildParseStaticProps(ctx android.TopDownMutatorContext, module *Module) staticOrSharedAttributes {
 	lib, ok := module.compiler.(*libraryDecorator)
 	if !ok {
-		return staticAttributes{}
+		return staticOrSharedAttributes{}
 	}
 
-	copts := bazel.StringListAttribute{Value: lib.StaticProperties.Static.Cflags}
+	return bp2buildParseStaticOrSharedProps(ctx, module, lib, true)
+}
 
-	srcs := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleSrc(ctx, lib.StaticProperties.Static.Srcs)}
+func bp2buildParseStaticOrSharedProps(ctx android.TopDownMutatorContext, module *Module, lib *libraryDecorator, isStatic bool) staticOrSharedAttributes {
+	var props StaticOrSharedProperties
+	if isStatic {
+		props = lib.StaticProperties.Static
+	} else {
+		props = lib.SharedProperties.Shared
+	}
 
-	staticDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Static_libs)}
+	attrs := staticOrSharedAttributes{
+		Copts:              bazel.StringListAttribute{Value: props.Cflags},
+		Srcs:               bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, props.Srcs)),
+		Static_deps:        bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, props.Static_libs)),
+		Dynamic_deps:       bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, props.Shared_libs)),
+		Whole_archive_deps: bazel.MakeLabelListAttribute(android.BazelLabelForModuleWholeDeps(ctx, props.Whole_static_libs)),
+	}
 
-	dynamicDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Shared_libs)}
+	setAttrs := func(axis bazel.ConfigurationAxis, config string, props StaticOrSharedProperties) {
+		attrs.Copts.SetSelectValue(axis, config, props.Cflags)
+		attrs.Srcs.SetSelectValue(axis, config, android.BazelLabelForModuleSrc(ctx, props.Srcs))
+		attrs.Static_deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, props.Static_libs))
+		attrs.Dynamic_deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, props.Shared_libs))
+		attrs.Whole_archive_deps.SetSelectValue(axis, config, android.BazelLabelForModuleWholeDeps(ctx, props.Whole_static_libs))
+	}
 
-	wholeArchiveDeps := bazel.LabelListAttribute{
-		Value: android.BazelLabelForModuleDeps(ctx, lib.StaticProperties.Static.Whole_static_libs)}
+	if isStatic {
+		for axis, configToProps := range module.GetArchVariantProperties(ctx, &StaticProperties{}) {
+			for config, props := range configToProps {
+				if staticOrSharedProps, ok := props.(*StaticProperties); ok {
+					setAttrs(axis, config, staticOrSharedProps.Static)
+				}
+			}
+		}
+	} else {
+		for axis, configToProps := range module.GetArchVariantProperties(ctx, &SharedProperties{}) {
+			for config, props := range configToProps {
+				if staticOrSharedProps, ok := props.(*SharedProperties); ok {
+					setAttrs(axis, config, staticOrSharedProps.Shared)
+				}
+			}
+		}
+	}
 
-	return staticAttributes{
-		copts:            copts,
-		srcs:             srcs,
-		staticDeps:       staticDeps,
-		dynamicDeps:      dynamicDeps,
-		wholeArchiveDeps: wholeArchiveDeps,
+	cppSrcs, cSrcs, asSrcs := groupSrcsByExtension(ctx, attrs.Srcs)
+	attrs.Srcs = cppSrcs
+	attrs.Srcs_c = cSrcs
+	attrs.Srcs_as = asSrcs
+
+	return attrs
+}
+
+// Convenience struct to hold all attributes parsed from prebuilt properties.
+type prebuiltAttributes struct {
+	Src bazel.LabelAttribute
+}
+
+func Bp2BuildParsePrebuiltLibraryProps(ctx android.TopDownMutatorContext, module *Module) prebuiltAttributes {
+	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
+	prebuiltLinker := prebuiltLibraryLinker.prebuiltLinker
+
+	var srcLabelAttribute bazel.LabelAttribute
+
+	if len(prebuiltLinker.properties.Srcs) > 1 {
+		ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file\n")
+	}
+
+	if len(prebuiltLinker.properties.Srcs) == 1 {
+		srcLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinker.properties.Srcs[0]))
+	}
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltLinkerProperties{}) {
+		for config, props := range configToProps {
+			if prebuiltLinkerProperties, ok := props.(*prebuiltLinkerProperties); ok {
+				if len(prebuiltLinkerProperties.Srcs) > 1 {
+					ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file for %s %s\n", axis, config)
+					continue
+				} else if len(prebuiltLinkerProperties.Srcs) == 0 {
+					continue
+				}
+				src := android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinkerProperties.Srcs[0])
+				srcLabelAttribute.SetSelectValue(axis, config, src)
+			}
+		}
+	}
+
+	return prebuiltAttributes{
+		Src: srcLabelAttribute,
 	}
 }
 
 // Convenience struct to hold all attributes parsed from compiler properties.
 type compilerAttributes struct {
-	copts    bazel.StringListAttribute
+	// Options for all languages
+	copts bazel.StringListAttribute
+	// Assembly options and sources
+	asFlags bazel.StringListAttribute
+	asSrcs  bazel.LabelListAttribute
+	// C options and sources
+	conlyFlags bazel.StringListAttribute
+	cSrcs      bazel.LabelListAttribute
+	// C++ options and sources
+	cppFlags bazel.StringListAttribute
 	srcs     bazel.LabelListAttribute
-	includes bazel.StringListAttribute
 }
 
 // bp2BuildParseCompilerProps returns copts, srcs and hdrs and other attributes.
 func bp2BuildParseCompilerProps(ctx android.TopDownMutatorContext, module *Module) compilerAttributes {
 	var srcs bazel.LabelListAttribute
 	var copts bazel.StringListAttribute
+	var asFlags bazel.StringListAttribute
+	var conlyFlags bazel.StringListAttribute
+	var cppFlags bazel.StringListAttribute
 
-	// Creates the -I flag for a directory, while making the directory relative
+	// Creates the -I flags for a directory, while making the directory relative
 	// to the exec root for Bazel to work.
-	includeFlag := func(dir string) string {
+	includeFlags := func(dir string) []string {
 		// filepath.Join canonicalizes the path, i.e. it takes care of . or .. elements.
-		return "-I" + filepath.Join(ctx.ModuleDir(), dir)
+		moduleDirRootedPath := filepath.Join(ctx.ModuleDir(), dir)
+		return []string{
+			"-I" + moduleDirRootedPath,
+			// Include the bindir-rooted path (using make variable substitution). This most
+			// closely matches Bazel's native include path handling, which allows for dependency
+			// on generated headers in these directories.
+			// TODO(b/188084383): Handle local include directories in Bazel.
+			"-I$(BINDIR)/" + moduleDirRootedPath,
+		}
 	}
 
 	// Parse the list of module-relative include directories (-I).
@@ -186,42 +374,47 @@
 		return append(includeDirs, baseCompilerProps.Local_include_dirs...)
 	}
 
+	parseCommandLineFlags := func(soongFlags []string) []string {
+		var result []string
+		for _, flag := range soongFlags {
+			// Soong's cflags can contain spaces, like `-include header.h`. For
+			// Bazel's copts, split them up to be compatible with the
+			// no_copts_tokenization feature.
+			result = append(result, strings.Split(flag, " ")...)
+		}
+		return result
+	}
+
 	// Parse the list of copts.
 	parseCopts := func(baseCompilerProps *BaseCompilerProperties) []string {
-		copts := append([]string{}, baseCompilerProps.Cflags...)
+		var copts []string
+		copts = append(copts, parseCommandLineFlags(baseCompilerProps.Cflags)...)
 		for _, dir := range parseLocalIncludeDirs(baseCompilerProps) {
-			copts = append(copts, includeFlag(dir))
+			copts = append(copts, includeFlags(dir)...)
 		}
 		return copts
 	}
 
-	// baseSrcs contain the list of src files that are used for every configuration.
-	var baseSrcs []string
-	// baseExcludeSrcs contain the list of src files that are excluded for every configuration.
-	var baseExcludeSrcs []string
-	// baseSrcsLabelList is a clone of the base srcs LabelList, used for computing the
-	// arch or os specific srcs later.
-	var baseSrcsLabelList bazel.LabelList
-
-	// Parse srcs from an arch or OS's props value, taking the base srcs and
-	// exclude srcs into account.
+	// Parse srcs from an arch or OS's props value.
 	parseSrcs := func(baseCompilerProps *BaseCompilerProperties) bazel.LabelList {
-		// Combine the base srcs and arch-specific srcs
-		allSrcs := append(baseSrcs, baseCompilerProps.Srcs...)
-		// Combine the base exclude_srcs and configuration-specific exclude_srcs
-		allExcludeSrcs := append(baseExcludeSrcs, baseCompilerProps.Exclude_srcs...)
-		return android.BazelLabelForModuleSrcExcludes(ctx, allSrcs, allExcludeSrcs)
+		// Add srcs-like dependencies such as generated files.
+		// First create a LabelList containing these dependencies, then merge the values with srcs.
+		generatedHdrsAndSrcs := baseCompilerProps.Generated_headers
+		generatedHdrsAndSrcs = append(generatedHdrsAndSrcs, baseCompilerProps.Generated_sources...)
+		generatedHdrsAndSrcsLabelList := android.BazelLabelForModuleDeps(ctx, generatedHdrsAndSrcs)
+
+		allSrcsLabelList := android.BazelLabelForModuleSrcExcludes(ctx, baseCompilerProps.Srcs, baseCompilerProps.Exclude_srcs)
+		return bazel.AppendBazelLabelLists(allSrcsLabelList, generatedHdrsAndSrcsLabelList)
 	}
 
 	for _, props := range module.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			srcs.Value = parseSrcs(baseCompilerProps)
+			srcs.SetValue(parseSrcs(baseCompilerProps))
 			copts.Value = parseCopts(baseCompilerProps)
+			asFlags.Value = parseCommandLineFlags(baseCompilerProps.Asflags)
+			conlyFlags.Value = parseCommandLineFlags(baseCompilerProps.Conlyflags)
+			cppFlags.Value = parseCommandLineFlags(baseCompilerProps.Cppflags)
 
-			// Used for arch-specific srcs later.
-			baseSrcs = baseCompilerProps.Srcs
-			baseExcludeSrcs = baseCompilerProps.Exclude_srcs
-			baseSrcsLabelList = parseSrcs(baseCompilerProps)
 			break
 		}
 	}
@@ -230,64 +423,79 @@
 	// target has access to all headers recursively in the package, and has
 	// "-I<module-dir>" in its copts.
 	if c, ok := module.compiler.(*baseCompiler); ok && c.includeBuildDirectory() {
-		copts.Value = append(copts.Value, includeFlag("."))
+		copts.Value = append(copts.Value, includeFlags(".")...)
 	} else if c, ok := module.compiler.(*libraryDecorator); ok && c.includeBuildDirectory() {
-		copts.Value = append(copts.Value, includeFlag("."))
+		copts.Value = append(copts.Value, includeFlags(".")...)
 	}
 
-	for arch, props := range module.GetArchProperties(ctx, &BaseCompilerProperties{}) {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
-			// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
-			if len(baseCompilerProps.Srcs) > 0 || len(baseCompilerProps.Exclude_srcs) > 0 {
-				srcsList := parseSrcs(baseCompilerProps)
-				srcs.SetValueForArch(arch.Name, srcsList)
-				// The base srcs value should not contain any arch-specific excludes.
-				srcs.Value = bazel.SubtractBazelLabelList(srcs.Value, bazel.LabelList{Includes: srcsList.Excludes})
+	archVariantCompilerProps := module.GetArchVariantProperties(ctx, &BaseCompilerProperties{})
+
+	for axis, configToProps := range archVariantCompilerProps {
+		for config, props := range configToProps {
+			if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+				// If there's arch specific srcs or exclude_srcs, generate a select entry for it.
+				// TODO(b/186153868): do this for OS specific srcs and exclude_srcs too.
+				if len(baseCompilerProps.Srcs) > 0 || len(baseCompilerProps.Exclude_srcs) > 0 {
+					srcsList := parseSrcs(baseCompilerProps)
+					srcs.SetSelectValue(axis, config, srcsList)
+				}
+
+				copts.SetSelectValue(axis, config, parseCopts(baseCompilerProps))
+				asFlags.SetSelectValue(axis, config, parseCommandLineFlags(baseCompilerProps.Asflags))
+				conlyFlags.SetSelectValue(axis, config, parseCommandLineFlags(baseCompilerProps.Conlyflags))
+				cppFlags.SetSelectValue(axis, config, parseCommandLineFlags(baseCompilerProps.Cppflags))
 			}
-
-			copts.SetValueForArch(arch.Name, parseCopts(baseCompilerProps))
 		}
 	}
 
-	// After going through all archs, delete the duplicate files in the arch
-	// values that are already in the base srcs.Value.
-	for arch, props := range module.GetArchProperties(ctx, &BaseCompilerProperties{}) {
-		if _, ok := props.(*BaseCompilerProperties); ok {
-			srcs.SetValueForArch(arch.Name, bazel.SubtractBazelLabelList(srcs.GetValueForArch(arch.Name), srcs.Value))
+	srcs.ResolveExcludes()
+
+	productVarPropNameToAttribute := map[string]*bazel.StringListAttribute{
+		"Cflags":   &copts,
+		"Asflags":  &asFlags,
+		"CppFlags": &cppFlags,
+	}
+	productVariableProps := android.ProductVariableProperties(ctx)
+	for propName, attr := range productVarPropNameToAttribute {
+		if props, exists := productVariableProps[propName]; exists {
+			for _, prop := range props {
+				flags, ok := prop.Property.([]string)
+				if !ok {
+					ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
+				}
+				newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
+				attr.SetSelectValue(bazel.ProductVariableConfigurationAxis(prop.FullConfig), prop.FullConfig, newFlags)
+			}
 		}
 	}
 
-	// Now that the srcs.Value list is finalized, compare it with the original
-	// list, and put the difference into the default condition for the arch
-	// select.
-	defaultsSrcs := bazel.SubtractBazelLabelList(baseSrcsLabelList, srcs.Value)
-	// TODO(b/186153868): handle the case with multiple variant types, e.g. when arch and os are both used.
-	srcs.SetValueForArch(bazel.CONDITIONS_DEFAULT, defaultsSrcs)
-
-	// Handle OS specific props.
-	for os, props := range module.GetTargetProperties(&BaseCompilerProperties{}) {
-		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			srcsList := parseSrcs(baseCompilerProps)
-			// TODO(b/186153868): add support for os-specific srcs and exclude_srcs
-			srcs.SetValueForOS(os.Name, bazel.SubtractBazelLabelList(srcsList, baseSrcsLabelList))
-			copts.SetValueForOS(os.Name, parseCopts(baseCompilerProps))
-		}
-	}
+	srcs, cSrcs, asSrcs := groupSrcsByExtension(ctx, srcs)
 
 	return compilerAttributes{
-		srcs:  srcs,
-		copts: copts,
+		copts:      copts,
+		srcs:       srcs,
+		asFlags:    asFlags,
+		asSrcs:     asSrcs,
+		cSrcs:      cSrcs,
+		conlyFlags: conlyFlags,
+		cppFlags:   cppFlags,
 	}
 }
 
 // Convenience struct to hold all attributes parsed from linker properties.
 type linkerAttributes struct {
-	deps             bazel.LabelListAttribute
-	dynamicDeps      bazel.LabelListAttribute
-	wholeArchiveDeps bazel.LabelListAttribute
-	linkopts         bazel.StringListAttribute
-	versionScript    bazel.LabelAttribute
+	deps                          bazel.LabelListAttribute
+	dynamicDeps                   bazel.LabelListAttribute
+	wholeArchiveDeps              bazel.LabelListAttribute
+	exportedDeps                  bazel.LabelListAttribute
+	useLibcrt                     bazel.BoolAttribute
+	linkopts                      bazel.StringListAttribute
+	versionScript                 bazel.LabelAttribute
+	stripKeepSymbols              bazel.BoolAttribute
+	stripKeepSymbolsAndDebugFrame bazel.BoolAttribute
+	stripKeepSymbolsList          bazel.StringListAttribute
+	stripAll                      bazel.BoolAttribute
+	stripNone                     bazel.BoolAttribute
 }
 
 // FIXME(b/187655838): Use the existing linkerFlags() function instead of duplicating logic here
@@ -302,78 +510,167 @@
 // bp2BuildParseLinkerProps parses the linker properties of a module, including
 // configurable attribute values.
 func bp2BuildParseLinkerProps(ctx android.TopDownMutatorContext, module *Module) linkerAttributes {
-	var deps bazel.LabelListAttribute
+	var headerDeps bazel.LabelListAttribute
+	var staticDeps bazel.LabelListAttribute
+	var exportedDeps bazel.LabelListAttribute
 	var dynamicDeps bazel.LabelListAttribute
 	var wholeArchiveDeps bazel.LabelListAttribute
 	var linkopts bazel.StringListAttribute
 	var versionScript bazel.LabelAttribute
+	var useLibcrt bazel.BoolAttribute
+
+	var stripKeepSymbols bazel.BoolAttribute
+	var stripKeepSymbolsAndDebugFrame bazel.BoolAttribute
+	var stripKeepSymbolsList bazel.StringListAttribute
+	var stripAll bazel.BoolAttribute
+	var stripNone bazel.BoolAttribute
+
+	if libraryDecorator, ok := module.linker.(*libraryDecorator); ok {
+		stripProperties := libraryDecorator.stripper.StripProperties
+		stripKeepSymbols.Value = stripProperties.Strip.Keep_symbols
+		stripKeepSymbolsList.Value = stripProperties.Strip.Keep_symbols_list
+		stripKeepSymbolsAndDebugFrame.Value = stripProperties.Strip.Keep_symbols_and_debug_frame
+		stripAll.Value = stripProperties.Strip.All
+		stripNone.Value = stripProperties.Strip.None
+	}
+
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &StripProperties{}) {
+		for config, props := range configToProps {
+			if stripProperties, ok := props.(*StripProperties); ok {
+				stripKeepSymbols.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols)
+				stripKeepSymbolsList.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_list)
+				stripKeepSymbolsAndDebugFrame.SetSelectValue(axis, config, stripProperties.Strip.Keep_symbols_and_debug_frame)
+				stripAll.SetSelectValue(axis, config, stripProperties.Strip.All)
+				stripNone.SetSelectValue(axis, config, stripProperties.Strip.None)
+			}
+		}
+	}
 
 	for _, linkerProps := range module.linker.linkerProps() {
 		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, libs))
+			// Excludes to parallel Soong:
+			// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=247-249;drc=088b53577dde6e40085ffd737a1ae96ad82fc4b0
+			staticLibs := android.FirstUniqueStrings(baseLinkerProps.Static_libs)
+			staticDeps.Value = android.BazelLabelForModuleDepsExcludes(ctx, staticLibs, baseLinkerProps.Exclude_static_libs)
+			wholeArchiveLibs := android.FirstUniqueStrings(baseLinkerProps.Whole_static_libs)
+			wholeArchiveDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleWholeDepsExcludes(ctx, wholeArchiveLibs, baseLinkerProps.Exclude_static_libs))
+			sharedLibs := android.FirstUniqueStrings(baseLinkerProps.Shared_libs)
+			dynamicDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDepsExcludes(ctx, sharedLibs, baseLinkerProps.Exclude_shared_libs))
+
+			headerLibs := android.FirstUniqueStrings(baseLinkerProps.Header_libs)
+			headerDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, headerLibs))
+			// TODO(b/188796939): also handle export_static_lib_headers, export_shared_lib_headers,
+			// export_generated_headers
+			exportedLibs := android.FirstUniqueStrings(baseLinkerProps.Export_header_lib_headers)
+			exportedDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, exportedLibs))
+
 			linkopts.Value = getBp2BuildLinkerFlags(baseLinkerProps)
-			wholeArchiveDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
-
 			if baseLinkerProps.Version_script != nil {
-				versionScript.Value = android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script)
+				versionScript.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script))
 			}
-
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, sharedLibs))
+			useLibcrt.Value = baseLinkerProps.libCrt()
 
 			break
 		}
 	}
 
-	for arch, p := range module.GetArchProperties(ctx, &BaseLinkerProperties{}) {
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			deps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, libs))
-			linkopts.SetValueForArch(arch.Name, getBp2BuildLinkerFlags(baseLinkerProps))
-			wholeArchiveDeps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &BaseLinkerProperties{}) {
+		for config, props := range configToProps {
+			if baseLinkerProps, ok := props.(*BaseLinkerProperties); ok {
+				staticLibs := android.FirstUniqueStrings(baseLinkerProps.Static_libs)
+				staticDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDepsExcludes(ctx, staticLibs, baseLinkerProps.Exclude_static_libs))
+				wholeArchiveLibs := android.FirstUniqueStrings(baseLinkerProps.Whole_static_libs)
+				wholeArchiveDeps.SetSelectValue(axis, config, android.BazelLabelForModuleWholeDepsExcludes(ctx, wholeArchiveLibs, baseLinkerProps.Exclude_static_libs))
+				sharedLibs := android.FirstUniqueStrings(baseLinkerProps.Shared_libs)
+				dynamicDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDepsExcludes(ctx, sharedLibs, baseLinkerProps.Exclude_shared_libs))
 
-			if baseLinkerProps.Version_script != nil {
-				versionScript.SetValueForArch(arch.Name,
-					android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script))
+				headerLibs := android.FirstUniqueStrings(baseLinkerProps.Header_libs)
+				headerDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, headerLibs))
+				exportedLibs := android.FirstUniqueStrings(baseLinkerProps.Export_header_lib_headers)
+				exportedDeps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, exportedLibs))
+
+				linkopts.SetSelectValue(axis, config, getBp2BuildLinkerFlags(baseLinkerProps))
+				if baseLinkerProps.Version_script != nil {
+					versionScript.SetSelectValue(axis, config, android.BazelLabelForModuleSrcSingle(ctx, *baseLinkerProps.Version_script))
+				}
+				useLibcrt.SetSelectValue(axis, config, baseLinkerProps.libCrt())
+			}
+		}
+	}
+
+	type productVarDep struct {
+		// the name of the corresponding excludes field, if one exists
+		excludesField string
+		// reference to the bazel attribute that should be set for the given product variable config
+		attribute *bazel.LabelListAttribute
+
+		depResolutionFunc func(ctx android.BazelConversionPathContext, modules, excludes []string) bazel.LabelList
+	}
+
+	productVarToDepFields := map[string]productVarDep{
+		// product variables do not support exclude_shared_libs
+		"Shared_libs":       productVarDep{attribute: &dynamicDeps, depResolutionFunc: android.BazelLabelForModuleDepsExcludes},
+		"Static_libs":       productVarDep{"Exclude_static_libs", &staticDeps, android.BazelLabelForModuleDepsExcludes},
+		"Whole_static_libs": productVarDep{"Exclude_static_libs", &wholeArchiveDeps, android.BazelLabelForModuleWholeDepsExcludes},
+	}
+
+	productVariableProps := android.ProductVariableProperties(ctx)
+	for name, dep := range productVarToDepFields {
+		props, exists := productVariableProps[name]
+		excludeProps, excludesExists := productVariableProps[dep.excludesField]
+		// if neither an include or excludes property exists, then skip it
+		if !exists && !excludesExists {
+			continue
+		}
+		// collect all the configurations that an include or exclude property exists for.
+		// we want to iterate all configurations rather than either the include or exclude because for a
+		// particular configuration we may have only and include or only an exclude to handle
+		configs := make(map[string]bool, len(props)+len(excludeProps))
+		for config := range props {
+			configs[config] = true
+		}
+		for config := range excludeProps {
+			configs[config] = true
+		}
+
+		for config := range configs {
+			prop, includesExists := props[config]
+			excludesProp, excludesExists := excludeProps[config]
+			var includes, excludes []string
+			var ok bool
+			// if there was no includes/excludes property, casting fails and that's expected
+			if includes, ok = prop.Property.([]string); includesExists && !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", name)
+			}
+			if excludes, ok = excludesProp.Property.([]string); excludesExists && !ok {
+				ctx.ModuleErrorf("Could not convert product variable %s property", dep.excludesField)
 			}
 
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps.SetValueForArch(arch.Name, android.BazelLabelForModuleDeps(ctx, sharedLibs))
+			dep.attribute.SetSelectValue(bazel.ProductVariableConfigurationAxis(config), config, dep.depResolutionFunc(ctx, android.FirstUniqueStrings(includes), excludes))
 		}
 	}
 
-	for os, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
-		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
-			libs := baseLinkerProps.Header_libs
-			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
-			libs = append(libs, baseLinkerProps.Static_libs...)
-			wholeArchiveLibs := baseLinkerProps.Whole_static_libs
-			libs = android.SortedUniqueStrings(libs)
-			wholeArchiveDeps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, wholeArchiveLibs))
-			deps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
+	staticDeps.ResolveExcludes()
+	dynamicDeps.ResolveExcludes()
+	wholeArchiveDeps.ResolveExcludes()
 
-			linkopts.SetValueForOS(os.Name, getBp2BuildLinkerFlags(baseLinkerProps))
-
-			sharedLibs := baseLinkerProps.Shared_libs
-			dynamicDeps.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, sharedLibs))
-		}
-	}
+	headerDeps.Append(staticDeps)
 
 	return linkerAttributes{
-		deps:             deps,
+		deps:             headerDeps,
+		exportedDeps:     exportedDeps,
 		dynamicDeps:      dynamicDeps,
 		wholeArchiveDeps: wholeArchiveDeps,
 		linkopts:         linkopts,
+		useLibcrt:        useLibcrt,
 		versionScript:    versionScript,
+
+		// Strip properties
+		stripKeepSymbols:              stripKeepSymbols,
+		stripKeepSymbolsAndDebugFrame: stripKeepSymbolsAndDebugFrame,
+		stripKeepSymbolsList:          stripKeepSymbolsList,
+		stripAll:                      stripAll,
+		stripNone:                     stripNone,
 	}
 }
 
@@ -400,11 +697,20 @@
 	return relativePaths
 }
 
-// bp2BuildParseExportedIncludes creates a string list attribute contains the
-// exported included directories of a module.
 func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
 	libraryDecorator := module.linker.(*libraryDecorator)
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+}
 
+func Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
+	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
+	libraryDecorator := prebuiltLibraryLinker.libraryDecorator
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+}
+
+// bp2BuildParseExportedIncludes creates a string list attribute contains the
+// exported included directories of a module.
+func bp2BuildParseExportedIncludesHelper(ctx android.TopDownMutatorContext, module *Module, libraryDecorator *libraryDecorator) bazel.StringListAttribute {
 	// Export_system_include_dirs and export_include_dirs are already module dir
 	// relative, so they don't need to be relativized like include_dirs, which
 	// are root-relative.
@@ -412,32 +718,23 @@
 	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
 	includeDirsAttribute := bazel.MakeStringListAttribute(includeDirs)
 
-	for arch, props := range module.GetArchProperties(ctx, &FlagExporterProperties{}) {
-		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-			archIncludeDirs := flagExporterProperties.Export_system_include_dirs
-			archIncludeDirs = append(archIncludeDirs, flagExporterProperties.Export_include_dirs...)
+	getVariantIncludeDirs := func(includeDirs []string, flagExporterProperties *FlagExporterProperties) []string {
+		variantIncludeDirs := flagExporterProperties.Export_system_include_dirs
+		variantIncludeDirs = append(variantIncludeDirs, flagExporterProperties.Export_include_dirs...)
 
-			// To avoid duplicate includes when base includes + arch includes are combined
-			// FIXME: This doesn't take conflicts between arch and os includes into account
-			archIncludeDirs = bazel.SubtractStrings(archIncludeDirs, includeDirs)
-
-			if len(archIncludeDirs) > 0 {
-				includeDirsAttribute.SetValueForArch(arch.Name, archIncludeDirs)
-			}
-		}
+		// To avoid duplicate includes when base includes + arch includes are combined
+		// TODO: This doesn't take conflicts between arch and os includes into account
+		variantIncludeDirs = bazel.SubtractStrings(variantIncludeDirs, includeDirs)
+		return variantIncludeDirs
 	}
 
-	for os, props := range module.GetTargetProperties(&FlagExporterProperties{}) {
-		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
-			osIncludeDirs := flagExporterProperties.Export_system_include_dirs
-			osIncludeDirs = append(osIncludeDirs, flagExporterProperties.Export_include_dirs...)
-
-			// To avoid duplicate includes when base includes + os includes are combined
-			// FIXME: This doesn't take conflicts between arch and os includes into account
-			osIncludeDirs = bazel.SubtractStrings(osIncludeDirs, includeDirs)
-
-			if len(osIncludeDirs) > 0 {
-				includeDirsAttribute.SetValueForOS(os.Name, osIncludeDirs)
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &FlagExporterProperties{}) {
+		for config, props := range configToProps {
+			if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
+				archVariantIncludeDirs := getVariantIncludeDirs(includeDirs, flagExporterProperties)
+				if len(archVariantIncludeDirs) > 0 {
+					includeDirsAttribute.SetSelectValue(axis, config, archVariantIncludeDirs)
+				}
 			}
 		}
 	}
diff --git a/cc/builder.go b/cc/builder.go
index 29cde9d..bde8c96 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -534,7 +534,7 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"windresCmd": gccCmd(flags.toolchain, "windres"),
+					"windresCmd": mingwCmd(flags.toolchain, "windres"),
 					"flags":      flags.toolchain.WindresFlags(),
 				},
 			})
@@ -730,8 +730,9 @@
 // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
 // and shared libraries, to a shared library (.so) or dynamic executable
 func transformObjToDynamicBinary(ctx android.ModuleContext,
-	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps android.Paths,
-	crtBegin, crtEnd android.OptionalPath, groupLate bool, flags builderFlags, outputFile android.WritablePath, implicitOutputs android.WritablePaths) {
+	objFiles, sharedLibs, staticLibs, lateStaticLibs, wholeStaticLibs, deps, crtBegin, crtEnd android.Paths,
+	groupLate bool, flags builderFlags, outputFile android.WritablePath,
+	implicitOutputs android.WritablePaths, validations android.WritablePaths) {
 
 	ldCmd := "${config.ClangBin}/clang++"
 
@@ -778,18 +779,17 @@
 	deps = append(deps, staticLibs...)
 	deps = append(deps, lateStaticLibs...)
 	deps = append(deps, wholeStaticLibs...)
-	if crtBegin.Valid() {
-		deps = append(deps, crtBegin.Path(), crtEnd.Path())
-	}
+	deps = append(deps, crtBegin...)
+	deps = append(deps, crtEnd...)
 
 	rule := ld
 	args := map[string]string{
 		"ldCmd":         ldCmd,
-		"crtBegin":      crtBegin.String(),
+		"crtBegin":      strings.Join(crtBegin.Strings(), " "),
 		"libFlags":      strings.Join(libFlagsList, " "),
 		"extraLibFlags": flags.extraLibFlags,
 		"ldFlags":       flags.globalLdFlags + " " + flags.localLdFlags,
-		"crtEnd":        crtEnd.String(),
+		"crtEnd":        strings.Join(crtEnd.Strings(), " "),
 	}
 	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
 		rule = ldRE
@@ -804,6 +804,8 @@
 		ImplicitOutputs: implicitOutputs,
 		Inputs:          objFiles,
 		Implicits:       deps,
+		OrderOnly:       sharedLibs,
+		Validations:     validations.Paths(),
 		Args:            args,
 	})
 }
@@ -1068,6 +1070,6 @@
 	})
 }
 
-func gccCmd(toolchain config.Toolchain, cmd string) string {
+func mingwCmd(toolchain config.Toolchain, cmd string) string {
 	return filepath.Join(toolchain.GccRoot(), "bin", toolchain.GccTriple()+"-"+cmd)
 }
diff --git a/cc/cc.go b/cc/cc.go
index 16a49d3..2e094d0 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -126,11 +126,10 @@
 
 	ReexportGeneratedHeaders []string
 
-	CrtBegin, CrtEnd string
+	CrtBegin, CrtEnd []string
 
 	// Used for host bionic
-	LinkerFlagsFile string
-	DynamicLinker   string
+	DynamicLinker string
 
 	// List of libs that need to be excluded for APEX variant
 	ExcludeLibsForApex []string
@@ -177,10 +176,7 @@
 	ReexportedDeps             android.Paths
 
 	// Paths to crt*.o files
-	CrtBegin, CrtEnd android.OptionalPath
-
-	// Path to the file container flags to use with the linker
-	LinkerFlagsFile android.OptionalPath
+	CrtBegin, CrtEnd android.Paths
 
 	// Path to the dynamic linker binary
 	DynamicLinker android.OptionalPath
@@ -290,9 +286,15 @@
 	// Set by DepsMutator.
 	AndroidMkSystemSharedLibs []string `blueprint:"mutated"`
 
+	// The name of the image this module is built for, suffixed with a '.'
 	ImageVariationPrefix string `blueprint:"mutated"`
-	VndkVersion          string `blueprint:"mutated"`
-	SubName              string `blueprint:"mutated"`
+
+	// The VNDK version this module is built against. If empty, the module is not
+	// build against the VNDK.
+	VndkVersion string `blueprint:"mutated"`
+
+	// Suffix for the name of Android.mk entries generated by this module
+	SubName string `blueprint:"mutated"`
 
 	// *.logtags files, to combine together in order to generate the /system/etc/event-log-tags
 	// file
@@ -315,12 +317,15 @@
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
-	// Set by imageMutator
-	CoreVariantNeeded          bool     `blueprint:"mutated"`
-	RamdiskVariantNeeded       bool     `blueprint:"mutated"`
-	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
-	RecoveryVariantNeeded      bool     `blueprint:"mutated"`
-	ExtraVariants              []string `blueprint:"mutated"`
+	// Used by imageMutator, set by ImageMutatorBegin()
+	CoreVariantNeeded          bool `blueprint:"mutated"`
+	RamdiskVariantNeeded       bool `blueprint:"mutated"`
+	VendorRamdiskVariantNeeded bool `blueprint:"mutated"`
+	RecoveryVariantNeeded      bool `blueprint:"mutated"`
+
+	// A list of variations for the "image" mutator of the form
+	//<image name> '.' <version char>, for example, 'vendor.S'
+	ExtraVersionedImageVariations []string `blueprint:"mutated"`
 
 	// Allows this module to use non-APEX version of libraries. Useful
 	// for building binaries that are started before APEXes are activated.
@@ -717,7 +722,6 @@
 	genHeaderDepTag       = dependencyTag{name: "gen header"}
 	genHeaderExportDepTag = dependencyTag{name: "gen header export"}
 	objDepTag             = dependencyTag{name: "obj"}
-	linkerFlagsDepTag     = dependencyTag{name: "linker flags file"}
 	dynamicLinkerDepTag   = installDependencyTag{name: "dynamic linker"}
 	reuseObjTag           = dependencyTag{name: "reuse objects"}
 	staticVariantTag      = dependencyTag{name: "static variant"}
@@ -726,15 +730,8 @@
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
-	llndkStubDepTag       = dependencyTag{name: "llndk stub"}
 )
 
-type copyDirectlyInAnyApexDependencyTag dependencyTag
-
-func (copyDirectlyInAnyApexDependencyTag) CopyDirectlyInAnyApex() {}
-
-var _ android.CopyDirectlyInAnyApexTag = copyDirectlyInAnyApexDependencyTag{}
-
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
 	ccLibDepTag, ok := depTag.(libraryDependencyTag)
 	return ok && ccLibDepTag.shared()
@@ -841,6 +838,10 @@
 	c.Properties.HideFromMake = true
 }
 
+func (c *Module) HiddenFromMake() bool {
+	return c.Properties.HideFromMake
+}
+
 func (c *Module) Toc() android.OptionalPath {
 	if c.linker != nil {
 		if library, ok := c.linker.(libraryInterface); ok {
@@ -1088,12 +1089,6 @@
 	return false
 }
 
-// Returns true if the module is using VNDK libraries instead of the libraries in /system/lib or /system/lib64.
-// "product" and "vendor" variant modules return true for this function.
-// When BOARD_VNDK_VERSION is set, vendor variants of "vendor_available: true", "vendor: true",
-// "soc_specific: true" and more vendor installed modules are included here.
-// When PRODUCT_PRODUCT_VNDK_VERSION is set, product variants of "product_available: true" or
-// "product_specific: true" modules are included here.
 func (c *Module) UseVndk() bool {
 	return c.Properties.VndkVersion != ""
 }
@@ -1141,6 +1136,18 @@
 	return c.VendorProperties.IsVendorPublicLibrary
 }
 
+func (c *Module) HasLlndkStubs() bool {
+	lib := moduleLibraryInterface(c)
+	return lib != nil && lib.hasLLNDKStubs()
+}
+
+func (c *Module) StubsVersion() string {
+	if lib, ok := c.linker.(versionedInterface); ok {
+		return lib.stubsVersion()
+	}
+	panic(fmt.Errorf("StubsVersion called on non-versioned module: %q", c.BaseModuleName()))
+}
+
 // isImplementationForLLNDKPublic returns true for any variant of a cc_library that has LLNDK stubs
 // and does not set llndk.vendor_available: false.
 func (c *Module) isImplementationForLLNDKPublic() bool {
@@ -1185,7 +1192,7 @@
 	return false
 }
 
-func (c *Module) isVndkSp() bool {
+func (c *Module) IsVndkSp() bool {
 	if vndkdep := c.vndkdep; vndkdep != nil {
 		return vndkdep.isVndkSp()
 	}
@@ -1204,7 +1211,7 @@
 }
 
 func (c *Module) MustUseVendorVariant() bool {
-	return c.isVndkSp() || c.Properties.MustUseVendorVariant
+	return c.IsVndkSp() || c.Properties.MustUseVendorVariant
 }
 
 func (c *Module) getVndkExtendsModuleName() string {
@@ -1264,8 +1271,8 @@
 }
 
 func (c *Module) IsSnapshotPrebuilt() bool {
-	if p, ok := c.linker.(snapshotInterface); ok {
-		return p.isSnapshotPrebuilt()
+	if p, ok := c.linker.(SnapshotInterface); ok {
+		return p.IsSnapshotPrebuilt()
 	}
 	return false
 }
@@ -1343,11 +1350,11 @@
 }
 
 func (ctx *moduleContextImpl) binary() bool {
-	return ctx.mod.binary()
+	return ctx.mod.Binary()
 }
 
 func (ctx *moduleContextImpl) object() bool {
-	return ctx.mod.object()
+	return ctx.mod.Object()
 }
 
 func (ctx *moduleContextImpl) canUseSdk() bool {
@@ -1445,7 +1452,7 @@
 }
 
 func (ctx *moduleContextImpl) isVndkSp() bool {
-	return ctx.mod.isVndkSp()
+	return ctx.mod.IsVndkSp()
 }
 
 func (ctx *moduleContextImpl) IsVndkExt() bool {
@@ -1670,10 +1677,6 @@
 
 	c.makeLinkType = GetMakeLinkType(actx, c)
 
-	if c.maybeGenerateBazelActions(actx) {
-		return
-	}
-
 	ctx := &moduleContext{
 		ModuleContext: actx,
 		moduleContextImpl: moduleContextImpl{
@@ -1682,6 +1685,11 @@
 	}
 	ctx.ctx = ctx
 
+	if c.maybeGenerateBazelActions(actx) {
+		c.maybeInstall(ctx, apexInfo)
+		return
+	}
+
 	deps := c.depsToPaths(ctx)
 	if ctx.Failed() {
 		return
@@ -1769,29 +1777,37 @@
 		}
 		c.outputFile = android.OptionalPathForPath(outputFile)
 
-		// If a lib is directly included in any of the APEXes or is not available to the
-		// platform (which is often the case when the stub is provided as a prebuilt),
-		// unhide the stubs variant having the latest version gets visible to make. In
-		// addition, the non-stubs variant is renamed to <libname>.bootstrap. This is to
-		// force anything in the make world to link against the stubs library.  (unless it
-		// is explicitly referenced via .bootstrap suffix or the module is marked with
-		// 'bootstrap: true').
-		if c.HasStubsVariants() && c.NotInPlatform() && !c.InRamdisk() &&
-			!c.InRecovery() && !c.UseVndk() && !c.static() && !c.isCoverageVariant() &&
-			c.IsStubs() && !c.InVendorRamdisk() {
-			c.Properties.HideFromMake = false // unhide
-			// Note: this is still non-installable
-		}
+		c.maybeUnhideFromMake()
 
 		// glob exported headers for snapshot, if BOARD_VNDK_VERSION is current or
 		// RECOVERY_SNAPSHOT_VERSION is current.
 		if i, ok := c.linker.(snapshotLibraryInterface); ok {
-			if shouldCollectHeadersForSnapshot(ctx, c, apexInfo) {
+			if ShouldCollectHeadersForSnapshot(ctx, c, apexInfo) {
 				i.collectHeadersForSnapshot(ctx)
 			}
 		}
 	}
 
+	c.maybeInstall(ctx, apexInfo)
+}
+
+func (c *Module) maybeUnhideFromMake() {
+	// If a lib is directly included in any of the APEXes or is not available to the
+	// platform (which is often the case when the stub is provided as a prebuilt),
+	// unhide the stubs variant having the latest version gets visible to make. In
+	// addition, the non-stubs variant is renamed to <libname>.bootstrap. This is to
+	// force anything in the make world to link against the stubs library.  (unless it
+	// is explicitly referenced via .bootstrap suffix or the module is marked with
+	// 'bootstrap: true').
+	if c.HasStubsVariants() && c.NotInPlatform() && !c.InRamdisk() &&
+		!c.InRecovery() && !c.UseVndk() && !c.static() && !c.isCoverageVariant() &&
+		c.IsStubs() && !c.InVendorRamdisk() {
+		c.Properties.HideFromMake = false // unhide
+		// Note: this is still non-installable
+	}
+}
+
+func (c *Module) maybeInstall(ctx ModuleContext, apexInfo android.ApexInfo) {
 	if !proptools.BoolDefault(c.Properties.Installable, true) {
 		// If the module has been specifically configure to not be installed then
 		// hide from make as otherwise it will break when running inside make
@@ -1799,7 +1815,7 @@
 		// modules can be hidden from make as some are needed for resolving make side
 		// dependencies.
 		c.HideFromMake()
-	} else if !c.installable(apexInfo) {
+	} else if !installable(c, apexInfo) {
 		c.SkipInstall()
 	}
 
@@ -1967,9 +1983,13 @@
 		if minSdkVersion == "" || minSdkVersion == "apex_inherit" {
 			minSdkVersion = m.SdkVersion()
 		}
+		apiLevel, err := android.ApiLevelFromUser(ctx, minSdkVersion)
+		if err != nil {
+			ctx.PropertyErrorf("min_sdk_version", err.Error())
+		}
 		return []blueprint.Variation{
 			{Mutator: "sdk", Variation: "sdk"},
-			{Mutator: "version", Variation: minSdkVersion},
+			{Mutator: "version", Variation: apiLevel.String()},
 		}
 	}
 	return []blueprint.Variation{
@@ -1977,15 +1997,19 @@
 	}
 }
 
-func (c *Module) addSharedLibDependenciesWithVersions(ctx android.BottomUpMutatorContext,
-	variations []blueprint.Variation, depTag libraryDependencyTag, name, version string, far bool) {
+func AddSharedLibDependenciesWithVersions(ctx android.BottomUpMutatorContext, mod LinkableInterface,
+	variations []blueprint.Variation, depTag blueprint.DependencyTag, name, version string, far bool) {
 
 	variations = append([]blueprint.Variation(nil), variations...)
 
-	if version != "" && CanBeOrLinkAgainstVersionVariants(c) {
+	if version != "" && CanBeOrLinkAgainstVersionVariants(mod) {
 		// Version is explicitly specified. i.e. libFoo#30
 		variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
-		depTag.explicitlyVersioned = true
+		if tag, ok := depTag.(libraryDependencyTag); ok {
+			tag.explicitlyVersioned = true
+		} else {
+			panic(fmt.Errorf("Unexpected dependency tag: %T", depTag))
+		}
 	}
 
 	if far {
@@ -1995,6 +2019,74 @@
 	}
 }
 
+func GetSnapshot(c LinkableInterface, snapshotInfo **SnapshotInfo, actx android.BottomUpMutatorContext) SnapshotInfo {
+	// Only modules with BOARD_VNDK_VERSION uses snapshot.  Others use the zero value of
+	// SnapshotInfo, which provides no mappings.
+	if *snapshotInfo == nil {
+		// Only retrieve the snapshot on demand in order to avoid circular dependencies
+		// between the modules in the snapshot and the snapshot itself.
+		var snapshotModule []blueprint.Module
+		if c.InVendor() && c.VndkVersion() == actx.DeviceConfig().VndkVersion() {
+			snapshotModule = actx.AddVariationDependencies(nil, nil, "vendor_snapshot")
+		} else if recoverySnapshotVersion := actx.DeviceConfig().RecoverySnapshotVersion(); recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" && c.InRecovery() {
+			snapshotModule = actx.AddVariationDependencies(nil, nil, "recovery_snapshot")
+		}
+		if len(snapshotModule) > 0 {
+			snapshot := actx.OtherModuleProvider(snapshotModule[0], SnapshotInfoProvider).(SnapshotInfo)
+			*snapshotInfo = &snapshot
+			// republish the snapshot for use in later mutators on this module
+			actx.SetProvider(SnapshotInfoProvider, snapshot)
+		} else {
+			*snapshotInfo = &SnapshotInfo{}
+		}
+	}
+
+	return **snapshotInfo
+}
+
+func RewriteSnapshotLib(lib string, snapshotMap map[string]string) string {
+	if snapshot, ok := snapshotMap[lib]; ok {
+		return snapshot
+	}
+
+	return lib
+}
+
+// RewriteLibs takes a list of names of shared libraries and scans it for three types
+// of names:
+//
+// 1. Name of an NDK library that refers to a prebuilt module.
+//    For each of these, it adds the name of the prebuilt module (which will be in
+//    prebuilts/ndk) to the list of nonvariant libs.
+// 2. Name of an NDK library that refers to an ndk_library module.
+//    For each of these, it adds the name of the ndk_library module to the list of
+//    variant libs.
+// 3. Anything else (so anything that isn't an NDK library).
+//    It adds these to the nonvariantLibs list.
+//
+// The caller can then know to add the variantLibs dependencies differently from the
+// nonvariantLibs
+func RewriteLibs(c LinkableInterface, snapshotInfo **SnapshotInfo, actx android.BottomUpMutatorContext, config android.Config, list []string) (nonvariantLibs []string, variantLibs []string) {
+	variantLibs = []string{}
+
+	nonvariantLibs = []string{}
+	for _, entry := range list {
+		// strip #version suffix out
+		name, _ := StubsLibNameAndVersion(entry)
+		if c.InRecovery() {
+			nonvariantLibs = append(nonvariantLibs, RewriteSnapshotLib(entry, GetSnapshot(c, snapshotInfo, actx).SharedLibs))
+		} else if c.UseSdk() && inList(name, *getNDKKnownLibs(config)) {
+			variantLibs = append(variantLibs, name+ndkLibrarySuffix)
+		} else if c.UseVndk() {
+			nonvariantLibs = append(nonvariantLibs, RewriteSnapshotLib(entry, GetSnapshot(c, snapshotInfo, actx).SharedLibs))
+		} else {
+			// put name#version back
+			nonvariantLibs = append(nonvariantLibs, entry)
+		}
+	}
+	return nonvariantLibs, variantLibs
+}
+
 func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
 	if !c.Enabled() {
 		return
@@ -2013,83 +2105,16 @@
 	c.Properties.AndroidMkSystemSharedLibs = deps.SystemSharedLibs
 
 	var snapshotInfo *SnapshotInfo
-	getSnapshot := func() SnapshotInfo {
-		// Only modules with BOARD_VNDK_VERSION uses snapshot.  Others use the zero value of
-		// SnapshotInfo, which provides no mappings.
-		if snapshotInfo == nil {
-			// Only retrieve the snapshot on demand in order to avoid circular dependencies
-			// between the modules in the snapshot and the snapshot itself.
-			var snapshotModule []blueprint.Module
-			if c.InVendor() && c.VndkVersion() == actx.DeviceConfig().VndkVersion() {
-				snapshotModule = ctx.AddVariationDependencies(nil, nil, "vendor_snapshot")
-			} else if recoverySnapshotVersion := actx.DeviceConfig().RecoverySnapshotVersion(); recoverySnapshotVersion != "current" && recoverySnapshotVersion != "" && c.InRecovery() {
-				snapshotModule = ctx.AddVariationDependencies(nil, nil, "recovery_snapshot")
-			}
-			if len(snapshotModule) > 0 {
-				snapshot := ctx.OtherModuleProvider(snapshotModule[0], SnapshotInfoProvider).(SnapshotInfo)
-				snapshotInfo = &snapshot
-				// republish the snapshot for use in later mutators on this module
-				ctx.SetProvider(SnapshotInfoProvider, snapshot)
-			} else {
-				snapshotInfo = &SnapshotInfo{}
-			}
-		}
-
-		return *snapshotInfo
-	}
-
-	rewriteSnapshotLib := func(lib string, snapshotMap map[string]string) string {
-		if snapshot, ok := snapshotMap[lib]; ok {
-			return snapshot
-		}
-
-		return lib
-	}
 
 	variantNdkLibs := []string{}
 	variantLateNdkLibs := []string{}
 	if ctx.Os() == android.Android {
-		// rewriteLibs takes a list of names of shared libraries and scans it for three types
-		// of names:
-		//
-		// 1. Name of an NDK library that refers to a prebuilt module.
-		//    For each of these, it adds the name of the prebuilt module (which will be in
-		//    prebuilts/ndk) to the list of nonvariant libs.
-		// 2. Name of an NDK library that refers to an ndk_library module.
-		//    For each of these, it adds the name of the ndk_library module to the list of
-		//    variant libs.
-		// 3. Anything else (so anything that isn't an NDK library).
-		//    It adds these to the nonvariantLibs list.
-		//
-		// The caller can then know to add the variantLibs dependencies differently from the
-		// nonvariantLibs
-
-		rewriteLibs := func(list []string) (nonvariantLibs []string, variantLibs []string) {
-			variantLibs = []string{}
-			nonvariantLibs = []string{}
-			for _, entry := range list {
-				// strip #version suffix out
-				name, _ := StubsLibNameAndVersion(entry)
-				if c.InRecovery() {
-					nonvariantLibs = append(nonvariantLibs, rewriteSnapshotLib(entry, getSnapshot().SharedLibs))
-				} else if ctx.useSdk() && inList(name, *getNDKKnownLibs(ctx.Config())) {
-					variantLibs = append(variantLibs, name+ndkLibrarySuffix)
-				} else if ctx.useVndk() {
-					nonvariantLibs = append(nonvariantLibs, rewriteSnapshotLib(entry, getSnapshot().SharedLibs))
-				} else {
-					// put name#version back
-					nonvariantLibs = append(nonvariantLibs, entry)
-				}
-			}
-			return nonvariantLibs, variantLibs
-		}
-
-		deps.SharedLibs, variantNdkLibs = rewriteLibs(deps.SharedLibs)
-		deps.LateSharedLibs, variantLateNdkLibs = rewriteLibs(deps.LateSharedLibs)
-		deps.ReexportSharedLibHeaders, _ = rewriteLibs(deps.ReexportSharedLibHeaders)
+		deps.SharedLibs, variantNdkLibs = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.SharedLibs)
+		deps.LateSharedLibs, variantLateNdkLibs = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.LateSharedLibs)
+		deps.ReexportSharedLibHeaders, _ = RewriteLibs(c, &snapshotInfo, actx, ctx.Config(), deps.ReexportSharedLibHeaders)
 
 		for idx, lib := range deps.RuntimeLibs {
-			deps.RuntimeLibs[idx] = rewriteSnapshotLib(lib, getSnapshot().SharedLibs)
+			deps.RuntimeLibs[idx] = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).SharedLibs)
 		}
 	}
 
@@ -2099,7 +2124,7 @@
 			depTag.reexportFlags = true
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().HeaderLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).HeaderLibs)
 
 		if c.IsStubs() {
 			actx.AddFarVariationDependencies(append(ctx.Target().Variations(), c.ImageVariation()),
@@ -2109,6 +2134,15 @@
 		}
 	}
 
+	if c.isNDKStubLibrary() {
+		// NDK stubs depend on their implementation because the ABI dumps are
+		// generated from the implementation library.
+		actx.AddFarVariationDependencies(append(ctx.Target().Variations(),
+			c.ImageVariation(),
+			blueprint.Variation{Mutator: "link", Variation: "shared"},
+		), stubImplementation, c.BaseModuleName())
+	}
+
 	// sysprop_library has to support both C++ and Java. So sysprop_library internally creates one
 	// C++ implementation library and one Java implementation library. When a module links against
 	// sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a
@@ -2122,7 +2156,7 @@
 			lib = impl
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().StaticLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs)
 
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
@@ -2142,7 +2176,7 @@
 			lib = impl
 		}
 
-		lib = rewriteSnapshotLib(lib, getSnapshot().StaticLibs)
+		lib = RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs)
 
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
@@ -2156,14 +2190,7 @@
 		depTag := libraryDependencyTag{Kind: staticLibraryDependency, staticUnwinder: true}
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
-		}, depTag, rewriteSnapshotLib(staticUnwinder(actx), getSnapshot().StaticLibs))
-	}
-
-	for _, lib := range deps.LateStaticLibs {
-		depTag := libraryDependencyTag{Kind: staticLibraryDependency, Order: lateLibraryDependency}
-		actx.AddVariationDependencies([]blueprint.Variation{
-			{Mutator: "link", Variation: "static"},
-		}, depTag, rewriteSnapshotLib(lib, getSnapshot().StaticLibs))
+		}, depTag, RewriteSnapshotLib(staticUnwinder(actx), GetSnapshot(c, &snapshotInfo, actx).StaticLibs))
 	}
 
 	// shared lib names without the #version suffix
@@ -2188,7 +2215,14 @@
 		variations := []blueprint.Variation{
 			{Mutator: "link", Variation: "shared"},
 		}
-		c.addSharedLibDependenciesWithVersions(ctx, variations, depTag, name, version, false)
+		AddSharedLibDependenciesWithVersions(ctx, c, variations, depTag, name, version, false)
+	}
+
+	for _, lib := range deps.LateStaticLibs {
+		depTag := libraryDependencyTag{Kind: staticLibraryDependency, Order: lateLibraryDependency}
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, RewriteSnapshotLib(lib, GetSnapshot(c, &snapshotInfo, actx).StaticLibs))
 	}
 
 	for _, lib := range deps.LateSharedLibs {
@@ -2202,7 +2236,7 @@
 		variations := []blueprint.Variation{
 			{Mutator: "link", Variation: "shared"},
 		}
-		c.addSharedLibDependenciesWithVersions(ctx, variations, depTag, lib, "", false)
+		AddSharedLibDependenciesWithVersions(ctx, c, variations, depTag, lib, "", false)
 	}
 
 	actx.AddVariationDependencies([]blueprint.Variation{
@@ -2225,16 +2259,13 @@
 
 	crtVariations := GetCrtVariations(ctx, c)
 	actx.AddVariationDependencies(crtVariations, objDepTag, deps.ObjFiles...)
-	if deps.CrtBegin != "" {
+	for _, crt := range deps.CrtBegin {
 		actx.AddVariationDependencies(crtVariations, CrtBeginDepTag,
-			rewriteSnapshotLib(deps.CrtBegin, getSnapshot().Objects))
+			RewriteSnapshotLib(crt, GetSnapshot(c, &snapshotInfo, actx).Objects))
 	}
-	if deps.CrtEnd != "" {
+	for _, crt := range deps.CrtEnd {
 		actx.AddVariationDependencies(crtVariations, CrtEndDepTag,
-			rewriteSnapshotLib(deps.CrtEnd, getSnapshot().Objects))
-	}
-	if deps.LinkerFlagsFile != "" {
-		actx.AddDependency(c, linkerFlagsDepTag, deps.LinkerFlagsFile)
+			RewriteSnapshotLib(crt, GetSnapshot(c, &snapshotInfo, actx).Objects))
 	}
 	if deps.DynamicLinker != "" {
 		actx.AddDependency(c, dynamicLinkerDepTag, deps.DynamicLinker)
@@ -2451,7 +2482,7 @@
 			return true
 		}
 
-		if to.isVndkSp() || to.IsLlndk() {
+		if to.IsVndkSp() || to.IsLlndk() {
 			return false
 		}
 
@@ -2534,17 +2565,10 @@
 				} else {
 					ctx.ModuleErrorf("module %q is not a genrule", depName)
 				}
-			case linkerFlagsDepTag:
-				if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
-					files := genRule.GeneratedSourceFiles()
-					if len(files) == 1 {
-						depPaths.LinkerFlagsFile = android.OptionalPathForPath(files[0])
-					} else if len(files) > 1 {
-						ctx.ModuleErrorf("module %q can only generate a single file if used for a linker flag file", depName)
-					}
-				} else {
-					ctx.ModuleErrorf("module %q is not a genrule", depName)
-				}
+			case CrtBeginDepTag:
+				depPaths.CrtBegin = append(depPaths.CrtBegin, android.OutputFileForModule(ctx, dep, ""))
+			case CrtEndDepTag:
+				depPaths.CrtEnd = append(depPaths.CrtEnd, android.OutputFileForModule(ctx, dep, ""))
 			}
 			return
 		}
@@ -2823,7 +2847,7 @@
 						// Add the dependency to the APEX(es) providing the library so that
 						// m <module> can trigger building the APEXes as well.
 						depApexInfo := ctx.OtherModuleProvider(dep, android.ApexInfoProvider).(android.ApexInfo)
-						for _, an := range depApexInfo.InApexes {
+						for _, an := range depApexInfo.InApexVariants {
 							c.Properties.ApexesProvidingSharedLibs = append(
 								c.Properties.ApexesProvidingSharedLibs, an)
 						}
@@ -2834,8 +2858,8 @@
 				// they merely serve as Make dependencies and do not affect this lib itself.
 				c.Properties.AndroidMkSharedLibs = append(
 					c.Properties.AndroidMkSharedLibs, makeLibName)
-				// Record baseLibName for snapshots.
-				c.Properties.SnapshotSharedLibs = append(c.Properties.SnapshotSharedLibs, baseLibName(depName))
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotSharedLibs = append(c.Properties.SnapshotSharedLibs, BaseLibName(depName))
 			case libDepTag.static():
 				if libDepTag.wholeStatic {
 					c.Properties.AndroidMkWholeStaticLibs = append(
@@ -2852,14 +2876,14 @@
 			case runtimeDepTag:
 				c.Properties.AndroidMkRuntimeLibs = append(
 					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ctx, c, ccDep, depName)+libDepTag.makeSuffix)
-				// Record baseLibName for snapshots.
-				c.Properties.SnapshotRuntimeLibs = append(c.Properties.SnapshotRuntimeLibs, baseLibName(depName))
+				// Record BaseLibName for snapshots.
+				c.Properties.SnapshotRuntimeLibs = append(c.Properties.SnapshotRuntimeLibs, BaseLibName(depName))
 			case objDepTag:
 				depPaths.Objs.objFiles = append(depPaths.Objs.objFiles, linkFile.Path())
 			case CrtBeginDepTag:
-				depPaths.CrtBegin = linkFile
+				depPaths.CrtBegin = append(depPaths.CrtBegin, linkFile.Path())
 			case CrtEndDepTag:
-				depPaths.CrtEnd = linkFile
+				depPaths.CrtEnd = append(depPaths.CrtEnd, linkFile.Path())
 			case dynamicLinkerDepTag:
 				depPaths.DynamicLinker = linkFile
 			}
@@ -2901,8 +2925,8 @@
 		transitiveStaticLibsBuilder.Transitive(staticDep.TransitiveStaticLibrariesForOrdering)
 	}
 	for _, sharedDep := range sharedDeps {
-		if sharedDep.StaticAnalogue != nil {
-			transitiveStaticLibsBuilder.Transitive(sharedDep.StaticAnalogue.TransitiveStaticLibrariesForOrdering)
+		if sharedDep.TransitiveStaticLibrariesForOrdering != nil {
+			transitiveStaticLibsBuilder.Transitive(sharedDep.TransitiveStaticLibrariesForOrdering)
 		}
 	}
 	transitiveStaticLibs := transitiveStaticLibsBuilder.Build()
@@ -2921,8 +2945,8 @@
 	return orderedStaticPaths, transitiveStaticLibs
 }
 
-// baseLibName trims known prefixes and suffixes
-func baseLibName(depName string) string {
+// BaseLibName trims known prefixes and suffixes
+func BaseLibName(depName string) string {
 	libName := strings.TrimSuffix(depName, llndkLibrarySuffix)
 	libName = strings.TrimSuffix(libName, vendorPublicLibrarySuffix)
 	libName = android.RemoveOptionalPrebuiltPrefix(libName)
@@ -2930,7 +2954,7 @@
 }
 
 func MakeLibName(ctx android.ModuleContext, c LinkableInterface, ccDep LinkableInterface, depName string) string {
-	libName := baseLibName(depName)
+	libName := BaseLibName(depName)
 	ccDepModule, _ := ccDep.(*Module)
 	isLLndk := ccDepModule != nil && ccDepModule.IsLlndk()
 	nonSystemVariantsExist := ccDep.HasNonSystemVariants() || isLLndk
@@ -2938,10 +2962,10 @@
 	if ccDepModule != nil {
 		// TODO(ivanlozano) Support snapshots for Rust-produced C library variants.
 		// Use base module name for snapshots when exporting to Makefile.
-		if snapshotPrebuilt, ok := ccDepModule.linker.(snapshotInterface); ok {
+		if snapshotPrebuilt, ok := ccDepModule.linker.(SnapshotInterface); ok {
 			baseName := ccDepModule.BaseModuleName()
 
-			return baseName + snapshotPrebuilt.snapshotAndroidMkSuffix()
+			return baseName + snapshotPrebuilt.SnapshotAndroidMkSuffix()
 		}
 	}
 
@@ -3064,7 +3088,7 @@
 	return false
 }
 
-func (c *Module) binary() bool {
+func (c *Module) Binary() bool {
 	if b, ok := c.linker.(interface {
 		binary() bool
 	}); ok {
@@ -3073,7 +3097,7 @@
 	return false
 }
 
-func (c *Module) object() bool {
+func (c *Module) Object() bool {
 	if o, ok := c.linker.(interface {
 		object() bool
 	}); ok {
@@ -3155,18 +3179,25 @@
 	}
 }
 
-// Return true if the module is ever installable.
 func (c *Module) EverInstallable() bool {
 	return c.installer != nil &&
 		// Check to see whether the module is actually ever installable.
 		c.installer.everInstallable()
 }
 
-func (c *Module) installable(apexInfo android.ApexInfo) bool {
+func (c *Module) PreventInstall() bool {
+	return c.Properties.PreventInstall
+}
+
+func (c *Module) Installable() *bool {
+	return c.Properties.Installable
+}
+
+func installable(c LinkableInterface, apexInfo android.ApexInfo) bool {
 	ret := c.EverInstallable() &&
 		// Check to see whether the module has been configured to not be installed.
-		proptools.BoolDefault(c.Properties.Installable, true) &&
-		!c.Properties.PreventInstall && c.outputFile.Valid()
+		proptools.BoolDefault(c.Installable(), true) &&
+		!c.PreventInstall() && c.OutputFile().Valid()
 
 	// The platform variant doesn't need further condition. Apex variants however might not
 	// be installable because it will likely to be included in the APEX and won't appear
@@ -3213,6 +3244,9 @@
 				return false
 			}
 		}
+		if cc.IsLlndk() {
+			return false
+		}
 		if isLibDepTag && c.static() && libDepTag.shared() {
 			// shared_lib dependency from a static lib is considered as crossing
 			// the APEX boundary because the dependency doesn't actually is
@@ -3224,8 +3258,8 @@
 			return false
 		}
 	}
-	if depTag == stubImplDepTag || depTag == llndkStubDepTag {
-		// We don't track beyond LLNDK or from an implementation library to its stubs.
+	if depTag == stubImplDepTag {
+		// We don't track from an implementation library to its stubs.
 		return false
 	}
 	if depTag == staticVariantTag {
@@ -3279,6 +3313,12 @@
 	return nil
 }
 
+// Implements android.ApexModule
+func (c *Module) AlwaysRequiresPlatformApexVariant() bool {
+	// stub libraries and native bridge libraries are always available to platform
+	return c.IsStubs() || c.Target().NativeBridge == android.NativeBridgeEnabled
+}
+
 //
 // Defaults
 //
diff --git a/cc/cc_test.go b/cc/cc_test.go
index e9daf33..2d0d78b 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -381,8 +381,8 @@
 	if !mod.IsVndk() {
 		t.Errorf("%q IsVndk() must equal to true", name)
 	}
-	if mod.isVndkSp() != isVndkSp {
-		t.Errorf("%q isVndkSp() must equal to %t", name, isVndkSp)
+	if mod.IsVndkSp() != isVndkSp {
+		t.Errorf("%q IsVndkSp() must equal to %t", name, isVndkSp)
 	}
 
 	// Check VNDK extension properties.
@@ -396,50 +396,6 @@
 	}
 }
 
-func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
-	t.Helper()
-	mod := ctx.ModuleForTests(moduleName, variant)
-	outputFiles := mod.OutputFiles(t, "")
-	if len(outputFiles) != 1 {
-		t.Errorf("%q must have single output\n", moduleName)
-		return
-	}
-	snapshotPath := filepath.Join(subDir, snapshotFilename)
-
-	if include {
-		out := singleton.Output(snapshotPath)
-		if fake {
-			if out.Rule == nil {
-				t.Errorf("Missing rule for module %q output file %q", moduleName, outputFiles[0])
-			}
-		} else {
-			if out.Input.String() != outputFiles[0].String() {
-				t.Errorf("The input of snapshot %q must be %q, but %q", moduleName, out.Input.String(), outputFiles[0])
-			}
-		}
-	} else {
-		out := singleton.MaybeOutput(snapshotPath)
-		if out.Rule != nil {
-			t.Errorf("There must be no rule for module %q output file %q", moduleName, outputFiles[0])
-		}
-	}
-}
-
-func checkSnapshot(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, false)
-}
-
-func checkSnapshotExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, false, false)
-}
-
-func checkSnapshotRule(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
-	t.Helper()
-	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
-}
-
 func checkWriteFileOutput(t *testing.T, params android.TestingBuildParams, expected []string) {
 	t.Helper()
 	content := android.ContentFromFileRuleForTests(t, params)
@@ -554,6 +510,13 @@
 			}
 		}
 
+		cc_library {
+			name: "libclang_rt.hwasan-llndk",
+			llndk: {
+				symbol_file: "libclang_rt.hwasan.map.txt",
+			}
+		}
+
 		cc_library_headers {
 			name: "libllndk_headers",
 			llndk: {
@@ -624,21 +587,21 @@
 
 	snapshotSingleton := ctx.SingletonForTests("vndk-snapshot")
 
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLib2ndPath, variant2nd)
-	checkSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLibPath, variant)
-	checkSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", vndkCoreLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_product", "libvndk_product.so", vndkCoreLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libvndk_sp", "libvndk_sp-x.so", vndkSpLib2ndPath, variant2nd)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLibPath, variant)
+	CheckSnapshot(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", llndkLib2ndPath, variant2nd)
 
 	snapshotConfigsPath := filepath.Join(snapshotVariantPath, "configs")
-	checkSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "")
-	checkSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "llndk.libraries.txt", "llndk.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkcore.libraries.txt", "vndkcore.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndksp.libraries.txt", "vndksp.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkprivate.libraries.txt", "vndkprivate.libraries.txt", snapshotConfigsPath, "")
+	CheckSnapshot(t, ctx, snapshotSingleton, "vndkproduct.libraries.txt", "vndkproduct.libraries.txt", snapshotConfigsPath, "")
 
 	checkVndkOutput(t, ctx, "vndk/vndk.libraries.txt", []string{
 		"LLNDK: libc.so",
@@ -661,7 +624,7 @@
 		"VNDK-product: libvndk_product.so",
 		"VNDK-product: libvndk_sp_product_private-x.so",
 	})
-	checkVndkLibrariesOutput(t, ctx, "llndk.libraries.txt", []string{"libc.so", "libdl.so", "libft2.so", "libllndk.so", "libm.so"})
+	checkVndkLibrariesOutput(t, ctx, "llndk.libraries.txt", []string{"libc.so", "libclang_rt.hwasan-llndk.so", "libdl.so", "libft2.so", "libllndk.so", "libm.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndkcore.libraries.txt", []string{"libvndk-private.so", "libvndk.so", "libvndk_product.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndksp.libraries.txt", []string{"libc++.so", "libvndk_sp-x.so", "libvndk_sp_private-x.so", "libvndk_sp_product_private-x.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndkprivate.libraries.txt", []string{"libft2.so", "libvndk-private.so", "libvndk_sp_private-x.so", "libvndk_sp_product_private-x.so"})
@@ -2636,15 +2599,6 @@
 	return modulesInOrder, allDeps
 }
 
-func getOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
-	for _, moduleName := range moduleNames {
-		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
-		output := module.outputFile.Path().RelativeToTop()
-		paths = append(paths, output)
-	}
-	return paths
-}
-
 func TestStaticLibDepReordering(t *testing.T) {
 	ctx := testCc(t, `
 	cc_library {
@@ -2672,7 +2626,7 @@
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
 	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
 		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
-	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
+	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -2707,7 +2661,7 @@
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
 	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
 		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
-	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b"})
+	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings did not account for shared libs"+
@@ -2792,12 +2746,8 @@
 		}
 	}
 	expected := []string{
-		"android_vendor.29_arm64_armv8-a_shared_1",
-		"android_vendor.29_arm64_armv8-a_shared_2",
 		"android_vendor.29_arm64_armv8-a_shared_current",
 		"android_vendor.29_arm64_armv8-a_shared",
-		"android_vendor.29_arm_armv7-a-neon_shared_1",
-		"android_vendor.29_arm_armv7-a-neon_shared_2",
 		"android_vendor.29_arm_armv7-a-neon_shared_current",
 		"android_vendor.29_arm_armv7-a-neon_shared",
 	}
@@ -2806,9 +2756,6 @@
 	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
 	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
 
-	params = result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared_1").Description("generate stub")
-	android.AssertSame(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
-
 	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
 		t.Helper()
 		m := result.ModuleForTests(module, variant).Module()
@@ -3357,7 +3304,7 @@
 
 	mybin := ctx.ModuleForTests("mybin", "android_arm64_armv8-a").Rule("ld")
 	actual := mybin.Implicits[:2]
-	expected := getOutputPaths(ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
+	expected := GetOutputPaths(ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -4284,3 +4231,152 @@
 		)
 	})
 }
+
+func TestIncludeDirectoryOrdering(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			local_include_dirs: ["local_include_dirs"],
+			export_include_dirs: ["export_include_dirs"],
+			export_system_include_dirs: ["export_system_include_dirs"],
+			static_libs: ["libstatic1", "libstatic2"],
+			whole_static_libs: ["libwhole1", "libwhole2"],
+			shared_libs: ["libshared1", "libshared2"],
+			header_libs: ["libheader1", "libheader2"],
+			target: {
+				android: {
+					shared_libs: ["libandroid"],
+					local_include_dirs: ["android_local_include_dirs"],
+					export_include_dirs: ["android_export_include_dirs"],
+				},
+				android_arm: {
+					shared_libs: ["libandroid_arm"],
+					local_include_dirs: ["android_arm_local_include_dirs"],
+					export_include_dirs: ["android_arm_export_include_dirs"],
+				},
+				linux: {
+					shared_libs: ["liblinux"],
+					local_include_dirs: ["linux_local_include_dirs"],
+					export_include_dirs: ["linux_export_include_dirs"],
+				},
+			},
+			multilib: {
+				lib32: {
+					shared_libs: ["lib32"],
+					local_include_dirs: ["lib32_local_include_dirs"],
+					export_include_dirs: ["lib32_export_include_dirs"],
+				},
+			},
+			arch: {
+				arm: {
+					shared_libs: ["libarm"],
+					local_include_dirs: ["arm_local_include_dirs"],
+					export_include_dirs: ["arm_export_include_dirs"],
+				},
+			},
+			stl: "libc++",
+			sdk_version: "20",
+		}
+
+		cc_library_headers {
+			name: "libheader1",
+			export_include_dirs: ["libheader1"],
+			sdk_version: "20",
+			stl: "none",
+		}
+
+		cc_library_headers {
+			name: "libheader2",
+			export_include_dirs: ["libheader2"],
+			sdk_version: "20",
+			stl: "none",
+		}
+	`
+
+	libs := []string{
+		"libstatic1",
+		"libstatic2",
+		"libwhole1",
+		"libwhole2",
+		"libshared1",
+		"libshared2",
+		"libandroid",
+		"libandroid_arm",
+		"liblinux",
+		"lib32",
+		"libarm",
+	}
+
+	for _, lib := range libs {
+		bp += fmt.Sprintf(`
+			cc_library {
+				name: "%s",
+				export_include_dirs: ["%s"],
+				sdk_version: "20",
+				stl: "none",
+			}
+		`, lib, lib)
+	}
+
+	ctx := PrepareForIntegrationTestWithCc.RunTestWithBp(t, bp)
+	// Use the arm variant instead of the arm64 variant so that it gets headers from
+	// ndk_libandroid_support to test LateStaticLibs.
+	cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/foo.o").Args["cFlags"]
+
+	var includes []string
+	flags := strings.Split(cflags, " ")
+	for i, flag := range flags {
+		if strings.Contains(flag, "Cflags") {
+			includes = append(includes, flag)
+		} else if strings.HasPrefix(flag, "-I") {
+			includes = append(includes, strings.TrimPrefix(flag, "-I"))
+		} else if flag == "-isystem" {
+			includes = append(includes, flags[i+1])
+		}
+	}
+
+	want := []string{
+		"${config.ArmClangThumbCflags}",
+		"${config.ArmClangCflags}",
+		"${config.CommonClangGlobalCflags}",
+		"${config.DeviceClangGlobalCflags}",
+		"${config.ClangExternalCflags}",
+		"${config.ArmToolchainClangCflags}",
+		"${config.ArmClangArmv7ANeonCflags}",
+		"${config.ArmClangGenericCflags}",
+		"export_include_dirs",
+		"linux_export_include_dirs",
+		"android_export_include_dirs",
+		"arm_export_include_dirs",
+		"lib32_export_include_dirs",
+		"android_arm_export_include_dirs",
+		"android_arm_local_include_dirs",
+		"lib32_local_include_dirs",
+		"arm_local_include_dirs",
+		"android_local_include_dirs",
+		"linux_local_include_dirs",
+		"local_include_dirs",
+		".",
+		"libheader1",
+		"libheader2",
+		"libwhole1",
+		"libwhole2",
+		"libstatic1",
+		"libstatic2",
+		"libshared1",
+		"libshared2",
+		"liblinux",
+		"libandroid",
+		"libarm",
+		"lib32",
+		"libandroid_arm",
+		"defaults/cc/common/ndk_libc++_shared",
+		"defaults/cc/common/ndk_libandroid_support",
+		"out/soong/ndk/sysroot/usr/include",
+		"out/soong/ndk/sysroot/usr/include/arm-linux-androideabi",
+		"${config.NoOverrideClangGlobalCflags}",
+	}
+
+	android.AssertArrayString(t, "includes", want, includes)
+}
diff --git a/cc/config/Android.bp b/cc/config/Android.bp
index 5ef247d..e4a8b62 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -10,6 +10,7 @@
         "soong-remoteexec",
     ],
     srcs: [
+        "bp2build.go",
         "clang.go",
         "global.go",
         "tidy.go",
@@ -31,6 +32,7 @@
         "arm64_linux_host.go",
     ],
     testSrcs: [
+        "bp2build_test.go",
         "tidy_test.go",
     ],
 }
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index a402f8f..439084e 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -163,56 +163,49 @@
 )
 
 const (
+	name          = "arm"
 	armGccVersion = "4.9"
+	gccTriple     = "arm-linux-androideabi"
+	clangTriple   = "armv7a-linux-androideabi"
 )
 
 func init() {
 	pctx.StaticVariable("armGccVersion", armGccVersion)
 
-	pctx.SourcePathVariable("ArmGccRoot",
-		"prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}")
+	pctx.SourcePathVariable("ArmGccRoot", "prebuilts/gcc/${HostPrebuiltTag}/arm/arm-linux-androideabi-${armGccVersion}")
 
-	pctx.StaticVariable("ArmLdflags", strings.Join(armLdflags, " "))
-	pctx.StaticVariable("ArmLldflags", strings.Join(armLldflags, " "))
+	// Just exported. Not created as a Ninja static variable.
+	exportedStringVars.Set("ArmClangTriple", clangTriple)
+
+	exportStringListStaticVariable("ArmLdflags", armLdflags)
+	exportStringListStaticVariable("ArmLldflags", armLldflags)
 
 	// Clang cflags
-	pctx.StaticVariable("ArmToolchainClangCflags", strings.Join(ClangFilterUnknownCflags(armToolchainCflags), " "))
-	pctx.StaticVariable("ArmClangCflags", strings.Join(ClangFilterUnknownCflags(armCflags), " "))
-	pctx.StaticVariable("ArmClangLdflags", strings.Join(ClangFilterUnknownCflags(armLdflags), " "))
-	pctx.StaticVariable("ArmClangLldflags", strings.Join(ClangFilterUnknownCflags(armLldflags), " "))
-	pctx.StaticVariable("ArmClangCppflags", strings.Join(ClangFilterUnknownCflags(armCppflags), " "))
+	exportStringListStaticVariable("ArmToolchainClangCflags", ClangFilterUnknownCflags(armToolchainCflags))
+	exportStringListStaticVariable("ArmClangCflags", ClangFilterUnknownCflags(armCflags))
+	exportStringListStaticVariable("ArmClangLdflags", ClangFilterUnknownCflags(armLdflags))
+	exportStringListStaticVariable("ArmClangLldflags", ClangFilterUnknownCflags(armLldflags))
+	exportStringListStaticVariable("ArmClangCppflags", ClangFilterUnknownCflags(armCppflags))
 
 	// Clang ARM vs. Thumb instruction set cflags
-	pctx.StaticVariable("ArmClangArmCflags", strings.Join(ClangFilterUnknownCflags(armArmCflags), " "))
-	pctx.StaticVariable("ArmClangThumbCflags", strings.Join(ClangFilterUnknownCflags(armThumbCflags), " "))
+	exportStringListStaticVariable("ArmClangArmCflags", ClangFilterUnknownCflags(armArmCflags))
+	exportStringListStaticVariable("ArmClangThumbCflags", ClangFilterUnknownCflags(armThumbCflags))
 
 	// Clang arch variant cflags
-	pctx.StaticVariable("ArmClangArmv7ACflags",
-		strings.Join(armClangArchVariantCflags["armv7-a"], " "))
-	pctx.StaticVariable("ArmClangArmv7ANeonCflags",
-		strings.Join(armClangArchVariantCflags["armv7-a-neon"], " "))
-	pctx.StaticVariable("ArmClangArmv8ACflags",
-		strings.Join(armClangArchVariantCflags["armv8-a"], " "))
-	pctx.StaticVariable("ArmClangArmv82ACflags",
-		strings.Join(armClangArchVariantCflags["armv8-2a"], " "))
+	exportStringListStaticVariable("ArmClangArmv7ACflags", armClangArchVariantCflags["armv7-a"])
+	exportStringListStaticVariable("ArmClangArmv7ANeonCflags", armClangArchVariantCflags["armv7-a-neon"])
+	exportStringListStaticVariable("ArmClangArmv8ACflags", armClangArchVariantCflags["armv8-a"])
+	exportStringListStaticVariable("ArmClangArmv82ACflags", armClangArchVariantCflags["armv8-2a"])
 
 	// Clang cpu variant cflags
-	pctx.StaticVariable("ArmClangGenericCflags",
-		strings.Join(armClangCpuVariantCflags[""], " "))
-	pctx.StaticVariable("ArmClangCortexA7Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a7"], " "))
-	pctx.StaticVariable("ArmClangCortexA8Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a8"], " "))
-	pctx.StaticVariable("ArmClangCortexA15Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a15"], " "))
-	pctx.StaticVariable("ArmClangCortexA53Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a53"], " "))
-	pctx.StaticVariable("ArmClangCortexA55Cflags",
-		strings.Join(armClangCpuVariantCflags["cortex-a55"], " "))
-	pctx.StaticVariable("ArmClangKraitCflags",
-		strings.Join(armClangCpuVariantCflags["krait"], " "))
-	pctx.StaticVariable("ArmClangKryoCflags",
-		strings.Join(armClangCpuVariantCflags["kryo"], " "))
+	exportStringListStaticVariable("ArmClangGenericCflags", armClangCpuVariantCflags[""])
+	exportStringListStaticVariable("ArmClangCortexA7Cflags", armClangCpuVariantCflags["cortex-a7"])
+	exportStringListStaticVariable("ArmClangCortexA8Cflags", armClangCpuVariantCflags["cortex-a8"])
+	exportStringListStaticVariable("ArmClangCortexA15Cflags", armClangCpuVariantCflags["cortex-a15"])
+	exportStringListStaticVariable("ArmClangCortexA53Cflags", armClangCpuVariantCflags["cortex-a53"])
+	exportStringListStaticVariable("ArmClangCortexA55Cflags", armClangCpuVariantCflags["cortex-a55"])
+	exportStringListStaticVariable("ArmClangKraitCflags", armClangCpuVariantCflags["krait"])
+	exportStringListStaticVariable("ArmClangKryoCflags", armClangCpuVariantCflags["kryo"])
 }
 
 var (
@@ -251,7 +244,7 @@
 }
 
 func (t *toolchainArm) Name() string {
-	return "arm"
+	return name
 }
 
 func (t *toolchainArm) GccRoot() string {
@@ -259,7 +252,7 @@
 }
 
 func (t *toolchainArm) GccTriple() string {
-	return "arm-linux-androideabi"
+	return gccTriple
 }
 
 func (t *toolchainArm) GccVersion() string {
@@ -272,7 +265,7 @@
 
 func (t *toolchainArm) ClangTriple() string {
 	// http://b/72619014 work around llvm LTO bug.
-	return "armv7a-linux-androideabi"
+	return clangTriple
 }
 
 func (t *toolchainArm) ndkTriple() string {
@@ -312,7 +305,7 @@
 }
 
 func (toolchainArm) LibclangRuntimeLibraryArch() string {
-	return "arm"
+	return name
 }
 
 func armToolchainFactory(arch android.Arch) Toolchain {
diff --git a/cc/config/bp2build.go b/cc/config/bp2build.go
new file mode 100644
index 0000000..19571f1
--- /dev/null
+++ b/cc/config/bp2build.go
@@ -0,0 +1,169 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"android/soong/android"
+	"fmt"
+	"regexp"
+	"strings"
+)
+
+// Helpers for exporting cc configuration information to Bazel.
+var (
+	// Map containing toolchain variables that are independent of the
+	// environment variables of the build.
+	exportedStringListVars = exportedStringListVariables{}
+	exportedStringVars     = exportedStringVariables{}
+)
+
+type exportedStringVariables map[string]string
+type exportedStringListVariables map[string][]string
+
+func (m exportedStringVariables) Set(k string, v string) {
+	m[k] = v
+}
+
+// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
+func exportStringStaticVariable(name string, value string) {
+	pctx.StaticVariable(name, value)
+	exportedStringVars.Set(name, value)
+}
+
+func (m exportedStringListVariables) Set(k string, v []string) {
+	m[k] = v
+}
+
+// Convenience function to declare a static variable and export it to Bazel's cc_toolchain.
+func exportStringListStaticVariable(name string, value []string) {
+	pctx.StaticVariable(name, strings.Join(value, " "))
+	exportedStringListVars.Set(name, value)
+}
+
+// BazelCcToolchainVars generates bzl file content containing variables for
+// Bazel's cc_toolchain configuration.
+func BazelCcToolchainVars() string {
+	ret := "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT.\n\n"
+
+	// Ensure that string s has no invalid characters to be generated into the bzl file.
+	validateCharacters := func(s string) string {
+		for _, c := range []string{`\n`, `"`, `\`} {
+			if strings.Contains(s, c) {
+				panic(fmt.Errorf("%s contains illegal character %s", s, c))
+			}
+		}
+		return s
+	}
+
+	// For each exported variable, recursively expand elements in the variableValue
+	// list to ensure that interpolated variables are expanded according to their values
+	// in the variable scope.
+	for _, k := range android.SortedStringKeys(exportedStringListVars) {
+		variableValue := exportedStringListVars[k]
+		var expandedVars []string
+		for _, v := range variableValue {
+			expandedVars = append(expandedVars, expandVar(v, exportedStringVars, exportedStringListVars)...)
+		}
+		// Build the list for this variable.
+		list := "["
+		for _, flag := range expandedVars {
+			list += fmt.Sprintf("\n    \"%s\",", validateCharacters(flag))
+		}
+		list += "\n]"
+		// Assign the list as a bzl-private variable; this variable will be exported
+		// out through a constants struct later.
+		ret += fmt.Sprintf("_%s = %s\n", k, list)
+		ret += "\n"
+	}
+
+	for _, k := range android.SortedStringKeys(exportedStringVars) {
+		variableValue := exportedStringVars[k]
+		expandedVar := expandVar(variableValue, exportedStringVars, exportedStringListVars)
+		if len(expandedVar) > 1 {
+			panic(fmt.Errorf("%s expands to more than one string value: %s", variableValue, expandedVar))
+		}
+		ret += fmt.Sprintf("_%s = \"%s\"\n", k, validateCharacters(expandedVar[0]))
+		ret += "\n"
+	}
+
+	// Build the exported constants struct.
+	ret += "constants = struct(\n"
+	for _, k := range android.SortedStringKeys(exportedStringVars) {
+		ret += fmt.Sprintf("    %s = _%s,\n", k, k)
+	}
+	for _, k := range android.SortedStringKeys(exportedStringListVars) {
+		ret += fmt.Sprintf("    %s = _%s,\n", k, k)
+	}
+	ret += ")"
+	return ret
+}
+
+// expandVar recursively expand interpolated variables in the exportedVars scope.
+//
+// We're using a string slice to track the seen variables to avoid
+// stackoverflow errors with infinite recursion. it's simpler to use a
+// string slice than to handle a pass-by-referenced map, which would make it
+// quite complex to track depth-first interpolations. It's also unlikely the
+// interpolation stacks are deep (n > 1).
+func expandVar(toExpand string, stringScope exportedStringVariables, stringListScope exportedStringListVariables) []string {
+	// e.g. "${ClangExternalCflags}"
+	r := regexp.MustCompile(`\${([a-zA-Z0-9_]+)}`)
+
+	// Internal recursive function.
+	var expandVarInternal func(string, map[string]bool) []string
+	expandVarInternal = func(toExpand string, seenVars map[string]bool) []string {
+		var ret []string
+		for _, v := range strings.Split(toExpand, " ") {
+			matches := r.FindStringSubmatch(v)
+			if len(matches) == 0 {
+				return []string{v}
+			}
+
+			if len(matches) != 2 {
+				panic(fmt.Errorf(
+					"Expected to only match 1 subexpression in %s, got %d",
+					v,
+					len(matches)-1))
+			}
+
+			// Index 1 of FindStringSubmatch contains the subexpression match
+			// (variable name) of the capture group.
+			variable := matches[1]
+			// toExpand contains a variable.
+			if _, ok := seenVars[variable]; ok {
+				panic(fmt.Errorf(
+					"Unbounded recursive interpolation of variable: %s", variable))
+			}
+			// A map is passed-by-reference. Create a new map for
+			// this scope to prevent variables seen in one depth-first expansion
+			// to be also treated as "seen" in other depth-first traversals.
+			newSeenVars := map[string]bool{}
+			for k := range seenVars {
+				newSeenVars[k] = true
+			}
+			newSeenVars[variable] = true
+			if unexpandedVars, ok := stringListScope[variable]; ok {
+				for _, unexpandedVar := range unexpandedVars {
+					ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
+				}
+			} else if unexpandedVar, ok := stringScope[variable]; ok {
+				ret = append(ret, expandVarInternal(unexpandedVar, newSeenVars)...)
+			}
+		}
+		return ret
+	}
+
+	return expandVarInternal(toExpand, map[string]bool{})
+}
diff --git a/cc/config/bp2build_test.go b/cc/config/bp2build_test.go
new file mode 100644
index 0000000..a4745e6
--- /dev/null
+++ b/cc/config/bp2build_test.go
@@ -0,0 +1,117 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+	"testing"
+)
+
+func TestExpandVars(t *testing.T) {
+	testCases := []struct {
+		description     string
+		stringScope     exportedStringVariables
+		stringListScope exportedStringListVariables
+		toExpand        string
+		expectedValues  []string
+	}{
+		{
+			description:    "no expansion for non-interpolated value",
+			toExpand:       "foo",
+			expectedValues: []string{"foo"},
+		},
+		{
+			description: "single level expansion for string var",
+			stringScope: exportedStringVariables{
+				"foo": "bar",
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "single level expansion string list var",
+			stringListScope: exportedStringListVariables{
+				"foo": []string{"bar"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"bar"},
+		},
+		{
+			description: "mixed level expansion for string list var",
+			stringScope: exportedStringVariables{
+				"foo": "${bar}",
+				"qux": "hello",
+			},
+			stringListScope: exportedStringListVariables{
+				"bar": []string{"baz", "${qux}"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz", "hello"},
+		},
+		{
+			description: "double level expansion",
+			stringListScope: exportedStringListVariables{
+				"foo": []string{"${bar}"},
+				"bar": []string{"baz"},
+			},
+			toExpand:       "${foo}",
+			expectedValues: []string{"baz"},
+		},
+		{
+			description: "double level expansion with a literal",
+			stringListScope: exportedStringListVariables{
+				"a": []string{"${b}", "c"},
+				"b": []string{"d"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d", "c"},
+		},
+		{
+			description: "double level expansion, with two variables in a string",
+			stringListScope: exportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"d"},
+				"c": []string{"e"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"d", "e"},
+		},
+		{
+			description: "triple level expansion with two variables in a string",
+			stringListScope: exportedStringListVariables{
+				"a": []string{"${b} ${c}"},
+				"b": []string{"${c}", "${d}"},
+				"c": []string{"${d}"},
+				"d": []string{"foo"},
+			},
+			toExpand:       "${a}",
+			expectedValues: []string{"foo", "foo", "foo"},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			output := expandVar(testCase.toExpand, testCase.stringScope, testCase.stringListScope)
+			if len(output) != len(testCase.expectedValues) {
+				t.Errorf("Expected %d values, got %d", len(testCase.expectedValues), len(output))
+			}
+			for i, actual := range output {
+				expectedValue := testCase.expectedValues[i]
+				if actual != expectedValue {
+					t.Errorf("Actual value '%s' doesn't match expected value '%s'", actual, expectedValue)
+				}
+			}
+		})
+	}
+}
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 5e46d5a..c484fc9 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -98,7 +98,7 @@
 }
 
 func init() {
-	pctx.StaticVariable("ClangExtraCflags", strings.Join([]string{
+	exportStringListStaticVariable("ClangExtraCflags", []string{
 		"-D__compiler_offsetof=__builtin_offsetof",
 
 		// Emit address-significance table which allows linker to perform safe ICF. Clang does
@@ -151,9 +151,9 @@
 		// This macro allows the bionic versioning.h to indirectly determine whether the
 		// option -Wunguarded-availability is on or not.
 		"-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__",
-	}, " "))
+	})
 
-	pctx.StaticVariable("ClangExtraCppflags", strings.Join([]string{
+	exportStringListStaticVariable("ClangExtraCppflags", []string{
 		// -Wimplicit-fallthrough is not enabled by -Wall.
 		"-Wimplicit-fallthrough",
 
@@ -162,13 +162,11 @@
 
 		// libc++'s math.h has an #include_next outside of system_headers.
 		"-Wno-gnu-include-next",
-	}, " "))
+	})
 
-	pctx.StaticVariable("ClangExtraTargetCflags", strings.Join([]string{
-		"-nostdlibinc",
-	}, " "))
+	exportStringListStaticVariable("ClangExtraTargetCflags", []string{"-nostdlibinc"})
 
-	pctx.StaticVariable("ClangExtraNoOverrideCflags", strings.Join([]string{
+	exportStringListStaticVariable("ClangExtraNoOverrideCflags", []string{
 		"-Werror=address-of-temporary",
 		// Bug: http://b/29823425 Disable -Wnull-dereference until the
 		// new cases detected by this warning in Clang r271374 are
@@ -203,11 +201,11 @@
 		"-Wno-non-c-typedef-for-linkage", // http://b/161304145
 		// New warnings to be fixed after clang-r407598
 		"-Wno-string-concatenation", // http://b/175068488
-	}, " "))
+	})
 
 	// Extra cflags for external third-party projects to disable warnings that
 	// are infeasible to fix in all the external projects and their upstream repos.
-	pctx.StaticVariable("ClangExtraExternalCflags", strings.Join([]string{
+	exportStringListStaticVariable("ClangExtraExternalCflags", []string{
 		"-Wno-enum-compare",
 		"-Wno-enum-compare-switch",
 
@@ -228,7 +226,7 @@
 
 		// http://b/165945989
 		"-Wno-psabi",
-	}, " "))
+	})
 }
 
 func ClangFilterUnknownCflags(cflags []string) []string {
diff --git a/cc/config/global.go b/cc/config/global.go
index 23106ec..24e10a4 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -165,13 +165,25 @@
 		commonGlobalCflags = append(commonGlobalCflags, "-fdebug-prefix-map=/proc/self/cwd=")
 	}
 
-	pctx.StaticVariable("CommonGlobalConlyflags", strings.Join(commonGlobalConlyflags, " "))
-	pctx.StaticVariable("DeviceGlobalCppflags", strings.Join(deviceGlobalCppflags, " "))
-	pctx.StaticVariable("DeviceGlobalLdflags", strings.Join(deviceGlobalLdflags, " "))
-	pctx.StaticVariable("DeviceGlobalLldflags", strings.Join(deviceGlobalLldflags, " "))
-	pctx.StaticVariable("HostGlobalCppflags", strings.Join(hostGlobalCppflags, " "))
-	pctx.StaticVariable("HostGlobalLdflags", strings.Join(hostGlobalLdflags, " "))
-	pctx.StaticVariable("HostGlobalLldflags", strings.Join(hostGlobalLldflags, " "))
+	exportStringListStaticVariable("CommonGlobalConlyflags", commonGlobalConlyflags)
+	exportStringListStaticVariable("DeviceGlobalCppflags", deviceGlobalCppflags)
+	exportStringListStaticVariable("DeviceGlobalLdflags", deviceGlobalLdflags)
+	exportStringListStaticVariable("DeviceGlobalLldflags", deviceGlobalLldflags)
+	exportStringListStaticVariable("HostGlobalCppflags", hostGlobalCppflags)
+	exportStringListStaticVariable("HostGlobalLdflags", hostGlobalLdflags)
+	exportStringListStaticVariable("HostGlobalLldflags", hostGlobalLldflags)
+
+	// Export the static default CommonClangGlobalCflags to Bazel.
+	// TODO(187086342): handle cflags that are set in VariableFuncs.
+	commonClangGlobalCFlags := append(
+		ClangFilterUnknownCflags(commonGlobalCflags),
+		[]string{
+			"${ClangExtraCflags}",
+			// Default to zero initialization.
+			"-ftrivial-auto-var-init=zero",
+			"-enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang",
+		}...)
+	exportedStringListVars.Set("CommonClangGlobalCflags", commonClangGlobalCFlags)
 
 	pctx.VariableFunc("CommonClangGlobalCflags", func(ctx android.PackageVarContext) string {
 		flags := ClangFilterUnknownCflags(commonGlobalCflags)
@@ -190,41 +202,42 @@
 			// Default to zero initialization.
 			flags = append(flags, "-ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang")
 		}
-
 		return strings.Join(flags, " ")
 	})
 
+	// Export the static default DeviceClangGlobalCflags to Bazel.
+	// TODO(187086342): handle cflags that are set in VariableFuncs.
+	deviceClangGlobalCflags := append(ClangFilterUnknownCflags(deviceGlobalCflags), "${ClangExtraTargetCflags}")
+	exportedStringListVars.Set("DeviceClangGlobalCflags", deviceClangGlobalCflags)
+
 	pctx.VariableFunc("DeviceClangGlobalCflags", func(ctx android.PackageVarContext) string {
 		if ctx.Config().Fuchsia() {
 			return strings.Join(ClangFilterUnknownCflags(deviceGlobalCflags), " ")
 		} else {
-			return strings.Join(append(ClangFilterUnknownCflags(deviceGlobalCflags), "${ClangExtraTargetCflags}"), " ")
+			return strings.Join(deviceClangGlobalCflags, " ")
 		}
 	})
-	pctx.StaticVariable("HostClangGlobalCflags",
-		strings.Join(ClangFilterUnknownCflags(hostGlobalCflags), " "))
-	pctx.StaticVariable("NoOverrideClangGlobalCflags",
-		strings.Join(append(ClangFilterUnknownCflags(noOverrideGlobalCflags), "${ClangExtraNoOverrideCflags}"), " "))
 
-	pctx.StaticVariable("CommonClangGlobalCppflags",
-		strings.Join(append(ClangFilterUnknownCflags(commonGlobalCppflags), "${ClangExtraCppflags}"), " "))
-
-	pctx.StaticVariable("ClangExternalCflags", "${ClangExtraExternalCflags}")
+	exportStringListStaticVariable("HostClangGlobalCflags", ClangFilterUnknownCflags(hostGlobalCflags))
+	exportStringListStaticVariable("NoOverrideClangGlobalCflags", append(ClangFilterUnknownCflags(noOverrideGlobalCflags), "${ClangExtraNoOverrideCflags}"))
+	exportStringListStaticVariable("CommonClangGlobalCppflags", append(ClangFilterUnknownCflags(commonGlobalCppflags), "${ClangExtraCppflags}"))
+	exportStringListStaticVariable("ClangExternalCflags", []string{"${ClangExtraExternalCflags}"})
 
 	// Everything in these lists is a crime against abstraction and dependency tracking.
 	// Do not add anything to this list.
-	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I",
-		[]string{
-			"system/core/include",
-			"system/logging/liblog/include",
-			"system/media/audio/include",
-			"hardware/libhardware/include",
-			"hardware/libhardware_legacy/include",
-			"hardware/ril/include",
-			"frameworks/native/include",
-			"frameworks/native/opengl/include",
-			"frameworks/av/include",
-		})
+	commonGlobalIncludes := []string{
+		"system/core/include",
+		"system/logging/liblog/include",
+		"system/media/audio/include",
+		"hardware/libhardware/include",
+		"hardware/libhardware_legacy/include",
+		"hardware/ril/include",
+		"frameworks/native/include",
+		"frameworks/native/opengl/include",
+		"frameworks/av/include",
+	}
+	exportedStringListVars.Set("CommonGlobalIncludes", commonGlobalIncludes)
+	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I", commonGlobalIncludes)
 
 	pctx.SourcePathVariable("ClangDefaultBase", ClangDefaultBase)
 	pctx.VariableFunc("ClangBase", func(ctx android.PackageVarContext) string {
@@ -281,12 +294,3 @@
 }
 
 var HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
-
-func envOverrideFunc(envVar, defaultVal string) func(ctx android.PackageVarContext) string {
-	return func(ctx android.PackageVarContext) string {
-		if override := ctx.Config().Getenv(envVar); override != "" {
-			return override
-		}
-		return defaultVal
-	}
-}
diff --git a/cc/coverage.go b/cc/coverage.go
index 5b5ccf2..baf4226 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -96,7 +96,8 @@
 			// flags that the module may use.
 			flags.Local.CFlags = append(flags.Local.CFlags, "-Wno-frame-larger-than=", "-O0")
 		} else if clangCoverage {
-			flags.Local.CommonFlags = append(flags.Local.CommonFlags, profileInstrFlag, "-fcoverage-mapping", "-Wno-pass-failed")
+			flags.Local.CommonFlags = append(flags.Local.CommonFlags, profileInstrFlag,
+				"-fcoverage-mapping", "-Wno-pass-failed", "-D__ANDROID_CLANG_COVERAGE__")
 		}
 	}
 
@@ -203,7 +204,7 @@
 type Coverage interface {
 	android.Module
 	IsNativeCoverageNeeded(ctx android.BaseModuleContext) bool
-	PreventInstall()
+	SetPreventInstall()
 	HideFromMake()
 	MarkAsCoverageVariant(bool)
 	EnableCoverageIfNeeded()
@@ -236,7 +237,7 @@
 		// to an APEX via 'data' property.
 		m := mctx.CreateVariations("", "cov")
 		m[0].(Coverage).MarkAsCoverageVariant(false)
-		m[0].(Coverage).PreventInstall()
+		m[0].(Coverage).SetPreventInstall()
 		m[0].(Coverage).HideFromMake()
 
 		m[1].(Coverage).MarkAsCoverageVariant(true)
diff --git a/cc/gen.go b/cc/gen.go
index b152e02..3a1a0e2 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -204,7 +204,7 @@
 	headerFile := android.GenPathWithExt(ctx, "windmc", srcFile, "h")
 	rcFile := android.GenPathWithExt(ctx, "windmc", srcFile, "rc")
 
-	windmcCmd := gccCmd(flags.toolchain, "windmc")
+	windmcCmd := mingwCmd(flags.toolchain, "windmc")
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:           windmc,
diff --git a/cc/genrule.go b/cc/genrule.go
index 82d7205..b0efc6c 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -103,7 +103,7 @@
 		// If not, we assume modules under proprietary paths are compatible for
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, that is
 		// PLATFORM_VNDK_VERSION.
-		if vndkVersion == "current" || !isVendorProprietaryModule(ctx) {
+		if vndkVersion == "current" || !IsVendorProprietaryModule(ctx) {
 			variants = append(variants, VendorVariationPrefix+ctx.DeviceConfig().PlatformVndkVersion())
 		} else {
 			variants = append(variants, VendorVariationPrefix+vndkVersion)
diff --git a/cc/image.go b/cc/image.go
index 5d41717..15ec1c8 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -341,11 +341,11 @@
 }
 
 func (m *Module) ExtraVariants() []string {
-	return m.Properties.ExtraVariants
+	return m.Properties.ExtraVersionedImageVariations
 }
 
 func (m *Module) AppendExtraVariant(extraVariant string) {
-	m.Properties.ExtraVariants = append(m.Properties.ExtraVariants, extraVariant)
+	m.Properties.ExtraVersionedImageVariations = append(m.Properties.ExtraVersionedImageVariations, extraVariant)
 }
 
 func (m *Module) SetRamdiskVariantNeeded(b bool) {
@@ -365,8 +365,8 @@
 }
 
 func (m *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
-	if snapshot, ok := m.linker.(snapshotInterface); ok {
-		return snapshot.version()
+	if snapshot, ok := m.linker.(SnapshotInterface); ok {
+		return snapshot.Version()
 	} else {
 		mctx.ModuleErrorf("version is unknown for snapshot prebuilt")
 		// Should we be panicking here instead?
@@ -430,6 +430,16 @@
 	recoverySnapshotVersion := mctx.DeviceConfig().RecoverySnapshotVersion()
 	usingRecoverySnapshot := recoverySnapshotVersion != "current" &&
 		recoverySnapshotVersion != ""
+	needVndkVersionVendorVariantForLlndk := false
+	if boardVndkVersion != "" {
+		boardVndkApiLevel, err := android.ApiLevelFromUser(mctx, boardVndkVersion)
+		if err == nil && !boardVndkApiLevel.IsPreview() {
+			// VNDK snapshot newer than v30 has LLNDK stub libraries.
+			// Only the VNDK version less than or equal to v30 requires generating the vendor
+			// variant of the VNDK version from the source tree.
+			needVndkVersionVendorVariantForLlndk = boardVndkApiLevel.LessThanOrEqualTo(android.ApiLevelOrPanic(mctx, "30"))
+		}
+	}
 	if boardVndkVersion == "current" {
 		boardVndkVersion = platformVndkVersion
 	}
@@ -446,7 +456,9 @@
 			vendorVariants = append(vendorVariants, platformVndkVersion)
 			productVariants = append(productVariants, platformVndkVersion)
 		}
-		if boardVndkVersion != "" {
+		// Generate vendor variants for boardVndkVersion only if the VNDK snapshot does not
+		// provide the LLNDK stub libraries.
+		if needVndkVersionVendorVariantForLlndk {
 			vendorVariants = append(vendorVariants, boardVndkVersion)
 		}
 		if productVndkVersion != "" {
@@ -484,7 +496,7 @@
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, or
 		// PLATFORM_VNDK_VERSION.
 		if m.HasVendorVariant() {
-			if isVendorProprietaryModule(mctx) {
+			if IsVendorProprietaryModule(mctx) {
 				vendorVariants = append(vendorVariants, boardVndkVersion)
 			} else {
 				vendorVariants = append(vendorVariants, platformVndkVersion)
@@ -513,7 +525,7 @@
 				platformVndkVersion,
 				boardVndkVersion,
 			)
-		} else if isVendorProprietaryModule(mctx) {
+		} else if IsVendorProprietaryModule(mctx) {
 			vendorVariants = append(vendorVariants, boardVndkVersion)
 		} else {
 			vendorVariants = append(vendorVariants, platformVndkVersion)
@@ -567,7 +579,7 @@
 
 	// If using a snapshot, the recovery variant under AOSP directories is not needed,
 	// except for kernel headers, which needs all variants.
-	if m.KernelHeadersDecorator() &&
+	if !m.KernelHeadersDecorator() &&
 		!m.IsSnapshotPrebuilt() &&
 		usingRecoverySnapshot &&
 		!isRecoveryProprietaryModule(mctx) {
@@ -617,7 +629,7 @@
 }
 
 func (c *Module) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	return c.Properties.ExtraVariants
+	return c.Properties.ExtraVersionedImageVariations
 }
 
 func squashVendorSrcs(m *Module) {
diff --git a/cc/library.go b/cc/library.go
index c5ff9b1..5b6c623 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -28,6 +28,7 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+	"android/soong/bazel/cquery"
 	"android/soong/cc/config"
 )
 
@@ -111,9 +112,6 @@
 		Check_all_apis *bool
 	}
 
-	// Order symbols in .bss section by their sizes.  Only useful for shared libraries.
-	Sort_bss_symbols_by_size *bool
-
 	// Inject boringssl hash into the shared library.  This is only intended for use by external/boringssl.
 	Inject_bssl_hash *bool `android:"arch_variant"`
 
@@ -221,28 +219,40 @@
 // For bp2build conversion.
 type bazelCcLibraryAttributes struct {
 	// Attributes pertaining to both static and shared variants.
-	Srcs               bazel.LabelListAttribute
-	Hdrs               bazel.LabelListAttribute
-	Deps               bazel.LabelListAttribute
-	Dynamic_deps       bazel.LabelListAttribute
-	Whole_archive_deps bazel.LabelListAttribute
-	Copts              bazel.StringListAttribute
-	Includes           bazel.StringListAttribute
-	Linkopts           bazel.StringListAttribute
-	// Attributes pertaining to shared variant.
-	Shared_copts                  bazel.StringListAttribute
-	Shared_srcs                   bazel.LabelListAttribute
-	Static_deps_for_shared        bazel.LabelListAttribute
-	Dynamic_deps_for_shared       bazel.LabelListAttribute
-	Whole_archive_deps_for_shared bazel.LabelListAttribute
-	User_link_flags               bazel.StringListAttribute
-	Version_script                bazel.LabelAttribute
-	// Attributes pertaining to static variant.
-	Static_copts                  bazel.StringListAttribute
-	Static_srcs                   bazel.LabelListAttribute
-	Static_deps_for_static        bazel.LabelListAttribute
-	Dynamic_deps_for_static       bazel.LabelListAttribute
-	Whole_archive_deps_for_static bazel.LabelListAttribute
+	Srcs    bazel.LabelListAttribute
+	Srcs_c  bazel.LabelListAttribute
+	Srcs_as bazel.LabelListAttribute
+
+	Copts      bazel.StringListAttribute
+	Cppflags   bazel.StringListAttribute
+	Conlyflags bazel.StringListAttribute
+	Asflags    bazel.StringListAttribute
+
+	Hdrs                bazel.LabelListAttribute
+	Deps                bazel.LabelListAttribute
+	Implementation_deps bazel.LabelListAttribute
+	Dynamic_deps        bazel.LabelListAttribute
+	Whole_archive_deps  bazel.LabelListAttribute
+	Includes            bazel.StringListAttribute
+	Linkopts            bazel.StringListAttribute
+	Use_libcrt          bazel.BoolAttribute
+
+	// This is shared only.
+	Version_script bazel.LabelAttribute
+
+	// Common properties shared between both shared and static variants.
+	Shared staticOrSharedAttributes
+	Static staticOrSharedAttributes
+
+	Strip stripAttributes
+}
+
+type stripAttributes struct {
+	Keep_symbols                 bazel.BoolAttribute
+	Keep_symbols_and_debug_frame bazel.BoolAttribute
+	Keep_symbols_list            bazel.StringListAttribute
+	All                          bazel.BoolAttribute
+	None                         bazel.BoolAttribute
 }
 
 type bazelCcLibrary struct {
@@ -287,28 +297,39 @@
 	linkerAttrs := bp2BuildParseLinkerProps(ctx, m)
 	exportedIncludes := bp2BuildParseExportedIncludes(ctx, m)
 
-	var srcs bazel.LabelListAttribute
-	srcs.Append(compilerAttrs.srcs)
+	srcs := compilerAttrs.srcs
 
 	attrs := &bazelCcLibraryAttributes{
-		Srcs:                          srcs,
-		Deps:                          linkerAttrs.deps,
-		Dynamic_deps:                  linkerAttrs.dynamicDeps,
-		Whole_archive_deps:            linkerAttrs.wholeArchiveDeps,
-		Copts:                         compilerAttrs.copts,
-		Includes:                      exportedIncludes,
-		Linkopts:                      linkerAttrs.linkopts,
-		Shared_copts:                  sharedAttrs.copts,
-		Shared_srcs:                   sharedAttrs.srcs,
-		Static_deps_for_shared:        sharedAttrs.staticDeps,
-		Whole_archive_deps_for_shared: sharedAttrs.wholeArchiveDeps,
-		Dynamic_deps_for_shared:       sharedAttrs.dynamicDeps,
-		Version_script:                linkerAttrs.versionScript,
-		Static_copts:                  staticAttrs.copts,
-		Static_srcs:                   staticAttrs.srcs,
-		Static_deps_for_static:        staticAttrs.staticDeps,
-		Whole_archive_deps_for_static: staticAttrs.wholeArchiveDeps,
-		Dynamic_deps_for_static:       staticAttrs.dynamicDeps,
+		Srcs:    srcs,
+		Srcs_c:  compilerAttrs.cSrcs,
+		Srcs_as: compilerAttrs.asSrcs,
+
+		Copts:      compilerAttrs.copts,
+		Cppflags:   compilerAttrs.cppFlags,
+		Conlyflags: compilerAttrs.conlyFlags,
+		Asflags:    compilerAttrs.asFlags,
+
+		Implementation_deps: linkerAttrs.deps,
+		Deps:                linkerAttrs.exportedDeps,
+		Dynamic_deps:        linkerAttrs.dynamicDeps,
+		Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
+		Includes:            exportedIncludes,
+		Linkopts:            linkerAttrs.linkopts,
+		Use_libcrt:          linkerAttrs.useLibcrt,
+
+		Version_script: linkerAttrs.versionScript,
+
+		Strip: stripAttributes{
+			Keep_symbols:                 linkerAttrs.stripKeepSymbols,
+			Keep_symbols_and_debug_frame: linkerAttrs.stripKeepSymbolsAndDebugFrame,
+			Keep_symbols_list:            linkerAttrs.stripKeepSymbolsList,
+			All:                          linkerAttrs.stripAll,
+			None:                         linkerAttrs.stripNone,
+		},
+
+		Shared: sharedAttrs,
+
+		Static: staticAttrs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
@@ -331,6 +352,7 @@
 		staticLibrarySdkMemberType,
 		staticAndSharedLibrarySdkMemberType,
 	}
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -339,7 +361,7 @@
 	module, library := NewLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyStatic()
 	module.sdkMemberTypes = []android.SdkMemberType{staticLibrarySdkMemberType}
-	module.bazelHandler = &staticLibraryBazelHandler{module: module}
+	module.bazelHandler = &ccLibraryBazelHandler{module: module}
 	return module.Init()
 }
 
@@ -524,36 +546,24 @@
 	collectedSnapshotHeaders android.Paths
 }
 
-type staticLibraryBazelHandler struct {
+type ccLibraryBazelHandler struct {
 	bazelHandler
 
 	module *Module
 }
 
-func (handler *staticLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
-	bazelCtx := ctx.Config().BazelContext
-	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
-	if err != nil {
-		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
+// generateStaticBazelBuildActions constructs the StaticLibraryInfo Soong
+// provider from a Bazel shared library's CcInfo provider.
+func (handler *ccLibraryBazelHandler) generateStaticBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+	rootStaticArchives := ccInfo.RootStaticArchives
+	if len(rootStaticArchives) != 1 {
+		ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
 		return false
 	}
-	if !ok {
-		return ok
-	}
-	outputPaths := ccInfo.OutputFiles
-	objPaths := ccInfo.CcObjectFiles
-	if len(outputPaths) > 1 {
-		// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
-		// We should support this.
-		ctx.ModuleErrorf("expected at most one output file for '%s', but got %s", label, objPaths)
-		return false
-	} else if len(outputPaths) == 0 {
-		handler.module.outputFile = android.OptionalPath{}
-		return true
-	}
-	outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
+	outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 
+	objPaths := ccInfo.CcObjectFiles
 	objFiles := make(android.Paths, len(objPaths))
 	for i, objPath := range objPaths {
 		objFiles[i] = android.PathForBazelOut(ctx, objPath)
@@ -567,40 +577,117 @@
 		ReuseObjects:  objects,
 		Objects:       objects,
 
-		// TODO(cparsons): Include transitive static libraries in this provider to support
+		// TODO(b/190524881): Include transitive static libraries in this provider to support
 		// static libraries with deps.
 		TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder(android.TOPOLOGICAL).
 			Direct(outputFilePath).
 			Build(),
 	})
 
-	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfoFromCcInfo(ctx, ccInfo))
+	return true
+}
+
+// generateSharedBazelBuildActions constructs the SharedLibraryInfo Soong
+// provider from a Bazel shared library's CcInfo provider.
+func (handler *ccLibraryBazelHandler) generateSharedBazelBuildActions(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
+	rootDynamicLibraries := ccInfo.RootDynamicLibraries
+
+	if len(rootDynamicLibraries) != 1 {
+		ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
+		return false
+	}
+	outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+
+	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = outputFilePath
+
+	tocFile := getTocFile(ctx, label, ccInfo.OutputFiles)
+	handler.module.linker.(*libraryDecorator).tocFile = tocFile
+
+	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
+		TableOfContents: tocFile,
+		SharedLibrary:   outputFilePath,
+		Target:          ctx.Target(),
+		// TODO(b/190524881): Include transitive static libraries in this provider to support
+		// static libraries with deps. The provider key for this is TransitiveStaticLibrariesForOrdering.
+	})
+	return true
+}
+
+// getTocFile looks for the .so.toc file in the target's output files, if any. The .so.toc file
+// contains the table of contents of all symbols of a shared object.
+func getTocFile(ctx android.ModuleContext, label string, outputFiles []string) android.OptionalPath {
+	var tocFile string
+	for _, file := range outputFiles {
+		if strings.HasSuffix(file, ".so.toc") {
+			if tocFile != "" {
+				ctx.ModuleErrorf("The %s target cannot produce more than 1 .toc file.", label)
+			}
+			tocFile = file
+			// Don't break to validate that there are no multiple .toc files per .so.
+		}
+	}
+	if tocFile == "" {
+		return android.OptionalPath{}
+	}
+	return android.OptionalPathForPath(android.PathForBazelOut(ctx, tocFile))
+}
+
+func (handler *ccLibraryBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, ctx.Arch().ArchType)
+	if err != nil {
+		ctx.ModuleErrorf("Error getting Bazel CcInfo: %s", err)
+		return false
+	}
+	if !ok {
+		return ok
+	}
+
+	if handler.module.static() {
+		if ok := handler.generateStaticBazelBuildActions(ctx, label, ccInfo); !ok {
+			return false
+		}
+	} else if handler.module.Shared() {
+		if ok := handler.generateSharedBazelBuildActions(ctx, label, ccInfo); !ok {
+			return false
+		}
+	} else {
+		return false
+	}
+
+	handler.module.linker.(*libraryDecorator).setFlagExporterInfoFromCcInfo(ctx, ccInfo)
+	handler.module.maybeUnhideFromMake()
+
 	if i, ok := handler.module.linker.(snapshotLibraryInterface); ok {
 		// Dependencies on this library will expect collectedSnapshotHeaders to
 		// be set, otherwise validation will fail. For now, set this to an empty
 		// list.
-		// TODO(cparsons): More closely mirror the collectHeadersForSnapshot
+		// TODO(b/190533363): More closely mirror the collectHeadersForSnapshot
 		// implementation.
 		i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
 	}
-
 	return ok
 }
 
-// collectHeadersForSnapshot collects all exported headers from library.
-// It globs header files in the source tree for exported include directories,
-// and tracks generated header files separately.
-//
-// This is to be called from GenerateAndroidBuildActions, and then collected
-// header files can be retrieved by snapshotHeaders().
-func (l *libraryDecorator) collectHeadersForSnapshot(ctx android.ModuleContext) {
+func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
+	flagExporterInfo := flagExporterInfoFromCcInfo(ctx, ccInfo)
+	// flag exporters consolidates properties like includes, flags, dependencies that should be
+	// exported from this module to other modules
+	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfo)
+	// Store flag info to be passed along to androidmk
+	// TODO(b/184387147): Androidmk should be done in Bazel, not Soong.
+	library.flagExporterInfo = &flagExporterInfo
+}
+
+func GlobHeadersForSnapshot(ctx android.ModuleContext, paths android.Paths) android.Paths {
 	ret := android.Paths{}
 
 	// Headers in the source tree should be globbed. On the contrast, generated headers
 	// can't be globbed, and they should be manually collected.
 	// So, we first filter out intermediate directories (which contains generated headers)
 	// from exported directories, and then glob headers under remaining directories.
-	for _, path := range append(android.CopyOfPaths(l.flagExporter.dirs), l.flagExporter.systemDirs...) {
+	for _, path := range paths {
 		dir := path.String()
 		// Skip if dir is for generated headers
 		if strings.HasPrefix(dir, android.PathForOutput(ctx).String()) {
@@ -616,7 +703,7 @@
 				glob, err := ctx.GlobWithDeps("external/eigen/"+subdir+"/**/*", nil)
 				if err != nil {
 					ctx.ModuleErrorf("glob failed: %#v", err)
-					return
+					return nil
 				}
 				for _, header := range glob {
 					if strings.HasSuffix(header, "/") {
@@ -634,7 +721,7 @@
 		glob, err := ctx.GlobWithDeps(dir+"/**/*", nil)
 		if err != nil {
 			ctx.ModuleErrorf("glob failed: %#v", err)
-			return
+			return nil
 		}
 		isLibcxx := strings.HasPrefix(dir, "external/libcxx/include")
 		for _, header := range glob {
@@ -647,7 +734,7 @@
 			} else {
 				// Filter out only the files with extensions that are headers.
 				found := false
-				for _, ext := range headerExts {
+				for _, ext := range HeaderExts {
 					if strings.HasSuffix(header, ext) {
 						found = true
 						break
@@ -660,15 +747,38 @@
 			ret = append(ret, android.PathForSource(ctx, header))
 		}
 	}
+	return ret
+}
 
-	// Collect generated headers
-	for _, header := range append(android.CopyOfPaths(l.flagExporter.headers), l.flagExporter.deps...) {
+func GlobGeneratedHeadersForSnapshot(ctx android.ModuleContext, paths android.Paths) android.Paths {
+	ret := android.Paths{}
+	for _, header := range paths {
 		// TODO(b/148123511): remove exportedDeps after cleaning up genrule
 		if strings.HasSuffix(header.Base(), "-phony") {
 			continue
 		}
 		ret = append(ret, header)
 	}
+	return ret
+}
+
+// collectHeadersForSnapshot collects all exported headers from library.
+// It globs header files in the source tree for exported include directories,
+// and tracks generated header files separately.
+//
+// This is to be called from GenerateAndroidBuildActions, and then collected
+// header files can be retrieved by snapshotHeaders().
+func (l *libraryDecorator) collectHeadersForSnapshot(ctx android.ModuleContext) {
+	ret := android.Paths{}
+
+	// Headers in the source tree should be globbed. On the contrast, generated headers
+	// can't be globbed, and they should be manually collected.
+	// So, we first filter out intermediate directories (which contains generated headers)
+	// from exported directories, and then glob headers under remaining directories.
+	ret = append(ret, GlobHeadersForSnapshot(ctx, append(android.CopyOfPaths(l.flagExporter.dirs), l.flagExporter.systemDirs...))...)
+
+	// Collect generated headers
+	ret = append(ret, GlobGeneratedHeadersForSnapshot(ctx, append(android.CopyOfPaths(l.flagExporter.headers), l.flagExporter.deps...))...)
 
 	l.collectedSnapshotHeaders = ret
 }
@@ -817,16 +927,23 @@
 		if library.stubsVersion() != "" {
 			vndkVer = library.stubsVersion()
 		}
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Llndk.Symbol_file), vndkVer, "--llndk")
+		nativeAbiResult := parseNativeAbiDefinition(ctx,
+			String(library.Properties.Llndk.Symbol_file),
+			android.ApiLevelOrPanic(ctx, vndkVer), "--llndk")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Llndk.Unversioned) {
-			library.versionScriptPath = android.OptionalPathForPath(versionScript)
+			library.versionScriptPath = android.OptionalPathForPath(
+				nativeAbiResult.versionScript)
 		}
 		return objs
 	}
 	if ctx.IsVendorPublicLibrary() {
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Vendor_public_library.Symbol_file), "current", "")
+		nativeAbiResult := parseNativeAbiDefinition(ctx,
+			String(library.Properties.Vendor_public_library.Symbol_file),
+			android.FutureApiLevel, "")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Vendor_public_library.Unversioned) {
-			library.versionScriptPath = android.OptionalPathForPath(versionScript)
+			library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.versionScript)
 		}
 		return objs
 	}
@@ -836,8 +953,12 @@
 			ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile)
 			return Objects{}
 		}
-		objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Stubs.Symbol_file), library.MutatedProperties.StubsVersion, "--apex")
-		library.versionScriptPath = android.OptionalPathForPath(versionScript)
+		nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
+			android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion),
+			"--apex")
+		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+		library.versionScriptPath = android.OptionalPathForPath(
+			nativeAbiResult.versionScript)
 		return objs
 	}
 
@@ -1054,8 +1175,8 @@
 		deps.ReexportStaticLibHeaders = append(deps.ReexportStaticLibHeaders, library.StaticProperties.Static.Export_static_lib_headers...)
 	} else if library.shared() {
 		if ctx.toolchain().Bionic() && !Bool(library.baseLinker.Properties.Nocrt) {
-			deps.CrtBegin = "crtbegin_so"
-			deps.CrtEnd = "crtend_so"
+			deps.CrtBegin = []string{"crtbegin_so"}
+			deps.CrtEnd = []string{"crtend_so"}
 		}
 		deps.WholeStaticLibs = append(deps.WholeStaticLibs, library.SharedProperties.Shared.Whole_static_libs...)
 		deps.StaticLibs = append(deps.StaticLibs, library.SharedProperties.Shared.Static_libs...)
@@ -1283,22 +1404,9 @@
 	linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
 	linkerDeps = append(linkerDeps, objs.tidyFiles...)
-
-	if Bool(library.Properties.Sort_bss_symbols_by_size) && !library.buildStubs() {
-		unsortedOutputFile := android.PathForModuleOut(ctx, "unsorted", fileName)
-		transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
-			deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-			linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, unsortedOutputFile, implicitOutputs)
-
-		symbolOrderingFile := android.PathForModuleOut(ctx, "unsorted", fileName+".symbol_order")
-		symbolOrderingFlag := library.baseLinker.sortBssSymbolsBySize(ctx, unsortedOutputFile, symbolOrderingFile, builderFlags)
-		builderFlags.localLdFlags += " " + symbolOrderingFlag
-		linkerDeps = append(linkerDeps, symbolOrderingFile)
-	}
-
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
 		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs)
+		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, nil)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -1309,19 +1417,17 @@
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
 	library.linkSAbiDumpFiles(ctx, objs, fileName, unstrippedOutputFile)
 
-	var staticAnalogue *StaticLibraryInfo
+	var transitiveStaticLibrariesForOrdering *android.DepSet
 	if static := ctx.GetDirectDepsWithTag(staticVariantTag); len(static) > 0 {
 		s := ctx.OtherModuleProvider(static[0], StaticLibraryInfoProvider).(StaticLibraryInfo)
-		staticAnalogue = &s
+		transitiveStaticLibrariesForOrdering = s.TransitiveStaticLibrariesForOrdering
 	}
 
 	ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-		TableOfContents:         android.OptionalPathForPath(tocFile),
-		SharedLibrary:           unstrippedOutputFile,
-		UnstrippedSharedLibrary: library.unstrippedOutputFile,
-		CoverageSharedLibrary:   library.coverageOutputFile,
-		StaticAnalogue:          staticAnalogue,
-		Target:                  ctx.Target(),
+		TableOfContents:                      android.OptionalPathForPath(tocFile),
+		SharedLibrary:                        unstrippedOutputFile,
+		TransitiveStaticLibrariesForOrdering: transitiveStaticLibrariesForOrdering,
+		Target:                               ctx.Target(),
 	})
 
 	stubs := ctx.GetDirectDepsWithTag(stubImplDepTag)
@@ -1426,11 +1532,10 @@
 	}
 }
 
-func processLLNDKHeaders(ctx ModuleContext, srcHeaderDir string, outDir android.ModuleGenPath) android.Path {
+func processLLNDKHeaders(ctx ModuleContext, srcHeaderDir string, outDir android.ModuleGenPath) (timestamp android.Path, installPaths android.WritablePaths) {
 	srcDir := android.PathForModuleSrc(ctx, srcHeaderDir)
 	srcFiles := ctx.GlobFiles(filepath.Join(srcDir.String(), "**/*.h"), nil)
 
-	var installPaths []android.WritablePath
 	for _, header := range srcFiles {
 		headerDir := filepath.Dir(header.String())
 		relHeaderDir, err := filepath.Rel(srcDir.String(), headerDir)
@@ -1443,7 +1548,7 @@
 		installPaths = append(installPaths, outDir.Join(ctx, relHeaderDir, header.Base()))
 	}
 
-	return processHeadersWithVersioner(ctx, srcDir, outDir, srcFiles, installPaths)
+	return processHeadersWithVersioner(ctx, srcDir, outDir, srcFiles, installPaths), installPaths
 }
 
 // link registers actions to link this library, and sets various fields
@@ -1459,7 +1564,9 @@
 
 			var timestampFiles android.Paths
 			for _, dir := range library.Properties.Llndk.Export_preprocessed_headers {
-				timestampFiles = append(timestampFiles, processLLNDKHeaders(ctx, dir, genHeaderOutDir))
+				timestampFile, installPaths := processLLNDKHeaders(ctx, dir, genHeaderOutDir)
+				timestampFiles = append(timestampFiles, timestampFile)
+				library.addExportedGeneratedHeaders(installPaths.Paths()...)
 			}
 
 			if Bool(library.Properties.Llndk.Export_headers_as_system) {
@@ -1793,6 +1900,11 @@
 		return nil
 	}
 
+	if library.hasLLNDKStubs() && ctx.Module().(*Module).UseVndk() {
+		// LLNDK libraries only need a single stubs variant.
+		return []string{android.FutureApiLevel.String()}
+	}
+
 	// Future API level is implicitly added if there isn't
 	vers := library.Properties.Stubs.Versions
 	if inList(android.FutureApiLevel.String(), vers) {
@@ -2134,8 +2246,7 @@
 	return nil
 }
 
-// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions,
-// and propagates the value from implementation libraries to llndk libraries with the same name.
+// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
 func versionSelectorMutator(mctx android.BottomUpMutatorContext) {
 	if library := moduleLibraryInterface(mctx.Module()); library != nil && CanBeVersionVariant(mctx.Module().(*Module)) {
 		if library.buildShared() {
@@ -2149,15 +2260,6 @@
 				// depend on the implementation library and haven't been mutated yet.
 				library.setAllStubsVersions(versions)
 			}
-
-			if mctx.Module().(*Module).UseVndk() && library.hasLLNDKStubs() {
-				// Propagate the version to the llndk stubs module.
-				mctx.VisitDirectDepsWithTag(llndkStubDepTag, func(stubs android.Module) {
-					if stubsLib := moduleLibraryInterface(stubs); stubsLib != nil {
-						stubsLib.setAllStubsVersions(library.allStubsVersions())
-					}
-				})
-			}
 		}
 	}
 }
@@ -2216,14 +2318,22 @@
 }
 
 type bazelCcLibraryStaticAttributes struct {
-	Copts              bazel.StringListAttribute
-	Srcs               bazel.LabelListAttribute
-	Deps               bazel.LabelListAttribute
-	Whole_archive_deps bazel.LabelListAttribute
-	Linkopts           bazel.StringListAttribute
-	Linkstatic         bool
-	Includes           bazel.StringListAttribute
-	Hdrs               bazel.LabelListAttribute
+	Copts               bazel.StringListAttribute
+	Srcs                bazel.LabelListAttribute
+	Implementation_deps bazel.LabelListAttribute
+	Deps                bazel.LabelListAttribute
+	Whole_archive_deps  bazel.LabelListAttribute
+	Linkopts            bazel.StringListAttribute
+	Linkstatic          bool
+	Use_libcrt          bazel.BoolAttribute
+	Includes            bazel.StringListAttribute
+	Hdrs                bazel.LabelListAttribute
+
+	Cppflags   bazel.StringListAttribute
+	Srcs_c     bazel.LabelListAttribute
+	Conlyflags bazel.StringListAttribute
+	Srcs_as    bazel.LabelListAttribute
+	Asflags    bazel.StringListAttribute
 }
 
 type bazelCcLibraryStatic struct {
@@ -2244,14 +2354,22 @@
 	exportedIncludes := bp2BuildParseExportedIncludes(ctx, module)
 
 	attrs := &bazelCcLibraryStaticAttributes{
-		Copts:              compilerAttrs.copts,
-		Srcs:               compilerAttrs.srcs,
-		Deps:               linkerAttrs.deps,
-		Whole_archive_deps: linkerAttrs.wholeArchiveDeps,
+		Copts:               compilerAttrs.copts,
+		Srcs:                compilerAttrs.srcs,
+		Implementation_deps: linkerAttrs.deps,
+		Deps:                linkerAttrs.exportedDeps,
+		Whole_archive_deps:  linkerAttrs.wholeArchiveDeps,
 
 		Linkopts:   linkerAttrs.linkopts,
 		Linkstatic: true,
+		Use_libcrt: linkerAttrs.useLibcrt,
 		Includes:   exportedIncludes,
+
+		Cppflags:   compilerAttrs.cppFlags,
+		Srcs_c:     compilerAttrs.cSrcs,
+		Conlyflags: compilerAttrs.conlyFlags,
+		Srcs_as:    compilerAttrs.asSrcs,
+		Asflags:    compilerAttrs.asFlags,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 0aba8de..d6b4529 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -73,13 +73,7 @@
 	// HeaderLibraryInfo is an empty struct to indicate to dependencies that this is a header library
 	ctx.SetProvider(HeaderLibraryInfoProvider, HeaderLibraryInfo{})
 
-	flagExporterInfo := flagExporterInfoFromCcInfo(ctx, ccInfo)
-	// Store flag info to be passed along to androimk
-	// TODO(b/184387147): Androidmk should be done in Bazel, not Soong.
-	h.library.flagExporterInfo = &flagExporterInfo
-	// flag exporters consolidates properties like includes, flags, dependencies that should be
-	// exported from this module to other modules
-	ctx.SetProvider(FlagExporterInfoProvider, flagExporterInfo)
+	h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
 
 	// Dependencies on this library will expect collectedSnapshotHeaders to be set, otherwise
 	// validation will fail. For now, set this to an empty list.
@@ -109,10 +103,11 @@
 }
 
 type bazelCcLibraryHeadersAttributes struct {
-	Copts    bazel.StringListAttribute
-	Hdrs     bazel.LabelListAttribute
-	Includes bazel.StringListAttribute
-	Deps     bazel.LabelListAttribute
+	Copts               bazel.StringListAttribute
+	Hdrs                bazel.LabelListAttribute
+	Includes            bazel.StringListAttribute
+	Deps                bazel.LabelListAttribute
+	Implementation_deps bazel.LabelListAttribute
 }
 
 type bazelCcLibraryHeaders struct {
@@ -147,9 +142,10 @@
 	linkerAttrs := bp2BuildParseLinkerProps(ctx, module)
 
 	attrs := &bazelCcLibraryHeadersAttributes{
-		Copts:    compilerAttrs.copts,
-		Includes: exportedIncludes,
-		Deps:     linkerAttrs.deps,
+		Copts:               compilerAttrs.copts,
+		Includes:            exportedIncludes,
+		Implementation_deps: linkerAttrs.deps,
+		Deps:                linkerAttrs.exportedDeps,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_test.go b/cc/library_test.go
index 7975275..ba372a8 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -19,6 +19,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/bazel/cquery"
 )
 
 func TestLibraryReuse(t *testing.T) {
@@ -240,3 +241,48 @@
 
 	testCcError(t, `"libfoo" .*: versions: "X" could not be parsed as an integer and is not a recognized codename`, bp)
 }
+
+func TestCcLibraryWithBazel(t *testing.T) {
+	bp := `
+cc_library {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				RootStaticArchives:   []string{"foo.a"},
+				RootDynamicLibraries: []string{"foo.so"},
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+
+	staticFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	outputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+
+	expectedOutputFiles := []string{"outputbase/execroot/__main__/foo.a"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	outputFiles, err = sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles = []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, sharedFoo)[0]
+	expectedFlags := []string{"-Ioutputbase/execroot/__main__/include", "-isystem outputbase/execroot/__main__/system_include"}
+	gotFlags := entries.EntryMap["LOCAL_EXPORT_CFLAGS"]
+	android.AssertDeepEquals(t, "androidmk exported cflags", expectedFlags, gotFlags)
+}
diff --git a/cc/linkable.go b/cc/linkable.go
index 40a9d8b..0a5d16c 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -48,6 +48,20 @@
 	// SanitizerSupported returns true if a sanitizer type is supported by this modules compiler.
 	SanitizerSupported(t SanitizerType) bool
 
+	// MinimalRuntimeDep returns true if this module needs to link the minimal UBSan runtime,
+	// either because it requires it or because a dependent module which requires it to be linked in this module.
+	MinimalRuntimeDep() bool
+
+	// UbsanRuntimeDep returns true if this module needs to link the full UBSan runtime,
+	// either because it requires it or because a dependent module which requires it to be linked in this module.
+	UbsanRuntimeDep() bool
+
+	// UbsanRuntimeNeeded returns true if the full UBSan runtime is required by this module.
+	UbsanRuntimeNeeded() bool
+
+	// MinimalRuntimeNeeded returns true if the minimal UBSan runtime is required by this module
+	MinimalRuntimeNeeded() bool
+
 	// SanitizableDepTagChecker returns a SantizableDependencyTagChecker function type.
 	SanitizableDepTagChecker() SantizableDependencyTagChecker
 }
@@ -60,14 +74,42 @@
 // implementation should handle tags from both.
 type SantizableDependencyTagChecker func(tag blueprint.DependencyTag) bool
 
+// Snapshottable defines those functions necessary for handling module snapshots.
+type Snapshottable interface {
+	// SnapshotHeaders returns a list of header paths provided by this module.
+	SnapshotHeaders() android.Paths
+
+	// ExcludeFromVendorSnapshot returns true if this module should be otherwise excluded from the vendor snapshot.
+	ExcludeFromVendorSnapshot() bool
+
+	// ExcludeFromRecoverySnapshot returns true if this module should be otherwise excluded from the recovery snapshot.
+	ExcludeFromRecoverySnapshot() bool
+
+	// SnapshotLibrary returns true if this module is a snapshot library.
+	IsSnapshotLibrary() bool
+
+	// SnapshotRuntimeLibs returns a list of libraries needed by this module at runtime but which aren't build dependencies.
+	SnapshotRuntimeLibs() []string
+
+	// SnapshotSharedLibs returns the list of shared library dependencies for this module.
+	SnapshotSharedLibs() []string
+
+	// IsSnapshotPrebuilt returns true if this module is a snapshot prebuilt.
+	IsSnapshotPrebuilt() bool
+}
+
 // LinkableInterface is an interface for a type of module that is linkable in a C++ library.
 type LinkableInterface interface {
 	android.Module
+	Snapshottable
 
 	Module() android.Module
 	CcLibrary() bool
 	CcLibraryInterface() bool
 
+	// BaseModuleName returns the android.ModuleBase.BaseModuleName() value for this module.
+	BaseModuleName() string
+
 	OutputFile() android.OptionalPath
 	CoverageFiles() android.Paths
 
@@ -79,9 +121,6 @@
 	BuildSharedVariant() bool
 	SetStatic()
 	SetShared()
-	Static() bool
-	Shared() bool
-	Header() bool
 	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
@@ -106,13 +145,29 @@
 	// IsLlndkPublic returns true only for LLNDK (public) libs.
 	IsLlndkPublic() bool
 
+	// HasLlndkStubs returns true if this library has a variant that will build LLNDK stubs.
+	HasLlndkStubs() bool
+
 	// NeedsLlndkVariants returns true if this module has LLNDK stubs or provides LLNDK headers.
 	NeedsLlndkVariants() bool
 
 	// NeedsVendorPublicLibraryVariants returns true if this module has vendor public library stubs.
 	NeedsVendorPublicLibraryVariants() bool
 
+	//StubsVersion returns the stubs version for this module.
+	StubsVersion() string
+
+	// UseVndk returns true if the module is using VNDK libraries instead of the libraries in /system/lib or /system/lib64.
+	// "product" and "vendor" variant modules return true for this function.
+	// When BOARD_VNDK_VERSION is set, vendor variants of "vendor_available: true", "vendor: true",
+	// "soc_specific: true" and more vendor installed modules are included here.
+	// When PRODUCT_PRODUCT_VNDK_VERSION is set, product variants of "vendor_available: true" or
+	// "product_specific: true" modules are included here.
 	UseVndk() bool
+
+	// IsVndkSp returns true if this is a VNDK-SP module.
+	IsVndkSp() bool
+
 	MustUseVendorVariant() bool
 	IsVndk() bool
 	IsVndkExt() bool
@@ -140,6 +195,51 @@
 	// KernelHeadersDecorator returns true if this is a kernel headers decorator module.
 	// This is specific to cc and should always return false for all other packages.
 	KernelHeadersDecorator() bool
+
+	// HiddenFromMake returns true if this module is hidden from Make.
+	HiddenFromMake() bool
+
+	// RelativeInstallPath returns the relative install path for this module.
+	RelativeInstallPath() string
+
+	// Binary returns true if this is a binary module.
+	Binary() bool
+
+	// Object returns true if this is an object module.
+	Object() bool
+
+	// Rlib returns true if this is an rlib module.
+	Rlib() bool
+
+	// Dylib returns true if this is an dylib module.
+	Dylib() bool
+
+	// Static returns true if this is a static library module.
+	Static() bool
+
+	// Shared returns true if this is a shared library module.
+	Shared() bool
+
+	// Header returns true if this is a library headers module.
+	Header() bool
+
+	// EverInstallable returns true if the module is ever installable
+	EverInstallable() bool
+
+	// PreventInstall returns true if this module is prevented from installation.
+	PreventInstall() bool
+
+	// InstallInData returns true if this module is installed in data.
+	InstallInData() bool
+
+	// Installable returns a bool pointer to the module installable property.
+	Installable() *bool
+
+	// Symlinks returns a list of symlinks that should be created for this module.
+	Symlinks() []string
+
+	// VndkVersion returns the VNDK version string for this module.
+	VndkVersion() string
 }
 
 var (
@@ -205,14 +305,13 @@
 
 // SharedLibraryInfo is a provider to propagate information about a shared C++ library.
 type SharedLibraryInfo struct {
-	SharedLibrary           android.Path
-	UnstrippedSharedLibrary android.Path
-	Target                  android.Target
+	SharedLibrary android.Path
+	Target        android.Target
 
-	TableOfContents       android.OptionalPath
-	CoverageSharedLibrary android.OptionalPath
+	TableOfContents android.OptionalPath
 
-	StaticAnalogue *StaticLibraryInfo
+	// should be obtained from static analogue
+	TransitiveStaticLibrariesForOrdering *android.DepSet
 }
 
 var SharedLibraryInfoProvider = blueprint.NewProvider(SharedLibraryInfo{})
diff --git a/cc/linker.go b/cc/linker.go
index a9930ad..d9ee0cf 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -18,7 +18,6 @@
 	"android/soong/android"
 	"android/soong/cc/config"
 	"fmt"
-	"strconv"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -123,7 +122,7 @@
 
 			// version script for vendor or product variant
 			Version_script *string `android:"arch_variant"`
-		}
+		} `android:"arch_variant"`
 		Recovery struct {
 			// list of shared libs that only should be used to build the recovery
 			// variant of the C/C++ module.
@@ -183,7 +182,7 @@
 			// variant of the C/C++ module.
 			Exclude_static_libs []string
 		}
-	}
+	} `android:"arch_variant"`
 
 	// make android::build:GetBuildNumber() available containing the build ID.
 	Use_version_lib *bool `android:"arch_variant"`
@@ -201,6 +200,18 @@
 	Exclude_shared_libs []string `android:"arch_variant"`
 }
 
+func invertBoolPtr(value *bool) *bool {
+	if value == nil {
+		return nil
+	}
+	ret := !(*value)
+	return &ret
+}
+
+func (blp *BaseLinkerProperties) libCrt() *bool {
+	return invertBoolPtr(blp.No_libcrt)
+}
+
 func NewBaseLinker(sanitize *sanitize) *baseLinker {
 	return &baseLinker{sanitize: sanitize}
 }
@@ -390,17 +401,17 @@
 }
 
 // Check whether the SDK version is not older than the specific one
-func CheckSdkVersionAtLeast(ctx ModuleContext, SdkVersion int) bool {
-	if ctx.sdkVersion() == "current" {
+func CheckSdkVersionAtLeast(ctx ModuleContext, SdkVersion android.ApiLevel) bool {
+	if ctx.minSdkVersion() == "current" {
 		return true
 	}
-	parsedSdkVersion, err := strconv.Atoi(ctx.sdkVersion())
+	parsedSdkVersion, err := nativeApiLevelFromUser(ctx, ctx.minSdkVersion())
 	if err != nil {
-		ctx.PropertyErrorf("sdk_version",
-			"Invalid sdk_version value (must be int or current): %q",
-			ctx.sdkVersion())
+		ctx.PropertyErrorf("min_sdk_version",
+			"Invalid min_sdk_version value (must be int or current): %q",
+			ctx.minSdkVersion())
 	}
-	if parsedSdkVersion < SdkVersion {
+	if parsedSdkVersion.LessThan(SdkVersion) {
 		return false
 	}
 	return true
@@ -425,13 +436,13 @@
 			// ANDROID_RELR relocations were supported at API level >= 28.
 			// Relocation packer was supported at API level >= 23.
 			// Do the best we can...
-			if !ctx.useSdk() || CheckSdkVersionAtLeast(ctx, 30) {
+			if (!ctx.useSdk() && ctx.minSdkVersion() == "") || CheckSdkVersionAtLeast(ctx, android.FirstShtRelrVersion) {
 				flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=android+relr")
-			} else if CheckSdkVersionAtLeast(ctx, 28) {
+			} else if CheckSdkVersionAtLeast(ctx, android.FirstAndroidRelrVersion) {
 				flags.Global.LdFlags = append(flags.Global.LdFlags,
 					"-Wl,--pack-dyn-relocs=android+relr",
 					"-Wl,--use-android-relr-tags")
-			} else if CheckSdkVersionAtLeast(ctx, 23) {
+			} else if CheckSdkVersionAtLeast(ctx, android.FirstPackedRelocationsVersion) {
 				flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=android")
 			}
 		}
@@ -595,28 +606,3 @@
 		},
 	})
 }
-
-// Rule to generate .bss symbol ordering file.
-
-var (
-	_                   = pctx.SourcePathVariable("genSortedBssSymbolsPath", "build/soong/scripts/gen_sorted_bss_symbols.sh")
-	genSortedBssSymbols = pctx.AndroidStaticRule("gen_sorted_bss_symbols",
-		blueprint.RuleParams{
-			Command:     "CLANG_BIN=${clangBin} $genSortedBssSymbolsPath ${in} ${out}",
-			CommandDeps: []string{"$genSortedBssSymbolsPath", "${clangBin}/llvm-nm"},
-		},
-		"clangBin")
-)
-
-func (linker *baseLinker) sortBssSymbolsBySize(ctx ModuleContext, in android.Path, symbolOrderingFile android.ModuleOutPath, flags builderFlags) string {
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        genSortedBssSymbols,
-		Description: "generate bss symbol order " + symbolOrderingFile.Base(),
-		Output:      symbolOrderingFile,
-		Input:       in,
-		Args: map[string]string{
-			"clangBin": "${config.ClangBin}",
-		},
-	})
-	return "-Wl,--symbol-ordering-file," + symbolOrderingFile.String()
-}
diff --git a/cc/makevars.go b/cc/makevars.go
index da5f1fd..2b326ef 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -288,9 +288,7 @@
 		ctx.Strict(makePrefix+"OBJCOPY", "${config.ClangBin}/llvm-objcopy")
 		ctx.Strict(makePrefix+"LD", "${config.ClangBin}/lld")
 		ctx.Strict(makePrefix+"NDK_TRIPLE", config.NDKTriple(toolchain))
-		// TODO: work out whether to make this "${config.ClangBin}/llvm-", which
-		// should mostly work, or remove it.
-		ctx.Strict(makePrefix+"TOOLS_PREFIX", gccCmd(toolchain, ""))
+		ctx.Strict(makePrefix+"TOOLS_PREFIX", "${config.ClangBin}/llvm-")
 		// TODO: GCC version is obsolete now that GCC has been removed.
 		ctx.Strict(makePrefix+"GCC_VERSION", toolchain.GccVersion())
 	}
diff --git a/cc/ndk_abi.go b/cc/ndk_abi.go
new file mode 100644
index 0000000..927fa2e
--- /dev/null
+++ b/cc/ndk_abi.go
@@ -0,0 +1,102 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"android/soong/android"
+)
+
+func init() {
+	android.RegisterSingletonType("ndk_abi_dump", NdkAbiDumpSingleton)
+	android.RegisterSingletonType("ndk_abi_diff", NdkAbiDiffSingleton)
+}
+
+func getNdkAbiDumpInstallBase(ctx android.PathContext) android.OutputPath {
+	return android.PathForOutput(ctx).Join(ctx, "abi-dumps/ndk")
+}
+
+func getNdkAbiDumpTimestampFile(ctx android.PathContext) android.OutputPath {
+	return android.PathForOutput(ctx, "ndk_abi_dump.timestamp")
+}
+
+func NdkAbiDumpSingleton() android.Singleton {
+	return &ndkAbiDumpSingleton{}
+}
+
+type ndkAbiDumpSingleton struct{}
+
+func (n *ndkAbiDumpSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var depPaths android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		if !module.Enabled() {
+			return
+		}
+
+		if m, ok := module.(*Module); ok {
+			if installer, ok := m.installer.(*stubDecorator); ok {
+				if canDumpAbi() {
+					depPaths = append(depPaths, installer.abiDumpPath)
+				}
+			}
+		}
+	})
+
+	// `m dump-ndk-abi` will dump the NDK ABI.
+	// `development/tools/ndk/update_ndk_abi.sh` will dump the NDK ABI and
+	// update the golden copies in prebuilts/abi-dumps/ndk.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    getNdkAbiDumpTimestampFile(ctx),
+		Implicits: depPaths,
+	})
+
+	ctx.Phony("dump-ndk-abi", getNdkAbiDumpTimestampFile(ctx))
+}
+
+func getNdkAbiDiffTimestampFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForOutput(ctx, "ndk_abi_diff.timestamp")
+}
+
+func NdkAbiDiffSingleton() android.Singleton {
+	return &ndkAbiDiffSingleton{}
+}
+
+type ndkAbiDiffSingleton struct{}
+
+func (n *ndkAbiDiffSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	var depPaths android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		if m, ok := module.(android.Module); ok && !m.Enabled() {
+			return
+		}
+
+		if m, ok := module.(*Module); ok {
+			if installer, ok := m.installer.(*stubDecorator); ok {
+				depPaths = append(depPaths, installer.abiDiffPaths...)
+			}
+		}
+	})
+
+	depPaths = append(depPaths, getNdkAbiDumpTimestampFile(ctx))
+
+	// `m diff-ndk-abi` will diff the NDK ABI.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    getNdkAbiDiffTimestampFile(ctx),
+		Implicits: depPaths,
+	})
+
+	ctx.Phony("diff-ndk-abi", getNdkAbiDiffTimestampFile(ctx))
+}
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 95d8477..a458380 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"path/filepath"
+	"runtime"
 	"strings"
 	"sync"
 
@@ -28,6 +30,9 @@
 func init() {
 	pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
 	pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser")
+	pctx.HostBinToolVariable("abidiff", "abidiff")
+	pctx.HostBinToolVariable("abitidy", "abitidy")
+	pctx.HostBinToolVariable("abidw", "abidw")
 }
 
 var (
@@ -44,11 +49,31 @@
 			CommandDeps: []string{"$ndk_api_coverage_parser"},
 		}, "apiMap")
 
+	abidw = pctx.AndroidStaticRule("abidw",
+		blueprint.RuleParams{
+			Command: "$abidw --type-id-style hash --no-corpus-path " +
+				"--no-show-locs --no-comp-dir-path -w $symbolList $in | " +
+				"$abitidy --all -o $out",
+			CommandDeps: []string{"$abitidy", "$abidw"},
+		}, "symbolList")
+
+	abidiff = pctx.AndroidStaticRule("abidiff",
+		blueprint.RuleParams{
+			// Need to create *some* output for ninja. We don't want to use tee
+			// because we don't want to spam the build output with "nothing
+			// changed" messages, so redirect output message to $out, and if
+			// changes were detected print the output and fail.
+			Command:     "$abidiff $args $in > $out || (cat $out && false)",
+			CommandDeps: []string{"$abidiff"},
+		}, "args")
+
 	ndkLibrarySuffix = ".ndk"
 
 	ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
 	// protects ndkKnownLibs writes during parallel BeginMutator.
 	ndkKnownLibsLock sync.Mutex
+
+	stubImplementation = dependencyTag{name: "stubImplementation"}
 )
 
 // The First_version and Unversioned_until properties of this struct should not
@@ -89,6 +114,8 @@
 	versionScriptPath     android.ModuleGenPath
 	parsedCoverageXmlPath android.ModuleOutPath
 	installPath           android.Path
+	abiDumpPath           android.OutputPath
+	abiDiffPaths          android.Paths
 
 	apiLevel         android.ApiLevel
 	firstVersion     android.ApiLevel
@@ -123,6 +150,16 @@
 	if !ctx.Module().Enabled() {
 		return nil
 	}
+	if ctx.Os() != android.Android {
+		// These modules are always android.DeviceEnabled only, but
+		// those include Fuchsia devices, which we don't support.
+		ctx.Module().Disable()
+		return nil
+	}
+	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+		ctx.Module().Disable()
+		return nil
+	}
 	firstVersion, err := nativeApiLevelFromUser(ctx,
 		String(this.properties.First_version))
 	if err != nil {
@@ -204,30 +241,45 @@
 	return addStubLibraryCompilerFlags(flags)
 }
 
-func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) {
-	arch := ctx.Arch().ArchType.String()
+type ndkApiOutputs struct {
+	stubSrc       android.ModuleGenPath
+	versionScript android.ModuleGenPath
+	symbolList    android.ModuleGenPath
+}
+
+func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string,
+	apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs {
 
 	stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
 	versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
 	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
+	symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt")
 	apiLevelsJson := android.GetApiLevelsJson(ctx)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        genStubSrc,
 		Description: "generate stubs " + symbolFilePath.Rel(),
-		Outputs:     []android.WritablePath{stubSrcPath, versionScriptPath},
-		Input:       symbolFilePath,
-		Implicits:   []android.Path{apiLevelsJson},
+		Outputs: []android.WritablePath{stubSrcPath, versionScriptPath,
+			symbolListPath},
+		Input:     symbolFilePath,
+		Implicits: []android.Path{apiLevelsJson},
 		Args: map[string]string{
-			"arch":     arch,
-			"apiLevel": apiLevel,
+			"arch":     ctx.Arch().ArchType.String(),
+			"apiLevel": apiLevel.String(),
 			"apiMap":   apiLevelsJson.String(),
 			"flags":    genstubFlags,
 		},
 	})
 
-	subdir := ""
-	srcs := []android.Path{stubSrcPath}
-	return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath
+	return ndkApiOutputs{
+		stubSrc:       stubSrcPath,
+		versionScript: versionScriptPath,
+		symbolList:    symbolListPath,
+	}
+}
+
+func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
+	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
+		android.Paths{src}, nil, nil)
 }
 
 func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath {
@@ -248,6 +300,140 @@
 	return parsedApiCoveragePath
 }
 
+func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
+	dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix),
+		stubImplementation)
+	if dep == nil {
+		ctx.ModuleErrorf("Could not find implementation for stub")
+		return nil
+	}
+	impl, ok := dep.(*Module)
+	if !ok {
+		ctx.ModuleErrorf("Implementation for stub is not correct module type")
+	}
+	output := impl.UnstrippedOutputFile()
+	if output == nil {
+		ctx.ModuleErrorf("implementation module (%s) has no output", impl)
+		return nil
+	}
+
+	return output
+}
+
+func (this *stubDecorator) libraryName(ctx ModuleContext) string {
+	return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix)
+}
+
+func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext,
+	apiLevel android.ApiLevel) android.OptionalPath {
+
+	subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(),
+		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml")
+	return android.ExistentPathForSource(ctx, subpath)
+}
+
+// Feature flag.
+func canDumpAbi() bool {
+	return runtime.GOOS != "darwin"
+}
+
+// Feature flag to disable diffing against prebuilts.
+func canDiffAbi() bool {
+	return false
+}
+
+func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
+	implementationLibrary := this.findImplementationLibrary(ctx)
+	this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx,
+		this.apiLevel.String(), ctx.Arch().ArchType.String(),
+		this.libraryName(ctx), "abi.xml")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        abidw,
+		Description: fmt.Sprintf("abidw %s", implementationLibrary),
+		Output:      this.abiDumpPath,
+		Input:       implementationLibrary,
+		Implicit:    symbolList,
+		Args: map[string]string{
+			"symbolList": symbolList.String(),
+		},
+	})
+}
+
+func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel {
+	apiLevels := append(ctx.Config().AllSupportedApiLevels(),
+		android.FutureApiLevel)
+	for _, api := range apiLevels {
+		if api.GreaterThan(apiLevel) {
+			return &api
+		}
+	}
+	return nil
+}
+
+func (this *stubDecorator) diffAbi(ctx ModuleContext) {
+	missingPrebuiltError := fmt.Sprintf(
+		"Did not find prebuilt ABI dump for %q. Generate with "+
+			"//development/tools/ndk/update_ndk_abi.sh.", this.libraryName(ctx))
+
+	// Catch any ABI changes compared to the checked-in definition of this API
+	// level.
+	abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp")
+	prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel)
+	if !prebuiltAbiDump.Valid() {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.ErrorRule,
+			Output: abiDiffPath,
+			Args: map[string]string{
+				"error": missingPrebuiltError,
+			},
+		})
+	} else {
+		ctx.Build(pctx, android.BuildParams{
+			Rule: abidiff,
+			Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump,
+				this.abiDumpPath),
+			Output: abiDiffPath,
+			Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath},
+		})
+	}
+	this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath)
+
+	// Also ensure that the ABI of the next API level (if there is one) matches
+	// this API level. *New* ABI is allowed, but any changes to APIs that exist
+	// in this API level are disallowed.
+	if !this.apiLevel.IsCurrent() {
+		nextApiLevel := findNextApiLevel(ctx, this.apiLevel)
+		if nextApiLevel == nil {
+			panic(fmt.Errorf("could not determine which API level follows "+
+				"non-current API level %s", this.apiLevel))
+		}
+		nextAbiDiffPath := android.PathForModuleOut(ctx,
+			"abidiff_next.timestamp")
+		nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel)
+		if !nextAbiDump.Valid() {
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.ErrorRule,
+				Output: nextAbiDiffPath,
+				Args: map[string]string{
+					"error": missingPrebuiltError,
+				},
+			})
+		} else {
+			ctx.Build(pctx, android.BuildParams{
+				Rule: abidiff,
+				Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath,
+					nextAbiDump),
+				Output: nextAbiDiffPath,
+				Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()},
+				Args: map[string]string{
+					"args": "--no-added-syms",
+				},
+			})
+		}
+		this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
+	}
+}
+
 func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
 	if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") {
 		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
@@ -264,9 +450,15 @@
 	}
 
 	symbolFile := String(c.properties.Symbol_file)
-	objs, versionScript := compileStubLibrary(ctx, flags, symbolFile,
-		c.apiLevel.String(), "")
-	c.versionScriptPath = versionScript
+	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
+	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+	c.versionScriptPath = nativeAbiResult.versionScript
+	if canDumpAbi() {
+		c.dumpAbi(ctx, nativeAbiResult.symbolList)
+		if canDiffAbi() {
+			c.diffAbi(ctx)
+		}
+	}
 	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
 		c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile)
 	}
diff --git a/cc/ndk_prebuilt.go b/cc/ndk_prebuilt.go
index b91c737..51ec6b8 100644
--- a/cc/ndk_prebuilt.go
+++ b/cc/ndk_prebuilt.go
@@ -186,9 +186,8 @@
 		})
 	} else {
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           lib,
-			UnstrippedSharedLibrary: lib,
-			Target:                  ctx.Target(),
+			SharedLibrary: lib,
+			Target:        ctx.Target(),
 		})
 	}
 
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index d8c500e..2726b1a 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -146,16 +146,20 @@
 
 	baseDepPaths := append(installPaths, combinedLicense)
 
-	// There's a dummy "ndk" rule defined in ndk/Android.mk that depends on
-	// this. `m ndk` will build the sysroots.
 	ctx.Build(pctx, android.BuildParams{
-		Rule:      android.Touch,
-		Output:    getNdkBaseTimestampFile(ctx),
-		Implicits: baseDepPaths,
+		Rule:       android.Touch,
+		Output:     getNdkBaseTimestampFile(ctx),
+		Implicits:  baseDepPaths,
+		Validation: getNdkAbiDiffTimestampFile(ctx),
 	})
 
 	fullDepPaths := append(staticLibInstallPaths, getNdkBaseTimestampFile(ctx))
 
+	// There's a phony "ndk" rule defined in core/main.mk that depends on this.
+	// `m ndk` will build the sysroots for the architectures in the current
+	// lunch target. `build/soong/scripts/build-ndk-prebuilts.sh` will build the
+	// sysroots for all the NDK architectures and package them so they can be
+	// imported into the NDK's build.
 	ctx.Build(pctx, android.BuildParams{
 		Rule:      android.Touch,
 		Output:    getNdkFullTimestampFile(ctx),
diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py
index 86bf6ff..5e6b8f5 100755
--- a/cc/ndkstubgen/__init__.py
+++ b/cc/ndkstubgen/__init__.py
@@ -18,7 +18,7 @@
 import argparse
 import json
 import logging
-import os
+from pathlib import Path
 import sys
 from typing import Iterable, TextIO
 
@@ -28,10 +28,12 @@
 
 class Generator:
     """Output generator that writes stub source files and version scripts."""
-    def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch,
-                 api: int, llndk: bool, apex: bool) -> None:
+    def __init__(self, src_file: TextIO, version_script: TextIO,
+                 symbol_list: TextIO, arch: Arch, api: int, llndk: bool,
+                 apex: bool) -> None:
         self.src_file = src_file
         self.version_script = version_script
+        self.symbol_list = symbol_list
         self.arch = arch
         self.api = api
         self.llndk = llndk
@@ -39,6 +41,7 @@
 
     def write(self, versions: Iterable[Version]) -> None:
         """Writes all symbol data to the output files."""
+        self.symbol_list.write('[abi_symbol_list]\n')
         for version in versions:
             self.write_version(version)
 
@@ -76,11 +79,11 @@
                     weak = '__attribute__((weak)) '
 
                 if 'var' in symbol.tags:
-                    self.src_file.write('{}int {} = 0;\n'.format(
-                        weak, symbol.name))
+                    self.src_file.write(f'{weak}int {symbol.name} = 0;\n')
                 else:
-                    self.src_file.write('{}void {}() {{}}\n'.format(
-                        weak, symbol.name))
+                    self.src_file.write(f'{weak}void {symbol.name}() {{}}\n')
+
+                self.symbol_list.write(f'{symbol.name}\n')
 
             if not version_empty and section_versioned:
                 base = '' if version.base is None else ' ' + version.base
@@ -91,6 +94,10 @@
     """Parses and returns command line arguments."""
     parser = argparse.ArgumentParser()
 
+    def resolved_path(raw: str) -> Path:
+        """Returns a resolved Path for the given string."""
+        return Path(raw).resolve()
+
     parser.add_argument('-v', '--verbose', action='count', default=0)
 
     parser.add_argument(
@@ -101,28 +108,32 @@
     parser.add_argument(
         '--llndk', action='store_true', help='Use the LLNDK variant.')
     parser.add_argument(
-        '--apex', action='store_true', help='Use the APEX variant.')
+        '--apex',
+        action='store_true',
+        help='Use the APEX variant. Note: equivalent to --system-api.')
+    parser.add_argument(
+        '--system-api',
+        action='store_true',
+        dest='apex',
+        help='Use the SystemAPI variant. Note: equivalent to --apex.')
 
-    # https://github.com/python/mypy/issues/1317
-    # mypy has issues with using os.path.realpath as an argument here.
-    parser.add_argument(
-        '--api-map',
-        type=os.path.realpath,  # type: ignore
-        required=True,
-        help='Path to the API level map JSON file.')
+    parser.add_argument('--api-map',
+                        type=resolved_path,
+                        required=True,
+                        help='Path to the API level map JSON file.')
 
-    parser.add_argument(
-        'symbol_file',
-        type=os.path.realpath,  # type: ignore
-        help='Path to symbol file.')
-    parser.add_argument(
-        'stub_src',
-        type=os.path.realpath,  # type: ignore
-        help='Path to output stub source file.')
-    parser.add_argument(
-        'version_script',
-        type=os.path.realpath,  # type: ignore
-        help='Path to output version script.')
+    parser.add_argument('symbol_file',
+                        type=resolved_path,
+                        help='Path to symbol file.')
+    parser.add_argument('stub_src',
+                        type=resolved_path,
+                        help='Path to output stub source file.')
+    parser.add_argument('version_script',
+                        type=resolved_path,
+                        help='Path to output version script.')
+    parser.add_argument('symbol_list',
+                        type=resolved_path,
+                        help='Path to output abigail symbol list.')
 
     return parser.parse_args()
 
@@ -131,7 +142,7 @@
     """Program entry point."""
     args = parse_args()
 
-    with open(args.api_map) as map_file:
+    with args.api_map.open() as map_file:
         api_map = json.load(map_file)
     api = symbolfile.decode_api_level(args.api, api_map)
 
@@ -141,19 +152,20 @@
         verbosity = 2
     logging.basicConfig(level=verbose_map[verbosity])
 
-    with open(args.symbol_file) as symbol_file:
+    with args.symbol_file.open() as symbol_file:
         try:
             versions = symbolfile.SymbolFileParser(symbol_file, api_map,
                                                    args.arch, api, args.llndk,
                                                    args.apex).parse()
         except symbolfile.MultiplyDefinedSymbolError as ex:
-            sys.exit('{}: error: {}'.format(args.symbol_file, ex))
+            sys.exit(f'{args.symbol_file}: error: {ex}')
 
-    with open(args.stub_src, 'w') as src_file:
-        with open(args.version_script, 'w') as version_file:
-            generator = Generator(src_file, version_file, args.arch, api,
-                                  args.llndk, args.apex)
-            generator.write(versions)
+    with args.stub_src.open('w') as src_file:
+        with args.version_script.open('w') as version_script:
+            with args.symbol_list.open('w') as symbol_list:
+                generator = Generator(src_file, version_script, symbol_list,
+                                      args.arch, api, args.llndk, args.apex)
+                generator.write(versions)
 
 
 if __name__ == '__main__':
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 6d2c9d6..09551ea 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -19,9 +19,10 @@
 import textwrap
 import unittest
 
-import ndkstubgen
 import symbolfile
-from symbolfile import Arch, Tag
+from symbolfile import Arch, Tags
+
+import ndkstubgen
 
 
 # pylint: disable=missing-docstring
@@ -33,26 +34,30 @@
         # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
-        version = symbolfile.Version('VERSION_PRIVATE', None, [], [
-            symbolfile.Symbol('foo', []),
+        version = symbolfile.Version('VERSION_PRIVATE', None, Tags(), [
+            symbolfile.Symbol('foo', Tags()),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, [Tag('x86')], [
-            symbolfile.Symbol('foo', []),
-        ])
+        version = symbolfile.Version('VERSION', None, Tags.from_strs(['x86']),
+                                     [
+                                         symbolfile.Symbol('foo', Tags()),
+                                     ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION', None, [Tag('introduced=14')], [
-            symbolfile.Symbol('foo', []),
-        ])
+        version = symbolfile.Version('VERSION', None,
+                                     Tags.from_strs(['introduced=14']), [
+                                         symbolfile.Symbol('foo', Tags()),
+                                     ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
@@ -62,32 +67,34 @@
         # SymbolPresenceTest.
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('x86')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['x86'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('introduced=14')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('llndk')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
         self.assertEqual('', version_file.getvalue())
 
-        version = symbolfile.Version('VERSION_1', None, [], [
-            symbolfile.Symbol('foo', [Tag('apex')]),
+        version = symbolfile.Version('VERSION_1', None, Tags(), [
+            symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
         ])
         generator.write_version(version)
         self.assertEqual('', src_file.getvalue())
@@ -96,22 +103,23 @@
     def test_write(self) -> None:
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
 
         versions = [
-            symbolfile.Version('VERSION_1', None, [], [
-                symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', [Tag('var')]),
-                symbolfile.Symbol('woodly', [Tag('weak')]),
-                symbolfile.Symbol('doodly',
-                                  [Tag('weak'), Tag('var')]),
+            symbolfile.Version('VERSION_1', None, Tags(), [
+                symbolfile.Symbol('foo', Tags()),
+                symbolfile.Symbol('bar', Tags.from_strs(['var'])),
+                symbolfile.Symbol('woodly', Tags.from_strs(['weak'])),
+                symbolfile.Symbol('doodly', Tags.from_strs(['weak', 'var'])),
             ]),
-            symbolfile.Version('VERSION_2', 'VERSION_1', [], [
-                symbolfile.Symbol('baz', []),
+            symbolfile.Version('VERSION_2', 'VERSION_1', Tags(), [
+                symbolfile.Symbol('baz', Tags()),
             ]),
-            symbolfile.Version('VERSION_3', 'VERSION_1', [], [
-                symbolfile.Symbol('qux', [Tag('versioned=14')]),
+            symbolfile.Version('VERSION_3', 'VERSION_1', Tags(), [
+                symbolfile.Symbol('qux', Tags.from_strs(['versioned=14'])),
             ]),
         ]
 
@@ -141,6 +149,17 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            bar
+            woodly
+            doodly
+            baz
+            qux
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
 
 class IntegrationTest(unittest.TestCase):
     def test_integration(self) -> None:
@@ -186,8 +205,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -215,6 +236,16 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            baz
+            qux
+            wibble
+            wobble
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
     def test_integration_future_api(self) -> None:
         api_map = {
             'O': 9000,
@@ -238,8 +269,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9001, False, False)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9001, False, False)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
@@ -257,6 +290,13 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+        expected_allowlist = textwrap.dedent("""\
+            [abi_symbol_list]
+            foo
+            bar
+        """)
+        self.assertEqual(expected_allowlist, symbol_list_file.getvalue())
+
     def test_multiple_definition(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
             VERSION_1 {
@@ -336,8 +376,10 @@
 
         src_file = io.StringIO()
         version_file = io.StringIO()
-        generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'),
-                                         9, False, True)
+        symbol_list_file = io.StringIO()
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file,
+                                         Arch('arm'), 9, False, True)
         generator.write(versions)
 
         expected_src = textwrap.dedent("""\
diff --git a/cc/object.go b/cc/object.go
index d8f1aba..39fc43d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -116,7 +116,7 @@
 	Hdrs    bazel.LabelListAttribute
 	Deps    bazel.LabelListAttribute
 	Copts   bazel.StringListAttribute
-	Asflags []string
+	Asflags bazel.StringListAttribute
 }
 
 type bazelObject struct {
@@ -157,8 +157,6 @@
 
 	// Set arch-specific configurable attributes
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	var asFlags []string
-
 	var deps bazel.LabelListAttribute
 	for _, props := range m.linker.linkerProps() {
 		if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
@@ -167,28 +165,17 @@
 		}
 	}
 
-	productVariableProps := android.ProductVariableProperties(ctx)
-	if props, exists := productVariableProps["Asflags"]; exists {
-		// TODO(b/183595873): consider deduplicating handling of product variable properties
-		for _, prop := range props {
-			flags, ok := prop.Property.([]string)
-			if !ok {
-				ctx.ModuleErrorf("Could not convert product variable asflag property")
-				return
-			}
-			// TODO(b/183595873) handle other product variable usages -- as selects?
-			if newFlags, subbed := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable); subbed {
-				asFlags = append(asFlags, newFlags...)
-			}
-		}
-	}
-	// TODO(b/183595872) warn/error if we're not handling product variables
+	// Don't split cc_object srcs across languages. Doing so would add complexity,
+	// and this isn't typically done for cc_object.
+	srcs := compilerAttrs.srcs
+	srcs.Append(compilerAttrs.cSrcs)
+	srcs.Append(compilerAttrs.asSrcs)
 
 	attrs := &bazelObjectAttributes{
-		Srcs:    compilerAttrs.srcs,
+		Srcs:    srcs,
 		Deps:    deps,
 		Copts:   compilerAttrs.copts,
-		Asflags: asFlags,
+		Asflags: compilerAttrs.asFlags,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index bea1782..fd310a2 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -183,9 +183,8 @@
 			})
 
 			ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-				SharedLibrary:           outputFile,
-				UnstrippedSharedLibrary: p.unstrippedOutputFile,
-				Target:                  ctx.Target(),
+				SharedLibrary: outputFile,
+				Target:        ctx.Target(),
 
 				TableOfContents: p.tocFile,
 			})
diff --git a/cc/sabi.go b/cc/sabi.go
index c0eb57c..1f331cb 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -84,7 +84,7 @@
 		return "LLNDK"
 	}
 	if m.UseVndk() && m.IsVndk() && !m.IsVndkPrivate() {
-		if m.isVndkSp() {
+		if m.IsVndkSp() {
 			if m.IsVndkExt() {
 				return "VNDK-SP-ext"
 			} else {
@@ -192,7 +192,7 @@
 			// Mark all of its static library dependencies.
 			mctx.VisitDirectDeps(func(child android.Module) {
 				depTag := mctx.OtherModuleDependencyTag(child)
-				if libDepTag, ok := depTag.(libraryDependencyTag); ok && libDepTag.static() {
+				if IsStaticDepTag(depTag) || depTag == reuseObjTag {
 					if c, ok := child.(*Module); ok && c.sabi != nil {
 						// Mark this module so that .sdump for this static library can be generated.
 						c.sabi.Properties.ShouldCreateSourceAbiDump = true
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 397121e..513730a 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -854,7 +854,7 @@
 // as vendor snapshot. Such modules must create both cfi and non-cfi variants,
 // except for ones which explicitly disable cfi.
 func needsCfiForVendorSnapshot(mctx android.TopDownMutatorContext) bool {
-	if isVendorProprietaryModule(mctx) {
+	if IsVendorProprietaryModule(mctx) {
 		return false
 	}
 
@@ -902,7 +902,7 @@
 					if d, ok := child.(PlatformSanitizeable); ok && d.SanitizePropDefined() &&
 						!d.SanitizeNever() &&
 						!d.IsSanitizerExplicitlyDisabled(t) {
-						if t == cfi || t == Hwasan || t == scs {
+						if t == cfi || t == Hwasan || t == scs || t == Asan {
 							if d.StaticallyLinked() && d.SanitizerSupported(t) {
 								// Rust does not support some of these sanitizers, so we need to check if it's
 								// supported before setting this true.
@@ -1075,7 +1075,7 @@
 			sanitizers = append(sanitizers, "shadow-call-stack")
 		}
 
-		if Bool(c.sanitize.Properties.Sanitize.Memtag_heap) && c.binary() {
+		if Bool(c.sanitize.Properties.Sanitize.Memtag_heap) && c.Binary() {
 			noteDep := "note_memtag_heap_async"
 			if Bool(c.sanitize.Properties.Sanitize.Diag.Memtag_heap) {
 				noteDep = "note_memtag_heap_sync"
@@ -1192,7 +1192,7 @@
 				if c.Device() {
 					variations = append(variations, c.ImageVariation())
 				}
-				c.addSharedLibDependenciesWithVersions(mctx, variations, depTag, runtimeLibrary, "", true)
+				AddSharedLibDependenciesWithVersions(mctx, c, variations, depTag, runtimeLibrary, "", true)
 			}
 			// static lib does not have dependency to the runtime library. The
 			// dependency will be added to the executables or shared libs using
@@ -1208,6 +1208,14 @@
 	AddSanitizerDependencies(ctx android.BottomUpMutatorContext, sanitizerName string)
 }
 
+func (c *Module) MinimalRuntimeDep() bool {
+	return c.sanitize.Properties.MinimalRuntimeDep
+}
+
+func (c *Module) UbsanRuntimeDep() bool {
+	return c.sanitize.Properties.UbsanRuntimeDep
+}
+
 func (c *Module) SanitizePropDefined() bool {
 	return c.sanitize != nil
 }
@@ -1253,7 +1261,7 @@
 				modules[0].(PlatformSanitizeable).SetSanitizer(t, true)
 			} else if c.IsSanitizerEnabled(t) || c.SanitizeDep() {
 				isSanitizerEnabled := c.IsSanitizerEnabled(t)
-				if c.StaticallyLinked() || c.Header() || t == Asan || t == Fuzzer {
+				if c.StaticallyLinked() || c.Header() || t == Fuzzer {
 					// Static and header libs are split into non-sanitized and sanitized variants.
 					// Shared libs are not split. However, for asan and fuzzer, we split even for shared
 					// libs because a library sanitized for asan/fuzzer can't be linked from a library
@@ -1441,6 +1449,14 @@
 	return false
 }
 
+func (m *Module) UbsanRuntimeNeeded() bool {
+	return enableUbsanRuntime(m.sanitize)
+}
+
+func (m *Module) MinimalRuntimeNeeded() bool {
+	return enableMinimalRuntime(m.sanitize)
+}
+
 func enableUbsanRuntime(sanitize *sanitize) bool {
 	return Bool(sanitize.Properties.Sanitize.Diag.Integer_overflow) ||
 		Bool(sanitize.Properties.Sanitize.Diag.Undefined) ||
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
new file mode 100644
index 0000000..f126346
--- /dev/null
+++ b/cc/sanitize_test.go
@@ -0,0 +1,204 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+	"testing"
+
+	"android/soong/android"
+)
+
+var prepareForAsanTest = android.FixtureAddFile("asan/Android.bp", []byte(`
+	cc_library_shared {
+		name: "libclang_rt.asan-aarch64-android",
+	}
+
+	cc_library_shared {
+		name: "libclang_rt.asan-arm-android",
+	}
+`))
+
+func TestAsan(t *testing.T) {
+	bp := `
+		cc_binary {
+			name: "bin_with_asan",
+			host_supported: true,
+			shared_libs: [
+				"libshared",
+				"libasan",
+			],
+			static_libs: [
+				"libstatic",
+				"libnoasan",
+			],
+			sanitize: {
+				address: true,
+			}
+		}
+
+		cc_binary {
+			name: "bin_no_asan",
+			host_supported: true,
+			shared_libs: [
+				"libshared",
+				"libasan",
+			],
+			static_libs: [
+				"libstatic",
+				"libnoasan",
+			],
+		}
+
+		cc_library_shared {
+			name: "libshared",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+		}
+
+		cc_library_shared {
+			name: "libasan",
+			host_supported: true,
+			shared_libs: ["libtransitive"],
+			sanitize: {
+				address: true,
+			}
+		}
+
+		cc_library_shared {
+			name: "libtransitive",
+			host_supported: true,
+		}
+
+		cc_library_static {
+			name: "libstatic",
+			host_supported: true,
+		}
+
+		cc_library_static {
+			name: "libnoasan",
+			host_supported: true,
+			sanitize: {
+				address: false,
+			}
+		}
+	`
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForAsanTest,
+	).RunTestWithBp(t, bp)
+
+	check := func(t *testing.T, result *android.TestResult, variant string) {
+		asanVariant := variant + "_asan"
+		sharedVariant := variant + "_shared"
+		sharedAsanVariant := sharedVariant + "_asan"
+		staticVariant := variant + "_static"
+		staticAsanVariant := staticVariant + "_asan"
+
+		// The binaries, one with asan and one without
+		binWithAsan := result.ModuleForTests("bin_with_asan", asanVariant)
+		binNoAsan := result.ModuleForTests("bin_no_asan", variant)
+
+		// Shared libraries that don't request asan
+		libShared := result.ModuleForTests("libshared", sharedVariant)
+		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+
+		// Shared library that requests asan
+		libAsan := result.ModuleForTests("libasan", sharedAsanVariant)
+
+		// Static library that uses an asan variant for bin_with_asan and a non-asan variant
+		// for bin_no_asan.
+		libStaticAsanVariant := result.ModuleForTests("libstatic", staticAsanVariant)
+		libStaticNoAsanVariant := result.ModuleForTests("libstatic", staticVariant)
+
+		// Static library that never uses asan.
+		libNoAsan := result.ModuleForTests("libnoasan", staticVariant)
+
+		// expectSharedLinkDep verifies that the from module links against the to module as a
+		// shared library.
+		expectSharedLinkDep := func(from, to android.TestingModule) {
+			t.Helper()
+			fromLink := from.Description("link")
+			toLink := to.Description("strip")
+
+			if g, w := fromLink.OrderOnly.Strings(), toLink.Output.String(); !android.InList(w, g) {
+				t.Errorf("%s should link against %s, expected %q, got %q",
+					from.Module(), to.Module(), w, g)
+			}
+		}
+
+		// expectStaticLinkDep verifies that the from module links against the to module as a
+		// static library.
+		expectStaticLinkDep := func(from, to android.TestingModule) {
+			t.Helper()
+			fromLink := from.Description("link")
+			toLink := to.Description("static link")
+
+			if g, w := fromLink.Implicits.Strings(), toLink.Output.String(); !android.InList(w, g) {
+				t.Errorf("%s should link against %s, expected %q, got %q",
+					from.Module(), to.Module(), w, g)
+			}
+
+		}
+
+		// expectInstallDep verifies that the install rule of the from module depends on the
+		// install rule of the to module.
+		expectInstallDep := func(from, to android.TestingModule) {
+			t.Helper()
+			fromInstalled := from.Description("install")
+			toInstalled := to.Description("install")
+
+			// combine implicits and order-only dependencies, host uses implicit but device uses
+			// order-only.
+			got := append(fromInstalled.Implicits.Strings(), fromInstalled.OrderOnly.Strings()...)
+			want := toInstalled.Output.String()
+			if !android.InList(want, got) {
+				t.Errorf("%s installation should depend on %s, expected %q, got %q",
+					from.Module(), to.Module(), want, got)
+			}
+		}
+
+		expectSharedLinkDep(binWithAsan, libShared)
+		expectSharedLinkDep(binWithAsan, libAsan)
+		expectSharedLinkDep(libShared, libTransitive)
+		expectSharedLinkDep(libAsan, libTransitive)
+
+		expectStaticLinkDep(binWithAsan, libStaticAsanVariant)
+		expectStaticLinkDep(binWithAsan, libNoAsan)
+
+		expectInstallDep(binWithAsan, libShared)
+		expectInstallDep(binWithAsan, libAsan)
+		expectInstallDep(binWithAsan, libTransitive)
+		expectInstallDep(libShared, libTransitive)
+		expectInstallDep(libAsan, libTransitive)
+
+		expectSharedLinkDep(binNoAsan, libShared)
+		expectSharedLinkDep(binNoAsan, libAsan)
+		expectSharedLinkDep(libShared, libTransitive)
+		expectSharedLinkDep(libAsan, libTransitive)
+
+		expectStaticLinkDep(binNoAsan, libStaticNoAsanVariant)
+		expectStaticLinkDep(binNoAsan, libNoAsan)
+
+		expectInstallDep(binNoAsan, libShared)
+		expectInstallDep(binNoAsan, libAsan)
+		expectInstallDep(binNoAsan, libTransitive)
+		expectInstallDep(libShared, libTransitive)
+		expectInstallDep(libAsan, libTransitive)
+	}
+
+	t.Run("host", func(t *testing.T) { check(t, result, result.Config.BuildOSTarget.String()) })
+	t.Run("device", func(t *testing.T) { check(t, result, "android_arm64_armv8-a") })
+}
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index 885a0ce..3a382a1 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -28,19 +28,19 @@
 
 // Defines the specifics of different images to which the snapshot process is applicable, e.g.,
 // vendor, recovery, ramdisk.
-type snapshotImage interface {
+type SnapshotImage interface {
 	// Returns true if a snapshot should be generated for this image.
 	shouldGenerateSnapshot(ctx android.SingletonContext) bool
 
 	// Function that returns true if the module is included in this image.
 	// Using a function return instead of a value to prevent early
 	// evalution of a function that may be not be defined.
-	inImage(m *Module) func() bool
+	inImage(m LinkableInterface) func() bool
 
 	// Returns true if the module is private and must not be included in the
 	// snapshot. For example VNDK-private modules must return true for the
 	// vendor snapshots. But false for the recovery snapshots.
-	private(m *Module) bool
+	private(m LinkableInterface) bool
 
 	// Returns true if a dir under source tree is an SoC-owned proprietary
 	// directory, such as device/, vendor/, etc.
@@ -56,7 +56,7 @@
 	// Whether a given module has been explicitly excluded from the
 	// snapshot, e.g., using the exclude_from_vendor_snapshot or
 	// exclude_from_recovery_snapshot properties.
-	excludeFromSnapshot(m *Module) bool
+	excludeFromSnapshot(m LinkableInterface) bool
 
 	// Returns true if the build is using a snapshot for this image.
 	isUsingSnapshot(cfg android.DeviceConfig) bool
@@ -108,7 +108,7 @@
 	}
 )
 
-func (vendorSnapshotImage) init(ctx android.RegistrationContext) {
+func (vendorSnapshotImage) Init(ctx android.RegistrationContext) {
 	ctx.RegisterSingletonType("vendor-snapshot", VendorSnapshotSingleton)
 	ctx.RegisterModuleType("vendor_snapshot", vendorSnapshotFactory)
 	ctx.RegisterModuleType("vendor_snapshot_shared", VendorSnapshotSharedFactory)
@@ -120,16 +120,20 @@
 	ctx.RegisterSingletonType("vendor-fake-snapshot", VendorFakeSnapshotSingleton)
 }
 
+func (vendorSnapshotImage) RegisterAdditionalModule(ctx android.RegistrationContext, name string, factory android.ModuleFactory) {
+	ctx.RegisterModuleType(name, factory)
+}
+
 func (vendorSnapshotImage) shouldGenerateSnapshot(ctx android.SingletonContext) bool {
 	// BOARD_VNDK_VERSION must be set to 'current' in order to generate a snapshot.
 	return ctx.DeviceConfig().VndkVersion() == "current"
 }
 
-func (vendorSnapshotImage) inImage(m *Module) func() bool {
+func (vendorSnapshotImage) inImage(m LinkableInterface) func() bool {
 	return m.InVendor
 }
 
-func (vendorSnapshotImage) private(m *Module) bool {
+func (vendorSnapshotImage) private(m LinkableInterface) bool {
 	return m.IsVndkPrivate()
 }
 
@@ -159,7 +163,7 @@
 	return true
 }
 
-func (vendorSnapshotImage) excludeFromSnapshot(m *Module) bool {
+func (vendorSnapshotImage) excludeFromSnapshot(m LinkableInterface) bool {
 	return m.ExcludeFromVendorSnapshot()
 }
 
@@ -206,12 +210,12 @@
 	return ctx.DeviceConfig().RecoverySnapshotVersion() == "current"
 }
 
-func (recoverySnapshotImage) inImage(m *Module) func() bool {
+func (recoverySnapshotImage) inImage(m LinkableInterface) func() bool {
 	return m.InRecovery
 }
 
 // recovery snapshot does not have private libraries.
-func (recoverySnapshotImage) private(m *Module) bool {
+func (recoverySnapshotImage) private(m LinkableInterface) bool {
 	return false
 }
 
@@ -224,7 +228,7 @@
 	return false
 }
 
-func (recoverySnapshotImage) excludeFromSnapshot(m *Module) bool {
+func (recoverySnapshotImage) excludeFromSnapshot(m LinkableInterface) bool {
 	return m.ExcludeFromRecoverySnapshot()
 }
 
@@ -254,26 +258,28 @@
 	return recoverySuffix
 }
 
-var vendorSnapshotImageSingleton vendorSnapshotImage
+var VendorSnapshotImageSingleton vendorSnapshotImage
 var recoverySnapshotImageSingleton recoverySnapshotImage
 
 func init() {
-	vendorSnapshotImageSingleton.init(android.InitRegistrationContext)
+	VendorSnapshotImageSingleton.Init(android.InitRegistrationContext)
 	recoverySnapshotImageSingleton.init(android.InitRegistrationContext)
 }
 
 const (
 	snapshotHeaderSuffix = "_header."
-	snapshotSharedSuffix = "_shared."
-	snapshotStaticSuffix = "_static."
+	SnapshotSharedSuffix = "_shared."
+	SnapshotStaticSuffix = "_static."
 	snapshotBinarySuffix = "_binary."
 	snapshotObjectSuffix = "_object."
+	SnapshotRlibSuffix   = "_rlib."
 )
 
 type SnapshotProperties struct {
 	Header_libs []string `android:"arch_variant"`
 	Static_libs []string `android:"arch_variant"`
 	Shared_libs []string `android:"arch_variant"`
+	Rlibs       []string `android:"arch_variant"`
 	Vndk_libs   []string `android:"arch_variant"`
 	Binaries    []string `android:"arch_variant"`
 	Objects     []string `android:"arch_variant"`
@@ -284,14 +290,14 @@
 
 	properties SnapshotProperties
 
-	baseSnapshot baseSnapshotDecorator
+	baseSnapshot BaseSnapshotDecorator
 
-	image snapshotImage
+	image SnapshotImage
 }
 
 func (s *snapshot) ImageMutatorBegin(ctx android.BaseModuleContext) {
 	cfg := ctx.DeviceConfig()
-	if !s.image.isUsingSnapshot(cfg) || s.image.targetSnapshotVersion(cfg) != s.baseSnapshot.version() {
+	if !s.image.isUsingSnapshot(cfg) || s.image.targetSnapshotVersion(cfg) != s.baseSnapshot.Version() {
 		s.Disable()
 	}
 }
@@ -341,7 +347,7 @@
 		for _, name := range names {
 			snapshotMap[name] = name +
 				getSnapshotNameSuffix(snapshotSuffix+moduleSuffix,
-					s.baseSnapshot.version(),
+					s.baseSnapshot.Version(),
 					ctx.DeviceConfig().Arches()[0].ArchType.String())
 		}
 		return snapshotMap
@@ -351,8 +357,9 @@
 	headers := collectSnapshotMap(s.properties.Header_libs, snapshotSuffix, snapshotHeaderSuffix)
 	binaries := collectSnapshotMap(s.properties.Binaries, snapshotSuffix, snapshotBinarySuffix)
 	objects := collectSnapshotMap(s.properties.Objects, snapshotSuffix, snapshotObjectSuffix)
-	staticLibs := collectSnapshotMap(s.properties.Static_libs, snapshotSuffix, snapshotStaticSuffix)
-	sharedLibs := collectSnapshotMap(s.properties.Shared_libs, snapshotSuffix, snapshotSharedSuffix)
+	staticLibs := collectSnapshotMap(s.properties.Static_libs, snapshotSuffix, SnapshotStaticSuffix)
+	sharedLibs := collectSnapshotMap(s.properties.Shared_libs, snapshotSuffix, SnapshotSharedSuffix)
+	rlibs := collectSnapshotMap(s.properties.Rlibs, snapshotSuffix, SnapshotRlibSuffix)
 	vndkLibs := collectSnapshotMap(s.properties.Vndk_libs, "", vndkSuffix)
 	for k, v := range vndkLibs {
 		sharedLibs[k] = v
@@ -364,11 +371,12 @@
 		Objects:    objects,
 		StaticLibs: staticLibs,
 		SharedLibs: sharedLibs,
+		Rlibs:      rlibs,
 	})
 }
 
 type SnapshotInfo struct {
-	HeaderLibs, Binaries, Objects, StaticLibs, SharedLibs map[string]string
+	HeaderLibs, Binaries, Objects, StaticLibs, SharedLibs, Rlibs map[string]string
 }
 
 var SnapshotInfoProvider = blueprint.NewMutatorProvider(SnapshotInfo{}, "deps")
@@ -376,14 +384,14 @@
 var _ android.ImageInterface = (*snapshot)(nil)
 
 func vendorSnapshotFactory() android.Module {
-	return snapshotFactory(vendorSnapshotImageSingleton)
+	return snapshotFactory(VendorSnapshotImageSingleton)
 }
 
 func recoverySnapshotFactory() android.Module {
 	return snapshotFactory(recoverySnapshotImageSingleton)
 }
 
-func snapshotFactory(image snapshotImage) android.Module {
+func snapshotFactory(image SnapshotImage) android.Module {
 	snapshot := &snapshot{}
 	snapshot.image = image
 	snapshot.AddProperties(
@@ -393,7 +401,7 @@
 	return snapshot
 }
 
-type baseSnapshotDecoratorProperties struct {
+type BaseSnapshotDecoratorProperties struct {
 	// snapshot version.
 	Version string
 
@@ -408,7 +416,7 @@
 	ModuleSuffix string `blueprint:"mutated"`
 }
 
-// baseSnapshotDecorator provides common basic functions for all snapshot modules, such as snapshot
+// BaseSnapshotDecorator provides common basic functions for all snapshot modules, such as snapshot
 // version, snapshot arch, etc. It also adds a special suffix to Soong module name, so it doesn't
 // collide with source modules. e.g. the following example module,
 //
@@ -420,52 +428,88 @@
 // }
 //
 // will be seen as "libbase.vendor_static.30.arm64" by Soong.
-type baseSnapshotDecorator struct {
-	baseProperties baseSnapshotDecoratorProperties
-	image          snapshotImage
+type BaseSnapshotDecorator struct {
+	baseProperties BaseSnapshotDecoratorProperties
+	image          SnapshotImage
 }
 
-func (p *baseSnapshotDecorator) Name(name string) string {
+func (p *BaseSnapshotDecorator) Name(name string) string {
 	return name + p.NameSuffix()
 }
 
-func (p *baseSnapshotDecorator) NameSuffix() string {
-	return getSnapshotNameSuffix(p.moduleSuffix(), p.version(), p.arch())
+func (p *BaseSnapshotDecorator) NameSuffix() string {
+	return getSnapshotNameSuffix(p.moduleSuffix(), p.Version(), p.Arch())
 }
 
-func (p *baseSnapshotDecorator) version() string {
+func (p *BaseSnapshotDecorator) Version() string {
 	return p.baseProperties.Version
 }
 
-func (p *baseSnapshotDecorator) arch() string {
+func (p *BaseSnapshotDecorator) Arch() string {
 	return p.baseProperties.Target_arch
 }
 
-func (p *baseSnapshotDecorator) moduleSuffix() string {
+func (p *BaseSnapshotDecorator) moduleSuffix() string {
 	return p.baseProperties.ModuleSuffix
 }
 
-func (p *baseSnapshotDecorator) isSnapshotPrebuilt() bool {
+func (p *BaseSnapshotDecorator) IsSnapshotPrebuilt() bool {
 	return true
 }
 
-func (p *baseSnapshotDecorator) snapshotAndroidMkSuffix() string {
+func (p *BaseSnapshotDecorator) SnapshotAndroidMkSuffix() string {
 	return p.baseProperties.Androidmk_suffix
 }
 
-func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext) {
-	if ctx.OtherModuleDependencyVariantExists([]blueprint.Variation{
-		{Mutator: "image", Variation: android.CoreVariation},
-	}, ctx.Module().(*Module).BaseModuleName()) {
+func (p *BaseSnapshotDecorator) SetSnapshotAndroidMkSuffix(ctx android.ModuleContext, variant string) {
+	// If there are any 2 or more variations among {core, product, vendor, recovery}
+	// we have to add the androidmk suffix to avoid duplicate modules with the same
+	// name.
+	variations := append(ctx.Target().Variations(), blueprint.Variation{
+		Mutator:   "image",
+		Variation: android.CoreVariation})
+
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(LinkableInterface).BaseModuleName()) {
 		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
-	} else {
-		p.baseProperties.Androidmk_suffix = ""
+		return
 	}
+
+	variations = append(ctx.Target().Variations(), blueprint.Variation{
+		Mutator:   "image",
+		Variation: ProductVariationPrefix + ctx.DeviceConfig().PlatformVndkVersion()})
+
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(LinkableInterface).BaseModuleName()) {
+		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+		return
+	}
+
+	images := []SnapshotImage{VendorSnapshotImageSingleton, recoverySnapshotImageSingleton}
+
+	for _, image := range images {
+		if p.image == image {
+			continue
+		}
+		variations = append(ctx.Target().Variations(), blueprint.Variation{
+			Mutator:   "image",
+			Variation: image.imageVariantName(ctx.DeviceConfig())})
+
+		if ctx.OtherModuleFarDependencyVariantExists(variations,
+			ctx.Module().(LinkableInterface).BaseModuleName()+
+				getSnapshotNameSuffix(
+					image.moduleNameSuffix()+variant,
+					p.Version(),
+					ctx.DeviceConfig().Arches()[0].ArchType.String())) {
+			p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+			return
+		}
+	}
+
+	p.baseProperties.Androidmk_suffix = ""
 }
 
 // Call this with a module suffix after creating a snapshot module, such as
 // vendorSnapshotSharedSuffix, recoverySnapshotBinarySuffix, etc.
-func (p *baseSnapshotDecorator) init(m *Module, image snapshotImage, moduleSuffix string) {
+func (p *BaseSnapshotDecorator) Init(m LinkableInterface, image SnapshotImage, moduleSuffix string) {
 	p.image = image
 	p.baseProperties.ModuleSuffix = image.moduleNameSuffix() + moduleSuffix
 	m.AddProperties(&p.baseProperties)
@@ -476,8 +520,8 @@
 
 // vendorSnapshotLoadHook disables snapshots if it's not BOARD_VNDK_VERSION.
 // As vendor snapshot is only for vendor, such modules won't be used at all.
-func vendorSnapshotLoadHook(ctx android.LoadHookContext, p *baseSnapshotDecorator) {
-	if p.version() != ctx.DeviceConfig().VndkVersion() {
+func vendorSnapshotLoadHook(ctx android.LoadHookContext, p *BaseSnapshotDecorator) {
+	if p.Version() != ctx.DeviceConfig().VndkVersion() {
 		ctx.Module().Disable()
 		return
 	}
@@ -492,7 +536,7 @@
 // include directories, c flags, sanitize dependency information, etc.
 //
 // These modules are auto-generated by development/vendor_snapshot/update.py.
-type snapshotLibraryProperties struct {
+type SnapshotLibraryProperties struct {
 	// Prebuilt file for each arch.
 	Src *string `android:"arch_variant"`
 
@@ -518,14 +562,14 @@
 }
 
 type snapshotLibraryDecorator struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	*libraryDecorator
-	properties          snapshotLibraryProperties
+	properties          SnapshotLibraryProperties
 	sanitizerProperties struct {
 		CfiEnabled bool `blueprint:"mutated"`
 
 		// Library flags for cfi variant.
-		Cfi snapshotLibraryProperties `android:"arch_variant"`
+		Cfi SnapshotLibraryProperties `android:"arch_variant"`
 	}
 }
 
@@ -534,9 +578,9 @@
 	return p.libraryDecorator.linkerFlags(ctx, flags)
 }
 
-func (p *snapshotLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *snapshotLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
-	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
+	if len(arches) == 0 || arches[0].ArchType.String() != p.Arch() {
 		return false
 	}
 	if !p.header() && p.properties.Src == nil {
@@ -549,7 +593,16 @@
 // As snapshots are prebuilts, this just returns the prebuilt binary after doing things which are
 // done by normal library decorator, e.g. exporting flags.
 func (p *snapshotLibraryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	var variant string
+	if p.shared() {
+		variant = SnapshotSharedSuffix
+	} else if p.static() {
+		variant = SnapshotStaticSuffix
+	} else {
+		variant = snapshotHeaderSuffix
+	}
+
+	p.SetSnapshotAndroidMkSuffix(ctx, variant)
 
 	if p.header() {
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
@@ -559,7 +612,7 @@
 		p.properties = p.sanitizerProperties.Cfi
 	}
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -589,9 +642,8 @@
 		transformSharedObjectToToc(ctx, in, tocFile, builderFlags)
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           in,
-			UnstrippedSharedLibrary: p.unstrippedOutputFile,
-			Target:                  ctx.Target(),
+			SharedLibrary: in,
+			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
 		})
@@ -612,7 +664,7 @@
 }
 
 func (p *snapshotLibraryDecorator) install(ctx ModuleContext, file android.Path) {
-	if p.matchesWithDevice(ctx.DeviceConfig()) && (p.shared() || p.static()) {
+	if p.MatchesWithDevice(ctx.DeviceConfig()) && (p.shared() || p.static()) {
 		p.baseInstaller.install(ctx, file)
 	}
 }
@@ -642,7 +694,7 @@
 	}
 }
 
-func snapshotLibraryFactory(image snapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+func snapshotLibraryFactory(image SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
@@ -665,7 +717,7 @@
 	module.linker = prebuilt
 	module.installer = prebuilt
 
-	prebuilt.init(module, image, moduleSuffix)
+	prebuilt.Init(module, image, moduleSuffix)
 	module.AddProperties(
 		&prebuilt.properties,
 		&prebuilt.sanitizerProperties,
@@ -679,7 +731,7 @@
 // overrides the vendor variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, SnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -689,7 +741,7 @@
 // overrides the recovery variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, SnapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -699,7 +751,7 @@
 // overrides the vendor variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, SnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -709,7 +761,7 @@
 // overrides the recovery variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, SnapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -719,7 +771,7 @@
 // overrides the vendor variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(VendorSnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -749,13 +801,13 @@
 }
 
 type snapshotBinaryDecorator struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	*binaryDecorator
 	properties snapshotBinaryProperties
 }
 
-func (p *snapshotBinaryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
-	if config.DeviceArch() != p.arch() {
+func (p *snapshotBinaryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
+	if config.DeviceArch() != p.Arch() {
 		return false
 	}
 	if p.properties.Src == nil {
@@ -767,9 +819,9 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotBinaryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	p.SetSnapshotAndroidMkSuffix(ctx, snapshotBinarySuffix)
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -797,7 +849,7 @@
 // development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_binary
 // overrides the vendor variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func VendorSnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(vendorSnapshotImageSingleton, snapshotBinarySuffix)
+	return snapshotBinaryFactory(VendorSnapshotImageSingleton, snapshotBinarySuffix)
 }
 
 // recovery_snapshot_binary is a special prebuilt executable binary which is auto-generated by
@@ -807,7 +859,7 @@
 	return snapshotBinaryFactory(recoverySnapshotImageSingleton, snapshotBinarySuffix)
 }
 
-func snapshotBinaryFactory(image snapshotImage, moduleSuffix string) android.Module {
+func snapshotBinaryFactory(image SnapshotImage, moduleSuffix string) android.Module {
 	module, binary := NewBinary(android.DeviceSupported)
 	binary.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	binary.baseLinker.Properties.Nocrt = BoolPtr(true)
@@ -826,7 +878,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, image, moduleSuffix)
+	prebuilt.Init(module, image, moduleSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -844,13 +896,13 @@
 }
 
 type snapshotObjectLinker struct {
-	baseSnapshotDecorator
+	BaseSnapshotDecorator
 	objectLinker
 	properties vendorSnapshotObjectProperties
 }
 
-func (p *snapshotObjectLinker) matchesWithDevice(config android.DeviceConfig) bool {
-	if config.DeviceArch() != p.arch() {
+func (p *snapshotObjectLinker) MatchesWithDevice(config android.DeviceConfig) bool {
+	if config.DeviceArch() != p.Arch() {
 		return false
 	}
 	if p.properties.Src == nil {
@@ -862,9 +914,9 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotObjectLinker) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
-	p.setSnapshotAndroidMkSuffix(ctx)
+	p.SetSnapshotAndroidMkSuffix(ctx, snapshotObjectSuffix)
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
 
@@ -888,7 +940,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotImageSingleton, snapshotObjectSuffix)
+	prebuilt.Init(module, VendorSnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -906,19 +958,19 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
+	prebuilt.Init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
 
-type snapshotInterface interface {
-	matchesWithDevice(config android.DeviceConfig) bool
-	isSnapshotPrebuilt() bool
-	version() string
-	snapshotAndroidMkSuffix() string
+type SnapshotInterface interface {
+	MatchesWithDevice(config android.DeviceConfig) bool
+	IsSnapshotPrebuilt() bool
+	Version() string
+	SnapshotAndroidMkSuffix() string
 }
 
-var _ snapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
-var _ snapshotInterface = (*snapshotLibraryDecorator)(nil)
-var _ snapshotInterface = (*snapshotBinaryDecorator)(nil)
-var _ snapshotInterface = (*snapshotObjectLinker)(nil)
+var _ SnapshotInterface = (*vndkPrebuiltLibraryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotLibraryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotBinaryDecorator)(nil)
+var _ SnapshotInterface = (*snapshotObjectLinker)(nil)
diff --git a/cc/snapshot_utils.go b/cc/snapshot_utils.go
index c32fa36..a6c8ed5 100644
--- a/cc/snapshot_utils.go
+++ b/cc/snapshot_utils.go
@@ -20,9 +20,39 @@
 )
 
 var (
-	headerExts = []string{".h", ".hh", ".hpp", ".hxx", ".h++", ".inl", ".inc", ".ipp", ".h.generic"}
+	HeaderExts = []string{".h", ".hh", ".hpp", ".hxx", ".h++", ".inl", ".inc", ".ipp", ".h.generic"}
 )
 
+func (m *Module) IsSnapshotLibrary() bool {
+	if _, ok := m.linker.(snapshotLibraryInterface); ok {
+		return true
+	}
+	return false
+}
+
+func (m *Module) SnapshotHeaders() android.Paths {
+	if m.IsSnapshotLibrary() {
+		return m.linker.(snapshotLibraryInterface).snapshotHeaders()
+	}
+	return android.Paths{}
+}
+
+func (m *Module) Dylib() bool {
+	return false
+}
+
+func (m *Module) Rlib() bool {
+	return false
+}
+
+func (m *Module) SnapshotRuntimeLibs() []string {
+	return m.Properties.SnapshotRuntimeLibs
+}
+
+func (m *Module) SnapshotSharedLibs() []string {
+	return m.Properties.SnapshotSharedLibs
+}
+
 // snapshotLibraryInterface is an interface for libraries captured to VNDK / vendor snapshots.
 type snapshotLibraryInterface interface {
 	libraryInterface
@@ -68,18 +98,18 @@
 	return snapshot, found
 }
 
-// shouldCollectHeadersForSnapshot determines if the module is a possible candidate for snapshot.
+// ShouldCollectHeadersForSnapshot determines if the module is a possible candidate for snapshot.
 // If it's true, collectHeadersForSnapshot will be called in GenerateAndroidBuildActions.
-func shouldCollectHeadersForSnapshot(ctx android.ModuleContext, m *Module, apexInfo android.ApexInfo) bool {
+func ShouldCollectHeadersForSnapshot(ctx android.ModuleContext, m LinkableInterface, apexInfo android.ApexInfo) bool {
 	if ctx.DeviceConfig().VndkVersion() != "current" &&
 		ctx.DeviceConfig().RecoverySnapshotVersion() != "current" {
 		return false
 	}
-	if _, _, ok := isVndkSnapshotAware(ctx.DeviceConfig(), m, apexInfo); ok {
+	if _, ok := isVndkSnapshotAware(ctx.DeviceConfig(), m, apexInfo); ok {
 		return ctx.Config().VndkSnapshotBuildArtifacts()
 	}
 
-	for _, image := range []snapshotImage{vendorSnapshotImageSingleton, recoverySnapshotImageSingleton} {
+	for _, image := range []SnapshotImage{VendorSnapshotImageSingleton, recoverySnapshotImageSingleton} {
 		if isSnapshotAware(ctx.DeviceConfig(), m, image.isProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()), apexInfo, image) {
 			return true
 		}
diff --git a/cc/stl.go b/cc/stl.go
index 75921c6..06dc840 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -199,7 +199,9 @@
 			deps.StaticLibs = append(deps.StaticLibs, stl.Properties.SelectedStl, "ndk_libc++abi")
 		}
 		if needsLibAndroidSupport(ctx) {
-			deps.StaticLibs = append(deps.StaticLibs, "ndk_libandroid_support")
+			// Use LateStaticLibs for ndk_libandroid_support so that its include directories
+			// come after ndk_libc++_static or ndk_libc++_shared.
+			deps.LateStaticLibs = append(deps.LateStaticLibs, "ndk_libandroid_support")
 		}
 		deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
 	default:
diff --git a/cc/strip.go b/cc/strip.go
index b1f34bb..c60e135 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -59,6 +59,7 @@
 	return !forceDisable && (forceEnable || defaultEnable)
 }
 
+// Keep this consistent with //build/bazel/rules/stripped_shared_library.bzl.
 func (stripper *Stripper) strip(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
 	flags StripFlags, isStaticLib bool) {
 	if actx.Darwin() {
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 5678e7d..31c4443 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -14,18 +14,22 @@
 # limitations under the License.
 #
 """Parser for Android's version script information."""
-from dataclasses import dataclass
+from __future__ import annotations
+
+from dataclasses import dataclass, field
 import logging
 import re
 from typing import (
     Dict,
     Iterable,
+    Iterator,
     List,
     Mapping,
     NewType,
     Optional,
     TextIO,
     Tuple,
+    Union,
 )
 
 
@@ -52,11 +56,52 @@
 
 
 @dataclass
+class Tags:
+    """Container class for the tags attached to a symbol or version."""
+
+    tags: tuple[Tag, ...] = field(default_factory=tuple)
+
+    @classmethod
+    def from_strs(cls, strs: Iterable[str]) -> Tags:
+        """Constructs tags from a collection of strings.
+
+        Does not decode API levels.
+        """
+        return Tags(tuple(Tag(s) for s in strs))
+
+    def __contains__(self, tag: Union[Tag, str]) -> bool:
+        return tag in self.tags
+
+    def __iter__(self) -> Iterator[Tag]:
+        yield from self.tags
+
+    @property
+    def has_mode_tags(self) -> bool:
+        """Returns True if any mode tags (apex, llndk, etc) are set."""
+        return self.has_apex_tags or self.has_llndk_tags
+
+    @property
+    def has_apex_tags(self) -> bool:
+        """Returns True if any APEX tags are set."""
+        return 'apex' in self.tags or 'systemapi' in self.tags
+
+    @property
+    def has_llndk_tags(self) -> bool:
+        """Returns True if any LL-NDK tags are set."""
+        return 'llndk' in self.tags
+
+    @property
+    def has_platform_only_tags(self) -> bool:
+        """Returns True if any platform-only tags are set."""
+        return 'platform-only' in self.tags
+
+
+@dataclass
 class Symbol:
     """A symbol definition from a symbol file."""
 
     name: str
-    tags: List[Tag]
+    tags: Tags
 
 
 @dataclass
@@ -65,14 +110,22 @@
 
     name: str
     base: Optional[str]
-    tags: List[Tag]
+    tags: Tags
     symbols: List[Symbol]
 
+    @property
+    def is_private(self) -> bool:
+        """Returns True if this version block is private (platform only)."""
+        return self.name.endswith('_PRIVATE') or self.name.endswith('_PLATFORM')
 
-def get_tags(line: str) -> List[Tag]:
+
+def get_tags(line: str, api_map: ApiMap) -> Tags:
     """Returns a list of all tags on this line."""
     _, _, all_tags = line.strip().partition('#')
-    return [Tag(e) for e in re.split(r'\s+', all_tags) if e.strip()]
+    return Tags(tuple(
+        decode_api_level_tag(Tag(e), api_map)
+        for e in re.split(r'\s+', all_tags) if e.strip()
+    ))
 
 
 def is_api_level_tag(tag: Tag) -> bool:
@@ -104,24 +157,21 @@
     return api_map[api]
 
 
-def decode_api_level_tags(tags: Iterable[Tag], api_map: ApiMap) -> List[Tag]:
-    """Decodes API level code names in a list of tags.
+def decode_api_level_tag(tag: Tag, api_map: ApiMap) -> Tag:
+    """Decodes API level code name in a tag.
 
     Raises:
         ParseError: An unknown version name was found in a tag.
     """
-    decoded_tags = list(tags)
-    for idx, tag in enumerate(tags):
-        if not is_api_level_tag(tag):
-            continue
-        name, value = split_tag(tag)
+    if not is_api_level_tag(tag):
+        return tag
 
-        try:
-            decoded = str(decode_api_level(value, api_map))
-            decoded_tags[idx] = Tag('='.join([name, decoded]))
-        except KeyError:
-            raise ParseError(f'Unknown version name in tag: {tag}')
-    return decoded_tags
+    name, value = split_tag(tag)
+    try:
+        decoded = str(decode_api_level(value, api_map))
+        return Tag(f'{name}={decoded}')
+    except KeyError as ex:
+        raise ParseError(f'Unknown version name in tag: {tag}') from ex
 
 
 def split_tag(tag: Tag) -> Tuple[str, str]:
@@ -149,55 +199,52 @@
     return split_tag(tag)[1]
 
 
-def version_is_private(version: str) -> bool:
-    """Returns True if the version name should be treated as private."""
-    return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
+def _should_omit_tags(tags: Tags, arch: Arch, api: int, llndk: bool,
+                      apex: bool) -> bool:
+    """Returns True if the tagged object should be omitted.
+
+    This defines the rules shared between version tagging and symbol tagging.
+    """
+    # The apex and llndk tags will only exclude APIs from other modes. If in
+    # APEX or LLNDK mode and neither tag is provided, we fall back to the
+    # default behavior because all NDK symbols are implicitly available to APEX
+    # and LLNDK.
+    if tags.has_mode_tags:
+        if not apex and not llndk:
+            return True
+        if apex and not tags.has_apex_tags:
+            return True
+        if llndk and not tags.has_llndk_tags:
+            return True
+    if not symbol_in_arch(tags, arch):
+        return True
+    if not symbol_in_api(tags, arch, api):
+        return True
+    return False
 
 
 def should_omit_version(version: Version, arch: Arch, api: int, llndk: bool,
                         apex: bool) -> bool:
-    """Returns True if the version section should be ommitted.
+    """Returns True if the version section should be omitted.
 
     We want to omit any sections that do not have any symbols we'll have in the
     stub library. Sections that contain entirely future symbols or only symbols
     for certain architectures.
     """
-    if version_is_private(version.name):
+    if version.is_private:
         return True
-    if 'platform-only' in version.tags:
+    if version.tags.has_platform_only_tags:
         return True
-
-    no_llndk_no_apex = ('llndk' not in version.tags
-                        and 'apex' not in version.tags)
-    keep = no_llndk_no_apex or \
-           ('llndk' in version.tags and llndk) or \
-           ('apex' in version.tags and apex)
-    if not keep:
-        return True
-    if not symbol_in_arch(version.tags, arch):
-        return True
-    if not symbol_in_api(version.tags, arch, api):
-        return True
-    return False
+    return _should_omit_tags(version.tags, arch, api, llndk, apex)
 
 
 def should_omit_symbol(symbol: Symbol, arch: Arch, api: int, llndk: bool,
                        apex: bool) -> bool:
     """Returns True if the symbol should be omitted."""
-    no_llndk_no_apex = 'llndk' not in symbol.tags and 'apex' not in symbol.tags
-    keep = no_llndk_no_apex or \
-           ('llndk' in symbol.tags and llndk) or \
-           ('apex' in symbol.tags and apex)
-    if not keep:
-        return True
-    if not symbol_in_arch(symbol.tags, arch):
-        return True
-    if not symbol_in_api(symbol.tags, arch, api):
-        return True
-    return False
+    return _should_omit_tags(symbol.tags, arch, api, llndk, apex)
 
 
-def symbol_in_arch(tags: Iterable[Tag], arch: Arch) -> bool:
+def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
     """Returns true if the symbol is present for the given architecture."""
     has_arch_tags = False
     for tag in tags:
@@ -325,8 +372,7 @@
         """Parses a single version section and returns a Version object."""
         assert self.current_line is not None
         name = self.current_line.split('{')[0].strip()
-        tags = get_tags(self.current_line)
-        tags = decode_api_level_tags(tags, self.api_map)
+        tags = get_tags(self.current_line, self.api_map)
         symbols: List[Symbol] = []
         global_scope = True
         cpp_symbols = False
@@ -373,8 +419,7 @@
                 'Wildcard global symbols are not permitted.')
         # Line is now in the format "<symbol-name>; # tags"
         name, _, _ = self.current_line.strip().partition(';')
-        tags = get_tags(self.current_line)
-        tags = decode_api_level_tags(tags, self.api_map)
+        tags = get_tags(self.current_line, self.api_map)
         return Symbol(name, tags)
 
     def next_line(self) -> str:
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index 92b1399..c1e8219 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -19,7 +19,7 @@
 import unittest
 
 import symbolfile
-from symbolfile import Arch, Tag
+from symbolfile import Arch, Tag, Tags, Version
 
 # pylint: disable=missing-docstring
 
@@ -35,12 +35,14 @@
 
 class TagsTest(unittest.TestCase):
     def test_get_tags_no_tags(self) -> None:
-        self.assertEqual([], symbolfile.get_tags(''))
-        self.assertEqual([], symbolfile.get_tags('foo bar baz'))
+        self.assertEqual(Tags(), symbolfile.get_tags('', {}))
+        self.assertEqual(Tags(), symbolfile.get_tags('foo bar baz', {}))
 
     def test_get_tags(self) -> None:
-        self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar'))
-        self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz'))
+        self.assertEqual(Tags.from_strs(['foo', 'bar']),
+                         symbolfile.get_tags('# foo bar', {}))
+        self.assertEqual(Tags.from_strs(['bar', 'baz']),
+                         symbolfile.get_tags('foo # bar baz', {}))
 
     def test_split_tag(self) -> None:
         self.assertTupleEqual(('foo', 'bar'),
@@ -77,12 +79,14 @@
         }
 
         tags = [
-            Tag('introduced=9'),
-            Tag('introduced-arm=14'),
-            Tag('versioned=16'),
-            Tag('arm'),
-            Tag('introduced=O'),
-            Tag('introduced=P'),
+            symbolfile.decode_api_level_tag(t, api_map) for t in (
+                Tag('introduced=9'),
+                Tag('introduced-arm=14'),
+                Tag('versioned=16'),
+                Tag('arm'),
+                Tag('introduced=O'),
+                Tag('introduced=P'),
+            )
         ]
         expected_tags = [
             Tag('introduced=9'),
@@ -92,33 +96,37 @@
             Tag('introduced=9000'),
             Tag('introduced=9001'),
         ]
-        self.assertListEqual(
-            expected_tags, symbolfile.decode_api_level_tags(tags, api_map))
+        self.assertListEqual(expected_tags, tags)
 
         with self.assertRaises(symbolfile.ParseError):
-            symbolfile.decode_api_level_tags([Tag('introduced=O')], {})
+            symbolfile.decode_api_level_tag(Tag('introduced=O'), {})
 
 
 class PrivateVersionTest(unittest.TestCase):
     def test_version_is_private(self) -> None:
-        self.assertFalse(symbolfile.version_is_private('foo'))
-        self.assertFalse(symbolfile.version_is_private('PRIVATE'))
-        self.assertFalse(symbolfile.version_is_private('PLATFORM'))
-        self.assertFalse(symbolfile.version_is_private('foo_private'))
-        self.assertFalse(symbolfile.version_is_private('foo_platform'))
-        self.assertFalse(symbolfile.version_is_private('foo_PRIVATE_'))
-        self.assertFalse(symbolfile.version_is_private('foo_PLATFORM_'))
+        def mock_version(name: str) -> Version:
+            return Version(name, base=None, tags=Tags(), symbols=[])
 
-        self.assertTrue(symbolfile.version_is_private('foo_PRIVATE'))
-        self.assertTrue(symbolfile.version_is_private('foo_PLATFORM'))
+        self.assertFalse(mock_version('foo').is_private)
+        self.assertFalse(mock_version('PRIVATE').is_private)
+        self.assertFalse(mock_version('PLATFORM').is_private)
+        self.assertFalse(mock_version('foo_private').is_private)
+        self.assertFalse(mock_version('foo_platform').is_private)
+        self.assertFalse(mock_version('foo_PRIVATE_').is_private)
+        self.assertFalse(mock_version('foo_PLATFORM_').is_private)
+
+        self.assertTrue(mock_version('foo_PRIVATE').is_private)
+        self.assertTrue(mock_version('foo_PLATFORM').is_private)
 
 
 class SymbolPresenceTest(unittest.TestCase):
     def test_symbol_in_arch(self) -> None:
-        self.assertTrue(symbolfile.symbol_in_arch([], Arch('arm')))
-        self.assertTrue(symbolfile.symbol_in_arch([Tag('arm')], Arch('arm')))
+        self.assertTrue(symbolfile.symbol_in_arch(Tags(), Arch('arm')))
+        self.assertTrue(
+            symbolfile.symbol_in_arch(Tags.from_strs(['arm']), Arch('arm')))
 
-        self.assertFalse(symbolfile.symbol_in_arch([Tag('x86')], Arch('arm')))
+        self.assertFalse(
+            symbolfile.symbol_in_arch(Tags.from_strs(['x86']), Arch('arm')))
 
     def test_symbol_in_api(self) -> None:
         self.assertTrue(symbolfile.symbol_in_api([], Arch('arm'), 9))
@@ -197,81 +205,99 @@
     def test_omit_private(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PRIVATE', None, [], []), Arch('arm'),
-                9, False, False))
+                symbolfile.Version('foo_PRIVATE', None, Tags(), []),
+                Arch('arm'), 9, False, False))
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo_PLATFORM', None, [], []), Arch('arm'),
-                9, False, False))
+                symbolfile.Version('foo_PLATFORM', None, Tags(), []),
+                Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('platform-only')], []),
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['platform-only']), []),
                 Arch('arm'), 9, False, False))
 
     def test_omit_llndk(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, True,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                True, False))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('llndk')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['llndk']), []),
                 Arch('arm'), 9, True, False))
 
     def test_omit_apex(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('apex')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                True))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, True))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('apex')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['apex']), []),
                 Arch('arm'), 9, False, True))
 
+    def test_omit_systemapi(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
+                                   []), Arch('arm'), 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, True))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags.from_strs(['systemapi']),
+                                   []), Arch('arm'), 9, False, True))
+
     def test_omit_arch(self) -> None:
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
         self.assertFalse(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('arm')], []), Arch('arm'),
-                9, False, False))
-
-        self.assertTrue(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('x86')], []), Arch('arm'),
-                9, False, False))
-
-    def test_omit_api(self) -> None:
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [], []), Arch('arm'), 9, False,
-                False))
-        self.assertFalse(
-            symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('introduced=9')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['arm']), []),
                 Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_version(
-                symbolfile.Version('foo', None, [Tag('introduced=14')], []),
+                symbolfile.Version('foo', None, Tags.from_strs(['x86']), []),
+                Arch('arm'), 9, False, False))
+
+    def test_omit_api(self) -> None:
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None, Tags(), []), Arch('arm'), 9,
+                False, False))
+        self.assertFalse(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['introduced=9']), []),
+                Arch('arm'), 9, False, False))
+
+        self.assertTrue(
+            symbolfile.should_omit_version(
+                symbolfile.Version('foo', None,
+                                   Tags.from_strs(['introduced=14']), []),
                 Arch('arm'), 9, False, False))
 
 
@@ -279,58 +305,72 @@
     def test_omit_llndk(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9,
-                False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
+                Arch('arm'), 9, False, False))
 
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, True, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('llndk')]), Arch('arm'), 9, True,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['llndk'])),
+                Arch('arm'), 9, True, False))
 
     def test_omit_apex(self) -> None:
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
+                Arch('arm'), 9, False, False))
 
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, True))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('apex')]), Arch('arm'), 9, False,
-                True))
+                symbolfile.Symbol('foo', Tags.from_strs(['apex'])),
+                Arch('arm'), 9, False, True))
+
+    def test_omit_systemapi(self) -> None:
+        self.assertTrue(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
+                Arch('arm'), 9, False, False))
+
+        self.assertFalse(
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
+                                          Arch('arm'), 9, False, True))
+        self.assertFalse(
+            symbolfile.should_omit_symbol(
+                symbolfile.Symbol('foo', Tags.from_strs(['systemapi'])),
+                Arch('arm'), 9, False, True))
 
     def test_omit_arch(self) -> None:
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('arm')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['arm'])), Arch('arm'),
+                9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('x86')]), Arch('arm'), 9, False,
-                False))
+                symbolfile.Symbol('foo', Tags.from_strs(['x86'])), Arch('arm'),
+                9, False, False))
 
     def test_omit_api(self) -> None:
         self.assertFalse(
-            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []),
+            symbolfile.should_omit_symbol(symbolfile.Symbol('foo', Tags()),
                                           Arch('arm'), 9, False, False))
         self.assertFalse(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('introduced=9')]), Arch('arm'),
-                9, False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['introduced=9'])),
+                Arch('arm'), 9, False, False))
 
         self.assertTrue(
             symbolfile.should_omit_symbol(
-                symbolfile.Symbol('foo', [Tag('introduced=14')]), Arch('arm'),
-                9, False, False))
+                symbolfile.Symbol('foo', Tags.from_strs(['introduced=14'])),
+                Arch('arm'), 9, False, False))
 
 
 class SymbolFileParseTest(unittest.TestCase):
@@ -376,11 +416,11 @@
         version = parser.parse_version()
         self.assertEqual('VERSION_1', version.name)
         self.assertIsNone(version.base)
-        self.assertEqual(['foo', 'bar'], version.tags)
+        self.assertEqual(Tags.from_strs(['foo', 'bar']), version.tags)
 
         expected_symbols = [
-            symbolfile.Symbol('baz', []),
-            symbolfile.Symbol('qux', [Tag('woodly'), Tag('doodly')]),
+            symbolfile.Symbol('baz', Tags()),
+            symbolfile.Symbol('qux', Tags.from_strs(['woodly', 'doodly'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -388,7 +428,7 @@
         version = parser.parse_version()
         self.assertEqual('VERSION_2', version.name)
         self.assertEqual('VERSION_1', version.base)
-        self.assertEqual([], version.tags)
+        self.assertEqual(Tags(), version.tags)
 
     def test_parse_version_eof(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
@@ -423,12 +463,12 @@
         parser.next_line()
         symbol = parser.parse_symbol()
         self.assertEqual('foo', symbol.name)
-        self.assertEqual([], symbol.tags)
+        self.assertEqual(Tags(), symbol.tags)
 
         parser.next_line()
         symbol = parser.parse_symbol()
         self.assertEqual('bar', symbol.name)
-        self.assertEqual(['baz', 'qux'], symbol.tags)
+        self.assertEqual(Tags.from_strs(['baz', 'qux']), symbol.tags)
 
     def test_wildcard_symbol_global(self) -> None:
         input_file = io.StringIO(textwrap.dedent("""\
@@ -497,14 +537,15 @@
         versions = parser.parse()
 
         expected = [
-            symbolfile.Version('VERSION_1', None, [], [
-                symbolfile.Symbol('foo', []),
-                symbolfile.Symbol('bar', [Tag('baz')]),
+            symbolfile.Version('VERSION_1', None, Tags(), [
+                symbolfile.Symbol('foo', Tags()),
+                symbolfile.Symbol('bar', Tags.from_strs(['baz'])),
             ]),
-            symbolfile.Version('VERSION_2', 'VERSION_1', [Tag('wasd')], [
-                symbolfile.Symbol('woodly', []),
-                symbolfile.Symbol('doodly', [Tag('asdf')]),
-            ]),
+            symbolfile.Version(
+                'VERSION_2', 'VERSION_1', Tags.from_strs(['wasd']), [
+                    symbolfile.Symbol('woodly', Tags()),
+                    symbolfile.Symbol('doodly', Tags.from_strs(['asdf'])),
+                ]),
         ]
 
         self.assertEqual(expected, versions)
@@ -527,10 +568,10 @@
         self.assertIsNone(version.base)
 
         expected_symbols = [
-            symbolfile.Symbol('foo', []),
-            symbolfile.Symbol('bar', [Tag('llndk')]),
-            symbolfile.Symbol('baz', [Tag('llndk'), Tag('apex')]),
-            symbolfile.Symbol('qux', [Tag('apex')]),
+            symbolfile.Symbol('foo', Tags()),
+            symbolfile.Symbol('bar', Tags.from_strs(['llndk'])),
+            symbolfile.Symbol('baz', Tags.from_strs(['llndk', 'apex'])),
+            symbolfile.Symbol('qux', Tags.from_strs(['apex'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
diff --git a/cc/test.go b/cc/test.go
index d4c23d7..047a69e 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -48,12 +48,19 @@
 	Unit_test *bool
 
 	// Add ShippingApiLevelModuleController to auto generated test config. If the device properties
-	// for the shipping api level is less than the test_min_api_level, skip this module.
-	Test_min_api_level *int64
+	// for the shipping api level is less than the min_shipping_api_level, skip this module.
+	Min_shipping_api_level *int64
+
+	// Add ShippingApiLevelModuleController to auto generated test config. If any of the device
+	// shipping api level and vendor api level properties are less than the
+	// vsr_min_shipping_api_level, skip this module.
+	// As this includes the shipping api level check, it is not allowed to define
+	// min_shipping_api_level at the same time with this property.
+	Vsr_min_shipping_api_level *int64
 
 	// Add MinApiLevelModuleController with ro.vndk.version property. If ro.vndk.version has an
-	// integer value and the value is less than the test_min_vndk_version, skip this module.
-	Test_min_vndk_version *int64
+	// integer value and the value is less than the min_vndk_version, skip this module.
+	Min_vndk_version *int64
 }
 
 type TestBinaryProperties struct {
@@ -97,7 +104,7 @@
 
 	// Add ShippingApiLevelModuleController to auto generated test config. If the device properties
 	// for the shipping api level is less than the test_min_api_level, skip this module.
-	// Deprecated (b/187258404). Use test_options.test_min_api_level instead.
+	// Deprecated (b/187258404). Use test_options.min_shipping_api_level instead.
 	Test_min_api_level *int64
 
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
@@ -404,19 +411,30 @@
 	for _, tag := range test.Properties.Test_options.Test_suite_tag {
 		configs = append(configs, tradefed.Option{Name: "test-suite-tag", Value: tag})
 	}
-	if test.Properties.Test_options.Test_min_api_level != nil {
+	if test.Properties.Test_options.Min_shipping_api_level != nil {
+		if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
+			ctx.PropertyErrorf("test_options.min_shipping_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
+		}
 		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Test_min_api_level), 10)})
+		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_shipping_api_level), 10)})
 		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
 	} else if test.Properties.Test_min_api_level != nil {
 		// TODO: (b/187258404) Remove test.Properties.Test_min_api_level
+		if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
+			ctx.PropertyErrorf("test_min_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
+		}
 		var options []tradefed.Option
 		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_min_api_level), 10)})
 		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
 	}
-	if test.Properties.Test_options.Test_min_vndk_version != nil {
+	if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
 		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Test_min_vndk_version), 10)})
+		options = append(options, tradefed.Option{Name: "vsr-min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Vsr_min_shipping_api_level), 10)})
+		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
+	}
+	if test.Properties.Test_options.Min_vndk_version != nil {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_vndk_version), 10)})
 		options = append(options, tradefed.Option{Name: "api-level-prop", Value: "ro.vndk.version"})
 		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.MinApiLevelModuleController", options})
 	}
diff --git a/cc/testing.go b/cc/testing.go
index 15f7ebb..80cc0ef 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -15,6 +15,9 @@
 package cc
 
 import (
+	"path/filepath"
+	"testing"
+
 	"android/soong/android"
 	"android/soong/genrule"
 )
@@ -31,6 +34,7 @@
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
 	ctx.RegisterModuleType("cc_genrule", genRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
+	ctx.RegisterModuleType("ndk_prebuilt_static_stl", NdkPrebuiltStaticStlFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_object", NdkPrebuiltObjectFactory)
 	ctx.RegisterModuleType("ndk_library", NdkLibraryFactory)
 }
@@ -400,7 +404,7 @@
 
 		cc_library {
 			name: "ndk_libunwind",
-			sdk_version: "current",
+			sdk_version: "minimum",
 			stl: "none",
 			system_shared_libs: [],
 		}
@@ -425,6 +429,12 @@
 
 		ndk_prebuilt_shared_stl {
 			name: "ndk_libc++_shared",
+			export_include_dirs: ["ndk_libc++_shared"],
+		}
+
+		ndk_prebuilt_static_stl {
+			name: "ndk_libandroid_support",
+			export_include_dirs: ["ndk_libandroid_support"],
 		}
 
 		cc_library_static {
@@ -487,7 +497,7 @@
 				}
 
 				cc_genrule {
-					name: "host_bionic_linker_flags",
+					name: "host_bionic_linker_script",
 					host_supported: true,
 					device_supported: false,
 					target: {
@@ -498,7 +508,7 @@
 							enabled: true,
 						},
 					},
-					out: ["linker.flags"],
+					out: ["linker.script"],
 				}
 
 				cc_defaults {
@@ -575,9 +585,11 @@
 
 	// Additional files needed in tests that disallow non-existent source.
 	android.MockFS{
-		"defaults/cc/common/libc.map.txt":  nil,
-		"defaults/cc/common/libdl.map.txt": nil,
-		"defaults/cc/common/libm.map.txt":  nil,
+		"defaults/cc/common/libc.map.txt":           nil,
+		"defaults/cc/common/libdl.map.txt":          nil,
+		"defaults/cc/common/libm.map.txt":           nil,
+		"defaults/cc/common/ndk_libandroid_support": nil,
+		"defaults/cc/common/ndk_libc++_shared":      nil,
 	}.AddToFixture(),
 
 	// Place the default cc test modules that are common to all platforms in a location that will not
@@ -625,7 +637,7 @@
 var PrepareForTestWithCcIncludeVndk = android.GroupFixturePreparers(
 	PrepareForIntegrationTestWithCc,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-		vendorSnapshotImageSingleton.init(ctx)
+		VendorSnapshotImageSingleton.Init(ctx)
 		recoverySnapshotImageSingleton.init(ctx)
 		ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	}),
@@ -674,7 +686,7 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
 
-	vendorSnapshotImageSingleton.init(ctx)
+	VendorSnapshotImageSingleton.Init(ctx)
 	recoverySnapshotImageSingleton.init(ctx)
 	ctx.RegisterSingletonType("vndk-snapshot", VndkSnapshotSingleton)
 	RegisterVndkLibraryTxtTypes(ctx)
@@ -685,3 +697,64 @@
 
 	return ctx
 }
+
+func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
+	t.Helper()
+	mod := ctx.ModuleForTests(moduleName, variant)
+	outputFiles := mod.OutputFiles(t, "")
+	if len(outputFiles) != 1 {
+		t.Errorf("%q must have single output\n", moduleName)
+		return
+	}
+	snapshotPath := filepath.Join(subDir, snapshotFilename)
+
+	if include {
+		out := singleton.Output(snapshotPath)
+		if fake {
+			if out.Rule == nil {
+				t.Errorf("Missing rule for module %q output file %q", moduleName, outputFiles[0])
+			}
+		} else {
+			if out.Input.String() != outputFiles[0].String() {
+				t.Errorf("The input of snapshot %q must be %q, but %q", moduleName, out.Input.String(), outputFiles[0])
+			}
+		}
+	} else {
+		out := singleton.MaybeOutput(snapshotPath)
+		if out.Rule != nil {
+			t.Errorf("There must be no rule for module %q output file %q", moduleName, outputFiles[0])
+		}
+	}
+}
+
+func CheckSnapshot(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, false)
+}
+
+func CheckSnapshotExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, false, false)
+}
+
+func CheckSnapshotRule(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
+	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
+}
+
+func AssertExcludeFromVendorSnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool, variant string) {
+	t.Helper()
+	m := ctx.ModuleForTests(name, variant).Module().(LinkableInterface)
+	if m.ExcludeFromVendorSnapshot() != expected {
+		t.Errorf("expected %q ExcludeFromVendorSnapshot to be %t", m.String(), expected)
+	}
+}
+
+func GetOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
+	for _, moduleName := range moduleNames {
+		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
+		output := module.outputFile.Path().RelativeToTop()
+		paths = append(paths, output)
+	}
+	return paths
+}
diff --git a/cc/vendor_public_library_test.go b/cc/vendor_public_library_test.go
index 01959b4..769be09 100644
--- a/cc/vendor_public_library_test.go
+++ b/cc/vendor_public_library_test.go
@@ -79,7 +79,7 @@
 	// test if libsystem is linked to the stub
 	ld := ctx.ModuleForTests("libsystem", coreVariant).Rule("ld")
 	libflags := ld.Args["libFlags"]
-	stubPaths := getOutputPaths(ctx, coreVariant, []string{"libvendorpublic"})
+	stubPaths := GetOutputPaths(ctx, coreVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libsystem must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
@@ -87,7 +87,7 @@
 	// test if libsystem is linked to the stub
 	ld = ctx.ModuleForTests("libproduct", productVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = getOutputPaths(ctx, productVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(ctx, productVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libproduct must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
@@ -95,7 +95,8 @@
 	// test if libvendor is linked to the real shared lib
 	ld = ctx.ModuleForTests("libvendor", vendorVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = getOutputPaths(ctx, vendorVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(ctx, vendorVariant, []string{"libvendorpublic"})
+
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libvendor must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 9ad51ad..5a303c8 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -31,7 +31,7 @@
 	"SOONG_VENDOR_SNAPSHOT_ZIP",
 	android.OptionalPath{},
 	true,
-	vendorSnapshotImageSingleton,
+	VendorSnapshotImageSingleton,
 	false, /* fake */
 }
 
@@ -40,7 +40,7 @@
 	"SOONG_VENDOR_FAKE_SNAPSHOT_ZIP",
 	android.OptionalPath{},
 	true,
-	vendorSnapshotImageSingleton,
+	VendorSnapshotImageSingleton,
 	true, /* fake */
 }
 
@@ -82,7 +82,7 @@
 	// Implementation of the image interface specific to the image
 	// associated with this snapshot (e.g., specific to the vendor image,
 	// recovery image, etc.).
-	image snapshotImage
+	image SnapshotImage
 
 	// Whether this singleton is for fake snapshot or not.
 	// Fake snapshot is a snapshot whose prebuilt binaries and headers are empty.
@@ -104,7 +104,7 @@
 	return RecoverySnapshotSingleton().(*snapshotSingleton).image.isProprietaryPath(dir, deviceConfig)
 }
 
-func isVendorProprietaryModule(ctx android.BaseModuleContext) bool {
+func IsVendorProprietaryModule(ctx android.BaseModuleContext) bool {
 	// Any module in a vendor proprietary path is a vendor proprietary
 	// module.
 	if isVendorProprietaryPath(ctx.ModuleDir(), ctx.DeviceConfig()) {
@@ -115,7 +115,7 @@
 	// still be a vendor proprietary module. This happens for cc modules
 	// that are excluded from the vendor snapshot, and it means that the
 	// vendor has assumed control of the framework-provided module.
-	if c, ok := ctx.Module().(*Module); ok {
+	if c, ok := ctx.Module().(LinkableInterface); ok {
 		if c.ExcludeFromVendorSnapshot() {
 			return true
 		}
@@ -137,7 +137,7 @@
 	// that are excluded from the recovery snapshot, and it means that the
 	// vendor has assumed control of the framework-provided module.
 
-	if c, ok := ctx.Module().(*Module); ok {
+	if c, ok := ctx.Module().(LinkableInterface); ok {
 		if c.ExcludeFromRecoverySnapshot() {
 			return true
 		}
@@ -147,8 +147,8 @@
 }
 
 // Determines if the module is a candidate for snapshot.
-func isSnapshotAware(cfg android.DeviceConfig, m *Module, inProprietaryPath bool, apexInfo android.ApexInfo, image snapshotImage) bool {
-	if !m.Enabled() || m.Properties.HideFromMake {
+func isSnapshotAware(cfg android.DeviceConfig, m LinkableInterface, inProprietaryPath bool, apexInfo android.ApexInfo, image SnapshotImage) bool {
+	if !m.Enabled() || m.HiddenFromMake() {
 		return false
 	}
 	// When android/prebuilt.go selects between source and prebuilt, it sets
@@ -177,51 +177,51 @@
 		return false
 	}
 	// skip kernel_headers which always depend on vendor
-	if _, ok := m.linker.(*kernelHeadersDecorator); ok {
+	if m.KernelHeadersDecorator() {
 		return false
 	}
-	// skip LLNDK libraries which are backward compatible
+
 	if m.IsLlndk() {
 		return false
 	}
 
 	// Libraries
-	if l, ok := m.linker.(snapshotLibraryInterface); ok {
-		if m.sanitize != nil {
+	if sanitizable, ok := m.(PlatformSanitizeable); ok && sanitizable.IsSnapshotLibrary() {
+		if sanitizable.SanitizePropDefined() {
 			// scs and hwasan export both sanitized and unsanitized variants for static and header
 			// Always use unsanitized variants of them.
 			for _, t := range []SanitizerType{scs, Hwasan} {
-				if !l.shared() && m.sanitize.isSanitizerEnabled(t) {
+				if !sanitizable.Shared() && sanitizable.IsSanitizerEnabled(t) {
 					return false
 				}
 			}
 			// cfi also exports both variants. But for static, we capture both.
 			// This is because cfi static libraries can't be linked from non-cfi modules,
 			// and vice versa. This isn't the case for scs and hwasan sanitizers.
-			if !l.static() && !l.shared() && m.sanitize.isSanitizerEnabled(cfi) {
+			if !sanitizable.Static() && !sanitizable.Shared() && sanitizable.IsSanitizerEnabled(cfi) {
 				return false
 			}
 		}
-		if l.static() {
-			return m.outputFile.Valid() && !image.private(m)
+		if sanitizable.Static() {
+			return sanitizable.OutputFile().Valid() && !image.private(m)
 		}
-		if l.shared() {
-			if !m.outputFile.Valid() {
+		if sanitizable.Shared() || sanitizable.Rlib() {
+			if !sanitizable.OutputFile().Valid() {
 				return false
 			}
 			if image.includeVndk() {
-				if !m.IsVndk() {
+				if !sanitizable.IsVndk() {
 					return true
 				}
-				return m.IsVndkExt()
+				return sanitizable.IsVndkExt()
 			}
 		}
 		return true
 	}
 
 	// Binaries and Objects
-	if m.binary() || m.object() {
-		return m.outputFile.Valid()
+	if m.Binary() || m.Object() {
+		return m.OutputFile().Valid()
 	}
 
 	return false
@@ -323,7 +323,7 @@
 
 	// installSnapshot function copies prebuilt file (.so, .a, or executable) and json flag file.
 	// For executables, init_rc and vintf_fragments files are also copied.
-	installSnapshot := func(m *Module, fake bool) android.Paths {
+	installSnapshot := func(m LinkableInterface, fake bool) android.Paths {
 		targetArch := "arch-" + m.Target().Arch.ArchType.String()
 		if m.Target().Arch.ArchVariant != "" {
 			targetArch += "-" + m.Target().Arch.ArchVariant
@@ -337,7 +337,7 @@
 		prop.ModuleName = ctx.ModuleName(m)
 		if c.supportsVndkExt && m.IsVndkExt() {
 			// vndk exts are installed to /vendor/lib(64)?/vndk(-sp)?
-			if m.isVndkSp() {
+			if m.IsVndkSp() {
 				prop.RelativeInstallPath = "vndk-sp"
 			} else {
 				prop.RelativeInstallPath = "vndk"
@@ -345,7 +345,7 @@
 		} else {
 			prop.RelativeInstallPath = m.RelativeInstallPath()
 		}
-		prop.RuntimeLibs = m.Properties.SnapshotRuntimeLibs
+		prop.RuntimeLibs = m.SnapshotRuntimeLibs()
 		prop.Required = m.RequiredModuleNames()
 		for _, path := range m.InitRc() {
 			prop.InitRc = append(prop.InitRc, filepath.Join("configs", path.Base()))
@@ -365,8 +365,8 @@
 
 		var propOut string
 
-		if l, ok := m.linker.(snapshotLibraryInterface); ok {
-			exporterInfo := ctx.ModuleProvider(m, FlagExporterInfoProvider).(FlagExporterInfo)
+		if m.IsSnapshotLibrary() {
+			exporterInfo := ctx.ModuleProvider(m.Module(), FlagExporterInfoProvider).(FlagExporterInfo)
 
 			// library flags
 			prop.ExportedFlags = exporterInfo.Flags
@@ -376,20 +376,25 @@
 			for _, dir := range exporterInfo.SystemIncludeDirs {
 				prop.ExportedSystemDirs = append(prop.ExportedSystemDirs, filepath.Join("include", dir.String()))
 			}
+
 			// shared libs dependencies aren't meaningful on static or header libs
-			if l.shared() {
-				prop.SharedLibs = m.Properties.SnapshotSharedLibs
+			if m.Shared() {
+				prop.SharedLibs = m.SnapshotSharedLibs()
 			}
-			if l.static() && m.sanitize != nil {
-				prop.SanitizeMinimalDep = m.sanitize.Properties.MinimalRuntimeDep || enableMinimalRuntime(m.sanitize)
-				prop.SanitizeUbsanDep = m.sanitize.Properties.UbsanRuntimeDep || enableUbsanRuntime(m.sanitize)
+			if sanitizable, ok := m.(PlatformSanitizeable); ok {
+				if sanitizable.Static() && sanitizable.SanitizePropDefined() {
+					prop.SanitizeMinimalDep = sanitizable.MinimalRuntimeDep() || sanitizable.MinimalRuntimeNeeded()
+					prop.SanitizeUbsanDep = sanitizable.UbsanRuntimeDep() || sanitizable.UbsanRuntimeNeeded()
+				}
 			}
 
 			var libType string
-			if l.static() {
+			if m.Static() {
 				libType = "static"
-			} else if l.shared() {
+			} else if m.Shared() {
 				libType = "shared"
+			} else if m.Rlib() {
+				libType = "rlib"
 			} else {
 				libType = "header"
 			}
@@ -398,16 +403,18 @@
 
 			// install .a or .so
 			if libType != "header" {
-				libPath := m.outputFile.Path()
+				libPath := m.OutputFile().Path()
 				stem = libPath.Base()
-				if l.static() && m.sanitize != nil && m.sanitize.isSanitizerEnabled(cfi) {
-					// both cfi and non-cfi variant for static libraries can exist.
-					// attach .cfi to distinguish between cfi and non-cfi.
-					// e.g. libbase.a -> libbase.cfi.a
-					ext := filepath.Ext(stem)
-					stem = strings.TrimSuffix(stem, ext) + ".cfi" + ext
-					prop.Sanitize = "cfi"
-					prop.ModuleName += ".cfi"
+				if sanitizable, ok := m.(PlatformSanitizeable); ok {
+					if (sanitizable.Static() || sanitizable.Rlib()) && sanitizable.SanitizePropDefined() && sanitizable.IsSanitizerEnabled(cfi) {
+						// both cfi and non-cfi variant for static libraries can exist.
+						// attach .cfi to distinguish between cfi and non-cfi.
+						// e.g. libbase.a -> libbase.cfi.a
+						ext := filepath.Ext(stem)
+						stem = strings.TrimSuffix(stem, ext) + ".cfi" + ext
+						prop.Sanitize = "cfi"
+						prop.ModuleName += ".cfi"
+					}
 				}
 				snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, libType, stem)
 				ret = append(ret, copyFile(ctx, libPath, snapshotLibOut, fake))
@@ -416,20 +423,20 @@
 			}
 
 			propOut = filepath.Join(snapshotArchDir, targetArch, libType, stem+".json")
-		} else if m.binary() {
+		} else if m.Binary() {
 			// binary flags
 			prop.Symlinks = m.Symlinks()
-			prop.SharedLibs = m.Properties.SnapshotSharedLibs
+			prop.SharedLibs = m.SnapshotSharedLibs()
 
 			// install bin
-			binPath := m.outputFile.Path()
+			binPath := m.OutputFile().Path()
 			snapshotBinOut := filepath.Join(snapshotArchDir, targetArch, "binary", binPath.Base())
 			ret = append(ret, copyFile(ctx, binPath, snapshotBinOut, fake))
 			propOut = snapshotBinOut + ".json"
-		} else if m.object() {
+		} else if m.Object() {
 			// object files aren't installed to the device, so their names can conflict.
 			// Use module name as stem.
-			objPath := m.outputFile.Path()
+			objPath := m.OutputFile().Path()
 			snapshotObjOut := filepath.Join(snapshotArchDir, targetArch, "object",
 				ctx.ModuleName(m)+filepath.Ext(objPath.Base()))
 			ret = append(ret, copyFile(ctx, objPath, snapshotObjOut, fake))
@@ -450,7 +457,7 @@
 	}
 
 	ctx.VisitAllModules(func(module android.Module) {
-		m, ok := module.(*Module)
+		m, ok := module.(LinkableInterface)
 		if !ok {
 			return
 		}
@@ -484,8 +491,8 @@
 		// installSnapshot installs prebuilts and json flag files
 		snapshotOutputs = append(snapshotOutputs, installSnapshot(m, installAsFake)...)
 		// just gather headers and notice files here, because they are to be deduplicated
-		if l, ok := m.linker.(snapshotLibraryInterface); ok {
-			headers = append(headers, l.snapshotHeaders()...)
+		if m.IsSnapshotLibrary() {
+			headers = append(headers, m.SnapshotHeaders()...)
 		}
 
 		if len(m.NoticeFiles()) > 0 {
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index fddd72a..ceffb29 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -108,27 +108,27 @@
 		// For shared libraries, only non-VNDK vendor_available modules are captured
 		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(sharedDir, "libvendor.so.json"),
 			filepath.Join(sharedDir, "libvendor_available.so.json"))
 
 		// LLNDK modules are not captured
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libllndk", "libllndk.so", sharedDir, sharedVariant)
 
 		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
 		// Also cfi variants are captured, except for prebuilts like toolchain_library
 		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
 		staticCfiVariant := fmt.Sprintf("android_vendor.29_%s_%s_static_cfi", archType, archVariant)
 		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
-		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.cfi.a", staticDir, staticCfiVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.cfi.a", staticDir, staticCfiVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.cfi.a", staticDir, staticCfiVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.cfi.a", staticDir, staticCfiVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(staticDir, "libb.a.json"),
 			filepath.Join(staticDir, "libvndk.a.json"),
@@ -142,8 +142,8 @@
 		if archType == "arm64" {
 			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
-			checkSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
-			checkSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
 			jsonFiles = append(jsonFiles,
 				filepath.Join(binaryDir, "vendor_bin.json"),
 				filepath.Join(binaryDir, "vendor_available_bin.json"))
@@ -156,7 +156,7 @@
 		// For object modules, all vendor:true and vendor_available modules are captured.
 		objectVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
-		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
 		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
 	}
 
@@ -239,15 +239,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libvendor.so.json"))
 		// Check that snapshot captures "prefer: true" prebuilt
-		checkSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo.so.json"))
 
 		// Excluded modules. Modules not included in the directed vendor snapshot
 		// are still include as fake modules.
-		checkSnapshotRule(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
+		CheckSnapshotRule(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libvendor_available.so.json"))
 	}
 
@@ -309,6 +309,13 @@
 		compile_multilib: "64",
 	}
 
+	cc_library {
+		name: "libllndk",
+		llndk: {
+			symbol_file: "libllndk.map.txt",
+		},
+	}
+
 	cc_binary {
 		name: "bin",
 		vendor: true,
@@ -332,7 +339,7 @@
 	vndkBp := `
 	vndk_prebuilt_shared {
 		name: "libvndk",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		vendor_available: true,
 		product_available: true,
@@ -376,7 +383,7 @@
 	// different arch snapshot which has to be ignored
 	vndk_prebuilt_shared {
 		name: "libvndk",
-		version: "30",
+		version: "31",
 		target_arch: "arm",
 		vendor_available: true,
 		product_available: true,
@@ -390,6 +397,22 @@
 			},
 		},
 	}
+
+	vndk_prebuilt_shared {
+		name: "libllndk",
+		version: "31",
+		target_arch: "arm64",
+		vendor_available: true,
+		product_available: true,
+		arch: {
+			arm64: {
+				srcs: ["libllndk.so"],
+			},
+			arm: {
+				srcs: ["libllndk.so"],
+			},
+		},
+	}
 `
 
 	vendorProprietaryBp := `
@@ -409,7 +432,7 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		shared_libs: ["libvndk", "libvendor_available"],
+		shared_libs: ["libvndk", "libvendor_available", "libllndk"],
 		static_libs: ["libvendor", "libvendor_without_snapshot"],
 		arch: {
 			arm64: {
@@ -449,16 +472,17 @@
 
 	vendor_snapshot {
 		name: "vendor_snapshot",
-		version: "30",
+		version: "31",
 		arch: {
 			arm64: {
 				vndk_libs: [
 					"libvndk",
+					"libllndk",
 				],
 				static_libs: [
 					"libc++_static",
 					"libc++demangle",
-					"libgcc_stripped",
+					"libunwind",
 					"libvendor",
 					"libvendor_available",
 					"libvndk",
@@ -476,6 +500,7 @@
 			arm: {
 				vndk_libs: [
 					"libvndk",
+					"libllndk",
 				],
 				static_libs: [
 					"libvendor",
@@ -497,7 +522,7 @@
 
 	vendor_snapshot_static {
 		name: "libvndk",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
@@ -515,7 +540,7 @@
 
 	vendor_snapshot_shared {
 		name: "libvendor",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
@@ -538,7 +563,7 @@
 
 	vendor_snapshot_static {
 		name: "lib32",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "32",
 		vendor: true,
@@ -551,7 +576,7 @@
 
 	vendor_snapshot_shared {
 		name: "lib32",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "32",
 		vendor: true,
@@ -564,7 +589,7 @@
 
 	vendor_snapshot_static {
 		name: "lib64",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
@@ -577,7 +602,7 @@
 
 	vendor_snapshot_shared {
 		name: "lib64",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
@@ -590,7 +615,7 @@
 
 	vendor_snapshot_static {
 		name: "libvendor",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
@@ -616,7 +641,7 @@
 
 	vendor_snapshot_shared {
 		name: "libvendor_available",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
@@ -634,7 +659,7 @@
 
 	vendor_snapshot_static {
 		name: "libvendor_available",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "both",
 		vendor: true,
@@ -652,7 +677,7 @@
 
 	vendor_snapshot_static {
 		name: "libc++_static",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
@@ -665,7 +690,7 @@
 
 	vendor_snapshot_static {
 		name: "libc++demangle",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
@@ -677,21 +702,21 @@
 	}
 
 	vendor_snapshot_static {
-		name: "libgcc_stripped",
-		version: "30",
+		name: "libunwind",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
 		arch: {
 			arm64: {
-				src: "libgcc_stripped.a",
+				src: "libunwind.a",
 			},
 		},
 	}
 
 	vendor_snapshot_binary {
 		name: "bin",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "64",
 		vendor: true,
@@ -704,7 +729,7 @@
 
 	vendor_snapshot_binary {
 		name: "bin32",
-		version: "30",
+		version: "31",
 		target_arch: "arm64",
 		compile_multilib: "32",
 		vendor: true,
@@ -732,7 +757,7 @@
 	// different arch snapshot which has to be ignored
 	vendor_snapshot_binary {
 		name: "bin",
-		version: "30",
+		version: "31",
 		target_arch: "arm",
 		compile_multilib: "first",
 		vendor: true,
@@ -759,7 +784,7 @@
 		"vendor/include/libvendor_cfi/c.h": nil,
 		"vendor/libc++_static.a":           nil,
 		"vendor/libc++demangle.a":          nil,
-		"vendor/libgcc_striped.a":          nil,
+		"vendor/libunwind.a":               nil,
 		"vendor/libvndk.a":                 nil,
 		"vendor/libvendor.a":               nil,
 		"vendor/libvendor.cfi.a":           nil,
@@ -771,11 +796,12 @@
 		"vndk/Android.bp":                  []byte(vndkBp),
 		"vndk/include/libvndk/a.h":         nil,
 		"vndk/libvndk.so":                  nil,
+		"vndk/libllndk.so":                 nil,
 	}
 
 	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
-	config.TestProductVariables.DeviceVndkVersion = StringPtr("30")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("31")
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("31")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("32")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -784,17 +810,17 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	android.FailIfErrored(t, errs)
 
-	sharedVariant := "android_vendor.30_arm64_armv8-a_shared"
-	staticVariant := "android_vendor.30_arm64_armv8-a_static"
-	binaryVariant := "android_vendor.30_arm64_armv8-a"
+	sharedVariant := "android_vendor.31_arm64_armv8-a_shared"
+	staticVariant := "android_vendor.31_arm64_armv8-a_static"
+	binaryVariant := "android_vendor.31_arm64_armv8-a"
 
-	sharedCfiVariant := "android_vendor.30_arm64_armv8-a_shared_cfi"
-	staticCfiVariant := "android_vendor.30_arm64_armv8-a_static_cfi"
+	sharedCfiVariant := "android_vendor.31_arm64_armv8-a_shared_cfi"
+	staticCfiVariant := "android_vendor.31_arm64_armv8-a_static_cfi"
 
-	shared32Variant := "android_vendor.30_arm_armv7-a-neon_shared"
-	binary32Variant := "android_vendor.30_arm_armv7-a-neon"
+	shared32Variant := "android_vendor.31_arm_armv7-a-neon_shared"
+	binary32Variant := "android_vendor.31_arm_armv7-a-neon"
 
-	// libclient uses libvndk.vndk.30.arm64, libvendor.vendor_static.30.arm64, libvendor_without_snapshot
+	// libclient uses libvndk.vndk.31.arm64, libvendor.vendor_static.31.arm64, libvendor_without_snapshot
 	libclientCcFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("cc").Args["cFlags"]
 	for _, includeFlags := range []string{
 		"-Ivndk/include/libvndk",     // libvndk
@@ -808,18 +834,20 @@
 
 	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("ld").Args["libFlags"]
 	for _, input := range [][]string{
-		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
-		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
+		[]string{sharedVariant, "libvndk.vndk.31.arm64"},
+		[]string{sharedVariant, "libllndk.vndk.31.arm64"},
+		[]string{staticVariant, "libvendor.vendor_static.31.arm64"},
 		[]string{staticVariant, "libvendor_without_snapshot"},
 	} {
-		outputPaths := getOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
+		outputPaths := GetOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
 		if !strings.Contains(libclientLdFlags, outputPaths[0].String()) {
 			t.Errorf("libflags for libclient must contain %#v, but was %#v", outputPaths[0], libclientLdFlags)
 		}
+
 	}
 
 	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
-	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64"}; !reflect.DeepEqual(g, w) {
+	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "libllndk.vendor", "lib64"}; !reflect.DeepEqual(g, w) {
 		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
 	}
 
@@ -829,11 +857,11 @@
 	}
 
 	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
-	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32"}; !reflect.DeepEqual(g, w) {
+	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "libllndk.vendor", "lib32"}; !reflect.DeepEqual(g, w) {
 		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
 	}
 
-	// libclient_cfi uses libvendor.vendor_static.30.arm64's cfi variant
+	// libclient_cfi uses libvendor.vendor_static.31.arm64's cfi variant
 	libclientCfiCcFlags := ctx.ModuleForTests("libclient_cfi", sharedCfiVariant).Rule("cc").Args["cFlags"]
 	if !strings.Contains(libclientCfiCcFlags, "-Ivendor/include/libvendor_cfi") {
 		t.Errorf("flags for libclient_cfi must contain %#v, but was %#v.",
@@ -841,12 +869,12 @@
 	}
 
 	libclientCfiLdFlags := ctx.ModuleForTests("libclient_cfi", sharedCfiVariant).Rule("ld").Args["libFlags"]
-	libvendorCfiOutputPaths := getOutputPaths(ctx, staticCfiVariant, []string{"libvendor.vendor_static.30.arm64"})
+	libvendorCfiOutputPaths := GetOutputPaths(ctx, staticCfiVariant, []string{"libvendor.vendor_static.31.arm64"})
 	if !strings.Contains(libclientCfiLdFlags, libvendorCfiOutputPaths[0].String()) {
 		t.Errorf("libflags for libclientCfi must contain %#v, but was %#v", libvendorCfiOutputPaths[0], libclientCfiLdFlags)
 	}
 
-	// bin_without_snapshot uses libvndk.vendor_static.30.arm64 (which reexports vndk's exported headers)
+	// bin_without_snapshot uses libvndk.vendor_static.31.arm64 (which reexports vndk's exported headers)
 	binWithoutSnapshotCcFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("cc").Args["cFlags"]
 	if !strings.Contains(binWithoutSnapshotCcFlags, "-Ivndk/include/libvndk") {
 		t.Errorf("flags for bin_without_snapshot must contain %#v, but was %#v.",
@@ -854,37 +882,37 @@
 	}
 
 	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("ld").Args["libFlags"]
-	libVndkStaticOutputPaths := getOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.30.arm64"})
+	libVndkStaticOutputPaths := GetOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.31.arm64"})
 	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
 		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
 			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
 	}
 
-	// libvendor.so is installed by libvendor.vendor_shared.30.arm64
-	ctx.ModuleForTests("libvendor.vendor_shared.30.arm64", sharedVariant).Output("libvendor.so")
+	// libvendor.so is installed by libvendor.vendor_shared.31.arm64
+	ctx.ModuleForTests("libvendor.vendor_shared.31.arm64", sharedVariant).Output("libvendor.so")
 
-	// lib64.so is installed by lib64.vendor_shared.30.arm64
-	ctx.ModuleForTests("lib64.vendor_shared.30.arm64", sharedVariant).Output("lib64.so")
+	// lib64.so is installed by lib64.vendor_shared.31.arm64
+	ctx.ModuleForTests("lib64.vendor_shared.31.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")
+	// lib32.so is installed by lib32.vendor_shared.31.arm64
+	ctx.ModuleForTests("lib32.vendor_shared.31.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_available.so is installed by libvendor_available.vendor_shared.31.arm64
+	ctx.ModuleForTests("libvendor_available.vendor_shared.31.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.30.arm64
-	ctx.ModuleForTests("bin.vendor_binary.30.arm64", binaryVariant).Output("bin")
+	// bin is installed by bin.vendor_binary.31.arm64
+	ctx.ModuleForTests("bin.vendor_binary.31.arm64", binaryVariant).Output("bin")
 
-	// bin32 is installed by bin32.vendor_binary.30.arm64
-	ctx.ModuleForTests("bin32.vendor_binary.30.arm64", binary32Variant).Output("bin32")
+	// bin32 is installed by bin32.vendor_binary.31.arm64
+	ctx.ModuleForTests("bin32.vendor_binary.31.arm64", binary32Variant).Output("bin32")
 
 	// bin_without_snapshot is installed by bin_without_snapshot
 	ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Output("bin_without_snapshot")
 
-	// libvendor, libvendor_available and bin don't have vendor.30 variant
+	// libvendor, libvendor_available and bin don't have vendor.31 variant
 	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
 	if inList(sharedVariant, libvendorVariants) {
 		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
@@ -979,14 +1007,6 @@
 	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
 }
 
-func assertExcludeFromVendorSnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool) {
-	t.Helper()
-	m := ctx.ModuleForTests(name, vendorVariant).Module().(*Module)
-	if m.ExcludeFromVendorSnapshot() != expected {
-		t.Errorf("expected %q ExcludeFromVendorSnapshot to be %t", m.String(), expected)
-	}
-}
-
 func assertExcludeFromRecoverySnapshotIs(t *testing.T, ctx *android.TestContext, name string, expected bool) {
 	t.Helper()
 	m := ctx.ModuleForTests(name, recoveryVariant).Module().(*Module)
@@ -1054,13 +1074,13 @@
 	android.FailIfErrored(t, errs)
 
 	// Test an include and exclude framework module.
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false)
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true)
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false, vendorVariant)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true, vendorVariant)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true, vendorVariant)
 
 	// A vendor module is excluded, but by its path, not the
 	// exclude_from_vendor_snapshot property.
-	assertExcludeFromVendorSnapshotIs(t, ctx, "libvendor", false)
+	AssertExcludeFromVendorSnapshotIs(t, ctx, "libvendor", false, vendorVariant)
 
 	// Verify the content of the vendor snapshot.
 
@@ -1083,15 +1103,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
 
 		// Excluded modules
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libvendor.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
 	}
 
@@ -1231,9 +1251,9 @@
 		// For shared libraries, only recovery_available modules are captured.
 		sharedVariant := fmt.Sprintf("android_recovery_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
-		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(sharedDir, "libvndk.so.json"),
 			filepath.Join(sharedDir, "librecovery.so.json"),
@@ -1242,9 +1262,9 @@
 		// For static libraries, all recovery:true and recovery_available modules are captured.
 		staticVariant := fmt.Sprintf("android_recovery_%s_%s_static", archType, archVariant)
 		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
-		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.a", staticDir, staticVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.a", staticDir, staticVariant)
 		jsonFiles = append(jsonFiles,
 			filepath.Join(staticDir, "libb.a.json"),
 			filepath.Join(staticDir, "librecovery.a.json"),
@@ -1254,8 +1274,8 @@
 		if archType == "arm64" {
 			binaryVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
 			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
-			checkSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
-			checkSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "recovery_bin", "recovery_bin", binaryDir, binaryVariant)
+			CheckSnapshot(t, ctx, snapshotSingleton, "recovery_available_bin", "recovery_available_bin", binaryDir, binaryVariant)
 			jsonFiles = append(jsonFiles,
 				filepath.Join(binaryDir, "recovery_bin.json"),
 				filepath.Join(binaryDir, "recovery_available_bin.json"))
@@ -1268,7 +1288,7 @@
 		// For object modules, all vendor:true and vendor_available modules are captured.
 		objectVariant := fmt.Sprintf("android_recovery_%s_%s", archType, archVariant)
 		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
-		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
 		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
 	}
 
@@ -1366,15 +1386,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
 
 		// Excluded modules
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
-		checkSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
 		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
 	}
 
@@ -1455,15 +1475,15 @@
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
-		checkSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "librecovery", "librecovery.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery.so.json"))
 		// Check that snapshot captures "prefer: true" prebuilt
-		checkSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
+		CheckSnapshot(t, ctx, snapshotSingleton, "prebuilt_libfoo", "libfoo.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libfoo.so.json"))
 
 		// Excluded modules. Modules not included in the directed recovery snapshot
 		// are still include as fake modules.
-		checkSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
+		CheckSnapshotRule(t, ctx, snapshotSingleton, "librecovery_available", "librecovery_available.so", sharedDir, sharedVariant)
 		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "librecovery_available.so.json"))
 	}
 
diff --git a/cc/vndk.go b/cc/vndk.go
index 8b3788b..dd1c3e1 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -234,7 +234,6 @@
 
 var (
 	llndkLibraries                = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsLLNDK && !m.Header() })
-	llndkLibrariesWithoutHWASAN   = vndkModuleListRemover(llndkLibraries, "libclang_rt.hwasan-")
 	vndkSPLibraries               = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKSP })
 	vndkCoreLibraries             = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKCore })
 	vndkPrivateLibraries          = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKPrivate })
@@ -361,7 +360,7 @@
 	// prebuilt vndk modules should match with device
 	// TODO(b/142675459): Use enabled: to select target device in vndk_prebuilt_shared
 	// When b/142675459 is landed, remove following check
-	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok && !p.matchesWithDevice(mctx.DeviceConfig()) {
+	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok && !p.MatchesWithDevice(mctx.DeviceConfig()) {
 		return false
 	}
 
@@ -372,7 +371,7 @@
 			if mctx.ModuleName() == "libz" {
 				return false
 			}
-			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.isVndkSp()
+			return m.ImageVariation().Variation == android.CoreVariation && lib.shared() && m.IsVndkSp()
 		}
 
 		useCoreVariant := m.VndkVersion() == mctx.DeviceConfig().PlatformVndkVersion() &&
@@ -419,10 +418,6 @@
 }
 
 func RegisterVndkLibraryTxtTypes(ctx android.RegistrationContext) {
-	// Make uses LLNDK_LIBRARIES to determine which libraries to install.
-	// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
-	// Therefore, by removing the library here, we cause it to only be installed if libc
-	// depends on it.
 	ctx.RegisterSingletonModuleType("llndk_libraries_txt", llndkLibrariesTxtFactory)
 	ctx.RegisterSingletonModuleType("vndksp_libraries_txt", vndkSPLibrariesTxtFactory)
 	ctx.RegisterSingletonModuleType("vndkcore_libraries_txt", vndkCoreLibrariesTxtFactory)
@@ -434,8 +429,9 @@
 type vndkLibrariesTxt struct {
 	android.SingletonModuleBase
 
-	lister      moduleListerFunc
-	makeVarName string
+	lister               moduleListerFunc
+	makeVarName          string
+	filterOutFromMakeVar string
 
 	properties VndkLibrariesTxtProperties
 
@@ -454,8 +450,12 @@
 // llndk_libraries_txt is a singleton module whose content is a list of LLNDK libraries
 // generated by Soong but can be referenced by other modules.
 // For example, apex_vndk can depend on these files as prebuilt.
+// Make uses LLNDK_LIBRARIES to determine which libraries to install.
+// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
+// Therefore, by removing the library here, we cause it to only be installed if libc
+// depends on it.
 func llndkLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(llndkLibrariesWithoutHWASAN, "LLNDK_LIBRARIES")
+	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "LLNDK_LIBRARIES", "libclang_rt.hwasan-")
 }
 
 // vndksp_libraries_txt is a singleton module whose content is a list of VNDKSP libraries
@@ -493,16 +493,21 @@
 	return newVndkLibrariesTxt(vndkUsingCoreVariantLibraries, "VNDK_USING_CORE_VARIANT_LIBRARIES")
 }
 
-func newVndkLibrariesTxt(lister moduleListerFunc, makeVarName string) android.SingletonModule {
+func newVndkLibrariesWithMakeVarFilter(lister moduleListerFunc, makeVarName string, filter string) android.SingletonModule {
 	m := &vndkLibrariesTxt{
-		lister:      lister,
-		makeVarName: makeVarName,
+		lister:               lister,
+		makeVarName:          makeVarName,
+		filterOutFromMakeVar: filter,
 	}
 	m.AddProperties(&m.properties)
 	android.InitAndroidModule(m)
 	return m
 }
 
+func newVndkLibrariesTxt(lister moduleListerFunc, makeVarName string) android.SingletonModule {
+	return newVndkLibrariesWithMakeVarFilter(lister, makeVarName, "")
+}
+
 func insertVndkVersion(filename string, vndkVersion string) string {
 	if index := strings.LastIndex(filename, "."); index != -1 {
 		return filename[:index] + "." + vndkVersion + filename[index:]
@@ -542,8 +547,21 @@
 }
 
 func (txt *vndkLibrariesTxt) MakeVars(ctx android.MakeVarsContext) {
-	ctx.Strict(txt.makeVarName, strings.Join(txt.moduleNames, " "))
-
+	filter := func(modules []string, prefix string) []string {
+		if prefix == "" {
+			return modules
+		}
+		var result []string
+		for _, module := range modules {
+			if strings.HasPrefix(module, prefix) {
+				continue
+			} else {
+				result = append(result, module)
+			}
+		}
+		return result
+	}
+	ctx.Strict(txt.makeVarName, strings.Join(filter(txt.moduleNames, txt.filterOutFromMakeVar), " "))
 }
 
 // PrebuiltEtcModule interface
@@ -574,38 +592,37 @@
 	vndkSnapshotZipFile android.OptionalPath
 }
 
-func isVndkSnapshotAware(config android.DeviceConfig, m *Module,
-	apexInfo android.ApexInfo) (i snapshotLibraryInterface, vndkType string, isVndkSnapshotLib bool) {
+func isVndkSnapshotAware(config android.DeviceConfig, m LinkableInterface,
+	apexInfo android.ApexInfo) (vndkType string, isVndkSnapshotLib bool) {
 
 	if m.Target().NativeBridge == android.NativeBridgeEnabled {
-		return nil, "", false
+		return "", false
 	}
 	// !inVendor: There's product/vendor variants for VNDK libs. We only care about vendor variants.
 	// !installable: Snapshot only cares about "installable" modules.
 	// !m.IsLlndk: llndk stubs are required for building against snapshots.
 	// IsSnapshotPrebuilt: Snapshotting a snapshot doesn't make sense.
 	// !outputFile.Valid: Snapshot requires valid output file.
-	if !m.InVendor() || (!m.installable(apexInfo) && !m.IsLlndk()) || m.IsSnapshotPrebuilt() || !m.outputFile.Valid() {
-		return nil, "", false
+	if !m.InVendor() || (!installable(m, apexInfo) && !m.IsLlndk()) || m.IsSnapshotPrebuilt() || !m.OutputFile().Valid() {
+		return "", false
 	}
-	l, ok := m.linker.(snapshotLibraryInterface)
-	if !ok || !l.shared() {
-		return nil, "", false
+	if !m.IsSnapshotLibrary() || !m.Shared() {
+		return "", false
 	}
 	if m.VndkVersion() == config.PlatformVndkVersion() {
 		if m.IsVndk() && !m.IsVndkExt() {
-			if m.isVndkSp() {
-				return l, "vndk-sp", true
+			if m.IsVndkSp() {
+				return "vndk-sp", true
 			} else {
-				return l, "vndk-core", true
+				return "vndk-core", true
 			}
-		} else if l.hasLLNDKStubs() && l.stubsVersion() == "" {
+		} else if m.HasLlndkStubs() && m.StubsVersion() == "" {
 			// Use default version for the snapshot.
-			return l, "llndk-stub", true
+			return "llndk-stub", true
 		}
 	}
 
-	return nil, "", false
+	return "", false
 }
 
 func (c *vndkSnapshotSingleton) GenerateBuildActions(ctx android.SingletonContext) {
@@ -722,7 +739,7 @@
 
 		apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
 
-		l, vndkType, ok := isVndkSnapshotAware(ctx.DeviceConfig(), m, apexInfo)
+		vndkType, ok := isVndkSnapshotAware(ctx.DeviceConfig(), m, apexInfo)
 		if !ok {
 			return
 		}
@@ -761,7 +778,7 @@
 		}
 
 		if ctx.Config().VndkSnapshotBuildArtifacts() {
-			headers = append(headers, l.snapshotHeaders()...)
+			headers = append(headers, m.SnapshotHeaders()...)
 		}
 	})
 
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index fc4412a..da34f36 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -82,7 +82,7 @@
 }
 
 func (p *vndkPrebuiltLibraryDecorator) NameSuffix() string {
-	suffix := p.version()
+	suffix := p.Version()
 	if p.arch() != "" {
 		suffix += "." + p.arch()
 	}
@@ -92,7 +92,7 @@
 	return vndkSuffix + suffix
 }
 
-func (p *vndkPrebuiltLibraryDecorator) version() string {
+func (p *vndkPrebuiltLibraryDecorator) Version() string {
 	return String(p.properties.Version)
 }
 
@@ -107,7 +107,7 @@
 	return "64"
 }
 
-func (p *vndkPrebuiltLibraryDecorator) snapshotAndroidMkSuffix() string {
+func (p *vndkPrebuiltLibraryDecorator) SnapshotAndroidMkSuffix() string {
 	return ".vendor"
 }
 
@@ -133,7 +133,7 @@
 func (p *vndkPrebuiltLibraryDecorator) link(ctx ModuleContext,
 	flags Flags, deps PathDeps, objs Objects) android.Path {
 
-	if !p.matchesWithDevice(ctx.DeviceConfig()) {
+	if !p.MatchesWithDevice(ctx.DeviceConfig()) {
 		ctx.Module().HideFromMake()
 		return nil
 	}
@@ -163,14 +163,13 @@
 		p.androidMkSuffix = p.NameSuffix()
 
 		vndkVersion := ctx.DeviceConfig().VndkVersion()
-		if vndkVersion == p.version() {
+		if vndkVersion == p.Version() {
 			p.androidMkSuffix = ""
 		}
 
 		ctx.SetProvider(SharedLibraryInfoProvider, SharedLibraryInfo{
-			SharedLibrary:           in,
-			UnstrippedSharedLibrary: p.unstrippedOutputFile,
-			Target:                  ctx.Target(),
+			SharedLibrary: in,
+			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
 		})
@@ -184,7 +183,7 @@
 	return nil
 }
 
-func (p *vndkPrebuiltLibraryDecorator) matchesWithDevice(config android.DeviceConfig) bool {
+func (p *vndkPrebuiltLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
 	arches := config.Arches()
 	if len(arches) == 0 || arches[0].ArchType.String() != p.arch() {
 		return false
@@ -202,7 +201,7 @@
 	return false
 }
 
-func (p *vndkPrebuiltLibraryDecorator) isSnapshotPrebuilt() bool {
+func (p *vndkPrebuiltLibraryDecorator) IsSnapshotPrebuilt() bool {
 	return true
 }
 
diff --git a/cmd/extract_linker/main.go b/cmd/extract_linker/main.go
index ea0bf4e..2dcb894 100644
--- a/cmd/extract_linker/main.go
+++ b/cmd/extract_linker/main.go
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 // This tool extracts ELF LOAD segments from our linker binary, and produces an
-// assembly file and linker flags which will embed those segments as sections
+// assembly file and linker script which will embed those segments as sections
 // in another binary.
 package main
 
@@ -31,10 +31,10 @@
 
 func main() {
 	var asmPath string
-	var flagsPath string
+	var scriptPath string
 
 	flag.StringVar(&asmPath, "s", "", "Path to save the assembly file")
-	flag.StringVar(&flagsPath, "f", "", "Path to save the linker flags")
+	flag.StringVar(&scriptPath, "T", "", "Path to save the linker script")
 	flag.Parse()
 
 	f, err := os.Open(flag.Arg(0))
@@ -49,20 +49,30 @@
 	}
 
 	asm := &bytes.Buffer{}
+	script := &bytes.Buffer{}
 	baseLoadAddr := uint64(0x1000)
 	load := 0
-	linkFlags := []string{}
 
 	fmt.Fprintln(asm, ".globl __dlwrap_linker_offset")
 	fmt.Fprintf(asm, ".set __dlwrap_linker_offset, 0x%x\n", baseLoadAddr)
 
+	fmt.Fprintln(script, "ENTRY(__dlwrap__start)")
+	fmt.Fprintln(script, "SECTIONS {")
+
 	for _, prog := range ef.Progs {
 		if prog.Type != elf.PT_LOAD {
 			continue
 		}
 
-		sectionName := fmt.Sprintf(".linker.sect%d", load)
-		symName := fmt.Sprintf("__dlwrap_linker_sect%d", load)
+		var progName string
+		progSection := progToFirstSection(prog, ef.Sections)
+		if progSection != nil {
+			progName = progSection.Name
+		} else {
+			progName = fmt.Sprintf(".sect%d", load)
+		}
+		sectionName := ".linker" + progName
+		symName := "__dlwrap_linker" + strings.ReplaceAll(progName, ".", "_")
 
 		flags := ""
 		if prog.Flags&elf.PF_W != 0 {
@@ -75,10 +85,9 @@
 
 		fmt.Fprintf(asm, ".globl %s\n%s:\n\n", symName, symName)
 
-		linkFlags = append(linkFlags,
-			fmt.Sprintf("-Wl,--undefined=%s", symName),
-			fmt.Sprintf("-Wl,--section-start=%s=0x%x",
-				sectionName, baseLoadAddr+prog.Vaddr))
+		fmt.Fprintf(script, "  %s %d : {\n", sectionName, baseLoadAddr+prog.Vaddr)
+		fmt.Fprintf(script, "    KEEP(*(%s));\n", sectionName)
+		fmt.Fprintln(script, "  }")
 
 		buffer, _ := ioutil.ReadAll(prog.Open())
 		bytesToAsm(asm, buffer)
@@ -97,16 +106,18 @@
 		load += 1
 	}
 
+	fmt.Fprintln(script, "}")
+	fmt.Fprintln(script, "INSERT BEFORE .note.android.ident;")
+
 	if asmPath != "" {
 		if err := ioutil.WriteFile(asmPath, asm.Bytes(), 0777); err != nil {
 			log.Fatalf("Unable to write %q: %v", asmPath, err)
 		}
 	}
 
-	if flagsPath != "" {
-		flags := strings.Join(linkFlags, " ")
-		if err := ioutil.WriteFile(flagsPath, []byte(flags), 0777); err != nil {
-			log.Fatalf("Unable to write %q: %v", flagsPath, err)
+	if scriptPath != "" {
+		if err := ioutil.WriteFile(scriptPath, script.Bytes(), 0777); err != nil {
+			log.Fatalf("Unable to write %q: %v", scriptPath, err)
 		}
 	}
 }
@@ -125,3 +136,12 @@
 	}
 	fmt.Fprintln(asm)
 }
+
+func progToFirstSection(prog *elf.Prog, sections []*elf.Section) *elf.Section {
+	for _, section := range sections {
+		if section.Addr == prog.Vaddr {
+			return section
+		}
+	}
+	return nil
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/go2bp/Android.bp
similarity index 76%
copy from cmd/host_bionic_inject/Android.bp
copy to cmd/go2bp/Android.bp
index 16bc179..53d70b6 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/go2bp/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2018 Google Inc. All rights reserved.
+// Copyright 2021 Google Inc. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
 }
 
 blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+    name: "go2bp",
+    deps: [
+        "blueprint-proptools",
+        "bpfix-lib",
+    ],
+    srcs: ["go2bp.go"],
 }
diff --git a/cmd/go2bp/go2bp.go b/cmd/go2bp/go2bp.go
new file mode 100644
index 0000000..67138f1
--- /dev/null
+++ b/cmd/go2bp/go2bp.go
@@ -0,0 +1,361 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"strings"
+	"text/template"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/bpfix/bpfix"
+)
+
+type RewriteNames []RewriteName
+type RewriteName struct {
+	prefix string
+	repl   string
+}
+
+func (r *RewriteNames) String() string {
+	return ""
+}
+
+func (r *RewriteNames) Set(v string) error {
+	split := strings.SplitN(v, "=", 2)
+	if len(split) != 2 {
+		return fmt.Errorf("Must be in the form of <prefix>=<replace>")
+	}
+	*r = append(*r, RewriteName{
+		prefix: split[0],
+		repl:   split[1],
+	})
+	return nil
+}
+
+func (r *RewriteNames) GoToBp(name string) string {
+	ret := name
+	for _, r := range *r {
+		prefix := r.prefix
+		if name == prefix {
+			ret = r.repl
+			break
+		}
+		prefix += "/"
+		if strings.HasPrefix(name, prefix) {
+			ret = r.repl + "-" + strings.TrimPrefix(name, prefix)
+		}
+	}
+	return strings.ReplaceAll(ret, "/", "-")
+}
+
+var rewriteNames = RewriteNames{}
+
+type Exclude map[string]bool
+
+func (e Exclude) String() string {
+	return ""
+}
+
+func (e Exclude) Set(v string) error {
+	e[v] = true
+	return nil
+}
+
+var excludes = make(Exclude)
+var excludeDeps = make(Exclude)
+var excludeSrcs = make(Exclude)
+
+type GoModule struct {
+	Dir string
+}
+
+type GoPackage struct {
+	Dir         string
+	ImportPath  string
+	Name        string
+	Imports     []string
+	GoFiles     []string
+	TestGoFiles []string
+	TestImports []string
+
+	Module *GoModule
+}
+
+func (g GoPackage) IsCommand() bool {
+	return g.Name == "main"
+}
+
+func (g GoPackage) BpModuleType() string {
+	if g.IsCommand() {
+		return "blueprint_go_binary"
+	}
+	return "bootstrap_go_package"
+}
+
+func (g GoPackage) BpName() string {
+	if g.IsCommand() {
+		return filepath.Base(g.ImportPath)
+	}
+	return rewriteNames.GoToBp(g.ImportPath)
+}
+
+func (g GoPackage) BpDeps(deps []string) []string {
+	var ret []string
+	for _, d := range deps {
+		// Ignore stdlib dependencies
+		if !strings.Contains(d, ".") {
+			continue
+		}
+		if _, ok := excludeDeps[d]; ok {
+			continue
+		}
+		name := rewriteNames.GoToBp(d)
+		ret = append(ret, name)
+	}
+	return ret
+}
+
+func (g GoPackage) BpSrcs(srcs []string) []string {
+	var ret []string
+	prefix, err := filepath.Rel(g.Module.Dir, g.Dir)
+	if err != nil {
+		panic(err)
+	}
+	for _, f := range srcs {
+		f = filepath.Join(prefix, f)
+		if _, ok := excludeSrcs[f]; ok {
+			continue
+		}
+		ret = append(ret, f)
+	}
+	return ret
+}
+
+// AllImports combines Imports and TestImports, as blueprint does not differentiate these.
+func (g GoPackage) AllImports() []string {
+	imports := append([]string(nil), g.Imports...)
+	imports = append(imports, g.TestImports...)
+
+	if len(imports) == 0 {
+		return nil
+	}
+
+	// Sort and de-duplicate
+	sort.Strings(imports)
+	j := 0
+	for i := 1; i < len(imports); i++ {
+		if imports[i] == imports[j] {
+			continue
+		}
+		j++
+		imports[j] = imports[i]
+	}
+	return imports[:j+1]
+}
+
+var bpTemplate = template.Must(template.New("bp").Parse(`
+{{.BpModuleType}} {
+    name: "{{.BpName}}",
+    {{- if not .IsCommand}}
+    pkgPath: "{{.ImportPath}}",
+    {{- end}}
+    {{- if .BpDeps .AllImports}}
+    deps: [
+        {{- range .BpDeps .AllImports}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+    {{- if .BpSrcs .GoFiles}}
+    srcs: [
+        {{- range .BpSrcs .GoFiles}}
+        "{{.}}",
+        {{- end}}
+    ],
+    {{- end}}
+    {{- if .BpSrcs .TestGoFiles}}
+    testSrcs: [
+    	{{- range .BpSrcs .TestGoFiles}}
+        "{{.}}",
+       {{- end}}
+    ],
+    {{- end}}
+}
+`))
+
+func rerunForRegen(filename string) error {
+	buf, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+
+	scanner := bufio.NewScanner(bytes.NewBuffer(buf))
+
+	// Skip the first line in the file
+	for i := 0; i < 2; i++ {
+		if !scanner.Scan() {
+			if scanner.Err() != nil {
+				return scanner.Err()
+			} else {
+				return fmt.Errorf("unexpected EOF")
+			}
+		}
+	}
+
+	// Extract the old args from the file
+	line := scanner.Text()
+	if strings.HasPrefix(line, "// go2bp ") {
+		line = strings.TrimPrefix(line, "// go2bp ")
+	} else {
+		return fmt.Errorf("unexpected second line: %q", line)
+	}
+	args := strings.Split(line, " ")
+	lastArg := args[len(args)-1]
+	args = args[:len(args)-1]
+
+	// Append all current command line args except -regen <file> to the ones from the file
+	for i := 1; i < len(os.Args); i++ {
+		if os.Args[i] == "-regen" || os.Args[i] == "--regen" {
+			i++
+		} else {
+			args = append(args, os.Args[i])
+		}
+	}
+	args = append(args, lastArg)
+
+	cmd := os.Args[0] + " " + strings.Join(args, " ")
+	// Re-exec pom2bp with the new arguments
+	output, err := exec.Command("/bin/sh", "-c", cmd).Output()
+	if exitErr, _ := err.(*exec.ExitError); exitErr != nil {
+		return fmt.Errorf("failed to run %s\n%s", cmd, string(exitErr.Stderr))
+	} else if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(filename, output, 0666)
+}
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `go2bp, a tool to create Android.bp files from go modules
+
+The tool will extract the necessary information from Go files to create an Android.bp that can
+compile them. This needs to be run from the same directory as the go.mod file.
+
+Usage: %s [--rewrite <pkg-prefix>=<replace>] [-exclude <package>] [-regen <file>]
+
+  -rewrite <pkg-prefix>=<replace>
+     rewrite can be used to specify mappings between go package paths and Android.bp modules. The -rewrite
+     option can be specified multiple times. When determining the Android.bp module for a given Go
+     package, mappings are searched in the order they were specified. The first <pkg-prefix> matching
+     either the package directly, or as the prefix '<pkg-prefix>/' will be replaced with <replace>.
+     After all replacements are finished, all '/' characters are replaced with '-'.
+  -exclude <package>
+     Don't put the specified go package in the Android.bp file.
+  -exclude-deps <package>
+     Don't put the specified go package in the dependency lists.
+  -exclude-srcs <module>
+     Don't put the specified source files in srcs or testSrcs lists.
+  -regen <file>
+     Read arguments from <file> and overwrite it.
+
+`, os.Args[0])
+	}
+
+	var regen string
+
+	flag.Var(&excludes, "exclude", "Exclude go package")
+	flag.Var(&excludeDeps, "exclude-dep", "Exclude go package from deps")
+	flag.Var(&excludeSrcs, "exclude-src", "Exclude go file from source lists")
+	flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
+	flag.StringVar(&regen, "regen", "", "Rewrite specified file")
+	flag.Parse()
+
+	if regen != "" {
+		err := rerunForRegen(regen)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, err)
+			os.Exit(1)
+		}
+		os.Exit(0)
+	}
+
+	if flag.NArg() != 0 {
+		fmt.Fprintf(os.Stderr, "Unused argument detected: %v\n", flag.Args())
+		os.Exit(1)
+	}
+
+	if _, err := os.Stat("go.mod"); err != nil {
+		fmt.Fprintln(os.Stderr, "go.mod file not found")
+		os.Exit(1)
+	}
+
+	cmd := exec.Command("go", "list", "-json", "./...")
+	output, err := cmd.Output()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to dump the go packages: %v\n", err)
+		os.Exit(1)
+	}
+	decoder := json.NewDecoder(bytes.NewReader(output))
+
+	pkgs := []GoPackage{}
+	for decoder.More() {
+		pkg := GoPackage{}
+		err := decoder.Decode(&pkg)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to parse json: %v\n", err)
+			os.Exit(1)
+		}
+		pkgs = append(pkgs, pkg)
+	}
+
+	buf := &bytes.Buffer{}
+
+	fmt.Fprintln(buf, "// Automatically generated with:")
+	fmt.Fprintln(buf, "// go2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " "))
+
+	for _, pkg := range pkgs {
+		if excludes[pkg.ImportPath] {
+			continue
+		}
+		if len(pkg.BpSrcs(pkg.GoFiles)) == 0 && len(pkg.BpSrcs(pkg.TestGoFiles)) == 0 {
+			continue
+		}
+		err := bpTemplate.Execute(buf, pkg)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Error writing", pkg.Name, err)
+			os.Exit(1)
+		}
+	}
+
+	out, err := bpfix.Reformat(buf.String())
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Error formatting output", err)
+		os.Exit(1)
+	}
+
+	os.Stdout.WriteString(out)
+}
diff --git a/cmd/host_bionic_inject/Android.bp b/cmd/host_bionic_verify/Android.bp
similarity index 82%
rename from cmd/host_bionic_inject/Android.bp
rename to cmd/host_bionic_verify/Android.bp
index 16bc179..4e7c379 100644
--- a/cmd/host_bionic_inject/Android.bp
+++ b/cmd/host_bionic_verify/Android.bp
@@ -17,8 +17,7 @@
 }
 
 blueprint_go_binary {
-    name: "host_bionic_inject",
-    deps: ["soong-symbol_inject"],
-    srcs: ["host_bionic_inject.go"],
-    testSrcs: ["host_bionic_inject_test.go"],
+    name: "host_bionic_verify",
+    srcs: ["host_bionic_verify.go"],
+    testSrcs: ["host_bionic_verify_test.go"],
 }
diff --git a/cmd/host_bionic_inject/host_bionic_inject.go b/cmd/host_bionic_verify/host_bionic_verify.go
similarity index 68%
rename from cmd/host_bionic_inject/host_bionic_inject.go
rename to cmd/host_bionic_verify/host_bionic_verify.go
index ce8b062..52400a3 100644
--- a/cmd/host_bionic_inject/host_bionic_inject.go
+++ b/cmd/host_bionic_verify/host_bionic_verify.go
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Verifies a host bionic executable with an embedded linker, then injects
-// the address of the _start function for the linker_wrapper to use.
+// Verifies a host bionic executable with an embedded linker.
 package main
 
 import (
@@ -22,19 +21,16 @@
 	"fmt"
 	"io"
 	"os"
-
-	"android/soong/symbol_inject"
 )
 
 func main() {
-	var inputFile, linkerFile, outputFile string
+	var inputFile, linkerFile string
 
 	flag.StringVar(&inputFile, "i", "", "Input file")
 	flag.StringVar(&linkerFile, "l", "", "Linker file")
-	flag.StringVar(&outputFile, "o", "", "Output file")
 	flag.Parse()
 
-	if inputFile == "" || linkerFile == "" || outputFile == "" || flag.NArg() != 0 {
+	if inputFile == "" || linkerFile == "" || flag.NArg() != 0 {
 		flag.Usage()
 		os.Exit(1)
 	}
@@ -46,75 +42,52 @@
 	}
 	defer r.Close()
 
-	file, err := symbol_inject.OpenFile(r)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(3)
-	}
-
 	linker, err := elf.Open(linkerFile)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		os.Exit(4)
 	}
 
-	startAddr, err := parseElf(r, linker)
+	err = checkElf(r, linker)
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err.Error())
 		os.Exit(5)
 	}
-
-	w, err := os.OpenFile(outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(6)
-	}
-	defer w.Close()
-
-	err = symbol_inject.InjectUint64Symbol(file, w, "__dlwrap_original_start", startAddr)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err.Error())
-		os.Exit(7)
-	}
 }
 
 // Check the ELF file, and return the address to the _start function
-func parseElf(r io.ReaderAt, linker *elf.File) (uint64, error) {
+func checkElf(r io.ReaderAt, linker *elf.File) error {
 	file, err := elf.NewFile(r)
 	if err != nil {
-		return 0, err
+		return err
 	}
 
 	symbols, err := file.Symbols()
 	if err != nil {
-		return 0, err
+		return err
 	}
 
 	for _, prog := range file.Progs {
 		if prog.Type == elf.PT_INTERP {
-			return 0, fmt.Errorf("File should not have a PT_INTERP header")
+			return fmt.Errorf("File should not have a PT_INTERP header")
 		}
 	}
 
 	if dlwrap_start, err := findSymbol(symbols, "__dlwrap__start"); err != nil {
-		return 0, err
+		return err
 	} else if dlwrap_start.Value != file.Entry {
-		return 0, fmt.Errorf("Expected file entry(0x%x) to point to __dlwrap_start(0x%x)",
+		return fmt.Errorf("Expected file entry(0x%x) to point to __dlwrap_start(0x%x)",
 			file.Entry, dlwrap_start.Value)
 	}
 
 	err = checkLinker(file, linker, symbols)
 	if err != nil {
-		return 0, fmt.Errorf("Linker executable failed verification against app embedded linker: %s\n"+
+		return fmt.Errorf("Linker executable failed verification against app embedded linker: %s\n"+
 			"linker might not be in sync with crtbegin_dynamic.o.",
 			err)
 	}
 
-	start, err := findSymbol(symbols, "_start")
-	if err != nil {
-		return 0, fmt.Errorf("Failed to find _start symbol")
-	}
-	return start.Value, nil
+	return nil
 }
 
 func findSymbol(symbols []elf.Symbol, name string) (elf.Symbol, error) {
diff --git a/cmd/host_bionic_inject/host_bionic_inject_test.go b/cmd/host_bionic_verify/host_bionic_verify_test.go
similarity index 100%
rename from cmd/host_bionic_inject/host_bionic_inject_test.go
rename to cmd/host_bionic_verify/host_bionic_verify_test.go
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index e0f8caa..55a5470 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -433,7 +433,7 @@
 	config := build.NewConfig(ctx, args...)
 	config.Environment().Set("OUT_DIR", outDir)
 	if !*keepArtifacts {
-		config.Environment().Set("EMPTY_NINJA_FILE", "true")
+		config.SetEmptyNinjaFile(true)
 	}
 	build.FindSources(ctx, config, mpctx.Finder)
 	config.Lunch(ctx, product, *buildVariant)
@@ -460,19 +460,21 @@
 		}
 	}()
 
-	buildWhat := build.BuildProductConfig
+	config.SetSkipNinja(true)
+
+	buildWhat := build.RunProductConfig
 	if !*onlyConfig {
-		buildWhat |= build.BuildSoong
+		buildWhat |= build.RunSoong
 		if !*onlySoong {
-			buildWhat |= build.BuildKati
+			buildWhat |= build.RunKati
 		}
 	}
 
 	before := time.Now()
-	build.Build(ctx, config, buildWhat)
+	build.Build(ctx, config)
 
 	// Save std_full.log if Kati re-read the makefiles
-	if buildWhat&build.BuildKati != 0 {
+	if buildWhat&build.RunKati != 0 {
 		if after, err := os.Stat(config.KatiBuildNinjaFile()); err == nil && after.ModTime().After(before) {
 			err := copyFile(stdLog, filepath.Join(filepath.Dir(stdLog), "std_full.log"))
 			if err != nil {
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 044689e..0336fb6 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -174,6 +174,9 @@
 	writeFakeNinjaFile(extraNinjaDeps, configuration.BuildDir())
 }
 
+// doChosenActivity runs Soong for a specific activity, like bp2build, queryview
+// or the actual Soong build for the build.ninja file. Returns the top level
+// output file of the specific activity.
 func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
 	bazelConversionRequested := bp2buildMarker != ""
 	mixedModeBuild := configuration.BazelContext.BazelEnabled()
@@ -186,11 +189,7 @@
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
 		runBp2Build(configuration, extraNinjaDeps)
-		if bp2buildMarker != "" {
-			return bp2buildMarker
-		} else {
-			return bootstrap.CmdlineArgs.OutFile
-		}
+		return bp2buildMarker
 	}
 
 	ctx := newContext(configuration, prepareBuildActions)
@@ -326,13 +325,13 @@
 
 	ninjaFileName := "build.ninja"
 	ninjaFile := shared.JoinPath(topDir, buildDir, ninjaFileName)
-	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName)
+	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName+".d")
 	// A workaround to create the 'nothing' ninja target so `m nothing` works,
 	// since bp2build runs without Kati, and the 'nothing' target is declared in
 	// a Makefile.
 	ioutil.WriteFile(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
 	ioutil.WriteFile(ninjaFileD,
-		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
+		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFile, extraNinjaDepsString)),
 		0666)
 }
 
@@ -357,6 +356,83 @@
 	}
 }
 
+// Find BUILD files in the srcDir which...
+//
+// - are not on the allow list (android/bazel.go#ShouldKeepExistingBuildFileForDir())
+//
+// - won't be overwritten by corresponding bp2build generated files
+//
+// And return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
+func getPathsToIgnoredBuildFiles(topDir string, generatedRoot string, srcDirBazelFiles []string) []string {
+	paths := make([]string, 0)
+
+	for _, srcDirBazelFileRelativePath := range srcDirBazelFiles {
+		srcDirBazelFileFullPath := shared.JoinPath(topDir, srcDirBazelFileRelativePath)
+		fileInfo, err := os.Stat(srcDirBazelFileFullPath)
+		if err != nil {
+			// Warn about error, but continue trying to check files
+			fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", srcDirBazelFileFullPath, err)
+			continue
+		}
+		if fileInfo.IsDir() {
+			// Don't ignore entire directories
+			continue
+		}
+		if !(fileInfo.Name() == "BUILD" || fileInfo.Name() == "BUILD.bazel") {
+			// Don't ignore this file - it is not a build file
+			continue
+		}
+		srcDirBazelFileDir := filepath.Dir(srcDirBazelFileRelativePath)
+		if android.ShouldKeepExistingBuildFileForDir(srcDirBazelFileDir) {
+			// Don't ignore this existing build file
+			continue
+		}
+		correspondingBp2BuildFile := shared.JoinPath(topDir, generatedRoot, srcDirBazelFileRelativePath)
+		if _, err := os.Stat(correspondingBp2BuildFile); err == nil {
+			// If bp2build generated an alternate BUILD file, don't exclude this workspace path
+			// BUILD file clash resolution happens later in the symlink forest creation
+			continue
+		}
+		fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", srcDirBazelFileRelativePath)
+		paths = append(paths, srcDirBazelFileRelativePath)
+	}
+
+	return paths
+}
+
+// Returns temporary symlink forest excludes necessary for bazel build //external/... (and bazel build //frameworks/...) to work
+func getTemporaryExcludes() []string {
+	excludes := make([]string, 0)
+
+	// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite symlink expansion error for Bazel
+	excludes = append(excludes, "external/autotest/venv/autotest_lib")
+
+	// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
+	// It contains several symlinks back to real source dirs, and those source dirs contain BUILD files we want to ignore
+	excludes = append(excludes, "external/google-fruit/extras/bazel_root/third_party/fruit")
+
+	// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
+	excludes = append(excludes, "frameworks/compile/slang")
+
+	return excludes
+}
+
+// Read the bazel.list file that the Soong Finder already dumped earlier (hopefully)
+// It contains the locations of BUILD files, BUILD.bazel files, etc. in the source dir
+func getExistingBazelRelatedFiles(topDir string) ([]string, error) {
+	bazelFinderFile := filepath.Join(filepath.Dir(bootstrap.CmdlineArgs.ModuleListFile), "bazel.list")
+	if !filepath.IsAbs(bazelFinderFile) {
+		// Assume this was a relative path under topDir
+		bazelFinderFile = filepath.Join(topDir, bazelFinderFile)
+	}
+	data, err := ioutil.ReadFile(bazelFinderFile)
+	if err != nil {
+		return nil, err
+	}
+	files := strings.Split(strings.TrimSpace(string(data)), "\n")
+	return files, nil
+}
+
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
@@ -415,6 +491,17 @@
 		excludes = append(excludes, bootstrap.CmdlineArgs.NinjaBuildDir)
 	}
 
+	existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
+		os.Exit(1)
+	}
+
+	pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(topDir, generatedRoot, existingBazelRelatedFiles)
+	excludes = append(excludes, pathsToIgnoredBuildFiles...)
+
+	excludes = append(excludes, getTemporaryExcludes()...)
+
 	symlinkForestDeps := bp2build.PlantSymlinkForest(
 		topDir, workspaceRoot, generatedRoot, configuration.SrcDir(), excludes)
 
@@ -433,9 +520,14 @@
 		os.Exit(1)
 	}
 
-	if bp2buildMarker != "" {
-		touch(shared.JoinPath(topDir, bp2buildMarker))
-	} else {
-		writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
-	}
+	// Create an empty bp2build marker file.
+	touch(shared.JoinPath(topDir, bp2buildMarker))
+
+	// bp2build *always* writes a fake Ninja file containing just the nothing
+	// phony target if it ever re-runs. This allows bp2build to exit early with
+	// GENERATE_BAZEL_FILES=1 m nothing.
+	//
+	// If bp2build is invoked as part of an integrated mixed build, the fake
+	// build.ninja file will be rewritten later into the real file anyway.
+	writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
 }
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 390a9ec..22922c0 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -59,12 +59,10 @@
 	run func(ctx build.Context, config build.Config, args []string, logsDir string)
 }
 
-const makeModeFlagName = "--make-mode"
-
 // list of supported commands (flags) supported by soong ui
 var commands []command = []command{
 	{
-		flag:        makeModeFlagName,
+		flag:        "--make-mode",
 		description: "build the modules by the target name (i.e. soong_docs)",
 		config: func(ctx build.Context, args ...string) build.Config {
 			return build.NewConfig(ctx, args...)
@@ -506,15 +504,7 @@
 		ctx.Fatal("done")
 	}
 
-	toBuild := build.BuildAll
-	if config.UseBazel() {
-		toBuild = build.BuildAllWithBazel
-	}
-
-	if config.Checkbuild() {
-		toBuild |= build.RunBuildTests
-	}
-	build.Build(ctx, config, toBuild)
+	build.Build(ctx, config)
 }
 
 // getCommand finds the appropriate command based on args[1] flag. args[0]
diff --git a/cuj/cuj.go b/cuj/cuj.go
index 3333012..b4ae9a2 100644
--- a/cuj/cuj.go
+++ b/cuj/cuj.go
@@ -115,7 +115,7 @@
 	defer f.Shutdown()
 	build.FindSources(buildCtx, config, f)
 
-	build.Build(buildCtx, config, build.BuildAll)
+	build.Build(buildCtx, config)
 
 	t.results.metrics = met
 }
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 1844bce..0bcec17 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -130,9 +130,11 @@
 	ProvidesUsesLibrary            string       // library name (usually the same as module name)
 	ClassLoaderContexts            ClassLoaderContextMap
 
-	Archs                         []android.ArchType
-	DexPreoptImagesDeps           []android.OutputPaths
-	DexPreoptImageLocationsOnHost []string // boot image location on host (file path without the arch subdirectory)
+	Archs               []android.ArchType
+	DexPreoptImagesDeps []android.OutputPaths
+
+	DexPreoptImageLocationsOnHost   []string // boot image location on host (file path without the arch subdirectory)
+	DexPreoptImageLocationsOnDevice []string // boot image location on device (file path without the arch subdirectory)
 
 	PreoptBootClassPathDexFiles     android.Paths // file paths of boot class path files
 	PreoptBootClassPathDexLocations []string      // virtual locations of boot class path files
@@ -370,6 +372,15 @@
 func (d dex2oatDependencyTag) ExcludeFromApexContents() {
 }
 
+func (d dex2oatDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
+	// RegisterToolDeps may run after the prebuilt mutators and hence register a
+	// dependency on the source module even when the prebuilt is to be used.
+	// dex2oatPathFromDep takes that into account when it retrieves the path to
+	// the binary, but we also need to disable the check for dependencies on
+	// disabled modules.
+	return target.IsReplacedByPrebuilt()
+}
+
 // Dex2oatDepTag represents the dependency onto the dex2oatd module. It is added to any module that
 // needs dexpreopting and so it makes no sense for it to be checked for visibility or included in
 // the apex.
@@ -377,6 +388,7 @@
 
 var _ android.ExcludeFromVisibilityEnforcementTag = Dex2oatDepTag
 var _ android.ExcludeFromApexContentsTag = Dex2oatDepTag
+var _ android.AllowDisabledModuleDependency = Dex2oatDepTag
 
 // RegisterToolDeps adds the necessary dependencies to binary modules for tools
 // that are required later when Get(Cached)GlobalSoongConfig is called. It
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 9d9234f..da015a3 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -499,11 +499,16 @@
 
 // PathToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art
 func PathToLocation(path android.Path, arch android.ArchType) string {
-	pathArch := filepath.Base(filepath.Dir(path.String()))
+	return PathStringToLocation(path.String(), arch)
+}
+
+// PathStringToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art
+func PathStringToLocation(path string, arch android.ArchType) string {
+	pathArch := filepath.Base(filepath.Dir(path))
 	if pathArch != arch.String() {
 		panic(fmt.Errorf("last directory in %q must be %q", path, arch.String()))
 	}
-	return filepath.Join(filepath.Dir(filepath.Dir(path.String())), filepath.Base(path.String()))
+	return filepath.Join(filepath.Dir(filepath.Dir(path)), filepath.Base(path))
 }
 
 func makefileMatch(pattern, s string) bool {
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index 32c4f84..7dbe74c 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -37,6 +37,12 @@
 	globalConfigPath      = flag.String("global", "", "path to global configuration file")
 	moduleConfigPath      = flag.String("module", "", "path to module configuration file")
 	outDir                = flag.String("out_dir", "", "path to output directory")
+	// If uses_target_files is true, dexpreopt_gen will be running on extracted target_files.zip files.
+	// In this case, the tool replace output file path with $(basePath)/$(on-device file path).
+	// The flag is useful when running dex2oat on system image and vendor image which are built separately.
+	usesTargetFiles = flag.Bool("uses_target_files", false, "whether or not dexpreopt is running on target_files")
+	// basePath indicates the path where target_files.zip is extracted.
+	basePath = flag.String("base_path", ".", "base path where images and tools are extracted")
 )
 
 type builderContext struct {
@@ -134,32 +140,28 @@
 			}
 		}
 	}()
-
+	if *usesTargetFiles {
+		moduleConfig.ManifestPath = android.OptionalPath{}
+		prefix := "dex2oat_result"
+		moduleConfig.BuildPath = android.PathForOutput(ctx, filepath.Join(prefix, moduleConfig.DexLocation))
+		for i, location := range moduleConfig.PreoptBootClassPathDexLocations {
+			moduleConfig.PreoptBootClassPathDexFiles[i] = android.PathForSource(ctx, *basePath+location)
+		}
+		for i := range moduleConfig.ClassLoaderContexts {
+			for _, v := range moduleConfig.ClassLoaderContexts[i] {
+				v.Host = android.PathForSource(ctx, *basePath+v.Device)
+			}
+		}
+		moduleConfig.EnforceUsesLibraries = false
+		for i, location := range moduleConfig.DexPreoptImageLocationsOnDevice {
+			moduleConfig.DexPreoptImageLocationsOnHost[i] = *basePath + location
+		}
+	}
 	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
 }
 
 func writeScripts(ctx android.BuilderContext, globalSoong *dexpreopt.GlobalSoongConfig,
 	global *dexpreopt.GlobalConfig, module *dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
-	if err != nil {
-		panic(err)
-	}
-
-	installDir := module.BuildPath.InSameDir(ctx, "dexpreopt_install")
-
-	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir.String())
-	dexpreoptRule.Command().FlagWithArg("mkdir -p ", installDir.String())
-
-	for _, install := range dexpreoptRule.Installs() {
-		installPath := installDir.Join(ctx, strings.TrimPrefix(install.To, "/"))
-		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
-		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
-	}
-	dexpreoptRule.Command().Tool(globalSoong.SoongZip).
-		FlagWithArg("-o ", "$2").
-		FlagWithArg("-C ", installDir.String()).
-		FlagWithArg("-D ", installDir.String())
-
 	write := func(rule *android.RuleBuilder, file string) {
 		script := &bytes.Buffer{}
 		script.WriteString(scriptHeader)
@@ -195,6 +197,30 @@
 			panic(err)
 		}
 	}
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	if err != nil {
+		panic(err)
+	}
+	// When usesTargetFiles is true, only odex/vdex files are necessary.
+	// So skip redunant processes(such as copying the result to the artifact path, and zipping, and so on.)
+	if *usesTargetFiles {
+		write(dexpreoptRule, dexpreoptScriptPath)
+		return
+	}
+	installDir := module.BuildPath.InSameDir(ctx, "dexpreopt_install")
+
+	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir.String())
+	dexpreoptRule.Command().FlagWithArg("mkdir -p ", installDir.String())
+
+	for _, install := range dexpreoptRule.Installs() {
+		installPath := installDir.Join(ctx, strings.TrimPrefix(install.To, "/"))
+		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
+		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
+	}
+	dexpreoptRule.Command().Tool(globalSoong.SoongZip).
+		FlagWithArg("-o ", "$2").
+		FlagWithArg("-C ", installDir.String()).
+		FlagWithArg("-D ", installDir.String())
 
 	// The written scripts will assume the input is $1 and the output is $2
 	if module.DexPath.String() != "$1" {
diff --git a/docs/map_files.md b/docs/map_files.md
index 192530f..1388059 100644
--- a/docs/map_files.md
+++ b/docs/map_files.md
@@ -70,9 +70,11 @@
 
 ### apex
 
-Indicates that the version or symbol is to be exposed in the APEX stubs rather
-than the NDK. May be used in combination with `llndk` if the symbol is exposed
-to both APEX and the LL-NDK.
+Indicates that the version or symbol is to be exposed by an APEX rather than the
+NDK. For APIs exposed by the platform *for* APEX, use `systemapi`.
+
+May be used in combination with `llndk` if the symbol is exposed to both APEX
+and the LL-NDK.
 
 ### future
 
@@ -144,6 +146,12 @@
 preferable to keep such APIs in an entirely separate library to protect them
 from access via `dlsym`, but this is not always possible.
 
+### systemapi
+
+This is a synonym of the `apex` tag. It should be used to clarify that the API
+is an API exposed by the system for an APEX, whereas `apex` should be used for
+APIs exposed by an APEX to the platform or another APEX.
+
 ### var
 
 Used to define a public global variable. By default all symbols are exposed as
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index a7c9143..1d24d6d 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -50,17 +50,19 @@
 func (s *systemImage) buildLinkerConfigFile(ctx android.ModuleContext, root android.OutputPath) android.OutputPath {
 	input := android.PathForModuleSrc(ctx, android.String(s.properties.Linker_config_src))
 	output := root.Join(ctx, "system", "etc", "linker.config.pb")
+
+	// we need "Module"s for packaging items
 	var otherModules []android.Module
+	deps := s.GatherPackagingSpecs(ctx)
 	ctx.WalkDeps(func(child, parent android.Module) bool {
-		// Don't track direct dependencies that aren't not packaged
-		if parent == s {
-			if pi, ok := ctx.OtherModuleDependencyTag(child).(android.PackagingItem); !ok || !pi.IsPackagingItem() {
-				return false
+		for _, ps := range child.PackagingSpecs() {
+			if _, ok := deps[ps.RelPathInPackage()]; ok {
+				otherModules = append(otherModules, child)
 			}
 		}
-		otherModules = append(otherModules, child)
 		return true
 	})
+
 	builder := android.NewRuleBuilder(pctx, ctx)
 	linkerconfig.BuildLinkerConfig(ctx, builder, input, otherModules, output)
 	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
diff --git a/jar/jar.go b/jar/jar.go
index a8f06a4..f164ee1 100644
--- a/jar/jar.go
+++ b/jar/jar.go
@@ -77,7 +77,7 @@
 		Name:  MetaDir,
 		Extra: []byte{MetaDirExtra[1], MetaDirExtra[0], 0, 0},
 	}
-	dirHeader.SetMode(0700 | os.ModeDir)
+	dirHeader.SetMode(0755 | os.ModeDir)
 	dirHeader.SetModTime(DefaultTime)
 
 	return dirHeader
@@ -95,7 +95,7 @@
 		Method:             zip.Store,
 		UncompressedSize64: uint64(len(b)),
 	}
-	fh.SetMode(0700)
+	fh.SetMode(0644)
 	fh.SetModTime(DefaultTime)
 
 	return fh, b, nil
diff --git a/java/Android.bp b/java/Android.bp
index 623a6c5..680f3a1 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -45,6 +45,7 @@
         "genrule.go",
         "hiddenapi.go",
         "hiddenapi_modular.go",
+        "hiddenapi_monolithic.go",
         "hiddenapi_singleton.go",
         "jacoco.go",
         "java.go",
diff --git a/java/androidmk.go b/java/androidmk.go
index 0154544..04357e0 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -460,6 +460,8 @@
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+		// introduce a flag variable to control the generation of the .config file
+		entries.SetString("LOCAL_DISABLE_TEST_CONFIG", "true")
 	})
 
 	return entriesList
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 5eaa77b..246c0eb 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -188,3 +188,21 @@
 
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_SOONG_DEX_JAR", result.Config, []string{expectedSoongDexJar}, actualSoongDexJar)
 }
+
+func TestAndroidTestHelperApp_LocalDisableTestConfig(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_test_helper_app {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+
+	expected := []string{"true"}
+	actual := entries.EntryMap["LOCAL_DISABLE_TEST_CONFIG"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected flag value - expected: %q, actual: %q", expected, actual)
+	}
+}
diff --git a/java/app.go b/java/app.go
index 5695022..fc1ace0 100755
--- a/java/app.go
+++ b/java/app.go
@@ -893,7 +893,7 @@
 	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
-func (a *AndroidApp) PreventInstall() {
+func (a *AndroidApp) SetPreventInstall() {
 	a.appProperties.PreventInstall = true
 }
 
diff --git a/java/app_import.go b/java/app_import.go
index 839051e..6fe6204 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -99,6 +99,9 @@
 	// If set, create package-export.apk, which other packages can
 	// use to get PRODUCT-agnostic resource data like IDs and type definitions.
 	Export_package_resources *bool
+
+	// Optional. Install to a subdirectory of the default install path for the module
+	Relative_install_path *string
 }
 
 func (a *AndroidAppImport) IsInstallable() bool {
@@ -263,20 +266,25 @@
 	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
 	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
 
-	var installDir android.InstallPath
+	var pathFragments []string
+	relInstallPath := String(a.properties.Relative_install_path)
 
 	if a.isPrebuiltFrameworkRes() {
 		// framework-res.apk is installed as system/framework/framework-res.apk
-		installDir = android.PathForModuleInstall(ctx, "framework")
+		if relInstallPath != "" {
+			ctx.PropertyErrorf("relative_install_path", "Relative_install_path cannot be set for framework-res")
+		}
+		pathFragments = []string{"framework"}
 		a.preprocessed = true
 	} else if Bool(a.properties.Privileged) {
-		installDir = android.PathForModuleInstall(ctx, "priv-app", a.BaseModuleName())
+		pathFragments = []string{"priv-app", relInstallPath, a.BaseModuleName()}
 	} else if ctx.InstallInTestcases() {
-		installDir = android.PathForModuleInstall(ctx, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch())
+		pathFragments = []string{relInstallPath, a.BaseModuleName(), ctx.DeviceConfig().DeviceArch()}
 	} else {
-		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
+		pathFragments = []string{"app", relInstallPath, a.BaseModuleName()}
 	}
 
+	installDir := android.PathForModuleInstall(ctx, pathFragments...)
 	a.dexpreopter.isApp = true
 	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
 	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 147ae45..024a3df 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -493,6 +493,69 @@
 	}
 }
 
+func TestAndroidAppImport_relativeInstallPath(t *testing.T) {
+	bp := `
+		android_app_import {
+			name: "no_relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+		}
+
+		android_app_import {
+			name: "relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			relative_install_path: "my/path",
+		}
+
+		android_app_import {
+			name: "framework-res",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			prefer: true,
+		}
+
+		android_app_import {
+			name: "privileged_relative_install_path",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			privileged: true,
+			relative_install_path: "my/path"
+		}
+		`
+	testCases := []struct {
+		name                string
+		expectedInstallPath string
+		errorMessage        string
+	}{
+		{
+			name:                "no_relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/app/no_relative_install_path/no_relative_install_path.apk",
+			errorMessage:        "Install path is not correct when relative_install_path is missing",
+		},
+		{
+			name:                "relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/app/my/path/relative_install_path/relative_install_path.apk",
+			errorMessage:        "Install path is not correct for app when relative_install_path is present",
+		},
+		{
+			name:                "prebuilt_framework-res",
+			expectedInstallPath: "out/soong/target/product/test_device/system/framework/framework-res.apk",
+			errorMessage:        "Install path is not correct for framework-res",
+		},
+		{
+			name:                "privileged_relative_install_path",
+			expectedInstallPath: "out/soong/target/product/test_device/system/priv-app/my/path/privileged_relative_install_path/privileged_relative_install_path.apk",
+			errorMessage:        "Install path is not correct for privileged app when relative_install_path is present",
+		},
+	}
+	for _, testCase := range testCases {
+		ctx, _ := testJava(t, bp)
+		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*AndroidAppImport)
+		android.AssertPathRelativeToTopEquals(t, testCase.errorMessage, testCase.expectedInstallPath, mod.installPath)
+	}
+}
+
 func TestAndroidTestImport(t *testing.T) {
 	ctx, _ := testJava(t, `
 		android_test_import {
diff --git a/java/base.go b/java/base.go
index 19c85cd..1daa108 100644
--- a/java/base.go
+++ b/java/base.go
@@ -155,6 +155,11 @@
 
 		// List of java_plugin modules that provide extra errorprone checks.
 		Extra_check_modules []string
+
+		// Whether to run errorprone on a normal build. If this is false, errorprone
+		// will still be run if the RUN_ERROR_PRONE environment variable is true.
+		// Default false.
+		Enabled *bool
 	}
 
 	Proto struct {
@@ -229,12 +234,6 @@
 	// otherwise provides defaults libraries to add to the bootclasspath.
 	System_modules *string
 
-	// The name of the module as used in build configuration.
-	//
-	// Allows a library to separate its actual name from the name used in
-	// build configuration, e.g.ctx.Config().BootJars().
-	ConfigurationName *string `blueprint:"mutated"`
-
 	// set the name of the output
 	Stem *string
 
@@ -707,7 +706,7 @@
 	// javaVersion flag.
 	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), android.SdkContext(j))
 
-	if ctx.Config().RunErrorProne() {
+	if ctx.Config().RunErrorProne() || Bool(j.properties.Errorprone.Enabled) {
 		if config.ErrorProneClasspath == nil && ctx.Config().TestProductVariables == nil {
 			ctx.ModuleErrorf("cannot build with Error Prone, missing external/error_prone?")
 		}
@@ -895,8 +894,8 @@
 		kotlincFlags := j.properties.Kotlincflags
 		CheckKotlincFlags(ctx, kotlincFlags)
 
-		// Dogfood the JVM_IR backend.
-		kotlincFlags = append(kotlincFlags, "-Xuse-ir")
+		// Workaround for KT-46512
+		kotlincFlags = append(kotlincFlags, "-Xsam-conversions=class")
 
 		// If there are kotlin files, compile them first but pass all the kotlin and java files
 		// kotlinc will use the java files to resolve types referenced by the kotlin files, but
@@ -978,14 +977,23 @@
 	}
 	if len(uniqueSrcFiles) > 0 || len(srcJars) > 0 {
 		var extraJarDeps android.Paths
-		if ctx.Config().RunErrorProne() {
-			// If error-prone is enabled, add an additional rule to compile the java files into
-			// a separate set of classes (so that they don't overwrite the normal ones and require
-			// a rebuild when error-prone is turned off).
-			// TODO(ccross): Once we always compile with javac9 we may be able to conditionally
-			//    enable error-prone without affecting the output class files.
+		if Bool(j.properties.Errorprone.Enabled) {
+			// If error-prone is enabled, enable errorprone flags on the regular
+			// build.
+			flags = enableErrorproneFlags(flags)
+		} else if ctx.Config().RunErrorProne() {
+			// Otherwise, if the RUN_ERROR_PRONE environment variable is set, create
+			// a new jar file just for compiling with the errorprone compiler to.
+			// This is because we don't want to cause the java files to get completely
+			// rebuilt every time the state of the RUN_ERROR_PRONE variable changes.
+			// We also don't want to run this if errorprone is enabled by default for
+			// this module, or else we could have duplicated errorprone messages.
+			errorproneFlags := enableErrorproneFlags(flags)
 			errorprone := android.PathForModuleOut(ctx, "errorprone", jarName)
-			RunErrorProne(ctx, errorprone, uniqueSrcFiles, srcJars, flags)
+
+			transformJavaToClasses(ctx, errorprone, -1, uniqueSrcFiles, srcJars, errorproneFlags, nil,
+				"errorprone", "errorprone")
+
 			extraJarDeps = append(extraJarDeps, errorprone)
 		}
 
@@ -1177,8 +1185,14 @@
 		j.properties.Instrument = true
 	}
 
+	// enforce syntax check to jacoco filters for any build (http://b/183622051)
+	specs := j.jacocoModuleToZipCommand(ctx)
+	if ctx.Failed() {
+		return
+	}
+
 	if j.shouldInstrument(ctx) {
-		outputFile = j.instrument(ctx, flags, outputFile, jarName)
+		outputFile = j.instrument(ctx, flags, outputFile, jarName, specs)
 	}
 
 	// merge implementation jar with resources if necessary
@@ -1217,10 +1231,6 @@
 				return
 			}
 
-			// Hidden API CSV generation and dex encoding
-			dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, j.implementationJarFile,
-				proptools.Bool(j.dexProperties.Uncompress_dex))
-
 			// merge dex jar with resources if necessary
 			if j.resourceJar != nil {
 				jars := android.Paths{dexOutputFile, j.resourceJar}
@@ -1236,6 +1246,12 @@
 				}
 			}
 
+			// Initialize the hiddenapi structure.
+			j.initHiddenAPI(ctx, dexOutputFile, j.implementationJarFile, j.dexProperties.Uncompress_dex)
+
+			// Encode hidden API flags in dex file, if needed.
+			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
+
 			j.dexJarFile = dexOutputFile
 
 			// Dexpreopting
@@ -1301,6 +1317,21 @@
 	j.outputFile = outputFile.WithoutRel()
 }
 
+// Returns a copy of the supplied flags, but with all the errorprone-related
+// fields copied to the regular build's fields.
+func enableErrorproneFlags(flags javaBuilderFlags) javaBuilderFlags {
+	flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
+
+	if len(flags.errorProneExtraJavacFlags) > 0 {
+		if len(flags.javacFlags) > 0 {
+			flags.javacFlags += " " + flags.errorProneExtraJavacFlags
+		} else {
+			flags.javacFlags = flags.errorProneExtraJavacFlags
+		}
+	}
+	return flags
+}
+
 func (j *Module) compileJavaClasses(ctx android.ModuleContext, jarName string, idx int,
 	srcFiles, srcJars android.Paths, flags javaBuilderFlags, extraJarDeps android.Paths) android.WritablePath {
 
@@ -1390,9 +1421,7 @@
 }
 
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
-	classesJar android.Path, jarName string) android.OutputPath {
-
-	specs := j.jacocoModuleToZipCommand(ctx)
+	classesJar android.Path, jarName string, specs string) android.OutputPath {
 
 	jacocoReportClassesFile := android.PathForModuleOut(ctx, "jacoco-report-classes", jarName)
 	instrumentedJar := android.PathForModuleOut(ctx, "jacoco", jarName).OutputPath
@@ -1495,15 +1524,6 @@
 	return proptools.StringDefault(j.deviceProperties.Stem, j.Name())
 }
 
-// ConfigurationName returns the name of the module as used in build configuration.
-//
-// This is usually the same as BaseModuleName() except for the <x>.impl libraries created by
-// java_sdk_library in which case this is the BaseModuleName() without the ".impl" suffix,
-// i.e. just <x>.
-func (j *Module) ConfigurationName() string {
-	return proptools.StringDefault(j.deviceProperties.ConfigurationName, j.BaseModuleName())
-}
-
 func (j *Module) JacocoReportClassesFile() android.Path {
 	return j.jacocoReportClassesFile
 }
@@ -1787,3 +1807,9 @@
 func (j *Module) ProvidesUsesLib() *string {
 	return j.usesLibraryProperties.Provides_uses_lib
 }
+
+type ModuleWithStem interface {
+	Stem() string
+}
+
+var _ ModuleWithStem = (*Module)(nil)
diff --git a/java/boot_jars.go b/java/boot_jars.go
index 1fb3deb..7abda80 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -89,7 +89,7 @@
 		name := android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName(module))
 		if apex, ok := moduleToApex[name]; ok {
 			apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
-			if (apex == "platform" && apexInfo.IsForPlatform()) || apexInfo.InApexByBaseName(apex) {
+			if (apex == "platform" && apexInfo.IsForPlatform()) || apexInfo.InApexModule(apex) {
 				// The module name/apex variant should be unique in the system but double check
 				// just in case something has gone wrong.
 				if existing, ok := nameToApexVariant[name]; ok {
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 02833ab..eddcc83 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -29,7 +29,7 @@
 
 func registerBootclasspathBuildComponents(ctx android.RegistrationContext) {
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("bootclasspath_deps", bootclasspathDepsMutator)
+		ctx.BottomUp("bootclasspath_deps", bootclasspathDepsMutator).Parallel()
 	})
 }
 
@@ -95,6 +95,15 @@
 	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
 		ctx.AddVariationDependencies(variations, tag, prebuiltName)
 		addedDep = true
+	} else if ctx.Config().AlwaysUsePrebuiltSdks() && len(variations) > 0 {
+		// TODO(b/179354495): Remove this code path once the Android build has been fully migrated to
+		//  use bootclasspath_fragment properly.
+		// Some prebuilt java_sdk_library modules do not yet have an APEX variations so try and add a
+		// dependency on the non-APEX variant.
+		if ctx.OtherModuleDependencyVariantExists(nil, prebuiltName) {
+			ctx.AddVariationDependencies(nil, tag, prebuiltName)
+			addedDep = true
+		}
 	}
 
 	// If no appropriate variant existing for this, so no dependency could be added, then it is an
@@ -226,12 +235,3 @@
 	m[android.SdkCorePlatform] = p.Core_platform_api.Stub_libs
 	return m
 }
-
-// bootclasspathApiInfo contains paths resolved from BootclasspathAPIProperties
-type bootclasspathApiInfo struct {
-	// stubJarsByKind maps from the android.SdkKind to the paths containing dex stub jars for each
-	// kind.
-	stubJarsByKind map[android.SdkKind]android.Paths
-}
-
-var bootclasspathApiInfoProvider = blueprint.NewProvider(bootclasspathApiInfo{})
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index bed629d..fc8f557 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -22,6 +22,7 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+
 	"github.com/google/blueprint/proptools"
 
 	"github.com/google/blueprint"
@@ -61,8 +62,14 @@
 }
 
 // SdkMemberType causes dependencies added with this tag to be automatically added to the sdk as if
-// they were specified using java_boot_libs.
-func (b bootclasspathFragmentContentDependencyTag) SdkMemberType(_ android.Module) android.SdkMemberType {
+// they were specified using java_boot_libs or java_sdk_libs.
+func (b bootclasspathFragmentContentDependencyTag) SdkMemberType(child android.Module) android.SdkMemberType {
+	// If the module is a java_sdk_library then treat it as if it was specified in the java_sdk_libs
+	// property, otherwise treat if it was specified in the java_boot_libs property.
+	if javaSdkLibrarySdkMemberType.IsInstance(child) {
+		return javaSdkLibrarySdkMemberType
+	}
+
 	return javaBootLibsSdkMemberType
 }
 
@@ -70,12 +77,17 @@
 	return true
 }
 
+// Contents of bootclasspath fragments in an apex are considered to be directly in the apex, as if
+// they were listed in java_libs.
+func (b bootclasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
+
 // The tag used for the dependency between the bootclasspath_fragment module and its contents.
 var bootclasspathFragmentContentDepTag = bootclasspathFragmentContentDependencyTag{}
 
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
 var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
 var _ android.SdkMemberTypeDependencyTag = bootclasspathFragmentContentDepTag
+var _ android.CopyDirectlyInAnyApexTag = bootclasspathFragmentContentDepTag
 
 func IsBootclasspathFragmentContentDepTag(tag blueprint.DependencyTag) bool {
 	return tag == bootclasspathFragmentContentDepTag
@@ -85,6 +97,9 @@
 type BootclasspathFragmentCoverageAffectedProperties struct {
 	// The contents of this bootclasspath_fragment, could be either java_library, or java_sdk_library.
 	//
+	// A java_sdk_library specified here will also be treated as if it was specified on the stub_libs
+	// property.
+	//
 	// The order of this list matters as it is the order that is used in the bootclasspath.
 	Contents []string
 
@@ -103,6 +118,11 @@
 	Coverage BootclasspathFragmentCoverageAffectedProperties
 
 	Hidden_api HiddenAPIFlagFileProperties
+
+	// Properties that allow a fragment to depend on other fragments. This is needed for hidden API
+	// processing as it needs access to all the classes used by a fragment including those provided
+	// by other fragments.
+	BootclasspathFragmentsDepsProperties
 }
 
 type BootclasspathFragmentModule struct {
@@ -114,6 +134,17 @@
 	properties bootclasspathFragmentProperties
 }
 
+// commonBootclasspathFragment defines the methods that are implemented by both source and prebuilt
+// bootclasspath fragment modules.
+type commonBootclasspathFragment interface {
+	// produceHiddenAPIAllFlagsFile produces the all-flags.csv and intermediate files.
+	//
+	// Updates the supplied hiddenAPIInfo with the paths to the generated files set.
+	produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIFlagOutput
+}
+
+var _ commonBootclasspathFragment = (*BootclasspathFragmentModule)(nil)
+
 func bootclasspathFragmentFactory() android.Module {
 	m := &BootclasspathFragmentModule{}
 	m.AddProperties(&m.properties)
@@ -143,61 +174,70 @@
 // necessary.
 func bootclasspathFragmentInitContentsFromImage(ctx android.EarlyModuleContext, m *BootclasspathFragmentModule) {
 	contents := m.properties.Contents
-	if m.properties.Image_name == nil && len(contents) == 0 {
-		ctx.ModuleErrorf(`neither of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
+	if len(contents) == 0 {
+		ctx.PropertyErrorf("contents", "required property is missing")
+		return
+	}
+
+	if m.properties.Image_name == nil {
+		// Nothing to do.
+		return
 	}
 
 	imageName := proptools.String(m.properties.Image_name)
-	if imageName == "art" {
-		// TODO(b/177892522): Prebuilts (versioned or not) should not use the image_name property.
-		if android.IsModuleInVersionedSdk(m) {
-			// The module is a versioned prebuilt so ignore it. This is done for a couple of reasons:
-			// 1. There is no way to use this at the moment so ignoring it is safe.
-			// 2. Attempting to initialize the contents property from the configuration will end up having
-			//    the versioned prebuilt depending on the unversioned prebuilt. That will cause problems
-			//    as the unversioned prebuilt could end up with an APEX variant created for the source
-			//    APEX which will prevent it from having an APEX variant for the prebuilt APEX which in
-			//    turn will prevent it from accessing the dex implementation jar from that which will
-			//    break hidden API processing, amongst others.
-			return
-		}
-
-		// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
-		// too early in the Soong processing for that to work.
-		global := dexpreopt.GetGlobalConfig(ctx)
-		modules := global.ArtApexJars
-
-		// Make sure that the apex specified in the configuration is consistent and is one for which
-		// this boot image is available.
-		commonApex := ""
-		for i := 0; i < modules.Len(); i++ {
-			apex := modules.Apex(i)
-			jar := modules.Jar(i)
-			if apex == "platform" {
-				ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
-				continue
-			}
-			if !m.AvailableFor(apex) {
-				ctx.ModuleErrorf("ArtApexJars configuration incompatible with this module, ArtApexJars expects this to be in apex %q but this is only in apexes %q",
-					apex, m.ApexAvailable())
-				continue
-			}
-			if commonApex == "" {
-				commonApex = apex
-			} else if commonApex != apex {
-				ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
-					commonApex, apex)
-			}
-		}
-
-		if len(contents) != 0 {
-			// Nothing to do.
-			return
-		}
-
-		// Store the jars in the Contents property so that they can be used to add dependencies.
-		m.properties.Contents = modules.CopyOfJars()
+	if imageName != "art" {
+		ctx.PropertyErrorf("image_name", `unknown image name %q, expected "art"`, imageName)
+		return
 	}
+
+	// TODO(b/177892522): Prebuilts (versioned or not) should not use the image_name property.
+	if android.IsModuleInVersionedSdk(m) {
+		// The module is a versioned prebuilt so ignore it. This is done for a couple of reasons:
+		// 1. There is no way to use this at the moment so ignoring it is safe.
+		// 2. Attempting to initialize the contents property from the configuration will end up having
+		//    the versioned prebuilt depending on the unversioned prebuilt. That will cause problems
+		//    as the unversioned prebuilt could end up with an APEX variant created for the source
+		//    APEX which will prevent it from having an APEX variant for the prebuilt APEX which in
+		//    turn will prevent it from accessing the dex implementation jar from that which will
+		//    break hidden API processing, amongst others.
+		return
+	}
+
+	// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
+	// too early in the Soong processing for that to work.
+	global := dexpreopt.GetGlobalConfig(ctx)
+	modules := global.ArtApexJars
+
+	// Make sure that the apex specified in the configuration is consistent and is one for which
+	// this boot image is available.
+	commonApex := ""
+	for i := 0; i < modules.Len(); i++ {
+		apex := modules.Apex(i)
+		jar := modules.Jar(i)
+		if apex == "platform" {
+			ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
+			continue
+		}
+		if !m.AvailableFor(apex) {
+			ctx.ModuleErrorf("ArtApexJars configuration incompatible with this module, ArtApexJars expects this to be in apex %q but this is only in apexes %q",
+				apex, m.ApexAvailable())
+			continue
+		}
+		if commonApex == "" {
+			commonApex = apex
+		} else if commonApex != apex {
+			ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
+				commonApex, apex)
+		}
+	}
+
+	if len(contents) != 0 {
+		// Nothing to do.
+		return
+	}
+
+	// Store the jars in the Contents property so that they can be used to add dependencies.
+	m.properties.Contents = modules.CopyOfJars()
 }
 
 // bootclasspathImageNameContentsConsistencyCheck checks that the configuration that applies to this
@@ -245,57 +285,40 @@
 // BootclasspathFragmentApexContentInfo contains the bootclasspath_fragments contributions to the
 // apex contents.
 type BootclasspathFragmentApexContentInfo struct {
-	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
-	//
-	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
-	// for more details.
-	ClasspathFragmentProtoOutput android.OutputPath
+	// The configured modules, will be empty if this is from a bootclasspath_fragment that does not
+	// set image_name: "art".
+	modules android.ConfiguredJarList
 
-	// ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file.
-	//
-	// The path encodes expected sub-location within partitions, i.e. etc/classpaths/<proto-file>,
-	// for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path
-	// use android.InstallPath#Rel().
-	//
-	// This is only relevant for APEX modules as they perform their own installation; while regular
-	// system files are installed via ClasspathFragmentBase#androidMkEntries().
-	ClasspathFragmentProtoInstallDir android.InstallPath
+	// Map from arch type to the boot image files.
+	bootImageFilesByArch map[android.ArchType]android.OutputPaths
 
-	// The image config, internal to this module (and the dex_bootjars singleton).
-	//
-	// Will be nil if the BootclasspathFragmentApexContentInfo has not been provided for a specific module. That can occur
-	// when SkipDexpreoptBootJars(ctx) returns true.
-	imageConfig *bootImageConfig
+	// Map from the name of the context module (as returned by Name()) to the hidden API encoded dex
+	// jar path.
+	contentModuleDexJarPaths map[string]android.Path
 }
 
 func (i BootclasspathFragmentApexContentInfo) Modules() android.ConfiguredJarList {
-	return i.imageConfig.modules
+	return i.modules
 }
 
 // Get a map from ArchType to the associated boot image's contents for Android.
 //
 // Extension boot images only return their own files, not the files of the boot images they extend.
 func (i BootclasspathFragmentApexContentInfo) AndroidBootImageFilesByArchType() map[android.ArchType]android.OutputPaths {
-	files := map[android.ArchType]android.OutputPaths{}
-	if i.imageConfig != nil {
-		for _, variant := range i.imageConfig.variants {
-			// We also generate boot images for host (for testing), but we don't need those in the apex.
-			// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
-			if variant.target.Os == android.Android {
-				files[variant.target.Arch.ArchType] = variant.imagesDeps
-			}
-		}
-	}
-	return files
+	return i.bootImageFilesByArch
 }
 
 // DexBootJarPathForContentModule returns the path to the dex boot jar for specified module.
 //
 // The dex boot jar is one which has had hidden API encoding performed on it.
-func (i BootclasspathFragmentApexContentInfo) DexBootJarPathForContentModule(module android.Module) android.Path {
-	j := module.(UsesLibraryDependency)
-	dexJar := j.DexJarBuildPath()
-	return dexJar
+func (i BootclasspathFragmentApexContentInfo) DexBootJarPathForContentModule(module android.Module) (android.Path, error) {
+	name := module.Name()
+	if dexJar, ok := i.contentModuleDexJarPaths[name]; ok {
+		return dexJar, nil
+	} else {
+		return nil, fmt.Errorf("unknown bootclasspath_fragment content module %s, expected one of %s",
+			name, strings.Join(android.SortedStringKeys(i.contentModuleDexJarPaths), ", "))
+	}
 }
 
 func (b *BootclasspathFragmentModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
@@ -350,6 +373,11 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
+func (b *BootclasspathFragmentModule) BootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
+	// Add dependencies on all the fragments.
+	b.properties.BootclasspathFragmentsDepsProperties.addDependenciesOntoFragments(ctx)
+}
+
 func (b *BootclasspathFragmentModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Only perform a consistency check if this module is the active module. That will prevent an
 	// unused prebuilt that was created without instrumentation from breaking an instrumentation
@@ -361,27 +389,100 @@
 	// Generate classpaths.proto config
 	b.generateClasspathProtoBuildActions(ctx)
 
+	// Gather the bootclasspath fragment's contents.
+	var contents []android.Module
+	ctx.VisitDirectDeps(func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		if IsBootclasspathFragmentContentDepTag(tag) {
+			contents = append(contents, module)
+		}
+	})
+
+	fragments := gatherApexModulePairDepsWithTag(ctx, bootclasspathFragmentDepTag)
+
 	// Perform hidden API processing.
-	b.generateHiddenAPIBuildActions(ctx)
+	hiddenAPIFlagOutput := b.generateHiddenAPIBuildActions(ctx, contents, fragments)
 
-	// Construct the boot image info from the config.
-	info := BootclasspathFragmentApexContentInfo{
-		ClasspathFragmentProtoInstallDir: b.classpathFragmentBase().installDirPath,
-		ClasspathFragmentProtoOutput:     b.classpathFragmentBase().outputFilepath,
-		imageConfig:                      nil,
+	// Verify that the image_name specified on a bootclasspath_fragment is valid even if this is a
+	// prebuilt which will not use the image config.
+	imageConfig := b.getImageConfig(ctx)
+
+	// A prebuilt fragment cannot contribute to the apex.
+	if !android.IsModulePrebuilt(ctx.Module()) {
+		// Provide the apex content info.
+		b.provideApexContentInfo(ctx, imageConfig, contents, hiddenAPIFlagOutput)
+	}
+}
+
+// provideApexContentInfo creates, initializes and stores the apex content info for use by other
+// modules.
+func (b *BootclasspathFragmentModule) provideApexContentInfo(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module, hiddenAPIFlagOutput *HiddenAPIFlagOutput) {
+	// Construct the apex content info from the config.
+	info := BootclasspathFragmentApexContentInfo{}
+
+	// Populate the apex content info with paths to the dex jars.
+	b.populateApexContentInfoDexJars(ctx, &info, contents, hiddenAPIFlagOutput)
+
+	if imageConfig != nil {
+		info.modules = imageConfig.modules
+
+		if !SkipDexpreoptBootJars(ctx) {
+			// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
+			// GenerateSingletonBuildActions method as it cannot create it for itself.
+			dexpreopt.GetGlobalSoongConfig(ctx)
+
+			// Only generate the boot image if the configuration does not skip it.
+			if b.generateBootImageBuildActions(ctx, contents, imageConfig) {
+				// Allow the apex to access the boot image files.
+				files := map[android.ArchType]android.OutputPaths{}
+				for _, variant := range imageConfig.variants {
+					// We also generate boot images for host (for testing), but we don't need those in the apex.
+					// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
+					if variant.target.Os == android.Android {
+						files[variant.target.Arch.ArchType] = variant.imagesDeps
+					}
+				}
+				info.bootImageFilesByArch = files
+			}
+		}
 	}
 
-	if !SkipDexpreoptBootJars(ctx) {
-		// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
-		// GenerateSingletonBuildActions method as it cannot create it for itself.
-		dexpreopt.GetGlobalSoongConfig(ctx)
-		info.imageConfig = b.getImageConfig(ctx)
-	}
-
-	// Make it available for other modules.
+	// Make the apex content info available for other modules.
 	ctx.SetProvider(BootclasspathFragmentApexContentInfoProvider, info)
 }
 
+// populateApexContentInfoDexJars adds paths to the dex jars provided by this fragment to the
+// apex content info.
+func (b *BootclasspathFragmentModule) populateApexContentInfoDexJars(ctx android.ModuleContext, info *BootclasspathFragmentApexContentInfo, contents []android.Module, hiddenAPIFlagOutput *HiddenAPIFlagOutput) {
+
+	info.contentModuleDexJarPaths = map[string]android.Path{}
+	if hiddenAPIFlagOutput != nil {
+		// Hidden API encoding has been performed.
+		flags := hiddenAPIFlagOutput.AllFlagsPath
+		for _, m := range contents {
+			h := m.(hiddenAPIModule)
+			unencodedDex := h.bootDexJar()
+			if unencodedDex == nil {
+				// This is an error. Sometimes Soong will report the error directly, other times it will
+				// defer the error reporting to happen only when trying to use the missing file in ninja.
+				// Either way it is handled by extractBootDexJarsFromModules which must have been
+				// called before this as it generates the flags that are used to encode these files.
+				continue
+			}
+
+			outputDir := android.PathForModuleOut(ctx, "hiddenapi-modular/encoded").OutputPath
+			encodedDex := hiddenAPIEncodeDex(ctx, unencodedDex, flags, *h.uncompressDex(), outputDir)
+			info.contentModuleDexJarPaths[m.Name()] = encodedDex
+		}
+	} else {
+		for _, m := range contents {
+			j := m.(UsesLibraryDependency)
+			dexJar := j.DexJarBuildPath()
+			info.contentModuleDexJarPaths[m.Name()] = dexJar
+		}
+	}
+}
+
 // generateClasspathProtoBuildActions generates all required build actions for classpath.proto config
 func (b *BootclasspathFragmentModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
 	var classpathJars []classpathJar
@@ -395,8 +496,36 @@
 }
 
 func (b *BootclasspathFragmentModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
-	// TODO(satayev): populate with actual content
-	return android.EmptyConfiguredJarList()
+	if "art" == proptools.String(b.properties.Image_name) {
+		return b.getImageConfig(ctx).modules
+	}
+
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	// Convert content names to their appropriate stems, in case a test library is overriding an actual boot jar
+	var stems []string
+	for _, name := range b.properties.Contents {
+		dep := ctx.GetDirectDepWithTag(name, bootclasspathFragmentContentDepTag)
+		if m, ok := dep.(ModuleWithStem); ok {
+			stems = append(stems, m.Stem())
+		} else {
+			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
+		}
+	}
+
+	// Only create configs for updatable boot jars. Non-updatable boot jars must be part of the
+	// platform_bootclasspath's classpath proto config to guarantee that they come before any
+	// updatable jars at runtime.
+	jars := global.UpdatableBootJars.Filter(stems)
+
+	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
+	// config. However, any test specific jars would not be present in UpdatableBootJars. Instead,
+	// we should check if we are creating a config for apex_test via ApexInfo and amend the values.
+	// This is an exception to support end-to-end test for SdkExtensions, until such support exists.
+	if android.InList("test_framework-sdkextensions", stems) {
+		jars = jars.Append("com.android.sdkext", "test_framework-sdkextensions")
+	}
+	return jars
 }
 
 func (b *BootclasspathFragmentModule) getImageConfig(ctx android.EarlyModuleContext) *bootImageConfig {
@@ -419,19 +548,111 @@
 }
 
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
-func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext) {
-	// Resolve the properties to paths.
-	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
+func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) *HiddenAPIFlagOutput {
 
-	// Store the information for use by platform_bootclasspath.
-	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
+	// Create hidden API input structure.
+	input := b.createHiddenAPIFlagInput(ctx, contents, fragments)
 
-	// Convert the kind specific lists of modules into kind specific lists of jars.
-	stubJarsByKind := hiddenAPIGatherStubLibDexJarPaths(ctx)
+	var output *HiddenAPIFlagOutput
 
-	// Store the information for use by other modules.
-	bootclasspathApiInfo := bootclasspathApiInfo{stubJarsByKind: stubJarsByKind}
-	ctx.SetProvider(bootclasspathApiInfoProvider, bootclasspathApiInfo)
+	// Hidden API processing is conditional as a temporary workaround as not all
+	// bootclasspath_fragments provide the appropriate information needed for hidden API processing
+	// which leads to breakages of the build.
+	// TODO(b/179354495): Stop hidden API processing being conditional once all bootclasspath_fragment
+	//  modules have been updated to support it.
+	if input.canPerformHiddenAPIProcessing(ctx, b.properties) {
+		// Delegate the production of the hidden API all-flags.csv file to a module type specific method.
+		common := ctx.Module().(commonBootclasspathFragment)
+		output = common.produceHiddenAPIAllFlagsFile(ctx, contents, input)
+	}
+
+	// Initialize a HiddenAPIInfo structure.
+	hiddenAPIInfo := HiddenAPIInfo{
+		// The monolithic hidden API processing needs access to the flag files that override the default
+		// flags from all the fragments whether or not they actually perform their own hidden API flag
+		// generation. That is because the monolithic hidden API processing uses those flag files to
+		// perform its own flag generation.
+		FlagFilesByCategory: input.FlagFilesByCategory,
+
+		// Other bootclasspath_fragments that depend on this need the transitive set of stub dex jars
+		// from this to resolve any references from their code to classes provided by this fragment
+		// and the fragments this depends upon.
+		TransitiveStubDexJarsByKind: input.transitiveStubDexJarsByKind(),
+	}
+
+	if output != nil {
+		// The monolithic hidden API processing also needs access to all the output files produced by
+		// hidden API processing of this fragment.
+		hiddenAPIInfo.HiddenAPIFlagOutput = *output
+	}
+
+	//  Provide it for use by other modules.
+	ctx.SetProvider(HiddenAPIInfoProvider, hiddenAPIInfo)
+
+	return output
+}
+
+// createHiddenAPIFlagInput creates a HiddenAPIFlagInput struct and initializes it with information derived
+// from the properties on this module and its dependencies.
+func (b *BootclasspathFragmentModule) createHiddenAPIFlagInput(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) HiddenAPIFlagInput {
+
+	// Merge the HiddenAPIInfo from all the fragment dependencies.
+	dependencyHiddenApiInfo := newHiddenAPIInfo()
+	dependencyHiddenApiInfo.mergeFromFragmentDeps(ctx, fragments)
+
+	// Create hidden API flag input structure.
+	input := newHiddenAPIFlagInput()
+
+	// Update the input structure with information obtained from the stub libraries.
+	input.gatherStubLibInfo(ctx, contents)
+
+	// Populate with flag file paths from the properties.
+	input.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api)
+
+	// Store the stub dex jars from this module's fragment dependencies.
+	input.DependencyStubDexJarsByKind = dependencyHiddenApiInfo.TransitiveStubDexJarsByKind
+
+	return input
+}
+
+// produceHiddenAPIAllFlagsFile produces the hidden API all-flags.csv file (and supporting files)
+// for the fragment.
+func (b *BootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIFlagOutput {
+	// Generate the rules to create the hidden API flags and update the supplied hiddenAPIInfo with the
+	// paths to the created files.
+	return hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx, contents, input)
+}
+
+// generateBootImageBuildActions generates ninja rules to create the boot image if required for this
+// module.
+//
+// Returns true if the boot image is created, false otherwise.
+func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.ModuleContext, contents []android.Module, imageConfig *bootImageConfig) bool {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if !shouldBuildBootImages(ctx.Config(), global) {
+		return false
+	}
+
+	// Bootclasspath fragment modules that are for the platform do not produce a boot image.
+	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+	if apexInfo.IsForPlatform() {
+		return false
+	}
+
+	// Bootclasspath fragment modules that are versioned do not produce a boot image.
+	if android.IsModuleInVersionedSdk(ctx.Module()) {
+		return false
+	}
+
+	// Copy the dex jars of this fragment's content modules to their predefined locations.
+	bootDexJarByModule := extractEncodedDexJarsFromModules(ctx, contents)
+	copyBootJarsToPredefinedLocations(ctx, bootDexJarByModule, imageConfig.dexPathsByModule)
+
+	// Build a profile for the image config and then use that to build the boot image.
+	profile := bootImageProfileRule(ctx, imageConfig)
+	buildBootImage(ctx, imageConfig, profile)
+
+	return true
 }
 
 type bootclasspathFragmentMemberType struct {
@@ -473,7 +694,22 @@
 	Core_platform_stub_libs []string
 
 	// Flag files by *hiddenAPIFlagFileCategory
-	Flag_files_by_category map[*hiddenAPIFlagFileCategory]android.Paths
+	Flag_files_by_category FlagFilesByCategory
+
+	// The path to the generated stub-flags.csv file.
+	Stub_flags_path android.OptionalPath
+
+	// The path to the generated annotation-flags.csv file.
+	Annotation_flags_path android.OptionalPath
+
+	// The path to the generated metadata.csv file.
+	Metadata_path android.OptionalPath
+
+	// The path to the generated index.csv file.
+	Index_path android.OptionalPath
+
+	// The path to the generated all-flags.csv file.
+	All_flags_path android.OptionalPath
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -482,10 +718,17 @@
 	b.Image_name = module.properties.Image_name
 	b.Contents = module.properties.Contents
 
-	// Get the flag file information from the module.
+	// Get the hidden API information from the module.
 	mctx := ctx.SdkModuleContext()
-	flagFileInfo := mctx.OtherModuleProvider(module, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
-	b.Flag_files_by_category = flagFileInfo.categoryToPaths
+	hiddenAPIInfo := mctx.OtherModuleProvider(module, HiddenAPIInfoProvider).(HiddenAPIInfo)
+	b.Flag_files_by_category = hiddenAPIInfo.FlagFilesByCategory
+
+	// Copy all the generated file paths.
+	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
+	b.Annotation_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AnnotationFlagsPath)
+	b.Metadata_path = android.OptionalPathForPath(hiddenAPIInfo.MetadataPath)
+	b.Index_path = android.OptionalPathForPath(hiddenAPIInfo.IndexPath)
+	b.All_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AllFlagsPath)
 
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
@@ -513,25 +756,65 @@
 		corePlatformApiPropertySet.AddPropertyWithTag("stub_libs", b.Core_platform_stub_libs, requiredMemberDependency)
 	}
 
+	hiddenAPISet := propertySet.AddPropertySet("hidden_api")
+	hiddenAPIDir := "hiddenapi"
+
+	// Copy manually curated flag files specified on the bootclasspath_fragment.
 	if b.Flag_files_by_category != nil {
-		hiddenAPISet := propertySet.AddPropertySet("hidden_api")
-		for _, category := range hiddenAPIFlagFileCategories {
+		for _, category := range HiddenAPIFlagFileCategories {
 			paths := b.Flag_files_by_category[category]
 			if len(paths) > 0 {
 				dests := []string{}
 				for _, p := range paths {
-					dest := filepath.Join("hiddenapi", p.Base())
+					dest := filepath.Join(hiddenAPIDir, p.Base())
 					builder.CopyToSnapshot(p, dest)
 					dests = append(dests, dest)
 				}
-				hiddenAPISet.AddProperty(category.propertyName, dests)
+				hiddenAPISet.AddProperty(category.PropertyName, dests)
 			}
 		}
 	}
+
+	copyOptionalPath := func(path android.OptionalPath, property string) {
+		if path.Valid() {
+			p := path.Path()
+			dest := filepath.Join(hiddenAPIDir, p.Base())
+			builder.CopyToSnapshot(p, dest)
+			hiddenAPISet.AddProperty(property, dest)
+		}
+	}
+
+	// Copy all the generated files, if available.
+	copyOptionalPath(b.Stub_flags_path, "stub_flags")
+	copyOptionalPath(b.Annotation_flags_path, "annotation_flags")
+	copyOptionalPath(b.Metadata_path, "metadata")
+	copyOptionalPath(b.Index_path, "index")
+	copyOptionalPath(b.All_flags_path, "all_flags")
 }
 
 var _ android.SdkMemberType = (*bootclasspathFragmentMemberType)(nil)
 
+// prebuiltBootclasspathFragmentProperties contains additional prebuilt_bootclasspath_fragment
+// specific properties.
+type prebuiltBootclasspathFragmentProperties struct {
+	Hidden_api struct {
+		// The path to the stub-flags.csv file created by the bootclasspath_fragment.
+		Stub_flags *string `android:"path"`
+
+		// The path to the annotation-flags.csv file created by the bootclasspath_fragment.
+		Annotation_flags *string `android:"path"`
+
+		// The path to the metadata.csv file created by the bootclasspath_fragment.
+		Metadata *string `android:"path"`
+
+		// The path to the index.csv file created by the bootclasspath_fragment.
+		Index *string `android:"path"`
+
+		// The path to the all-flags.csv file created by the bootclasspath_fragment.
+		All_flags *string `android:"path"`
+	}
+}
+
 // A prebuilt version of the bootclasspath_fragment module.
 //
 // At the moment this is basically just a bootclasspath_fragment module that can be used as a
@@ -540,6 +823,9 @@
 type prebuiltBootclasspathFragmentModule struct {
 	BootclasspathFragmentModule
 	prebuilt android.Prebuilt
+
+	// Additional prebuilt specific properties.
+	prebuiltProperties prebuiltBootclasspathFragmentProperties
 }
 
 func (module *prebuiltBootclasspathFragmentModule) Prebuilt() *android.Prebuilt {
@@ -550,9 +836,33 @@
 	return module.prebuilt.Name(module.ModuleBase.Name())
 }
 
+// produceHiddenAPIAllFlagsFile returns a path to the prebuilt all-flags.csv or nil if none is
+// specified.
+func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []android.Module, _ HiddenAPIFlagInput) *HiddenAPIFlagOutput {
+	pathForOptionalSrc := func(src *string) android.Path {
+		if src == nil {
+			// TODO(b/179354495): Fail if this is not provided once prebuilts have been updated.
+			return nil
+		}
+		return android.PathForModuleSrc(ctx, *src)
+	}
+
+	output := HiddenAPIFlagOutput{
+		StubFlagsPath:       pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags),
+		AnnotationFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Annotation_flags),
+		MetadataPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Metadata),
+		IndexPath:           pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Index),
+		AllFlagsPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags),
+	}
+
+	return &output
+}
+
+var _ commonBootclasspathFragment = (*prebuiltBootclasspathFragmentModule)(nil)
+
 func prebuiltBootclasspathFragmentFactory() android.Module {
 	m := &prebuiltBootclasspathFragmentModule{}
-	m.AddProperties(&m.properties)
+	m.AddProperties(&m.properties, &m.prebuiltProperties)
 	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
 	// array.
 	android.InitPrebuiltModule(m, &[]string{"placeholder"})
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index 32ed7ea..fba7d1a 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -29,38 +29,28 @@
 	dexpreopt.PrepareForTestByEnablingDexpreopt,
 )
 
-func TestUnknownBootclasspathFragment(t *testing.T) {
+func TestBootclasspathFragment_UnknownImageName(t *testing.T) {
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
 		RunTestWithBp(t, `
 			bootclasspath_fragment {
 				name: "unknown-bootclasspath-fragment",
 				image_name: "unknown",
+				contents: ["foo"],
 			}
 		`)
 }
 
-func TestUnknownBootclasspathFragmentImageName(t *testing.T) {
+func TestPrebuiltBootclasspathFragment_UnknownImageName(t *testing.T) {
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
-		RunTestWithBp(t, `
-			bootclasspath_fragment {
-				name: "unknown-bootclasspath-fragment",
-				image_name: "unknown",
-			}
-		`)
-}
-
-func TestUnknownPrebuiltBootclasspathFragment(t *testing.T) {
-	prepareForTestWithBootclasspathFragment.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
 		RunTestWithBp(t, `
 			prebuilt_bootclasspath_fragment {
 				name: "unknown-bootclasspath-fragment",
 				image_name: "unknown",
+				contents: ["foo"],
 			}
 		`)
 }
@@ -76,6 +66,7 @@
 			bootclasspath_fragment {
 				name: "bootclasspath-fragment",
 				image_name: "art",
+				contents: ["foo", "bar"],
 				apex_available: [
 					"apex",
 				],
@@ -94,6 +85,7 @@
 			bootclasspath_fragment {
 				name: "bootclasspath-fragment",
 				image_name: "art",
+				contents: ["foo", "bar"],
 				apex_available: [
 					"apex1",
 					"apex2",
@@ -102,17 +94,6 @@
 		`)
 }
 
-func TestBootclasspathFragmentWithoutImageNameOrContents(t *testing.T) {
-	prepareForTestWithBootclasspathFragment.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qneither of the "image_name" and "contents" properties\E`)).
-		RunTestWithBp(t, `
-			bootclasspath_fragment {
-				name: "bootclasspath-fragment",
-			}
-		`)
-}
-
 func TestBootclasspathFragment_Coverage(t *testing.T) {
 	prepareForTestWithFrameworkCoverage := android.FixtureMergeEnv(map[string]string{
 		"EMMA_INSTRUMENT":           "true",
@@ -204,7 +185,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("mysdklibrary", "mycoreplatform"),
+		FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary", "mycoreplatform"),
 	).RunTestWithBp(t, `
 		bootclasspath_fragment {
 			name: "myfragment",
@@ -212,7 +193,7 @@
 			api: {
 				stub_libs: [
 					"mystublib",
-					"mysdklibrary",
+					"myothersdklibrary",
 				],
 			},
 			core_platform_api: {
@@ -231,36 +212,50 @@
 		java_sdk_library {
 			name: "mysdklibrary",
 			srcs: ["a.java"],
-			compile_dex: true,
+			shared_library: false,
 			public: {enabled: true},
 			system: {enabled: true},
 		}
 
 		java_sdk_library {
+			name: "myothersdklibrary",
+			srcs: ["a.java"],
+			shared_library: false,
+			public: {enabled: true},
+		}
+
+		java_sdk_library {
 			name: "mycoreplatform",
 			srcs: ["a.java"],
-			compile_dex: true,
+			shared_library: false,
 			public: {enabled: true},
 		}
 	`)
 
 	fragment := result.Module("myfragment", "android_common")
-	info := result.ModuleProvider(fragment, bootclasspathApiInfoProvider).(bootclasspathApiInfo)
+	info := result.ModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
 
 	stubsJar := "out/soong/.intermediates/mystublib/android_common/dex/mystublib.jar"
 
-	// Check that SdkPublic uses public stubs.
+	// Stubs jars for mysdklibrary
 	publicStubsJar := "out/soong/.intermediates/mysdklibrary.stubs/android_common/dex/mysdklibrary.stubs.jar"
-	android.AssertPathsRelativeToTopEquals(t, "public dex stubs jar", []string{stubsJar, publicStubsJar}, info.stubJarsByKind[android.SdkPublic])
-
-	// Check that SdkSystem uses system stubs.
 	systemStubsJar := "out/soong/.intermediates/mysdklibrary.stubs.system/android_common/dex/mysdklibrary.stubs.system.jar"
-	android.AssertPathsRelativeToTopEquals(t, "system dex stubs jar", []string{stubsJar, systemStubsJar}, info.stubJarsByKind[android.SdkSystem])
 
-	// Check that SdkTest also uses system stubs as the mysdklibrary does not provide test stubs.
-	android.AssertPathsRelativeToTopEquals(t, "test dex stubs jar", []string{stubsJar, systemStubsJar}, info.stubJarsByKind[android.SdkTest])
+	// Stubs jars for myothersdklibrary
+	otherPublicStubsJar := "out/soong/.intermediates/myothersdklibrary.stubs/android_common/dex/myothersdklibrary.stubs.jar"
+
+	// Check that SdkPublic uses public stubs for all sdk libraries.
+	android.AssertPathsRelativeToTopEquals(t, "public dex stubs jar", []string{otherPublicStubsJar, publicStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkPublic])
+
+	// Check that SdkSystem uses system stubs for mysdklibrary and public stubs for myothersdklibrary
+	// as it does not provide system stubs.
+	android.AssertPathsRelativeToTopEquals(t, "system dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkSystem])
+
+	// Check that SdkTest also uses system stubs for mysdklibrary as it does not provide test stubs
+	// and public stubs for myothersdklibrary as it does not provide test stubs either.
+	android.AssertPathsRelativeToTopEquals(t, "test dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkTest])
 
 	// Check that SdkCorePlatform uses public stubs from the mycoreplatform library.
 	corePlatformStubsJar := "out/soong/.intermediates/mycoreplatform.stubs/android_common/dex/mycoreplatform.stubs.jar"
-	android.AssertPathsRelativeToTopEquals(t, "core platform dex stubs jar", []string{corePlatformStubsJar}, info.stubJarsByKind[android.SdkCorePlatform])
+	android.AssertPathsRelativeToTopEquals(t, "core platform dex stubs jar", []string{corePlatformStubsJar}, info.TransitiveStubDexJarsByKind[android.SdkCorePlatform])
 }
diff --git a/java/builder.go b/java/builder.go
index cde8731..ea011b8 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -279,23 +279,6 @@
 	transformJavaToClasses(ctx, outputFile, shardIdx, srcFiles, srcJars, flags, deps, "javac", desc)
 }
 
-func RunErrorProne(ctx android.ModuleContext, outputFile android.WritablePath,
-	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
-
-	flags.processorPath = append(flags.errorProneProcessorPath, flags.processorPath...)
-
-	if len(flags.errorProneExtraJavacFlags) > 0 {
-		if len(flags.javacFlags) > 0 {
-			flags.javacFlags += " " + flags.errorProneExtraJavacFlags
-		} else {
-			flags.javacFlags = flags.errorProneExtraJavacFlags
-		}
-	}
-
-	transformJavaToClasses(ctx, outputFile, -1, srcFiles, srcJars, flags, nil,
-		"errorprone", "errorprone")
-}
-
 // Emits the rule to generate Xref input file (.kzip file) for the given set of source files and source jars
 // to compile with given set of builder flags, etc.
 func emitXrefRule(ctx android.ModuleContext, xrefFile android.WritablePath, idx int,
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 7422fa2..0e14d24 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -18,6 +18,7 @@
 
 import (
 	"fmt"
+	"github.com/google/blueprint"
 	"strings"
 
 	"android/soong/android"
@@ -104,7 +105,7 @@
 }
 
 func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, jars []classpathJar) {
-	outputFilename := ctx.ModuleName() + ".pb"
+	outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
 	c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
 	c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
 
@@ -120,6 +121,12 @@
 		FlagWithOutput("--output=", c.outputFilepath)
 
 	rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
+
+	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
+		ClasspathFragmentProtoInstallDir: c.installDirPath,
+		ClasspathFragmentProtoOutput:     c.outputFilepath,
+	}
+	ctx.SetProvider(ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
 }
 
 func writeClasspathsJson(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
@@ -129,7 +136,7 @@
 	for idx, jar := range jars {
 		fmt.Fprintf(&content, "{\n")
 
-		fmt.Fprintf(&content, "\"relativePath\": \"%s\",\n", jar.path)
+		fmt.Fprintf(&content, "\"path\": \"%s\",\n", jar.path)
 		fmt.Fprintf(&content, "\"classpath\": \"%s\"\n", jar.classpath)
 
 		if idx < len(jars)-1 {
@@ -157,3 +164,23 @@
 		},
 	}}
 }
+
+var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider(ClasspathFragmentProtoContentInfo{})
+
+type ClasspathFragmentProtoContentInfo struct {
+	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
+	//
+	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
+	// for more details.
+	ClasspathFragmentProtoOutput android.OutputPath
+
+	// ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file.
+	//
+	// The path encodes expected sub-location within partitions, i.e. etc/classpaths/<proto-file>,
+	// for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path
+	// use android.InstallPath#Rel().
+	//
+	// This is only relevant for APEX modules as they perform their own installation; while regular
+	// system files are installed via ClasspathFragmentBase#androidMkEntries().
+	ClasspathFragmentProtoInstallDir android.InstallPath
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 8d23ad6..2e46d74 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -184,7 +184,7 @@
 		imagesDeps = append(imagesDeps, variant.imagesDeps)
 	}
 	// The image locations for all Android variants are identical.
-	hostImageLocations := bootImage.getAnyAndroidVariant().imageLocations()
+	hostImageLocations, deviceImageLocations := bootImage.getAnyAndroidVariant().imageLocations()
 
 	var profileClassListing android.OptionalPath
 	var profileBootListing android.OptionalPath
@@ -224,9 +224,10 @@
 		ProvidesUsesLibrary:            providesUsesLib,
 		ClassLoaderContexts:            d.classLoaderContexts,
 
-		Archs:                         archs,
-		DexPreoptImagesDeps:           imagesDeps,
-		DexPreoptImageLocationsOnHost: hostImageLocations,
+		Archs:                           archs,
+		DexPreoptImagesDeps:             imagesDeps,
+		DexPreoptImageLocationsOnHost:   hostImageLocations,
+		DexPreoptImageLocationsOnDevice: deviceImageLocations,
 
 		PreoptBootClassPathDexFiles:     dexFiles.Paths(),
 		PreoptBootClassPathDexLocations: dexLocations,
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index ce68c48..dc8df5e 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -15,7 +15,6 @@
 package java
 
 import (
-	"fmt"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -244,6 +243,9 @@
 	// Subdirectory where the image files are installed.
 	installDirOnHost string
 
+	// Subdirectory where the image files on device are installed.
+	installDirOnDevice string
+
 	// A list of (location, jar) pairs for the Java modules in this image.
 	modules android.ConfiguredJarList
 
@@ -251,6 +253,9 @@
 	dexPaths     android.WritablePaths // for this image
 	dexPathsDeps android.WritablePaths // for the dependency images and in this image
 
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
 	// File path to a zip archive with all image files (or nil, if not needed).
 	zip android.WritablePath
 
@@ -273,12 +278,24 @@
 	dexLocationsDeps []string // for the dependency images and in this image
 
 	// Paths to image files.
-	imagePathOnHost android.OutputPath  // first image file
-	imagesDeps      android.OutputPaths // all files
+	imagePathOnHost   android.OutputPath // first image file path on host
+	imagePathOnDevice string             // first image file path on device
 
-	// Only for extensions, paths to the primary boot images.
+	// All the files that constitute this image variant, i.e. .art, .oat and .vdex files.
+	imagesDeps android.OutputPaths
+
+	// The path to the primary image variant's imagePathOnHost field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
 	primaryImages android.OutputPath
 
+	// The paths to the primary image variant's imagesDeps field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
+	primaryImagesDeps android.Paths
+
 	// Rules which should be used in make to install the outputs.
 	installs           android.RuleBuilderInstalls
 	vdexInstalls       android.RuleBuilderInstalls
@@ -361,11 +378,12 @@
 // The location is passed as an argument to the ART tools like dex2oat instead of the real path.
 // ART tools will then reconstruct the architecture-specific real path.
 //
-func (image *bootImageVariant) imageLocations() (imageLocations []string) {
+func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) {
 	if image.extends != nil {
-		imageLocations = image.extends.getVariant(image.target).imageLocations()
+		imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations()
 	}
-	return append(imageLocations, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType))
+	return append(imageLocationsOnHost, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType)),
+		append(imageLocationsOnDevice, dexpreopt.PathStringToLocation(image.imagePathOnDevice, image.target.Arch.ArchType))
 }
 
 func dexpreoptBootJarsFactory() android.SingletonModule {
@@ -427,17 +445,10 @@
 		return
 	}
 
-	// Generate the profile rule from the default boot image.
 	defaultImageConfig := defaultBootImageConfig(ctx)
-	profile := bootImageProfileRule(ctx, defaultImageConfig)
-
-	// Create the default boot image.
-	d.defaultBootImage = buildBootImage(ctx, defaultImageConfig, profile)
-
-	// Create boot image for the ART apex (build artifacts are accessed via the global boot image config).
-	d.otherImages = append(d.otherImages, buildBootImage(ctx, artBootImageConfig(ctx), profile))
-
-	copyUpdatableBootJars(ctx)
+	d.defaultBootImage = defaultImageConfig
+	artBootImageConfig := artBootImageConfig(ctx)
+	d.otherImages = []*bootImageConfig{artBootImageConfig}
 }
 
 // shouldBuildBootImages determines whether boot images should be built.
@@ -452,121 +463,34 @@
 	return true
 }
 
-// A copy of isModuleInConfiguredList created to work with singleton context.
-//
-// TODO(b/177892522): Remove this.
-func isModuleInConfiguredListForSingleton(ctx android.SingletonContext, module android.Module, configuredBootJars android.ConfiguredJarList) bool {
-	name := ctx.ModuleName(module)
+// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined
+// paths in the global config.
+func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) {
+	// Create the super set of module names.
+	names := []string{}
+	names = append(names, android.SortedStringKeys(srcBootDexJarsByModule)...)
+	names = append(names, android.SortedStringKeys(dstBootJarsByModule)...)
+	names = android.SortedUniqueStrings(names)
+	for _, name := range names {
+		src := srcBootDexJarsByModule[name]
+		dst := dstBootJarsByModule[name]
 
-	// Strip a prebuilt_ prefix so that this can match a prebuilt module that has not been renamed.
-	name = android.RemoveOptionalPrebuiltPrefix(name)
-
-	// Ignore any module that is not listed in the boot image configuration.
-	index := configuredBootJars.IndexOfJar(name)
-	if index == -1 {
-		return false
-	}
-
-	// It is an error if the module is not an ApexModule.
-	if _, ok := module.(android.ApexModule); !ok {
-		ctx.Errorf("%s is configured in boot jars but does not support being added to an apex", ctx.ModuleName(module))
-		return false
-	}
-
-	apexInfo := ctx.ModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
-
-	// Now match the apex part of the boot image configuration.
-	requiredApex := configuredBootJars.Apex(index)
-	if requiredApex == "platform" || requiredApex == "system_ext" {
-		if len(apexInfo.InApexes) != 0 {
-			// A platform variant is required but this is for an apex so ignore it.
-			return false
-		}
-	} else if !apexInfo.InApexByBaseName(requiredApex) {
-		// An apex variant for a specific apex is required but this is the wrong apex.
-		return false
-	}
-
-	return true
-}
-
-// findBootJarModules finds the boot jar module variants specified in the bootjars parameter.
-//
-// It returns a list of modules such that the module at index i corresponds to the configured jar
-// at index i.
-func findBootJarModules(ctx android.SingletonContext, bootjars android.ConfiguredJarList) []android.Module {
-	modules := make([]android.Module, bootjars.Len())
-
-	// This logic is tested in the apex package to avoid import cycle apex <-> java.
-	ctx.VisitAllModules(func(module android.Module) {
-		if !isActiveModule(module) || !isModuleInConfiguredListForSingleton(ctx, module, bootjars) {
-			return
-		}
-
-		name := android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName(module))
-		index := bootjars.IndexOfJar(name)
-		if existing := modules[index]; existing != nil {
-			ctx.Errorf("Multiple boot jar modules found for %s:%s - %q and %q",
-				bootjars.Apex(index), bootjars.Jar(index), existing, module)
-			return
-		}
-		modules[index] = module
-	})
-	return modules
-}
-
-// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to
-// predefined paths in the global config.
-func copyBootJarsToPredefinedLocations(ctx android.SingletonContext, bootModules []android.Module, bootjars android.ConfiguredJarList, jarPathsPredefined android.WritablePaths) {
-	jarPaths := make(android.Paths, bootjars.Len())
-	for i, module := range bootModules {
-		if module != nil {
-			bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-			jarPaths[i] = bootDexJar
-		}
-	}
-
-	// The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
-	// time, before the boot images are built (these paths are used in dexpreopt rule generation for
-	// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
-	// paths.
-	for i := range jarPaths {
-		input := jarPaths[i]
-		output := jarPathsPredefined[i]
-		module := bootjars.Jar(i)
-		if input == nil {
-			if ctx.Config().AllowMissingDependencies() {
-				apex := bootjars.Apex(i)
-
-				// Create an error rule that pretends to create the output file but will actually fail if it
-				// is run.
-				ctx.Build(pctx, android.BuildParams{
-					Rule:   android.ErrorRule,
-					Output: output,
-					Args: map[string]string{
-						"error": fmt.Sprintf("missing dependencies: dex jar for %s:%s", module, apex),
-					},
-				})
-			} else {
-				ctx.Errorf("failed to find a dex jar path for module '%s'"+
-					", note that some jars may be filtered out by module constraints", module)
-			}
-
+		if src == nil {
+			ctx.ModuleErrorf("module %s does not provide a dex boot jar", name)
+		} else if dst == nil {
+			ctx.ModuleErrorf("module %s is not part of the boot configuration", name)
 		} else {
 			ctx.Build(pctx, android.BuildParams{
 				Rule:   android.Cp,
-				Input:  input,
-				Output: output,
+				Input:  src,
+				Output: dst,
 			})
 		}
 	}
 }
 
 // buildBootImage takes a bootImageConfig, creates rules to build it, and returns the image.
-func buildBootImage(ctx android.SingletonContext, image *bootImageConfig, profile android.WritablePath) *bootImageConfig {
-	bootModules := findBootJarModules(ctx, image.modules)
-	copyBootJarsToPredefinedLocations(ctx, bootModules, image.modules, image.dexPaths)
-
+func buildBootImage(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) {
 	var zipFiles android.Paths
 	for _, variant := range image.variants {
 		files := buildBootImageVariant(ctx, variant, profile)
@@ -585,19 +509,10 @@
 
 		rule.Build("zip_"+image.name, "zip "+image.name+" image")
 	}
-
-	return image
-}
-
-// Generate commands that will copy updatable boot jars to predefined paths in the global config.
-func copyUpdatableBootJars(ctx android.SingletonContext) {
-	config := GetUpdatableBootConfig(ctx)
-	bootModules := findBootJarModules(ctx, config.modules)
-	copyBootJarsToPredefinedLocations(ctx, bootModules, config.modules, config.dexPaths)
 }
 
 // Generate boot image build rules for a specific target.
-func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant, profile android.Path) android.WritablePaths {
+func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) android.WritablePaths {
 
 	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
@@ -660,7 +575,15 @@
 		cmd.
 			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
-			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
+			// Add the path to the first file in the boot image with the arch specific directory removed,
+			// dex2oat will reconstruct the path to the actual file when it needs it. As the actual path
+			// to the file cannot be passed to the command make sure to add the actual path as an Implicit
+			// dependency to ensure that it is built before the command runs.
+			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage).
+			// Similarly, the dex2oat tool will automatically find the paths to other files in the base
+			// boot image so make sure to add them as implicit dependencies to ensure that they are built
+			// before this command is run.
+			Implicits(image.primaryImagesDeps)
 	} else {
 		// It is a primary image, so it needs a base address.
 		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
@@ -747,7 +670,7 @@
 It is likely that the boot classpath is inconsistent.
 Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.`
 
-func bootImageProfileRule(ctx android.SingletonContext, image *bootImageConfig) android.WritablePath {
+func bootImageProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath {
 	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
 
@@ -838,7 +761,7 @@
 			if len(pp) > 0 {
 				updatablePackages = append(updatablePackages, pp...)
 			} else {
-				ctx.ModuleErrorf("Missing permitted_packages")
+				ctx.OtherModuleErrorf(module, "Missing permitted_packages")
 			}
 		}
 	}
@@ -873,12 +796,12 @@
 		// Create a rule to call oatdump.
 		output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt")
 		rule := android.NewRuleBuilder(pctx, ctx)
+		imageLocationsOnHost, _ := image.imageLocations()
 		rule.Command().
-			// TODO: for now, use the debug version for better error reporting
-			BuiltTool("oatdumpd").
+			BuiltTool("oatdump").
 			FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":").
-			FlagWithArg("--image=", strings.Join(image.imageLocations(), ":")).Implicits(image.imagesDeps.Paths()).
+			FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()).
 			FlagWithOutput("--output=", output).
 			FlagWithArg("--instruction-set=", arch.String())
 		rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String())
@@ -948,8 +871,8 @@
 				ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, variant.installs.String())
 				ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, variant.unstrippedInstalls.String())
 			}
-			imageLocations := current.getAnyAndroidVariant().imageLocations()
-			ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_"+current.name, strings.Join(imageLocations, ":"))
+			imageLocationsOnHost, _ := current.getAnyAndroidVariant().imageLocations()
+			ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_"+current.name, strings.Join(imageLocationsOnHost, ":"))
 			ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+current.name, current.zip.String())
 		}
 		ctx.Strict("DEXPREOPT_IMAGE_NAMES", strings.Join(imageNames, " "))
diff --git a/java/dexpreopt_bootjars_test.go b/java/dexpreopt_bootjars_test.go
index 73f21d1..bc7a55e 100644
--- a/java/dexpreopt_bootjars_test.go
+++ b/java/dexpreopt_bootjars_test.go
@@ -20,7 +20,6 @@
 	"testing"
 
 	"android/soong/android"
-	"android/soong/dexpreopt"
 )
 
 func testDexpreoptBoot(t *testing.T, ruleFile string, expectedInputs, expectedOutputs []string) {
@@ -42,17 +41,21 @@
 			name: "baz",
 			jars: ["a.jar"],
 		}
+
+		platform_bootclasspath {
+			name: "platform-bootclasspath",
+		}
 	`
 
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		dexpreopt.FixtureSetBootJars("platform:foo", "system_ext:bar", "platform:baz"),
+		FixtureConfigureBootJars("platform:foo", "system_ext:bar", "platform:baz"),
 	).RunTestWithBp(t, bp)
 
-	dexpreoptBootJars := result.SingletonForTests("dex_bootjars")
-	rule := dexpreoptBootJars.Output(ruleFile)
+	platformBootclasspath := result.ModuleForTests("platform-bootclasspath", "android_common")
+	rule := platformBootclasspath.Output(ruleFile)
 
 	for i := range expectedInputs {
 		expectedInputs[i] = filepath.Join("out/soong/test_device", expectedInputs[i])
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 7fb0444..b13955f 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -15,7 +15,6 @@
 package java
 
 import (
-	"fmt"
 	"path/filepath"
 	"strings"
 
@@ -23,32 +22,6 @@
 	"android/soong/dexpreopt"
 )
 
-// systemServerClasspath returns the on-device locations of the modules in the system server classpath.  It is computed
-// once the first time it is called for any ctx.Config(), and returns the same slice for all future calls with the same
-// ctx.Config().
-func systemServerClasspath(ctx android.PathContext) []string {
-	return ctx.Config().OnceStringSlice(systemServerClasspathKey, func() []string {
-		global := dexpreopt.GetGlobalConfig(ctx)
-		var systemServerClasspathLocations []string
-		nonUpdatable := dexpreopt.NonUpdatableSystemServerJars(ctx, global)
-		// 1) Non-updatable jars.
-		for _, m := range nonUpdatable {
-			systemServerClasspathLocations = append(systemServerClasspathLocations,
-				filepath.Join("/system/framework", m+".jar"))
-		}
-		// 2) The jars that are from an updatable apex.
-		systemServerClasspathLocations = append(systemServerClasspathLocations,
-			global.UpdatableSystemServerJars.DevicePaths(ctx.Config(), android.Android)...)
-
-		if expectedLen := global.SystemServerJars.Len() + global.UpdatableSystemServerJars.Len(); expectedLen != len(systemServerClasspathLocations) {
-			panic(fmt.Errorf("wrong number of system server jars, got %d, expected %d", len(systemServerClasspathLocations), expectedLen))
-		}
-		return systemServerClasspathLocations
-	})
-}
-
-var systemServerClasspathKey = android.NewOnceKey("systemServerClasspath")
-
 // dexpreoptTargets returns the list of targets that are relevant to dexpreopting, which excludes architectures
 // supported through native bridge.
 func dexpreoptTargets(ctx android.PathContext) []android.Target {
@@ -84,25 +57,28 @@
 		frameworkModules := global.BootJars.RemoveList(artModules)
 
 		artDirOnHost := "apex/art_boot_images/javalib"
+		artDirOnDevice := "apex/com.android.art/javalib"
 		frameworkSubdir := "system/framework"
 
 		// ART config for the primary boot image in the ART apex.
 		// It includes the Core Libraries.
 		artCfg := bootImageConfig{
-			name:             artBootImageName,
-			stem:             "boot",
-			installDirOnHost: artDirOnHost,
-			modules:          artModules,
+			name:               artBootImageName,
+			stem:               "boot",
+			installDirOnHost:   artDirOnHost,
+			installDirOnDevice: artDirOnDevice,
+			modules:            artModules,
 		}
 
 		// Framework config for the boot image extension.
 		// It includes framework libraries and depends on the ART config.
 		frameworkCfg := bootImageConfig{
-			extends:          &artCfg,
-			name:             frameworkBootImageName,
-			stem:             "boot",
-			installDirOnHost: frameworkSubdir,
-			modules:          frameworkModules,
+			extends:            &artCfg,
+			name:               frameworkBootImageName,
+			stem:               "boot",
+			installDirOnHost:   frameworkSubdir,
+			installDirOnDevice: frameworkSubdir,
+			modules:            frameworkModules,
 		}
 
 		configs := map[string]*bootImageConfig{
@@ -124,6 +100,7 @@
 			// TODO(b/143682396): use module dependencies instead
 			inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input")
 			c.dexPaths = c.modules.BuildPaths(ctx, inputDir)
+			c.dexPathsByModule = c.modules.BuildPathsByModule(ctx, inputDir)
 			c.dexPathsDeps = c.dexPaths
 
 			// Create target-specific variants.
@@ -131,11 +108,12 @@
 				arch := target.Arch.ArchType
 				imageDir := c.dir.Join(ctx, target.Os.String(), c.installDirOnHost, arch.String())
 				variant := &bootImageVariant{
-					bootImageConfig: c,
-					target:          target,
-					imagePathOnHost: imageDir.Join(ctx, imageName),
-					imagesDeps:      c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"),
-					dexLocations:    c.modules.DevicePaths(ctx.Config(), target.Os),
+					bootImageConfig:   c,
+					target:            target,
+					imagePathOnHost:   imageDir.Join(ctx, imageName),
+					imagePathOnDevice: filepath.Join("/", c.installDirOnDevice, arch.String(), imageName),
+					imagesDeps:        c.moduleFiles(ctx, imageDir, ".art", ".oat", ".vdex"),
+					dexLocations:      c.modules.DevicePaths(ctx.Config(), target.Os),
 				}
 				variant.dexLocationsDeps = variant.dexLocations
 				c.variants = append(c.variants, variant)
@@ -148,6 +126,7 @@
 		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
 		for i := range targets {
 			frameworkCfg.variants[i].primaryImages = artCfg.variants[i].imagePathOnHost
+			frameworkCfg.variants[i].primaryImagesDeps = artCfg.variants[i].imagesDeps.Paths()
 			frameworkCfg.variants[i].dexLocationsDeps = append(artCfg.variants[i].dexLocations, frameworkCfg.variants[i].dexLocationsDeps...)
 		}
 
@@ -175,6 +154,9 @@
 	// later on a singleton adds commands to copy actual jars to the predefined paths.
 	dexPaths android.WritablePaths
 
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
 	// A list of dex locations (a.k.a. on-device paths) to the boot jars.
 	dexLocations []string
 }
@@ -188,10 +170,11 @@
 
 		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars")
 		dexPaths := updatableBootJars.BuildPaths(ctx, dir)
+		dexPathsByModuleName := updatableBootJars.BuildPathsByModule(ctx, dir)
 
 		dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android)
 
-		return updatableBootConfig{updatableBootJars, dexPaths, dexLocations}
+		return updatableBootConfig{updatableBootJars, dexPaths, dexPathsByModuleName, dexLocations}
 	}).(updatableBootConfig)
 }
 
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index a9e0773..b25dece 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -15,7 +15,12 @@
 package java
 
 import (
+	"fmt"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/dexpreopt"
 )
 
 func TestDexpreoptEnabled(t *testing.T) {
@@ -166,3 +171,51 @@
 		return "disabled"
 	}
 }
+
+func TestDex2oatToolDeps(t *testing.T) {
+	if android.BuildOs != android.Linux {
+		// The host binary paths checked below are build OS dependent.
+		t.Skipf("Unsupported build OS %s", android.BuildOs)
+	}
+
+	preparers := android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd,
+		dexpreopt.PrepareForTestByEnablingDexpreopt)
+
+	testDex2oatToolDep := func(sourceEnabled, prebuiltEnabled, prebuiltPreferred bool,
+		expectedDex2oatPath string) {
+		name := fmt.Sprintf("sourceEnabled:%t,prebuiltEnabled:%t,prebuiltPreferred:%t",
+			sourceEnabled, prebuiltEnabled, prebuiltPreferred)
+		t.Run(name, func(t *testing.T) {
+			result := preparers.RunTestWithBp(t, fmt.Sprintf(`
+					cc_binary {
+						name: "dex2oatd",
+						enabled: %t,
+						host_supported: true,
+					}
+					cc_prebuilt_binary {
+						name: "dex2oatd",
+						enabled: %t,
+						prefer: %t,
+						host_supported: true,
+						srcs: ["x86_64/bin/dex2oatd"],
+					}
+					java_library {
+						name: "myjavalib",
+					}
+				`, sourceEnabled, prebuiltEnabled, prebuiltPreferred))
+			pathContext := android.PathContextForTesting(result.Config)
+			dex2oatPath := dexpreopt.GetCachedGlobalSoongConfig(pathContext).Dex2oat
+			android.AssertStringEquals(t, "Testing "+name, expectedDex2oatPath, android.NormalizePathForTesting(dex2oatPath))
+		})
+	}
+
+	sourceDex2oatPath := "host/linux-x86/bin/dex2oatd"
+	prebuiltDex2oatPath := ".intermediates/prebuilt_dex2oatd/linux_glibc_x86_64/dex2oatd"
+
+	testDex2oatToolDep(true, false, false, sourceDex2oatPath)
+	testDex2oatToolDep(true, true, false, sourceDex2oatPath)
+	testDex2oatToolDep(true, true, true, prebuiltDex2oatPath)
+	testDex2oatToolDep(false, true, false, prebuiltDex2oatPath)
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 56e6247..869a598 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -425,23 +425,6 @@
 	}
 	srcFiles = filterByPackage(srcFiles, j.properties.Filter_packages)
 
-	// While metalava needs package html files, it does not need them to be explicit on the command
-	// line. javadoc complains if it receives html files on the command line. The filter
-	// below excludes html files from the rsp file metalava. Note that the html
-	// files are still included as implicit inputs for successful remote execution and correct
-	// incremental builds.
-	filterHtml := func(srcs []android.Path) []android.Path {
-		filtered := []android.Path{}
-		for _, src := range srcs {
-			if src.Ext() == ".html" {
-				continue
-			}
-			filtered = append(filtered, src)
-		}
-		return filtered
-	}
-	srcFiles = filterHtml(srcFiles)
-
 	aidlFlags := j.collectAidlFlags(ctx, deps)
 	srcFiles = j.genSources(ctx, srcFiles, aidlFlags)
 
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 566f7e3..d348b55 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -51,7 +51,6 @@
 	lastReleasedApiXmlFile  android.WritablePath
 	privateApiFile          android.WritablePath
 	removedApiFile          android.WritablePath
-	removedDexApiFile       android.WritablePath
 	nullabilityWarningsFile android.WritablePath
 
 	checkCurrentApiTimestamp      android.WritablePath
@@ -79,9 +78,6 @@
 	// the generated removed API filename by Metalava, defaults to <module>_removed.txt
 	Removed_api_filename *string
 
-	// the generated removed Dex API filename by Metalava.
-	Removed_dex_api_filename *string
-
 	Check_api struct {
 		Last_released ApiToCheck
 
@@ -274,11 +270,6 @@
 		d.removedApiFilePath = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
 	}
 
-	if String(d.properties.Removed_dex_api_filename) != "" {
-		d.removedDexApiFile = android.PathForModuleOut(ctx, "metalava", String(d.properties.Removed_dex_api_filename))
-		cmd.FlagWithOutput("--removed-dex-api ", d.removedDexApiFile)
-	}
-
 	if Bool(d.properties.Write_sdk_values) {
 		d.metadataDir = android.PathForModuleOut(ctx, "metalava", "metadata")
 		cmd.FlagWithArg("--sdk-values ", d.metadataDir.String())
@@ -401,8 +392,7 @@
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
-	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths,
-	homeDir android.WritablePath) *android.RuleBuilderCommand {
+	srcJarList android.Path, bootclasspath, classpath classpath, homeDir android.WritablePath) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -439,12 +429,6 @@
 		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
 	}
 
-	if len(sourcepaths) > 0 {
-		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
-	} else {
-		cmd.FlagWithArg("-sourcepath ", `""`)
-	}
-
 	cmd.Flag("--no-banner").
 		Flag("--color").
 		Flag("--quiet").
@@ -488,7 +472,7 @@
 
 	homeDir := android.PathForModuleOut(ctx, "metalava", "home")
 	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
-		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, homeDir)
+		deps.bootClasspath, deps.classpath, homeDir)
 	cmd.Implicits(d.Javadoc.implicits)
 
 	d.stubsFlags(ctx, cmd, stubsDir)
@@ -546,7 +530,8 @@
 			`\n` +
 			`If it is not possible to do so, there are workarounds:\n` +
 			`\n` +
-			`1. You can suppress the errors with @SuppressLint("<id>")\n`
+			`1. You can suppress the errors with @SuppressLint("<id>")\n` +
+			`   where the <id> is given in brackets in the error message above.\n`
 
 		if baselineFile.Valid() {
 			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index a34044f..f901434 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -26,27 +26,10 @@
 }, "outFlag", "stubAPIFlags")
 
 type hiddenAPI struct {
-	// The name of the module as it would be used in the boot jars configuration, e.g. without any
-	// prebuilt_ prefix (if it is a prebuilt) and without any ".impl" suffix if it is a
-	// java_sdk_library implementation library.
-	configurationName string
-
 	// True if the module containing this structure contributes to the hiddenapi information or has
 	// that information encoded within it.
 	active bool
 
-	// Identifies the active module variant which will be used as the source of hiddenapi information.
-	//
-	// A class may be compiled into a number of different module variants each of which will need the
-	// hiddenapi information encoded into it and so will be marked as active. However, only one of
-	// them must be used as a source of information by hiddenapi otherwise it will end up with
-	// duplicate entries. That module will have primary=true.
-	//
-	// Note, that modules <x>-hiddenapi that provide additional annotation information for module <x>
-	// that is on the bootclasspath are marked as primary=true as they are the primary source of that
-	// annotation information.
-	primary bool
-
 	// The path to the dex jar that is in the boot class path. If this is nil then the associated
 	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
 	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
@@ -56,130 +39,82 @@
 	// this file so using the encoded dex jar here would result in a cycle in the ninja rules.
 	bootDexJarPath android.Path
 
-	// The path to the CSV file that contains mappings from Java signature to various flags derived
-	// from annotations in the source, e.g. whether it is public or the sdk version above which it
-	// can no longer be used.
-	//
-	// It is created by the Class2NonSdkList tool which processes the .class files in the class
-	// implementation jar looking for UnsupportedAppUsage and CovariantReturnType annotations. The
-	// tool also consumes the hiddenAPISingletonPathsStruct.stubFlags file in order to perform
-	// consistency checks on the information in the annotations and to filter out bridge methods
-	// that are already part of the public API.
-	flagsCSVPath android.Path
-
-	// The path to the CSV file that contains mappings from Java signature to the value of properties
-	// specified on UnsupportedAppUsage annotations in the source.
-	//
-	// Like the flagsCSVPath file this is also created by the Class2NonSdkList in the same way.
-	// Although the two files could potentially be created in a single invocation of the
-	// Class2NonSdkList at the moment they are created using their own invocation, with the behavior
-	// being determined by the property that is used.
-	metadataCSVPath android.Path
-
-	// The path to the CSV file that contains mappings from Java signature to source location
-	// information.
-	//
-	// It is created by the merge_csv tool which processes the class implementation jar, extracting
-	// all the files ending in .uau (which are CSV files) and merges them together. The .uau files are
-	// created by the unsupported app usage annotation processor during compilation of the class
-	// implementation jar.
-	indexCSVPath android.Path
-
 	// The paths to the classes jars that contain classes and class members annotated with
 	// the UnsupportedAppUsage annotation that need to be extracted as part of the hidden API
 	// processing.
 	classesJarPaths android.Paths
-}
 
-func (h *hiddenAPI) flagsCSV() android.Path {
-	return h.flagsCSVPath
-}
-
-func (h *hiddenAPI) metadataCSV() android.Path {
-	return h.metadataCSVPath
+	// The compressed state of the dex file being encoded. This is used to ensure that the encoded
+	// dex file has the same state.
+	uncompressDexState *bool
 }
 
 func (h *hiddenAPI) bootDexJar() android.Path {
 	return h.bootDexJarPath
 }
 
-func (h *hiddenAPI) indexCSV() android.Path {
-	return h.indexCSVPath
-}
-
 func (h *hiddenAPI) classesJars() android.Paths {
 	return h.classesJarPaths
 }
 
+func (h *hiddenAPI) uncompressDex() *bool {
+	return h.uncompressDexState
+}
+
+// hiddenAPIModule is the interface a module that embeds the hiddenAPI structure must implement.
+type hiddenAPIModule interface {
+	android.Module
+	hiddenAPIIntf
+}
+
 type hiddenAPIIntf interface {
 	bootDexJar() android.Path
-	flagsCSV() android.Path
-	indexCSV() android.Path
-	metadataCSV() android.Path
 	classesJars() android.Paths
+	uncompressDex() *bool
 }
 
 var _ hiddenAPIIntf = (*hiddenAPI)(nil)
 
 // Initialize the hiddenapi structure
-func (h *hiddenAPI) initHiddenAPI(ctx android.BaseModuleContext, configurationName string) {
+//
+// uncompressedDexState should be nil when the module is a prebuilt and so does not require hidden
+// API encoding.
+func (h *hiddenAPI) initHiddenAPI(ctx android.ModuleContext, dexJar, classesJar android.Path, uncompressedDexState *bool) {
+
+	// Save the classes jars even if this is not active as they may be used by modular hidden API
+	// processing.
+	classesJars := android.Paths{classesJar}
+	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
+		javaInfo := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
+		classesJars = append(classesJars, javaInfo.ImplementationJars...)
+	})
+	h.classesJarPaths = classesJars
+
+	// Save the unencoded dex jar so it can be used when generating the
+	// hiddenAPISingletonPathsStruct.stubFlags file.
+	h.bootDexJarPath = dexJar
+
+	h.uncompressDexState = uncompressedDexState
+
 	// If hiddenapi processing is disabled treat this as inactive.
 	if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
 		return
 	}
 
-	h.configurationName = configurationName
+	// The context module must implement hiddenAPIModule.
+	module := ctx.Module().(hiddenAPIModule)
+
+	// If the frameworks/base directories does not exist and no prebuilt hidden API flag files have
+	// been configured then it is not possible to do hidden API encoding.
+	if !ctx.Config().FrameworksBaseDirExists(ctx) && ctx.Config().PrebuiltHiddenApiDir(ctx) == "" {
+		return
+	}
 
 	// It is important that hiddenapi information is only gathered for/from modules that are actually
 	// on the boot jars list because the runtime only enforces access to the hidden API for the
 	// bootclassloader. If information is gathered for modules not on the list then that will cause
 	// failures in the CtsHiddenApiBlocklist... tests.
-	module := ctx.Module()
 	h.active = isModuleInBootClassPath(ctx, module)
-	if !h.active {
-		// The rest of the properties will be ignored if active is false.
-		return
-	}
-
-	// Determine whether this module is the primary module or not.
-	primary := true
-
-	// A prebuilt module is only primary if it is preferred and conversely a source module is only
-	// primary if it has not been replaced by a prebuilt module.
-	if pi, ok := module.(android.PrebuiltInterface); ok {
-		if p := pi.Prebuilt(); p != nil {
-			primary = p.UsePrebuilt()
-		}
-	} else {
-		// The only module that will pass a different configurationName to its module name to this
-		// method is the implementation library of a java_sdk_library. It has a configuration name of
-		// <x> the same as its parent java_sdk_library but a module name of <x>.impl. It is not the
-		// primary module, the java_sdk_library with the name of <x> is.
-		primary = configurationName == ctx.ModuleName()
-
-		// A source module that has been replaced by a prebuilt can never be the primary module.
-		if module.IsReplacedByPrebuilt() {
-			if ctx.HasProvider(android.ApexInfoProvider) {
-				// The source module is in an APEX but the prebuilt module on which it depends is not in an
-				// APEX and so is not the one that will actually be used for hidden API processing. That
-				// means it is not possible to check to see if it is a suitable replacement so just assume
-				// that it is.
-				primary = false
-			} else {
-				ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(prebuilt android.Module) {
-					if h, ok := prebuilt.(hiddenAPIIntf); ok && h.bootDexJar() != nil {
-						primary = false
-					} else {
-						ctx.ModuleErrorf(
-							"hiddenapi has determined that the source module %q should be ignored as it has been"+
-								" replaced by the prebuilt module %q but unfortunately it does not provide a"+
-								" suitable boot dex jar", ctx.ModuleName(), ctx.OtherModuleName(prebuilt))
-					}
-				})
-			}
-		}
-	}
-	h.primary = primary
 }
 
 func isModuleInBootClassPath(ctx android.BaseModuleContext, module android.Module) bool {
@@ -191,84 +126,74 @@
 	return active
 }
 
-// hiddenAPIExtractAndEncode is called by any module that could contribute to the hiddenapi
-// processing.
+// hiddenAPIEncodeDex is called by any module that needs to encode dex files.
 //
 // It ignores any module that has not had initHiddenApi() called on it and which is not in the boot
-// jar list.
+// jar list. In that case it simply returns the supplied dex jar path.
 //
-// Otherwise, it generates ninja rules to do the following:
-// 1. Extract information needed for hiddenapi processing from the module and output it into CSV
-//    files.
-// 2. Conditionally adds the supplied dex file to the list of files used to generate the
-//    hiddenAPISingletonPathsStruct.stubsFlag file.
-// 3. Conditionally creates a copy of the supplied dex file into which it has encoded the hiddenapi
-//    flags and returns this instead of the supplied dex jar, otherwise simply returns the supplied
-//    dex jar.
-func (h *hiddenAPI) hiddenAPIExtractAndEncode(ctx android.ModuleContext, dexJar android.OutputPath,
-	implementationJar android.Path, uncompressDex bool) android.OutputPath {
+// Otherwise, it creates a copy of the supplied dex file into which it has encoded the hiddenapi
+// flags and returns this instead of the supplied dex jar.
+func (h *hiddenAPI) hiddenAPIEncodeDex(ctx android.ModuleContext, dexJar android.OutputPath) android.OutputPath {
 
 	if !h.active {
 		return dexJar
 	}
 
-	h.hiddenAPIExtractInformation(ctx, dexJar, implementationJar)
-
-	hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", h.configurationName+".jar").OutputPath
+	// A nil uncompressDexState prevents the dex file from being encoded.
+	if h.uncompressDexState == nil {
+		ctx.ModuleErrorf("cannot encode dex file %s when uncompressDexState is nil", dexJar)
+	}
+	uncompressDex := *h.uncompressDexState
 
 	// Create a copy of the dex jar which has been encoded with hiddenapi flags.
-	hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex)
+	flagsCSV := hiddenAPISingletonPaths(ctx).flags
+	outputDir := android.PathForModuleOut(ctx, "hiddenapi").OutputPath
+	encodedDex := hiddenAPIEncodeDex(ctx, dexJar, flagsCSV, uncompressDex, outputDir)
 
 	// Use the encoded dex jar from here onwards.
-	dexJar = hiddenAPIJar
-
-	return dexJar
+	return encodedDex
 }
 
-// hiddenAPIExtractInformation generates ninja rules to extract the information from the classes
-// jar, and outputs it to the appropriate module specific CSV file.
+// buildRuleToGenerateAnnotationFlags builds a ninja rule to generate the annotation-flags.csv file
+// from the classes jars and stub-flags.csv files.
 //
-// It also makes the dex jar available for use when generating the
-// hiddenAPISingletonPathsStruct.stubFlags.
-func (h *hiddenAPI) hiddenAPIExtractInformation(ctx android.ModuleContext, dexJar, classesJar android.Path) {
-	if !h.active {
-		return
-	}
-
-	// More than one library with the same classes may need to be encoded but only one should be
-	// used as a source of information for hidden API processing otherwise it will result in
-	// duplicate entries in the files.
-	if !h.primary {
-		return
-	}
-
-	classesJars := android.Paths{classesJar}
-	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
-		javaInfo := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
-		classesJars = append(classesJars, javaInfo.ImplementationJars...)
-	})
-	h.classesJarPaths = classesJars
-
-	stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags
-
-	flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
+// The annotation-flags.csv file contains mappings from Java signature to various flags derived from
+// annotations in the source, e.g. whether it is public or the sdk version above which it can no
+// longer be used.
+//
+// It is created by the Class2NonSdkList tool which processes the .class files in the class
+// implementation jar looking for UnsupportedAppUsage and CovariantReturnType annotations. The
+// tool also consumes the hiddenAPISingletonPathsStruct.stubFlags file in order to perform
+// consistency checks on the information in the annotations and to filter out bridge methods
+// that are already part of the public API.
+func buildRuleToGenerateAnnotationFlags(ctx android.ModuleContext, desc string, classesJars android.Paths, stubFlagsCSV android.Path, outputPath android.WritablePath) {
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
-		Description: "hiddenapi flags",
+		Description: desc,
 		Inputs:      classesJars,
-		Output:      flagsCSV,
+		Output:      outputPath,
 		Implicit:    stubFlagsCSV,
 		Args: map[string]string{
 			"outFlag":      "--write-flags-csv",
 			"stubAPIFlags": stubFlagsCSV.String(),
 		},
 	})
-	h.flagsCSVPath = flagsCSV
+}
 
-	metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv")
+// buildRuleToGenerateMetadata builds a ninja rule to generate the metadata.csv file from
+// the classes jars and stub-flags.csv files.
+//
+// The metadata.csv file contains mappings from Java signature to the value of properties specified
+// on UnsupportedAppUsage annotations in the source.
+//
+// Like the annotation-flags.csv file this is also created by the Class2NonSdkList in the same way.
+// Although the two files could potentially be created in a single invocation of the
+// Class2NonSdkList at the moment they are created using their own invocation, with the behavior
+// being determined by the property that is used.
+func buildRuleToGenerateMetadata(ctx android.ModuleContext, desc string, classesJars android.Paths, stubFlagsCSV android.Path, metadataCSV android.WritablePath) {
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
-		Description: "hiddenapi metadata",
+		Description: desc,
 		Inputs:      classesJars,
 		Output:      metadataCSV,
 		Implicit:    stubFlagsCSV,
@@ -277,9 +202,18 @@
 			"stubAPIFlags": stubFlagsCSV.String(),
 		},
 	})
-	h.metadataCSVPath = metadataCSV
+}
 
-	indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv")
+// buildRuleToGenerateIndex builds a ninja rule to generate the index.csv file from the classes
+// jars.
+//
+// The index.csv file contains mappings from Java signature to source location information.
+//
+// It is created by the merge_csv tool which processes the class implementation jar, extracting
+// all the files ending in .uau (which are CSV files) and merges them together. The .uau files are
+// created by the unsupported app usage annotation processor during compilation of the class
+// implementation jar.
+func buildRuleToGenerateIndex(ctx android.ModuleContext, desc string, classesJars android.Paths, indexCSV android.WritablePath) {
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		BuiltTool("merge_csv").
@@ -287,12 +221,7 @@
 		Flag("--key_field signature").
 		FlagWithOutput("--output=", indexCSV).
 		Inputs(classesJars)
-	rule.Build("merged-hiddenapi-index", "Merged Hidden API index")
-	h.indexCSVPath = indexCSV
-
-	// Save the unencoded dex jar so it can be used when generating the
-	// hiddenAPISingletonPathsStruct.stubFlags file.
-	h.bootDexJarPath = dexJar
+	rule.Build(desc, desc)
 }
 
 var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{
@@ -303,7 +232,7 @@
 		  echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})";
 		done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags &&
 		${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" &&
-		${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`,
+		${config.MergeZipsCmd} -j -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`,
 	CommandDeps: []string{
 		"${config.HiddenAPI}",
 		"${config.SoongZipCmd}",
@@ -311,39 +240,37 @@
 	},
 }, "flagsCsv", "hiddenapiFlags", "tmpDir", "soongZipFlags")
 
-func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.Path,
-	uncompressDex bool) {
+// hiddenAPIEncodeDex generates the build rule that will encode the supplied dex jar and place the
+// encoded dex jar in a file of the same name in the output directory.
+//
+// The encode dex rule requires unzipping, encoding and rezipping the classes.dex files along with
+// all the resources from the input jar. It also ensures that if it was uncompressed in the input
+// it stays uncompressed in the output.
+func hiddenAPIEncodeDex(ctx android.ModuleContext, dexInput, flagsCSV android.Path, uncompressDex bool, outputDir android.OutputPath) android.OutputPath {
 
-	flagsCSV := hiddenAPISingletonPaths(ctx).flags
+	// The output file has the same name as the input file and is in the output directory.
+	output := outputDir.Join(ctx, dexInput.Base())
 
-	// The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed
-	// in the input it stays uncompressed in the output.
+	// Create a jar specific temporary directory in which to do the work just in case this is called
+	// with the same output directory for multiple modules.
+	tmpDir := outputDir.Join(ctx, dexInput.Base()+"-tmp")
+
+	// If the input is uncompressed then generate the output of the encode rule to an intermediate
+	// file as the final output will need further processing after encoding.
 	soongZipFlags := ""
-	hiddenapiFlags := ""
-	tmpOutput := output
-	tmpDir := android.PathForModuleOut(ctx, "hiddenapi", "dex")
+	encodeRuleOutput := output
 	if uncompressDex {
 		soongZipFlags = "-L 0"
-		tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar")
-		tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned")
+		encodeRuleOutput = outputDir.Join(ctx, "unaligned", dexInput.Base())
 	}
 
-	enforceHiddenApiFlagsToAllMembers := true
-	// If frameworks/base doesn't exist we must be building with the 'master-art' manifest.
-	// Disable assertion that all methods/fields have hidden API flags assigned.
-	if !ctx.Config().FrameworksBaseDirExists(ctx) {
-		enforceHiddenApiFlagsToAllMembers = false
-	}
 	// b/149353192: when a module is instrumented, jacoco adds synthetic members
 	// $jacocoData and $jacocoInit. Since they don't exist when building the hidden API flags,
 	// don't complain when we don't find hidden API flags for the synthetic members.
+	hiddenapiFlags := ""
 	if j, ok := ctx.Module().(interface {
 		shouldInstrument(android.BaseModuleContext) bool
 	}); ok && j.shouldInstrument(ctx) {
-		enforceHiddenApiFlagsToAllMembers = false
-	}
-
-	if !enforceHiddenApiFlagsToAllMembers {
 		hiddenapiFlags = "--no-force-assign-all"
 	}
 
@@ -351,7 +278,7 @@
 		Rule:        hiddenAPIEncodeDexRule,
 		Description: "hiddenapi encode dex",
 		Input:       dexInput,
-		Output:      tmpOutput,
+		Output:      encodeRuleOutput,
 		Implicit:    flagsCSV,
 		Args: map[string]string{
 			"flagsCsv":       flagsCSV.String(),
@@ -362,8 +289,10 @@
 	})
 
 	if uncompressDex {
-		TransformZipAlign(ctx, output, tmpOutput)
+		TransformZipAlign(ctx, output, encodeRuleOutput)
 	}
+
+	return output
 }
 
 type hiddenApiAnnotationsDependencyTag struct {
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index 335f5b8..643c5cb 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -15,8 +15,12 @@
 package java
 
 import (
+	"fmt"
+	"strings"
+
 	"android/soong/android"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // Contains support for processing hiddenAPI in a modular fashion.
@@ -63,6 +67,12 @@
 
 // hiddenAPIRelevantSdkKinds lists all the android.SdkKind instances that are needed by the hidden
 // API processing.
+//
+// These are in order from narrowest API surface to widest. Widest means the API stubs with the
+// biggest API surface, e.g. test is wider than system is wider than public. Core platform is
+// considered wider than test even though it has no relationship with test because the libraries
+// that provide core platform API don't provide test. While the core platform API is being converted
+// to a system API the system API is still a subset of core platform.
 var hiddenAPIRelevantSdkKinds = []android.SdkKind{
 	android.SdkPublic,
 	android.SdkSystem,
@@ -97,7 +107,12 @@
 	systemStubModules = append(systemStubModules, config.ProductHiddenAPIStubsSystem()...)
 	testStubModules = append(testStubModules, config.ProductHiddenAPIStubsTest()...)
 	if config.IsEnvTrue("EMMA_INSTRUMENT") {
+		// Add jacoco-stubs to public, system and test. It doesn't make any real difference as public
+		// allows everyone access but it is needed to ensure consistent flags between the
+		// bootclasspath fragment generated flags and the platform_bootclasspath generated flags.
 		publicStubModules = append(publicStubModules, "jacoco-stubs")
+		systemStubModules = append(systemStubModules, "jacoco-stubs")
+		testStubModules = append(testStubModules, "jacoco-stubs")
 	}
 
 	m := map[android.SdkKind][]string{}
@@ -119,23 +134,6 @@
 	}
 }
 
-// hiddenAPIGatherStubLibDexJarPaths gathers the paths to the dex jars from the dependencies added
-// in hiddenAPIAddStubLibDependencies.
-func hiddenAPIGatherStubLibDexJarPaths(ctx android.ModuleContext) map[android.SdkKind]android.Paths {
-	m := map[android.SdkKind]android.Paths{}
-	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok {
-			kind := hiddenAPIStubsTag.sdkKind
-			dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, kind)
-			if dexJar != nil {
-				m[kind] = append(m[kind], dexJar)
-			}
-		}
-	})
-	return m
-}
-
 // hiddenAPIRetrieveDexJarBuildPath retrieves the DexJarBuildPath from the specified module, if
 // available, or reports an error.
 func hiddenAPIRetrieveDexJarBuildPath(ctx android.ModuleContext, module android.Module, kind android.SdkKind) android.Path {
@@ -166,20 +164,36 @@
 //
 // The rule is initialized but not built so that the caller can modify it and select an appropriate
 // name.
-func ruleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, outputPath android.OutputPath, bootDexJars android.Paths, sdkKindToPathList map[android.SdkKind]android.Paths) *android.RuleBuilder {
+func ruleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, outputPath android.WritablePath, bootDexJars android.Paths, input HiddenAPIFlagInput) *android.RuleBuilder {
 	// Singleton rule which applies hiddenapi on all boot class path dex files.
 	rule := android.NewRuleBuilder(pctx, ctx)
 
 	tempPath := tempPathForRestat(ctx, outputPath)
 
+	// Find the widest API stubs provided by the fragments on which this depends, if any.
+	var dependencyStubDexJars android.Paths
+	for i := len(hiddenAPIRelevantSdkKinds) - 1; i >= 0; i-- {
+		kind := hiddenAPIRelevantSdkKinds[i]
+		stubsForKind := input.DependencyStubDexJarsByKind[kind]
+		if len(stubsForKind) != 0 {
+			dependencyStubDexJars = stubsForKind
+			break
+		}
+	}
+
 	command := rule.Command().
 		Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")).
 		Text("list").
+		FlagForEachInput("--dependency-stub-dex=", dependencyStubDexJars).
 		FlagForEachInput("--boot-dex=", bootDexJars)
 
 	// Iterate over the sdk kinds in a fixed order.
 	for _, sdkKind := range hiddenAPIRelevantSdkKinds {
-		paths := sdkKindToPathList[sdkKind]
+		// Merge in the stub dex jar paths for this kind from the fragments on which it depends. They
+		// will be needed to resolve dependencies from this fragment's stubs to classes in the other
+		// fragment's APIs.
+		dependencyPaths := input.DependencyStubDexJarsByKind[sdkKind]
+		paths := append(dependencyPaths, input.StubDexJarsByKind[sdkKind]...)
 		if len(paths) > 0 {
 			option := sdkKindToHiddenapiListOption[sdkKind]
 			command.FlagWithInputList("--"+option+"=", paths, ":")
@@ -233,18 +247,9 @@
 	Unsupported_packages []string `android:"path"`
 }
 
-func (p *HiddenAPIFlagFileProperties) hiddenAPIFlagFileInfo(ctx android.ModuleContext) hiddenAPIFlagFileInfo {
-	info := hiddenAPIFlagFileInfo{categoryToPaths: map[*hiddenAPIFlagFileCategory]android.Paths{}}
-	for _, category := range hiddenAPIFlagFileCategories {
-		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
-		info.categoryToPaths[category] = paths
-	}
-	return info
-}
-
 type hiddenAPIFlagFileCategory struct {
-	// propertyName is the name of the property for this category.
-	propertyName string
+	// PropertyName is the name of the property for this category.
+	PropertyName string
 
 	// propertyValueReader retrieves the value of the property for this category from the set of
 	// properties.
@@ -255,10 +260,26 @@
 	commandMutator func(command *android.RuleBuilderCommand, path android.Path)
 }
 
-var hiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
+// The flag file category for removed members of the API.
+//
+// This is extracted from HiddenAPIFlagFileCategories as it is needed to add the dex signatures
+// list of removed API members that are generated automatically from the removed.txt files provided
+// by API stubs.
+var hiddenAPIRemovedFlagFileCategory = &hiddenAPIFlagFileCategory{
+	// See HiddenAPIFlagFileProperties.Removed
+	PropertyName: "removed",
+	propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
+		return properties.Removed
+	},
+	commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+		command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
+	},
+}
+
+var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
 	// See HiddenAPIFlagFileProperties.Unsupported
 	{
-		propertyName: "unsupported",
+		PropertyName: "unsupported",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported
 		},
@@ -266,19 +287,10 @@
 			command.FlagWithInput("--unsupported ", path)
 		},
 	},
-	// See HiddenAPIFlagFileProperties.Removed
-	{
-		propertyName: "removed",
-		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
-			return properties.Removed
-		},
-		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
-			command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
-		},
-	},
+	hiddenAPIRemovedFlagFileCategory,
 	// See HiddenAPIFlagFileProperties.Max_target_r_low_priority
 	{
-		propertyName: "max_target_r_low_priority",
+		PropertyName: "max_target_r_low_priority",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_r_low_priority
 		},
@@ -288,7 +300,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_q
 	{
-		propertyName: "max_target_q",
+		PropertyName: "max_target_q",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_q
 		},
@@ -298,7 +310,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_p
 	{
-		propertyName: "max_target_p",
+		PropertyName: "max_target_p",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_p
 		},
@@ -308,7 +320,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_o_low_priority
 	{
-		propertyName: "max_target_o_low_priority",
+		PropertyName: "max_target_o_low_priority",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_o_low_priority
 		},
@@ -318,7 +330,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Blocked
 	{
-		propertyName: "blocked",
+		PropertyName: "blocked",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Blocked
 		},
@@ -328,7 +340,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Unsupported_packages
 	{
-		propertyName: "unsupported_packages",
+		PropertyName: "unsupported_packages",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported_packages
 		},
@@ -338,63 +350,574 @@
 	},
 }
 
-// hiddenAPIFlagFileInfo contains paths resolved from HiddenAPIFlagFileProperties
-type hiddenAPIFlagFileInfo struct {
-	// categoryToPaths maps from the flag file category to the paths containing information for that
-	// category.
-	categoryToPaths map[*hiddenAPIFlagFileCategory]android.Paths
-}
+// FlagFilesByCategory maps a hiddenAPIFlagFileCategory to the paths to the files in that category.
+type FlagFilesByCategory map[*hiddenAPIFlagFileCategory]android.Paths
 
-func (i *hiddenAPIFlagFileInfo) append(other hiddenAPIFlagFileInfo) {
-	for _, category := range hiddenAPIFlagFileCategories {
-		i.categoryToPaths[category] = append(i.categoryToPaths[category], other.categoryToPaths[category]...)
+// append appends the supplied flags files to the corresponding category in this map.
+func (s FlagFilesByCategory) append(other FlagFilesByCategory) {
+	for _, category := range HiddenAPIFlagFileCategories {
+		s[category] = append(s[category], other[category]...)
 	}
 }
 
-var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{})
+// dedup removes duplicates in the flag files, while maintaining the order in which they were
+// appended.
+func (s FlagFilesByCategory) dedup() {
+	for category, paths := range s {
+		s[category] = android.FirstUniquePaths(paths)
+	}
+}
 
-// ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
-// flags from all the modules, the stub flags, augmented with some additional configuration files.
+// HiddenAPIInfo contains information provided by the hidden API processing.
+//
+// That includes paths resolved from HiddenAPIFlagFileProperties and also generated by hidden API
+// processing.
+type HiddenAPIInfo struct {
+	// FlagFilesByCategory maps from the flag file category to the paths containing information for
+	// that category.
+	FlagFilesByCategory FlagFilesByCategory
+
+	// The paths to the stub dex jars for each of the android.SdkKind in hiddenAPIRelevantSdkKinds.
+	TransitiveStubDexJarsByKind StubDexJarsByKind
+
+	// The output from the hidden API processing needs to be made available to other modules.
+	HiddenAPIFlagOutput
+}
+
+func newHiddenAPIInfo() *HiddenAPIInfo {
+	info := HiddenAPIInfo{
+		FlagFilesByCategory:         FlagFilesByCategory{},
+		TransitiveStubDexJarsByKind: StubDexJarsByKind{},
+	}
+	return &info
+}
+
+func (i *HiddenAPIInfo) mergeFromFragmentDeps(ctx android.ModuleContext, fragments []android.Module) {
+	// Merge all the information from the fragments. The fragments form a DAG so it is possible that
+	// this will introduce duplicates so they will be resolved after processing all the fragments.
+	for _, fragment := range fragments {
+		if ctx.OtherModuleHasProvider(fragment, HiddenAPIInfoProvider) {
+			info := ctx.OtherModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
+			i.TransitiveStubDexJarsByKind.append(info.TransitiveStubDexJarsByKind)
+		}
+	}
+
+	// Dedup and sort paths.
+	i.TransitiveStubDexJarsByKind.dedupAndSort()
+}
+
+var HiddenAPIInfoProvider = blueprint.NewProvider(HiddenAPIInfo{})
+
+// StubDexJarsByKind maps an android.SdkKind to the paths to stub dex jars appropriate for that
+// level. See hiddenAPIRelevantSdkKinds for a list of the acceptable android.SdkKind values.
+type StubDexJarsByKind map[android.SdkKind]android.Paths
+
+// append appends the supplied kind specific stub dex jar pargs to the corresponding kind in this
+// map.
+func (s StubDexJarsByKind) append(other StubDexJarsByKind) {
+	for _, kind := range hiddenAPIRelevantSdkKinds {
+		s[kind] = append(s[kind], other[kind]...)
+	}
+}
+
+// dedupAndSort removes duplicates in the stub dex jar paths and sorts them into a consistent and
+// deterministic order.
+func (s StubDexJarsByKind) dedupAndSort() {
+	for kind, paths := range s {
+		s[kind] = android.SortedUniquePaths(paths)
+	}
+}
+
+// HiddenAPIFlagInput encapsulates information obtained from a module and its dependencies that are
+// needed for hidden API flag generation.
+type HiddenAPIFlagInput struct {
+	// FlagFilesByCategory contains the flag files that override the initial flags that are derived
+	// from the stub dex files.
+	FlagFilesByCategory FlagFilesByCategory
+
+	// StubDexJarsByKind contains the stub dex jars for different android.SdkKind and which determine
+	// the initial flags for each dex member.
+	StubDexJarsByKind StubDexJarsByKind
+
+	// DependencyStubDexJarsByKind contains the stub dex jars provided by the fragments on which this
+	// depends. It is the result of merging HiddenAPIInfo.TransitiveStubDexJarsByKind from each
+	// fragment on which this depends.
+	DependencyStubDexJarsByKind StubDexJarsByKind
+
+	// RemovedTxtFiles is the list of removed.txt files provided by java_sdk_library modules that are
+	// specified in the bootclasspath_fragment's stub_libs and contents properties.
+	RemovedTxtFiles android.Paths
+}
+
+// newHiddenAPIFlagInput creates a new initialize HiddenAPIFlagInput struct.
+func newHiddenAPIFlagInput() HiddenAPIFlagInput {
+	input := HiddenAPIFlagInput{
+		FlagFilesByCategory: FlagFilesByCategory{},
+		StubDexJarsByKind:   StubDexJarsByKind{},
+	}
+
+	return input
+}
+
+// canPerformHiddenAPIProcessing determines whether hidden API processing should be performed.
+//
+// A temporary workaround to avoid existing bootclasspath_fragments that do not provide the
+// appropriate information needed for hidden API processing breaking the build.
+// TODO(b/179354495): Remove this workaround.
+func (i *HiddenAPIFlagInput) canPerformHiddenAPIProcessing(ctx android.ModuleContext, properties bootclasspathFragmentProperties) bool {
+	// Performing hidden API processing without stubs is not supported and it is unlikely to ever be
+	// required as the whole point of adding something to the bootclasspath fragment is to add it to
+	// the bootclasspath in order to be used by something else in the system. Without any stubs it
+	// cannot do that.
+	if len(i.StubDexJarsByKind) == 0 {
+		return false
+	}
+
+	// Hidden API processing is always enabled in tests.
+	if ctx.Config().TestProductVariables != nil {
+		return true
+	}
+
+	// A module that has fragments should have access to the information it needs in order to perform
+	// hidden API processing.
+	if len(properties.Fragments) != 0 {
+		return true
+	}
+
+	// The art bootclasspath fragment does not depend on any other fragments but already supports
+	// hidden API processing.
+	imageName := proptools.String(properties.Image_name)
+	if imageName == "art" {
+		return true
+	}
+
+	// Disable it for everything else.
+	return false
+}
+
+// gatherStubLibInfo gathers information from the stub libs needed by hidden API processing from the
+// dependencies added in hiddenAPIAddStubLibDependencies.
+//
+// That includes paths to the stub dex jars as well as paths to the *removed.txt files.
+func (i *HiddenAPIFlagInput) gatherStubLibInfo(ctx android.ModuleContext, contents []android.Module) {
+	addFromModule := func(ctx android.ModuleContext, module android.Module, kind android.SdkKind) {
+		dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, kind)
+		if dexJar != nil {
+			i.StubDexJarsByKind[kind] = append(i.StubDexJarsByKind[kind], dexJar)
+		}
+
+		if sdkLibrary, ok := module.(SdkLibraryDependency); ok {
+			removedTxtFile := sdkLibrary.SdkRemovedTxtFile(ctx, kind)
+			i.RemovedTxtFiles = append(i.RemovedTxtFiles, removedTxtFile.AsPaths()...)
+		}
+	}
+
+	// If the contents includes any java_sdk_library modules then add them to the stubs.
+	for _, module := range contents {
+		if _, ok := module.(SdkLibraryDependency); ok {
+			// Add information for every possible kind needed by hidden API. SdkCorePlatform is not used
+			// as the java_sdk_library does not have special support for core_platform API, instead it is
+			// implemented as a customized form of SdkPublic.
+			for _, kind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkTest} {
+				addFromModule(ctx, module, kind)
+			}
+		}
+	}
+
+	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok {
+			kind := hiddenAPIStubsTag.sdkKind
+			addFromModule(ctx, module, kind)
+		}
+	})
+
+	// Normalize the paths, i.e. remove duplicates and sort.
+	i.StubDexJarsByKind.dedupAndSort()
+	i.RemovedTxtFiles = android.SortedUniquePaths(i.RemovedTxtFiles)
+}
+
+// extractFlagFilesFromProperties extracts the paths to flag files that are specified in the
+// supplied properties and stores them in this struct.
+func (i *HiddenAPIFlagInput) extractFlagFilesFromProperties(ctx android.ModuleContext, p *HiddenAPIFlagFileProperties) {
+	for _, category := range HiddenAPIFlagFileCategories {
+		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
+		i.FlagFilesByCategory[category] = paths
+	}
+}
+
+func (i *HiddenAPIFlagInput) transitiveStubDexJarsByKind() StubDexJarsByKind {
+	transitive := i.DependencyStubDexJarsByKind
+	transitive.append(i.StubDexJarsByKind)
+	return transitive
+}
+
+// HiddenAPIFlagOutput contains paths to output files from the hidden API flag generation for a
+// bootclasspath_fragment module.
+type HiddenAPIFlagOutput struct {
+	// The path to the generated stub-flags.csv file.
+	StubFlagsPath android.Path
+
+	// The path to the generated annotation-flags.csv file.
+	AnnotationFlagsPath android.Path
+
+	// The path to the generated metadata.csv file.
+	MetadataPath android.Path
+
+	// The path to the generated index.csv file.
+	IndexPath android.Path
+
+	// The path to the generated all-flags.csv file.
+	AllFlagsPath android.Path
+}
+
+// bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
+// path.
+type bootDexJarByModule map[string]android.Path
+
+// addPath adds the path for a module to the map.
+func (b bootDexJarByModule) addPath(module android.Module, path android.Path) {
+	b[android.RemoveOptionalPrebuiltPrefix(module.Name())] = path
+}
+
+// pathForValidation creates a path of the same type as the supplied type but with a name of
+// <path>.valid.
+//
+// e.g. If path is an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv then this will return
+// an OutputPath for out/soong/hiddenapi/hiddenapi-flags.csv.valid
+func pathForValidation(ctx android.PathContext, path android.WritablePath) android.WritablePath {
+	extWithoutLeadingDot := strings.TrimPrefix(path.Ext(), ".")
+	return path.ReplaceExtension(ctx, extWithoutLeadingDot+".valid")
+}
+
+// buildRuleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from
+// the flags from all the modules, the stub flags, augmented with some additional configuration
+// files.
 //
 // baseFlagsPath is the path to the flags file containing all the information from the stubs plus
 // an entry for every single member in the dex implementation jars of the individual modules. Every
 // signature in any of the other files MUST be included in this file.
 //
-// moduleSpecificFlagsPaths are the paths to the flags files generated by each module using
-// information from the baseFlagsPath as well as from annotations within the source.
+// annotationFlags is the path to the annotation flags file generated from annotation information
+// in each module.
 //
-// augmentationInfo is a struct containing paths to files that augment the information provided by
-// the moduleSpecificFlagsPaths.
-// ruleToGenerateHiddenApiFlags creates a rule to create the monolithic hidden API flags from the
-// flags from all the modules, the stub flags, augmented with some additional configuration files.
-//
-// baseFlagsPath is the path to the flags file containing all the information from the stubs plus
-// an entry for every single member in the dex implementation jars of the individual modules. Every
-// signature in any of the other files MUST be included in this file.
-//
-// moduleSpecificFlagsPaths are the paths to the flags files generated by each module using
-// information from the baseFlagsPath as well as from annotations within the source.
-//
-// augmentationInfo is a struct containing paths to files that augment the information provided by
-// the moduleSpecificFlagsPaths.
-func ruleToGenerateHiddenApiFlags(ctx android.BuilderContext, outputPath android.WritablePath, baseFlagsPath android.Path, moduleSpecificFlagsPaths android.Paths, augmentationInfo hiddenAPIFlagFileInfo) {
+// hiddenAPIInfo is a struct containing paths to files that augment the information provided by
+// the annotationFlags.
+func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc string,
+	outputPath android.WritablePath, baseFlagsPath android.Path, annotationFlags android.Path,
+	flagFilesByCategory FlagFilesByCategory, allFlagsPaths android.Paths, generatedRemovedDexSignatures android.OptionalPath) {
+
+	// The file which is used to record that the flags file is valid.
+	var validFile android.WritablePath
+
+	// If there are flag files that have been generated by fragments on which this depends then use
+	// them to validate the flag file generated by the rules created by this method.
+	if len(allFlagsPaths) > 0 {
+		// The flags file generated by the rule created by this method needs to be validated to ensure
+		// that it is consistent with the flag files generated by the individual fragments.
+
+		validFile = pathForValidation(ctx, outputPath)
+
+		// Create a rule to validate the output from the following rule.
+		rule := android.NewRuleBuilder(pctx, ctx)
+		rule.Command().
+			BuiltTool("verify_overlaps").
+			Input(outputPath).
+			Inputs(allFlagsPaths).
+			// If validation passes then update the file that records that.
+			Text("&& touch").Output(validFile)
+		rule.Build(name+"Validation", desc+" validation")
+	}
+
+	// Create the rule that will generate the flag files.
 	tempPath := tempPathForRestat(ctx, outputPath)
 	rule := android.NewRuleBuilder(pctx, ctx)
 	command := rule.Command().
 		BuiltTool("generate_hiddenapi_lists").
 		FlagWithInput("--csv ", baseFlagsPath).
-		Inputs(moduleSpecificFlagsPaths).
+		Input(annotationFlags).
 		FlagWithOutput("--output ", tempPath)
 
 	// Add the options for the different categories of flag files.
-	for _, category := range hiddenAPIFlagFileCategories {
-		paths := augmentationInfo.categoryToPaths[category]
+	for _, category := range HiddenAPIFlagFileCategories {
+		paths := flagFilesByCategory[category]
 		for _, path := range paths {
 			category.commandMutator(command, path)
 		}
 	}
 
+	// If available then pass the automatically generated file containing dex signatures of removed
+	// API members to the rule so they can be marked as removed.
+	if generatedRemovedDexSignatures.Valid() {
+		hiddenAPIRemovedFlagFileCategory.commandMutator(command, generatedRemovedDexSignatures.Path())
+	}
+
 	commitChangeForRestat(rule, tempPath, outputPath)
 
-	rule.Build("hiddenAPIFlagsFile", "hiddenapi flags")
+	if validFile != nil {
+		// Add the file that indicates that the file generated by this is valid.
+		//
+		// This will cause the validation rule above to be run any time that the output of this rule
+		// changes but the validation will run in parallel with other rules that depend on this file.
+		command.Validation(validFile)
+	}
+
+	rule.Build(name, desc)
+}
+
+// hiddenAPIGenerateAllFlagsForBootclasspathFragment will generate all the flags for a fragment
+// of the bootclasspath.
+//
+// It takes:
+// * Map from android.SdkKind to stub dex jar paths defining the API for that sdk kind.
+// * The list of modules that are the contents of the fragment.
+// * The additional manually curated flag files to use.
+//
+// It generates:
+// * stub-flags.csv
+// * annotation-flags.csv
+// * metadata.csv
+// * index.csv
+// * all-flags.csv
+func hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIFlagOutput {
+	hiddenApiSubDir := "modular-hiddenapi"
+
+	// Gather the dex files for the boot libraries provided by this fragment.
+	bootDexJars := extractBootDexJarsFromModules(ctx, contents)
+
+	// Generate the stub-flags.csv.
+	stubFlagsCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "stub-flags.csv")
+	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlagsCSV, bootDexJars, input)
+	rule.Build("modularHiddenAPIStubFlagsFile", "modular hiddenapi stub flags")
+
+	// Extract the classes jars from the contents.
+	classesJars := extractClassesJarsFromModules(contents)
+
+	// Generate the set of flags from the annotations in the source code.
+	annotationFlagsCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "annotation-flags.csv")
+	buildRuleToGenerateAnnotationFlags(ctx, "modular hiddenapi annotation flags", classesJars, stubFlagsCSV, annotationFlagsCSV)
+
+	// Generate the metadata from the annotations in the source code.
+	metadataCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "metadata.csv")
+	buildRuleToGenerateMetadata(ctx, "modular hiddenapi metadata", classesJars, stubFlagsCSV, metadataCSV)
+
+	// Generate the index file from the CSV files in the classes jars.
+	indexCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "index.csv")
+	buildRuleToGenerateIndex(ctx, "modular hiddenapi index", classesJars, indexCSV)
+
+	// Removed APIs need to be marked and in order to do that the hiddenAPIInfo needs to specify files
+	// containing dex signatures of all the removed APIs. In the monolithic files that is done by
+	// manually combining all the removed.txt files for each API and then converting them to dex
+	// signatures, see the combined-removed-dex module. This does that automatically by using the
+	// *removed.txt files retrieved from the java_sdk_library modules that are specified in the
+	// stub_libs and contents properties of a bootclasspath_fragment.
+	removedDexSignatures := buildRuleToGenerateRemovedDexSignatures(ctx, input.RemovedTxtFiles)
+
+	// Generate the all-flags.csv which are the flags that will, in future, be encoded into the dex
+	// files.
+	outputPath := android.PathForModuleOut(ctx, hiddenApiSubDir, "all-flags.csv")
+	buildRuleToGenerateHiddenApiFlags(ctx, "modularHiddenApiAllFlags", "modular hiddenapi all flags", outputPath, stubFlagsCSV, annotationFlagsCSV, input.FlagFilesByCategory, nil, removedDexSignatures)
+
+	// Store the paths in the info for use by other modules and sdk snapshot generation.
+	output := HiddenAPIFlagOutput{
+		StubFlagsPath:       stubFlagsCSV,
+		AnnotationFlagsPath: annotationFlagsCSV,
+		MetadataPath:        metadataCSV,
+		IndexPath:           indexCSV,
+		AllFlagsPath:        outputPath,
+	}
+	return &output
+}
+
+func buildRuleToGenerateRemovedDexSignatures(ctx android.ModuleContext, removedTxtFiles android.Paths) android.OptionalPath {
+	if len(removedTxtFiles) == 0 {
+		return android.OptionalPath{}
+	}
+
+	output := android.PathForModuleOut(ctx, "modular-hiddenapi/removed-dex-signatures.txt")
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("metalava").
+		Flag("--no-banner").
+		Inputs(removedTxtFiles).
+		FlagWithOutput("--dex-api ", output)
+	rule.Build("modular-hiddenapi-removed-dex-signatures", "modular hiddenapi removed dex signatures")
+	return android.OptionalPathForPath(output)
+}
+
+// extractBootDexJarsFromModules extracts the boot dex jars from the supplied modules.
+func extractBootDexJarsFromModules(ctx android.ModuleContext, contents []android.Module) android.Paths {
+	bootDexJars := android.Paths{}
+	for _, module := range contents {
+		hiddenAPIModule := hiddenAPIModuleFromModule(ctx, module)
+		if hiddenAPIModule == nil {
+			continue
+		}
+		bootDexJar := hiddenAPIModule.bootDexJar()
+		if bootDexJar == nil {
+			if ctx.Config().AlwaysUsePrebuiltSdks() {
+				// TODO(b/179354495): Remove this workaround when it is unnecessary.
+				// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
+				// create a fake one that will cause a build error only if it is used.
+				fake := android.PathForModuleOut(ctx, "fake/boot-dex/%s.jar", module.Name())
+
+				// Create an error rule that pretends to create the output file but will actually fail if it
+				// is run.
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   android.ErrorRule,
+					Output: fake,
+					Args: map[string]string{
+						"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
+					},
+				})
+				bootDexJars = append(bootDexJars, fake)
+			} else {
+				ctx.ModuleErrorf("module %s does not provide a dex jar", module)
+			}
+		} else {
+			bootDexJars = append(bootDexJars, bootDexJar)
+		}
+	}
+	return bootDexJars
+}
+
+func hiddenAPIModuleFromModule(ctx android.BaseModuleContext, module android.Module) hiddenAPIModule {
+	if hiddenAPIModule, ok := module.(hiddenAPIModule); ok {
+		return hiddenAPIModule
+	} else if _, ok := module.(*DexImport); ok {
+		// Ignore this for the purposes of hidden API processing
+	} else {
+		ctx.ModuleErrorf("module %s does not implement hiddenAPIModule", module)
+	}
+
+	return nil
+}
+
+// extractClassesJarsFromModules extracts the class jars from the supplied modules.
+func extractClassesJarsFromModules(contents []android.Module) android.Paths {
+	classesJars := android.Paths{}
+	for _, module := range contents {
+		classesJars = append(classesJars, retrieveClassesJarsFromModule(module)...)
+	}
+	return classesJars
+}
+
+// retrieveClassesJarsFromModule retrieves the classes jars from the supplied module.
+func retrieveClassesJarsFromModule(module android.Module) android.Paths {
+	if hiddenAPIModule, ok := module.(hiddenAPIModule); ok {
+		return hiddenAPIModule.classesJars()
+	}
+
+	return nil
+}
+
+// deferReportingMissingBootDexJar returns true if a missing boot dex jar should not be reported by
+// Soong but should instead only be reported in ninja if the file is actually built.
+func deferReportingMissingBootDexJar(ctx android.ModuleContext, module android.Module) bool {
+	// TODO(b/179354495): Remove this workaround when it is unnecessary.
+	// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
+	// create a fake one that will cause a build error only if it is used.
+	if ctx.Config().AlwaysUsePrebuiltSdks() {
+		return true
+	}
+
+	// This is called for both platform_bootclasspath and bootclasspath_fragment modules.
+	//
+	// A bootclasspath_fragment module should only use the APEX variant of source or prebuilt modules.
+	// Ideally, a bootclasspath_fragment module should never have a platform variant created for it
+	// but unfortunately, due to b/187910671 it does.
+	//
+	// That causes issues when obtaining a boot dex jar for a prebuilt module as a prebuilt module
+	// used by a bootclasspath_fragment can only provide a boot dex jar when it is part of APEX, i.e.
+	// has an APEX variant not a platform variant.
+	//
+	// There are some other situations when a prebuilt module used by a bootclasspath_fragment cannot
+	// provide a boot dex jar:
+	// 1. If the bootclasspath_fragment is not exported by the prebuilt_apex/apex_set module then it
+	//    does not have an APEX variant and only has a platform variant and neither do its content
+	//    modules.
+	// 2. Some build configurations, e.g. setting TARGET_BUILD_USE_PREBUILT_SDKS causes all
+	//    java_sdk_library_import modules to be treated as preferred and as many of them are not part
+	//    of an apex they cannot provide a boot dex jar.
+	//
+	// The first case causes problems when the affected prebuilt modules are preferred but that is an
+	// invalid configuration and it is ok for it to fail as the work to enable that is not yet
+	// complete. The second case is used for building targets that do not use boot dex jars and so
+	// deferring error reporting to ninja is fine as the affected ninja targets should never be built.
+	// That is handled above.
+	//
+	// A platform_bootclasspath module can use libraries from both platform and APEX variants. Unlike
+	// the bootclasspath_fragment it supports dex_import modules which provides the dex file. So, it
+	// can obtain a boot dex jar from a prebuilt that is not part of an APEX. However, it is assumed
+	// that if the library can be part of an APEX then it is the APEX variant that is used.
+	//
+	// This check handles the slightly different requirements of the bootclasspath_fragment and
+	// platform_bootclasspath modules by only deferring error reporting for the platform variant of
+	// a prebuilt modules that has other variants which are part of an APEX.
+	//
+	// TODO(b/187910671): Remove this once platform variants are no longer created unnecessarily.
+	if android.IsModulePrebuilt(module) {
+		if am, ok := module.(android.ApexModule); ok && am.InAnyApex() {
+			apexInfo := ctx.OtherModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
+			if apexInfo.IsForPlatform() {
+				return true
+			}
+		}
+	}
+
+	// A bootclasspath module that is part of a versioned sdk never provides a boot dex jar as there
+	// is no equivalently versioned prebuilt APEX file from which it can be obtained. However,
+	// versioned bootclasspath modules are processed by Soong so in order to avoid them causing build
+	// failures missing boot dex jars need to be deferred.
+	if android.IsModuleInVersionedSdk(ctx.Module()) {
+		return true
+	}
+
+	return false
+}
+
+// handleMissingDexBootFile will either log a warning or create an error rule to create the fake
+// file depending on the value returned from deferReportingMissingBootDexJar.
+func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath) {
+	if deferReportingMissingBootDexJar(ctx, module) {
+		// Create an error rule that pretends to create the output file but will actually fail if it
+		// is run.
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.ErrorRule,
+			Output: fake,
+			Args: map[string]string{
+				"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
+			},
+		})
+	} else {
+		ctx.ModuleErrorf("module %s does not provide a dex jar", module)
+	}
+}
+
+// retrieveEncodedBootDexJarFromModule returns a path to the boot dex jar from the supplied module's
+// DexJarBuildPath() method.
+//
+// The returned path will usually be to a dex jar file that has been encoded with hidden API flags.
+// However, under certain conditions, e.g. errors, or special build configurations it will return
+// a path to a fake file.
+func retrieveEncodedBootDexJarFromModule(ctx android.ModuleContext, module android.Module) android.Path {
+	bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
+	if bootDexJar == nil {
+		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/encoded-dex/%s.jar", module.Name()))
+		bootDexJar = fake
+
+		handleMissingDexBootFile(ctx, module, fake)
+	}
+	return bootDexJar
+}
+
+// extractEncodedDexJarsFromModules extracts the encoded dex jars from the supplied modules.
+func extractEncodedDexJarsFromModules(ctx android.ModuleContext, contents []android.Module) bootDexJarByModule {
+	encodedDexJarsByModuleName := bootDexJarByModule{}
+	for _, module := range contents {
+		path := retrieveEncodedBootDexJarFromModule(ctx, module)
+		encodedDexJarsByModuleName.addPath(module, path)
+	}
+	return encodedDexJarsByModuleName
 }
diff --git a/java/hiddenapi_monolithic.go b/java/hiddenapi_monolithic.go
new file mode 100644
index 0000000..edf4235
--- /dev/null
+++ b/java/hiddenapi_monolithic.go
@@ -0,0 +1,102 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+// MonolithicHiddenAPIInfo contains information needed/provided by the hidden API generation of the
+// monolithic hidden API files.
+//
+// Each list of paths includes all the equivalent paths from each of the bootclasspath_fragment
+// modules that contribute to the platform-bootclasspath.
+type MonolithicHiddenAPIInfo struct {
+	// FlagsFilesByCategory maps from the flag file category to the paths containing information for
+	// that category.
+	FlagsFilesByCategory FlagFilesByCategory
+
+	// The paths to the generated stub-flags.csv files.
+	StubFlagsPaths android.Paths
+
+	// The paths to the generated annotation-flags.csv files.
+	AnnotationFlagsPaths android.Paths
+
+	// The paths to the generated metadata.csv files.
+	MetadataPaths android.Paths
+
+	// The paths to the generated index.csv files.
+	IndexPaths android.Paths
+
+	// The paths to the generated all-flags.csv files.
+	AllFlagsPaths android.Paths
+}
+
+// newMonolithicHiddenAPIInfo creates a new MonolithicHiddenAPIInfo from the flagFilesByCategory
+// plus information provided by each of the fragments.
+func newMonolithicHiddenAPIInfo(ctx android.ModuleContext, flagFilesByCategory FlagFilesByCategory, fragments []android.Module) MonolithicHiddenAPIInfo {
+	monolithicInfo := MonolithicHiddenAPIInfo{}
+
+	monolithicInfo.FlagsFilesByCategory = flagFilesByCategory
+
+	// Merge all the information from the fragments. The fragments form a DAG so it is possible that
+	// this will introduce duplicates so they will be resolved after processing all the fragments.
+	for _, fragment := range fragments {
+		if ctx.OtherModuleHasProvider(fragment, HiddenAPIInfoProvider) {
+			info := ctx.OtherModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
+			monolithicInfo.append(&info)
+		}
+	}
+
+	// Dedup paths.
+	monolithicInfo.dedup()
+
+	return monolithicInfo
+}
+
+// append appends all the files from the supplied info to the corresponding files in this struct.
+func (i *MonolithicHiddenAPIInfo) append(other *HiddenAPIInfo) {
+	i.FlagsFilesByCategory.append(other.FlagFilesByCategory)
+
+	// The output may not be set if the bootclasspath_fragment has not yet been updated to support
+	// hidden API processing.
+	// TODO(b/179354495): Switch back to append once all bootclasspath_fragment modules have been
+	//  updated to support hidden API processing properly.
+	appendIfNotNil := func(paths android.Paths, path android.Path) android.Paths {
+		if path == nil {
+			return paths
+		}
+		return append(paths, path)
+	}
+	i.StubFlagsPaths = appendIfNotNil(i.StubFlagsPaths, other.StubFlagsPath)
+	i.AnnotationFlagsPaths = appendIfNotNil(i.AnnotationFlagsPaths, other.AnnotationFlagsPath)
+	i.MetadataPaths = appendIfNotNil(i.MetadataPaths, other.MetadataPath)
+	i.IndexPaths = appendIfNotNil(i.IndexPaths, other.IndexPath)
+	i.AllFlagsPaths = appendIfNotNil(i.AllFlagsPaths, other.AllFlagsPath)
+}
+
+// dedup removes duplicates in all the paths, while maintaining the order in which they were
+// appended.
+func (i *MonolithicHiddenAPIInfo) dedup() {
+	i.FlagsFilesByCategory.dedup()
+	i.StubFlagsPaths = android.FirstUniquePaths(i.StubFlagsPaths)
+	i.AnnotationFlagsPaths = android.FirstUniquePaths(i.AnnotationFlagsPaths)
+	i.MetadataPaths = android.FirstUniquePaths(i.MetadataPaths)
+	i.IndexPaths = android.FirstUniquePaths(i.IndexPaths)
+	i.AllFlagsPaths = android.FirstUniquePaths(i.AllFlagsPaths)
+}
+
+var MonolithicHiddenAPIInfoProvider = blueprint.NewProvider(MonolithicHiddenAPIInfo{})
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index f6af501..52934a3 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -117,7 +117,6 @@
 }
 
 type hiddenAPISingleton struct {
-	flags android.Path
 }
 
 // hiddenAPI singleton rules
@@ -136,24 +135,15 @@
 	// consistency.
 
 	if ctx.Config().PrebuiltHiddenApiDir(ctx) != "" {
-		h.flags = prebuiltFlagsRule(ctx)
+		prebuiltFlagsRule(ctx)
 		prebuiltIndexRule(ctx)
 		return
 	}
-
-	// These rules depend on files located in frameworks/base, skip them if running in a tree that doesn't have them.
-	if ctx.Config().FrameworksBaseDirExists(ctx) {
-		h.flags = flagsRule(ctx)
-	} else {
-		h.flags = emptyFlagsRule(ctx)
-	}
 }
 
 // Checks to see whether the supplied module variant is in the list of boot jars.
 //
-// Apart from the context this is identical to isModuleInConfiguredListForSingleton.
-//
-// TODO(b/179354495): Avoid having to perform this type of check or if necessary dedup it.
+// TODO(b/179354495): Avoid having to perform this type of check.
 func isModuleInConfiguredList(ctx android.BaseModuleContext, module android.Module, configuredBootJars android.ConfiguredJarList) bool {
 	name := ctx.OtherModuleName(module)
 
@@ -177,11 +167,11 @@
 	// Now match the apex part of the boot image configuration.
 	requiredApex := configuredBootJars.Apex(index)
 	if requiredApex == "platform" || requiredApex == "system_ext" {
-		if len(apexInfo.InApexes) != 0 {
+		if len(apexInfo.InApexVariants) != 0 {
 			// A platform variant is required but this is for an apex so ignore it.
 			return false
 		}
-	} else if !apexInfo.InApexByBaseName(requiredApex) {
+	} else if !apexInfo.InApexVariant(requiredApex) {
 		// An apex variant for a specific apex is required but this is the wrong apex.
 		return false
 	}
@@ -189,7 +179,7 @@
 	return true
 }
 
-func prebuiltFlagsRule(ctx android.SingletonContext) android.Path {
+func prebuiltFlagsRule(ctx android.SingletonContext) {
 	outputPath := hiddenAPISingletonPaths(ctx).flags
 	inputPath := android.PathForSource(ctx, ctx.Config().PrebuiltHiddenApiDir(ctx), "hiddenapi-flags.csv")
 
@@ -198,8 +188,6 @@
 		Output: outputPath,
 		Input:  inputPath,
 	})
-
-	return outputPath
 }
 
 func prebuiltIndexRule(ctx android.SingletonContext) {
@@ -213,28 +201,6 @@
 	})
 }
 
-// flagsRule is a placeholder that simply returns the location of the file, the generation of the
-// ninja rules is done in generateHiddenAPIBuildActions.
-func flagsRule(ctx android.SingletonContext) android.Path {
-	outputPath := hiddenAPISingletonPaths(ctx).flags
-	return outputPath
-}
-
-// emptyFlagsRule creates a rule to build an empty hiddenapi-flags.csv, which is needed by master-art-host builds that
-// have a partial manifest without frameworks/base but still need to build a boot image.
-func emptyFlagsRule(ctx android.SingletonContext) android.Path {
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	outputPath := hiddenAPISingletonPaths(ctx).flags
-
-	rule.Command().Text("rm").Flag("-f").Output(outputPath)
-	rule.Command().Text("touch").Output(outputPath)
-
-	rule.Build("emptyHiddenAPIFlagsFile", "empty hiddenapi flags")
-
-	return outputPath
-}
-
 // tempPathForRestat creates a path of the same type as the supplied type but with a name of
 // <path>.tmp.
 //
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index 3ab2277..dcd363c 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -60,10 +60,7 @@
 }
 
 func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
-	expectedErrorMessage :=
-		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been" +
-			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a" +
-			" suitable boot dex jar"
+	expectedErrorMessage := "module prebuilt_foo{os:android,arch:common} does not provide a dex jar"
 
 	android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
@@ -277,3 +274,56 @@
 
 	android.AssertStringEquals(t, "hiddenapi encode dex rule flags csv", expectedFlagsCsv, actualFlagsCsv)
 }
+
+func TestHiddenAPIEncoding_JavaSdkLibrary(t *testing.T) {
+
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
+		FixtureConfigureBootJars("platform:foo"),
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+
+		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
+		// is disabled.
+		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			shared_library: false,
+			compile_dex: true,
+			public: {enabled: true},
+		}
+	`)
+
+	checkDexEncoded := func(t *testing.T, name, unencodedDexJar, encodedDexJar string) {
+		moduleForTests := result.ModuleForTests(name, "android_common")
+
+		encodeDexRule := moduleForTests.Rule("hiddenAPIEncodeDex")
+		actualUnencodedDexJar := encodeDexRule.Input
+
+		// Make sure that the module has its dex jar encoded.
+		android.AssertStringEquals(t, "encode embedded java_library", unencodedDexJar, actualUnencodedDexJar.String())
+
+		// Make sure that the encoded dex jar is the exported one.
+		exportedDexJar := moduleForTests.Module().(UsesLibraryDependency).DexJarBuildPath()
+		android.AssertPathRelativeToTopEquals(t, "encode embedded java_library", encodedDexJar, exportedDexJar)
+	}
+
+	// The java_library embedded with the java_sdk_library must be dex encoded.
+	t.Run("foo", func(t *testing.T) {
+		expectedUnencodedDexJar := "out/soong/.intermediates/foo/android_common/aligned/foo.jar"
+		expectedEncodedDexJar := "out/soong/.intermediates/foo/android_common/hiddenapi/foo.jar"
+		checkDexEncoded(t, "foo", expectedUnencodedDexJar, expectedEncodedDexJar)
+	})
+
+	// The dex jar of the child implementation java_library of the java_sdk_library is not currently
+	// dex encoded.
+	t.Run("foo.impl", func(t *testing.T) {
+		fooImpl := result.ModuleForTests("foo.impl", "android_common")
+		encodeDexRule := fooImpl.MaybeRule("hiddenAPIEncodeDex")
+		if encodeDexRule.Rule != nil {
+			t.Errorf("foo.impl is not expected to be encoded")
+		}
+	})
+}
diff --git a/java/java.go b/java/java.go
index 9a5fbfc..2bbb5b1 100644
--- a/java/java.go
+++ b/java/java.go
@@ -57,6 +57,10 @@
 	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
 	ctx.RegisterModuleType("dex_import", DexImportFactory)
 
+	// This mutator registers dependencies on dex2oat for modules that should be
+	// dexpreopted. This is done late when the final variants have been
+	// established, to not get the dependencies split into the wrong variants and
+	// to support the checks in dexpreoptDisabled().
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator).Parallel()
 	})
@@ -481,11 +485,6 @@
 }
 
 func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Initialize the hiddenapi structure. Pass in the configuration name rather than the module name
-	// so the hidden api will encode the <x>.impl java_ library created by java_sdk_library just as it
-	// would the <x> library if <x> was configured as a boot jar.
-	j.initHiddenAPI(ctx, j.ConfigurationName())
-
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
 
@@ -1241,9 +1240,6 @@
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
 
-	// Initialize the hiddenapi structure.
-	j.initHiddenAPI(ctx, j.BaseModuleName())
-
 	if !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform() {
 		j.hideApexVariantFromMake = true
 	}
@@ -1315,7 +1311,9 @@
 			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
 			if dexOutputPath := di.PrebuiltExportPath(j.BaseModuleName(), ".dexjar"); dexOutputPath != nil {
 				j.dexJarFile = dexOutputPath
-				j.hiddenAPIExtractInformation(ctx, dexOutputPath, outputFile)
+
+				// Initialize the hiddenapi structure.
+				j.initHiddenAPI(ctx, dexOutputPath, outputFile, nil)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
@@ -1346,9 +1344,11 @@
 				return
 			}
 
-			// Hidden API CSV generation and dex encoding
-			dexOutputFile = j.hiddenAPIExtractAndEncode(ctx, dexOutputFile, outputFile,
-				proptools.Bool(j.dexProperties.Uncompress_dex))
+			// Initialize the hiddenapi structure.
+			j.initHiddenAPI(ctx, dexOutputFile, outputFile, j.dexProperties.Uncompress_dex)
+
+			// Encode hidden API flags in dex file.
+			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)
 
 			j.dexJarFile = dexOutputFile
 		}
diff --git a/java/java_test.go b/java/java_test.go
index 1b8aec2..78d9ab4 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -19,7 +19,6 @@
 	"os"
 	"path/filepath"
 	"reflect"
-	"regexp"
 	"runtime"
 	"strconv"
 	"strings"
@@ -655,306 +654,6 @@
 	})
 }
 
-func TestJavaSdkLibraryImport(t *testing.T) {
-	result := prepareForJavaTest.RunTestWithBp(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "current",
-		}
-
-		java_library {
-			name: "foo.system",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "system_current",
-		}
-
-		java_library {
-			name: "foo.test",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "test_current",
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			public: {
-				jars: ["a.jar"],
-			},
-			system: {
-				jars: ["b.jar"],
-			},
-			test: {
-				jars: ["c.jar"],
-				stub_srcs: ["c.java"],
-			},
-		}
-		`)
-
-	for _, scope := range []string{"", ".system", ".test"} {
-		fooModule := result.ModuleForTests("foo"+scope, "android_common")
-		javac := fooModule.Rule("javac")
-
-		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
-		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
-	}
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`prebuilt_sdklib.stubs.source.test`,
-		`prebuilt_sdklib.stubs.system`,
-		`prebuilt_sdklib.stubs.test`,
-	})
-}
-
-func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`prebuilt_sdklib`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-
-	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`sdklib.impl`,
-		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
-		// dependency is added after prebuilts may have been renamed and so has to use
-		// the renamed name.
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			prefer: true,
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`prebuilt_sdklib`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-
-	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`sdklib.impl`,
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryEnforce(t *testing.T) {
-	partitionToBpOption := func(partition string) string {
-		switch partition {
-		case "system":
-			return ""
-		case "vendor":
-			return "soc_specific: true,"
-		case "product":
-			return "product_specific: true,"
-		default:
-			panic("Invalid partition group name: " + partition)
-		}
-	}
-
-	type testConfigInfo struct {
-		libraryType                string
-		fromPartition              string
-		toPartition                string
-		enforceVendorInterface     bool
-		enforceProductInterface    bool
-		enforceJavaSdkLibraryCheck bool
-		allowList                  []string
-	}
-
-	createPreparer := func(info testConfigInfo) android.FixturePreparer {
-		bpFileTemplate := `
-			java_library {
-				name: "foo",
-				srcs: ["foo.java"],
-				libs: ["bar"],
-				sdk_version: "current",
-				%s
-			}
-
-			%s {
-				name: "bar",
-				srcs: ["bar.java"],
-				sdk_version: "current",
-				%s
-			}
-		`
-
-		bpFile := fmt.Sprintf(bpFileTemplate,
-			partitionToBpOption(info.fromPartition),
-			info.libraryType,
-			partitionToBpOption(info.toPartition))
-
-		return android.GroupFixturePreparers(
-			PrepareForTestWithJavaSdkLibraryFiles,
-			FixtureWithLastReleaseApis("bar"),
-			android.FixtureWithRootAndroidBp(bpFile),
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
-				if info.enforceVendorInterface {
-					variables.DeviceVndkVersion = proptools.StringPtr("current")
-				}
-				variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
-				variables.InterPartitionJavaLibraryAllowList = info.allowList
-			}),
-		)
-	}
-
-	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
-		t.Run(fmt.Sprintf("%v", info), func(t *testing.T) {
-			errorHandler := android.FixtureExpectsNoErrors
-			if expectedErrorPattern != "" {
-				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
-			}
-			android.GroupFixturePreparers(
-				prepareForJavaTest,
-				createPreparer(info),
-			).
-				ExtendWithErrorHandler(errorHandler).
-				RunTest(t)
-		})
-	}
-
-	errorMessage := "is not allowed across the partitions"
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: false,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    false,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-		allowList:                  []string{"bar"},
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "product",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "vendor",
-		toPartition:                "product",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-}
-
 func TestDefaults(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_defaults {
@@ -1411,521 +1110,6 @@
 	})
 }
 
-func TestJavaSdkLibrary(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithPrebuiltApis(map[string][]string{
-			"28": {"foo"},
-			"29": {"foo"},
-			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
-		}),
-	).RunTestWithBp(t, `
-		droiddoc_exported_dir {
-			name: "droiddoc-templates-sdk",
-			path: ".",
-		}
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-		}
-		java_sdk_library {
-			name: "bar",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["bar"],
-		}
-		java_library {
-			name: "baz",
-			srcs: ["c.java"],
-			libs: ["foo", "bar.stubs"],
-			sdk_version: "system_current",
-		}
-		java_sdk_library {
-			name: "barney",
-			srcs: ["c.java"],
-			api_only: true,
-		}
-		java_sdk_library {
-			name: "betty",
-			srcs: ["c.java"],
-			shared_library: false,
-		}
-		java_sdk_library_import {
-		    name: "quuz",
-				public: {
-					jars: ["c.jar"],
-				},
-		}
-		java_sdk_library_import {
-		    name: "fred",
-				public: {
-					jars: ["b.jar"],
-				},
-		}
-		java_sdk_library_import {
-		    name: "wilma",
-				public: {
-					jars: ["b.jar"],
-				},
-				shared_library: false,
-		}
-		java_library {
-		    name: "qux",
-		    srcs: ["c.java"],
-		    libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"],
-		    sdk_version: "system_current",
-		}
-		java_library {
-			name: "baz-test",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "test_current",
-		}
-		java_library {
-			name: "baz-29",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "system_29",
-		}
-		java_library {
-			name: "baz-module-30",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "module_30",
-		}
-		`)
-
-	// check the existence of the internal modules
-	result.ModuleForTests("foo", "android_common")
-	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
-	result.ModuleForTests("foo.api.public.28", "")
-	result.ModuleForTests("foo.api.system.28", "")
-	result.ModuleForTests("foo.api.test.28", "")
-
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
-	// tests if baz is actually linked to the stubs lib
-	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
-	// ... and not to the impl lib
-	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
-	// test if baz is not linked to the system variant of foo
-	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
-
-	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
-	// tests if baz-test is actually linked to the test stubs lib
-	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
-
-	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
-	// tests if baz-29 is actually linked to the system 29 stubs lib
-	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar")
-
-	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
-	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
-	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar")
-
-	// test if baz has exported SDK lib names foo and bar to qux
-	qux := result.ModuleForTests("qux", "android_common")
-	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
-		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
-	}
-}
-
-func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			libs: ["lib"],
-			static_libs: ["static-lib"],
-			impl_only_libs: ["impl-only-lib"],
-			stub_only_libs: ["stub-only-lib"],
-			stub_only_static_libs: ["stub-only-static-lib"],
-		}
-		java_defaults {
-			name: "defaults",
-			srcs: ["a.java"],
-			sdk_version: "current",
-		}
-		java_library { name: "lib", defaults: ["defaults"] }
-		java_library { name: "static-lib", defaults: ["defaults"] }
-		java_library { name: "impl-only-lib", defaults: ["defaults"] }
-		java_library { name: "stub-only-lib", defaults: ["defaults"] }
-		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
-		`)
-	var expectations = []struct {
-		lib               string
-		on_impl_classpath bool
-		on_stub_classpath bool
-		in_impl_combined  bool
-		in_stub_combined  bool
-	}{
-		{lib: "lib", on_impl_classpath: true},
-		{lib: "static-lib", in_impl_combined: true},
-		{lib: "impl-only-lib", on_impl_classpath: true},
-		{lib: "stub-only-lib", on_stub_classpath: true},
-		{lib: "stub-only-static-lib", in_stub_combined: true},
-	}
-	verify := func(sdklib, dep string, cp, combined bool) {
-		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
-		expected := cp || combined // Every combined jar is also on the classpath.
-		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
-
-		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
-		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
-		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
-	}
-	for _, expectation := range expectations {
-		verify("sdklib", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
-		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
-
-		stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
-		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
-	}
-}
-
-func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_only: true,
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java"],
-			libs: ["foo"],
-		}
-		`)
-
-	// The bar library should depend on the stubs jar.
-	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
-	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
-		t.Errorf("expected %q, found %#q", expected, actual)
-	}
-}
-
-func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_packages: ["foo"],
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java", ":foo{.public.stubs.source}"],
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
-		RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_packages: ["foo"],
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java", ":foo{.system.stubs.source}"],
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_Deps(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
-	prepareForJavaTest.RunTestWithBp(t, `
-		java_sdk_library_import {
-			name: "foo",
-			public: {
-				jars: ["a.jar"],
-				stub_srcs: ["a.java"],
-				current_api: "api/current.txt",
-				removed_api: "api/removed.txt",
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: [":foo{.public.stubs.source}"],
-			java_resources: [
-				":foo{.public.api.txt}",
-				":foo{.public.removed-api.txt}",
-			],
-		}
-		`)
-}
-
-func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
-	bp := `
-		java_sdk_library_import {
-			name: "foo",
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`
-
-	t.Run("stubs.source", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: [":foo{.public.stubs.source}"],
-					java_resources: [
-						":foo{.public.api.txt}",
-						":foo{.public.removed-api.txt}",
-					],
-				}
-			`)
-	})
-
-	t.Run("api.txt", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: ["a.java"],
-					java_resources: [
-						":foo{.public.api.txt}",
-					],
-				}
-			`)
-	})
-
-	t.Run("removed-api.txt", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: ["a.java"],
-					java_resources: [
-						":foo{.public.removed-api.txt}",
-					],
-				}
-			`)
-	})
-}
-
-func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
-	prepareForJavaTest.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
-		RunTestWithBp(t, `
-			java_sdk_library {
-				name: "foo",
-				srcs: ["a.java", "b.java"],
-				api_packages: ["foo"],
-				// Explicitly disable public to test the check that ensures the set of enabled
-				// scopes is consistent.
-				public: {
-					enabled: false,
-				},
-				system: {
-					enabled: true,
-				},
-			}
-		`)
-}
-
-func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-				sdk_version: "module_current",
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_SystemServer(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-			},
-			system_server: {
-				enabled: true,
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_MissingScope(t *testing.T) {
-	prepareForJavaTest.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
-		RunTestWithBp(t, `
-			java_sdk_library {
-				name: "foo",
-				srcs: ["a.java"],
-				public: {
-					enabled: false,
-				},
-			}
-
-			java_library {
-				name: "baz",
-				srcs: ["a.java"],
-				libs: ["foo"],
-				sdk_version: "module_current",
-			}
-		`)
-}
-
-func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			system: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "baz",
-			srcs: ["a.java"],
-			libs: ["foo"],
-			// foo does not have module-lib scope so it should fallback to system
-			sdk_version: "module_current",
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			system: {
-				enabled: true,
-			},
-			default_to_stubs: true,
-		}
-
-		java_library {
-			name: "baz",
-			srcs: ["a.java"],
-			libs: ["foo"],
-			// does not have sdk_version set, should fallback to module,
-			// which will then fallback to system because the module scope
-			// is not enabled.
-		}
-		`)
-	// The baz library should depend on the system stubs jar.
-	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
-	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
-		t.Errorf("expected %q, found %#q", expected, actual)
-	}
-}
-
 var compilerFlagsTestCases = []struct {
 	in  string
 	out bool
@@ -2208,3 +1392,31 @@
 	assertDeepEquals(t, "Default installable value should be true.", proptools.BoolPtr(true),
 		module.properties.Installable)
 }
+
+func TestErrorproneEnabled(t *testing.T) {
+	ctx, _ := testJava(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			errorprone: {
+				enabled: true,
+			},
+		}
+	`)
+
+	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+
+	// Test that the errorprone plugins are passed to javac
+	expectedSubstring := "-Xplugin:ErrorProne"
+	if !strings.Contains(javac.Args["javacFlags"], expectedSubstring) {
+		t.Errorf("expected javacFlags to conain %q, got %q", expectedSubstring, javac.Args["javacFlags"])
+	}
+
+	// Modules with errorprone { enabled: true } will include errorprone checks
+	// in the main javac build rule. Only when RUN_ERROR_PRONE is true will
+	// the explicit errorprone build rule be created.
+	errorprone := ctx.ModuleForTests("foo", "android_common").MaybeDescription("errorprone")
+	if errorprone.RuleParams.Description != "" {
+		t.Errorf("expected errorprone build rule to not exist, but it did")
+	}
+}
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 1c146a1..fd2f3ca 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -185,7 +185,6 @@
 		buildOS := android.BuildOs.String()
 
 		kapt := result.ModuleForTests("foo", "android_common").Rule("kapt")
-		//kotlinc := ctx.ModuleForTests("foo", "android_common").Rule("kotlinc")
 		javac := result.ModuleForTests("foo", "android_common").Description("javac")
 		errorprone := result.ModuleForTests("foo", "android_common").Description("errorprone")
 
diff --git a/java/lint.go b/java/lint.go
index 9f769df..1511cfe 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -361,10 +361,7 @@
 			Labels:          map[string]string{"type": "tool", "name": "lint"},
 			ExecStrategy:    lintRBEExecStrategy(ctx),
 			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
-			EnvironmentVariables: []string{
-				"LANG",
-			},
-			Platform: map[string]string{remoteexec.PoolKey: pool},
+			Platform:        map[string]string{remoteexec.PoolKey: pool},
 		})
 	}
 
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index c787e47..fba73d0 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -196,25 +196,18 @@
 		return
 	}
 
-	b.generateBootImageBuildActions(ctx, updatableModules)
+	b.generateBootImageBuildActions(ctx, nonUpdatableModules, updatableModules)
 }
 
 // Generate classpaths.proto config
 func (b *platformBootclasspathModule) generateClasspathProtoBuildActions(ctx android.ModuleContext) {
 	// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
 	classpathJars := configuredJarListToClasspathJars(ctx, b.ClasspathFragmentToConfiguredJarList(ctx), BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
-
-	// TODO(satayev): remove updatable boot jars once each apex has its own fragment
-	global := dexpreopt.GetGlobalConfig(ctx)
-	classpathJars = append(classpathJars, configuredJarListToClasspathJars(ctx, global.UpdatableBootJars, BOOTCLASSPATH)...)
-
 	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
 }
 
 func (b *platformBootclasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
-	global := dexpreopt.GetGlobalConfig(ctx)
-	// TODO(satayev): split ART apex jars into their own classpathFragment
-	return global.BootJars
+	return b.getImageConfig(ctx).modules
 }
 
 // checkNonUpdatableModules ensures that the non-updatable modules supplied are not part of an
@@ -225,7 +218,7 @@
 		fromUpdatableApex := apexInfo.Updatable
 		if fromUpdatableApex {
 			// error: this jar is part of an updatable apex
-			ctx.ModuleErrorf("module %q from updatable apexes %q is not allowed in the framework boot image", ctx.OtherModuleName(m), apexInfo.InApexes)
+			ctx.ModuleErrorf("module %q from updatable apexes %q is not allowed in the framework boot image", ctx.OtherModuleName(m), apexInfo.InApexVariants)
 		} else {
 			// ok: this jar is part of the platform or a non-updatable apex
 		}
@@ -242,8 +235,15 @@
 		} else {
 			name := ctx.OtherModuleName(m)
 			if apexInfo.IsForPlatform() {
-				// error: this jar is part of the platform
-				ctx.ModuleErrorf("module %q from platform is not allowed in the updatable boot jars list", name)
+				// If AlwaysUsePrebuiltSdks() returns true then it is possible that the updatable list will
+				// include platform variants of a prebuilt module due to workarounds elsewhere. In that case
+				// do not treat this as an error.
+				// TODO(b/179354495): Always treat this as an error when migration to bootclasspath_fragment
+				//  modules is complete.
+				if !ctx.Config().AlwaysUsePrebuiltSdks() {
+					// error: this jar is part of the platform
+					ctx.ModuleErrorf("module %q from platform is not allowed in the updatable boot jars list", name)
+				}
 			} else {
 				// TODO(b/177892522): Treat this as an error.
 				// Cannot do that at the moment because framework-wifi and framework-tethering are in the
@@ -257,17 +257,6 @@
 	return defaultBootImageConfig(ctx)
 }
 
-// hiddenAPISupportingModule encapsulates the information provided by any module that contributes to
-// the hidden API processing.
-type hiddenAPISupportingModule struct {
-	module android.Module
-
-	bootDexJar  android.Path
-	flagsCSV    android.Path
-	indexCSV    android.Path
-	metadataCSV android.Path
-}
-
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
 func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module, fragments []android.Module) {
 
@@ -290,133 +279,79 @@
 		return
 	}
 
-	// nilPathHandler will check the supplied path and if it is nil then it will either immediately
-	// report an error, or it will defer the error reporting until it is actually used, depending
-	// whether missing dependencies are allowed.
-	var nilPathHandler func(path android.Path, name string, module android.Module) android.Path
-	if ctx.Config().AllowMissingDependencies() {
-		nilPathHandler = func(path android.Path, name string, module android.Module) android.Path {
-			if path == nil {
-				outputPath := android.PathForModuleOut(ctx, "missing", module.Name(), name)
-				path = outputPath
+	monolithicInfo := b.createAndProvideMonolithicHiddenAPIInfo(ctx, fragments)
+	// Create the input to pass to ruleToGenerateHiddenAPIStubFlagsFile
+	input := newHiddenAPIFlagInput()
 
-				// Create an error rule that pretends to create the output file but will actually fail if it
-				// is run.
-				ctx.Build(pctx, android.BuildParams{
-					Rule:   android.ErrorRule,
-					Output: outputPath,
-					Args: map[string]string{
-						"error": fmt.Sprintf("missing hidden API file: %s for %s", name, module),
-					},
-				})
-			}
-			return path
-		}
-	} else {
-		nilPathHandler = func(path android.Path, name string, module android.Module) android.Path {
-			if path == nil {
-				ctx.ModuleErrorf("module %s does not provide a %s file", module, name)
-			}
-			return path
-		}
-	}
+	// Gather stub library information from the dependencies on modules provided by
+	// hiddenAPIComputeMonolithicStubLibModules.
+	input.gatherStubLibInfo(ctx, nil)
 
-	hiddenAPISupportingModules := []hiddenAPISupportingModule{}
-	for _, module := range modules {
-		if h, ok := module.(hiddenAPIIntf); ok {
-			hiddenAPISupportingModule := hiddenAPISupportingModule{
-				module:      module,
-				bootDexJar:  nilPathHandler(h.bootDexJar(), "bootDexJar", module),
-				flagsCSV:    nilPathHandler(h.flagsCSV(), "flagsCSV", module),
-				indexCSV:    nilPathHandler(h.indexCSV(), "indexCSV", module),
-				metadataCSV: nilPathHandler(h.metadataCSV(), "metadataCSV", module),
-			}
+	// Use the flag files from this module and all the fragments.
+	input.FlagFilesByCategory = monolithicInfo.FlagsFilesByCategory
 
-			// If any errors were reported when trying to populate the hiddenAPISupportingModule struct
-			// then don't add it to the list.
-			if ctx.Failed() {
-				continue
-			}
+	// Generate the monolithic stub-flags.csv file.
+	bootDexJars := extractBootDexJarsFromModules(ctx, modules)
+	stubFlags := hiddenAPISingletonPaths(ctx).stubFlags
+	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlags, bootDexJars, input)
+	rule.Build("platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags")
 
-			hiddenAPISupportingModules = append(hiddenAPISupportingModules, hiddenAPISupportingModule)
-		} else if _, ok := module.(*DexImport); ok {
-			// Ignore this for the purposes of hidden API processing
-		} else {
-			ctx.ModuleErrorf("module %s of type %s does not support hidden API processing", module, ctx.OtherModuleType(module))
-		}
-	}
+	// Extract the classes jars from the contents.
+	classesJars := extractClassesJarsFromModules(modules)
 
-	moduleSpecificFlagsPaths := android.Paths{}
-	for _, module := range hiddenAPISupportingModules {
-		moduleSpecificFlagsPaths = append(moduleSpecificFlagsPaths, module.flagsCSV)
-	}
+	// Generate the annotation-flags.csv file from all the module annotations.
+	annotationFlags := android.PathForModuleOut(ctx, "hiddenapi-monolithic", "annotation-flags.csv")
+	buildRuleToGenerateAnnotationFlags(ctx, "monolithic hiddenapi flags", classesJars, stubFlags, annotationFlags)
 
-	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
-	for _, fragment := range fragments {
-		if ctx.OtherModuleHasProvider(fragment, hiddenAPIFlagFileInfoProvider) {
-			info := ctx.OtherModuleProvider(fragment, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
-			flagFileInfo.append(info)
-		}
-	}
+	// Generate the monotlithic hiddenapi-flags.csv file.
+	allFlags := hiddenAPISingletonPaths(ctx).flags
+	buildRuleToGenerateHiddenApiFlags(ctx, "hiddenAPIFlagsFile", "hiddenapi flags", allFlags, stubFlags, annotationFlags, monolithicInfo.FlagsFilesByCategory, monolithicInfo.AllFlagsPaths, android.OptionalPath{})
+
+	// Generate an intermediate monolithic hiddenapi-metadata.csv file directly from the annotations
+	// in the source code.
+	intermediateMetadataCSV := android.PathForModuleOut(ctx, "hiddenapi-monolithic", "intermediate-metadata.csv")
+	buildRuleToGenerateMetadata(ctx, "monolithic hidden API metadata", classesJars, stubFlags, intermediateMetadataCSV)
+
+	// Reformat the intermediate file to add | quotes just in case that is important for the tools
+	// that consume the metadata file.
+	// TODO(b/179354495): Investigate whether it is possible to remove this reformatting step.
+	metadataCSV := hiddenAPISingletonPaths(ctx).metadata
+	b.buildRuleMergeCSV(ctx, "reformat monolithic hidden API metadata", android.Paths{intermediateMetadataCSV}, metadataCSV)
+
+	// Generate the monolithic hiddenapi-index.csv file directly from the CSV files in the classes
+	// jars.
+	indexCSV := hiddenAPISingletonPaths(ctx).index
+	buildRuleToGenerateIndex(ctx, "monolithic hidden API index", classesJars, indexCSV)
+}
+
+// createAndProvideMonolithicHiddenAPIInfo creates a MonolithicHiddenAPIInfo and provides it for
+// testing.
+func (b *platformBootclasspathModule) createAndProvideMonolithicHiddenAPIInfo(ctx android.ModuleContext, fragments []android.Module) MonolithicHiddenAPIInfo {
+	// Create a temporary input structure in which to collate information provided directly by this
+	// module, either through properties or direct dependencies.
+	temporaryInput := newHiddenAPIFlagInput()
+
+	// Create paths to the flag files specified in the properties.
+	temporaryInput.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api)
+
+	// Create the monolithic info, by starting with the flag files specified on this and then merging
+	// in information from all the fragment dependencies of this.
+	monolithicInfo := newMonolithicHiddenAPIInfo(ctx, temporaryInput.FlagFilesByCategory, fragments)
 
 	// Store the information for testing.
-	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
-
-	outputPath := hiddenAPISingletonPaths(ctx).flags
-	baseFlagsPath := hiddenAPISingletonPaths(ctx).stubFlags
-	ruleToGenerateHiddenApiFlags(ctx, outputPath, baseFlagsPath, moduleSpecificFlagsPaths, flagFileInfo)
-
-	b.generateHiddenAPIStubFlagsRules(ctx, hiddenAPISupportingModules)
-	b.generateHiddenAPIIndexRules(ctx, hiddenAPISupportingModules)
-	b.generatedHiddenAPIMetadataRules(ctx, hiddenAPISupportingModules)
+	ctx.SetProvider(MonolithicHiddenAPIInfoProvider, monolithicInfo)
+	return monolithicInfo
 }
 
-func (b *platformBootclasspathModule) generateHiddenAPIStubFlagsRules(ctx android.ModuleContext, modules []hiddenAPISupportingModule) {
-	bootDexJars := android.Paths{}
-	for _, module := range modules {
-		bootDexJars = append(bootDexJars, module.bootDexJar)
-	}
-
-	sdkKindToStubPaths := hiddenAPIGatherStubLibDexJarPaths(ctx)
-
-	outputPath := hiddenAPISingletonPaths(ctx).stubFlags
-	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, outputPath, bootDexJars, sdkKindToStubPaths)
-	rule.Build("platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags")
-}
-
-func (b *platformBootclasspathModule) generateHiddenAPIIndexRules(ctx android.ModuleContext, modules []hiddenAPISupportingModule) {
-	indexes := android.Paths{}
-	for _, module := range modules {
-		indexes = append(indexes, module.indexCSV)
-	}
-
+func (b *platformBootclasspathModule) buildRuleMergeCSV(ctx android.ModuleContext, desc string, inputPaths android.Paths, outputPath android.WritablePath) {
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		BuiltTool("merge_csv").
 		Flag("--key_field signature").
-		FlagWithArg("--header=", "signature,file,startline,startcol,endline,endcol,properties").
-		FlagWithOutput("--output=", hiddenAPISingletonPaths(ctx).index).
-		Inputs(indexes)
-	rule.Build("platform-bootclasspath-monolithic-hiddenapi-index", "monolithic hidden API index")
-}
-
-func (b *platformBootclasspathModule) generatedHiddenAPIMetadataRules(ctx android.ModuleContext, modules []hiddenAPISupportingModule) {
-	metadataCSVFiles := android.Paths{}
-	for _, module := range modules {
-		metadataCSVFiles = append(metadataCSVFiles, module.metadataCSV)
-	}
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	outputPath := hiddenAPISingletonPaths(ctx).metadata
-
-	rule.Command().
-		BuiltTool("merge_csv").
-		Flag("--key_field signature").
 		FlagWithOutput("--output=", outputPath).
-		Inputs(metadataCSVFiles)
+		Inputs(inputPaths)
 
-	rule.Build("platform-bootclasspath-monolithic-hiddenapi-metadata", "monolithic hidden API metadata")
+	rule.Build(desc, desc)
 }
 
 // generateHiddenApiMakeVars generates make variables needed by hidden API related make rules, e.g.
@@ -430,7 +365,7 @@
 }
 
 // generateBootImageBuildActions generates ninja rules related to the boot image creation.
-func (b *platformBootclasspathModule) generateBootImageBuildActions(ctx android.ModuleContext, updatableModules []android.Module) {
+func (b *platformBootclasspathModule) generateBootImageBuildActions(ctx android.ModuleContext, nonUpdatableModules, updatableModules []android.Module) {
 	// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
 	// GenerateSingletonBuildActions method as it cannot create it for itself.
 	dexpreopt.GetGlobalSoongConfig(ctx)
@@ -451,5 +386,18 @@
 	// Generate the updatable bootclasspath packages rule.
 	generateUpdatableBcpPackagesRule(ctx, imageConfig, updatableModules)
 
+	// Copy non-updatable module dex jars to their predefined locations.
+	nonUpdatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, nonUpdatableModules)
+	copyBootJarsToPredefinedLocations(ctx, nonUpdatableBootDexJarsByModule, imageConfig.dexPathsByModule)
+
+	// Copy updatable module dex jars to their predefined locations.
+	config := GetUpdatableBootConfig(ctx)
+	updatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, updatableModules)
+	copyBootJarsToPredefinedLocations(ctx, updatableBootDexJarsByModule, config.dexPathsByModule)
+
+	// Build a profile for the image config and then use that to build the boot image.
+	profile := bootImageProfileRule(ctx, imageConfig)
+	buildBootImage(ctx, imageConfig, profile)
+
 	dumpOatRules(ctx, imageConfig)
 }
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 98d4614..0318a07 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -15,8 +15,6 @@
 package java
 
 import (
-	"fmt"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -152,96 +150,6 @@
 	})
 }
 
-func TestPlatformBootclasspath_Fragments(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForTestWithPlatformBootclasspath,
-		android.FixtureWithRootAndroidBp(`
-			platform_bootclasspath {
-				name: "platform-bootclasspath",
-				fragments: [
-					{module:"bar-fragment"},
-				],
-				hidden_api: {
-					unsupported: [
-							"unsupported.txt",
-					],
-					removed: [
-							"removed.txt",
-					],
-					max_target_r_low_priority: [
-							"max-target-r-low-priority.txt",
-					],
-					max_target_q: [
-							"max-target-q.txt",
-					],
-					max_target_p: [
-							"max-target-p.txt",
-					],
-					max_target_o_low_priority: [
-							"max-target-o-low-priority.txt",
-					],
-					blocked: [
-							"blocked.txt",
-					],
-					unsupported_packages: [
-							"unsupported-packages.txt",
-					],
-				},
-			}
-
-			bootclasspath_fragment {
-				name: "bar-fragment",
-				contents: ["bar"],
-				hidden_api: {
-					unsupported: [
-							"bar-unsupported.txt",
-					],
-					removed: [
-							"bar-removed.txt",
-					],
-					max_target_r_low_priority: [
-							"bar-max-target-r-low-priority.txt",
-					],
-					max_target_q: [
-							"bar-max-target-q.txt",
-					],
-					max_target_p: [
-							"bar-max-target-p.txt",
-					],
-					max_target_o_low_priority: [
-							"bar-max-target-o-low-priority.txt",
-					],
-					blocked: [
-							"bar-blocked.txt",
-					],
-					unsupported_packages: [
-							"bar-unsupported-packages.txt",
-					],
-				},
-			}
-
-			java_library {
-				name: "bar",
-				srcs: ["a.java"],
-				system_modules: "none",
-				sdk_version: "none",
-				compile_dex: true,
-			}
-		`),
-	).RunTest(t)
-
-	pbcp := result.Module("platform-bootclasspath", "android_common")
-	info := result.ModuleProvider(pbcp, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
-
-	for _, category := range hiddenAPIFlagFileCategories {
-		name := category.propertyName
-		message := fmt.Sprintf("category %s", name)
-		filename := strings.ReplaceAll(name, "_", "-")
-		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
-		android.AssertPathsRelativeToTopEquals(t, message, expected, info.categoryToPaths[category])
-	}
-}
-
 func TestPlatformBootclasspathVariant(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
@@ -267,7 +175,7 @@
 	).RunTest(t)
 
 	p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
-	android.AssertStringEquals(t, "output filepath", p.Name()+".pb", p.ClasspathFragmentBase.outputFilepath.Base())
+	android.AssertStringEquals(t, "output filepath", "bootclasspath.pb", p.ClasspathFragmentBase.outputFilepath.Base())
 	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/soong/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
 }
 
@@ -307,7 +215,7 @@
 		want := map[string][]string{
 			"LOCAL_MODULE":                {"platform-bootclasspath"},
 			"LOCAL_MODULE_CLASS":          {"ETC"},
-			"LOCAL_INSTALLED_MODULE_STEM": {"platform-bootclasspath.pb"},
+			"LOCAL_INSTALLED_MODULE_STEM": {"bootclasspath.pb"},
 			// Output and Install paths are tested separately in TestPlatformBootclasspath_ClasspathFragmentPaths
 		}
 
@@ -407,20 +315,14 @@
 		}
 	`)
 
-	platformBootclasspath := result.ModuleForTests("myplatform-bootclasspath", "android_common")
-	indexRule := platformBootclasspath.Rule("platform-bootclasspath-monolithic-hiddenapi-index")
-	CheckHiddenAPIRuleInputs(t, `
-.intermediates/bar/android_common/hiddenapi/index.csv
-.intermediates/foo/android_common/hiddenapi/index.csv
-`,
-		indexRule)
-
 	// Make sure that the foo-hiddenapi-annotations.jar is included in the inputs to the rules that
 	// creates the index.csv file.
-	foo := result.ModuleForTests("foo", "android_common")
-	indexParams := foo.Output("hiddenapi/index.csv")
+	platformBootclasspath := result.ModuleForTests("myplatform-bootclasspath", "android_common")
+	indexRule := platformBootclasspath.Rule("monolithic_hidden_API_index")
 	CheckHiddenAPIRuleInputs(t, `
+.intermediates/bar/android_common/javac/bar.jar
 .intermediates/foo-hiddenapi-annotations/android_common/javac/foo-hiddenapi-annotations.jar
 .intermediates/foo/android_common/javac/foo.jar
-`, indexParams)
+`,
+		indexRule)
 }
diff --git a/java/rro.go b/java/rro.go
index 2e58c04..0b4d091 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -90,6 +90,22 @@
 	Theme() string
 }
 
+// RRO's partition logic is different from the partition logic of other modules defined in soong/android/paths.go
+// The default partition for RRO is "/product" and not "/system"
+func rroPartition(ctx android.ModuleContext) string {
+	var partition string
+	if ctx.DeviceSpecific() {
+		partition = ctx.DeviceConfig().OdmPath()
+	} else if ctx.SocSpecific() {
+		partition = ctx.DeviceConfig().VendorPath()
+	} else if ctx.SystemExtSpecific() {
+		partition = ctx.DeviceConfig().SystemExtPath()
+	} else {
+		partition = ctx.DeviceConfig().ProductPath()
+	}
+	return partition
+}
+
 func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
 	sdkDep := decodeSdkDep(ctx, android.SdkContext(r))
 	if sdkDep.hasFrameworkLibs() {
@@ -137,7 +153,8 @@
 	r.certificate = certificates[0]
 
 	r.outputFile = signed
-	r.installDir = android.PathForModuleInstall(ctx, "overlay", String(r.properties.Theme))
+	partition := rroPartition(ctx)
+	r.installDir = android.PathForModuleInPartitionInstall(ctx, partition, "overlay", String(r.properties.Theme))
 	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
 }
 
diff --git a/java/rro_test.go b/java/rro_test.go
index bad60bc..27abbe4 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -177,7 +177,7 @@
 
 	// Check device location.
 	path = android.AndroidMkEntriesForTest(t, ctx, m.Module())[0].EntryMap["LOCAL_MODULE_PATH"]
-	expectedPath = []string{shared.JoinPath("out/target/product/test_device/system/overlay")}
+	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay")}
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", config, expectedPath, path)
 }
 
@@ -343,3 +343,57 @@
 		})
 	}
 }
+
+func TestRuntimeResourceOverlayPartition(t *testing.T) {
+	bp := `
+		runtime_resource_overlay {
+			name: "device_specific",
+			device_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "soc_specific",
+			soc_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "system_ext_specific",
+			system_ext_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "product_specific",
+			product_specific: true,
+		}
+		runtime_resource_overlay {
+			name: "default"
+		}
+	`
+	testCases := []struct {
+		name         string
+		expectedPath string
+	}{
+		{
+			name:         "device_specific",
+			expectedPath: "out/soong/target/product/test_device/odm/overlay",
+		},
+		{
+			name:         "soc_specific",
+			expectedPath: "out/soong/target/product/test_device/vendor/overlay",
+		},
+		{
+			name:         "system_ext_specific",
+			expectedPath: "out/soong/target/product/test_device/system_ext/overlay",
+		},
+		{
+			name:         "product_specific",
+			expectedPath: "out/soong/target/product/test_device/product/overlay",
+		},
+		{
+			name:         "default",
+			expectedPath: "out/soong/target/product/test_device/product/overlay",
+		},
+	}
+	for _, testCase := range testCases {
+		ctx, _ := testJava(t, bp)
+		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*RuntimeResourceOverlay)
+		android.AssertPathRelativeToTopEquals(t, "Install dir is not correct for "+testCase.name, testCase.expectedPath, mod.installDir)
+	}
+}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 800e93b..9492729 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -435,9 +435,6 @@
 	// a list of top-level directories containing Java stub files to merge show/hide annotations from.
 	Merge_inclusion_annotations_dirs []string
 
-	// If set to true, the path of dist files is apistubs/core. Defaults to false.
-	Core_lib *bool
-
 	// If set to true then don't create dist rules.
 	No_dist *bool
 
@@ -457,6 +454,11 @@
 	// * API incompatibilities baseline filegroup -> <dist-stem>-incompatibilities.api.<scope>.latest
 	Dist_stem *string
 
+	// The subdirectory for the artifacts that are copied to the dist directory.  If not specified
+	// then defaults to "unknown".  Should be set to "android" for anything that should be published
+	// in the public Android SDK.
+	Dist_group *string
+
 	// A compatibility mode that allows historical API-tracking files to not exist.
 	// Do not use.
 	Unsafe_ignore_missing_latest_api bool
@@ -845,19 +847,7 @@
 // closest kind which is a subset of the requested kind. e.g. if requesting android.SdkModule then
 // it will return *scopePaths for android.SdkSystem if available or android.SdkPublic of not.
 func (c *commonToSdkLibraryAndImport) selectScopePaths(ctx android.BaseModuleContext, kind android.SdkKind) *scopePaths {
-	var apiScope *apiScope
-	switch kind {
-	case android.SdkSystem:
-		apiScope = apiScopeSystem
-	case android.SdkModule:
-		apiScope = apiScopeModuleLib
-	case android.SdkTest:
-		apiScope = apiScopeTest
-	case android.SdkSystemServer:
-		apiScope = apiScopeSystemServer
-	default:
-		apiScope = apiScopePublic
-	}
+	apiScope := sdkKindToApiScope(kind)
 
 	paths := c.findClosestScopePath(apiScope)
 	if paths == nil {
@@ -874,6 +864,24 @@
 	return paths
 }
 
+// sdkKindToApiScope maps from android.SdkKind to apiScope.
+func sdkKindToApiScope(kind android.SdkKind) *apiScope {
+	var apiScope *apiScope
+	switch kind {
+	case android.SdkSystem:
+		apiScope = apiScopeSystem
+	case android.SdkModule:
+		apiScope = apiScopeModuleLib
+	case android.SdkTest:
+		apiScope = apiScopeTest
+	case android.SdkSystemServer:
+		apiScope = apiScopeSystemServer
+	default:
+		apiScope = apiScopePublic
+	}
+	return apiScope
+}
+
 // to satisfy SdkLibraryDependency interface
 func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path {
 	paths := c.selectScopePaths(ctx, kind)
@@ -884,6 +892,17 @@
 	return paths.stubsDexJarPath
 }
 
+// to satisfy SdkLibraryDependency interface
+func (c *commonToSdkLibraryAndImport) SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath {
+	apiScope := sdkKindToApiScope(kind)
+	paths := c.findScopePaths(apiScope)
+	if paths == nil {
+		return android.OptionalPath{}
+	}
+
+	return paths.removedApiFilePath
+}
+
 func (c *commonToSdkLibraryAndImport) sdkComponentPropertiesForChildLibrary() interface{} {
 	componentProps := &struct {
 		SdkLibraryToImplicitlyTrack *string
@@ -900,11 +919,17 @@
 	return componentProps
 }
 
-// Check if this can be used as a shared library.
 func (c *commonToSdkLibraryAndImport) sharedLibrary() bool {
 	return proptools.BoolDefault(c.commonSdkLibraryProperties.Shared_library, true)
 }
 
+// Check if the stub libraries should be compiled for dex
+func (c *commonToSdkLibraryAndImport) stubLibrariesCompiledForDex() bool {
+	// Always compile the dex file files for the stub libraries if they will be used on the
+	// bootclasspath.
+	return !c.sharedLibrary()
+}
+
 // Properties related to the use of a module as an component of a java_sdk_library.
 type SdkLibraryComponentProperties struct {
 
@@ -958,7 +983,7 @@
 var _ SdkLibraryComponentDependency = (*SdkLibrary)(nil)
 var _ SdkLibraryComponentDependency = (*SdkLibraryImport)(nil)
 
-// Provides access to sdk_version related header and implentation jars.
+// Provides access to sdk_version related files, e.g. header and implementation jars.
 type SdkLibraryDependency interface {
 	SdkLibraryComponentDependency
 
@@ -978,6 +1003,12 @@
 	// SdkApiStubDexJar returns the dex jar for the stubs. It is needed by the hiddenapi processing
 	// tool which processes dex files.
 	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path
+
+	// SdkRemovedTxtFile returns the optional path to the removed.txt file for the specified sdk kind.
+	SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath
+
+	// sharedLibrary returns true if this can be used as a shared library.
+	sharedLibrary() bool
 }
 
 type SdkLibrary struct {
@@ -1169,13 +1200,7 @@
 
 // The dist path of the stub artifacts
 func (module *SdkLibrary) apiDistPath(apiScope *apiScope) string {
-	if module.ModuleBase.Owner() != "" {
-		return path.Join("apistubs", module.ModuleBase.Owner(), apiScope.name)
-	} else if Bool(module.sdkLibraryProperties.Core_lib) {
-		return path.Join("apistubs", "core", apiScope.name)
-	} else {
-		return path.Join("apistubs", "android", apiScope.name)
-	}
+	return path.Join("apistubs", module.distGroup(), apiScope.name)
 }
 
 // Get the sdk version for use when compiling the stubs library.
@@ -1199,6 +1224,11 @@
 	return proptools.StringDefault(module.sdkLibraryProperties.Dist_stem, module.BaseModuleName())
 }
 
+// distGroup returns the subdirectory of the dist path of the stub artifacts.
+func (module *SdkLibrary) distGroup() string {
+	return proptools.StringDefault(module.sdkLibraryProperties.Dist_group, "unknown")
+}
+
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
 	return ":" + module.distStem() + ".api." + apiScope.name + ".latest"
 }
@@ -1226,16 +1256,13 @@
 
 // Creates the implementation java library
 func (module *SdkLibrary) createImplLibrary(mctx android.DefaultableHookContext) {
-	moduleNamePtr := proptools.StringPtr(module.BaseModuleName())
-
 	visibility := childModuleVisibility(module.sdkLibraryProperties.Impl_library_visibility)
 
 	props := struct {
-		Name              *string
-		Visibility        []string
-		Instrument        bool
-		Libs              []string
-		ConfigurationName *string
+		Name       *string
+		Visibility []string
+		Instrument bool
+		Libs       []string
 	}{
 		Name:       proptools.StringPtr(module.implLibraryModuleName()),
 		Visibility: visibility,
@@ -1244,9 +1271,6 @@
 		// Set the impl_only libs. Note that the module's "Libs" get appended as well, via the
 		// addition of &module.properties below.
 		Libs: module.sdkLibraryProperties.Impl_only_libs,
-
-		// Make the created library behave as if it had the same name as this module.
-		ConfigurationName: moduleNamePtr,
 	}
 
 	properties := []interface{}{
@@ -1309,9 +1333,13 @@
 	// We compile the stubs for 1.8 in line with the main android.jar stubs, and potential
 	// interop with older developer tools that don't support 1.9.
 	props.Java_version = proptools.StringPtr("1.8")
-	if module.dexProperties.Compile_dex != nil {
-		props.Compile_dex = module.dexProperties.Compile_dex
+
+	// The imports need to be compiled to dex if the java_sdk_library requests it.
+	compileDex := module.dexProperties.Compile_dex
+	if module.stubLibrariesCompiledForDex() {
+		compileDex = proptools.BoolPtr(true)
 	}
+	props.Compile_dex = compileDex
 
 	// Dist the class jar artifact for sdk builds.
 	if !Bool(module.sdkLibraryProperties.No_dist) {
@@ -1539,7 +1567,7 @@
 func withinSameApexesAs(ctx android.BaseModuleContext, other android.Module) bool {
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	otherApexInfo := ctx.OtherModuleProvider(other, android.ApexInfoProvider).(android.ApexInfo)
-	return len(otherApexInfo.InApexes) > 0 && reflect.DeepEqual(apexInfo.InApexes, otherApexInfo.InApexes)
+	return len(otherApexInfo.InApexVariants) > 0 && reflect.DeepEqual(apexInfo.InApexVariants, otherApexInfo.InApexVariants)
 }
 
 func (module *SdkLibrary) sdkJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec, headerJars bool) android.Paths {
@@ -1622,8 +1650,12 @@
 			path := path.Join(mctx.ModuleDir(), apiDir, scope.apiFilePrefix+api)
 			p := android.ExistentPathForSource(mctx, path)
 			if !p.Valid() {
-				mctx.ModuleErrorf("Current api file %#v doesn't exist", path)
-				missingCurrentApi = true
+				if mctx.Config().AllowMissingDependencies() {
+					mctx.AddMissingDependencies([]string{path})
+				} else {
+					mctx.ModuleErrorf("Current api file %#v doesn't exist", path)
+					missingCurrentApi = true
+				}
 			}
 		}
 	}
@@ -1969,7 +2001,11 @@
 	props.Prefer = proptools.BoolPtr(module.prebuilt.Prefer())
 
 	// The imports need to be compiled to dex if the java_sdk_library_import requests it.
-	props.Compile_dex = module.properties.Compile_dex
+	compileDex := module.properties.Compile_dex
+	if module.stubLibrariesCompiledForDex() {
+		compileDex = proptools.BoolPtr(true)
+	}
+	props.Compile_dex = compileDex
 
 	mctx.CreateModule(ImportFactory, &props, module.sdkComponentPropertiesForChildLibrary())
 }
@@ -2110,8 +2146,7 @@
 			di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo)
 			if dexOutputPath := di.PrebuiltExportPath(module.BaseModuleName(), ".dexjar"); dexOutputPath != nil {
 				module.dexJarFile = dexOutputPath
-				module.initHiddenAPI(ctx, module.configurationName)
-				module.hiddenAPIExtractInformation(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0])
+				module.initHiddenAPI(ctx, dexOutputPath, module.findScopePaths(apiScopePublic).stubsImplPath[0], nil)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
@@ -2482,11 +2517,18 @@
 			}
 			scopeSet.AddProperty("jars", jars)
 
-			// Merge the stubs source jar into the snapshot zip so that when it is unpacked
-			// the source files are also unpacked.
-			snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources")
-			ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir)
-			scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir})
+			if ctx.SdkModuleContext().Config().IsEnvTrue("SOONG_SDK_SNAPSHOT_USE_SRCJAR") {
+				// Copy the stubs source jar into the snapshot zip as is.
+				srcJarSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".srcjar")
+				ctx.SnapshotBuilder().CopyToSnapshot(properties.StubsSrcJar, srcJarSnapshotPath)
+				scopeSet.AddProperty("stub_srcs", []string{srcJarSnapshotPath})
+			} else {
+				// Merge the stubs source jar into the snapshot zip so that when it is unpacked
+				// the source files are also unpacked.
+				snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources")
+				ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir)
+				scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir})
+			}
 
 			if properties.CurrentApiFile != nil {
 				currentApiSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".txt")
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
new file mode 100644
index 0000000..2520dde
--- /dev/null
+++ b/java/sdk_library_test.go
@@ -0,0 +1,921 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func TestJavaSdkLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+			"29": {"foo"},
+			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
+		}),
+	).RunTestWithBp(t, `
+		droiddoc_exported_dir {
+			name: "droiddoc-templates-sdk",
+			path: ".",
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+		}
+		java_sdk_library {
+			name: "bar",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["bar"],
+		}
+		java_library {
+			name: "baz",
+			srcs: ["c.java"],
+			libs: ["foo", "bar.stubs"],
+			sdk_version: "system_current",
+		}
+		java_sdk_library {
+			name: "barney",
+			srcs: ["c.java"],
+			api_only: true,
+		}
+		java_sdk_library {
+			name: "betty",
+			srcs: ["c.java"],
+			shared_library: false,
+		}
+		java_sdk_library_import {
+		    name: "quuz",
+				public: {
+					jars: ["c.jar"],
+				},
+		}
+		java_sdk_library_import {
+		    name: "fred",
+				public: {
+					jars: ["b.jar"],
+				},
+		}
+		java_sdk_library_import {
+		    name: "wilma",
+				public: {
+					jars: ["b.jar"],
+				},
+				shared_library: false,
+		}
+		java_library {
+		    name: "qux",
+		    srcs: ["c.java"],
+		    libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"],
+		    sdk_version: "system_current",
+		}
+		java_library {
+			name: "baz-test",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "test_current",
+		}
+		java_library {
+			name: "baz-29",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "system_29",
+		}
+		java_library {
+			name: "baz-module-30",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "module_30",
+		}
+		`)
+
+	// check the existence of the internal modules
+	result.ModuleForTests("foo", "android_common")
+	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
+	result.ModuleForTests("foo.api.public.28", "")
+	result.ModuleForTests("foo.api.system.28", "")
+	result.ModuleForTests("foo.api.test.28", "")
+
+	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
+	// tests if baz is actually linked to the stubs lib
+	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
+	// ... and not to the impl lib
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
+	// test if baz is not linked to the system variant of foo
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
+
+	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
+	// tests if baz-test is actually linked to the test stubs lib
+	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
+
+	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
+	// tests if baz-29 is actually linked to the system 29 stubs lib
+	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar")
+
+	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
+	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
+	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar")
+
+	// test if baz has exported SDK lib names foo and bar to qux
+	qux := result.ModuleForTests("qux", "android_common")
+	if quxLib, ok := qux.Module().(*Library); ok {
+		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
+		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
+	}
+}
+
+func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			libs: ["lib"],
+			static_libs: ["static-lib"],
+			impl_only_libs: ["impl-only-lib"],
+			stub_only_libs: ["stub-only-lib"],
+			stub_only_static_libs: ["stub-only-static-lib"],
+		}
+		java_defaults {
+			name: "defaults",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+		java_library { name: "lib", defaults: ["defaults"] }
+		java_library { name: "static-lib", defaults: ["defaults"] }
+		java_library { name: "impl-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
+		`)
+	var expectations = []struct {
+		lib               string
+		on_impl_classpath bool
+		on_stub_classpath bool
+		in_impl_combined  bool
+		in_stub_combined  bool
+	}{
+		{lib: "lib", on_impl_classpath: true},
+		{lib: "static-lib", in_impl_combined: true},
+		{lib: "impl-only-lib", on_impl_classpath: true},
+		{lib: "stub-only-lib", on_stub_classpath: true},
+		{lib: "stub-only-static-lib", in_stub_combined: true},
+	}
+	verify := func(sdklib, dep string, cp, combined bool) {
+		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
+		expected := cp || combined // Every combined jar is also on the classpath.
+		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
+
+		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
+		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
+		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
+	}
+	for _, expectation := range expectations {
+		verify("sdklib", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+
+		stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
+		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
+	}
+}
+
+func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_only: true,
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			libs: ["foo"],
+		}
+		`)
+
+	// The bar library should depend on the stubs jar.
+	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
+	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+		t.Errorf("expected %q, found %#q", expected, actual)
+	}
+}
+
+func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.public.stubs.source}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
+		RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.system.stubs.source}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_Deps(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
+	prepareForJavaTest.RunTestWithBp(t, `
+		java_sdk_library_import {
+			name: "foo",
+			public: {
+				jars: ["a.jar"],
+				stub_srcs: ["a.java"],
+				current_api: "api/current.txt",
+				removed_api: "api/removed.txt",
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: [":foo{.public.stubs.source}"],
+			java_resources: [
+				":foo{.public.api.txt}",
+				":foo{.public.removed-api.txt}",
+			],
+		}
+		`)
+}
+
+func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
+	bp := `
+		java_sdk_library_import {
+			name: "foo",
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`
+
+	t.Run("stubs.source", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: [":foo{.public.stubs.source}"],
+					java_resources: [
+						":foo{.public.api.txt}",
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
+	})
+
+	t.Run("api.txt", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.api.txt}",
+					],
+				}
+			`)
+	})
+
+	t.Run("removed-api.txt", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
+	})
+}
+
+func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
+	prepareForJavaTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java", "b.java"],
+				api_packages: ["foo"],
+				// Explicitly disable public to test the check that ensures the set of enabled
+				// scopes is consistent.
+				public: {
+					enabled: false,
+				},
+				system: {
+					enabled: true,
+				},
+			}
+		`)
+}
+
+func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+				sdk_version: "module_current",
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_SystemServer(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+			},
+			system_server: {
+				enabled: true,
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_MissingScope(t *testing.T) {
+	prepareForJavaTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java"],
+				public: {
+					enabled: false,
+				},
+			}
+
+			java_library {
+				name: "baz",
+				srcs: ["a.java"],
+				libs: ["foo"],
+				sdk_version: "module_current",
+			}
+		`)
+}
+
+func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			system: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "baz",
+			srcs: ["a.java"],
+			libs: ["foo"],
+			// foo does not have module-lib scope so it should fallback to system
+			sdk_version: "module_current",
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			system: {
+				enabled: true,
+			},
+			default_to_stubs: true,
+		}
+
+		java_library {
+			name: "baz",
+			srcs: ["a.java"],
+			libs: ["foo"],
+			// does not have sdk_version set, should fallback to module,
+			// which will then fallback to system because the module scope
+			// is not enabled.
+		}
+		`)
+	// The baz library should depend on the system stubs jar.
+	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
+	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+		t.Errorf("expected %q, found %#q", expected, actual)
+	}
+}
+
+func TestJavaSdkLibraryImport(t *testing.T) {
+	result := prepareForJavaTest.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "foo.system",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "system_current",
+		}
+
+		java_library {
+			name: "foo.test",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "test_current",
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			public: {
+				jars: ["a.jar"],
+			},
+			system: {
+				jars: ["b.jar"],
+			},
+			test: {
+				jars: ["c.jar"],
+				stub_srcs: ["c.java"],
+			},
+		}
+		`)
+
+	for _, scope := range []string{"", ".system", ".test"} {
+		fooModule := result.ModuleForTests("foo"+scope, "android_common")
+		javac := fooModule.Rule("javac")
+
+		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
+		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
+	}
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`prebuilt_sdklib.stubs.source.test`,
+		`prebuilt_sdklib.stubs.system`,
+		`prebuilt_sdklib.stubs.test`,
+	})
+}
+
+func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
+		// dependency is added after prebuilts may have been renamed and so has to use
+		// the renamed name.
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			prefer: true,
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryEnforce(t *testing.T) {
+	partitionToBpOption := func(partition string) string {
+		switch partition {
+		case "system":
+			return ""
+		case "vendor":
+			return "soc_specific: true,"
+		case "product":
+			return "product_specific: true,"
+		default:
+			panic("Invalid partition group name: " + partition)
+		}
+	}
+
+	type testConfigInfo struct {
+		libraryType                string
+		fromPartition              string
+		toPartition                string
+		enforceVendorInterface     bool
+		enforceProductInterface    bool
+		enforceJavaSdkLibraryCheck bool
+		allowList                  []string
+	}
+
+	createPreparer := func(info testConfigInfo) android.FixturePreparer {
+		bpFileTemplate := `
+			java_library {
+				name: "foo",
+				srcs: ["foo.java"],
+				libs: ["bar"],
+				sdk_version: "current",
+				%s
+			}
+
+			%s {
+				name: "bar",
+				srcs: ["bar.java"],
+				sdk_version: "current",
+				%s
+			}
+		`
+
+		bpFile := fmt.Sprintf(bpFileTemplate,
+			partitionToBpOption(info.fromPartition),
+			info.libraryType,
+			partitionToBpOption(info.toPartition))
+
+		return android.GroupFixturePreparers(
+			PrepareForTestWithJavaSdkLibraryFiles,
+			FixtureWithLastReleaseApis("bar"),
+			android.FixtureWithRootAndroidBp(bpFile),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+				if info.enforceVendorInterface {
+					variables.DeviceVndkVersion = proptools.StringPtr("current")
+				}
+				variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+				variables.InterPartitionJavaLibraryAllowList = info.allowList
+			}),
+		)
+	}
+
+	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
+		t.Run(fmt.Sprintf("%v", info), func(t *testing.T) {
+			errorHandler := android.FixtureExpectsNoErrors
+			if expectedErrorPattern != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
+			}
+			android.GroupFixturePreparers(
+				prepareForJavaTest,
+				createPreparer(info),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTest(t)
+		})
+	}
+
+	errorMessage := "is not allowed across the partitions"
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: false,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    false,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"bar"},
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "product",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "vendor",
+		toPartition:                "product",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+}
+
+func TestJavaSdkLibraryDist(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaBuildComponents,
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis(
+			"sdklib_no_group",
+			"sdklib_group_foo",
+			"sdklib_owner_foo",
+			"foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib_no_group",
+			srcs: ["foo.java"],
+		}
+
+		java_sdk_library {
+			name: "sdklib_group_foo",
+			srcs: ["foo.java"],
+			dist_group: "foo",
+		}
+
+		java_sdk_library {
+			name: "sdklib_owner_foo",
+			srcs: ["foo.java"],
+			owner: "foo",
+		}
+
+		java_sdk_library {
+			name: "sdklib_stem_foo",
+			srcs: ["foo.java"],
+			dist_stem: "foo",
+		}
+	`)
+
+	type testCase struct {
+		module   string
+		distDir  string
+		distStem string
+	}
+	testCases := []testCase{
+		{
+			module:   "sdklib_no_group",
+			distDir:  "apistubs/unknown/public",
+			distStem: "sdklib_no_group.jar",
+		},
+		{
+			module:   "sdklib_group_foo",
+			distDir:  "apistubs/foo/public",
+			distStem: "sdklib_group_foo.jar",
+		},
+		{
+			// Owner doesn't affect distDir after b/186723288.
+			module:   "sdklib_owner_foo",
+			distDir:  "apistubs/unknown/public",
+			distStem: "sdklib_owner_foo.jar",
+		},
+		{
+			module:   "sdklib_stem_foo",
+			distDir:  "apistubs/unknown/public",
+			distStem: "foo.jar",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.module, func(t *testing.T) {
+			m := result.ModuleForTests(tt.module+".stubs", "android_common").Module().(*Library)
+			dists := m.Dists()
+			if len(dists) != 1 {
+				t.Fatalf("expected exactly 1 dist entry, got %d", len(dists))
+			}
+			if g, w := String(dists[0].Dir), tt.distDir; g != w {
+				t.Errorf("expected dist dir %q, got %q", w, g)
+			}
+			if g, w := String(dists[0].Dest), tt.distStem; g != w {
+				t.Errorf("expected dist stem %q, got %q", w, g)
+			}
+		})
+	}
+}
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 82cdb89..7ffb056 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -17,6 +17,8 @@
 import (
 	"android/soong/android"
 	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint"
 )
 
 func init() {
@@ -24,8 +26,8 @@
 }
 
 func registerSystemserverClasspathBuildComponents(ctx android.RegistrationContext) {
-	// TODO(satayev): add systemserver_classpath_fragment module
 	ctx.RegisterModuleType("platform_systemserverclasspath", platformSystemServerClasspathFactory)
+	ctx.RegisterModuleType("systemserverclasspath_fragment", systemServerClasspathFactory)
 }
 
 type platformSystemServerClasspathModule struct {
@@ -41,27 +43,99 @@
 	return m
 }
 
-func (b *platformSystemServerClasspathModule) AndroidMkEntries() (entries []android.AndroidMkEntries) {
-	return b.classpathFragmentBase().androidMkEntries()
+func (p *platformSystemServerClasspathModule) AndroidMkEntries() (entries []android.AndroidMkEntries) {
+	return p.classpathFragmentBase().androidMkEntries()
 }
 
-func (b *platformSystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	configuredJars := configuredJarListToClasspathJars(ctx, b.ClasspathFragmentToConfiguredJarList(ctx), b.classpathType)
-	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars)
+func (p *platformSystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	classpathJars := configuredJarListToClasspathJars(ctx, p.ClasspathFragmentToConfiguredJarList(ctx), p.classpathType)
+	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
 }
 
-var platformSystemServerClasspathKey = android.NewOnceKey("platform_systemserverclasspath")
+func (p *platformSystemServerClasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	return global.SystemServerJars
+}
 
-func (b *platformSystemServerClasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
-	return ctx.Config().Once(platformSystemServerClasspathKey, func() interface{} {
-		global := dexpreopt.GetGlobalConfig(ctx)
+type SystemServerClasspathModule struct {
+	android.ModuleBase
+	android.ApexModuleBase
 
-		jars := global.SystemServerJars
+	ClasspathFragmentBase
 
-		// TODO(satayev): split apex jars into separate configs.
-		for i := 0; i < global.UpdatableSystemServerJars.Len(); i++ {
-			jars = jars.Append(global.UpdatableSystemServerJars.Apex(i), global.UpdatableSystemServerJars.Jar(i))
+	properties systemServerClasspathFragmentProperties
+}
+
+func (s *SystemServerClasspathModule) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+	return nil
+}
+
+type systemServerClasspathFragmentProperties struct {
+	// The contents of this systemserverclasspath_fragment, could be either java_library, or java_sdk_library.
+	//
+	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
+	Contents []string
+}
+
+func systemServerClasspathFactory() android.Module {
+	m := &SystemServerClasspathModule{}
+	m.AddProperties(&m.properties)
+	android.InitApexModule(m)
+	initClasspathFragment(m, SYSTEMSERVERCLASSPATH)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+func (s *SystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if len(s.properties.Contents) == 0 {
+		ctx.PropertyErrorf("contents", "empty contents are not allowed")
+	}
+
+	classpathJars := configuredJarListToClasspathJars(ctx, s.ClasspathFragmentToConfiguredJarList(ctx), s.classpathType)
+	s.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, classpathJars)
+}
+
+func (s *SystemServerClasspathModule) ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList {
+	global := dexpreopt.GetGlobalConfig(ctx)
+
+	// Convert content names to their appropriate stems, in case a test library is overriding an actual boot jar
+	var stems []string
+	for _, name := range s.properties.Contents {
+		dep := ctx.GetDirectDepWithTag(name, systemServerClasspathFragmentContentDepTag)
+		if m, ok := dep.(ModuleWithStem); ok {
+			stems = append(stems, m.Stem())
+		} else {
+			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
 		}
-		return jars
-	}).(android.ConfiguredJarList)
+	}
+
+	// Only create configs for updatable boot jars. Non-updatable system server jars must be part of the
+	// platform_systemserverclasspath's classpath proto config to guarantee that they come before any
+	// updatable jars at runtime.
+	return global.UpdatableSystemServerJars.Filter(stems)
+}
+
+type systemServerClasspathFragmentContentDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+// Contents of system server fragments in an apex are considered to be directly in the apex, as if
+// they were listed in java_libs.
+func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
+
+var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
+
+// The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
+var systemServerClasspathFragmentContentDepTag = systemServerClasspathFragmentContentDependencyTag{}
+
+func IsSystemServerClasspathFragmentContentDepTag(tag blueprint.DependencyTag) bool {
+	return tag == systemServerClasspathFragmentContentDepTag
+}
+
+func (s *SystemServerClasspathModule) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
+	module := ctx.Module()
+
+	for _, name := range s.properties.Contents {
+		ctx.AddDependency(module, systemServerClasspathFragmentContentDepTag, name)
+	}
 }
diff --git a/java/systemserver_classpath_fragment_test.go b/java/systemserver_classpath_fragment_test.go
index 6126d5e..9ad50dd 100644
--- a/java/systemserver_classpath_fragment_test.go
+++ b/java/systemserver_classpath_fragment_test.go
@@ -20,13 +20,13 @@
 	"android/soong/android"
 )
 
-var prepareForTestWithSystemserverClasspath = android.GroupFixturePreparers(
+var prepareForTestWithSystemServerClasspath = android.GroupFixturePreparers(
 	PrepareForTestWithJavaDefaultModules,
 )
 
-func TestSystemserverClasspathVariant(t *testing.T) {
+func TestPlatformSystemServerClasspathVariant(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithSystemserverClasspath,
+		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
 			platform_systemserverclasspath {
 				name: "platform-systemserverclasspath",
@@ -38,9 +38,9 @@
 	android.AssertIntEquals(t, "expect 1 variant", 1, len(variants))
 }
 
-func TestSystemserverClasspath_ClasspathFragmentPaths(t *testing.T) {
+func TestPlatformSystemServerClasspath_ClasspathFragmentPaths(t *testing.T) {
 	result := android.GroupFixturePreparers(
-		prepareForTestWithSystemserverClasspath,
+		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
 			platform_systemserverclasspath {
 				name: "platform-systemserverclasspath",
@@ -49,13 +49,13 @@
 	).RunTest(t)
 
 	p := result.Module("platform-systemserverclasspath", "android_common").(*platformSystemServerClasspathModule)
-	android.AssertStringEquals(t, "output filepath", p.Name()+".pb", p.ClasspathFragmentBase.outputFilepath.Base())
+	android.AssertStringEquals(t, "output filepath", "systemserverclasspath.pb", p.ClasspathFragmentBase.outputFilepath.Base())
 	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/soong/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
 }
 
-func TestSystemserverClasspathModule_AndroidMkEntries(t *testing.T) {
+func TestPlatformSystemServerClasspathModule_AndroidMkEntries(t *testing.T) {
 	preparer := android.GroupFixturePreparers(
-		prepareForTestWithSystemserverClasspath,
+		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
 			platform_systemserverclasspath {
 				name: "platform-systemserverclasspath",
@@ -78,8 +78,8 @@
 		want := map[string][]string{
 			"LOCAL_MODULE":                {"platform-systemserverclasspath"},
 			"LOCAL_MODULE_CLASS":          {"ETC"},
-			"LOCAL_INSTALLED_MODULE_STEM": {"platform-systemserverclasspath.pb"},
-			// Output and Install paths are tested separately in TestSystemserverClasspath_ClasspathFragmentPaths
+			"LOCAL_INSTALLED_MODULE_STEM": {"systemserverclasspath.pb"},
+			// Output and Install paths are tested separately in TestPlatformSystemServerClasspath_ClasspathFragmentPaths
 		}
 
 		p := result.Module("platform-systemserverclasspath", "android_common").(*platformSystemServerClasspathModule)
@@ -95,3 +95,14 @@
 		}
 	})
 }
+
+func TestSystemServerClasspathFragmentWithoutContents(t *testing.T) {
+	prepareForTestWithSystemServerClasspath.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qempty contents are not allowed\E`)).
+		RunTestWithBp(t, `
+			systemserverclasspath_fragment {
+				name: "systemserverclasspath-fragment",
+			}
+		`)
+}
diff --git a/java/testing.go b/java/testing.go
index 387d595..1fef337 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -24,6 +24,7 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
+
 	"github.com/google/blueprint"
 )
 
@@ -55,8 +56,9 @@
 	}.AddToFixture(),
 )
 
-// Test fixture preparer that will define default java modules, e.g. standard prebuilt modules.
-var PrepareForTestWithJavaDefaultModules = android.GroupFixturePreparers(
+// Test fixture preparer that will define all default java modules except the
+// fake_tool_binary for dex2oatd.
+var PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd = android.GroupFixturePreparers(
 	// Make sure that all the module types used in the defaults are registered.
 	PrepareForTestWithJavaBuildComponents,
 	// Additional files needed when test disallows non-existent source.
@@ -72,6 +74,11 @@
 	android.FixtureAddTextFile(defaultJavaDir+"/Android.bp", gatherRequiredDepsForTest()),
 	// Add dexpreopt compat libs (android.test.base, etc.) and a fake dex2oatd module.
 	dexpreopt.PrepareForTestWithDexpreoptCompatLibs,
+)
+
+// Test fixture preparer that will define default java modules, e.g. standard prebuilt modules.
+var PrepareForTestWithJavaDefaultModules = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd,
 	dexpreopt.PrepareForTestWithFakeDex2oatd,
 )
 
@@ -371,7 +378,7 @@
 	if apexInfo.IsForPlatform() {
 		apex = "platform"
 	} else {
-		apex = apexInfo.InApexes[0]
+		apex = apexInfo.InApexVariants[0]
 	}
 
 	return fmt.Sprintf("%s:%s", apex, name)
diff --git a/python/test.go b/python/test.go
index 6713189..7413782 100644
--- a/python/test.go
+++ b/python/test.go
@@ -15,6 +15,8 @@
 package python
 
 import (
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 )
@@ -102,6 +104,9 @@
 	binary.pythonInstaller = NewPythonInstaller("nativetest", "nativetest64")
 
 	test := &testDecorator{binaryDecorator: binary}
+	if hod == android.HostSupportedNoCross && test.testProperties.Test_options.Unit_test == nil {
+		test.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
 
 	module.bootstrapper = test
 	module.installer = test
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
index ef4672a..9e7a0f1 100644
--- a/remoteexec/remoteexec.go
+++ b/remoteexec/remoteexec.go
@@ -50,8 +50,14 @@
 )
 
 var (
-	defaultLabels       = map[string]string{"type": "tool"}
-	defaultExecStrategy = LocalExecStrategy
+	defaultLabels               = map[string]string{"type": "tool"}
+	defaultExecStrategy         = LocalExecStrategy
+	defaultEnvironmentVariables = []string{
+		// This is a subset of the allowlist in ui/build/ninja.go that makes sense remotely.
+		"LANG",
+		"LC_MESSAGES",
+		"PYTHONDONTWRITEBYTECODE",
+	}
 )
 
 // REParams holds information pertinent to the remote execution of a rule.
@@ -149,8 +155,10 @@
 		args += " --toolchain_inputs=" + strings.Join(r.ToolchainInputs, ",")
 	}
 
-	if len(r.EnvironmentVariables) > 0 {
-		args += " --env_var_allowlist=" + strings.Join(r.EnvironmentVariables, ",")
+	envVarAllowlist := append(r.EnvironmentVariables, defaultEnvironmentVariables...)
+
+	if len(envVarAllowlist) > 0 {
+		args += " --env_var_allowlist=" + strings.Join(envVarAllowlist, ",")
 	}
 
 	return args + " -- "
diff --git a/remoteexec/remoteexec_test.go b/remoteexec/remoteexec_test.go
index b117b89..3686316 100644
--- a/remoteexec/remoteexec_test.go
+++ b/remoteexec/remoteexec_test.go
@@ -36,7 +36,7 @@
 					PoolKey:           "default",
 				},
 			},
-			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out -- ", DefaultImage),
+			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out --env_var_allowlist=LANG,LC_MESSAGES,PYTHONDONTWRITEBYTECODE -- ", DefaultImage),
 		},
 		{
 			name: "all params",
@@ -52,7 +52,7 @@
 					PoolKey:           "default",
 				},
 			},
-			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp,out2.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
+			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp,out2.rsp --output_files=$out --toolchain_inputs=clang++ --env_var_allowlist=LANG,LC_MESSAGES,PYTHONDONTWRITEBYTECODE -- ", DefaultImage),
 		},
 	}
 	for _, test := range tests {
@@ -74,7 +74,7 @@
 			PoolKey:           "default",
 		},
 	}
-	want := fmt.Sprintf("prebuilts/remoteexecution-client/live/rewrapper --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out -- ", DefaultImage)
+	want := fmt.Sprintf("prebuilts/remoteexecution-client/live/rewrapper --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out --env_var_allowlist=LANG,LC_MESSAGES,PYTHONDONTWRITEBYTECODE -- ", DefaultImage)
 	if got := params.NoVarTemplate(DefaultWrapperPath); got != want {
 		t.Errorf("NoVarTemplate() returned\n%s\nwant\n%s", got, want)
 	}
@@ -90,7 +90,7 @@
 			PoolKey:           "default",
 		},
 	}
-	want := fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out -- ", DefaultImage)
+	want := fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=local --inputs=$in --output_files=$out --env_var_allowlist=LANG,LC_MESSAGES,PYTHONDONTWRITEBYTECODE -- ", DefaultImage)
 	for i := 0; i < 1000; i++ {
 		if got := r.Template(); got != want {
 			t.Fatalf("Template() returned\n%s\nwant\n%s", got, want)
diff --git a/rust/Android.bp b/rust/Android.bp
index f45404f..11069d1 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -31,8 +31,10 @@
         "protobuf.go",
         "rust.go",
         "sanitize.go",
-        "strip.go",
         "source_provider.go",
+        "snapshot_prebuilt.go",
+        "snapshot_utils.go",
+        "strip.go",
         "test.go",
         "testing.go",
     ],
@@ -52,6 +54,7 @@
         "rust_test.go",
         "source_provider_test.go",
         "test_test.go",
+        "vendor_snapshot_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/rust/binary.go b/rust/binary.go
index ffc0413..8d0a0a7 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -137,6 +137,9 @@
 	// Binaries default to dylib dependencies for device, rlib for host.
 	if binary.preferRlib() {
 		return rlibAutoDep
+	} else if mod, ok := ctx.Module().(*Module); ok && mod.InVendor() {
+		// Vendor Rust binaries should prefer rlibs.
+		return rlibAutoDep
 	} else if ctx.Device() {
 		return dylibAutoDep
 	} else {
@@ -147,6 +150,8 @@
 func (binary *binaryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
 	if binary.preferRlib() {
 		return RlibLinkage
+	} else if ctx.RustModule().InVendor() {
+		return RlibLinkage
 	}
 	return binary.baseCompiler.stdLinkage(ctx)
 }
diff --git a/rust/compiler.go b/rust/compiler.go
index a3f02c0..1598ebf 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -341,6 +341,11 @@
 	return compiler.Properties.Crate_name
 }
 
+func (compiler *baseCompiler) everInstallable() bool {
+	// Most modules are installable, so return true by default.
+	return true
+}
+
 func (compiler *baseCompiler) installDir(ctx ModuleContext) android.InstallPath {
 	dir := compiler.dir
 	if ctx.toolchain().Is64Bit() && compiler.dir64 != "" {
diff --git a/rust/image.go b/rust/image.go
index 900842e..3b54f12 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -82,7 +82,12 @@
 }
 
 func (mod *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
-	panic("Rust modules do not support snapshotting: " + mod.BaseModuleName())
+	if snapshot, ok := mod.compiler.(cc.SnapshotInterface); ok {
+		return snapshot.Version()
+	} else {
+		mctx.ModuleErrorf("version is unknown for snapshot prebuilt")
+		return ""
+	}
 }
 
 func (mod *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
@@ -110,7 +115,9 @@
 }
 
 func (mod *Module) IsSnapshotPrebuilt() bool {
-	// Rust does not support prebuilts in its snapshots
+	if p, ok := mod.compiler.(cc.SnapshotInterface); ok {
+		return p.IsSnapshotPrebuilt()
+	}
 	return false
 }
 
@@ -202,6 +209,8 @@
 
 func (mod *Module) ImageMutatorBegin(mctx android.BaseModuleContext) {
 	// Rust does not support installing to the product image yet.
+	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
+
 	if Bool(mod.VendorProperties.Product_available) {
 		mctx.PropertyErrorf("product_available",
 			"Rust modules do not yet support being available to the product image")
@@ -217,6 +226,11 @@
 			mctx.PropertyErrorf("vendor_ramdisk_available", "cannot be set for rust_ffi or rust_ffi_shared modules.")
 		}
 	}
+	if vendorSpecific {
+		if lib, ok := mod.compiler.(libraryInterface); ok && lib.buildDylib() {
+			mctx.PropertyErrorf("vendor", "Vendor-only dylibs are not yet supported, use rust_library_rlib.")
+		}
+	}
 
 	cc.MutateImage(mctx, mod)
 
diff --git a/rust/library.go b/rust/library.go
index 1bdf83a..747a29d 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -99,6 +99,8 @@
 	MutatedProperties LibraryMutatedProperties
 	includeDirs       android.Paths
 	sourceProvider    SourceProvider
+
+	collectedSnapshotHeaders android.Paths
 }
 
 type libraryInterface interface {
@@ -122,7 +124,8 @@
 	setStatic()
 	setSource()
 
-	// Set libstd linkage
+	// libstd linkage functions
+	rlibStd() bool
 	setRlibStd()
 	setDylibStd()
 
@@ -193,6 +196,10 @@
 	library.MutatedProperties.VariantIsShared = false
 }
 
+func (library *libraryDecorator) rlibStd() bool {
+	return library.MutatedProperties.VariantIsStaticStd
+}
+
 func (library *libraryDecorator) setRlibStd() {
 	library.MutatedProperties.VariantIsStaticStd = true
 }
@@ -220,7 +227,10 @@
 }
 
 func (library *libraryDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
-	if library.preferRlib() {
+	if ctx.Module().(*Module).InVendor() {
+		// Vendor modules should statically link libstd.
+		return rlibAutoDep
+	} else if library.preferRlib() {
 		return rlibAutoDep
 	} else if library.rlib() || library.static() {
 		return rlibAutoDep
@@ -236,7 +246,10 @@
 }
 
 func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
-	if library.static() || library.MutatedProperties.VariantIsStaticStd {
+	if ctx.RustModule().InVendor() {
+		// Vendor modules should statically link libstd.
+		return RlibLinkage
+	} else if library.static() || library.MutatedProperties.VariantIsStaticStd {
 		return RlibLinkage
 	} else if library.baseCompiler.preferRlib() {
 		return RlibLinkage
@@ -490,9 +503,8 @@
 
 	if library.shared() {
 		ctx.SetProvider(cc.SharedLibraryInfoProvider, cc.SharedLibraryInfo{
-			SharedLibrary:           outputFile,
-			UnstrippedSharedLibrary: outputFile,
-			Target:                  ctx.Target(),
+			SharedLibrary: outputFile,
+			Target:        ctx.Target(),
 		})
 	}
 
@@ -623,6 +635,19 @@
 				// Disable dylib Vendor Ramdisk variations until we support these.
 				v.(*Module).Disable()
 			}
+
+			variation := v.(*Module).ModuleBase.ImageVariation().Variation
+			if strings.HasPrefix(variation, cc.VendorVariationPrefix) &&
+				m.HasVendorVariant() &&
+				!cc.IsVendorProprietaryModule(mctx) &&
+				strings.TrimPrefix(variation, cc.VendorVariationPrefix) == mctx.DeviceConfig().VndkVersion() {
+
+				// cc.MutateImage runs before LibraryMutator, so vendor variations which are meant for rlibs only are
+				// produced for Dylibs; however, dylibs should not be enabled for boardVndkVersion for
+				// non-vendor proprietary modules.
+				v.(*Module).Disable()
+			}
+
 		case "source":
 			v.(*Module).compiler.(libraryInterface).setSource()
 			// The source variant does not produce any library.
@@ -659,9 +684,10 @@
 				dylib := modules[1].(*Module)
 				rlib.compiler.(libraryInterface).setRlibStd()
 				dylib.compiler.(libraryInterface).setDylibStd()
-				if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+				if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation ||
+					strings.HasPrefix(dylib.ModuleBase.ImageVariation().Variation, cc.VendorVariationPrefix) {
 					// TODO(b/165791368)
-					// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
+					// Disable rlibs that link against dylib-std on vendor and vendor ramdisk variations until those dylib
 					// variants are properly supported.
 					dylib.Disable()
 				}
@@ -671,3 +697,54 @@
 		}
 	}
 }
+
+func (l *libraryDecorator) snapshotHeaders() android.Paths {
+	if l.collectedSnapshotHeaders == nil {
+		panic("snapshotHeaders() must be called after collectHeadersForSnapshot()")
+	}
+	return l.collectedSnapshotHeaders
+}
+
+// collectHeadersForSnapshot collects all exported headers from library.
+// It globs header files in the source tree for exported include directories,
+// and tracks generated header files separately.
+//
+// This is to be called from GenerateAndroidBuildActions, and then collected
+// header files can be retrieved by snapshotHeaders().
+func (l *libraryDecorator) collectHeadersForSnapshot(ctx android.ModuleContext, deps PathDeps) {
+	ret := android.Paths{}
+
+	// Glob together the headers from the modules include_dirs property
+	for _, path := range android.CopyOfPaths(l.includeDirs) {
+		dir := path.String()
+		glob, err := ctx.GlobWithDeps(dir+"/**/*", nil)
+		if err != nil {
+			ctx.ModuleErrorf("glob failed: %#v", err)
+			return
+		}
+
+		for _, header := range glob {
+			// Filter out only the files with extensions that are headers.
+			found := false
+			for _, ext := range cc.HeaderExts {
+				if strings.HasSuffix(header, ext) {
+					found = true
+					break
+				}
+			}
+			if !found {
+				continue
+			}
+			ret = append(ret, android.PathForSource(ctx, header))
+		}
+	}
+
+	// Glob together the headers from C dependencies as well, starting with non-generated headers.
+	ret = append(ret, cc.GlobHeadersForSnapshot(ctx, append(android.CopyOfPaths(deps.depIncludePaths), deps.depSystemIncludePaths...))...)
+
+	// Collect generated headers from C dependencies.
+	ret = append(ret, cc.GlobGeneratedHeadersForSnapshot(ctx, deps.depGeneratedHeaders)...)
+
+	// TODO(185577950): If support for generated headers is added, they need to be collected here as well.
+	l.collectedSnapshotHeaders = ret
+}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 4eead32..c217959 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -82,3 +82,8 @@
 func (procMacro *procMacroDecorator) autoDep(ctx android.BottomUpMutatorContext) autoDep {
 	return rlibAutoDep
 }
+
+func (procMacro *procMacroDecorator) everInstallable() bool {
+	// Proc_macros are never installed
+	return false
+}
diff --git a/rust/rust.go b/rust/rust.go
index bb97142..b8c8be5 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -85,6 +85,9 @@
 	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
 	ExtraVariants              []string `blueprint:"mutated"`
 
+	// Used by vendor snapshot to record dependencies from snapshot modules.
+	SnapshotSharedLibs []string `blueprint:"mutated"`
+
 	// Make this module available when building for vendor ramdisk.
 	// On device without a dedicated recovery partition, the module is only
 	// available after switching root into
@@ -92,11 +95,26 @@
 	// the recovery variant instead (TODO(b/165791368) recovery not yet supported)
 	Vendor_ramdisk_available *bool
 
+	// Normally Soong uses the directory structure to decide which modules
+	// should be included (framework) or excluded (non-framework) from the
+	// different snapshots (vendor, recovery, etc.), but this property
+	// allows a partner to exclude a module normally thought of as a
+	// framework module from the vendor snapshot.
+	Exclude_from_vendor_snapshot *bool
+
+	// Normally Soong uses the directory structure to decide which modules
+	// should be included (framework) or excluded (non-framework) from the
+	// different snapshots (vendor, recovery, etc.), but this property
+	// allows a partner to exclude a module normally thought of as a
+	// framework module from the recovery snapshot.
+	Exclude_from_recovery_snapshot *bool
+
 	// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
 	Min_sdk_version *string
 
 	PreventInstall bool
 	HideFromMake   bool
+	Installable    *bool
 }
 
 type Module struct {
@@ -143,6 +161,10 @@
 	mod.Properties.HideFromMake = true
 }
 
+func (c *Module) HiddenFromMake() bool {
+	return c.Properties.HideFromMake
+}
+
 func (mod *Module) SanitizePropDefined() bool {
 	// Because compiler is not set for some Rust modules where sanitize might be set, check that compiler is also not
 	// nil since we need compiler to actually sanitize.
@@ -210,6 +232,38 @@
 	return false
 }
 
+func (mod *Module) Dylib() bool {
+	if mod.compiler != nil {
+		if library, ok := mod.compiler.(libraryInterface); ok {
+			return library.dylib()
+		}
+	}
+	return false
+}
+
+func (mod *Module) Rlib() bool {
+	if mod.compiler != nil {
+		if library, ok := mod.compiler.(libraryInterface); ok {
+			return library.rlib()
+		}
+	}
+	return false
+}
+
+func (mod *Module) Binary() bool {
+	if mod.compiler != nil {
+		if _, ok := mod.compiler.(*binaryDecorator); ok {
+			return true
+		}
+	}
+	return false
+}
+
+func (mod *Module) Object() bool {
+	// Rust has no modules which produce only object files.
+	return false
+}
+
 func (mod *Module) Toc() android.OptionalPath {
 	if mod.compiler != nil {
 		if _, ok := mod.compiler.(libraryInterface); ok {
@@ -223,12 +277,13 @@
 	return false
 }
 
-// Returns true if the module is using VNDK libraries instead of the libraries in /system/lib or /system/lib64.
-// "product" and "vendor" variant modules return true for this function.
-// When BOARD_VNDK_VERSION is set, vendor variants of "vendor_available: true", "vendor: true",
-// "soc_specific: true" and more vendor installed modules are included here.
-// When PRODUCT_PRODUCT_VNDK_VERSION is set, product variants of "vendor_available: true" or
-// "product_specific: true" modules are included here.
+func (mod *Module) RelativeInstallPath() string {
+	if mod.compiler != nil {
+		return mod.compiler.relativeInstallPath()
+	}
+	return ""
+}
+
 func (mod *Module) UseVndk() bool {
 	return mod.Properties.VndkVersion != ""
 }
@@ -250,6 +305,10 @@
 	return false
 }
 
+func (mod *Module) IsVndkSp() bool {
+	return false
+}
+
 func (c *Module) IsVndkPrivate() bool {
 	return false
 }
@@ -274,6 +333,14 @@
 	return false
 }
 
+func (mod *Module) HasLlndkStubs() bool {
+	return false
+}
+
+func (mod *Module) StubsVersion() string {
+	panic(fmt.Errorf("StubsVersion called on non-versioned module: %q", mod.BaseModuleName()))
+}
+
 func (mod *Module) SdkVersion() string {
 	return ""
 }
@@ -362,6 +429,7 @@
 	inData() bool
 	install(ctx ModuleContext)
 	relativeInstallPath() string
+	everInstallable() bool
 
 	nativeCoverage() bool
 
@@ -423,8 +491,12 @@
 	return mod.coverage != nil && mod.coverage.Properties.NeedCoverageVariant
 }
 
-func (mod *Module) PreventInstall() {
-	mod.Properties.PreventInstall = true
+func (mod *Module) VndkVersion() string {
+	return mod.Properties.VndkVersion
+}
+
+func (mod *Module) PreventInstall() bool {
+	return mod.Properties.PreventInstall
 }
 
 func (mod *Module) HideFromMake() {
@@ -564,6 +636,10 @@
 }
 
 func (mod *Module) installable(apexInfo android.ApexInfo) bool {
+	if !mod.EverInstallable() {
+		return false
+	}
+
 	// The apex variant is not installable because it is included in the APEX and won't appear
 	// in the system partition as a standalone file.
 	if !apexInfo.IsForPlatform() {
@@ -676,6 +752,16 @@
 	return mod.compiler != nil && mod.compiler.nativeCoverage()
 }
 
+func (mod *Module) EverInstallable() bool {
+	return mod.compiler != nil &&
+		// Check to see whether the module is actually ever installable.
+		mod.compiler.everInstallable()
+}
+
+func (mod *Module) Installable() *bool {
+	return mod.Properties.Installable
+}
+
 func (mod *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if mod.cachedToolchain == nil {
 		mod.cachedToolchain = config.FindToolchain(ctx.Os(), ctx.Arch())
@@ -757,6 +843,14 @@
 
 		mod.docTimestampFile = mod.compiler.rustdoc(ctx, flags, deps)
 
+		// glob exported headers for snapshot, if BOARD_VNDK_VERSION is current or
+		// RECOVERY_SNAPSHOT_VERSION is current.
+		if lib, ok := mod.compiler.(snapshotLibraryInterface); ok {
+			if cc.ShouldCollectHeadersForSnapshot(ctx, mod, apexInfo) {
+				lib.collectHeadersForSnapshot(ctx, deps)
+			}
+		}
+
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if mod.installable(apexInfo) {
 			mod.compiler.install(ctx)
@@ -987,6 +1081,10 @@
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 				directSharedLibDeps = append(directSharedLibDeps, ccDep)
+
+				// Record baseLibName for snapshots.
+				mod.Properties.SnapshotSharedLibs = append(mod.Properties.SnapshotSharedLibs, cc.BaseLibName(depName))
+
 				mod.Properties.AndroidMkSharedLibs = append(mod.Properties.AndroidMkSharedLibs, makeLibName)
 				exportDep = true
 			case cc.IsHeaderDepTag(depTag):
@@ -1092,6 +1190,11 @@
 
 	deps := mod.deps(ctx)
 	var commonDepVariations []blueprint.Variation
+	var snapshotInfo *cc.SnapshotInfo
+
+	if ctx.Os() == android.Android {
+		deps.SharedLibs, _ = cc.RewriteLibs(mod, &snapshotInfo, actx, ctx.Config(), deps.SharedLibs)
+	}
 
 	stdLinkage := "dylib-std"
 	if mod.compiler.stdLinkage(ctx) == RlibLinkage {
@@ -1099,61 +1202,101 @@
 	}
 
 	rlibDepVariations := commonDepVariations
+
 	if lib, ok := mod.compiler.(libraryInterface); !ok || !lib.sysroot() {
 		rlibDepVariations = append(rlibDepVariations,
 			blueprint.Variation{Mutator: "rust_stdlinkage", Variation: stdLinkage})
 	}
 
-	actx.AddVariationDependencies(
-		append(rlibDepVariations, []blueprint.Variation{
-			{Mutator: "rust_libraries", Variation: rlibVariation}}...),
-		rlibDepTag, deps.Rlibs...)
+	// rlibs
+	for _, lib := range deps.Rlibs {
+		depTag := rlibDepTag
+		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+
+		actx.AddVariationDependencies(append(rlibDepVariations, []blueprint.Variation{
+			{Mutator: "rust_libraries", Variation: rlibVariation},
+		}...), depTag, lib)
+	}
+
+	// dylibs
 	actx.AddVariationDependencies(
 		append(commonDepVariations, []blueprint.Variation{
 			{Mutator: "rust_libraries", Variation: dylibVariation}}...),
 		dylibDepTag, deps.Dylibs...)
 
+	// rustlibs
 	if deps.Rustlibs != nil && !mod.compiler.Disabled() {
 		autoDep := mod.compiler.(autoDeppable).autoDep(ctx)
 		if autoDep.depTag == rlibDepTag {
-			actx.AddVariationDependencies(
-				append(rlibDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation}),
-				autoDep.depTag, deps.Rustlibs...)
+			for _, lib := range deps.Rustlibs {
+				depTag := autoDep.depTag
+				lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+				actx.AddVariationDependencies(append(rlibDepVariations, []blueprint.Variation{
+					{Mutator: "rust_libraries", Variation: autoDep.variation},
+				}...), depTag, lib)
+			}
 		} else {
 			actx.AddVariationDependencies(
 				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation}),
 				autoDep.depTag, deps.Rustlibs...)
 		}
 	}
+
+	// stdlibs
 	if deps.Stdlibs != nil {
 		if mod.compiler.stdLinkage(ctx) == RlibLinkage {
-			actx.AddVariationDependencies(
-				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: "rlib"}),
-				rlibDepTag, deps.Stdlibs...)
+			for _, lib := range deps.Stdlibs {
+				depTag := rlibDepTag
+				lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+
+				actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}...),
+					depTag, lib)
+			}
 		} else {
 			actx.AddVariationDependencies(
 				append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"}),
 				dylibDepTag, deps.Stdlibs...)
 		}
 	}
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "shared"}),
-		cc.SharedDepTag(), deps.SharedLibs...)
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "static"}),
-		cc.StaticDepTag(false), deps.StaticLibs...)
-	actx.AddVariationDependencies(append(commonDepVariations,
-		blueprint.Variation{Mutator: "link", Variation: "static"}),
-		cc.StaticDepTag(true), deps.WholeStaticLibs...)
+
+	for _, lib := range deps.SharedLibs {
+		depTag := cc.SharedDepTag()
+		name, version := cc.StubsLibNameAndVersion(lib)
+
+		variations := []blueprint.Variation{
+			{Mutator: "link", Variation: "shared"},
+		}
+		cc.AddSharedLibDependenciesWithVersions(ctx, mod, variations, depTag, name, version, false)
+	}
+
+	for _, lib := range deps.WholeStaticLibs {
+		depTag := cc.StaticDepTag(true)
+		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).StaticLibs)
+
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, lib)
+	}
+
+	for _, lib := range deps.StaticLibs {
+		depTag := cc.StaticDepTag(false)
+		lib = cc.RewriteSnapshotLib(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).StaticLibs)
+
+		actx.AddVariationDependencies([]blueprint.Variation{
+			{Mutator: "link", Variation: "static"},
+		}, depTag, lib)
+	}
 
 	actx.AddVariationDependencies(nil, cc.HeaderDepTag(), deps.HeaderLibs...)
 
 	crtVariations := cc.GetCrtVariations(ctx, mod)
 	if deps.CrtBegin != "" {
-		actx.AddVariationDependencies(crtVariations, cc.CrtBeginDepTag, deps.CrtBegin)
+		actx.AddVariationDependencies(crtVariations, cc.CrtBeginDepTag,
+			cc.RewriteSnapshotLib(deps.CrtBegin, cc.GetSnapshot(mod, &snapshotInfo, actx).Objects))
 	}
 	if deps.CrtEnd != "" {
-		actx.AddVariationDependencies(crtVariations, cc.CrtEndDepTag, deps.CrtEnd)
+		actx.AddVariationDependencies(crtVariations, cc.CrtEndDepTag,
+			cc.RewriteSnapshotLib(deps.CrtEnd, cc.GetSnapshot(mod, &snapshotInfo, actx).Objects))
 	}
 
 	if mod.sourceProvider != nil {
@@ -1163,6 +1306,7 @@
 				bindgen.Properties.Custom_bindgen)
 		}
 	}
+
 	// proc_macros are compiler plugins, and so we need the host arch variant as a dependendcy.
 	actx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), procMacroDepTag, deps.ProcMacros...)
 }
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 6ae05d9..80f693e 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -37,21 +37,29 @@
 
 	genrule.PrepareForTestWithGenRuleBuildComponents,
 
-	PrepareForIntegrationTestWithRust,
+	PrepareForTestWithRustIncludeVndk,
+	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		variables.DeviceVndkVersion = StringPtr("current")
+		variables.ProductVndkVersion = StringPtr("current")
+		variables.Platform_vndk_version = StringPtr("29")
+	}),
 )
 
 var rustMockedFiles = android.MockFS{
-	"foo.rs":          nil,
-	"foo.c":           nil,
-	"src/bar.rs":      nil,
-	"src/any.h":       nil,
-	"proto.proto":     nil,
-	"proto/buf.proto": nil,
-	"buf.proto":       nil,
-	"foo.proto":       nil,
-	"liby.so":         nil,
-	"libz.so":         nil,
-	"data.txt":        nil,
+	"foo.rs":                       nil,
+	"foo.c":                        nil,
+	"src/bar.rs":                   nil,
+	"src/any.h":                    nil,
+	"c_includes/c_header.h":        nil,
+	"rust_includes/rust_headers.h": nil,
+	"proto.proto":                  nil,
+	"proto/buf.proto":              nil,
+	"buf.proto":                    nil,
+	"foo.proto":                    nil,
+	"liby.so":                      nil,
+	"libz.so":                      nil,
+	"data.txt":                     nil,
+	"liblog.map.txt":               nil,
 }
 
 // testRust returns a TestContext in which a basic environment has been setup.
@@ -67,19 +75,33 @@
 }
 
 func testRustVndk(t *testing.T, bp string) *android.TestContext {
+	return testRustVndkFs(t, bp, rustMockedFiles)
+}
+
+const (
+	sharedVendorVariant = "android_vendor.29_arm64_armv8-a_shared"
+	rlibVendorVariant   = "android_vendor.29_arm64_armv8-a_rlib_rlib-std"
+)
+
+func testRustVndkFs(t *testing.T, bp string, fs android.MockFS) *android.TestContext {
+	return testRustVndkFsVersions(t, bp, fs, "current", "current", "29")
+}
+
+func testRustVndkFsVersions(t *testing.T, bp string, fs android.MockFS, device_version, product_version, vndk_version string) *android.TestContext {
 	skipTestIfOsNotSupported(t)
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
-		rustMockedFiles.AddToFixture(),
+		fs.AddToFixture(),
 		android.FixtureModifyProductVariables(
 			func(variables android.FixtureProductVariables) {
-				variables.DeviceVndkVersion = StringPtr("current")
-				variables.ProductVndkVersion = StringPtr("current")
-				variables.Platform_vndk_version = StringPtr("29")
+				variables.DeviceVndkVersion = StringPtr(device_version)
+				variables.ProductVndkVersion = StringPtr(product_version)
+				variables.Platform_vndk_version = StringPtr(vndk_version)
 			},
 		),
 	).RunTestWithBp(t, bp)
 	return result.TestContext
+
 }
 
 // testRustCov returns a TestContext in which a basic environment has been
@@ -115,10 +137,14 @@
 
 // testRustVndkError is similar to testRustError, but can be used to test VNDK-related errors.
 func testRustVndkError(t *testing.T, pattern string, bp string) {
+	testRustVndkFsError(t, pattern, bp, rustMockedFiles)
+}
+
+func testRustVndkFsError(t *testing.T, pattern string, bp string, fs android.MockFS) {
 	skipTestIfOsNotSupported(t)
 	android.GroupFixturePreparers(
 		prepareForRustTest,
-		rustMockedFiles.AddToFixture(),
+		fs.AddToFixture(),
 		android.FixtureModifyProductVariables(
 			func(variables android.FixtureProductVariables) {
 				variables.DeviceVndkVersion = StringPtr("current")
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 0a53f98..3d14d51 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -189,6 +189,22 @@
 	}
 }
 
+func (m *Module) UbsanRuntimeNeeded() bool {
+	return false
+}
+
+func (m *Module) MinimalRuntimeNeeded() bool {
+	return false
+}
+
+func (m *Module) UbsanRuntimeDep() bool {
+	return false
+}
+
+func (m *Module) MinimalRuntimeDep() bool {
+	return false
+}
+
 // Check if the sanitizer is explicitly disabled (as opposed to nil by
 // virtue of not being set).
 func (sanitize *sanitize) isSanitizerExplicitlyDisabled(t cc.SanitizerType) bool {
diff --git a/rust/snapshot_prebuilt.go b/rust/snapshot_prebuilt.go
new file mode 100644
index 0000000..2f54973
--- /dev/null
+++ b/rust/snapshot_prebuilt.go
@@ -0,0 +1,121 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"github.com/google/blueprint/proptools"
+)
+
+const (
+	snapshotRlibSuffix = "_rlib."
+)
+
+type snapshotLibraryDecorator struct {
+	cc.BaseSnapshotDecorator
+	*libraryDecorator
+	properties          cc.SnapshotLibraryProperties
+	sanitizerProperties struct {
+		CfiEnabled bool `blueprint:"mutated"`
+
+		// Library flags for cfi variant.
+		Cfi cc.SnapshotLibraryProperties `android:"arch_variant"`
+	}
+}
+
+func init() {
+	registerRustSnapshotModules(android.InitRegistrationContext)
+}
+
+func registerRustSnapshotModules(ctx android.RegistrationContext) {
+	cc.VendorSnapshotImageSingleton.RegisterAdditionalModule(ctx,
+		"vendor_snapshot_rlib", VendorSnapshotRlibFactory)
+}
+
+func snapshotLibraryFactory(image cc.SnapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+	module, library := NewRustLibrary(android.DeviceSupported)
+
+	module.sanitize = nil
+	library.stripper.StripProperties.Strip.None = proptools.BoolPtr(true)
+
+	prebuilt := &snapshotLibraryDecorator{
+		libraryDecorator: library,
+	}
+
+	module.compiler = prebuilt
+
+	prebuilt.Init(module, image, moduleSuffix)
+	module.AddProperties(
+		&prebuilt.properties,
+		&prebuilt.sanitizerProperties,
+	)
+
+	return module, prebuilt
+}
+
+func (library *snapshotLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) android.Path {
+	var variant string
+	if library.static() {
+		variant = cc.SnapshotStaticSuffix
+	} else if library.shared() {
+		variant = cc.SnapshotSharedSuffix
+	} else if library.rlib() {
+		variant = cc.SnapshotRlibSuffix
+	}
+
+	if !library.dylib() {
+		// TODO(184042776): Remove this check when dylibs are supported in snapshots.
+		library.SetSnapshotAndroidMkSuffix(ctx, variant)
+	}
+
+	if !library.MatchesWithDevice(ctx.DeviceConfig()) {
+		return nil
+	}
+
+	return android.PathForModuleSrc(ctx, *library.properties.Src)
+}
+
+func (library *snapshotLibraryDecorator) rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath {
+	return android.OptionalPath{}
+}
+
+// vendor_snapshot_rlib is a special prebuilt rlib library which is auto-generated by
+// development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_rlib
+// overrides the vendor variant of the rust rlib library with the same name, if BOARD_VNDK_VERSION
+// is set.
+func VendorSnapshotRlibFactory() android.Module {
+	module, prebuilt := snapshotLibraryFactory(cc.VendorSnapshotImageSingleton, cc.SnapshotRlibSuffix)
+	prebuilt.libraryDecorator.BuildOnlyRlib()
+	prebuilt.libraryDecorator.setNoStdlibs()
+	return module.Init()
+}
+
+func (library *snapshotLibraryDecorator) MatchesWithDevice(config android.DeviceConfig) bool {
+	arches := config.Arches()
+	if len(arches) == 0 || arches[0].ArchType.String() != library.Arch() {
+		return false
+	}
+	if library.properties.Src == nil {
+		return false
+	}
+	return true
+}
+
+func (library *snapshotLibraryDecorator) IsSnapshotPrebuilt() bool {
+	return true
+}
+
+var _ cc.SnapshotInterface = (*snapshotLibraryDecorator)(nil)
diff --git a/rust/snapshot_utils.go b/rust/snapshot_utils.go
new file mode 100644
index 0000000..bd7ca7f
--- /dev/null
+++ b/rust/snapshot_utils.go
@@ -0,0 +1,70 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"android/soong/android"
+)
+
+// snapshotLibraryInterface is an interface for libraries captured to VNDK / vendor snapshots.
+type snapshotLibraryInterface interface {
+	libraryInterface
+
+	// collectHeadersForSnapshot is called in GenerateAndroidBuildActions for snapshot aware
+	// modules (See isSnapshotAware below).
+	// This function should gather all headers needed for snapshot.
+	collectHeadersForSnapshot(ctx android.ModuleContext, deps PathDeps)
+
+	// snapshotHeaders should return collected headers by collectHeadersForSnapshot.
+	// Calling snapshotHeaders before collectHeadersForSnapshot is an error.
+	snapshotHeaders() android.Paths
+}
+
+func (mod *Module) ExcludeFromVendorSnapshot() bool {
+	return Bool(mod.Properties.Exclude_from_vendor_snapshot)
+}
+
+func (mod *Module) ExcludeFromRecoverySnapshot() bool {
+	return Bool(mod.Properties.Exclude_from_recovery_snapshot)
+}
+
+func (mod *Module) IsSnapshotLibrary() bool {
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		// Rust-native dylibs are not snapshot supported yet. Only snapshot the rlib-std variants of rlibs.
+		return lib.shared() || lib.static() || (lib.rlib() && lib.rlibStd())
+	}
+	return false
+}
+
+func (mod *Module) SnapshotRuntimeLibs() []string {
+	// TODO Rust does not yet support a runtime libs notion similar to CC
+	return []string{}
+}
+
+func (mod *Module) SnapshotSharedLibs() []string {
+	return mod.Properties.SnapshotSharedLibs
+}
+
+func (mod *Module) Symlinks() []string {
+	// TODO update this to return the list of symlinks when Rust supports defining symlinks
+	return nil
+}
+
+func (m *Module) SnapshotHeaders() android.Paths {
+	if l, ok := m.compiler.(snapshotLibraryInterface); ok {
+		return l.snapshotHeaders()
+	}
+	return android.Paths{}
+}
diff --git a/rust/testing.go b/rust/testing.go
index a0f86b2..72f87e1 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -45,6 +45,11 @@
 	PrepareForTestWithRustDefaultModules,
 )
 
+var PrepareForTestWithRustIncludeVndk = android.GroupFixturePreparers(
+	PrepareForIntegrationTestWithRust,
+	cc.PrepareForTestWithCcIncludeVndk,
+)
+
 func GatherRequiredDepsForTest() string {
 	bp := `
 		rust_prebuilt_library {
@@ -130,6 +135,9 @@
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
+			llndk: {
+				symbol_file: "liblog.map.txt",
+			},
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -240,4 +248,5 @@
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+	registerRustSnapshotModules(ctx)
 }
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
new file mode 100644
index 0000000..815f80e
--- /dev/null
+++ b/rust/vendor_snapshot_test.go
@@ -0,0 +1,1015 @@
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package rust
+
+import (
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+	"android/soong/cc"
+)
+
+func TestVendorSnapshotCapture(t *testing.T) {
+	bp := `
+	rust_ffi {
+		name: "libffivendor_available",
+		crate_name: "ffivendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_ffi {
+		name: "libffivendor",
+		crate_name: "ffivendor",
+		srcs: ["lib.rs"],
+		vendor: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_library {
+		name: "librustvendor_available",
+		crate_name: "rustvendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_library_rlib {
+		name: "librustvendor",
+		crate_name: "rustvendor",
+		srcs: ["lib.rs"],
+		vendor: true,
+		include_dirs: ["rust_headers/"],
+	}
+
+	rust_binary {
+		name: "vendor_available_bin",
+		vendor_available: true,
+		srcs: ["srcs/lib.rs"],
+	}
+
+	rust_binary {
+		name: "vendor_bin",
+		vendor: true,
+		srcs: ["srcs/lib.rs"],
+	}
+    `
+	skipTestIfOsNotSupported(t)
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		rustMockedFiles.AddToFixture(),
+		android.FixtureModifyProductVariables(
+			func(variables android.FixtureProductVariables) {
+				variables.DeviceVndkVersion = StringPtr("current")
+				variables.Platform_vndk_version = StringPtr("29")
+			},
+		),
+	).RunTestWithBp(t, bp)
+	ctx := result.TestContext
+
+	// Check Vendor snapshot output.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+	var jsonFiles []string
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		// For shared libraries, only non-VNDK vendor_available modules are captured
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(sharedDir, "libffivendor_available.so.json"))
+
+		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
+		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
+		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.a", staticDir, staticVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor", "libffivendor.a", staticDir, staticVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libffivendor_available.a.json"))
+		jsonFiles = append(jsonFiles,
+			filepath.Join(staticDir, "libffivendor.a.json"))
+
+		// For rlib libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor", "librustvendor.rlib", rlibDir, rlibVariant)
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
+		jsonFiles = append(jsonFiles,
+			filepath.Join(rlibDir, "librustvendor.rlib.json"))
+
+		// For binary executables, all vendor:true and vendor_available modules are captured.
+		if archType == "arm64" {
+			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
+			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
+			cc.CheckSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "vendor_available_bin.json"))
+			jsonFiles = append(jsonFiles,
+				filepath.Join(binaryDir, "vendor_bin.json"))
+		}
+	}
+
+	for _, jsonFile := range jsonFiles {
+		// verify all json files exist
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("%q expected but not found; #%v", jsonFile, jsonFiles)
+		}
+	}
+
+	// fake snapshot should have all outputs in the normal snapshot.
+	fakeSnapshotSingleton := ctx.SingletonForTests("vendor-fake-snapshot")
+
+	for _, output := range snapshotSingleton.AllOutputs() {
+		fakeOutput := strings.Replace(output, "/vendor-snapshot/", "/fake/vendor-snapshot/", 1)
+		if fakeSnapshotSingleton.MaybeOutput(fakeOutput).Rule == nil {
+			t.Errorf("%q expected but not found", fakeOutput)
+		}
+	}
+}
+
+func TestVendorSnapshotDirected(t *testing.T) {
+	bp := `
+	rust_ffi_shared {
+		name: "libffivendor_available",
+		crate_name: "ffivendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_library {
+		name: "librustvendor_available",
+		crate_name: "rustvendor_available",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_ffi_shared {
+		name: "libffivendor_exclude",
+		crate_name: "ffivendor_exclude",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+
+	rust_library {
+		name: "librustvendor_exclude",
+		crate_name: "rustvendor_exclude",
+		srcs: ["lib.rs"],
+		vendor_available: true,
+	}
+`
+	ctx := testRustVndk(t, bp)
+	ctx.Config().TestProductVariables.VendorSnapshotModules = make(map[string]bool)
+	ctx.Config().TestProductVariables.VendorSnapshotModules["librustvendor_available"] = true
+	ctx.Config().TestProductVariables.VendorSnapshotModules["libffivendor_available"] = true
+	ctx.Config().TestProductVariables.DirectedVendorSnapshot = true
+
+	// Check Vendor snapshot output.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+
+	var includeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librustvendor_available", "librustvendor_available.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libffivendor_available", "libffivendor_available.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_available.rlib.json"))
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_available.so.json"))
+
+		// Excluded modules. Modules not included in the directed vendor snapshot
+		// are still include as fake modules.
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "librustvendor_exclude", "librustvendor_exclude.rlib", rlibDir, rlibVariant)
+		cc.CheckSnapshotRule(t, ctx, snapshotSingleton, "libffivendor_exclude", "libffivendor_exclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librustvendor_exclude.rlib.json"))
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libffivendor_exclude.so.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+}
+
+func TestVendorSnapshotExclude(t *testing.T) {
+
+	// This test verifies that the exclude_from_vendor_snapshot property
+	// makes its way from the Android.bp source file into the module data
+	// structure. It also verifies that modules are correctly included or
+	// excluded in the vendor snapshot based on their path (framework or
+	// vendor) and the exclude_from_vendor_snapshot property.
+
+	frameworkBp := `
+		rust_ffi_shared {
+			name: "libinclude",
+			crate_name: "include",
+			srcs: ["include.rs"],
+			vendor_available: true,
+		}
+
+		rust_ffi_shared {
+			name: "libexclude",
+			crate_name: "exclude",
+			srcs: ["exclude.rs"],
+			vendor: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_ffi_shared {
+			name: "libavailable_exclude",
+			crate_name: "available_exclude",
+			srcs: ["lib.rs"],
+			vendor_available: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_library {
+			name: "librust_include",
+			crate_name: "rust_include",
+			srcs: ["include.rs"],
+			vendor_available: true,
+		}
+
+		rust_library_rlib {
+			name: "librust_exclude",
+			crate_name: "rust_exclude",
+			srcs: ["exclude.rs"],
+			vendor: true,
+			exclude_from_vendor_snapshot: true,
+		}
+
+		rust_library {
+			name: "librust_available_exclude",
+			crate_name: "rust_available_exclude",
+			srcs: ["lib.rs"],
+			vendor_available: true,
+			exclude_from_vendor_snapshot: true,
+		}
+	`
+
+	mockFS := map[string][]byte{
+		"framework/Android.bp": []byte(frameworkBp),
+		"framework/include.rs": nil,
+		"framework/exclude.rs": nil,
+	}
+
+	ctx := testRustVndkFs(t, "", mockFS)
+
+	// Test an include and exclude framework module.
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libinclude", false, sharedVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libexclude", true, sharedVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "libavailable_exclude", true, sharedVendorVariant)
+
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_include", false, rlibVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_exclude", true, rlibVendorVariant)
+	cc.AssertExcludeFromVendorSnapshotIs(t, ctx, "librust_available_exclude", true, rlibVendorVariant)
+
+	// Verify the content of the vendor snapshot.
+
+	snapshotDir := "vendor-snapshot"
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
+	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
+
+	var includeJsonFiles []string
+	var excludeJsonFiles []string
+
+	for _, arch := range [][]string{
+		[]string{"arm64", "armv8-a"},
+		[]string{"arm", "armv7-a-neon"},
+	} {
+		archType := arch[0]
+		archVariant := arch[1]
+		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
+
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
+		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
+		rlibVariant := fmt.Sprintf("android_vendor.29_%s_%s_rlib_rlib-std", archType, archVariant)
+		rlibDir := filepath.Join(snapshotVariantPath, archDir, "rlib")
+
+		// Included modules
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "libinclude", "libinclude.so", sharedDir, sharedVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(sharedDir, "libinclude.so.json"))
+		cc.CheckSnapshot(t, ctx, snapshotSingleton, "librust_include", "librust_include.rlib", rlibDir, rlibVariant)
+		includeJsonFiles = append(includeJsonFiles, filepath.Join(rlibDir, "librust_include.rlib.json"))
+
+		// Excluded modules
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libexclude", "libexclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libexclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "libavailable_exclude", "libavailable_exclude.so", sharedDir, sharedVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(sharedDir, "libavailable_exclude.so.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_exclude", "librust_exclude.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_exclude.rlib.json"))
+		cc.CheckSnapshotExclude(t, ctx, snapshotSingleton, "librust_available_exclude", "librust_available_exclude.rlib", rlibDir, rlibVariant)
+		excludeJsonFiles = append(excludeJsonFiles, filepath.Join(rlibDir, "librust_available_exclude.rlib.json"))
+	}
+
+	// Verify that each json file for an included module has a rule.
+	for _, jsonFile := range includeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule == nil {
+			t.Errorf("include json file %q not found", jsonFile)
+		}
+	}
+
+	// Verify that each json file for an excluded module has no rule.
+	for _, jsonFile := range excludeJsonFiles {
+		if snapshotSingleton.MaybeOutput(jsonFile).Rule != nil {
+			t.Errorf("exclude json file %q found", jsonFile)
+		}
+	}
+}
+
+func TestVendorSnapshotUse(t *testing.T) {
+	frameworkBp := `
+	cc_library {
+		name: "libvndk",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		nocrt: true,
+	}
+
+	cc_library {
+		name: "libvendor",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	cc_library {
+		name: "libvendor_available",
+		vendor_available: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	cc_library {
+		name: "lib32",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "32",
+	}
+
+	cc_library {
+		name: "lib64",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "64",
+	}
+
+	rust_binary {
+		name: "bin",
+		vendor: true,
+		srcs: ["bin.rs"],
+	}
+
+	rust_binary {
+		name: "bin32",
+		vendor: true,
+		compile_multilib: "32",
+		srcs: ["bin.rs"],
+	}
+`
+
+	vndkBp := `
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm64",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm64: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+
+	// old snapshot module which has to be ignored
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "26",
+		target_arch: "arm64",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm64: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+`
+
+	vendorProprietaryBp := `
+	cc_library {
+		name: "libvendor_without_snapshot",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+	}
+
+	rust_library {
+		name: "librust_vendor_available",
+		crate_name: "rust_vendor",
+		vendor_available: true,
+		srcs: ["client.rs"],
+	}
+
+	rust_ffi_shared {
+		name: "libclient",
+		crate_name: "client",
+		vendor: true,
+		shared_libs: ["libvndk", "libvendor_available"],
+		static_libs: ["libvendor", "libvendor_without_snapshot"],
+		rustlibs: ["librust_vendor_available"],
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
+		srcs: ["client.rs"],
+	}
+
+	rust_library_rlib {
+		name: "libclient_rust",
+		crate_name: "client_rust",
+		vendor: true,
+		shared_libs: ["libvndk", "libvendor_available"],
+		static_libs: ["libvendor", "libvendor_without_snapshot"],
+		rustlibs: ["librust_vendor_available"],
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
+		srcs: ["client.rs"],
+	}
+
+	rust_binary {
+		name: "bin_without_snapshot",
+		vendor: true,
+		static_libs: ["libvndk"],
+		srcs: ["bin.rs"],
+		rustlibs: ["librust_vendor_available"],
+	}
+
+	vendor_snapshot {
+		name: "vendor_snapshot",
+		version: "30",
+		arch: {
+			arm64: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvndk",
+					"libclang_rt.builtins-aarch64-android",
+				],
+				shared_libs: [
+					"libvendor_available",
+					"lib64",
+				],
+				rlibs: [
+					"libstd",
+					"libtest",
+					"librust_vendor_available",
+				],
+				binaries: [
+					"bin",
+				],
+                objects: [
+				    "crtend_so",
+					"crtbegin_so",
+					"crtbegin_dynamic",
+					"crtend_android"
+				],
+			},
+			arm: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvndk",
+					"libclang_rt.builtins-arm-android",
+				],
+				shared_libs: [
+					"libvendor_available",
+					"lib32",
+				],
+				rlibs: [
+					"libstd",
+					"libtest",
+					"librust_vendor_available",
+				],
+				binaries: [
+					"bin32",
+				],
+                objects: [
+				    "crtend_so",
+					"crtbegin_so",
+					"crtbegin_dynamic",
+					"crtend_android"
+				],
+
+			},
+		}
+	}
+
+	vendor_snapshot_object {
+		name: "crtend_so",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtend_so.o",
+			},
+			arm: {
+				src: "crtend_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtbegin_so",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtbegin_so.o",
+			},
+			arm: {
+				src: "crtbegin_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_rlib {
+		name: "libstd",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		sysroot: true,
+		arch: {
+			arm64: {
+				src: "libstd.rlib",
+			},
+			arm: {
+				src: "libstd.rlib",
+			},
+		},
+	}
+
+	vendor_snapshot_rlib {
+		name: "libtest",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		sysroot: true,
+		arch: {
+			arm64: {
+				src: "libtest.rlib",
+			},
+			arm: {
+				src: "libtest.rlib",
+			},
+		},
+	}
+
+	vendor_snapshot_rlib {
+		name: "librust_vendor_available",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "librust_vendor_available.rlib",
+			},
+			arm: {
+				src: "librust_vendor_available.rlib",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtend_android",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtend_so.o",
+			},
+			arm: {
+				src: "crtend_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_object {
+		name: "crtbegin_dynamic",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		stl: "none",
+		crt: true,
+		arch: {
+			arm64: {
+				src: "crtbegin_so.o",
+			},
+			arm: {
+				src: "crtbegin_so.o",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libvndk",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvndk.a",
+			},
+			arm: {
+				src: "libvndk.a",
+			},
+		},
+		shared_libs: ["libvndk"],
+		export_shared_lib_headers: ["libvndk"],
+	}
+
+	vendor_snapshot_static {
+		name: "libclang_rt.builtins-aarch64-android",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libclang_rt.builtins-aarch64-android.a",
+			},
+		},
+    }
+
+    vendor_snapshot_static {
+		name: "libclang_rt.builtins-arm-android",
+		version: "30",
+		target_arch: "arm64",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "libclang_rt.builtins-arm-android.a",
+			},
+		},
+    }
+
+	vendor_snapshot_shared {
+		name: "lib32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.so",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib64",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.so",
+			},
+		},
+	}
+	vendor_snapshot_shared {
+		name: "liblog",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "liblog.so",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "libvendor",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvendor.a",
+				export_include_dirs: ["include/libvendor"],
+			},
+			arm: {
+				src: "libvendor.a",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "libvendor_available",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "both",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+			arm: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	vendor_snapshot_binary {
+		name: "bin32",
+		version: "30",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "bin32",
+			},
+		},
+	}
+
+	// old snapshot module which has to be ignored
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "26",
+		target_arch: "arm64",
+		compile_multilib: "first",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "30",
+		target_arch: "arm",
+		compile_multilib: "first",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+`
+
+	mockFS := android.MockFS{
+		"framework/Android.bp":                          []byte(frameworkBp),
+		"framework/bin.rs":                              nil,
+		"vendor/Android.bp":                             []byte(vendorProprietaryBp),
+		"vendor/bin":                                    nil,
+		"vendor/bin32":                                  nil,
+		"vendor/bin.rs":                                 nil,
+		"vendor/client.rs":                              nil,
+		"vendor/include/libvndk/a.h":                    nil,
+		"vendor/include/libvendor/b.h":                  nil,
+		"vendor/libvndk.a":                              nil,
+		"vendor/libvendor.a":                            nil,
+		"vendor/libvendor.so":                           nil,
+		"vendor/lib32.so":                               nil,
+		"vendor/lib64.so":                               nil,
+		"vendor/liblog.so":                              nil,
+		"vendor/libstd.rlib":                            nil,
+		"vendor/libtest.rlib":                           nil,
+		"vendor/librust_vendor_available.rlib":          nil,
+		"vendor/crtbegin_so.o":                          nil,
+		"vendor/crtend_so.o":                            nil,
+		"vendor/libclang_rt.builtins-aarch64-android.a": nil,
+		"vendor/libclang_rt.builtins-arm-android.a":     nil,
+		"vndk/Android.bp":                               []byte(vndkBp),
+		"vndk/include/libvndk/a.h":                      nil,
+		"vndk/libvndk.so":                               nil,
+	}
+
+	sharedVariant := "android_vendor.30_arm64_armv8-a_shared"
+	rlibVariant := "android_vendor.30_arm64_armv8-a_rlib_rlib-std"
+	staticVariant := "android_vendor.30_arm64_armv8-a_static"
+	binaryVariant := "android_vendor.30_arm64_armv8-a"
+
+	shared32Variant := "android_vendor.30_arm_armv7-a-neon_shared"
+	binary32Variant := "android_vendor.30_arm_armv7-a-neon"
+
+	ctx := testRustVndkFsVersions(t, "", mockFS, "30", "current", "31")
+
+	// libclient uses libvndk.vndk.30.arm64, libvendor.vendor_static.30.arm64, libvendor_without_snapshot
+	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("rustc").Args["linkFlags"]
+	for _, input := range [][]string{
+		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
+		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
+		[]string{staticVariant, "libvendor_without_snapshot"},
+	} {
+		outputPaths := cc.GetOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
+		if !strings.Contains(libclientLdFlags, outputPaths[0].String()) {
+			t.Errorf("libflags for libclient must contain %#v, but was %#v", outputPaths[0], libclientLdFlags)
+		}
+	}
+
+	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkStaticLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkStaticLibs
+	if g, w := libclientAndroidMkStaticLibs, []string{"libvendor", "libvendor_without_snapshot", "libclang_rt.builtins-aarch64-android.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient AndroidMkStaticLibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkRlibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkRlibs
+	if g, w := libclientAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64", "libtest.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
+	}
+
+	libclientAndroidMkDylibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkDylibs
+	if len(libclientAndroidMkDylibs) > 0 {
+		t.Errorf("wanted libclient libclientAndroidMkDylibs [], got %q", libclientAndroidMkDylibs)
+	}
+
+	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32", "liblog.vendor", "libc.vendor", "libm.vendor", "libdl.vendor"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	libclientRustAndroidMkRlibs := ctx.ModuleForTests("libclient_rust", rlibVariant).Module().(*Module).Properties.AndroidMkRlibs
+	if g, w := libclientRustAndroidMkRlibs, []string{"librust_vendor_available.vendor_rlib.30.arm64.rlib-std", "libstd.vendor_rlib.30.arm64", "libtest.vendor_rlib.30.arm64"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient libclientAndroidMkRlibs %q, got %q", w, g)
+	}
+
+	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("rustc").Args["linkFlags"]
+	libVndkStaticOutputPaths := cc.GetOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.30.arm64"})
+	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
+		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
+			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
+	}
+
+	// bin is installed by bin.vendor_binary.30.arm64
+	ctx.ModuleForTests("bin.vendor_binary.30.arm64", binaryVariant).Output("bin")
+
+	// bin32 is installed by bin32.vendor_binary.30.arm64
+	ctx.ModuleForTests("bin32.vendor_binary.30.arm64", binary32Variant).Output("bin32")
+
+	// bin_without_snapshot is installed by bin_without_snapshot
+	ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Output("bin_without_snapshot")
+
+	// libvendor, libvendor_available and bin don't have vendor.30 variant
+	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
+	if android.InList(sharedVariant, libvendorVariants) {
+		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
+	}
+
+	libvendorAvailableVariants := ctx.ModuleVariantsForTests("libvendor_available")
+	if android.InList(sharedVariant, libvendorAvailableVariants) {
+		t.Errorf("libvendor_available must not have variant %#v, but it does", sharedVariant)
+	}
+
+	binVariants := ctx.ModuleVariantsForTests("bin")
+	if android.InList(binaryVariant, binVariants) {
+		t.Errorf("bin must not have variant %#v, but it does", sharedVariant)
+	}
+}
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
index 4b08ac3..8a5513e 100755
--- a/scripts/build-aml-prebuilts.sh
+++ b/scripts/build-aml-prebuilts.sh
@@ -1,14 +1,24 @@
 #!/bin/bash -e
 
-# This is a wrapper around "m" that builds the given modules in multi-arch mode
-# for all architectures supported by Mainline modules. The make (kati) stage is
-# skipped, so the build targets in the arguments can only be Soong modules or
-# intermediate output files - make targets and normal installed paths are not
-# supported.
+# This script is similar to "m" but builds in --soong-only mode, and handles
+# special cases to make that mode work. All arguments are passed on to
+# build/soong/soong_ui.bash.
 #
-# This script is typically used with "sdk" or "module_export" modules, which
-# Soong will install in $OUT_DIR/soong/mainline-sdks (cf
-# PathForMainlineSdksInstall in android/paths.go).
+# --soong-only bypasses the kati step and hence the make logic that e.g. doesn't
+# handle more than two device architectures. It is particularly intended for use
+# with TARGET_PRODUCT=mainline_sdk to build 'sdk' and 'module_export' Soong
+# modules in TARGET_ARCH_SUITE=mainline_sdk mode so that they get all four
+# device architectures (artifacts get installed in $OUT_DIR/soong/mainline-sdks
+# - cf PathForMainlineSdksInstall in android/paths.go).
+#
+# TODO(b/174315599): Replace this script completely with a 'soong_ui.bash
+# --soong-only' invocation. For now it is still necessary to set up
+# build_number.txt.
+
+if [ ! -e build/soong/soong_ui.bash ]; then
+  echo "$0 must be run from the top of the tree"
+  exit 1
+fi
 
 export OUT_DIR=${OUT_DIR:-out}
 
@@ -23,109 +33,19 @@
   OUT_DIR=${AML_OUT_DIR}
 fi
 
-if [ ! -e "build/envsetup.sh" ]; then
-  echo "$0 must be run from the top of the tree"
-  exit 1
-fi
+mkdir -p ${OUT_DIR}/soong
 
-source build/envsetup.sh
-
-my_get_build_var() {
-  # get_build_var will run Soong in normal in-make mode where it creates
-  # .soong.kati_enabled. That would clobber our real out directory, so we need
-  # to run it in a different one.
-  OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
-}
-
-readonly SOONG_OUT=${OUT_DIR}/soong
-mkdir -p ${SOONG_OUT}
+# The --dumpvars-mode invocation will run Soong in normal make mode where it
+# creates .soong.kati_enabled. That would clobber our real out directory, so we
+# need to use a different OUT_DIR.
+vars="$(OUT_DIR=${OUT_DIR}/dumpvars_mode build/soong/soong_ui.bash \
+        --dumpvars-mode --vars=BUILD_NUMBER)"
+# Assign to a variable and eval that, since bash ignores any error status
+# from the command substitution if it's directly on the eval line.
+eval $vars
 
 # Some Soong build rules may require this, and the failure mode if it's missing
 # is confusing (b/172548608).
-readonly BUILD_NUMBER="$(my_get_build_var BUILD_NUMBER)"
-echo -n ${BUILD_NUMBER} > ${SOONG_OUT}/build_number.txt
+echo -n ${BUILD_NUMBER} > ${OUT_DIR}/soong/build_number.txt
 
-readonly PLATFORM_SDK_VERSION="$(my_get_build_var PLATFORM_SDK_VERSION)"
-readonly PLATFORM_VERSION="$(my_get_build_var PLATFORM_VERSION)"
-PLATFORM_VERSION_ALL_CODENAMES="$(my_get_build_var PLATFORM_VERSION_ALL_CODENAMES)"
-
-# PLATFORM_VERSION_ALL_CODENAMES is a comma separated list like O,P. We need to
-# turn this into ["O","P"].
-PLATFORM_VERSION_ALL_CODENAMES="${PLATFORM_VERSION_ALL_CODENAMES/,/'","'}"
-PLATFORM_VERSION_ALL_CODENAMES="[\"${PLATFORM_VERSION_ALL_CODENAMES}\"]"
-
-# Get the list of missing <uses-library> modules and convert it to a JSON array
-# (quote module names, add comma separator and wrap in brackets).
-MISSING_USES_LIBRARIES="$(my_get_build_var INTERNAL_PLATFORM_MISSING_USES_LIBRARIES)"
-MISSING_USES_LIBRARIES="[$(echo $MISSING_USES_LIBRARIES | sed -e 's/\([^ ]\+\)/\"\1\"/g' -e 's/[ ]\+/, /g')]"
-
-# Logic from build/make/core/goma.mk
-if [ "${USE_GOMA}" = true ]; then
-  if [ -n "${GOMA_DIR}" ]; then
-    goma_dir="${GOMA_DIR}"
-  else
-    goma_dir="${HOME}/goma"
-  fi
-  GOMA_CC="${goma_dir}/gomacc"
-  export CC_WRAPPER="${CC_WRAPPER}${CC_WRAPPER:+ }${GOMA_CC}"
-  export CXX_WRAPPER="${CXX_WRAPPER}${CXX_WRAPPER:+ }${GOMA_CC}"
-  export JAVAC_WRAPPER="${JAVAC_WRAPPER}${JAVAC_WRAPPER:+ }${GOMA_CC}"
-else
-  USE_GOMA=false
-fi
-
-readonly SOONG_VARS=${SOONG_OUT}/soong.variables
-
-# Aml_abis: true
-#   -  This flag configures Soong to compile for all architectures required for
-#      Mainline modules.
-# CrossHost: linux_bionic
-# CrossHostArch: x86_64
-#   -  Enable Bionic on host as ART needs prebuilts for it.
-# VendorVars.art_mdoule.source_build
-#   -  TODO(b/172480615): Change default to false when platform uses ART Module
-#      prebuilts by default.
-cat > ${SOONG_VARS}.new << EOF
-{
-    "BuildNumberFile": "build_number.txt",
-
-    "Platform_version_name": "${PLATFORM_VERSION}",
-    "Platform_sdk_version": ${PLATFORM_SDK_VERSION},
-    "Platform_sdk_codename": "${PLATFORM_VERSION}",
-    "Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
-
-    "DeviceName": "generic_arm64",
-    "HostArch": "x86_64",
-    "HostSecondaryArch": "x86",
-    "CrossHost": "linux_bionic",
-    "CrossHostArch": "x86_64",
-    "Aml_abis": true,
-
-    "Allow_missing_dependencies": ${SOONG_ALLOW_MISSING_DEPENDENCIES:-false},
-    "Unbundled_build": ${TARGET_BUILD_UNBUNDLED:-false},
-    "UseGoma": ${USE_GOMA},
-
-    "VendorVars": {
-        "art_module": {
-            "source_build": "${ENABLE_ART_SOURCE_BUILD:-true}"
-        }
-    },
-
-    "MissingUsesLibraries": ${MISSING_USES_LIBRARIES}
-}
-EOF
-
-if [ -f ${SOONG_VARS} ] && cmp -s ${SOONG_VARS} ${SOONG_VARS}.new; then
-  # Don't touch soong.variables if we don't have to, to avoid Soong rebuilding
-  # the ninja file when it isn't necessary.
-  rm ${SOONG_VARS}.new
-else
-  mv ${SOONG_VARS}.new ${SOONG_VARS}
-fi
-
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-m --skip-make "$@"
+build/soong/soong_ui.bash --make-mode --soong-only "$@"
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index 30cb937..7d49492 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -68,6 +68,9 @@
 # Make sure this build builds from source, regardless of the default.
 export SOONG_CONFIG_art_module_source_build=true
 
+# This script does not intend to handle compressed APEX
+export OVERRIDE_PRODUCT_COMPRESSED_APEX=false
+
 OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
 DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
 
@@ -90,11 +93,17 @@
   done
 done
 
+# We use force building LLVM components flag (even though we actually don't
+# compile them) because we don't have bionic host prebuilts
+# for them.
+export FORCE_BUILD_LLVM_COMPONENTS=true
+
 # Create multi-archs SDKs in a different out directory. The multi-arch script
 # uses Soong in --skip-make mode which cannot use the same directory as normal
 # mode with make.
 export OUT_DIR=${OUT_DIR}/aml
-echo_and_run build/soong/scripts/build-aml-prebuilts.sh ${MODULES_SDK_AND_EXPORTS[@]}
+echo_and_run build/soong/scripts/build-aml-prebuilts.sh \
+  TARGET_PRODUCT=mainline_sdk ${MODULES_SDK_AND_EXPORTS[@]}
 
 rm -rf ${DIST_DIR}/mainline-sdks
 echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}
diff --git a/scripts/build-rustdocs.sh b/scripts/build-rustdocs.sh
new file mode 100755
index 0000000..ad8ba16
--- /dev/null
+++ b/scripts/build-rustdocs.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -ex
+
+# Copyright 2021 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Builds the platform rustdocs and copies them to the dist directory to provide
+# online docs for each build.
+
+if [ -z "${OUT_DIR}" ]; then
+    echo Must set OUT_DIR
+    exit 1
+fi
+
+source build/envsetup.sh
+m rustdoc
+
+if [ -n "${DIST_DIR}" ]; then
+    mkdir -p ${DIST_DIR}
+    cp -r ${OUT_DIR}/soong/rustdoc $DIST_DIR/rustdoc
+fi
diff --git a/scripts/gen_sorted_bss_symbols.sh b/scripts/gen_sorted_bss_symbols.sh
deleted file mode 100755
index a9b61a1..0000000
--- a/scripts/gen_sorted_bss_symbols.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash -e
-
-# Copyright 2019 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Script to generate a symbol ordering file that sorts bss section symbols by
-# their sizes.
-# Inputs:
-#  Environment:
-#   CLANG_BIN: path to the clang bin directory
-#  Arguments:
-#   $1: Input ELF file
-#   $2: Output symbol ordering file
-
-set -o pipefail
-
-${CLANG_BIN}/llvm-nm --size-sort $1 | awk '{if ($2 == "b" || $2 == "B") print $3}' > $2
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index af7e7fe..7472f52 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -47,3 +47,18 @@
         },
     },
 }
+
+python_binary_host {
+    name: "verify_overlaps",
+    main: "verify_overlaps.py",
+    srcs: ["verify_overlaps.py"],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/scripts/hiddenapi/merge_csv.py b/scripts/hiddenapi/merge_csv.py
index b047aab..a65326c 100755
--- a/scripts/hiddenapi/merge_csv.py
+++ b/scripts/hiddenapi/merge_csv.py
@@ -55,14 +55,15 @@
                 if entry.endswith('.uau'):
                     csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
 
-headers = set()
 if args.header:
     fieldnames = args.header.split(',')
 else:
+    headers = {}
     # Build union of all columns from source files:
     for reader in csv_readers:
-        headers = headers.union(reader.fieldnames)
-    fieldnames = sorted(headers)
+        for fieldname in reader.fieldnames:
+            headers[fieldname] = ""
+    fieldnames = list(headers.keys())
 
 # By default chain the csv readers together so that the resulting output is
 # the concatenation of the rows from each of them:
diff --git a/scripts/hiddenapi/verify_overlaps.py b/scripts/hiddenapi/verify_overlaps.py
new file mode 100755
index 0000000..c8e3879
--- /dev/null
+++ b/scripts/hiddenapi/verify_overlaps.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Verify that one set of hidden API flags is a subset of another.
+"""
+
+import argparse
+import csv
+
+args_parser = argparse.ArgumentParser(description='Verify that one set of hidden API flags is a subset of another.')
+args_parser.add_argument('all', help='All the flags')
+args_parser.add_argument('subsets', nargs=argparse.REMAINDER, help='Subsets of the flags')
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+    return csv.DictReader(input, delimiter=',', quotechar='|', fieldnames=['signature'])
+
+# Read in all the flags into a dict indexed by signature
+allFlagsBySignature = {}
+with open(args.all, 'r') as allFlagsFile:
+    allFlagsReader = dict_reader(allFlagsFile)
+    for row in allFlagsReader:
+        signature = row['signature']
+        allFlagsBySignature[signature]=row
+
+failed = False
+for subsetPath in args.subsets:
+    mismatchingSignatures = []
+    with open(subsetPath, 'r') as subsetFlagsFile:
+        subsetReader = dict_reader(subsetFlagsFile)
+        for row in subsetReader:
+            signature = row['signature']
+            if signature in allFlagsBySignature:
+                allFlags = allFlagsBySignature.get(signature)
+                if allFlags != row:
+                    mismatchingSignatures.append((signature, row[None], allFlags[None]))
+            else:
+                mismatchingSignatures.append((signature, row[None], []))
+
+
+    if mismatchingSignatures:
+        failed = True
+        print("ERROR: Hidden API flags are inconsistent:")
+        print("< " + subsetPath)
+        print("> " + args.all)
+        for mismatch in mismatchingSignatures:
+            print()
+            print("< " + mismatch[0] + "," + ",".join(mismatch[1]))
+            if mismatch[2] != None:
+                print("> " + mismatch[0] + "," + ",".join(mismatch[2]))
+            else:
+                print("> " + mismatch[0] + " - missing")
+
+if failed:
+    sys.exit(1)
diff --git a/scripts/rbc-run b/scripts/rbc-run
new file mode 100755
index 0000000..e2fa6d1
--- /dev/null
+++ b/scripts/rbc-run
@@ -0,0 +1,16 @@
+#! /bin/bash
+# Convert and run one configuration
+# Args: <product>-<variant>
+[[ $# -eq 1 && "$1" =~ ^(.*)-(.*)$ ]] || { echo Usage: ${0##*/} PRODUCT-VARIANT >&2; exit 1; }
+declare -r product="${BASH_REMATCH[1]:-aosp_arm}"
+declare -r variant="${BASH_REMATCH[2]:-eng}"
+set -eu
+declare -r output_root=${OUT_DIR:-out}
+declare -r runner="$output_root/soong/.bootstrap/bin/rbcrun"
+declare -r converter="$output_root/soong/.bootstrap/bin/mk2rbc"
+declare -r launcher=$output_root/launchers/run.rbc
+$converter -mode=write -r --outdir $output_root --launcher=$launcher $product
+printf "#TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant\n"
+env TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant \
+  $runner RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $launcher
+
diff --git a/sdk/Android.bp b/sdk/Android.bp
index 09a7286..368c03a 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -26,6 +26,7 @@
         "compat_config_sdk_test.go",
         "exports_test.go",
         "java_sdk_test.go",
+        "license_sdk_test.go",
         "sdk_test.go",
         "testing.go",
     ],
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index e91546e..f2ab6a1 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -15,12 +15,53 @@
 package sdk
 
 import (
+	"fmt"
+	"path/filepath"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/java"
 )
 
+// fixtureAddPlatformBootclasspathForBootclasspathFragment adds a platform_bootclasspath module that
+// references the bootclasspath fragment.
+func fixtureAddPlatformBootclasspathForBootclasspathFragment(apex, fragment string) android.FixturePreparer {
+	return android.GroupFixturePreparers(
+		// Add a platform_bootclasspath module.
+		android.FixtureAddTextFile("frameworks/base/boot/Android.bp", fmt.Sprintf(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "%s",
+						module: "%s",
+					},
+				],
+			}
+		`, apex, fragment)),
+		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+	)
+}
+
+// fixtureAddPrebuiltApexForBootclasspathFragment adds a prebuilt_apex that exports the fragment.
+func fixtureAddPrebuiltApexForBootclasspathFragment(apex, fragment string) android.FixturePreparer {
+	apexFile := fmt.Sprintf("%s.apex", apex)
+	dir := "prebuilts/apex"
+	return android.GroupFixturePreparers(
+		// A preparer to add a prebuilt apex to the test fixture.
+		android.FixtureAddTextFile(filepath.Join(dir, "Android.bp"), fmt.Sprintf(`
+			prebuilt_apex {
+				name: "%s",
+				src: "%s",
+				exported_bootclasspath_fragments: [
+					"%s",
+				],
+			}
+		`, apex, apexFile, fragment)),
+		android.FixtureAddFile(filepath.Join(dir, apexFile), nil),
+	)
+}
+
 func TestSnapshotWithBootclasspathFragment_ImageName(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
@@ -34,20 +75,8 @@
 			"system/sepolicy/apex/com.android.art-file_contexts": nil,
 		}),
 
-		// platform_bootclasspath that depends on the fragment.
-		android.FixtureAddTextFile("frameworks/base/boot/Android.bp", `
-			platform_bootclasspath {
-				name: "platform-bootclasspath",
-				fragments: [
-					{
-						apex: "com.android.art",
-						module: "mybootclasspathfragment",
-					},
-				],
-			}
-		`),
-		// Needed for platform_bootclasspath
-		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("com.android.art", "mybootclasspathfragment"),
 
 		java.FixtureConfigureBootJars("com.android.art:mybootlib"),
 		android.FixtureWithRootAndroidBp(`
@@ -89,19 +118,8 @@
 		`),
 	).RunTest(t)
 
-	// A preparer to add a prebuilt apex to the test fixture.
-	prepareWithPrebuiltApex := android.GroupFixturePreparers(
-		android.FixtureAddTextFile("prebuilts/apex/Android.bp", `
-				prebuilt_apex {
-					name: "com.android.art",
-					src: "art.apex",
-					exported_bootclasspath_fragments: [
-						"mybootclasspathfragment",
-					],
-				}
-			`),
-		android.FixtureAddFile("prebuilts/apex/art.apex", nil),
-	)
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("com.android.art", "mybootclasspathfragment")
 
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
@@ -154,9 +172,9 @@
 		checkAllCopyRules(`
 .intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
 `),
-		snapshotTestPreparer(checkSnapshotWithoutSource, prepareWithPrebuiltApex),
-		snapshotTestPreparer(checkSnapshotWithSourcePreferred, prepareWithPrebuiltApex),
-		snapshotTestPreparer(checkSnapshotPreferredWithSource, prepareWithPrebuiltApex),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
 	)
 }
 
@@ -165,49 +183,95 @@
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
-		java.FixtureWithLastReleaseApis("mysdklibrary", "mycoreplatform"),
+		java.FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary", "mycoreplatform"),
+		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib", "myapex:myothersdklibrary"),
+		prepareForSdkTestWithApex,
+
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("myapex", "mybootclasspathfragment"),
+
 		android.FixtureWithRootAndroidBp(`
 			sdk {
 				name: "mysdk",
 				bootclasspath_fragments: ["mybootclasspathfragment"],
-				java_sdk_libs: ["mysdklibrary", "mycoreplatform"],
+				java_sdk_libs: [
+					// This is not strictly needed as it should be automatically added to the sdk_snapshot as
+					// a java_sdk_libs module because it is used in the mybootclasspathfragment's
+					// api.stub_libs property. However, it is specified here to ensure that duplicates are
+					// correctly deduped.
+					"mysdklibrary",
+				],
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
 			}
 
 			bootclasspath_fragment {
 				name: "mybootclasspathfragment",
-				contents: ["mybootlib"],
+				apex_available: ["myapex"],
+				contents: [
+					// This should be automatically added to the sdk_snapshot as a java_boot_libs module.
+					"mybootlib",
+					// This should be automatically added to the sdk_snapshot as a java_sdk_libs module.
+					"myothersdklibrary",
+				],
 				api: {
 					stub_libs: ["mysdklibrary"],
 				},
 				core_platform_api: {
+					// This should be automatically added to the sdk_snapshot as a java_sdk_libs module.
 					stub_libs: ["mycoreplatform"],
 				},
 			}
 
 			java_library {
 				name: "mybootlib",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				system_modules: "none",
 				sdk_version: "none",
+				min_sdk_version: "2",
 				compile_dex: true,
+				permitted_packages: ["mybootlib"],
 			}
 
 			java_sdk_library {
 				name: "mysdklibrary",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
-				compile_dex: true,
+				shared_library: false,
 				public: {enabled: true},
+				min_sdk_version: "2",
+			}
+
+			java_sdk_library {
+				name: "myothersdklibrary",
+				apex_available: ["myapex"],
+				srcs: ["Test.java"],
+				shared_library: false,
+				public: {enabled: true},
+				min_sdk_version: "2",
+				permitted_packages: ["myothersdklibrary"],
 			}
 
 			java_sdk_library {
 				name: "mycoreplatform",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
-				compile_dex: true,
+				shared_library: false,
 				public: {enabled: true},
+				min_sdk_version: "2",
 			}
 		`),
 	).RunTest(t)
 
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -216,31 +280,55 @@
     name: "mybootclasspathfragment",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    contents: ["mybootlib"],
+    apex_available: ["myapex"],
+    contents: [
+        "mybootlib",
+        "myothersdklibrary",
+    ],
     api: {
         stub_libs: ["mysdklibrary"],
     },
     core_platform_api: {
         stub_libs: ["mycoreplatform"],
     },
+    hidden_api: {
+        stub_flags: "hiddenapi/stub-flags.csv",
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
 }
 
 java_import {
     name: "mybootlib",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     jars: ["java/mybootlib.jar"],
 }
 
 java_sdk_library_import {
+    name: "myothersdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myothersdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myothersdklibrary_stub_sources"],
+        current_api: "sdk_library/public/myothersdklibrary.txt",
+        removed_api: "sdk_library/public/myothersdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_sdk_library_import {
     name: "mysdklibrary",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    shared_library: true,
-    compile_dex: true,
+    apex_available: ["myapex"],
+    shared_library: false,
     public: {
         jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
         stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
@@ -254,9 +342,8 @@
     name: "mycoreplatform",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    shared_library: true,
-    compile_dex: true,
+    apex_available: ["myapex"],
+    shared_library: false,
     public: {
         jars: ["sdk_library/public/mycoreplatform-stubs.jar"],
         stub_srcs: ["sdk_library/public/mycoreplatform_stub_sources"],
@@ -265,7 +352,7 @@
         sdk_version: "current",
     },
 }
-`),
+		`),
 		checkVersionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -273,31 +360,55 @@
     name: "mysdk_mybootclasspathfragment@current",
     sdk_member_name: "mybootclasspathfragment",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    contents: ["mysdk_mybootlib@current"],
+    apex_available: ["myapex"],
+    contents: [
+        "mysdk_mybootlib@current",
+        "mysdk_myothersdklibrary@current",
+    ],
     api: {
         stub_libs: ["mysdk_mysdklibrary@current"],
     },
     core_platform_api: {
         stub_libs: ["mysdk_mycoreplatform@current"],
     },
+    hidden_api: {
+        stub_flags: "hiddenapi/stub-flags.csv",
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
 }
 
 java_import {
     name: "mysdk_mybootlib@current",
     sdk_member_name: "mybootlib",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     jars: ["java/mybootlib.jar"],
 }
 
 java_sdk_library_import {
+    name: "mysdk_myothersdklibrary@current",
+    sdk_member_name: "myothersdklibrary",
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myothersdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myothersdklibrary_stub_sources"],
+        current_api: "sdk_library/public/myothersdklibrary.txt",
+        removed_api: "sdk_library/public/myothersdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_sdk_library_import {
     name: "mysdk_mysdklibrary@current",
     sdk_member_name: "mysdklibrary",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    shared_library: true,
-    compile_dex: true,
+    apex_available: ["myapex"],
+    shared_library: false,
     public: {
         jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
         stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
@@ -311,9 +422,8 @@
     name: "mysdk_mycoreplatform@current",
     sdk_member_name: "mycoreplatform",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    shared_library: true,
-    compile_dex: true,
+    apex_available: ["myapex"],
+    shared_library: false,
     public: {
         jars: ["sdk_library/public/mycoreplatform-stubs.jar"],
         stub_srcs: ["sdk_library/public/mycoreplatform_stub_sources"],
@@ -329,20 +439,33 @@
     bootclasspath_fragments: ["mysdk_mybootclasspathfragment@current"],
     java_boot_libs: ["mysdk_mybootlib@current"],
     java_sdk_libs: [
+        "mysdk_myothersdklibrary@current",
         "mysdk_mysdklibrary@current",
         "mysdk_mycoreplatform@current",
     ],
 }
-`),
+		`),
 		checkAllCopyRules(`
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
 .intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+.intermediates/myothersdklibrary.stubs/android_common/javac/myothersdklibrary.stubs.jar -> sdk_library/public/myothersdklibrary-stubs.jar
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_api.txt -> sdk_library/public/myothersdklibrary.txt
+.intermediates/myothersdklibrary.stubs.source/android_common/metalava/myothersdklibrary.stubs.source_removed.txt -> sdk_library/public/myothersdklibrary-removed.txt
 .intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
 .intermediates/mycoreplatform.stubs/android_common/javac/mycoreplatform.stubs.jar -> sdk_library/public/mycoreplatform-stubs.jar
 .intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_api.txt -> sdk_library/public/mycoreplatform.txt
 .intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
-`))
+`),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
+	)
 }
 
 // Test that bootclasspath_fragment works with sdk.
@@ -350,6 +473,7 @@
 	android.GroupFixturePreparers(
 		prepareForSdkTestWithApex,
 		prepareForSdkTestWithJava,
+		android.FixtureAddFile("java/mybootlib.jar", nil),
 		android.FixtureWithRootAndroidBp(`
 		sdk {
 			name: "mysdk",
@@ -359,16 +483,27 @@
 		bootclasspath_fragment {
 			name: "mybootclasspathfragment",
 			image_name: "art",
+			contents: ["mybootlib"],
 			apex_available: ["myapex"],
 		}
 
+		java_library {
+			name: "mybootlib",
+			apex_available: ["myapex"],
+			srcs: ["Test.java"],
+			system_modules: "none",
+			sdk_version: "none",
+			min_sdk_version: "1",
+			compile_dex: true,
+		}
+
 		sdk_snapshot {
 			name: "mysdk@1",
-			bootclasspath_fragments: ["mybootclasspathfragment_mysdk_1"],
+			bootclasspath_fragments: ["mysdk_mybootclasspathfragment@1"],
 		}
 
 		prebuilt_bootclasspath_fragment {
-			name: "mybootclasspathfragment_mysdk_1",
+			name: "mysdk_mybootclasspathfragment@1",
 			sdk_member_name: "mybootclasspathfragment",
 			prefer: false,
 			visibility: ["//visibility:public"],
@@ -376,6 +511,15 @@
 				"myapex",
 			],
 			image_name: "art",
+			contents: ["mysdk_mybootlib@1"],
+		}
+
+		java_import {
+			name: "mysdk_mybootlib@1",
+			sdk_member_name: "mybootlib",
+			visibility: ["//visibility:public"],
+			apex_available: ["com.android.art"],
+			jars: ["java/mybootlib.jar"],
 		}
 	`),
 	).RunTest(t)
@@ -384,6 +528,15 @@
 func TestSnapshotWithBootclasspathFragment_HiddenAPI(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mysdklibrary"),
+		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib"),
+		prepareForSdkTestWithApex,
+
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("myapex", "mybootclasspathfragment"),
+
 		android.MockFS{
 			"my-blocked.txt":                   nil,
 			"my-max-target-o-low-priority.txt": nil,
@@ -400,9 +553,20 @@
 				bootclasspath_fragments: ["mybootclasspathfragment"],
 			}
 
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				min_sdk_version: "1",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
 			bootclasspath_fragment {
 				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
 				contents: ["mybootlib"],
+				api: {
+					stub_libs: ["mysdklibrary"],
+				},
 				hidden_api: {
 					unsupported: [
 							"my-unsupported.txt",
@@ -433,14 +597,27 @@
 
 			java_library {
 				name: "mybootlib",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				system_modules: "none",
 				sdk_version: "none",
+				min_sdk_version: "1",
 				compile_dex: true,
+				permitted_packages: ["mybootlib"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				public: {enabled: true},
 			}
 		`),
 	).RunTest(t)
 
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -449,8 +626,11 @@
     name: "mybootclasspathfragment",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     contents: ["mybootlib"],
+    api: {
+        stub_libs: ["mysdklibrary"],
+    },
     hidden_api: {
         unsupported: ["hiddenapi/my-unsupported.txt"],
         removed: ["hiddenapi/my-removed.txt"],
@@ -460,6 +640,11 @@
         max_target_o_low_priority: ["hiddenapi/my-max-target-o-low-priority.txt"],
         blocked: ["hiddenapi/my-blocked.txt"],
         unsupported_packages: ["hiddenapi/my-unsupported-packages.txt"],
+        stub_flags: "hiddenapi/stub-flags.csv",
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        all_flags: "hiddenapi/all-flags.csv",
     },
 }
 
@@ -467,9 +652,25 @@
     name: "mybootlib",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     jars: ["java/mybootlib.jar"],
 }
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
 `),
 		checkAllCopyRules(`
 my-unsupported.txt -> hiddenapi/my-unsupported.txt
@@ -480,7 +681,18 @@
 my-max-target-o-low-priority.txt -> hiddenapi/my-max-target-o-low-priority.txt
 my-blocked.txt -> hiddenapi/my-blocked.txt
 my-unsupported-packages.txt -> hiddenapi/my-unsupported-packages.txt
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
 .intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
 `),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
 	)
 }
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index dc58d93..6f769a3 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -1089,6 +1089,57 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_UseSrcJar(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJavaSdkLibrary,
+		android.FixtureMergeEnv(map[string]string{
+			"SOONG_SDK_SNAPSHOT_USE_SRCJAR": "true",
+		}),
+	).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib.srcjar"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source-stubs.srcjar -> sdk_library/public/myjavalib.srcjar
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+		`),
+	)
+}
+
 func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
diff --git a/sdk/license_sdk_test.go b/sdk/license_sdk_test.go
new file mode 100644
index 0000000..1ef6fe6
--- /dev/null
+++ b/sdk/license_sdk_test.go
@@ -0,0 +1,138 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestSnapshotWithPackageDefaultLicense(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.PrepareForTestWithLicenses,
+		android.PrepareForTestWithLicenseDefaultModules,
+		android.MockFS{
+			"NOTICE1": nil,
+			"NOTICE2": nil,
+		}.AddToFixture(),
+	).RunTestWithBp(t, `
+		package {
+			default_applicable_licenses: ["mylicense"],
+		}
+
+		license {
+			name: "mylicense",
+			license_kinds: [
+				"SPDX-license-identifier-Apache-2.0",
+				"legacy_unencumbered",
+			],
+			license_text: [
+				"NOTICE1",
+				"NOTICE2",
+			],
+		}
+
+		sdk {
+			name: "mysdk",
+			java_header_libs: ["myjavalib"],
+		}
+
+		java_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			system_modules: "none",
+			sdk_version: "none",
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+package {
+    // A default list here prevents the license LSC from adding its own list which would
+    // be unnecessary as every module in the sdk already has its own licenses property.
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    licenses: ["mysdk_mylicense"],
+    jars: ["java/myjavalib.jar"],
+}
+
+license {
+    name: "mysdk_mylicense",
+    visibility: ["//visibility:private"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "legacy_unencumbered",
+    ],
+    license_text: [
+        "licenses/NOTICE1",
+        "licenses/NOTICE2",
+    ],
+}
+		`),
+		checkVersionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+package {
+    // A default list here prevents the license LSC from adding its own list which would
+    // be unnecessary as every module in the sdk already has its own licenses property.
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    licenses: ["mysdk_mylicense@current"],
+    jars: ["java/myjavalib.jar"],
+}
+
+license {
+    name: "mysdk_mylicense@current",
+    sdk_member_name: "mylicense",
+    visibility: ["//visibility:private"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "legacy_unencumbered",
+    ],
+    license_text: [
+        "licenses/NOTICE1",
+        "licenses/NOTICE2",
+    ],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@current"],
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib/android_common/turbine-combined/myjavalib.jar -> java/myjavalib.jar
+NOTICE1 -> licenses/NOTICE1
+NOTICE2 -> licenses/NOTICE2
+`),
+	)
+}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 12545d6..a13b0d7 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -564,4 +564,101 @@
 			`),
 		)
 	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=unversioned", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "unversioned",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+			`),
+		)
+	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=current", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "current",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-current.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@current"],
+}
+			`),
+		)
+	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=2", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "2",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-2.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@2",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@2",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@2"],
+}
+			`),
+			// A versioned snapshot cannot be used on its own so add the source back in.
+			snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)),
+		)
+	})
 }
diff --git a/sdk/testing.go b/sdk/testing.go
index f4e85c0..3254cf9 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -131,6 +131,7 @@
 	info := &snapshotBuildInfo{
 		t:                            t,
 		r:                            result,
+		version:                      sdk.builderForTests.version,
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
@@ -236,8 +237,13 @@
 	if dir != "" {
 		dir = filepath.Clean(dir) + "/"
 	}
-	android.AssertStringEquals(t, "Snapshot zip file in wrong place",
-		fmt.Sprintf(".intermediates/%s%s/%s/%s-current.zip", dir, name, variant, name), actual)
+	suffix := ""
+	if snapshotBuildInfo.version != soongSdkSnapshotVersionUnversioned {
+		suffix = "-" + snapshotBuildInfo.version
+	}
+
+	expectedZipPath := fmt.Sprintf(".intermediates/%s%s/%s/%s%s.zip", dir, name, variant, name, suffix)
+	android.AssertStringEquals(t, "Snapshot zip file in wrong place", expectedZipPath, actual)
 
 	// Populate a mock filesystem with the files that would have been copied by
 	// the rules.
@@ -432,6 +438,11 @@
 	// The result from RunTest()
 	r *android.TestResult
 
+	// The version of the generated snapshot.
+	//
+	// See snapshotBuilder.version for more information about this field.
+	version string
+
 	// The contents of the generated Android.bp file
 	androidBpContents string
 
diff --git a/sdk/update.go b/sdk/update.go
index a265676..36b564f 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -36,6 +36,20 @@
 //     By default every unversioned module in the generated snapshot has prefer: false. Building it
 //     with SOONG_SDK_SNAPSHOT_PREFER=true will force them to use prefer: true.
 //
+// SOONG_SDK_SNAPSHOT_VERSION
+//     This provides control over the version of the generated snapshot.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=current will generate unversioned and versioned prebuilts and a
+//     versioned snapshot module. This is the default behavior. The zip file containing the
+//     generated snapshot will be <sdk-name>-current.zip.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=unversioned will generate unversioned prebuilts only and the zip
+//     file containing the generated snapshot will be <sdk-name>.zip.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=<number> will generate versioned prebuilts and a versioned
+//     snapshot module only. The zip file containing the generated snapshot will be
+//     <sdk-name>-<number>.zip.
+//
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
@@ -69,6 +83,11 @@
 		})
 )
 
+const (
+	soongSdkSnapshotVersionUnversioned = "unversioned"
+	soongSdkSnapshotVersionCurrent     = "current"
+)
+
 type generatedContents struct {
 	content     strings.Builder
 	indentLevel int
@@ -228,6 +247,7 @@
 
 	allMembersByName := make(map[string]struct{})
 	exportedMembersByName := make(map[string]struct{})
+	hasLicenses := false
 	var memberVariantDeps []sdkMemberVariantDep
 	for _, sdkVariant := range sdkVariants {
 		memberVariantDeps = append(memberVariantDeps, sdkVariant.memberVariantDeps...)
@@ -241,6 +261,10 @@
 			if memberVariantDep.export {
 				exportedMembersByName[name] = struct{}{}
 			}
+
+			if memberVariantDep.memberType == android.LicenseModuleSdkMemberType {
+				hasLicenses = true
+			}
 		}
 	}
 
@@ -252,10 +276,26 @@
 		modules: make(map[string]*bpModule),
 	}
 
+	config := ctx.Config()
+	version := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_VERSION", "current")
+
+	// Generate versioned modules in the snapshot unless an unversioned snapshot has been requested.
+	generateVersioned := version != soongSdkSnapshotVersionUnversioned
+
+	// Generate unversioned modules in the snapshot unless a numbered snapshot has been requested.
+	//
+	// Unversioned modules are not required in that case because the numbered version will be a
+	// finalized version of the snapshot that is intended to be kept separate from the
+	generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
+	snapshotZipFileSuffix := ""
+	if generateVersioned {
+		snapshotZipFileSuffix = "-" + version
+	}
+
 	builder := &snapshotBuilder{
 		ctx:                   ctx,
 		sdk:                   s,
-		version:               "current",
+		version:               version,
 		snapshotDir:           snapshotDir.OutputPath,
 		copies:                make(map[string]string),
 		filesToZip:            []android.Path{bp.path},
@@ -266,6 +306,22 @@
 	}
 	s.builderForTests = builder
 
+	// If the sdk snapshot includes any license modules then add a package module which has a
+	// default_applicable_licenses property. That will prevent the LSC license process from updating
+	// the generated Android.bp file to add a package module that includes all licenses used by all
+	// the modules in that package. That would be unnecessary as every module in the sdk should have
+	// their own licenses property specified.
+	if hasLicenses {
+		pkg := bpFile.newModule("package")
+		property := "default_applicable_licenses"
+		pkg.AddCommentForProperty(property, `
+A default list here prevents the license LSC from adding its own list which would
+be unnecessary as every module in the sdk already has its own licenses property.
+`)
+		pkg.AddProperty(property, []string{"Android-Apache-2.0"})
+		bpFile.AddModule(pkg)
+	}
+
 	// Group the variants for each member module together and then group the members of each member
 	// type together.
 	members := s.groupMemberVariantsByMemberThenType(ctx, memberVariantDeps)
@@ -293,20 +349,26 @@
 		// Prune any empty property sets.
 		unversioned = unversioned.transform(pruneEmptySetTransformer{})
 
-		// Copy the unversioned module so it can be modified to make it versioned.
-		versioned := unversioned.deepCopy()
+		if generateVersioned {
+			// Copy the unversioned module so it can be modified to make it versioned.
+			versioned := unversioned.deepCopy()
 
-		// Transform the unversioned module into a versioned one.
-		versioned.transform(unversionedToVersionedTransformer)
-		bpFile.AddModule(versioned)
+			// Transform the unversioned module into a versioned one.
+			versioned.transform(unversionedToVersionedTransformer)
+			bpFile.AddModule(versioned)
+		}
 
-		// Transform the unversioned module to make it suitable for use in the snapshot.
-		unversioned.transform(unversionedTransformer)
-		bpFile.AddModule(unversioned)
+		if generateUnversioned {
+			// Transform the unversioned module to make it suitable for use in the snapshot.
+			unversioned.transform(unversionedTransformer)
+			bpFile.AddModule(unversioned)
+		}
 	}
 
-	// Add the sdk/module_exports_snapshot module to the bp file.
-	s.addSnapshotModule(ctx, builder, sdkVariants, memberVariantDeps)
+	if generateVersioned {
+		// Add the sdk/module_exports_snapshot module to the bp file.
+		s.addSnapshotModule(ctx, builder, sdkVariants, memberVariantDeps)
+	}
 
 	// generate Android.bp
 	bp = newGeneratedFile(ctx, "snapshot", "Android.bp")
@@ -320,7 +382,8 @@
 	filesToZip := builder.filesToZip
 
 	// zip them all
-	outputZipFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.zip").OutputPath
+	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+	outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
 	outputDesc := "Building snapshot for " + ctx.ModuleName()
 
 	// If there are no zips to merge then generate the output zip directly.
@@ -332,7 +395,8 @@
 		zipFile = outputZipFile
 		desc = outputDesc
 	} else {
-		zipFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.unmerged.zip").OutputPath
+		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+		zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
 		desc = "Building intermediate snapshot for " + ctx.ModuleName()
 	}
 
@@ -780,9 +844,15 @@
 }
 
 type snapshotBuilder struct {
-	ctx         android.ModuleContext
-	sdk         *sdk
-	version     string
+	ctx android.ModuleContext
+	sdk *sdk
+
+	// The version of the generated snapshot.
+	//
+	// See the documentation of SOONG_SDK_SNAPSHOT_VERSION above for details of the valid values of
+	// this field.
+	version string
+
 	snapshotDir android.OutputPath
 	bpFile      *bpFile
 
@@ -893,6 +963,13 @@
 		m.AddProperty("apex_available", apexAvailable)
 	}
 
+	// The licenses are the same for all variants.
+	mctx := s.ctx
+	licenseInfo := mctx.OtherModuleProvider(variant, android.LicenseInfoProvider).(android.LicenseInfo)
+	if len(licenseInfo.Licenses) > 0 {
+		m.AddPropertyWithTag("licenses", licenseInfo.Licenses, s.OptionalSdkMemberReferencePropertyTag())
+	}
+
 	deviceSupported := false
 	hostSupported := false
 
@@ -920,6 +997,12 @@
 }
 
 func addHostDeviceSupportedProperties(deviceSupported bool, hostSupported bool, bpModule *bpModule) {
+	// If neither device or host is supported then this module does not support either so will not
+	// recognize the properties.
+	if !deviceSupported && !hostSupported {
+		return
+	}
+
 	if !deviceSupported {
 		bpModule.AddProperty("device_supported", false)
 	}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 42d5680..4805846 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -104,6 +104,12 @@
 	Recovery_available *bool
 }
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
@@ -143,6 +149,9 @@
 	// list of device library modules that should be installed alongside the test.
 	// Only available for host sh_test modules.
 	Data_device_libs []string `android:"path,arch_variant"`
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type ShBinary struct {
@@ -440,6 +449,9 @@
 					dir := strings.TrimSuffix(s.dataModules[relPath].String(), relPath)
 					entries.AddStrings("LOCAL_TEST_DATA", dir+":"+relPath)
 				}
+				if Bool(s.testProperties.Test_options.Unit_test) {
+					entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+				}
 			},
 		},
 	}}
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 9e7e594..20317d8 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -3,6 +3,7 @@
 import (
 	"os"
 	"path/filepath"
+	"strconv"
 	"testing"
 
 	"android/soong/android"
@@ -148,6 +149,9 @@
 				"testdata/data1",
 				"testdata/sub/data2",
 			],
+			test_options: {
+				unit_test: true,
+			},
 		}
 	`)
 
@@ -156,6 +160,9 @@
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a sh_test_host module.")
 	}
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	actualData, _ := strconv.ParseBool(entries.EntryMap["LOCAL_IS_UNIT_TEST"][0])
+	android.AssertBoolEquals(t, "LOCAL_IS_UNIT_TEST", true, actualData)
 }
 
 func TestShTestHost_dataDeviceModules(t *testing.T) {
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 3f51114..8c8dc82 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -7,6 +7,8 @@
 
 source "$(dirname "$0")/lib.sh"
 
+readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
+
 function test_smoke {
   setup
   run_soong
@@ -491,6 +493,21 @@
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
 }
 
+function test_bp2build_generates_fake_ninja_file {
+  setup
+  create_mock_bazel
+
+  run_bp2build
+
+  if [[ ! -f "./out/soong/build.ninja" ]]; then
+    fail "./out/soong/build.ninja was not generated"
+  fi
+
+  if ! grep "build nothing: phony" "./out/soong/build.ninja"; then
+    fail "missing phony nothing target in out/soong/build.ninja"
+  fi
+}
+
 function test_bp2build_add_android_bp {
   setup
 
@@ -505,8 +522,8 @@
 EOF
 
   GENERATE_BAZEL_FILES=1 run_soong
-  [[ -e out/soong/bp2build/a/BUILD ]] || fail "a/BUILD not created"
-  [[ -L out/soong/workspace/a/BUILD ]] || fail "a/BUILD not symlinked"
+  [[ -e out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
+  [[ -L out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
 
   mkdir -p b
   touch b/b.txt
@@ -519,8 +536,8 @@
 EOF
 
   GENERATE_BAZEL_FILES=1 run_soong
-  [[ -e out/soong/bp2build/b/BUILD ]] || fail "a/BUILD not created"
-  [[ -L out/soong/workspace/b/BUILD ]] || fail "a/BUILD not symlinked"
+  [[ -e out/soong/bp2build/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not created"
+  [[ -L out/soong/workspace/b/${GENERATED_BUILD_FILE_NAME} ]] || fail "a/${GENERATED_BUILD_FILE_NAME} not symlinked"
 }
 
 function test_bp2build_null_build {
@@ -551,11 +568,11 @@
 EOF
 
   GENERATE_BAZEL_FILES=1 run_soong
-  grep -q a1.txt out/soong/bp2build/a/BUILD || fail "a1.txt not in BUILD file"
+  grep -q a1.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a1.txt not in ${GENERATED_BUILD_FILE_NAME} file"
 
   touch a/a2.txt
   GENERATE_BAZEL_FILES=1 run_soong
-  grep -q a2.txt out/soong/bp2build/a/BUILD || fail "a2.txt not in BUILD file"
+  grep -q a2.txt "out/soong/bp2build/a/${GENERATED_BUILD_FILE_NAME}" || fail "a2.txt not in ${GENERATED_BUILD_FILE_NAME} file"
 }
 
 function test_dump_json_module_graph() {
@@ -583,8 +600,8 @@
   GENERATE_BAZEL_FILES=1 run_soong
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
   [[ -d out/soong/workspace/a/b ]] || fail "module directory not a directory"
-  [[ -L out/soong/workspace/a/b/BUILD ]] || fail "BUILD file not symlinked"
-  [[ "$(readlink -f out/soong/workspace/a/b/BUILD)" =~ bp2build/a/b/BUILD$ ]] \
+  [[ -L "out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
+  [[ "$(readlink -f out/soong/workspace/a/b/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/b/${GENERATED_BUILD_FILE_NAME}"$ ]] \
     || fail "BUILD files symlinked at the wrong place"
   [[ -L out/soong/workspace/a/b/b.txt ]] || fail "a/b/b.txt not symlinked"
   [[ -L out/soong/workspace/a/a.txt ]] || fail "a/b/a.txt not symlinked"
@@ -616,7 +633,7 @@
 
   mkdir -p a
   touch a/a.txt
-  touch a/BUILD
+  touch a/${GENERATED_BUILD_FILE_NAME}
   cat > a/Android.bp <<EOF
 filegroup {
   name: "a",
@@ -626,15 +643,15 @@
 EOF
 
   GENERATE_BAZEL_FILES=1 run_soong
-  [[ -L out/soong/workspace/a/BUILD ]] || fail "BUILD file not symlinked"
-  [[ "$(readlink -f out/soong/workspace/a/BUILD)" =~ bp2build/a/BUILD$ ]] \
-    || fail "BUILD files symlinked to the wrong place"
+  [[ -L "out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME}" ]] || fail "${GENERATED_BUILD_FILE_NAME} file not symlinked"
+  [[ "$(readlink -f out/soong/workspace/a/${GENERATED_BUILD_FILE_NAME})" =~ "bp2build/a/${GENERATED_BUILD_FILE_NAME}"$ ]] \
+    || fail "${GENERATED_BUILD_FILE_NAME} files symlinked to the wrong place"
 }
 
 function test_bp2build_reports_multiple_errors {
   setup
 
-  mkdir -p a/BUILD
+  mkdir -p "a/${GENERATED_BUILD_FILE_NAME}"
   touch a/a.txt
   cat > a/Android.bp <<EOF
 filegroup {
@@ -644,7 +661,7 @@
 }
 EOF
 
-  mkdir -p b/BUILD
+  mkdir -p "b/${GENERATED_BUILD_FILE_NAME}"
   touch b/b.txt
   cat > b/Android.bp <<EOF
 filegroup {
@@ -658,8 +675,8 @@
     fail "Build should have failed"
   fi
 
-  grep -q "a/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for a/BUILD not found"
-  grep -q "b/BUILD' exist" "$MOCK_TOP/errors" || fail "Error for b/BUILD not found"
+  grep -q "a/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for a/${GENERATED_BUILD_FILE_NAME} not found"
+  grep -q "b/${GENERATED_BUILD_FILE_NAME}' exist" "$MOCK_TOP/errors" || fail "Error for b/${GENERATED_BUILD_FILE_NAME} not found"
 }
 
 test_smoke
@@ -676,6 +693,7 @@
 test_soong_build_rerun_iff_environment_changes
 test_dump_json_module_graph
 test_bp2build_smoke
+test_bp2build_generates_fake_ninja_file
 test_bp2build_null_build
 test_bp2build_add_android_bp
 test_bp2build_add_to_glob
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 082cd06..e357710 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -6,6 +6,8 @@
 
 source "$(dirname "$0")/lib.sh"
 
+readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
+
 function test_bp2build_generates_all_buildfiles {
   setup
   create_mock_bazel
@@ -40,24 +42,24 @@
 
   run_bp2build
 
-  if [[ ! -f "./out/soong/workspace/foo/convertible_soong_module/BUILD" ]]; then
-    fail "./out/soong/workspace/foo/convertible_soong_module/BUILD was not generated"
+  if [[ ! -f "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME}" ]]; then
+    fail "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME} was not generated"
   fi
 
-  if [[ ! -f "./out/soong/workspace/foo/unconvertible_soong_module/BUILD" ]]; then
-    fail "./out/soong/workspace/foo/unconvertible_soong_module/BUILD was not generated"
+  if [[ ! -f "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}" ]]; then
+    fail "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME} was not generated"
   fi
 
-  if ! grep "the_answer" "./out/soong/workspace/foo/convertible_soong_module/BUILD"; then
-    fail "missing BUILD target the_answer in convertible_soong_module/BUILD"
+  if ! grep "the_answer" "./out/soong/workspace/foo/convertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "missing BUILD target the_answer in convertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
-  if grep "not_the_answer" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
-    fail "found unexpected BUILD target not_the_answer in unconvertible_soong_module/BUILD"
+  if grep "not_the_answer" "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "found unexpected BUILD target not_the_answer in unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
-  if ! grep "filegroup" "./out/soong/workspace/foo/unconvertible_soong_module/BUILD"; then
-    fail "missing filegroup in unconvertible_soong_module/BUILD"
+  if ! grep "filegroup" "./out/soong/workspace/foo/unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"; then
+    fail "missing filegroup in unconvertible_soong_module/${GENERATED_BUILD_FILE_NAME}"
   fi
 
   # NOTE: We don't actually use the extra BUILD file for anything here
diff --git a/tests/lib.sh b/tests/lib.sh
index e561a3d..35ccea9 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -114,6 +114,7 @@
   symlink_directory prebuilts/jdk
 
   symlink_file WORKSPACE
+  symlink_file BUILD
   symlink_file tools/bazel
 }
 
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
index 80774bf..b408fd3 100755
--- a/tests/mixed_mode_test.sh
+++ b/tests/mixed_mode_test.sh
@@ -14,7 +14,7 @@
   setup
   create_mock_bazel
 
-  run_bazel info
+  STANDALONE_BAZEL=true run_bazel info
 }
 
 test_bazel_smoke
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index d17b464..37940ba 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -60,6 +60,7 @@
         "path.go",
         "proc_sync.go",
         "rbe.go",
+        "sandbox_config.go",
         "signal.go",
         "soong.go",
         "test_build.go",
@@ -86,5 +87,8 @@
             "config_linux.go",
             "sandbox_linux.go",
         ],
+        testSrcs: [
+            "sandbox_linux_test.go",
+        ],
     },
 }
diff --git a/ui/build/build.go b/ui/build/build.go
index c2ad057..8f050d9 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -60,15 +60,15 @@
 {{end -}}
 pool highmem_pool
  depth = {{.HighmemParallel}}
-{{if .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
+{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
 subninja {{.KatiPackageNinjaFile}}
 {{end -}}
 subninja {{.SoongNinjaFile}}
 `))
 
 func createCombinedBuildNinjaFile(ctx Context, config Config) {
-	// If we're in SkipKati mode, skip creating this file if it already exists
-	if config.SkipKati() {
+	// If we're in SkipKati mode but want to run kati ninja, skip creating this file if it already exists
+	if config.SkipKati() && !config.SkipKatiNinja() {
 		if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
 			return
 		}
@@ -87,15 +87,22 @@
 
 // These are bitmasks which can be used to check whether various flags are set e.g. whether to use Bazel.
 const (
-	BuildNone          = iota
-	BuildProductConfig = 1 << iota
-	BuildSoong         = 1 << iota
-	BuildKati          = 1 << iota
-	BuildNinja         = 1 << iota
-	BuildBazel         = 1 << iota
-	RunBuildTests      = 1 << iota
-	BuildAll           = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
-	BuildAllWithBazel  = BuildProductConfig | BuildSoong | BuildKati | BuildBazel
+	_ = iota
+	// Whether to run the kati config step.
+	RunProductConfig = 1 << iota
+	// Whether to run soong to generate a ninja file.
+	RunSoong = 1 << iota
+	// Whether to run kati to generate a ninja file.
+	RunKati = 1 << iota
+	// Whether to include the kati-generated ninja file in the combined ninja.
+	RunKatiNinja = 1 << iota
+	// Whether to run ninja on the combined ninja.
+	RunNinja = 1 << iota
+	// Whether to run bazel on the combined ninja.
+	RunBazel        = 1 << iota
+	RunBuildTests   = 1 << iota
+	RunAll          = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunNinja
+	RunAllWithBazel = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunBazel
 )
 
 // checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
@@ -173,7 +180,7 @@
 
 // Build the tree. The 'what' argument can be used to chose which components of
 // the build to run, via checking various bitmasks.
-func Build(ctx Context, config Config, what int) {
+func Build(ctx Context, config Config) {
 	ctx.Verboseln("Starting build with args:", config.Arguments())
 	ctx.Verboseln("Environment:", config.Environment().Environ())
 
@@ -208,33 +215,39 @@
 
 	SetupPath(ctx, config)
 
+	what := RunAll
+	if config.UseBazel() {
+		what = RunAllWithBazel
+	}
+	if config.Checkbuild() {
+		what |= RunBuildTests
+	}
 	if config.SkipConfig() {
 		ctx.Verboseln("Skipping Config as requested")
-		what = what &^ BuildProductConfig
+		what = what &^ RunProductConfig
 	}
-
 	if config.SkipKati() {
 		ctx.Verboseln("Skipping Kati as requested")
-		what = what &^ BuildKati
+		what = what &^ RunKati
 	}
-
+	if config.SkipKatiNinja() {
+		ctx.Verboseln("Skipping use of Kati ninja as requested")
+		what = what &^ RunKatiNinja
+	}
 	if config.SkipNinja() {
 		ctx.Verboseln("Skipping Ninja as requested")
-		what = what &^ BuildNinja
+		what = what &^ RunNinja
 	}
 
 	if config.StartGoma() {
-		// Ensure start Goma compiler_proxy
 		startGoma(ctx, config)
 	}
 
 	if config.StartRBE() {
-		// Ensure RBE proxy is started
 		startRBE(ctx, config)
 	}
 
-	if what&BuildProductConfig != 0 {
-		// Run make for product config
+	if what&RunProductConfig != 0 {
 		runMakeProductConfig(ctx, config)
 	}
 
@@ -254,8 +267,7 @@
 		return
 	}
 
-	if what&BuildSoong != 0 {
-		// Run Soong
+	if what&RunSoong != 0 {
 		runSoong(ctx, config)
 
 		if config.bazelBuildMode() == generateBuildFiles {
@@ -264,15 +276,14 @@
 		}
 	}
 
-	if what&BuildKati != 0 {
-		// Run ckati
+	if what&RunKati != 0 {
 		genKatiSuffix(ctx, config)
 		runKatiCleanSpec(ctx, config)
 		runKatiBuild(ctx, config)
 		runKatiPackage(ctx, config)
 
 		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
-	} else {
+	} else if what&RunKatiNinja != 0 {
 		// Load last Kati Suffix if it exists
 		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
 			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
@@ -289,17 +300,16 @@
 		testForDanglingRules(ctx, config)
 	}
 
-	if what&BuildNinja != 0 {
-		if what&BuildKati != 0 {
+	if what&RunNinja != 0 {
+		if what&RunKati != 0 {
 			installCleanIfNecessary(ctx, config)
 		}
 
-		// Run ninja
 		runNinjaForBuild(ctx, config)
 	}
 
 	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
-	if what&BuildBazel != 0 {
+	if what&RunBazel != 0 {
 		runBazel(ctx, config)
 	}
 }
diff --git a/ui/build/config.go b/ui/build/config.go
index 1d1f71f..220e734 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -48,6 +48,7 @@
 	dist           bool
 	skipConfig     bool
 	skipKati       bool
+	skipKatiNinja  bool
 	skipNinja      bool
 	skipSoongTests bool
 
@@ -57,6 +58,7 @@
 	katiSuffix      string
 	targetDevice    string
 	targetDeviceDir string
+	sandboxConfig   *SandboxConfig
 
 	// Autodetected
 	totalRAM uint64
@@ -72,6 +74,9 @@
 	// During Bazel execution, Bazel cannot write outside OUT_DIR.
 	// So if DIST_DIR is set to an external dir (outside of OUT_DIR), we need to rig it temporarily and then migrate files at the end of the build.
 	riggedDistDirForBazel string
+
+	// Set by multiproduct_kati
+	emptyNinjaFile bool
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -120,7 +125,8 @@
 
 func NewConfig(ctx Context, args ...string) Config {
 	ret := &configImpl{
-		environ: OsEnvironment(),
+		environ:       OsEnvironment(),
+		sandboxConfig: &SandboxConfig{},
 	}
 
 	// Default matching ninja
@@ -203,9 +209,6 @@
 		"ANDROID_DEV_SCRIPTS",
 		"ANDROID_EMULATOR_PREBUILTS",
 		"ANDROID_PRE_BUILD_PATHS",
-
-		// Only set in multiproduct_kati after config generation
-		"EMPTY_NINJA_FILE",
 	)
 
 	if ret.UseGoma() || ret.ForceUseGoma() {
@@ -565,8 +568,7 @@
 func (c *configImpl) parseArgs(ctx Context, args []string) {
 	for i := 0; i < len(args); i++ {
 		arg := strings.TrimSpace(args[i])
-		if arg == "--make-mode" {
-		} else if arg == "showcommands" {
+		if arg == "showcommands" {
 			c.verbose = true
 		} else if arg == "--skip-ninja" {
 			c.skipNinja = true
@@ -574,7 +576,11 @@
 			c.skipConfig = true
 			c.skipKati = true
 		} else if arg == "--skip-kati" {
+			// TODO: remove --skip-kati once module builds have been migrated to --song-only
 			c.skipKati = true
+		} else if arg == "--soong-only" {
+			c.skipKati = true
+			c.skipKatiNinja = true
 		} else if arg == "--skip-soong-tests" {
 			c.skipSoongTests = true
 		} else if len(arg) > 0 && arg[0] == '-' {
@@ -790,10 +796,18 @@
 	return c.skipKati
 }
 
+func (c *configImpl) SkipKatiNinja() bool {
+	return c.skipKatiNinja
+}
+
 func (c *configImpl) SkipNinja() bool {
 	return c.skipNinja
 }
 
+func (c *configImpl) SetSkipNinja(v bool) {
+	c.skipNinja = v
+}
+
 func (c *configImpl) SkipConfig() bool {
 	return c.skipConfig
 }
@@ -1189,3 +1203,11 @@
 func (c *configImpl) BazelMetricsDir() string {
 	return filepath.Join(c.LogsDir(), "bazel_metrics")
 }
+
+func (c *configImpl) SetEmptyNinjaFile(v bool) {
+	c.emptyNinjaFile = v
+}
+
+func (c *configImpl) EmptyNinjaFile() bool {
+	return c.emptyNinjaFile
+}
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index fe0aca9..83c8865 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -162,6 +162,8 @@
 	"OUT_DIR",
 	"AUX_OS_VARIANT_LIST",
 	"PRODUCT_SOONG_NAMESPACES",
+	"SOONG_SDK_SNAPSHOT_PREFER",
+	"SOONG_SDK_SNAPSHOT_VERSION",
 }
 
 func Banner(make_vars map[string]string) string {
@@ -223,6 +225,10 @@
 		// Extra environment variables to be exported to ninja
 		"BUILD_BROKEN_NINJA_USES_ENV_VARS",
 
+		// Used to restrict write access to source tree
+		"BUILD_BROKEN_SRC_DIR_IS_WRITABLE",
+		"BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST",
+
 		// Not used, but useful to be in the soong.log
 		"BOARD_VNDK_VERSION",
 
@@ -278,6 +284,8 @@
 	config.SetNinjaArgs(strings.Fields(makeVars["NINJA_GOALS"]))
 	config.SetTargetDevice(makeVars["TARGET_DEVICE"])
 	config.SetTargetDeviceDir(makeVars["TARGET_DEVICE_DIR"])
+	config.sandboxConfig.SetSrcDirIsRO(makeVars["BUILD_BROKEN_SRC_DIR_IS_WRITABLE"] == "false")
+	config.sandboxConfig.SetSrcDirRWAllowlist(strings.Fields(makeVars["BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST"]))
 
 	config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true")
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 2eb84ca..09d53cc 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -76,6 +76,8 @@
 			"Blueprints",
 			// Bazel build definitions.
 			"BUILD.bazel",
+			// Bazel build definitions.
+			"BUILD",
 			// Kati clean definitions.
 			"CleanSpec.mk",
 			// Ownership definition.
@@ -102,7 +104,7 @@
 func findBazelFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
 	matches := []string{}
 	for _, foundName := range entries.FileNames {
-		if foundName == "BUILD.bazel" || foundName == "WORKSPACE" || strings.HasSuffix(foundName, ".bzl") {
+		if foundName == "BUILD.bazel" || foundName == "BUILD" || foundName == "WORKSPACE" || strings.HasSuffix(foundName, ".bzl") {
 			matches = append(matches, foundName)
 		}
 	}
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 06ec646..dad68fa 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -136,7 +136,7 @@
 	// information out with --empty_ninja_file.
 	//
 	// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
-	if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {
+	if config.EmptyNinjaFile() {
 		args = append(args, "--empty_ninja_file")
 	}
 
diff --git a/ui/build/sandbox_config.go b/ui/build/sandbox_config.go
new file mode 100644
index 0000000..1b46459
--- /dev/null
+++ b/ui/build/sandbox_config.go
@@ -0,0 +1,36 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package build
+
+type SandboxConfig struct {
+	srcDirIsRO        bool
+	srcDirRWAllowlist []string
+}
+
+func (sc *SandboxConfig) SetSrcDirIsRO(ro bool) {
+	sc.srcDirIsRO = ro
+}
+
+func (sc *SandboxConfig) SrcDirIsRO() bool {
+	return sc.srcDirIsRO
+}
+
+func (sc *SandboxConfig) SetSrcDirRWAllowlist(allowlist []string) {
+	sc.srcDirRWAllowlist = allowlist
+}
+
+func (sc *SandboxConfig) SrcDirRWAllowlist() []string {
+	return sc.srcDirRWAllowlist
+}
diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go
index dab0e75..b0a6748 100644
--- a/ui/build/sandbox_linux.go
+++ b/ui/build/sandbox_linux.go
@@ -145,6 +145,13 @@
 func (c *Cmd) wrapSandbox() {
 	wd, _ := os.Getwd()
 
+	var srcDirMountFlag string
+	if c.config.sandboxConfig.SrcDirIsRO() {
+		srcDirMountFlag = "-R"
+	} else {
+		srcDirMountFlag = "-B" //Read-Write
+	}
+
 	sandboxArgs := []string{
 		// The executable to run
 		"-x", c.Path,
@@ -184,8 +191,8 @@
 		// Mount a writable tmp dir
 		"-B", "/tmp",
 
-		// Mount source are read-write
-		"-B", sandboxConfig.srcDir,
+		// Mount source
+		srcDirMountFlag, sandboxConfig.srcDir,
 
 		//Mount out dir as read-write
 		"-B", sandboxConfig.outDir,
@@ -198,6 +205,18 @@
 		"-q",
 	}
 
+	// Mount srcDir RW allowlists as Read-Write
+	if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() {
+		errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary.
+			To recover, either
+			1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or
+			2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST`
+		c.ctx.Fatalln(errMsg)
+	}
+	for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() {
+		sandboxArgs = append(sandboxArgs, "-B", srcDirChild)
+	}
+
 	if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
 		//Mount dist dir as read-write if it already exists
 		sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir)
diff --git a/ui/build/sandbox_linux_test.go b/ui/build/sandbox_linux_test.go
new file mode 100644
index 0000000..7bfd750
--- /dev/null
+++ b/ui/build/sandbox_linux_test.go
@@ -0,0 +1,104 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package build
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMain(m *testing.M) {
+	// set src dir of sandbox
+	sandboxConfig.srcDir = "/my/src/dir"
+	os.Exit(m.Run())
+}
+
+func TestMountFlagsSrcDir(t *testing.T) {
+	testCases := []struct {
+		srcDirIsRO         bool
+		expectedSrcDirFlag string
+	}{
+		{
+			srcDirIsRO:         false,
+			expectedSrcDirFlag: "-B",
+		},
+		{
+			srcDirIsRO:         true,
+			expectedSrcDirFlag: "-R",
+		},
+	}
+	for _, testCase := range testCases {
+		c := testCmd()
+		c.config.sandboxConfig.SetSrcDirIsRO(testCase.srcDirIsRO)
+		c.wrapSandbox()
+		if !isExpectedMountFlag(c.Args, sandboxConfig.srcDir, testCase.expectedSrcDirFlag) {
+			t.Error("Mount flag of srcDir is not correct")
+		}
+	}
+}
+
+func TestMountFlagsSrcDirRWAllowlist(t *testing.T) {
+	testCases := []struct {
+		srcDirRWAllowlist []string
+	}{
+		{
+			srcDirRWAllowlist: []string{},
+		},
+		{
+			srcDirRWAllowlist: []string{"my/path"},
+		},
+		{
+			srcDirRWAllowlist: []string{"my/path1", "my/path2"},
+		},
+	}
+	for _, testCase := range testCases {
+		c := testCmd()
+		c.config.sandboxConfig.SetSrcDirIsRO(true)
+		c.config.sandboxConfig.SetSrcDirRWAllowlist(testCase.srcDirRWAllowlist)
+		c.wrapSandbox()
+		for _, allowlistPath := range testCase.srcDirRWAllowlist {
+			if !isExpectedMountFlag(c.Args, allowlistPath, "-B") {
+				t.Error("Mount flag of srcDirRWAllowlist is not correct, expect -B")
+			}
+		}
+	}
+}
+
+// utils for setting up test
+func testConfig() Config {
+	// create a minimal testConfig
+	env := Environment([]string{})
+	sandboxConfig := SandboxConfig{}
+	return Config{&configImpl{environ: &env,
+		sandboxConfig: &sandboxConfig}}
+}
+
+func testCmd() *Cmd {
+	return Command(testContext(), testConfig(), "sandbox_test", "path/to/nsjail")
+}
+
+func isExpectedMountFlag(cmdArgs []string, dirName string, expectedFlag string) bool {
+	indexOfSrcDir := index(cmdArgs, dirName)
+	return cmdArgs[indexOfSrcDir-1] == expectedFlag
+}
+
+func index(arr []string, target string) int {
+	for idx, element := range arr {
+		if element == target {
+			return idx
+		}
+	}
+	panic("element could not be located in input array")
+}
diff --git a/ui/build/soong.go b/ui/build/soong.go
index a41dbe1..19a47ae 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -99,6 +99,21 @@
 		"--used_env", shared.JoinPath(config.SoongOutDir(), usedEnvFile+suffix),
 	}
 }
+
+func writeEmptyGlobFile(ctx Context, path string) {
+	err := os.MkdirAll(filepath.Dir(path), 0777)
+	if err != nil {
+		ctx.Fatalf("Failed to create parent directories of empty ninja glob file '%s': %s", path, err)
+	}
+
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		err = ioutil.WriteFile(path, nil, 0666)
+		if err != nil {
+			ctx.Fatalf("Failed to create empty ninja glob file '%s': %s", path, err)
+		}
+	}
+}
+
 func bootstrapBlueprint(ctx Context, config Config, integratedBp2Build bool) {
 	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 	defer ctx.EndTrace()
@@ -106,8 +121,10 @@
 	var args bootstrap.Args
 
 	mainNinjaFile := shared.JoinPath(config.SoongOutDir(), "build.ninja")
-	globFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/soong-build-globs.ninja")
 	bootstrapGlobFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build-globs.ninja")
+	// .bootstrap/build.ninja "includes" .bootstrap/build-globs.ninja for incremental builds
+	// generate an empty glob before running any rule in .bootstrap/build.ninja
+	writeEmptyGlobFile(ctx, bootstrapGlobFile)
 	bootstrapDepFile := shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja.d")
 
 	args.RunGoTests = !config.skipSoongTests
@@ -117,8 +134,12 @@
 	args.TopFile = "Android.bp"
 	args.ModuleListFile = filepath.Join(config.FileListDir(), "Android.bp.list")
 	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
-	args.GlobFile = globFile
+	// The primary builder (aka soong_build) will use bootstrapGlobFile as the globFile to generate build.ninja(.d)
+	// Building soong_build does not require a glob file
+	// Using "" instead of "<soong_build_glob>.ninja" will ensure that an unused glob file is not written to out/soong/.bootstrap during StagePrimary
+	args.GlobFile = ""
 	args.GeneratingPrimaryBuilder = true
+	args.EmptyNinjaFile = config.EmptyNinjaFile()
 
 	args.DelveListen = os.Getenv("SOONG_DELVE")
 	if args.DelveListen != "" {
@@ -155,9 +176,9 @@
 			Outputs: []string{bp2BuildMarkerFile},
 			Args:    bp2buildArgs,
 		}
-		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{
-			bp2buildInvocation,
-			mainSoongBuildInvocation,
+		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{bp2buildInvocation}
+		if config.bazelBuildMode() == mixedBuild {
+			args.PrimaryBuilderInvocations = append(args.PrimaryBuilderInvocations, mainSoongBuildInvocation)
 		}
 	} else {
 		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{mainSoongBuildInvocation}
diff --git a/zip/zip.go b/zip/zip.go
index 84e974b..ae379f5 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -656,9 +656,11 @@
 			UncompressedSize64: uint64(fileSize),
 		}
 
+		mode := os.FileMode(0644)
 		if executable {
-			header.SetMode(0700)
+			mode = 0755
 		}
+		header.SetMode(mode)
 
 		err = createParentDirs(dest, src)
 		if err != nil {
@@ -953,7 +955,7 @@
 				dirHeader = &zip.FileHeader{
 					Name: cleanDir + "/",
 				}
-				dirHeader.SetMode(0700 | os.ModeDir)
+				dirHeader.SetMode(0755 | os.ModeDir)
 			}
 
 			dirHeader.SetModTime(z.time)
diff --git a/zip/zip_test.go b/zip/zip_test.go
index a37ae41..79cc0b4 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -62,7 +62,7 @@
 		Method:             method,
 		CRC32:              crc32.ChecksumIEEE(contents),
 		UncompressedSize64: uint64(len(contents)),
-		ExternalAttrs:      0,
+		ExternalAttrs:      (syscall.S_IFREG | 0644) << 16,
 	}
 }
 
@@ -72,7 +72,7 @@
 		Method:             zip.Store,
 		CRC32:              crc32.ChecksumIEEE(contents),
 		UncompressedSize64: uint64(len(contents)),
-		ExternalAttrs:      (syscall.S_IFREG | 0700) << 16,
+		ExternalAttrs:      (syscall.S_IFREG | 0644) << 16,
 	}
 }
 
@@ -92,7 +92,7 @@
 		Method:             zip.Store,
 		CRC32:              crc32.ChecksumIEEE(nil),
 		UncompressedSize64: 0,
-		ExternalAttrs:      (syscall.S_IFDIR|0700)<<16 | 0x10,
+		ExternalAttrs:      (syscall.S_IFDIR|0755)<<16 | 0x10,
 	}
 }