Merge "Improve mixed builds error messages"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 2e4068d..24731e8 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -726,7 +726,6 @@
 
 		// aar support
 		"prebuilt_car-ui-androidx-core-common",         // TODO(b/224773339), genrule dependency creates an .aar, not a .jar
-		"prebuilt_platform-robolectric-4.4-prebuilt",   // aosp/1999250, needs .aar support in Jars
 		"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
 		// ERROR: The dependencies for the following 1 jar(s) are not complete.
 		// 1.bazel-out/android_target-fastbuild/bin/prebuilts/tools/common/m2/_aar/robolectric-monitor-1.0.2-alpha1/classes_and_libs_merged.jar
@@ -1345,7 +1344,6 @@
 		"prebuilt_kotlin-stdlib-jdk8",
 		"prebuilt_kotlin-test",
 		// TODO(b/217750501) exclude_files property not supported
-		"prebuilt_platform-robolectric-4.4-prebuilt",
 		"prebuilt_platform-robolectric-4.5.1-prebuilt",
 		"prebuilt_currysrc_org.eclipse",
 	}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index fd395a1..acb81a4 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -1050,18 +1050,26 @@
 
 	for _, depset := range ctx.Config().BazelContext.AqueryDepsets() {
 		var outputs []Path
+		var orderOnlies []Path
 		for _, depsetDepHash := range depset.TransitiveDepSetHashes {
 			otherDepsetName := bazelDepsetName(depsetDepHash)
 			outputs = append(outputs, PathForPhony(ctx, otherDepsetName))
 		}
 		for _, artifactPath := range depset.DirectArtifacts {
-			outputs = append(outputs, PathForBazelOut(ctx, artifactPath))
+			pathInBazelOut := PathForBazelOut(ctx, artifactPath)
+			if artifactPath == "bazel-out/volatile-status.txt" {
+				// See https://bazel.build/docs/user-manual#workspace-status
+				orderOnlies = append(orderOnlies, pathInBazelOut)
+			} else {
+				outputs = append(outputs, pathInBazelOut)
+			}
 		}
 		thisDepsetName := bazelDepsetName(depset.ContentHash)
 		ctx.Build(pctx, BuildParams{
 			Rule:      blueprint.Phony,
 			Outputs:   []WritablePath{PathForPhony(ctx, thisDepsetName)},
 			Implicits: outputs,
+			OrderOnly: orderOnlies,
 		})
 	}
 
diff --git a/android/license.go b/android/license.go
index cde5e6e..ab8431a 100644
--- a/android/license.go
+++ b/android/license.go
@@ -15,10 +15,12 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"fmt"
-	"github.com/google/blueprint"
 	"os"
+
+	"github.com/google/blueprint"
+
+	"android/soong/bazel"
 )
 
 type licenseKindDependencyTag struct {
@@ -101,6 +103,14 @@
 }
 
 func (m *licenseModule) DepsMutator(ctx BottomUpMutatorContext) {
+	for i, license := range m.properties.License_kinds {
+		for j := i + 1; j < len(m.properties.License_kinds); j++ {
+			if license == m.properties.License_kinds[j] {
+				ctx.ModuleErrorf("Duplicated license kind: %q", license)
+				break
+			}
+		}
+	}
 	ctx.AddVariationDependencies(nil, licenseKindTag, m.properties.License_kinds...)
 }
 
diff --git a/android/license_test.go b/android/license_test.go
index 7222cd7..89e7f06 100644
--- a/android/license_test.go
+++ b/android/license_test.go
@@ -90,6 +90,36 @@
 		},
 	},
 	{
+		name: "must not duplicate license_kind",
+		fs: map[string][]byte{
+			"top/Android.bp": []byte(`
+				license_kind {
+					name: "top_by_exception_only",
+					conditions: ["by_exception_only"],
+					visibility: ["//visibility:private"],
+				}
+
+				license_kind {
+					name: "top_by_exception_only_2",
+					conditions: ["by_exception_only"],
+					visibility: ["//visibility:private"],
+				}
+
+				license {
+					name: "top_proprietary",
+					license_kinds: [
+						"top_by_exception_only",
+						"top_by_exception_only_2",
+						"top_by_exception_only"
+					],
+					visibility: ["//visibility:public"],
+				}`),
+		},
+		expectedErrors: []string{
+			`top/Android.bp:14:5: module "top_proprietary": Duplicated license kind: "top_by_exception_only"`,
+		},
+	},
+	{
 		name: "license_kind module must exist",
 		fs: map[string][]byte{
 			"top/Android.bp": []byte(`
diff --git a/android/neverallow.go b/android/neverallow.go
index 293bac8..ad9880a 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -59,6 +59,7 @@
 	AddNeverAllowRules(createInitFirstStageRules()...)
 	AddNeverAllowRules(createProhibitFrameworkAccessRules()...)
 	AddNeverAllowRules(createBp2BuildRule())
+	AddNeverAllowRules(createCcStubsRule())
 }
 
 // Add a NeverAllow rule to the set of rules to apply.
@@ -214,6 +215,17 @@
 	}
 }
 
+func createCcStubsRule() Rule {
+	ccStubsImplementationInstallableProjectsAllowedList := []string{
+		"packages/modules/Virtualization/vm_payload",
+	}
+
+	return NeverAllow().
+		NotIn(ccStubsImplementationInstallableProjectsAllowedList...).
+		WithMatcher("stubs.implementation_installable", isSetMatcherInstance).
+		Because("implementation_installable can only be used in allowed projects.")
+}
+
 func createUncompressDexRules() []Rule {
 	return []Rule{
 		NeverAllow().
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 4772799..5f5f9a1 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -367,6 +367,22 @@
 			"framework can't be used when building against SDK",
 		},
 	},
+	// Test for the rule restricting use of implementation_installable
+	{
+		name: `"implementation_installable" outside allowed list`,
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+				cc_library {
+					name: "outside_allowed_list",
+					stubs: {
+                                                implementation_installable: true,
+					},
+				}`),
+		},
+		expectedErrors: []string{
+			`module "outside_allowed_list": violates neverallow`,
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
@@ -419,6 +435,10 @@
 	Platform struct {
 		Shared_libs []string
 	}
+
+	Stubs struct {
+		Implementation_installable *bool
+	}
 }
 
 type mockCcLibraryModule struct {
diff --git a/apex/apex.go b/apex/apex.go
index 04808c1..b1b4e47 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2301,7 +2301,7 @@
 				//
 				// Always include if we are a host-apex however since those won't have any
 				// system libraries.
-				if !am.DirectlyInAnyApex() {
+				if ch.IsStubsImplementationRequired() && !am.DirectlyInAnyApex() {
 					// we need a module name for Make
 					name := ch.ImplementationModuleNameForMake(ctx) + ch.Properties.SubName
 					if !android.InList(name, a.requiredDeps) {
diff --git a/bazel/configurability.go b/bazel/configurability.go
index a93aa00..3f4cc73 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -203,6 +203,11 @@
 	osAndInApexMap = map[string]string{
 		AndroidAndInApex:           "//build/bazel/rules/apex:android-in_apex",
 		AndroidAndNonApex:          "//build/bazel/rules/apex:android-non_apex",
+		osDarwin:                   "//build/bazel/platforms/os:darwin",
+		osLinux:                    "//build/bazel/platforms/os:linux",
+		osLinuxMusl:                "//build/bazel/platforms/os:linux_musl",
+		osLinuxBionic:              "//build/bazel/platforms/os:linux_bionic",
+		osWindows:                  "//build/bazel/platforms/os:windows",
 		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey,
 	}
 
diff --git a/bazel/properties.go b/bazel/properties.go
index 823cda8..ee9609a 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -843,6 +843,26 @@
 // 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() {
+	// If there are OsAndInApexAxis, we need to use
+	//   * includes from the OS & in APEX Axis for non-Android configs for libraries that need to be
+	//     included in non-Android OSes
+	//   * excludes from the OS Axis for non-Android configs, to exclude libraries that should _not_
+	//     be included in the non-Android OSes
+	if _, ok := lla.ConfigurableValues[OsAndInApexAxis]; ok {
+		inApexLabels := lla.ConfigurableValues[OsAndInApexAxis][ConditionsDefaultConfigKey]
+		for config, labels := range lla.ConfigurableValues[OsConfigurationAxis] {
+			// OsAndroid has already handled its excludes.
+			// We only need to copy the excludes from other arches, so if there are none, skip it.
+			if config == OsAndroid || len(labels.Excludes) == 0 {
+				continue
+			}
+			lla.ConfigurableValues[OsAndInApexAxis][config] = LabelList{
+				Includes: inApexLabels.Includes,
+				Excludes: labels.Excludes,
+			}
+		}
+	}
+
 	for axis, configToLabels := range lla.ConfigurableValues {
 		baseLabels := lla.Value.deepCopy()
 		for config, val := range configToLabels {
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
index 8aa2c3e..68dc383 100644
--- a/bp2build/cc_binary_conversion_test.go
+++ b/bp2build/cc_binary_conversion_test.go
@@ -789,3 +789,82 @@
 		},
 	})
 }
+
+func TestCcBinaryWithIntegerOverflowProperty(t *testing.T) {
+	runCcBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "cc_binary with integer overflow property specified",
+		blueprint: `
+{rule_name} {
+	name: "foo",
+	sanitize: {
+		integer_overflow: true,
+	},
+}`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", AttrNameToString{
+				"local_includes": `["."]`,
+				"features":       `["ubsan_integer_overflow"]`,
+			}},
+		},
+	})
+}
+
+func TestCcBinaryWithMiscUndefinedProperty(t *testing.T) {
+	runCcBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "cc_binary with miscellaneous properties specified",
+		blueprint: `
+{rule_name} {
+	name: "foo",
+	sanitize: {
+		misc_undefined: ["undefined", "nullability"],
+	},
+}`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", AttrNameToString{
+				"local_includes": `["."]`,
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ]`,
+			}},
+		},
+	})
+}
+
+func TestCcBinaryWithUBSanPropertiesArchSpecific(t *testing.T) {
+	runCcBinaryTestCase(t, ccBinaryBp2buildTestCase{
+		description: "cc_binary has correct feature select when UBSan props are specified in arch specific blocks",
+		blueprint: `
+{rule_name} {
+	name: "foo",
+	sanitize: {
+		misc_undefined: ["undefined", "nullability"],
+	},
+	target: {
+			android: {
+					sanitize: {
+							misc_undefined: ["alignment"],
+					},
+			},
+			linux_glibc: {
+					sanitize: {
+							integer_overflow: true,
+					},
+			},
+	},
+}`,
+		targets: []testBazelTarget{
+			{"cc_binary", "foo", AttrNameToString{
+				"local_includes": `["."]`,
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ] + select({
+        "//build/bazel/platforms/os:android": ["ubsan_alignment"],
+        "//build/bazel/platforms/os:linux": ["ubsan_integer_overflow"],
+        "//conditions:default": [],
+    })`,
+			}},
+		},
+	})
+}
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 4c86374..419baef 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -2976,6 +2976,63 @@
 	})
 }
 
+func TestCcLibraryExcludesLibsHost(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Filesystem: map[string]string{
+			"bar.map.txt": "",
+		},
+		Blueprint: simpleModuleDoNotConvertBp2build("cc_library", "bazlib") + `
+cc_library {
+	name: "quxlib",
+	stubs: { symbol_file: "bar.map.txt", versions: ["current"] },
+	bazel_module: { bp2build_available: false },
+}
+cc_library {
+	name: "barlib",
+	stubs: { symbol_file: "bar.map.txt", versions: ["28", "29", "current"] },
+	bazel_module: { bp2build_available: false },
+}
+cc_library {
+	name: "foolib",
+	shared_libs: ["barlib", "quxlib"],
+	target: {
+		host: {
+			shared_libs: ["bazlib"],
+			exclude_shared_libs: ["barlib"],
+		},
+	},
+	include_build_directory: false,
+	bazel_module: { bp2build_available: true },
+}`,
+		ExpectedBazelTargets: makeCcLibraryTargets("foolib", AttrNameToString{
+			"implementation_dynamic_deps": `select({
+        "//build/bazel/platforms/os:darwin": [":bazlib"],
+        "//build/bazel/platforms/os:linux": [":bazlib"],
+        "//build/bazel/platforms/os:linux_bionic": [":bazlib"],
+        "//build/bazel/platforms/os:linux_musl": [":bazlib"],
+        "//build/bazel/platforms/os:windows": [":bazlib"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:darwin": [":quxlib"],
+        "//build/bazel/platforms/os:linux": [":quxlib"],
+        "//build/bazel/platforms/os:linux_bionic": [":quxlib"],
+        "//build/bazel/platforms/os:linux_musl": [":quxlib"],
+        "//build/bazel/platforms/os:windows": [":quxlib"],
+        "//build/bazel/rules/apex:android-in_apex": [
+            ":barlib_stub_libs_current",
+            ":quxlib_stub_libs_current",
+        ],
+        "//conditions:default": [
+            ":barlib",
+            ":quxlib",
+        ],
+    })`,
+		}),
+	})
+}
+
 func TestCcLibraryEscapeLdflags(t *testing.T) {
 	runCcLibraryTestCase(t, Bp2buildTestCase{
 		ModuleTypeUnderTest:        "cc_library",
@@ -3543,3 +3600,113 @@
 		},
 	})
 }
+
+func TestCcLibraryWithIntegerOverflowProperty(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library has correct features when integer_overflow property is provided",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: `
+cc_library {
+		name: "foo",
+		sanitize: {
+				integer_overflow: true,
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{
+				"features":       `["ubsan_integer_overflow"]`,
+				"local_includes": `["."]`,
+			}),
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features":       `["ubsan_integer_overflow"]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryWithMiscUndefinedProperty(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library has correct features when misc_undefined property is provided",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: `
+cc_library {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ]`,
+				"local_includes": `["."]`,
+			}),
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryWithUBSanPropertiesArchSpecific(t *testing.T) {
+	runCcLibraryTestCase(t, Bp2buildTestCase{
+		Description:                "cc_library has correct feature select when UBSan props are specified in arch specific blocks",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: `
+cc_library {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+		target: {
+				android: {
+						sanitize: {
+								misc_undefined: ["alignment"],
+						},
+				},
+				linux_glibc: {
+						sanitize: {
+								integer_overflow: true,
+						},
+				},
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ] + select({
+        "//build/bazel/platforms/os:android": ["ubsan_alignment"],
+        "//build/bazel/platforms/os:linux": ["ubsan_integer_overflow"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+			}),
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ] + select({
+        "//build/bazel/platforms/os:android": ["ubsan_alignment"],
+        "//build/bazel/platforms/os:linux": ["ubsan_integer_overflow"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/cc_library_shared_conversion_test.go b/bp2build/cc_library_shared_conversion_test.go
index b86f607..8ef7d16 100644
--- a/bp2build/cc_library_shared_conversion_test.go
+++ b/bp2build/cc_library_shared_conversion_test.go
@@ -845,3 +845,85 @@
 		},
 	})
 }
+
+func TestCcLibrarySharedWithIntegerOverflowProperty(t *testing.T) {
+	runCcLibrarySharedTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_shared has correct features when integer_overflow property is provided",
+		Blueprint: `
+cc_library_shared {
+		name: "foo",
+		sanitize: {
+				integer_overflow: true,
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features":       `["ubsan_integer_overflow"]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedWithMiscUndefinedProperty(t *testing.T) {
+	runCcLibrarySharedTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_shared has correct features when misc_undefined property is provided",
+		Blueprint: `
+cc_library_shared {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibrarySharedWithUBSanPropertiesArchSpecific(t *testing.T) {
+	runCcLibrarySharedTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_shared has correct feature select when UBSan props are specified in arch specific blocks",
+		Blueprint: `
+cc_library_shared {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+		target: {
+				android: {
+						sanitize: {
+								misc_undefined: ["alignment"],
+						},
+				},
+				linux_glibc: {
+						sanitize: {
+								integer_overflow: true,
+						},
+				},
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ] + select({
+        "//build/bazel/platforms/os:android": ["ubsan_alignment"],
+        "//build/bazel/platforms/os:linux": ["ubsan_integer_overflow"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index b47d1f1..cac7f9b 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -1723,3 +1723,85 @@
 		},
 	})
 }
+
+func TestCcLibraryStaticWithIntegerOverflowProperty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_static has correct features when integer_overflow property is provided",
+		Blueprint: `
+cc_library_static {
+		name: "foo",
+		sanitize: {
+				integer_overflow: true,
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
+				"features":       `["ubsan_integer_overflow"]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticWithMiscUndefinedProperty(t *testing.T) {
+	runCcLibraryStaticTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_static has correct features when misc_undefined property is provided",
+		Blueprint: `
+cc_library_static {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ]`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
+
+func TestCcLibraryStaticWithUBSanPropertiesArchSpecific(t *testing.T) {
+	runCcLibraryStaticTestCase(t, Bp2buildTestCase{
+		Description: "cc_library_static has correct feature select when UBSan props are specified in arch specific blocks",
+		Blueprint: `
+cc_library_static {
+		name: "foo",
+		sanitize: {
+				misc_undefined: ["undefined", "nullability"],
+		},
+		target: {
+				android: {
+						sanitize: {
+								misc_undefined: ["alignment"],
+						},
+				},
+				linux_glibc: {
+						sanitize: {
+								integer_overflow: true,
+						},
+				},
+		},
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
+				"features": `[
+        "ubsan_undefined",
+        "ubsan_nullability",
+    ] + select({
+        "//build/bazel/platforms/os:android": ["ubsan_alignment"],
+        "//build/bazel/platforms/os:linux": ["ubsan_integer_overflow"],
+        "//conditions:default": [],
+    })`,
+				"local_includes": `["."]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 3750804..0f1a8b2 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -90,6 +90,7 @@
 }
 
 func RunBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
+	t.Helper()
 	bp2buildSetup := func(ctx *android.TestContext) {
 		registerModuleTypes(ctx)
 		ctx.RegisterForBazelConversion()
@@ -98,6 +99,7 @@
 }
 
 func RunApiBp2BuildTestCase(t *testing.T, registerModuleTypes func(ctx android.RegistrationContext), tc Bp2buildTestCase) {
+	t.Helper()
 	apiBp2BuildSetup := func(ctx *android.TestContext) {
 		registerModuleTypes(ctx)
 		ctx.RegisterForApiBazelConversion()
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 6caa854..d41aa00 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -341,7 +341,7 @@
 	compilerAttributes
 	linkerAttributes
 
-	// A combination of compilerAttributes.features and linkerAttributes.features
+	// A combination of compilerAttributes.features and linkerAttributes.features, as well as sanitizer features
 	features        bazel.StringListAttribute
 	protoDependency *bazel.LabelAttribute
 	aidlDependency  *bazel.LabelAttribute
@@ -781,7 +781,7 @@
 		(&linkerAttrs).wholeArchiveDeps.Add(bp2buildCcSysprop(ctx, module.Name(), module.Properties.Min_sdk_version, compilerAttrs.syspropSrcs))
 	}
 
-	features := compilerAttrs.features.Clone().Append(linkerAttrs.features)
+	features := compilerAttrs.features.Clone().Append(linkerAttrs.features).Append(bp2buildSanitizerFeatures(ctx, module))
 	features.DeduplicateAxesFromBase()
 
 	return baseAttributes{
@@ -1364,3 +1364,20 @@
 
 	return attrs
 }
+
+func bp2buildSanitizerFeatures(ctx android.BazelConversionPathContext, m *Module) bazel.StringListAttribute {
+	sanitizerFeatures := bazel.StringListAttribute{}
+	bp2BuildPropParseHelper(ctx, m, &SanitizeProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		var features []string
+		if sanitizerProps, ok := props.(*SanitizeProperties); ok {
+			if sanitizerProps.Sanitize.Integer_overflow != nil && *sanitizerProps.Sanitize.Integer_overflow {
+				features = append(features, "ubsan_integer_overflow")
+			}
+			for _, sanitizer := range sanitizerProps.Sanitize.Misc_undefined {
+				features = append(features, "ubsan_"+sanitizer)
+			}
+			sanitizerFeatures.SetSelectValue(axis, config, features)
+		}
+	})
+	return sanitizerFeatures
+}
diff --git a/cc/cc.go b/cc/cc.go
index 306e483..8b3f456 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1205,6 +1205,8 @@
 	return c
 }
 
+// UseVndk() returns true if this module is built against VNDK.
+// This means the vendor and product variants of a module.
 func (c *Module) UseVndk() bool {
 	return c.Properties.VndkVersion != ""
 }
@@ -1298,6 +1300,9 @@
 	return false
 }
 
+// IsVndk() returns true if this module has a vndk variant.
+// Note that IsVndk() returns true for all variants of vndk-enabled libraries. Not only vendor variant,
+// but also platform and product variants of vndk-enabled libraries return true for IsVndk().
 func (c *Module) IsVndk() bool {
 	if vndkdep := c.vndkdep; vndkdep != nil {
 		return vndkdep.isVndk()
@@ -1376,6 +1381,13 @@
 	return false
 }
 
+func (c *Module) IsStubsImplementationRequired() bool {
+	if lib := c.library; lib != nil {
+		return lib.isStubsImplementationRequired()
+	}
+	return false
+}
+
 // If this is a stubs library, ImplementationModuleName returns the name of the module that contains
 // the implementation.  If it is an implementation library it returns its own name.
 func (c *Module) ImplementationModuleName(ctx android.BaseModuleContext) string {
diff --git a/cc/library.go b/cc/library.go
index b639930..0729ff4 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -71,6 +71,12 @@
 		// List versions to generate stubs libs for. The version name "current" is always
 		// implicitly added.
 		Versions []string
+
+		// Whether to not require the implementation of the library to be installed if a
+		// client of the stubs is installed. Defaults to true; set to false if the
+		// implementation is made available by some other means, e.g. in a Microdroid
+		// virtual machine.
+		Implementation_installable *bool
 	}
 
 	// set the name of the output
@@ -1339,6 +1345,7 @@
 	buildStubs() bool
 	setBuildStubs(isLatest bool)
 	hasStubsVariants() bool
+	isStubsImplementationRequired() bool
 	setStubsVersion(string)
 	stubsVersion() string
 
@@ -2298,6 +2305,10 @@
 		len(library.Properties.Stubs.Versions) > 0
 }
 
+func (library *libraryDecorator) isStubsImplementationRequired() bool {
+	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
+}
+
 func (library *libraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
 	if !library.hasStubsVariants() {
 		return nil
diff --git a/cmd/extract_apks/main.go b/cmd/extract_apks/main.go
index c420567..82db634 100644
--- a/cmd/extract_apks/main.go
+++ b/cmd/extract_apks/main.go
@@ -132,21 +132,21 @@
 	*android_bundle_proto.ApkDescription
 }
 
-func (m apkDescriptionMatcher) matches(config TargetConfig) bool {
-	return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config)
+func (m apkDescriptionMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
+	return m.ApkDescription == nil || (apkTargetingMatcher{m.Targeting}).matches(config, allAbisMustMatch)
 }
 
 type apkTargetingMatcher struct {
 	*android_bundle_proto.ApkTargeting
 }
 
-func (m apkTargetingMatcher) matches(config TargetConfig) bool {
+func (m apkTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
 	return m.ApkTargeting == nil ||
 		(abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
 			languageTargetingMatcher{m.LanguageTargeting}.matches(config) &&
 			screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
 			sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
-			multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config))
+			multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch))
 }
 
 type languageTargetingMatcher struct {
@@ -215,33 +215,27 @@
 		}
 	}
 
-	m = append(multiAbiValue{}, m...)
-	sort.Slice(m, sortAbis(m))
-	other = append(multiAbiValue{}, other...)
-	sort.Slice(other, sortAbis(other))
+	sortedM := append(multiAbiValue{}, m...)
+	sort.Slice(sortedM, sortAbis(sortedM))
+	sortedOther := append(multiAbiValue{}, other...)
+	sort.Slice(sortedOther, sortAbis(sortedOther))
 
-	for i := 0; i < min(len(m), len(other)); i++ {
-		if multiAbiPriorities[m[i].Alias] > multiAbiPriorities[other[i].Alias] {
+	for i := 0; i < min(len(sortedM), len(sortedOther)); i++ {
+		if multiAbiPriorities[sortedM[i].Alias] > multiAbiPriorities[sortedOther[i].Alias] {
 			return 1
 		}
-		if multiAbiPriorities[m[i].Alias] < multiAbiPriorities[other[i].Alias] {
+		if multiAbiPriorities[sortedM[i].Alias] < multiAbiPriorities[sortedOther[i].Alias] {
 			return -1
 		}
 	}
 
-	if len(m) == len(other) {
-		return 0
-	}
-	if len(m) > len(other) {
-		return 1
-	}
-	return -1
+	return len(sortedM) - len(sortedOther)
 }
 
 // this logic should match the logic in bundletool at
 // https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
 // (note link is the commit at time of writing; but logic should always match the latest)
-func (t multiAbiTargetingMatcher) matches(config TargetConfig) bool {
+func (t multiAbiTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
 	if t.MultiAbiTargeting == nil {
 		return true
 	}
@@ -250,12 +244,19 @@
 	}
 
 	multiAbiIsValid := func(m multiAbiValue) bool {
+		numValid := 0
 		for _, abi := range m {
-			if _, ok := config.abis[abi.Alias]; !ok {
-				return false
+			if _, ok := config.abis[abi.Alias]; ok {
+				numValid += 1
 			}
 		}
-		return true
+		if numValid == 0 {
+			return false
+		} else if numValid > 0 && !allAbisMustMatch {
+			return true
+		} else {
+			return numValid == len(m)
+		}
 	}
 
 	// ensure that the current value is valid for our config
@@ -264,6 +265,7 @@
 	for _, multiAbi := range multiAbiSet {
 		if multiAbiIsValid(multiAbi.GetAbi()) {
 			valueSetContainsViableAbi = true
+			break
 		}
 	}
 
@@ -362,13 +364,13 @@
 	*android_bundle_proto.VariantTargeting
 }
 
-func (m variantTargetingMatcher) matches(config TargetConfig) bool {
+func (m variantTargetingMatcher) matches(config TargetConfig, allAbisMustMatch bool) bool {
 	if m.VariantTargeting == nil {
 		return true
 	}
 	return sdkVersionTargetingMatcher{m.SdkVersionTargeting}.matches(config) &&
 		abiTargetingMatcher{m.AbiTargeting}.matches(config) &&
-		multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config) &&
+		multiAbiTargetingMatcher{m.MultiAbiTargeting}.matches(config, allAbisMustMatch) &&
 		screenDensityTargetingMatcher{m.ScreenDensityTargeting}.matches(config) &&
 		textureCompressionFormatTargetingMatcher{m.TextureCompressionFormatTargeting}.matches(config)
 }
@@ -380,30 +382,42 @@
 
 // Return all entries matching target configuration
 func selectApks(toc Toc, targetConfig TargetConfig) SelectionResult {
-	var result SelectionResult
-	for _, variant := range (*toc).GetVariant() {
-		if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig)) {
-			continue
-		}
-		for _, as := range variant.GetApkSet() {
-			if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
+	checkMatching := func(allAbisMustMatch bool) SelectionResult {
+		var result SelectionResult
+		for _, variant := range (*toc).GetVariant() {
+			if !(variantTargetingMatcher{variant.GetTargeting()}.matches(targetConfig, allAbisMustMatch)) {
 				continue
 			}
-			for _, apkdesc := range as.GetApkDescription() {
-				if (apkDescriptionMatcher{apkdesc}).matches(targetConfig) {
-					result.entries = append(result.entries, apkdesc.GetPath())
-					// TODO(asmundak): As it turns out, moduleName which we get from
-					// the ModuleMetadata matches the module names of the generated
-					// entry paths just by coincidence, only for the split APKs. We
-					// need to discuss this with bundletool folks.
-					result.moduleName = as.GetModuleMetadata().GetName()
+			for _, as := range variant.GetApkSet() {
+				if !(moduleMetadataMatcher{as.ModuleMetadata}.matches(targetConfig)) {
+					continue
+				}
+				for _, apkdesc := range as.GetApkDescription() {
+					if (apkDescriptionMatcher{apkdesc}).matches(targetConfig, allAbisMustMatch) {
+						result.entries = append(result.entries, apkdesc.GetPath())
+						// TODO(asmundak): As it turns out, moduleName which we get from
+						// the ModuleMetadata matches the module names of the generated
+						// entry paths just by coincidence, only for the split APKs. We
+						// need to discuss this with bundletool folks.
+						result.moduleName = as.GetModuleMetadata().GetName()
+					}
+				}
+				// we allow only a single module, so bail out here if we found one
+				if result.moduleName != "" {
+					return result
 				}
 			}
-			// we allow only a single module, so bail out here if we found one
-			if result.moduleName != "" {
-				return result
-			}
 		}
+		return result
+	}
+	result := checkMatching(true)
+	if result.moduleName == "" {
+		// if there are no matches where all of the ABIs are available in the
+		// TargetConfig, then search again with a looser requirement of at
+		// least one matching ABI
+		// NOTE(b/260130686): this logic diverges from the logic in bundletool
+		// https://github.com/google/bundletool/blob/ae0fc0162fd80d92ef8f4ef4527c066f0106942f/src/main/java/com/android/tools/build/bundletool/device/MultiAbiMatcher.java#L43
+		result = checkMatching(false)
 	}
 	return result
 }
diff --git a/cmd/extract_apks/main_test.go b/cmd/extract_apks/main_test.go
index c1d712d..9f52877 100644
--- a/cmd/extract_apks/main_test.go
+++ b/cmd/extract_apks/main_test.go
@@ -744,7 +744,11 @@
 							bp.Abi_X86_64: 0,
 						},
 					},
-					expected: SelectionResult{},
+					expected: SelectionResult{
+						"base",
+						[]string{
+							"standalones/standalone-x86.x86_64.apex",
+						}},
 				},
 				{
 					name: "multi-variant multi-target cross-target",
diff --git a/java/fuzz_test.go b/java/fuzz_test.go
index 6e5d912..186c3aa 100644
--- a/java/fuzz_test.go
+++ b/java/fuzz_test.go
@@ -88,7 +88,7 @@
 
 	expected := "lib64/libjni.so"
 	if runtime.GOOS == "darwin" {
-		expected = "libjni.dylib"
+		expected = "lib64/libjni.dylib"
 	}
 
 	fooJniFilePaths := foo.jniFilePaths