Merge "Update min_sdk_version used in testNoUpdatableJarsInBootImage"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index ff0d33e..892e218 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -132,6 +132,7 @@
 		"external/f2fs-tools":                    Bp2BuildDefaultTrue,
 		"external/flac":                          Bp2BuildDefaultTrueRecursively,
 		"external/fmtlib":                        Bp2BuildDefaultTrueRecursively,
+		"external/guava":                         Bp2BuildDefaultTrueRecursively,
 		"external/google-benchmark":              Bp2BuildDefaultTrueRecursively,
 		"external/googletest":                    Bp2BuildDefaultTrueRecursively,
 		"external/gwp_asan":                      Bp2BuildDefaultTrueRecursively,
@@ -364,6 +365,8 @@
 		"system/testing/gtest_extras":                            Bp2BuildDefaultTrueRecursively,
 		"system/timezone/apex":                                   Bp2BuildDefaultTrueRecursively,
 		"system/timezone/output_data":                            Bp2BuildDefaultTrueRecursively,
+		"system/timezone/testdata":                               Bp2BuildDefaultTrueRecursively,
+		"system/timezone/testing":                                Bp2BuildDefaultTrueRecursively,
 		"system/tools/aidl/build/tests_bp2build":                 Bp2BuildDefaultTrue,
 		"system/tools/aidl/metadata":                             Bp2BuildDefaultTrue,
 		"system/tools/hidl/metadata":                             Bp2BuildDefaultTrue,
@@ -372,8 +375,9 @@
 		"system/tools/xsdc/utils":                                Bp2BuildDefaultTrueRecursively,
 		"system/unwinding/libunwindstack":                        Bp2BuildDefaultTrueRecursively,
 
-		"tools/apksig":   Bp2BuildDefaultTrue,
-		"tools/metalava": Bp2BuildDefaultTrue,
+		"tools/apksig":                               Bp2BuildDefaultTrue,
+		"tools/external_updater":                     Bp2BuildDefaultTrueRecursively,
+		"tools/metalava":                             Bp2BuildDefaultTrue,
 		"tools/platform-compat/java/android/compat":  Bp2BuildDefaultTrueRecursively,
 		"tools/tradefederation/prebuilts/filegroups": Bp2BuildDefaultTrueRecursively,
 	}
@@ -385,6 +389,7 @@
 		"build/bazel":/* recursive = */ true,
 		"build/make/core":/* recursive = */ false,
 		"build/bazel_common_rules":/* recursive = */ true,
+		"build/make/target/product/security":/* recursive = */ false,
 		// build/make/tools/signapk BUILD file is generated, so build/make/tools is not recursive.
 		"build/make/tools":/* recursive = */ false,
 		"build/pesto":/* recursive = */ true,
@@ -396,7 +401,6 @@
 		"external/bazelbuild-rules_license":/* recursive = */ true,
 		"external/bazelbuild-kotlin-rules":/* recursive = */ true,
 		"external/bazel-skylib":/* recursive = */ true,
-		"external/guava":/* recursive = */ true,
 		"external/protobuf":/* recursive = */ false,
 		"external/python/absl-py":/* recursive = */ true,
 
@@ -405,8 +409,6 @@
 		// this BUILD file is globbed by //external/icu/icu4c/source:icu4c_test_data's "data/**/*".
 		"external/icu/icu4c/source/data/unidata/norm2":/* recursive = */ false,
 
-		"frameworks/ex/common":/* recursive = */ true,
-
 		// Building manually due to b/179889880: resource files cross package boundary
 		"packages/apps/Music":/* recursive = */ true,
 
@@ -472,6 +474,7 @@
 		"libgralloctypes",
 		"libnativewindow",
 		"libneuralnetworks",
+		"libneuralnetworks_static",
 		"libgraphicsenv",
 		"libhardware",
 		"libhardware_headers",
@@ -686,7 +689,6 @@
 		"libcodec2_soft_common",
 
 		// kotlin srcs in java libs
-		"CtsPkgInstallerConstants",
 		"kotlinx_atomicfu",
 
 		// kotlin srcs in java binary
@@ -699,6 +701,9 @@
 		//kotlin srcs in android_binary
 		"MusicKotlin",
 
+		// java_library with prebuilt sdk_version
+		"android-common",
+
 		// checked in current.txt for merged_txts
 		"non-updatable-current.txt",
 		"non-updatable-system-current.txt",
@@ -710,6 +715,8 @@
 
 		// allowlisting for kotlinx_coroutines
 		"kotlinx_coroutines",
+		"kotlinx_coroutines-device",
+		"kotlinx_coroutines-host",
 		"annotations",
 		"kotlinx-coroutines-android-annotation-stubs",
 
@@ -719,7 +726,6 @@
 
 		// min_sdk_version in android_app
 		"CtsShimUpgrade",
-		"fake-framework",
 	}
 
 	Bp2buildModuleTypeAlwaysConvertList = []string{
@@ -773,7 +779,8 @@
 		"tjbench", // TODO(b/240563612): Stem property
 
 		// java bugs
-		"libbase_ndk", // TODO(b/186826477): fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
+		"libbase_ndk",  // TODO(b/186826477): fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
+		"bouncycastle", // TODO(b/274474005): Need support for custom system_modules.
 
 		// python protos
 		"libprotobuf-python", // Has a handcrafted alternative
@@ -840,7 +847,6 @@
 		"libgmock_ndk",                                            // depends on unconverted modules: libgtest_ndk_c++
 		"libnativehelper_lazy_mts_jni", "libnativehelper_mts_jni", // depends on unconverted modules: libnativetesthelper_jni, libgmock_ndk
 		"libnativetesthelper_jni",   // depends on unconverted modules: libgtest_ndk_c++
-		"libprotobuf-java-nano",     // b/220869005, depends on non-public_current SDK
 		"libstatslog",               // depends on unconverted modules: libstatspull, statsd-aidl-ndk
 		"libstatslog_art",           // depends on unconverted modules: statslog_art.cpp, statslog_art.h
 		"linker_reloc_bench_main",   // depends on unconverted modules: liblinker_reloc_bench_*
@@ -1390,6 +1396,16 @@
 
 		// TODO(b/266459895): depends on libunwindstack
 		"libutils_test",
+
+		// TODO(b/274805756): Support core_platform and current java APIs
+		"fake-framework",
+
+		// TODO(b/277616982): These modules depend on private java APIs, but maybe they don't need to.
+		"StreamingProtoTest",
+		"textclassifierprotoslite",
+		"styleprotoslite",
+		"CtsPkgInstallerConstants",
+		"guava-android-testlib",
 	}
 
 	MixedBuildsDisabledList = []string{
@@ -1468,6 +1484,7 @@
 		// M5: tzdata launch
 		"com.android.tzdata",
 		"test1_com.android.tzdata",
+		"test3_com.android.tzdata",
 		// M7: adbd launch
 		"com.android.adbd",
 		"test_com.android.adbd",
@@ -1487,6 +1504,8 @@
 	// also be built - do not add them to this list.
 	StagingMixedBuildsEnabledList = []string{
 		"com.android.neuralnetworks",
+		"libneuralnetworks",
+		"libneuralnetworks_static",
 	}
 
 	// These should be the libs that are included by the apexes in the ProdMixedBuildsEnabledList
diff --git a/android/api_levels.go b/android/api_levels.go
index ea2afdf..7214ccb 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -344,14 +344,17 @@
 		}
 	}
 
-	canonical := ReplaceFinalizedCodenames(config, raw)
-	asInt, err := strconv.Atoi(canonical)
-	if err != nil {
-		return NoneApiLevel, fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", canonical)
+	canonical, ok := getApiLevelsMapReleasedVersions()[raw]
+	if !ok {
+		asInt, err := strconv.Atoi(raw)
+		if err != nil {
+			return NoneApiLevel, fmt.Errorf("%q could not be parsed as an integer and is not a recognized codename", raw)
+		}
+		return uncheckedFinalApiLevel(asInt), nil
 	}
 
-	apiLevel := uncheckedFinalApiLevel(asInt)
-	return apiLevel, nil
+	return uncheckedFinalApiLevel(canonical), nil
+
 }
 
 // ApiLevelForTest returns an ApiLevel constructed from the supplied raw string.
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 44dc055..5c429e2 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -960,9 +960,13 @@
 // request type.
 func (context *mixedBuildBazelContext) cqueryStarlarkFileContents() []byte {
 	requestTypeToCqueryIdEntries := map[cqueryRequest][]string{}
+	requestTypes := []cqueryRequest{}
 	for _, val := range context.requests {
 		cqueryId := getCqueryId(val)
 		mapEntryString := fmt.Sprintf("%q : True", cqueryId)
+		if _, seenKey := requestTypeToCqueryIdEntries[val.requestType]; !seenKey {
+			requestTypes = append(requestTypes, val.requestType)
+		}
 		requestTypeToCqueryIdEntries[val.requestType] =
 			append(requestTypeToCqueryIdEntries[val.requestType], mapEntryString)
 	}
@@ -984,7 +988,7 @@
     return id_string + ">>" + %s(target, id_string)
 `
 
-	for requestType := range requestTypeToCqueryIdEntries {
+	for _, requestType := range requestTypes {
 		labelMapName := requestType.Name() + "_Labels"
 		functionName := requestType.Name() + "_Fn"
 		labelRegistrationMapSection += fmt.Sprintf(mapDeclarationFormatString,
diff --git a/android/config.go b/android/config.go
index 33deba5..2904581 100644
--- a/android/config.go
+++ b/android/config.go
@@ -420,6 +420,8 @@
 		fmt.Sprintf(`_arch_variant_product_var_constraints = %s`, archVariantProductVariablesJson),
 		"\n", `
 product_vars = _product_vars
+
+# TODO(b/269577299) Remove these when everything switches over to loading them from product_variable_constants.bzl
 product_var_constraints = _product_var_constraints
 arch_variant_product_var_constraints = _arch_variant_product_var_constraints
 `,
@@ -429,6 +431,13 @@
 	if err != nil {
 		return fmt.Errorf("Could not write .bzl config file %s", err)
 	}
+	err = pathtools.WriteFileIfChanged(filepath.Join(dir, "product_variable_constants.bzl"), []byte(fmt.Sprintf(`
+product_var_constraints = %s
+arch_variant_product_var_constraints = %s
+`, nonArchVariantProductVariablesJson, archVariantProductVariablesJson)), 0644)
+	if err != nil {
+		return fmt.Errorf("Could not write .bzl config file %s", err)
+	}
 	err = pathtools.WriteFileIfChanged(filepath.Join(dir, "BUILD"),
 		[]byte(bazel.GeneratedBazelFileWarning), 0644)
 	if err != nil {
@@ -824,6 +833,10 @@
 	return uncheckedFinalApiLevel(*c.productVariables.Platform_sdk_version)
 }
 
+func (c *config) RawPlatformSdkVersion() *int {
+	return c.productVariables.Platform_sdk_version
+}
+
 func (c *config) PlatformSdkFinal() bool {
 	return Bool(c.productVariables.Platform_sdk_final)
 }
@@ -913,8 +926,16 @@
 		return c.PlatformSdkVersion()
 	}
 	codename := c.PlatformSdkCodename()
+	hostOnlyBuild := c.productVariables.DeviceArch == nil
 	if codename == "" {
-		return NoneApiLevel
+		// There are some host-only builds (those are invoked by build-prebuilts.sh) which
+		// don't set platform sdk codename. Platform sdk codename makes sense only when we
+		// are building the platform. So we don't enforce the below panic for the host-only
+		// builds.
+		if hostOnlyBuild {
+			return NoneApiLevel
+		}
+		panic("Platform_sdk_codename must be set")
 	}
 	if codename == "REL" {
 		panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
@@ -1418,6 +1439,21 @@
 	return c.config.productVariables.PgoAdditionalProfileDirs
 }
 
+// AfdoProfile returns fully qualified path associated to the given module name
+func (c *deviceConfig) AfdoProfile(name string) (*string, error) {
+	for _, afdoProfile := range c.config.productVariables.AfdoProfiles {
+		split := strings.Split(afdoProfile, ":")
+		if len(split) != 3 {
+			return nil, fmt.Errorf("AFDO_PROFILES has invalid value: %s. "+
+				"The expected format is <module>:<fully-qualified-path-to-fdo_profile>", afdoProfile)
+		}
+		if split[0] == name {
+			return proptools.StringPtr(strings.Join([]string{split[1], split[2]}, ":")), nil
+		}
+	}
+	return nil, nil
+}
+
 func (c *deviceConfig) VendorSepolicyDirs() []string {
 	return c.config.productVariables.BoardVendorSepolicyDirs
 }
@@ -1894,3 +1930,7 @@
 func (c *config) BuildFromTextStub() bool {
 	return c.buildFromTextStub
 }
+
+func (c *config) SetBuildFromTextStub(b bool) {
+	c.buildFromTextStub = b
+}
diff --git a/android/filegroup.go b/android/filegroup.go
index 38de855..c259f21 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -126,7 +126,7 @@
 
 		props := bazel.BazelTargetModuleProperties{
 			Rule_class:        "aidl_library",
-			Bzl_load_location: "//build/bazel/rules/aidl:library.bzl",
+			Bzl_load_location: "//build/bazel/rules/aidl:aidl_library.bzl",
 		}
 
 		ctx.CreateBazelTargetModule(
diff --git a/android/module.go b/android/module.go
index 76fe8dc..ba47453 100644
--- a/android/module.go
+++ b/android/module.go
@@ -550,6 +550,7 @@
 	ExportedToMake() bool
 	InitRc() Paths
 	VintfFragments() Paths
+	EffectiveLicenseKinds() []string
 	EffectiveLicenseFiles() Paths
 
 	AddProperties(props ...interface{})
@@ -2024,6 +2025,10 @@
 	return m.commonProperties.NamespaceExportedToMake
 }
 
+func (m *ModuleBase) EffectiveLicenseKinds() []string {
+	return m.commonProperties.Effective_license_kinds
+}
+
 func (m *ModuleBase) EffectiveLicenseFiles() Paths {
 	result := make(Paths, 0, len(m.commonProperties.Effective_license_text))
 	for _, p := range m.commonProperties.Effective_license_text {
diff --git a/android/namespace.go b/android/namespace.go
index c47a1c5..ebf85a1 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -325,8 +325,8 @@
 	namespace.id = strconv.Itoa(id)
 }
 
-func (r *NameResolver) MissingDependencyError(depender string, dependerNamespace blueprint.Namespace, depName string) (err error) {
-	text := fmt.Sprintf("%q depends on undefined module %q", depender, depName)
+func (r *NameResolver) MissingDependencyError(depender string, dependerNamespace blueprint.Namespace, depName string, guess []string) (err error) {
+	text := fmt.Sprintf("%q depends on undefined module %q.", depender, depName)
 
 	_, _, isAbs := r.parseFullyQualifiedName(depName)
 	if isAbs {
@@ -345,9 +345,10 @@
 		}
 		_, skipped := namespace.moduleContainer.SkippedModuleFromName(depName, nil)
 		if skipped {
-			skippedDepErrors = append(skippedDepErrors, namespace.moduleContainer.MissingDependencyError(depender, dependerNamespace, depName))
+			skippedDepErrors = append(skippedDepErrors, namespace.moduleContainer.MissingDependencyError(depender, dependerNamespace, depName, nil))
 		}
 	}
+
 	if len(foundInNamespaces) > 0 {
 		// determine which namespaces are visible to dependerNamespace
 		dependerNs := dependerNamespace.(*Namespace)
@@ -363,6 +364,10 @@
 		text += fmt.Sprintf("\n%s", err.Error())
 	}
 
+	if len(guess) > 0 {
+		text += fmt.Sprintf("\nOr did you mean %q?", guess)
+	}
+
 	return fmt.Errorf(text)
 }
 
diff --git a/android/namespace_test.go b/android/namespace_test.go
index 7a387a0..ea51c6e 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -174,9 +174,10 @@
 			`,
 		}),
 	).
-		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:4:5: "b" depends on undefined module "a"
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:4:5: "b" depends on undefined module "a".
 Module "b" is defined in namespace "dir3" which can read these 2 namespaces: ["dir3" "."]
-Module "a" can be found in these namespaces: ["dir1" "dir2"]\E`)).
+Module "a" can be found in these namespaces: ["dir1" "dir2"]\E
+Or did you mean ["soong_namespace"]?`)).
 		RunTest(t)
 }
 
@@ -421,9 +422,10 @@
 			`,
 		}),
 	).
-		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/subdir1/Android.bp:4:5: "b" depends on undefined module "a"
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir1/subdir1/Android.bp:4:5: "b" depends on undefined module "a".
 Module "b" is defined in namespace "dir1/subdir1" which can read these 2 namespaces: ["dir1/subdir1" "."]
-Module "a" can be found in these namespaces: ["dir1"]\E`)).
+Module "a" can be found in these namespaces: ["dir1"]\E
+Or did you mean ["soong_namespace"]?`)).
 		RunTest(t)
 }
 
@@ -481,9 +483,10 @@
 			`,
 		}),
 	).
-		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:5:5: "c" depends on undefined module "a"
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`\Qdir3/Android.bp:5:5: "c" depends on undefined module "a".
 Module "c" is defined in namespace "dir3" which can read these 3 namespaces: ["dir3" "dir2" "."]
-Module "a" can be found in these namespaces: ["dir1"]\E`)).
+Module "a" can be found in these namespaces: ["dir1"]\E
+Or did you mean ["b"]?`)).
 		RunTest(t)
 }
 
diff --git a/android/packaging.go b/android/packaging.go
index 4a9b591..c764a6d 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -238,11 +238,11 @@
 
 // CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into the specified directory.
-func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir ModuleOutPath) (entries []string) {
+func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
 	seenDir := make(map[string]bool)
 	for _, k := range SortedKeys(specs) {
 		ps := specs[k]
-		destPath := dir.Join(ctx, ps.relPathInPackage).String()
+		destPath := filepath.Join(dir.String(), ps.relPathInPackage)
 		destDir := filepath.Dir(destPath)
 		entries = append(entries, ps.relPathInPackage)
 		if _, ok := seenDir[destDir]; !ok {
diff --git a/android/sdk_version.go b/android/sdk_version.go
index 1f01dc6..0ae8073 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -86,7 +86,7 @@
 
 // JavaLibraryName returns the soong module containing the Java APIs of that API surface.
 func (k SdkKind) JavaLibraryName(c Config) string {
-	name := k.defaultJavaLibraryName()
+	name := k.DefaultJavaLibraryName()
 	return JavaApiLibraryName(c, name)
 }
 
@@ -100,7 +100,7 @@
 	return name
 }
 
-func (k SdkKind) defaultJavaLibraryName() string {
+func (k SdkKind) DefaultJavaLibraryName() string {
 	switch k {
 	case SdkPublic:
 		return "android_stubs_current"
diff --git a/android/variable.go b/android/variable.go
index 1da5974..249d53b 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -464,6 +464,8 @@
 
 	IncludeTags    []string `json:",omitempty"`
 	SourceRootDirs []string `json:",omitempty"`
+
+	AfdoProfiles []string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/android/visibility.go b/android/visibility.go
index 5955133..3130135 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -47,7 +47,6 @@
 //   the dependency. If it cannot then an error is reported.
 //
 // TODO(b/130631145) - Make visibility work properly with prebuilts.
-// TODO(b/130796911) - Make visibility work properly with defaults.
 
 // Patterns for the values that can be specified in visibility property.
 const (
diff --git a/apex/apex.go b/apex/apex.go
index 3678636..5451a04 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1986,9 +1986,9 @@
 	// Set the output file to .apex or .capex depending on the compression configuration.
 	a.setCompression(ctx)
 	if a.isCompressed {
-		a.outputApexFile = android.PathForBazelOut(ctx, outputs.SignedCompressedOutput)
+		a.outputApexFile = android.PathForBazelOutRelative(ctx, ctx.ModuleDir(), outputs.SignedCompressedOutput)
 	} else {
-		a.outputApexFile = android.PathForBazelOut(ctx, outputs.SignedOutput)
+		a.outputApexFile = android.PathForBazelOutRelative(ctx, ctx.ModuleDir(), outputs.SignedOutput)
 	}
 	a.outputFile = a.outputApexFile
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index d33d96c..557265c 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -1001,7 +1001,7 @@
 	// Ensure that stub dependency from a rust module is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
 	// The rust module is linked to the stub cc library
-	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustLink").Args["linkFlags"]
 	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
 	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
 
@@ -1077,7 +1077,7 @@
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
-	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustLink").Args["linkFlags"]
 	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
 	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
 }
diff --git a/apex/builder.go b/apex/builder.go
index e3b6f8e..94aef49 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -71,6 +71,9 @@
 	pctx.HostBinToolVariable("make_erofs", "make_erofs")
 	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
 	pctx.HostBinToolVariable("dexdeps", "dexdeps")
+	pctx.HostBinToolVariable("apex_sepolicy_tests", "apex_sepolicy_tests")
+	pctx.HostBinToolVariable("deapexer", "deapexer")
+	pctx.HostBinToolVariable("debugfs_static", "debugfs_static")
 	pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
 }
 
@@ -226,7 +229,12 @@
 		Description: "Generate symbol list used by Apex",
 	}, "image_dir", "readelf")
 
-	// Don't add more rules here. Consider using android.NewRuleBuilder instead.
+	apexSepolicyTestsRule = pctx.StaticRule("apexSepolicyTestsRule", blueprint.RuleParams{
+		Command: `${deapexer} --debugfs_path ${debugfs_static} list -Z ${in} > ${out}.fc` +
+			`&& ${apex_sepolicy_tests} -f ${out}.fc && touch ${out}`,
+		CommandDeps: []string{"${apex_sepolicy_tests}", "${deapexer}", "${debugfs_static}"},
+		Description: "run apex_sepolicy_tests",
+	})
 )
 
 // buildManifest creates buile rules to modify the input apex_manifest.json to add information
@@ -872,6 +880,10 @@
 		args["implicits"] = strings.Join(implicits.Strings(), ",")
 		args["outCommaList"] = signedOutputFile.String()
 	}
+	var validations android.Paths
+	if suffix == imageApexSuffix {
+		validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile.OutputPath))
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
 		Description: "signapk",
@@ -879,6 +891,7 @@
 		Input:       unsignedOutputFile,
 		Implicits:   implicits,
 		Args:        args,
+		Validations: validations,
 	})
 	if suffix == imageApexSuffix {
 		a.outputApexFile = signedOutputFile
@@ -1172,3 +1185,17 @@
 
 	return cannedFsConfig.OutputPath
 }
+
+// Runs apex_sepolicy_tests
+//
+// $ deapexer list -Z {apex_file} > {file_contexts}
+// $ apex_sepolicy_tests -f {file_contexts}
+func runApexSepolicyTests(ctx android.ModuleContext, apexFile android.OutputPath) android.Path {
+	timestamp := android.PathForModuleOut(ctx, "sepolicy_tests.timestamp")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   apexSepolicyTestsRule,
+		Input:  apexFile,
+		Output: timestamp,
+	})
+	return timestamp
+}
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index 6edd78a..598ca32 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -61,6 +61,7 @@
         "genrule_conversion_test.go",
         "gensrcs_conversion_test.go",
         "java_binary_host_conversion_test.go",
+        "java_host_for_device_conversion_test.go",
         "java_import_conversion_test.go",
         "java_library_conversion_test.go",
         "java_library_host_conversion_test.go",
diff --git a/bp2build/aar_conversion_test.go b/bp2build/aar_conversion_test.go
index 6020ee5..09d9dc1 100644
--- a/bp2build/aar_conversion_test.go
+++ b/bp2build/aar_conversion_test.go
@@ -66,9 +66,12 @@
 					"resource_files": `["res/res.png"]`,
 					"deps":           `[":static_lib_dep"]`,
 					"exports":        `[":static_lib_dep"]`,
-					"javacopts":      `["-source 1.7 -target 1.7"]`,
+					"java_version":   `"7"`,
 				}),
-			MakeNeverlinkDuplicateTarget("android_library", "TestLib"),
+			MakeNeverlinkDuplicateTargetWithAttrs(
+				"android_library",
+				"TestLib",
+				AttrNameToString{"java_version": `"7"`}),
 		}})
 }
 
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index ef3f124..928a1f2 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -53,6 +53,7 @@
 				"srcs":           `["app.java"]`,
 				"manifest":       `"AndroidManifest.xml"`,
 				"resource_files": `["res/res.png"]`,
+				"sdk_version":    `"current"`,
 			}),
 		}})
 }
@@ -91,7 +92,8 @@
     ]`,
 				"custom_package":   `"com.google"`,
 				"deps":             `[":static_lib_dep"]`,
-				"javacopts":        `["-source 1.7 -target 1.7"]`,
+				"java_version":     `"7"`,
+				"sdk_version":      `"current"`,
 				"certificate_name": `"foocert"`,
 			}),
 		}})
@@ -131,6 +133,7 @@
     })`,
 				"manifest":       `"AndroidManifest.xml"`,
 				"resource_files": `["res/res.png"]`,
+				"sdk_version":    `"current"`,
 			}),
 		}})
 }
@@ -365,6 +368,7 @@
 				"manifest_values": `{
         "minSdkVersion": "24",
     }`,
+				"sdk_version": `"current"`,
 			}),
 		}})
 }
@@ -388,6 +392,7 @@
 				"manifest_values": `{
         "minSdkVersion": "30",
     }`,
+				"sdk_version": `"30"`,
 			}),
 		}})
 }
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 6a39e25..608fcd8 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -1,8 +1,11 @@
 package bp2build
 
 import (
+	"android/soong/starlark_fmt"
 	"encoding/json"
+	"fmt"
 	"reflect"
+	"strconv"
 	"strings"
 
 	"android/soong/android"
@@ -48,7 +51,9 @@
 	if err != nil {
 		panic(err)
 	}
+	files = append(files, newFile("metrics", GeneratedBuildFileName, "")) // Creates a //metrics package.
 	files = append(files, newFile("metrics", "converted_modules_path_map.json", string(convertedModulePathMap)))
+	files = append(files, newFile("metrics", "converted_modules_path_map.bzl", "modules = "+strings.ReplaceAll(string(convertedModulePathMap), "\\", "\\\\")))
 
 	files = append(files, newFile("product_config", "soong_config_variables.bzl", cfg.Bp2buildSoongConfigDefinitions.String()))
 
@@ -62,6 +67,7 @@
 	// TODO(b/269691302)  value of apiLevelsContent is product variable dependent and should be avoided for soong injection
 	files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
 	files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg)))
+	files = append(files, newFile("api_levels", "platform_versions.bzl", platformVersionContents(cfg)))
 
 	files = append(files, newFile("allowlists", GeneratedBuildFileName, ""))
 	files = append(files, newFile("allowlists", "env.bzl", android.EnvironmentVarsFile(cfg)))
@@ -72,6 +78,31 @@
 	return files, nil
 }
 
+func platformVersionContents(cfg android.Config) string {
+	// Despite these coming from cfg.productVariables, they are actually hardcoded in global
+	// makefiles, not set in individual product config makesfiles, so they're safe to just export
+	// and load() directly.
+
+	platformVersionActiveCodenames := make([]string, 0, len(cfg.PlatformVersionActiveCodenames()))
+	for _, codename := range cfg.PlatformVersionActiveCodenames() {
+		platformVersionActiveCodenames = append(platformVersionActiveCodenames, fmt.Sprintf("%q", codename))
+	}
+
+	platformSdkVersion := "None"
+	if cfg.RawPlatformSdkVersion() != nil {
+		platformSdkVersion = strconv.Itoa(*cfg.RawPlatformSdkVersion())
+	}
+
+	return fmt.Sprintf(`
+platform_versions = struct(
+    platform_sdk_final = %s,
+    platform_sdk_version = %s,
+    platform_sdk_codename = %q,
+    platform_version_active_codenames = [%s],
+)
+`, starlark_fmt.PrintBool(cfg.PlatformSdkFinal()), platformSdkVersion, cfg.PlatformSdkCodename(), strings.Join(platformVersionActiveCodenames, ", "))
+}
+
 func CreateBazelFiles(
 	cfg android.Config,
 	ruleShims map[string]RuleShim,
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 8c1d2ae..2f5dc3c 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -131,9 +131,17 @@
 		},
 		{
 			dir:      "metrics",
+			basename: "BUILD.bazel",
+		},
+		{
+			dir:      "metrics",
 			basename: "converted_modules_path_map.json",
 		},
 		{
+			dir:      "metrics",
+			basename: "converted_modules_path_map.bzl",
+		},
+		{
 			dir:      "product_config",
 			basename: "soong_config_variables.bzl",
 		},
@@ -154,6 +162,10 @@
 			basename: "api_levels.bzl",
 		},
 		{
+			dir:      "api_levels",
+			basename: "platform_versions.bzl",
+		},
+		{
 			dir:      "allowlists",
 			basename: GeneratedBuildFileName,
 		},
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index 1b9777c..c821f59 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -57,24 +57,24 @@
 }`,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("java_library", "java-binary-host-1_lib", AttrNameToString{
-				"srcs": `["a.java"]`,
-				"deps": `["//other:jni-lib-1"]`,
-				"javacopts": `[
-        "-Xdoclint:all/protected",
-        "-source 1.8 -target 1.8",
-    ]`,
+				"srcs":         `["a.java"]`,
+				"deps":         `["//other:jni-lib-1"]`,
+				"java_version": `"8"`,
+				"javacopts":    `["-Xdoclint:all/protected"]`,
 				"target_compatible_with": `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
-    })`}),
+    })`,
+			}),
 			MakeBazelTarget("java_binary", "java-binary-host-1", AttrNameToString{
-				"main_class":   `"com.android.test.MainClass"`,
-				"jvm_flags":    `["-Djava.library.path=$${RUNPATH}other"]`,
-				"runtime_deps": `[":java-binary-host-1_lib"]`,
+				"main_class": `"com.android.test.MainClass"`,
+				"jvm_flags":  `["-Djava.library.path=$${RUNPATH}other"]`,
 				"target_compatible_with": `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
-    })`}),
+    })`,
+				"runtime_deps": `[":java-binary-host-1_lib"]`,
+			}),
 		},
 	})
 }
diff --git a/bp2build/java_host_for_device_conversion_test.go b/bp2build/java_host_for_device_conversion_test.go
new file mode 100644
index 0000000..448cba4
--- /dev/null
+++ b/bp2build/java_host_for_device_conversion_test.go
@@ -0,0 +1,65 @@
+// Copyright 2023 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaHostForDeviceTestCaseWithRegistrationCtxFunc(t *testing.T, tc Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
+	t.Helper()
+	(&tc).ModuleTypeUnderTest = "java_host_for_device"
+	(&tc).ModuleTypeUnderTestFactory = java.HostForDeviceFactory
+	RunBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runJavaHostForDeviceTestCase(t *testing.T, tc Bp2buildTestCase) {
+	t.Helper()
+	runJavaHostForDeviceTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_library", java.LibraryFactory)
+	})
+}
+
+func TestJavaHostForDevice(t *testing.T) {
+	runJavaHostForDeviceTestCase(t, Bp2buildTestCase{
+		Description: "java_host_for_device test",
+		Blueprint: `java_host_for_device {
+    name: "java-lib-1",
+    libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: true },
+}`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("java_host_for_device", "java-lib-1", AttrNameToString{
+				"exports": `[":java-lib-2"]`,
+			}),
+			MakeNeverlinkDuplicateTargetWithAttrs("java_library", "java-lib-1", AttrNameToString{
+				"sdk_version": `"none"`,
+			}),
+			MakeBazelTarget("java_library", "java-lib-2", AttrNameToString{
+				"srcs": `["b.java"]`,
+			}),
+			MakeNeverlinkDuplicateTarget("java_library", "java-lib-2"),
+		},
+	})
+}
diff --git a/bp2build/java_import_conversion_test.go b/bp2build/java_import_conversion_test.go
index a5c01cb..5661620 100644
--- a/bp2build/java_import_conversion_test.go
+++ b/bp2build/java_import_conversion_test.go
@@ -49,8 +49,9 @@
 				"jars": `["import.jar"]`,
 			}),
 			MakeBazelTarget("java_library", "example_import-neverlink", AttrNameToString{
-				"exports":   `[":example_import"]`,
-				"neverlink": `True`,
+				"exports":     `[":example_import"]`,
+				"neverlink":   `True`,
+				"sdk_version": `"none"`,
 			}),
 		}})
 }
@@ -86,8 +87,9 @@
     })`,
 			}),
 			MakeBazelTarget("java_library", "example_import-neverlink", AttrNameToString{
-				"exports":   `[":example_import"]`,
-				"neverlink": `True`,
+				"exports":     `[":example_import"]`,
+				"neverlink":   `True`,
+				"sdk_version": `"none"`,
 			}),
 		}})
 }
@@ -112,8 +114,9 @@
 				"jars": `["import.jar"]`,
 			}),
 			MakeBazelTarget("java_library", "example_import-neverlink", AttrNameToString{
-				"exports":   `[":example_import"]`,
-				"neverlink": `True`,
+				"exports":     `[":example_import"]`,
+				"neverlink":   `True`,
+				"sdk_version": `"none"`,
 			}),
 		}})
 }
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 69d0db9..24b763b 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -172,10 +172,13 @@
 }`,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("java_library", "java-lib-1", AttrNameToString{
-				"srcs":      `["a.java"]`,
-				"javacopts": `["-source 11 -target 11"]`,
+				"srcs":         `["a.java"]`,
+				"java_version": `"11"`,
 			}),
-			MakeNeverlinkDuplicateTarget("java_library", "java-lib-1"),
+			MakeNeverlinkDuplicateTargetWithAttrs(
+				"java_library",
+				"java-lib-1",
+				AttrNameToString{"java_version": `"11"`}),
 		},
 	})
 }
@@ -740,7 +743,7 @@
 	})
 }
 
-func TestJavaLibraryArchVariantLibs(t *testing.T) {
+func TestJavaLibraryArchVariantDeps(t *testing.T) {
 	runJavaLibraryTestCase(t, Bp2buildTestCase{
 		Description: "java_library with arch variant libs",
 		Blueprint: `java_library {
@@ -750,6 +753,7 @@
     target: {
         android: {
             libs: ["java-lib-3"],
+            static_libs: ["java-lib-4"],
         },
     },
     bazel_module: { bp2build_available: true },
@@ -762,12 +766,23 @@
 	java_library{
 		name: "java-lib-3",
 }
+
+	java_library{
+		name: "java-lib-4",
+}
 `,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("java_library", "java-lib-1", AttrNameToString{
 				"srcs": `["a.java"]`,
+				"exports": `select({
+        "//build/bazel/platforms/os:android": [":java-lib-4"],
+        "//conditions:default": [],
+    })`,
 				"deps": `[":java-lib-2-neverlink"] + select({
-        "//build/bazel/platforms/os:android": [":java-lib-3-neverlink"],
+        "//build/bazel/platforms/os:android": [
+            ":java-lib-3-neverlink",
+            ":java-lib-4",
+        ],
         "//conditions:default": [],
     })`,
 			}),
@@ -776,6 +791,8 @@
 			MakeNeverlinkDuplicateTarget("java_library", "java-lib-2"),
 			MakeBazelTarget("java_library", "java-lib-3", AttrNameToString{}),
 			MakeNeverlinkDuplicateTarget("java_library", "java-lib-3"),
+			MakeBazelTarget("java_library", "java-lib-4", AttrNameToString{}),
+			MakeNeverlinkDuplicateTarget("java_library", "java-lib-4"),
 		},
 	})
 }
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
index 14854c0..9e47b09 100644
--- a/bp2build/java_library_host_conversion_test.go
+++ b/bp2build/java_library_host_conversion_test.go
@@ -63,8 +63,8 @@
     })`,
 			}),
 			MakeBazelTarget("java_library", "java-lib-host-2", AttrNameToString{
-				"javacopts": `["-source 1.9 -target 1.9"]`,
-				"srcs":      `["c.java"]`,
+				"java_version": `"9"`,
+				"srcs":         `["c.java"]`,
 				"target_compatible_with": `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
@@ -77,6 +77,7 @@
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
     })`,
+				"java_version": `"9"`,
 			}),
 		},
 	})
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
index 8c6337b..f2b6f20 100644
--- a/bp2build/java_plugin_conversion_test.go
+++ b/bp2build/java_plugin_conversion_test.go
@@ -67,7 +67,7 @@
         "a.java",
         "b.java",
     ]`,
-				"javacopts": `["-source 1.7 -target 1.7"]`,
+				"java_version": `"7"`,
 			}),
 		},
 	})
diff --git a/bp2build/java_proto_conversion_test.go b/bp2build/java_proto_conversion_test.go
index d25b7c4..f546cf4 100644
--- a/bp2build/java_proto_conversion_test.go
+++ b/bp2build/java_proto_conversion_test.go
@@ -114,13 +114,17 @@
 				"java_lite_proto_library",
 				"java-protos_java_proto_lite",
 				AttrNameToString{
-					"deps": `[":java-protos_proto"]`,
+					"deps":         `[":java-protos_proto"]`,
+					"java_version": `"7"`,
 				}),
 			MakeBazelTarget("java_library", "java-protos", AttrNameToString{
-				"exports":   `[":java-protos_java_proto_lite"]`,
-				"javacopts": `["-source 1.7 -target 1.7"]`,
+				"exports":      `[":java-protos_java_proto_lite"]`,
+				"java_version": `"7"`,
 			}),
-			MakeNeverlinkDuplicateTarget("java_library", "java-protos"),
+			MakeNeverlinkDuplicateTargetWithAttrs(
+				"java_library",
+				"java-protos",
+				AttrNameToString{"java_version": `"7"`}),
 		},
 	})
 }
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index aac5e7d..5c33308 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -21,10 +21,13 @@
 	"path/filepath"
 	"regexp"
 	"sort"
+	"strconv"
+	"strings"
 	"sync"
 	"sync/atomic"
 
 	"android/soong/shared"
+
 	"github.com/google/blueprint/pathtools"
 )
 
@@ -35,6 +38,13 @@
 // excluded from symlinking. Otherwise, the node is not excluded, but one of its
 // descendants is (otherwise the node in question would not exist)
 
+// This is a version int written to a file called symlink_forest_version at the root of the
+// symlink forest. If the version here does not match the version in the file, then we'll
+// clean the whole symlink forest and recreate it. This number can be bumped whenever there's
+// an incompatible change to the forest layout or a bug in incrementality that needs to be fixed
+// on machines that may still have the bug present in their forest.
+const symlinkForestVersion = 1
+
 type instructionsNode struct {
 	name     string
 	excluded bool // If false, this is just an intermediate node
@@ -123,6 +133,34 @@
 	}
 	newContents = append(newContents, srcBuildFileContent...)
 
+	// Say you run bp2build 4 times:
+	// - The first time there's only an Android.bp file. bp2build will convert it to a build file
+	//   under out/soong/bp2build, then symlink from the forest to that generated file
+	// - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
+	//   the generated one, and write the result to the output file in the forest. But the output
+	//   file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
+	//   the file in out/soong/bp2build instead. So far this doesn't cause any problems...
+	// - You run a 3rd bp2build with no relevant changes. Everything continues to work.
+	// - You then add a comment to the handcrafted BUILD file. This causes a merge with the
+	//   generated file again. But since we wrote to the generated file in step 2, the generated
+	//   file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
+	//   targets.
+	// To solve this, if we see that the output file is a symlink from a previous build, remove it.
+	stat, err := os.Lstat(output)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	} else if err == nil {
+		if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
+			if verbose {
+				fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
+			}
+			err = os.Remove(output)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	return pathtools.WriteFileIfChanged(output, newContents, 0666)
 }
 
@@ -202,6 +240,46 @@
 	return false
 }
 
+// maybeCleanSymlinkForest will remove the whole symlink forest directory if the version recorded
+// in the symlink_forest_version file is not equal to symlinkForestVersion.
+func maybeCleanSymlinkForest(topdir, forest string, verbose bool) error {
+	versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
+	versionFileContents, err := os.ReadFile(versionFilePath)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+	versionFileString := strings.TrimSpace(string(versionFileContents))
+	symlinkForestVersionString := strconv.Itoa(symlinkForestVersion)
+	if err != nil || versionFileString != symlinkForestVersionString {
+		if verbose {
+			fmt.Fprintf(os.Stderr, "Old symlink_forest_version was %q, current is %q. Cleaning symlink forest before recreating...\n", versionFileString, symlinkForestVersionString)
+		}
+		err = os.RemoveAll(shared.JoinPath(topdir, forest))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// maybeWriteVersionFile will write the symlink_forest_version file containing symlinkForestVersion
+// if it doesn't exist already. If it exists we know it must contain symlinkForestVersion because
+// we checked for that already in maybeCleanSymlinkForest
+func maybeWriteVersionFile(topdir, forest string) error {
+	versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
+	_, err := os.Stat(versionFilePath)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return err
+		}
+		err = os.WriteFile(versionFilePath, []byte(strconv.Itoa(symlinkForestVersion)+"\n"), 0666)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // Recursively plants a symlink forest at forestDir. The symlink tree will
 // contain every file in buildFilesDir and srcDir excluding the files in
 // instructions. Collects every directory encountered during the traversal of
@@ -395,6 +473,12 @@
 		symlinkCount: atomic.Uint64{},
 	}
 
+	err := maybeCleanSymlinkForest(topdir, forest, verbose)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+
 	instructions := instructionsFromExcludePathList(exclude)
 	go func() {
 		context.wg.Add(1)
@@ -407,5 +491,11 @@
 		deps = append(deps, dep)
 	}
 
+	err = maybeWriteVersionFile(topdir, forest)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+
 	return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
 }
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 856b6ee..6e919db 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -642,10 +642,14 @@
 }
 
 func MakeNeverlinkDuplicateTarget(moduleType string, name string) string {
-	return MakeBazelTarget(moduleType, name+"-neverlink", AttrNameToString{
-		"neverlink": `True`,
-		"exports":   `[":` + name + `"]`,
-	})
+	return MakeNeverlinkDuplicateTargetWithAttrs(moduleType, name, AttrNameToString{})
+}
+
+func MakeNeverlinkDuplicateTargetWithAttrs(moduleType string, name string, extraAttrs AttrNameToString) string {
+	attrs := extraAttrs
+	attrs["neverlink"] = `True`
+	attrs["exports"] = `[":` + name + `"]`
+	return MakeBazelTarget(moduleType, name+"-neverlink", attrs)
 }
 
 func getTargetName(targetContent string) string {
diff --git a/cc/Android.bp b/cc/Android.bp
index 5fd9afe..be2cc5a 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -21,6 +21,8 @@
     ],
     srcs: [
         "afdo.go",
+        "fdo_profile.go",
+
         "androidmk.go",
         "api_level.go",
         "bp2build.go",
diff --git a/cc/afdo.go b/cc/afdo.go
index d36f4af..be4f50a 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -18,11 +18,13 @@
 	"fmt"
 	"strings"
 
-	"github.com/google/blueprint/proptools"
-
 	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
+// TODO(b/267229066): Remove globalAfdoProfileProjects after implementing bp2build converter for fdo_profile
 var (
 	globalAfdoProfileProjects = []string{
 		"vendor/google_data/pgo_profile/sampling/",
@@ -34,23 +36,23 @@
 
 const afdoCFlagsFormat = "-funique-internal-linkage-names -fprofile-sample-accurate -fprofile-sample-use=%s"
 
-func getAfdoProfileProjects(config android.DeviceConfig) []string {
-	return config.OnceStringSlice(afdoProfileProjectsConfigKey, func() []string {
-		return globalAfdoProfileProjects
-	})
-}
-
 func recordMissingAfdoProfileFile(ctx android.BaseModuleContext, missing string) {
 	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
 }
 
+type afdoRdep struct {
+	VariationName *string
+	ProfilePath   *string
+}
+
 type AfdoProperties struct {
 	// Afdo allows developers self-service enroll for
 	// automatic feedback-directed optimization using profile data.
 	Afdo bool
 
-	AfdoTarget *string  `blueprint:"mutated"`
-	AfdoDeps   []string `blueprint:"mutated"`
+	FdoProfilePath *string `blueprint:"mutated"`
+
+	AfdoRDeps []afdoRdep `blueprint:"mutated"`
 }
 
 type afdo struct {
@@ -61,116 +63,131 @@
 	return []interface{}{&afdo.Properties}
 }
 
-func (afdo *afdo) AfdoEnabled() bool {
-	return afdo != nil && afdo.Properties.Afdo && afdo.Properties.AfdoTarget != nil
-}
-
-// Get list of profile file names, ordered by level of specialisation. For example:
-//  1. libfoo_arm64.afdo
-//  2. libfoo.afdo
-//
-// Add more specialisation as needed.
-func getProfileFiles(ctx android.BaseModuleContext, moduleName string) []string {
-	var files []string
-	files = append(files, moduleName+"_"+ctx.Arch().ArchType.String()+".afdo")
-	files = append(files, moduleName+".afdo")
-	return files
-}
-
-func (props *AfdoProperties) GetAfdoProfileFile(ctx android.BaseModuleContext, module string) android.OptionalPath {
-	// Test if the profile_file is present in any of the Afdo profile projects
-	for _, profileFile := range getProfileFiles(ctx, module) {
-		for _, profileProject := range getAfdoProfileProjects(ctx.DeviceConfig()) {
-			path := android.ExistentPathForSource(ctx, profileProject, profileFile)
-			if path.Valid() {
-				return path
-			}
-		}
-	}
-
-	// Record that this module's profile file is absent
-	missing := ctx.ModuleDir() + ":" + module
-	recordMissingAfdoProfileFile(ctx, missing)
-
-	return android.OptionalPathForPath(nil)
-}
-
-func (afdo *afdo) begin(ctx BaseModuleContext) {
-	if ctx.Host() {
-		return
-	}
-	if ctx.static() && !ctx.staticBinary() {
-		return
-	}
-	if afdo.Properties.Afdo {
-		module := ctx.ModuleName()
-		if afdo.Properties.GetAfdoProfileFile(ctx, module).Valid() {
-			afdo.Properties.AfdoTarget = proptools.StringPtr(module)
-		}
-	}
+// afdoEnabled returns true for binaries and shared libraries
+// that set afdo prop to True and there is a profile available
+func (afdo *afdo) afdoEnabled() bool {
+	return afdo != nil && afdo.Properties.Afdo && afdo.Properties.FdoProfilePath != nil
 }
 
 func (afdo *afdo) flags(ctx ModuleContext, flags Flags) Flags {
-	if profile := afdo.Properties.AfdoTarget; profile != nil {
-		if profileFile := afdo.Properties.GetAfdoProfileFile(ctx, *profile); profileFile.Valid() {
-			profileFilePath := profileFile.Path()
+	if path := afdo.Properties.FdoProfilePath; path != nil {
+		// The flags are prepended to allow overriding.
+		profileUseFlag := fmt.Sprintf(afdoCFlagsFormat, *path)
+		flags.Local.CFlags = append([]string{profileUseFlag}, flags.Local.CFlags...)
+		flags.Local.LdFlags = append([]string{profileUseFlag, "-Wl,-mllvm,-no-warn-sample-unused=true"}, flags.Local.LdFlags...)
 
-			profileUseFlag := fmt.Sprintf(afdoCFlagsFormat, profileFile)
-			flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlag)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlag)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-no-warn-sample-unused=true")
-
-			// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
-			// if profileFile gets updated
-			flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath)
-			flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath)
-		}
+		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
+		// if profileFile gets updated
+		pathForSrc := android.PathForSource(ctx, *path)
+		flags.CFlagsDeps = append(flags.CFlagsDeps, pathForSrc)
+		flags.LdFlagsDeps = append(flags.LdFlagsDeps, pathForSrc)
 	}
 
 	return flags
 }
 
-// Propagate afdo requirements down from binaries
+func (afdo *afdo) addDep(ctx BaseModuleContext, actx android.BottomUpMutatorContext) {
+	if ctx.Host() {
+		return
+	}
+
+	if ctx.static() && !ctx.staticBinary() {
+		return
+	}
+
+	if c, ok := ctx.Module().(*Module); ok && c.Enabled() {
+		if fdoProfileName, err := actx.DeviceConfig().AfdoProfile(actx.ModuleName()); fdoProfileName != nil && err == nil {
+			actx.AddFarVariationDependencies(
+				[]blueprint.Variation{
+					{Mutator: "arch", Variation: actx.Target().ArchVariation()},
+					{Mutator: "os", Variation: "android"},
+				},
+				FdoProfileTag,
+				[]string{*fdoProfileName}...,
+			)
+		}
+	}
+}
+
+// FdoProfileMutator reads the FdoProfileProvider from a direct dep with FdoProfileTag
+// assigns FdoProfileInfo.Path to the FdoProfilePath mutated property
+func (c *Module) fdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if !c.Enabled() {
+		return
+	}
+
+	ctx.VisitDirectDepsWithTag(FdoProfileTag, func(m android.Module) {
+		if ctx.OtherModuleHasProvider(m, FdoProfileProvider) {
+			info := ctx.OtherModuleProvider(m, FdoProfileProvider).(FdoProfileInfo)
+			c.afdo.Properties.FdoProfilePath = proptools.StringPtr(info.Path.String())
+		}
+	})
+}
+
+var _ FdoProfileMutatorInterface = (*Module)(nil)
+
+// Propagate afdo requirements down from binaries and shared libraries
 func afdoDepsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.afdo.AfdoEnabled() {
-		afdoTarget := *m.afdo.Properties.AfdoTarget
-		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			tag := mctx.OtherModuleDependencyTag(dep)
-			libTag, isLibTag := tag.(libraryDependencyTag)
+	if m, ok := mctx.Module().(*Module); ok && m.afdo.afdoEnabled() {
+		if path := m.afdo.Properties.FdoProfilePath; path != nil {
+			mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
+				tag := mctx.OtherModuleDependencyTag(dep)
+				libTag, isLibTag := tag.(libraryDependencyTag)
 
-			// Do not recurse down non-static dependencies
-			if isLibTag {
-				if !libTag.static() {
-					return false
+				// Do not recurse down non-static dependencies
+				if isLibTag {
+					if !libTag.static() {
+						return false
+					}
+				} else {
+					if tag != objDepTag && tag != reuseObjTag {
+						return false
+					}
 				}
-			} else {
-				if tag != objDepTag && tag != reuseObjTag {
-					return false
+
+				if dep, ok := dep.(*Module); ok {
+					dep.afdo.Properties.AfdoRDeps = append(
+						dep.afdo.Properties.AfdoRDeps,
+						afdoRdep{
+							VariationName: proptools.StringPtr(encodeTarget(m.Name())),
+							ProfilePath:   path,
+						},
+					)
 				}
-			}
 
-			if dep, ok := dep.(*Module); ok {
-				dep.afdo.Properties.AfdoDeps = append(dep.afdo.Properties.AfdoDeps, afdoTarget)
-			}
-
-			return true
-		})
+				return true
+			})
+		}
 	}
 }
 
 // Create afdo variants for modules that need them
 func afdoMutator(mctx android.BottomUpMutatorContext) {
 	if m, ok := mctx.Module().(*Module); ok && m.afdo != nil {
-		if m.afdo.AfdoEnabled() && !m.static() {
-			afdoTarget := *m.afdo.Properties.AfdoTarget
-			mctx.SetDependencyVariation(encodeTarget(afdoTarget))
+		if !m.static() && m.afdo.Properties.Afdo && m.afdo.Properties.FdoProfilePath != nil {
+			mctx.SetDependencyVariation(encodeTarget(m.Name()))
+			return
 		}
 
 		variationNames := []string{""}
-		afdoDeps := android.FirstUniqueStrings(m.afdo.Properties.AfdoDeps)
-		for _, dep := range afdoDeps {
-			variationNames = append(variationNames, encodeTarget(dep))
+
+		variantNameToProfilePath := make(map[string]*string)
+
+		for _, afdoRDep := range m.afdo.Properties.AfdoRDeps {
+			variantName := *afdoRDep.VariationName
+			// An rdep can be set twice in AfdoRDeps because there can be
+			// more than one path from an afdo-enabled module to
+			// a static dep such as
+			// afdo_enabled_foo -> static_bar ----> static_baz
+			//                   \                      ^
+			//                    ----------------------|
+			// We only need to create one variant per unique rdep
+			if variantNameToProfilePath[variantName] == nil {
+				variationNames = append(variationNames, variantName)
+				variantNameToProfilePath[variantName] = afdoRDep.ProfilePath
+			}
 		}
+
 		if len(variationNames) > 1 {
 			modules := mctx.CreateVariations(variationNames...)
 			for i, name := range variationNames {
@@ -180,7 +197,7 @@
 				variation := modules[i].(*Module)
 				variation.Properties.PreventInstall = true
 				variation.Properties.HideFromMake = true
-				variation.afdo.Properties.AfdoTarget = proptools.StringPtr(decodeTarget(name))
+				variation.afdo.Properties.FdoProfilePath = variantNameToProfilePath[name]
 			}
 		}
 	}
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
index 40f705b..1c20bfc 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -58,38 +58,77 @@
 		srcs: ["bar.c"],
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
+		PrepareForTestWithFdoProfile,
 		prepareForCcTest,
-		prepareForAfdoTest,
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+	expectedCFlag := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
 
-	if !hasDirectDep(result, libTest.Module(), libFoo.Module()) {
+	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libFooAfdoVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libBarAfdoVariant := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+
+	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
+	cFlags := libTest.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	cFlags = libFooAfdoVariant.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libFooAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	cFlags = libBarAfdoVariant.Rule("cc").Args["cFlags"]
+	if !strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	}
+
+	// Check dependency edge from afdo-enabled module to static deps
+	if !hasDirectDep(result, libTest.Module(), libFooAfdoVariant.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libFoo")
 	}
 
-	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
+	if !hasDirectDep(result, libFooAfdoVariant.Module(), libBarAfdoVariant.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libBar")
 	}
 
-	cFlags := libTest.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", w, cFlags)
-	}
+	// Verify non-afdo variant exists and doesn't contain afdo
+	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libFoo' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	if strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libFoo' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
+	}
+	cFlags = libBar.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, expectedCFlag) {
+		t.Errorf("Expected 'libBar' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
 	}
 
-	cFlags = libBar.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-accurate"; !strings.Contains(cFlags, w) {
-		t.Errorf("Expected 'libBar' to enable afdo, but did not find %q in cflags %q", w, cFlags)
+	// Check dependency edges of static deps
+	if hasDirectDep(result, libTest.Module(), libFoo.Module()) {
+		t.Errorf("libTest should not depend on non-afdo variant of libFoo")
+	}
+
+	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
+		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
 }
 
@@ -113,11 +152,21 @@
 		name: "libBar",
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libFoo.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
 		prepareForCcTest,
-		prepareForAfdoTest,
+		PrepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libFoo.afdo", ""),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				soong_namespace {
+				}
+				fdo_profile {
+					name: "libFoo_afdo",
+					profile: "libFoo.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
@@ -150,7 +199,6 @@
 			t.Errorf("Expected no afdo variant of 'bar', got %q", v)
 		}
 	}
-
 }
 
 func TestAfdoEnabledWithRuntimeDepNoAfdo(t *testing.T) {
@@ -166,11 +214,24 @@
 		name: "libFoo",
 	}
 	`
-	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
 
 	result := android.GroupFixturePreparers(
 		prepareForCcTest,
-		prepareForAfdoTest,
+		PrepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	libFooVariants := result.ModuleVariantsForTests("libFoo")
@@ -182,7 +243,6 @@
 }
 
 func TestAfdoEnabledWithMultiArchs(t *testing.T) {
-	t.Parallel()
 	bp := `
 	cc_library_shared {
 		name: "foo",
@@ -192,20 +252,43 @@
 	}
 `
 	result := android.GroupFixturePreparers(
+		PrepareForTestWithFdoProfile,
 		prepareForCcTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm.afdo", "TEST"),
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm64.afdo", "TEST"),
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm.afdo", ""),
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm64.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"foo://afdo_profiles_package:foo_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				soong_namespace {
+				}
+				fdo_profile {
+					name: "foo_afdo",
+					arch: {
+						arm: {
+							profile: "foo_arm.afdo",
+						},
+						arm64: {
+							profile: "foo_arm64.afdo",
+						}
+					}
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	fooArm := result.ModuleForTests("foo", "android_arm_armv7-a-neon_shared")
 	fooArmCFlags := fooArm.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/foo_arm.afdo"; !strings.Contains(fooArmCFlags, w) {
+	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm.afdo"; !strings.Contains(fooArmCFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArmCFlags)
 	}
 
 	fooArm64 := result.ModuleForTests("foo", "android_arm64_armv8-a_shared")
 	fooArm64CFlags := fooArm64.Rule("cc").Args["cFlags"]
-	if w := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/foo_arm64.afdo"; !strings.Contains(fooArm64CFlags, w) {
+	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm64.afdo"; !strings.Contains(fooArm64CFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArm64CFlags)
 	}
 }
@@ -234,46 +317,65 @@
 	`
 
 	result := android.GroupFixturePreparers(
+		PrepareForTestWithFdoProfile,
 		prepareForCcTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST"),
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libBar.afdo", "TEST"),
+		android.FixtureAddTextFile("afdo_profiles_package/libTest.afdo", ""),
+		android.FixtureAddTextFile("afdo_profiles_package/libBar.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"libTest://afdo_profiles_package:libTest_afdo",
+				"libBar://afdo_profiles_package:libBar_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "libTest_afdo",
+					profile: "libTest.afdo",
+				}
+				fdo_profile {
+					name: "libBar_afdo",
+					profile: "libBar.afdo",
+				}
+			`),
+		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	expectedCFlagLibTest := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/libTest.afdo"
-	expectedCFlagLibBar := "-fprofile-sample-use=toolchain/pgo-profiles/sampling/libBar.afdo"
+	expectedCFlagLibTest := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
+	expectedCFlagLibBar := "-fprofile-sample-use=afdo_profiles_package/libBar.afdo"
 
 	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libTestAfdoVariantOfLibFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libFooAfdoVariantWithLibTest := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
 
 	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_shared")
-	libBarAfdoVariantOfLibFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libBar")
+	libFooAfdoVariantWithLibBar := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libBar")
 
-	// Check cFlags of afdo-enabled modules and the afdo-variant of their static deps
+	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibTest) {
 		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
 	}
 	cFlags = libBar.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibBar) {
-		t.Errorf("Expected 'libBar' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
 	}
 
-	cFlags = libTestAfdoVariantOfLibFoo.Rule("cc").Args["cFlags"]
+	cFlags = libFooAfdoVariantWithLibTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibTest) {
-		t.Errorf("Expected 'libTestAfdoVariantOfLibFoo' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
+		t.Errorf("Expected 'libFooAfdoVariantWithLibTest' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibTest, cFlags)
 	}
 
-	cFlags = libBarAfdoVariantOfLibFoo.Rule("cc").Args["cFlags"]
+	cFlags = libFooAfdoVariantWithLibBar.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlagLibBar) {
-		t.Errorf("Expected 'libBarAfdoVariantOfLibFoo' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlagLibBar, cFlags)
 	}
 
 	// Check dependency edges of static deps
-	if !hasDirectDep(result, libTest.Module(), libTestAfdoVariantOfLibFoo.Module()) {
+	if !hasDirectDep(result, libTest.Module(), libFooAfdoVariantWithLibTest.Module()) {
 		t.Errorf("libTest missing dependency on afdo variant of libFoo")
 	}
 
-	if !hasDirectDep(result, libBar.Module(), libBarAfdoVariantOfLibFoo.Module()) {
-		t.Errorf("libBar missing dependency on afdo variant of libFoo")
+	if !hasDirectDep(result, libBar.Module(), libFooAfdoVariantWithLibBar.Module()) {
+		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
 }
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 7c817a2..c8f516c 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -915,7 +915,7 @@
 			ctx.CreateBazelTargetModule(
 				bazel.BazelTargetModuleProperties{
 					Rule_class:        "aidl_library",
-					Bzl_load_location: "//build/bazel/rules/aidl:library.bzl",
+					Bzl_load_location: "//build/bazel/rules/aidl:aidl_library.bzl",
 				},
 				android.CommonAttributes{Name: aidlLibName},
 				&aidlLibraryAttributes{
diff --git a/cc/cc.go b/cc/cc.go
index b029d71..9c555a1 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -52,6 +52,7 @@
 		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
 		ctx.BottomUp("version", versionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
+		ctx.BottomUp("fdo_profile", fdoProfileMutator)
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
@@ -763,6 +764,7 @@
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
 	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
+	FdoProfileTag         = dependencyTag{name: "fdo_profile"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
@@ -1336,7 +1338,7 @@
 
 func (c *Module) isAfdoCompile() bool {
 	if afdo := c.afdo; afdo != nil {
-		return afdo.Properties.AfdoTarget != nil
+		return afdo.Properties.FdoProfilePath != nil
 	}
 	return false
 }
@@ -2162,9 +2164,6 @@
 	if c.lto != nil {
 		c.lto.begin(ctx)
 	}
-	if c.afdo != nil {
-		c.afdo.begin(ctx)
-	}
 	if c.pgo != nil {
 		c.pgo.begin(ctx)
 	}
@@ -2239,6 +2238,10 @@
 	}
 	ctx.ctx = ctx
 
+	if !actx.Host() || !ctx.static() || ctx.staticBinary() {
+		c.afdo.addDep(ctx, actx)
+	}
+
 	c.begin(ctx)
 }
 
diff --git a/cc/config/global.go b/cc/config/global.go
index 5b2191a..a2d5cf4 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -111,6 +111,9 @@
 
 		// Turn off FMA which got enabled by default in clang-r445002 (http://b/218805949)
 		"-ffp-contract=off",
+
+		// Turn off stack protector check for noreturn calls. (http://b/264965700)
+		"-mllvm -disable-check-noreturn-call",
 	}
 
 	commonGlobalConlyflags = []string{}
@@ -147,6 +150,9 @@
 	commonGlobalLldflags = []string{
 		"-fuse-ld=lld",
 		"-Wl,--icf=safe",
+
+		// Turn off stack protector check for noreturn calls. (http://b/264965700)
+		"-Wl,-mllvm,-disable-check-noreturn-call",
 	}
 
 	deviceGlobalCppflags = []string{
@@ -315,6 +321,9 @@
 	}
 
 	VersionScriptFlagPrefix = "-Wl,--version-script,"
+
+	VisibilityHiddenFlag  = "-fvisibility=hidden"
+	VisibilityDefaultFlag = "-fvisibility=default"
 )
 
 // BazelCcToolchainVars generates bzl file content containing variables for
@@ -405,6 +414,9 @@
 
 	exportedVars.ExportString("VersionScriptFlagPrefix", VersionScriptFlagPrefix)
 
+	exportedVars.ExportString("VisibilityHiddenFlag", VisibilityHiddenFlag)
+	exportedVars.ExportString("VisibilityDefaultFlag", VisibilityDefaultFlag)
+
 	// Everything in these lists is a crime against abstraction and dependency tracking.
 	// Do not add anything to this list.
 	commonGlobalIncludes := []string{
diff --git a/cc/fdo_profile.go b/cc/fdo_profile.go
new file mode 100644
index 0000000..7fbe719
--- /dev/null
+++ b/cc/fdo_profile.go
@@ -0,0 +1,85 @@
+// Copyright 2023 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"
+
+	"github.com/google/blueprint"
+)
+
+func init() {
+	RegisterFdoProfileBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterFdoProfileBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("fdo_profile", fdoProfileFactory)
+}
+
+type fdoProfile struct {
+	android.ModuleBase
+
+	properties fdoProfileProperties
+}
+
+type fdoProfileProperties struct {
+	Profile *string `android:"arch_variant"`
+}
+
+// FdoProfileInfo is provided by FdoProfileProvider
+type FdoProfileInfo struct {
+	Path android.Path
+}
+
+// FdoProfileProvider is used to provide path to an fdo profile
+var FdoProfileProvider = blueprint.NewMutatorProvider(FdoProfileInfo{}, "fdo_profile")
+
+// FdoProfileMutatorInterface is the interface implemented by fdo_profile module type
+// module types that can depend on an fdo_profile module
+type FdoProfileMutatorInterface interface {
+	// FdoProfileMutator eithers set or get FdoProfileProvider
+	fdoProfileMutator(ctx android.BottomUpMutatorContext)
+}
+
+var _ FdoProfileMutatorInterface = (*fdoProfile)(nil)
+
+// GenerateAndroidBuildActions of fdo_profile does not have any build actions
+func (fp *fdoProfile) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
+
+// FdoProfileMutator sets FdoProfileProvider to fdo_profile module
+// or sets afdo.Properties.FdoProfilePath to path in FdoProfileProvider of the depended fdo_profile
+func (fp *fdoProfile) fdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if fp.properties.Profile != nil {
+		path := android.PathForModuleSrc(ctx, *fp.properties.Profile)
+		ctx.SetProvider(FdoProfileProvider, FdoProfileInfo{
+			Path: path,
+		})
+	}
+}
+
+// fdoProfileMutator calls the generic fdoProfileMutator function of fdoProfileMutator
+// which is implemented by cc and cc.FdoProfile
+func fdoProfileMutator(ctx android.BottomUpMutatorContext) {
+	if f, ok := ctx.Module().(FdoProfileMutatorInterface); ok {
+		f.fdoProfileMutator(ctx)
+	}
+}
+
+func fdoProfileFactory() android.Module {
+	m := &fdoProfile{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibBoth)
+	return m
+}
diff --git a/cc/library.go b/cc/library.go
index a9ada97..7504302 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -26,7 +26,6 @@
 	"android/soong/android"
 	"android/soong/bazel"
 	"android/soong/bazel/cquery"
-	"android/soong/cc/config"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/pathtools"
@@ -2261,8 +2260,7 @@
 		!ctx.useVndk() && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() && ctx.Device() &&
 		library.baseLinker.sanitize.isUnsanitizedVariant() &&
 		ctx.isForPlatform() && !ctx.isPreventInstall() {
-		installPath := getNdkSysrootBase(ctx).Join(
-			ctx, "usr/lib", config.NDKTriple(ctx.toolchain()), file.Base())
+		installPath := getUnversionedLibraryInstallPath(ctx).Join(ctx, file.Base())
 
 		ctx.ModuleBuild(pctx, android.ModuleBuildParams{
 			Rule:        android.Cp,
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 2473ba2..a824361 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -528,17 +528,20 @@
 	return false
 }
 
-func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
-	arch := ctx.Target().Arch.ArchType.Name
-	// arm64 isn't actually a multilib toolchain, so unlike the other LP64
-	// architectures it's just installed to lib.
-	libDir := "lib"
-	if ctx.toolchain().Is64Bit() && arch != "arm64" {
-		libDir = "lib64"
-	}
+// Returns the install path for unversioned NDK libraries (currently only static
+// libraries).
+func getUnversionedLibraryInstallPath(ctx ModuleContext) android.InstallPath {
+	return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain()))
+}
 
-	installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf(
-		"platforms/android-%s/arch-%s/usr/%s", stub.apiLevel, arch, libDir))
+// Returns the install path for versioned NDK libraries. These are most often
+// stubs, but the same paths are used for CRT objects.
+func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.InstallPath {
+	return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String())
+}
+
+func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
+	installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel)
 	stub.installPath = ctx.InstallFile(installDir, path.Base(), path)
 }
 
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index 622558e..dffc6c6 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -142,6 +142,13 @@
 						staticLibInstallPaths, library.ndkSysrootPath)
 				}
 			}
+
+			if object, ok := m.linker.(*objectLinker); ok {
+				if object.ndkSysrootPath != nil {
+					staticLibInstallPaths = append(
+						staticLibInstallPaths, object.ndkSysrootPath)
+				}
+			}
 		}
 	})
 
diff --git a/cc/object.go b/cc/object.go
index ef44467..d65cdea 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -44,6 +44,10 @@
 type objectLinker struct {
 	*baseLinker
 	Properties ObjectLinkerProperties
+
+	// Location of the object in the sysroot. Empty if the object is not
+	// included in the NDK.
+	ndkSysrootPath android.Path
 }
 
 type objectBazelHandler struct {
@@ -99,6 +103,10 @@
 	// Indicates that this module is a CRT object. CRT objects will be split
 	// into a variant per-API level between min_sdk_version and current.
 	Crt *bool
+
+	// Indicates that this module should not be included in the NDK sysroot.
+	// Only applies to CRT objects. Defaults to false.
+	Exclude_from_ndk_sysroot *bool
 }
 
 func newObject(hod android.HostOrDeviceSupported) *Module {
@@ -268,17 +276,28 @@
 
 	objs = objs.Append(deps.Objs)
 
-	var outputFile android.Path
+	var output android.WritablePath
 	builderFlags := flagsToBuilderFlags(flags)
 	outputName := ctx.ModuleName()
 	if !strings.HasSuffix(outputName, objectExtension) {
 		outputName += objectExtension
 	}
 
-	if len(objs.objFiles) == 1 && String(object.Properties.Linker_script) == "" {
-		output := android.PathForModuleOut(ctx, outputName)
-		outputFile = output
+	// isForPlatform is terribly named and actually means isNotApex.
+	if Bool(object.Properties.Crt) &&
+		!Bool(object.Properties.Exclude_from_ndk_sysroot) && ctx.useSdk() &&
+		ctx.isSdkVariant() && ctx.isForPlatform() {
 
+		output = getVersionedLibraryInstallPath(ctx,
+			nativeApiLevelOrPanic(ctx, ctx.sdkVersion())).Join(ctx, outputName)
+		object.ndkSysrootPath = output
+	} else {
+		output = android.PathForModuleOut(ctx, outputName)
+	}
+
+	outputFile := output
+
+	if len(objs.objFiles) == 1 && String(object.Properties.Linker_script) == "" {
 		if String(object.Properties.Prefix_symbols) != "" {
 			transformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), objs.objFiles[0],
 				builderFlags, output)
@@ -290,9 +309,6 @@
 			})
 		}
 	} else {
-		output := android.PathForModuleOut(ctx, outputName)
-		outputFile = output
-
 		if String(object.Properties.Prefix_symbols) != "" {
 			input := android.PathForModuleOut(ctx, "unprefixed", outputName)
 			transformBinaryPrefixSymbols(ctx, String(object.Properties.Prefix_symbols), input,
diff --git a/cc/object_test.go b/cc/object_test.go
index 5359a35..b1e2a0f 100644
--- a/cc/object_test.go
+++ b/cc/object_test.go
@@ -65,7 +65,7 @@
 	variant := "android_arm64_armv8-a_sdk"
 	crt := ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
 	android.AssertStringDoesContain(t, "crt dep of sdk variant", crt,
-		variant+"_29/crtbegin_dynamic.o")
+		"29/crtbegin_dynamic.o")
 
 	// platform variant uses the crt object built for platform
 	variant = "android_arm64_armv8-a"
diff --git a/cc/sanitize.go b/cc/sanitize.go
index f19659c..4601ee6 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -64,12 +64,13 @@
 
 	cfiBlocklistPath     = "external/compiler-rt/lib/cfi"
 	cfiBlocklistFilename = "cfi_blocklist.txt"
-	cfiCflags            = []string{"-flto", "-fsanitize-cfi-cross-dso",
+	cfiCrossDsoFlag      = "-fsanitize-cfi-cross-dso"
+	cfiCflags            = []string{"-flto", cfiCrossDsoFlag,
 		"-fsanitize-ignorelist=" + cfiBlocklistPath + "/" + cfiBlocklistFilename}
 	// -flto and -fvisibility are required by clang when -fsanitize=cfi is
 	// used, but have no effect on assembly files
 	cfiAsflags = []string{"-flto", "-fvisibility=default"}
-	cfiLdflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fsanitize=cfi",
+	cfiLdflags = []string{"-flto", cfiCrossDsoFlag, "-fsanitize=cfi",
 		"-Wl,-plugin-opt,O1"}
 	cfiExportsMapPath      = "build/soong/cc/config"
 	cfiExportsMapFilename  = "cfi_exports.map"
@@ -393,11 +394,13 @@
 	exportedVars.ExportStringList("DeviceOnlySanitizeFlags", deviceOnlySanitizeFlags)
 
 	// Leave out "-flto" from the slices exported to bazel, as we will use the
-	// dedicated LTO feature for this
-	exportedVars.ExportStringList("CfiCFlags", cfiCflags[1:])
+	// dedicated LTO feature for this. For C Flags and Linker Flags, also leave
+	// out the cross DSO flag which will be added separately by transitions.
+	exportedVars.ExportStringList("CfiCFlags", cfiCflags[2:])
+	exportedVars.ExportStringList("CfiLdFlags", cfiLdflags[2:])
 	exportedVars.ExportStringList("CfiAsFlags", cfiAsflags[1:])
-	exportedVars.ExportStringList("CfiLdFlags", cfiLdflags[1:])
 
+	exportedVars.ExportString("CfiCrossDsoFlag", cfiCrossDsoFlag)
 	exportedVars.ExportString("CfiBlocklistPath", cfiBlocklistPath)
 	exportedVars.ExportString("CfiBlocklistFilename", cfiBlocklistFilename)
 	exportedVars.ExportString("CfiExportsMapPath", cfiExportsMapPath)
@@ -613,6 +616,10 @@
 	if (ctx.Arch().ArchType != android.Arm64 && ctx.Arch().ArchType != android.Riscv64) || !ctx.toolchain().Bionic() {
 		s.Scs = nil
 	}
+	// ...but temporarily globally disabled on riscv64 (http://b/277909695).
+	if ctx.Arch().ArchType == android.Riscv64 {
+		s.Scs = nil
+	}
 
 	// Memtag_heap is only implemented on AArch64.
 	// Memtag ABI is Android specific for now, so disable for host.
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index 718186d..29b17d4 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -1166,3 +1166,83 @@
 	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
 	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
 }
+
+func TestCfi(t *testing.T) {
+	t.Parallel()
+
+	bp := `
+	cc_library_shared {
+		name: "shared_with_cfi",
+		static_libs: [
+			"static_dep_with_cfi",
+			"static_dep_no_cfi",
+		],
+		sanitize: {
+			cfi: true,
+		},
+	}
+
+	cc_library_shared {
+		name: "shared_no_cfi",
+		static_libs: [
+			"static_dep_with_cfi",
+			"static_dep_no_cfi",
+		],
+	}
+
+	cc_library_static {
+		name: "static_dep_with_cfi",
+		sanitize: {
+			cfi: true,
+		},
+	}
+
+	cc_library_static {
+		name: "static_dep_no_cfi",
+	}
+
+	cc_library_shared {
+		name: "shared_rdep_no_cfi",
+		static_libs: ["static_dep_with_cfi_2"],
+	}
+
+	cc_library_static {
+		name: "static_dep_with_cfi_2",
+		sanitize: {
+			cfi: true,
+		},
+	}
+`
+	preparer := android.GroupFixturePreparers(
+		prepareForCcTest,
+	)
+	result := preparer.RunTestWithBp(t, bp)
+	ctx := result.TestContext
+
+	buildOs := "android_arm64_armv8-a"
+	shared_suffix := "_shared"
+	cfi_suffix := "_cfi"
+	static_suffix := "_static"
+
+	sharedWithCfiLib := result.ModuleForTests("shared_with_cfi", buildOs+shared_suffix+cfi_suffix)
+	sharedNoCfiLib := result.ModuleForTests("shared_no_cfi", buildOs+shared_suffix)
+	staticWithCfiLib := result.ModuleForTests("static_dep_with_cfi", buildOs+static_suffix)
+	staticWithCfiLibCfiVariant := result.ModuleForTests("static_dep_with_cfi", buildOs+static_suffix+cfi_suffix)
+	staticNoCfiLib := result.ModuleForTests("static_dep_no_cfi", buildOs+static_suffix)
+	staticNoCfiLibCfiVariant := result.ModuleForTests("static_dep_no_cfi", buildOs+static_suffix+cfi_suffix)
+	sharedRdepNoCfi := result.ModuleForTests("shared_rdep_no_cfi", buildOs+shared_suffix)
+	staticDepWithCfi2Lib := result.ModuleForTests("static_dep_with_cfi_2", buildOs+static_suffix)
+
+	// Confirm assumptions about propagation of CFI enablement
+	expectStaticLinkDep(t, ctx, sharedWithCfiLib, staticWithCfiLibCfiVariant)
+	expectStaticLinkDep(t, ctx, sharedNoCfiLib, staticWithCfiLib)
+	expectStaticLinkDep(t, ctx, sharedWithCfiLib, staticNoCfiLibCfiVariant)
+	expectStaticLinkDep(t, ctx, sharedNoCfiLib, staticNoCfiLib)
+	expectStaticLinkDep(t, ctx, sharedRdepNoCfi, staticDepWithCfi2Lib)
+
+	// Confirm that non-CFI variants do not add CFI flags
+	bazLibCflags := staticWithCfiLib.Rule("cc").Args["cFlags"]
+	if strings.Contains(bazLibCflags, "-fsanitize-cfi-cross-dso") {
+		t.Errorf("non-CFI variant of baz not expected to contain CFI flags ")
+	}
+}
diff --git a/cc/testing.go b/cc/testing.go
index f78ea0f..ced0929 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -670,6 +670,12 @@
 	`),
 )
 
+// PrepareForTestWithFdoProfile registers module types to test with fdo_profile
+var PrepareForTestWithFdoProfile = android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("soong_namespace", android.NamespaceFactory)
+	ctx.RegisterModuleType("fdo_profile", fdoProfileFactory)
+})
+
 // TestConfig is the legacy way of creating a test Config for testing cc modules.
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
diff --git a/cc/vndk.go b/cc/vndk.go
index 3b7c87d..be66cd7 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -674,8 +674,12 @@
 	snapshotArchDir := filepath.Join(snapshotDir, ctx.DeviceConfig().DeviceArch())
 
 	configsDir := filepath.Join(snapshotArchDir, "configs")
+	noticeDir := filepath.Join(snapshotArchDir, "NOTICE_FILES")
 	includeDir := filepath.Join(snapshotArchDir, "include")
 
+	// set of notice files copied.
+	noticeBuilt := make(map[string]bool)
+
 	// paths of VNDK modules for GPL license checking
 	modulePaths := make(map[string]string)
 
@@ -700,28 +704,36 @@
 		snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "shared", vndkType, libPath.Base())
 		ret = append(ret, snapshot.CopyFileRule(pctx, ctx, libPath, snapshotLibOut))
 
+		// json struct to export snapshot information
+		prop := struct {
+			LicenseKinds        []string `json:",omitempty"`
+			LicenseTexts        []string `json:",omitempty"`
+			ExportedDirs        []string `json:",omitempty"`
+			ExportedSystemDirs  []string `json:",omitempty"`
+			ExportedFlags       []string `json:",omitempty"`
+			RelativeInstallPath string   `json:",omitempty"`
+		}{}
+
+		prop.LicenseKinds = m.EffectiveLicenseKinds()
+		prop.LicenseTexts = m.EffectiveLicenseFiles().Strings()
+
 		if ctx.Config().VndkSnapshotBuildArtifacts() {
-			prop := struct {
-				ExportedDirs        []string `json:",omitempty"`
-				ExportedSystemDirs  []string `json:",omitempty"`
-				ExportedFlags       []string `json:",omitempty"`
-				RelativeInstallPath string   `json:",omitempty"`
-			}{}
 			exportedInfo := ctx.ModuleProvider(m, FlagExporterInfoProvider).(FlagExporterInfo)
 			prop.ExportedFlags = exportedInfo.Flags
 			prop.ExportedDirs = exportedInfo.IncludeDirs.Strings()
 			prop.ExportedSystemDirs = exportedInfo.SystemIncludeDirs.Strings()
 			prop.RelativeInstallPath = m.RelativeInstallPath()
-
-			propOut := snapshotLibOut + ".json"
-
-			j, err := json.Marshal(prop)
-			if err != nil {
-				ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
-				return nil, false
-			}
-			ret = append(ret, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
 		}
+
+		propOut := snapshotLibOut + ".json"
+
+		j, err := json.Marshal(prop)
+		if err != nil {
+			ctx.Errorf("json marshal to %q failed: %#v", propOut, err)
+			return nil, false
+		}
+		ret = append(ret, snapshot.WriteStringToFileRule(ctx, string(j), propOut))
+
 		return ret, true
 	}
 
@@ -761,6 +773,14 @@
 		moduleNames[stem] = ctx.ModuleName(m)
 		modulePaths[stem] = ctx.ModuleDir(m)
 
+		for _, notice := range m.EffectiveLicenseFiles() {
+			if _, ok := noticeBuilt[notice.String()]; !ok {
+				noticeBuilt[notice.String()] = true
+				snapshotOutputs = append(snapshotOutputs, snapshot.CopyFileRule(
+					pctx, ctx, notice, filepath.Join(noticeDir, notice.String())))
+			}
+		}
+
 		if ctx.Config().VndkSnapshotBuildArtifacts() {
 			headers = append(headers, m.SnapshotHeaders()...)
 		}
diff --git a/java/aar.go b/java/aar.go
index 47e6efa..f1b137d 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -1015,9 +1015,10 @@
 }
 
 type bazelAndroidLibraryImport struct {
-	Aar     bazel.Label
-	Deps    bazel.LabelListAttribute
-	Exports bazel.LabelListAttribute
+	Aar         bazel.Label
+	Deps        bazel.LabelListAttribute
+	Exports     bazel.LabelListAttribute
+	Sdk_version bazel.StringAttribute
 }
 
 func (a *aapt) convertAaptAttrsWithBp2Build(ctx android.TopDownMutatorContext) *bazelAapt {
@@ -1059,9 +1060,10 @@
 		},
 		android.CommonAttributes{Name: name},
 		&bazelAndroidLibraryImport{
-			Aar:     aars.Includes[0],
-			Deps:    bazel.MakeLabelListAttribute(deps),
-			Exports: bazel.MakeLabelListAttribute(exports),
+			Aar:         aars.Includes[0],
+			Deps:        bazel.MakeLabelListAttribute(deps),
+			Exports:     bazel.MakeLabelListAttribute(exports),
+			Sdk_version: bazel.StringAttribute{Value: a.properties.Sdk_version},
 		},
 	)
 
@@ -1073,6 +1075,9 @@
 			javaLibraryAttributes: &javaLibraryAttributes{
 				Neverlink: bazel.BoolAttribute{Value: &neverlink},
 				Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
+				javaCommonAttributes: &javaCommonAttributes{
+					Sdk_version: bazel.StringAttribute{Value: a.properties.Sdk_version},
+				},
 			},
 		},
 	)
@@ -1119,6 +1124,10 @@
 			javaLibraryAttributes: &javaLibraryAttributes{
 				Neverlink: bazel.BoolAttribute{Value: &neverlink},
 				Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
+				javaCommonAttributes: &javaCommonAttributes{
+					Sdk_version:  bazel.StringAttribute{Value: a.deviceProperties.Sdk_version},
+					Java_version: bazel.StringAttribute{Value: a.properties.Java_version},
+				},
 			},
 		},
 	)
diff --git a/java/app.go b/java/app.go
index 52caf6d..03e2330 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1565,6 +1565,9 @@
 
 		appAttrs.bazelAapt = &bazelAapt{Manifest: aapt.Manifest}
 		appAttrs.Deps = bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + ktName})
+		appAttrs.javaCommonAttributes = &javaCommonAttributes{
+			Sdk_version: commonAttrs.Sdk_version,
+		}
 	}
 
 	ctx.CreateBazelTargetModule(
diff --git a/java/base.go b/java/base.go
index 1bcff2e..9911323 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1923,7 +1923,7 @@
 
 func (m *Module) getSdkLinkType(ctx android.BaseModuleContext, name string) (ret sdkLinkType, stubs bool) {
 	switch name {
-	case "core.current.stubs", "legacy.core.platform.api.stubs", "stable.core.platform.api.stubs",
+	case android.SdkCore.JavaLibraryName(ctx.Config()), "legacy.core.platform.api.stubs", "stable.core.platform.api.stubs",
 		"stub-annotations", "private-stub-annotations-jar",
 		"core-lambda-stubs", "core-generated-annotation-stubs":
 		return javaCore, true
diff --git a/java/config/config.go b/java/config/config.go
index 13670ee..b82a137 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -95,11 +95,13 @@
 		"-JXX:TieredStopAtLevel=1",
 		"-JDcom.android.tools.r8.emitRecordAnnotationsInDex",
 		"-JDcom.android.tools.r8.emitPermittedSubclassesAnnotationsInDex",
+		"-JDcom.android.tools.r8.emitRecordAnnotationsExInDex",
 	}, dexerJavaVmFlagsList...))
 	exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{
 		"-JXmx2048M",
 		"-JDcom.android.tools.r8.emitRecordAnnotationsInDex",
 		"-JDcom.android.tools.r8.emitPermittedSubclassesAnnotationsInDex",
+		"-JDcom.android.tools.r8.emitRecordAnnotationsExInDex",
 	}, dexerJavaVmFlagsList...))
 
 	exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index b9332dd..958f4ce 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -79,16 +79,25 @@
     ],
 }
 
+// Defaults module to strip out android annotations
+java_defaults {
+    name: "system-modules-no-annotations",
+    sdk_version: "none",
+    system_modules: "none",
+    jarjar_rules: "jarjar-strip-annotations-rules.txt",
+}
+
 // Same as core-current-stubs-for-system-modules, but android annotations are
 // stripped.
 java_library {
     name: "core-current-stubs-for-system-modules-no-annotations",
     visibility: ["//development/sdk"],
+    defaults: [
+        "system-modules-no-annotations",
+    ],
     static_libs: [
         "core-current-stubs-for-system-modules",
     ],
-    sdk_version: "none",
-    system_modules: "none",
     dists: [
         {
             // Legacy dist location for the public file.
@@ -100,7 +109,6 @@
             targets: dist_targets,
         },
     ],
-    jarjar_rules: "jarjar-strip-annotations-rules.txt",
 }
 
 // Used when compiling higher-level code against core.current.stubs.
@@ -158,16 +166,16 @@
 java_library {
     name: "core-module-lib-stubs-for-system-modules-no-annotations",
     visibility: ["//visibility:private"],
+    defaults: [
+        "system-modules-no-annotations",
+    ],
     static_libs: [
         "core-module-lib-stubs-for-system-modules",
     ],
-    sdk_version: "none",
-    system_modules: "none",
     dist: {
         dest: "system-modules/module-lib/core-for-system-modules-no-annotations.jar",
         targets: dist_targets,
     },
-    jarjar_rules: "jarjar-strip-annotations-rules.txt",
 }
 
 // Used when compiling higher-level code with sdk_version "module_current"
@@ -212,16 +220,16 @@
 java_library {
     name: "legacy.core.platform.api.no.annotations.stubs",
     visibility: core_platform_visibility,
+    defaults: [
+        "system-modules-no-annotations",
+    ],
     hostdex: true,
     compile_dex: true,
 
-    sdk_version: "none",
-    system_modules: "none",
     static_libs: [
         "legacy.core.platform.api.stubs",
     ],
     patch_module: "java.base",
-    jarjar_rules: "jarjar-strip-annotations-rules.txt",
 }
 
 java_library {
@@ -247,16 +255,16 @@
 java_library {
     name: "stable.core.platform.api.no.annotations.stubs",
     visibility: core_platform_visibility,
+    defaults: [
+        "system-modules-no-annotations",
+    ],
     hostdex: true,
     compile_dex: true,
 
-    sdk_version: "none",
-    system_modules: "none",
     static_libs: [
         "stable.core.platform.api.stubs",
     ],
     patch_module: "java.base",
-    jarjar_rules: "jarjar-strip-annotations-rules.txt",
 }
 
 // Used when compiling higher-level code against *.core.platform.api.stubs.
@@ -307,12 +315,6 @@
         // the UnsupportedAppUsage, CorePlatformApi and IntraCoreApi
         // annotations.
         "art.module.api.annotations.for.system.modules",
-
-        // Make nullability annotations available when compiling public stubs.
-        // They are provided as a separate library because while the
-        // annotations are not themselves part of the public API provided by
-        // this module they are used in the stubs.
-        "stub-annotations",
     ],
 }
 
@@ -349,3 +351,7 @@
         "art-module-intra-core-api-stubs-system-modules-lib",
     ],
 }
+
+build = [
+    "TxtStubLibraries.bp",
+]
diff --git a/java/core-libraries/TxtStubLibraries.bp b/java/core-libraries/TxtStubLibraries.bp
new file mode 100644
index 0000000..813187e
--- /dev/null
+++ b/java/core-libraries/TxtStubLibraries.bp
@@ -0,0 +1,156 @@
+// Copyright (C) 2023 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.
+
+// This file contains java_system_modules provided by the SDK.
+// These system modules transitively depend on core stub libraries generated from .txt files.
+
+// Same as core-public-stubs-system-modules, but the stubs are generated from .txt files
+java_system_modules {
+    name: "core-public-stubs-system-modules.from-text",
+    visibility: ["//visibility:public"],
+    libs: [
+        "core-current-stubs-for-system-modules-no-annotations.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_library {
+    name: "core-current-stubs-for-system-modules-no-annotations.from-text",
+    visibility: ["//visibility:private"],
+    defaults: [
+        "system-modules-no-annotations",
+    ],
+    static_libs: [
+        "core.current.stubs.from-text",
+        "core-lambda-stubs.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+// Same as core-module-lib-stubs-system-modules, but the stubs are generated from .txt files
+java_system_modules {
+    name: "core-module-lib-stubs-system-modules.from-text",
+    visibility: ["//visibility:public"],
+    libs: [
+        "core-module-lib-stubs-for-system-modules-no-annotations.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_library {
+    name: "core-module-lib-stubs-for-system-modules-no-annotations.from-text",
+    visibility: ["//visibility:private"],
+    defaults: [
+        "system-modules-no-annotations",
+    ],
+    static_libs: [
+        "core.module_lib.stubs.from-text",
+        "core-lambda-stubs.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_library {
+    name: "core.module_lib.stubs.from-text",
+    static_libs: [
+        "art.module.public.api.stubs.module_lib.from-text",
+
+        // Replace the following with the module-lib correspondence when Conscrypt or i18N module
+        // provides @SystemApi(MODULE_LIBRARIES). Currently, assume that only ART module provides
+        // @SystemApi(MODULE_LIBRARIES).
+        "conscrypt.module.public.api.stubs.from-text",
+        "i18n.module.public.api.stubs.from-text",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+    visibility: ["//visibility:private"],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+// Same as legacy-core-platform-api-stubs-system-modules, but the stubs are generated from .txt files
+java_system_modules {
+    name: "legacy-core-platform-api-stubs-system-modules.from-text",
+    visibility: core_platform_visibility,
+    libs: [
+        "legacy.core.platform.api.no.annotations.stubs.from-text",
+        "core-lambda-stubs.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_library {
+    name: "legacy.core.platform.api.no.annotations.stubs.from-text",
+    visibility: core_platform_visibility,
+    defaults: [
+        "system-modules-no-annotations",
+    ],
+    hostdex: true,
+    compile_dex: true,
+
+    static_libs: [
+        "legacy.core.platform.api.stubs.from-text",
+    ],
+    patch_module: "java.base",
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+// Same as stable-core-platform-api-stubs-system-modules, but the stubs are generated from .txt files
+java_system_modules {
+    name: "stable-core-platform-api-stubs-system-modules.from-text",
+    visibility: core_platform_visibility,
+    libs: [
+        "stable.core.platform.api.no.annotations.stubs.from-text",
+        "core-lambda-stubs.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_library {
+    name: "stable.core.platform.api.no.annotations.stubs.from-text",
+    visibility: core_platform_visibility,
+    defaults: [
+        "system-modules-no-annotations",
+    ],
+    hostdex: true,
+    compile_dex: true,
+
+    static_libs: [
+        "stable.core.platform.api.stubs.from-text",
+    ],
+    patch_module: "java.base",
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
+
+java_api_library {
+    name: "core-lambda-stubs.from-text",
+    api_surface: "toolchain",
+    api_contributions: [
+        "art.module.toolchain.api.api.contribution",
+    ],
+    libs: [
+        // LambdaMetaFactory depends on CallSite etc. which is part of the Core API surface
+        "core.current.stubs.from-text",
+    ],
+    // TODO: Enable after stub generation from .txt file is available
+    enabled: false,
+}
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 4abdcc6..3581040 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -19,12 +19,16 @@
 	"io"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type DeviceHostConverter struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	android.BazelModuleBase
 
 	properties DeviceHostConverterProperties
 
@@ -76,6 +80,7 @@
 	module.AddProperties(&module.properties)
 
 	InitJavaModule(module, android.DeviceSupported)
+	android.InitBazelModule(module)
 	return module
 }
 
@@ -186,3 +191,32 @@
 		},
 	}
 }
+
+type bazelDeviceHostConverterAttributes struct {
+	Exports bazel.LabelListAttribute
+}
+
+func (d *DeviceHostConverter) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	ctx.CreateBazelTargetModule(
+		bazel.BazelTargetModuleProperties{
+			Rule_class:        "java_host_for_device",
+			Bzl_load_location: "//build/bazel/rules/java:host_for_device.bzl",
+		},
+		android.CommonAttributes{Name: d.Name()},
+		&bazelDeviceHostConverterAttributes{
+			Exports: bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, d.properties.Libs)),
+		},
+	)
+	neverLinkAttrs := &javaLibraryAttributes{
+		Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + d.Name()}),
+		Neverlink: bazel.BoolAttribute{Value: proptools.BoolPtr(true)},
+		javaCommonAttributes: &javaCommonAttributes{
+			Sdk_version: bazel.StringAttribute{Value: proptools.StringPtr("none")},
+		},
+	}
+	ctx.CreateBazelTargetModule(
+		javaLibraryBazelTargetModuleProperties(),
+		android.CommonAttributes{Name: d.Name() + "-neverlink"},
+		neverLinkAttrs)
+
+}
diff --git a/java/java.go b/java/java.go
index 499a6b6..2de4ea9 100644
--- a/java/java.go
+++ b/java/java.go
@@ -388,6 +388,8 @@
 	jniLibTag               = dependencyTag{name: "jnilib", runtimeLinked: true}
 	r8LibraryJarTag         = dependencyTag{name: "r8-libraryjar", runtimeLinked: true}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
+	javaApiContributionTag  = dependencyTag{name: "java-api-contribution"}
+	depApiSrcsTag           = dependencyTag{name: "dep-api-srcs"}
 	jniInstallTag           = installDependencyTag{name: "jni install"}
 	binaryInstallTag        = installDependencyTag{name: "binary install"}
 	usesLibReqTag           = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false)
@@ -1609,6 +1611,13 @@
 	})
 }
 
+type JavaApiLibraryDepsInfo struct {
+	JavaInfo
+	StubsSrcJar android.Path
+}
+
+var JavaApiLibraryDepsProvider = blueprint.NewProvider(JavaApiLibraryDepsInfo{})
+
 type ApiLibrary struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
@@ -1618,8 +1627,10 @@
 
 	properties JavaApiLibraryProperties
 
-	stubsSrcJar android.WritablePath
-	stubsJar    android.WritablePath
+	stubsSrcJar               android.WritablePath
+	stubsJar                  android.WritablePath
+	stubsJarWithoutStaticLibs android.WritablePath
+	extractedSrcJar           android.WritablePath
 	// .dex of stubs, used for hiddenapi processing
 	dexJarFile OptionalDexJarPath
 }
@@ -1645,8 +1656,13 @@
 	Libs []string
 
 	// List of java libs that this module has static dependencies to and will be
-	// passed in metalava invocation
+	// merge zipped after metalava invocation
 	Static_libs []string
+
+	// Java Api library to provide the full API surface text files and jar file.
+	// If this property is set, the provided full API surface text files and
+	// jar file are passed to metalava invocation.
+	Dep_api_srcs *string
 }
 
 func ApiLibraryFactory() android.Module {
@@ -1725,7 +1741,36 @@
 	}
 }
 
-var javaApiContributionTag = dependencyTag{name: "java-api-contribution"}
+// This method extracts the stub java files from the srcjar file provided from dep_api_srcs module
+// and replaces the java stubs generated by invoking metalava in this module.
+// This method is used because metalava can generate compilable from-text stubs only when
+// the codebase encompasses all classes listed in the input API text file, but a class can extend
+// a class that is not within the same API domain.
+func (al *ApiLibrary) extractApiSrcs(ctx android.ModuleContext, rule *android.RuleBuilder, stubsDir android.OptionalPath, depApiSrcsSrcJar android.Path) {
+	generatedStubsList := android.PathForModuleOut(ctx, "metalava", "sources.txt")
+	unzippedSrcJarDir := android.PathForModuleOut(ctx, "metalava", "unzipDir")
+
+	rule.Command().
+		BuiltTool("list_files").
+		Text(stubsDir.String()).
+		FlagWithOutput("--out ", generatedStubsList).
+		FlagWithArg("--extensions ", ".java").
+		FlagWithArg("--root ", unzippedSrcJarDir.String())
+
+	rule.Command().
+		Text("unzip").
+		Flag("-q").
+		Input(depApiSrcsSrcJar).
+		FlagWithArg("-d ", unzippedSrcJarDir.String())
+
+	rule.Command().
+		BuiltTool("soong_zip").
+		Flag("-srcjar").
+		Flag("-write_if_changed").
+		FlagWithArg("-C ", unzippedSrcJarDir.String()).
+		FlagWithInput("-l ", generatedStubsList).
+		FlagWithOutput("-o ", al.stubsSrcJar)
+}
 
 func (al *ApiLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
 	apiContributions := al.properties.Api_contributions
@@ -1734,6 +1779,9 @@
 	}
 	ctx.AddVariationDependencies(nil, libTag, al.properties.Libs...)
 	ctx.AddVariationDependencies(nil, staticLibTag, al.properties.Static_libs...)
+	if al.properties.Dep_api_srcs != nil {
+		ctx.AddVariationDependencies(nil, depApiSrcsTag, String(al.properties.Dep_api_srcs))
+	}
 }
 
 func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1754,6 +1802,7 @@
 	var srcFiles android.Paths
 	var classPaths android.Paths
 	var staticLibs android.Paths
+	var depApiSrcsStubsSrcJar android.Path
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		switch tag {
@@ -1770,6 +1819,10 @@
 		case staticLibTag:
 			provider := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
 			staticLibs = append(staticLibs, provider.HeaderJars...)
+		case depApiSrcsTag:
+			provider := ctx.OtherModuleProvider(dep, JavaApiLibraryDepsProvider).(JavaApiLibraryDepsInfo)
+			classPaths = append(classPaths, provider.HeaderJars...)
+			depApiSrcsStubsSrcJar = provider.StubsSrcJar
 		}
 	})
 
@@ -1780,21 +1833,31 @@
 		srcFiles = append(srcFiles, android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), api))
 	}
 
+	if srcFiles == nil {
+		ctx.ModuleErrorf("Error: %s has an empty api file.", ctx.ModuleName())
+	}
+
 	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
 	al.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar")
-	rule.Command().
-		BuiltTool("soong_zip").
-		Flag("-write_if_changed").
-		Flag("-jar").
-		FlagWithOutput("-o ", al.stubsSrcJar).
-		FlagWithArg("-C ", stubsDir.String()).
-		FlagWithArg("-D ", stubsDir.String())
+
+	if depApiSrcsStubsSrcJar != nil {
+		al.extractApiSrcs(ctx, rule, stubsDir, depApiSrcsStubsSrcJar)
+	} else {
+		rule.Command().
+			BuiltTool("soong_zip").
+			Flag("-write_if_changed").
+			Flag("-jar").
+			FlagWithOutput("-o ", al.stubsSrcJar).
+			FlagWithArg("-C ", stubsDir.String()).
+			FlagWithArg("-D ", stubsDir.String())
+	}
 
 	rule.Build("metalava", "metalava merged")
-	compiledStubs := android.PathForModuleOut(ctx, ctx.ModuleName(), "stubs.jar")
+
+	al.stubsJarWithoutStaticLibs = android.PathForModuleOut(ctx, ctx.ModuleName(), "stubs.jar")
 	al.stubsJar = android.PathForModuleOut(ctx, ctx.ModuleName(), fmt.Sprintf("%s.jar", ctx.ModuleName()))
 
 	var flags javaBuilderFlags
@@ -1802,14 +1865,14 @@
 	flags.javacFlags = strings.Join(al.properties.Javacflags, " ")
 	flags.classpath = classpath(classPaths)
 
-	TransformJavaToClasses(ctx, compiledStubs, 0, android.Paths{},
+	TransformJavaToClasses(ctx, al.stubsJarWithoutStaticLibs, 0, android.Paths{},
 		android.Paths{al.stubsSrcJar}, flags, android.Paths{})
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().
 		BuiltTool("merge_zips").
 		Output(al.stubsJar).
-		Inputs(android.Paths{compiledStubs}).
+		Inputs(android.Paths{al.stubsJarWithoutStaticLibs}).
 		Inputs(staticLibs)
 	builder.Build("merge_zips", "merge jar files")
 
@@ -1835,6 +1898,13 @@
 		ImplementationJars:             android.PathsIfNonNil(al.stubsJar),
 		AidlIncludeDirs:                android.Paths{},
 	})
+
+	ctx.SetProvider(JavaApiLibraryDepsProvider, JavaApiLibraryDepsInfo{
+		JavaInfo: JavaInfo{
+			HeaderJars: android.PathsIfNonNil(al.stubsJar),
+		},
+		StubsSrcJar: al.stubsSrcJar,
+	})
 }
 
 func (al *ApiLibrary) DexJarBuildPath() OptionalDexJarPath {
@@ -2670,9 +2740,11 @@
 type javaCommonAttributes struct {
 	*javaResourcesAttributes
 	*kotlinAttributes
-	Srcs      bazel.LabelListAttribute
-	Plugins   bazel.LabelListAttribute
-	Javacopts bazel.StringListAttribute
+	Srcs         bazel.LabelListAttribute
+	Plugins      bazel.LabelListAttribute
+	Javacopts    bazel.StringListAttribute
+	Sdk_version  bazel.StringAttribute
+	Java_version bazel.StringAttribute
 }
 
 type javaDependencyLabels struct {
@@ -2772,7 +2844,7 @@
 			ctx.CreateBazelTargetModule(
 				bazel.BazelTargetModuleProperties{
 					Rule_class:        "aidl_library",
-					Bzl_load_location: "//build/bazel/rules/aidl:library.bzl",
+					Bzl_load_location: "//build/bazel/rules/aidl:aidl_library.bzl",
 				},
 				android.CommonAttributes{Name: aidlLibName},
 				&aidlLibraryAttributes{
@@ -2787,7 +2859,7 @@
 		ctx.CreateBazelTargetModule(
 			bazel.BazelTargetModuleProperties{
 				Rule_class:        "java_aidl_library",
-				Bzl_load_location: "//build/bazel/rules/java:aidl_library.bzl",
+				Bzl_load_location: "//build/bazel/rules/java:java_aidl_library.bzl",
 			},
 			android.CommonAttributes{Name: javaAidlLibName},
 			&javaAidlLibraryAttributes{
@@ -2803,10 +2875,6 @@
 	if m.properties.Javacflags != nil {
 		javacopts = append(javacopts, m.properties.Javacflags...)
 	}
-	if m.properties.Java_version != nil {
-		javaVersion := normalizeJavaVersion(ctx, *m.properties.Java_version).String()
-		javacopts = append(javacopts, fmt.Sprintf("-source %s -target %s", javaVersion, javaVersion))
-	}
 
 	epEnabled := m.properties.Errorprone.Enabled
 	//TODO(b/227504307) add configuration that depends on RUN_ERROR_PRONE environment variable
@@ -2820,7 +2888,9 @@
 		Plugins: bazel.MakeLabelListAttribute(
 			android.BazelLabelForModuleDeps(ctx, m.properties.Plugins),
 		),
-		Javacopts: bazel.MakeStringListAttribute(javacopts),
+		Javacopts:    bazel.MakeStringListAttribute(javacopts),
+		Java_version: bazel.StringAttribute{Value: m.properties.Java_version},
+		Sdk_version:  bazel.StringAttribute{Value: m.deviceProperties.Sdk_version},
 	}
 
 	for axis, configToProps := range archVariantProps {
@@ -2837,10 +2907,6 @@
 		}
 	}
 
-	if m.properties.Static_libs != nil {
-		staticDeps.Append(android.BazelLabelForModuleDeps(ctx, android.LastUniqueStrings(android.CopyOf(m.properties.Static_libs))))
-	}
-
 	protoDepLabel := bp2buildProto(ctx, &m.Module, srcPartitions[protoSrcPartition])
 	// Soong does not differentiate between a java_library and the Bazel equivalent of
 	// a java_proto_library + proto_library pair. Instead, in Soong proto sources are
@@ -2852,7 +2918,18 @@
 
 	depLabels := &javaDependencyLabels{}
 	depLabels.Deps = deps
-	depLabels.StaticDeps = bazel.MakeLabelListAttribute(staticDeps)
+
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*CommonProperties); ok {
+				archStaticLibs := android.BazelLabelForModuleDeps(
+					ctx,
+					android.LastUniqueStrings(android.CopyOf(archProps.Static_libs)))
+				depLabels.StaticDeps.SetSelectValue(axis, config, archStaticLibs)
+			}
+		}
+	}
+	depLabels.StaticDeps.Value.Append(staticDeps)
 
 	hasKotlin := !kotlinSrcs.IsEmpty()
 	commonAttrs.kotlinAttributes = &kotlinAttributes{
@@ -2904,19 +2981,9 @@
 	deps := depLabels.Deps
 	if !commonAttrs.Srcs.IsEmpty() {
 		deps.Append(depLabels.StaticDeps) // we should only append these if there are sources to use them
-
-		sdkVersion := m.SdkVersion(ctx)
-		if sdkVersion.Kind == android.SdkPublic && sdkVersion.ApiLevel == android.FutureApiLevel {
-			// TODO(b/220869005) remove forced dependency on current public android.jar
-			deps.Add(bazel.MakeLabelAttribute("//prebuilts/sdk:public_current_android_sdk_java_import"))
-		} else if sdkVersion.Kind == android.SdkSystem && sdkVersion.ApiLevel == android.FutureApiLevel {
-			// TODO(b/215230098) remove forced dependency on current public android.jar
-			deps.Add(bazel.MakeLabelAttribute("//prebuilts/sdk:system_current_android_sdk_java_import"))
-		}
 	} else if !deps.IsEmpty() {
 		ctx.ModuleErrorf("Module has direct dependencies but no sources. Bazel will not allow this.")
 	}
-
 	var props bazel.BazelTargetModuleProperties
 	attrs := &javaLibraryAttributes{
 		javaCommonAttributes: commonAttrs,
@@ -2936,6 +3003,10 @@
 	neverLinkAttrs := &javaLibraryAttributes{
 		Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
 		Neverlink: bazel.BoolAttribute{Value: &neverlinkProp},
+		javaCommonAttributes: &javaCommonAttributes{
+			Sdk_version:  bazel.StringAttribute{Value: m.deviceProperties.Sdk_version},
+			Java_version: bazel.StringAttribute{Value: m.properties.Java_version},
+		},
 	}
 	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name + "-neverlink"}, neverLinkAttrs)
 
@@ -3075,6 +3146,9 @@
 	neverlinkAttrs := &javaLibraryAttributes{
 		Neverlink: bazel.BoolAttribute{Value: &neverlink},
 		Exports:   bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + name}),
+		javaCommonAttributes: &javaCommonAttributes{
+			Sdk_version: bazel.StringAttribute{Value: proptools.StringPtr("none")},
+		},
 	}
 	ctx.CreateBazelTargetModule(
 		javaLibraryBazelTargetModuleProperties(),
diff --git a/java/java_test.go b/java/java_test.go
index 68b749b..553b762 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2209,6 +2209,50 @@
 	}
 }
 
+func TestJavaApiLibraryDepApiSrcs(t *testing.T) {
+	provider_bp_a := `
+	java_api_contribution {
+		name: "foo1",
+		api_file: "foo1.txt",
+	}
+	`
+	provider_bp_b := `
+	java_api_contribution {
+		name: "foo2",
+		api_file: "foo2.txt",
+	}
+	`
+	lib_bp_a := `
+	java_api_library {
+		name: "lib1",
+		api_surface: "public",
+		api_contributions: ["foo1", "foo2"],
+	}
+	`
+
+	ctx, _ := testJavaWithFS(t, `
+		java_api_library {
+			name: "bar1",
+			api_surface: "public",
+			api_contributions: ["foo1"],
+			dep_api_srcs: "lib1",
+		}
+		`,
+		map[string][]byte{
+			"a/Android.bp": []byte(provider_bp_a),
+			"b/Android.bp": []byte(provider_bp_b),
+			"c/Android.bp": []byte(lib_bp_a),
+		})
+
+	m := ctx.ModuleForTests("bar1", "android_common")
+	manifest := m.Output("metalava.sbox.textproto")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest)
+	manifestCommand := sboxProto.Commands[0].GetCommand()
+
+	android.AssertStringDoesContain(t, "Command expected to contain module srcjar file", manifestCommand, "bar1-stubs.srcjar")
+	android.AssertStringDoesContain(t, "Command expected to contain output files list text file flag", manifestCommand, "--out __SBOX_SANDBOX_DIR__/out/sources.txt")
+}
+
 func TestTradefedOptions(t *testing.T) {
 	result := PrepareForTestWithJavaBuildComponents.RunTestWithBp(t, `
 java_test_host {
diff --git a/java/proto.go b/java/proto.go
index 5280077..c732d98 100644
--- a/java/proto.go
+++ b/java/proto.go
@@ -143,7 +143,9 @@
 }
 
 type protoAttributes struct {
-	Deps bazel.LabelListAttribute
+	Deps         bazel.LabelListAttribute
+	Sdk_version  bazel.StringAttribute
+	Java_version bazel.StringAttribute
 }
 
 func bp2buildProto(ctx android.Bp2buildMutatorContext, m *Module, protoSrcs bazel.LabelListAttribute) *bazel.Label {
@@ -175,8 +177,11 @@
 	}
 
 	protoLabel := bazel.Label{Label: ":" + m.Name() + "_proto"}
-	var protoAttrs protoAttributes
-	protoAttrs.Deps.SetValue(bazel.LabelList{Includes: []bazel.Label{protoLabel}})
+	protoAttrs := &protoAttributes{
+		Deps:         bazel.MakeSingleLabelListAttribute(protoLabel),
+		Java_version: bazel.StringAttribute{Value: m.properties.Java_version},
+		Sdk_version:  bazel.StringAttribute{Value: m.deviceProperties.Sdk_version},
+	}
 
 	name := m.Name() + suffix
 
@@ -186,7 +191,7 @@
 			Bzl_load_location: "//build/bazel/rules/java:proto.bzl",
 		},
 		android.CommonAttributes{Name: name},
-		&protoAttrs)
+		protoAttrs)
 
 	return &bazel.Label{Label: ":" + name}
 }
diff --git a/java/sdk.go b/java/sdk.go
index 72a5006..8b4918a 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -148,10 +148,11 @@
 	toModule := func(module string, aidl android.Path) sdkDep {
 		// Select the kind of system modules needed for the sdk version.
 		systemModulesKind := systemModuleKind(sdkVersion.Kind, android.FutureApiLevel)
+		systemModules := android.JavaApiLibraryName(ctx.Config(), fmt.Sprintf("core-%s-stubs-system-modules", systemModulesKind))
 		return sdkDep{
 			useModule:          true,
-			bootclasspath:      []string{module, config.DefaultLambdaStubsLibrary},
-			systemModules:      fmt.Sprintf("core-%s-stubs-system-modules", systemModulesKind),
+			bootclasspath:      []string{module, android.JavaApiLibraryName(ctx.Config(), config.DefaultLambdaStubsLibrary)},
+			systemModules:      systemModules,
 			java9Classpath:     []string{module},
 			frameworkResModule: "framework-res",
 			aidl:               android.OptionalPathForPath(aidl),
@@ -196,8 +197,8 @@
 	case android.SdkCore:
 		return sdkDep{
 			useModule:        true,
-			bootclasspath:    []string{"core.current.stubs", config.DefaultLambdaStubsLibrary},
-			systemModules:    "core-public-stubs-system-modules",
+			bootclasspath:    []string{android.SdkCore.JavaLibraryName(ctx.Config()), android.JavaApiLibraryName(ctx.Config(), config.DefaultLambdaStubsLibrary)},
+			systemModules:    android.JavaApiLibraryName(ctx.Config(), "core-public-stubs-system-modules"),
 			noFrameworksLibs: true,
 		}
 	case android.SdkModule:
diff --git a/java/testing.go b/java/testing.go
index 63d7dba..0764d26 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -368,6 +368,7 @@
 		"core.current.stubs",
 		"legacy.core.platform.api.stubs",
 		"stable.core.platform.api.stubs",
+
 		"kotlin-stdlib",
 		"kotlin-stdlib-jdk7",
 		"kotlin-stdlib-jdk8",
@@ -387,6 +388,27 @@
 		`, extra)
 	}
 
+	extraApiLibraryModules := map[string]string{
+		"android_stubs_current.from-text":               "api/current.txt",
+		"android_system_stubs_current.from-text":        "api/system-current.txt",
+		"android_test_stubs_current.from-text":          "api/test-current.txt",
+		"android_module_lib_stubs_current.from-text":    "api/module-lib-current.txt",
+		"android_system_server_stubs_current.from-text": "api/system-server-current.txt",
+		"core.current.stubs.from-text":                  "api/current.txt",
+		"legacy.core.platform.api.stubs.from-text":      "api/current.txt",
+		"stable.core.platform.api.stubs.from-text":      "api/current.txt",
+		"core-lambda-stubs.from-text":                   "api/current.txt",
+	}
+
+	for libName, apiFile := range extraApiLibraryModules {
+		bp += fmt.Sprintf(`
+            java_api_library {
+                name: "%s",
+                api_files: ["%s"],
+            }
+        `, libName, apiFile)
+	}
+
 	bp += `
 		java_library {
 			name: "framework",
@@ -409,6 +431,10 @@
 		"core-module-lib-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
 		"stable-core-platform-api-stubs-system-modules",
+		"core-public-stubs-system-modules.from-text",
+		"core-module-lib-stubs-system-modules.from-text",
+		"legacy-core-platform-api-stubs-system-modules.from-text",
+		"stable-core-platform-api-stubs-system-modules.from-text",
 	}
 
 	for _, extra := range systemModules {
diff --git a/mk2rbc/soong_variables.go b/mk2rbc/soong_variables.go
index a52ec4f..7a6aa5f 100644
--- a/mk2rbc/soong_variables.go
+++ b/mk2rbc/soong_variables.go
@@ -67,7 +67,11 @@
 	var valueType starlarkType
 	switch typeString {
 	case "bool":
-		valueType = starlarkTypeBool
+		// TODO: We run into several issues later on if we type this as a bool:
+		//    - We still assign bool-typed variables to strings
+		//    - When emitting the final results as make code, some bool's false values have to
+		//      be an empty string, and some have to be false in order to match the make variables.
+		valueType = starlarkTypeString
 	case "csv":
 		// Only PLATFORM_VERSION_ALL_CODENAMES, and it's a list
 		valueType = starlarkTypeList
diff --git a/rust/afdo.go b/rust/afdo.go
index 996fd7e..3534ee6 100644
--- a/rust/afdo.go
+++ b/rust/afdo.go
@@ -17,7 +17,10 @@
 import (
 	"fmt"
 
+	"android/soong/android"
 	"android/soong/cc"
+
+	"github.com/google/blueprint"
 )
 
 const afdoFlagFormat = "-Zprofile-sample-use=%s"
@@ -30,19 +33,49 @@
 	return []interface{}{&afdo.Properties}
 }
 
-func (afdo *afdo) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
+func (afdo *afdo) addDep(ctx BaseModuleContext, actx android.BottomUpMutatorContext) {
+	// afdo is not supported outside of Android
+	if ctx.Host() {
+		return
+	}
+
+	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled() {
+		fdoProfileName, err := actx.DeviceConfig().AfdoProfile(actx.ModuleName())
+		if err != nil {
+			ctx.ModuleErrorf("%s", err.Error())
+		}
+		if fdoProfileName != nil {
+			actx.AddFarVariationDependencies(
+				[]blueprint.Variation{
+					{Mutator: "arch", Variation: actx.Target().ArchVariation()},
+					{Mutator: "os", Variation: "android"},
+				},
+				cc.FdoProfileTag,
+				[]string{*fdoProfileName}...,
+			)
+		}
+	}
+}
+
+func (afdo *afdo) flags(ctx android.ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
 	if ctx.Host() {
 		return flags, deps
 	}
 
-	if afdo != nil && afdo.Properties.Afdo {
-		if profileFile := afdo.Properties.GetAfdoProfileFile(ctx, ctx.ModuleName()); profileFile.Valid() {
-			profileUseFlag := fmt.Sprintf(afdoFlagFormat, profileFile)
+	if !afdo.Properties.Afdo {
+		return flags, deps
+	}
+
+	ctx.VisitDirectDepsWithTag(cc.FdoProfileTag, func(m android.Module) {
+		if ctx.OtherModuleHasProvider(m, cc.FdoProfileProvider) {
+			info := ctx.OtherModuleProvider(m, cc.FdoProfileProvider).(cc.FdoProfileInfo)
+			path := info.Path
+			profileUseFlag := fmt.Sprintf(afdoFlagFormat, path.String())
 			flags.RustFlags = append(flags.RustFlags, profileUseFlag)
 
-			profileFilePath := profileFile.Path()
-			deps.AfdoProfiles = append(deps.AfdoProfiles, profileFilePath)
+			deps.AfdoProfiles = append(deps.AfdoProfiles, path)
 		}
-	}
+	})
+
 	return flags, deps
 }
diff --git a/rust/afdo_test.go b/rust/afdo_test.go
index fa20eef..0cdf704 100644
--- a/rust/afdo_test.go
+++ b/rust/afdo_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/cc"
 	"fmt"
 	"strings"
 	"testing"
@@ -31,13 +32,27 @@
 `
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo.afdo", ""),
+		cc.PrepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("afdo_profiles_package/foo.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"foo://afdo_profiles_package:foo_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "foo_afdo",
+					profile: "foo.afdo",
+				}
+			`),
+		}.AddToFixture(),
 		rustMockedFiles.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	foo := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
 
-	expectedCFlag := fmt.Sprintf(afdoFlagFormat, "toolchain/pgo-profiles/sampling/foo.afdo")
+	expectedCFlag := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo.afdo")
 
 	if !strings.Contains(foo.Args["rustcFlags"], expectedCFlag) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", expectedCFlag, foo.Args["rustcFlags"])
@@ -55,16 +70,37 @@
 `
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm.afdo", ""),
-		android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/foo_arm64.afdo", ""),
+		cc.PrepareForTestWithFdoProfile,
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm.afdo", ""),
+		android.FixtureAddTextFile("afdo_profiles_package/foo_arm64.afdo", ""),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.AfdoProfiles = []string{
+				"foo://afdo_profiles_package:foo_afdo",
+			}
+		}),
+		android.MockFS{
+			"afdo_profiles_package/Android.bp": []byte(`
+				fdo_profile {
+					name: "foo_afdo",
+					arch: {
+						arm: {
+							profile: "foo_arm.afdo",
+						},
+						arm64: {
+							profile: "foo_arm64.afdo",
+						}
+					}
+				}
+			`),
+		}.AddToFixture(),
 		rustMockedFiles.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
 	fooArm := result.ModuleForTests("foo", "android_arm_armv7-a-neon").Rule("rustc")
 	fooArm64 := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
 
-	expectedCFlagArm := fmt.Sprintf(afdoFlagFormat, "toolchain/pgo-profiles/sampling/foo_arm.afdo")
-	expectedCFlagArm64 := fmt.Sprintf(afdoFlagFormat, "toolchain/pgo-profiles/sampling/foo_arm64.afdo")
+	expectedCFlagArm := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo_arm.afdo")
+	expectedCFlagArm64 := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo_arm64.afdo")
 
 	if !strings.Contains(fooArm.Args["rustcFlags"], expectedCFlagArm) {
 		t.Errorf("Expected 'fooArm' to enable afdo, but did not find %q in cflags %q", expectedCFlagArm, fooArm.Args["rustcFlags"])
diff --git a/rust/binary.go b/rust/binary.go
index 056888e..2de92c1 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -72,11 +72,14 @@
 func (binary *binaryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = binary.baseCompiler.compilerFlags(ctx, flags)
 
+	if ctx.Os().Linux() {
+		flags.LinkFlags = append(flags.LinkFlags, "-Wl,--gc-sections")
+	}
+
 	if ctx.toolchain().Bionic() {
 		// no-undefined-version breaks dylib compilation since __rust_*alloc* functions aren't defined,
 		// but we can apply this to binaries.
 		flags.LinkFlags = append(flags.LinkFlags,
-			"-Wl,--gc-sections",
 			"-Wl,-z,nocopyreloc",
 			"-Wl,--no-undefined-version")
 
@@ -136,7 +139,7 @@
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
-	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects.Strings()...)
 
 	if binary.stripper.NeedsStrip(ctx) {
 		strippedOutputFile := outputFile
diff --git a/rust/binary_test.go b/rust/binary_test.go
index 7dac249..dd4f993 100644
--- a/rust/binary_test.go
+++ b/rust/binary_test.go
@@ -123,7 +123,7 @@
 			bootstrap: true,
 		}`)
 
-	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
+	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustLink")
 
 	flag := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker64"
 	if !strings.Contains(foo.Args["linkFlags"], flag) {
@@ -140,10 +140,11 @@
 		}`)
 
 	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
+	fizzOutLink := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustLink")
 	fizzMod := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Module().(*Module)
 
 	flags := fizzOut.Args["rustcFlags"]
-	linkFlags := fizzOut.Args["linkFlags"]
+	linkFlags := fizzOutLink.Args["linkFlags"]
 	if !strings.Contains(flags, "-C relocation-model=static") {
 		t.Errorf("static binary missing '-C relocation-model=static' in rustcFlags, found: %#v", flags)
 	}
@@ -173,7 +174,7 @@
 			name: "libfoo",
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Rule("rustc")
+	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Rule("rustLink")
 	linkFlags := fizzBuzz.Args["linkFlags"]
 	if !strings.Contains(linkFlags, "/libfoo.so") {
 		t.Errorf("missing shared dependency 'libfoo.so' in linkFlags: %#v", linkFlags)
diff --git a/rust/builder.go b/rust/builder.go
index b89e7ad..0aa2225 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -26,14 +26,14 @@
 
 var (
 	_     = pctx.SourcePathVariable("rustcCmd", "${config.RustBin}/rustc")
+	_     = pctx.SourcePathVariable("mkcraterspCmd", "build/soong/scripts/mkcratersp.py")
 	rustc = pctx.AndroidStaticRule("rustc",
 		blueprint.RuleParams{
 			Command: "$envVars $rustcCmd " +
-				"-C linker=${config.RustLinker} " +
-				"-C link-args=\"${crtBegin} ${config.RustLinkerArgs} ${linkFlags} ${crtEnd}\" " +
+				"-C linker=$mkcraterspCmd " +
 				"--emit link -o $out --emit dep-info=$out.d.raw $in ${libFlags} $rustcFlags" +
 				" && grep \"^$out:\" $out.d.raw > $out.d",
-			CommandDeps: []string{"$rustcCmd"},
+			CommandDeps: []string{"$rustcCmd", "$mkcraterspCmd"},
 			// Rustc deps-info writes out make compatible dep files: https://github.com/rust-lang/rust/issues/7633
 			// Rustc emits unneeded dependency lines for the .d and input .rs files.
 			// Those extra lines cause ninja warning:
@@ -42,7 +42,12 @@
 			Deps:    blueprint.DepsGCC,
 			Depfile: "$out.d",
 		},
-		"rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars")
+		"rustcFlags", "libFlags", "envVars")
+	rustLink = pctx.AndroidStaticRule("rustLink",
+		blueprint.RuleParams{
+			Command: "${config.RustLinker} -o $out ${crtBegin} ${config.RustLinkerArgs} @$in ${linkFlags} ${crtEnd}",
+		},
+		"linkFlags", "crtBegin", "crtEnd")
 
 	_       = pctx.SourcePathVariable("rustdocCmd", "${config.RustBin}/rustdoc")
 	rustdoc = pctx.AndroidStaticRule("rustdoc",
@@ -101,14 +106,13 @@
 				`KYTHE_CANONICALIZE_VNAME_PATHS=prefer-relative ` +
 				`$rustExtractor $envVars ` +
 				`$rustcCmd ` +
-				`-C linker=${config.RustLinker} ` +
-				`-C link-args="${crtBegin} ${config.RustLinkerArgs} ${linkFlags} ${crtEnd}" ` +
+				`-C linker=true ` +
 				`$in ${libFlags} $rustcFlags`,
 			CommandDeps:    []string{"$rustExtractor", "$kytheVnames"},
 			Rspfile:        "${out}.rsp",
 			RspfileContent: "$in",
 		},
-		"rustcFlags", "linkFlags", "libFlags", "crtBegin", "crtEnd", "envVars")
+		"rustcFlags", "libFlags", "envVars")
 )
 
 type buildOutput struct {
@@ -208,6 +212,9 @@
 			outDirPrefix = ""
 		}
 		envVars = append(envVars, "OUT_DIR="+filepath.Join(outDirPrefix, moduleGenDir.String()))
+	} else {
+		// TODO(pcc): Change this to "OUT_DIR=" after fixing crates to not rely on this value.
+		envVars = append(envVars, "OUT_DIR=out")
 	}
 
 	return envVars
@@ -217,11 +224,9 @@
 	outputFile android.WritablePath, crateType string) buildOutput {
 
 	var inputs android.Paths
-	var implicits android.Paths
-	var orderOnly android.Paths
+	var implicits, linkImplicits, linkOrderOnly android.Paths
 	var output buildOutput
 	var rustcFlags, linkFlags []string
-	var implicitOutputs android.WritablePaths
 
 	output.outputFile = outputFile
 	crateName := ctx.RustModule().CrateName()
@@ -278,15 +283,15 @@
 	implicits = append(implicits, rustLibsToPaths(deps.RLibs)...)
 	implicits = append(implicits, rustLibsToPaths(deps.DyLibs)...)
 	implicits = append(implicits, rustLibsToPaths(deps.ProcMacros)...)
-	implicits = append(implicits, deps.StaticLibs...)
-	implicits = append(implicits, deps.SharedLibDeps...)
-	implicits = append(implicits, deps.srcProviderFiles...)
 	implicits = append(implicits, deps.AfdoProfiles...)
+	implicits = append(implicits, deps.srcProviderFiles...)
+	implicits = append(implicits, deps.WholeStaticLibs...)
 
-	implicits = append(implicits, deps.CrtBegin...)
-	implicits = append(implicits, deps.CrtEnd...)
+	linkImplicits = append(linkImplicits, deps.LibDeps...)
+	linkImplicits = append(linkImplicits, deps.CrtBegin...)
+	linkImplicits = append(linkImplicits, deps.CrtEnd...)
 
-	orderOnly = append(orderOnly, deps.SharedLibs...)
+	linkOrderOnly = append(linkOrderOnly, deps.linkObjects...)
 
 	if len(deps.SrcDeps) > 0 {
 		moduleGenDir := ctx.RustModule().compiler.CargoOutDir()
@@ -325,16 +330,16 @@
 		}
 	}
 
+	envVars = append(envVars, "AR=${cc_config.ClangBin}/llvm-ar")
+
 	if flags.Clippy {
 		clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy")
 		ctx.Build(pctx, android.BuildParams{
-			Rule:            clippyDriver,
-			Description:     "clippy " + main.Rel(),
-			Output:          clippyFile,
-			ImplicitOutputs: nil,
-			Inputs:          inputs,
-			Implicits:       implicits,
-			OrderOnly:       orderOnly,
+			Rule:        clippyDriver,
+			Description: "clippy " + main.Rel(),
+			Output:      clippyFile,
+			Inputs:      inputs,
+			Implicits:   implicits,
 			Args: map[string]string{
 				"rustcFlags":  strings.Join(rustcFlags, " "),
 				"libFlags":    strings.Join(libFlags, " "),
@@ -346,24 +351,41 @@
 		implicits = append(implicits, clippyFile)
 	}
 
+	rustcOutputFile := outputFile
+	usesLinker := crateType == "bin" || crateType == "dylib" || crateType == "cdylib" || crateType == "proc-macro"
+	if usesLinker {
+		rustcOutputFile = android.PathForModuleOut(ctx, outputFile.Base()+".rsp")
+	}
+
 	ctx.Build(pctx, android.BuildParams{
-		Rule:            rustc,
-		Description:     "rustc " + main.Rel(),
-		Output:          outputFile,
-		ImplicitOutputs: implicitOutputs,
-		Inputs:          inputs,
-		Implicits:       implicits,
-		OrderOnly:       orderOnly,
+		Rule:        rustc,
+		Description: "rustc " + main.Rel(),
+		Output:      rustcOutputFile,
+		Inputs:      inputs,
+		Implicits:   implicits,
 		Args: map[string]string{
 			"rustcFlags": strings.Join(rustcFlags, " "),
-			"linkFlags":  strings.Join(linkFlags, " "),
 			"libFlags":   strings.Join(libFlags, " "),
-			"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
-			"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
 			"envVars":    strings.Join(envVars, " "),
 		},
 	})
 
+	if usesLinker {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        rustLink,
+			Description: "rustLink " + main.Rel(),
+			Output:      outputFile,
+			Inputs:      android.Paths{rustcOutputFile},
+			Implicits:   linkImplicits,
+			OrderOnly:   linkOrderOnly,
+			Args: map[string]string{
+				"linkFlags": strings.Join(linkFlags, " "),
+				"crtBegin":  strings.Join(deps.CrtBegin.Strings(), " "),
+				"crtEnd":    strings.Join(deps.CrtEnd.Strings(), " "),
+			},
+		})
+	}
+
 	if flags.EmitXrefs {
 		kytheFile := android.PathForModuleOut(ctx, outputFile.Base()+".kzip")
 		ctx.Build(pctx, android.BuildParams{
@@ -372,13 +394,9 @@
 			Output:      kytheFile,
 			Inputs:      inputs,
 			Implicits:   implicits,
-			OrderOnly:   orderOnly,
 			Args: map[string]string{
 				"rustcFlags": strings.Join(rustcFlags, " "),
-				"linkFlags":  strings.Join(linkFlags, " "),
 				"libFlags":   strings.Join(libFlags, " "),
-				"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
-				"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
 				"envVars":    strings.Join(envVars, " "),
 			},
 		})
diff --git a/rust/coverage.go b/rust/coverage.go
index bc6504d..5216d60 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -65,7 +65,7 @@
 			"-C instrument-coverage", "-g")
 		flags.LinkFlags = append(flags.LinkFlags,
 			profileInstrFlag, "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,open")
-		deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile().Path())
+		deps.LibDeps = append(deps.LibDeps, coverage.OutputFile().Path())
 
 		// no_std modules are missing libprofiler_builtins which provides coverage, so we need to add it as a dependency.
 		if rustModule, ok := ctx.Module().(*Module); ok && rustModule.compiler.noStdlibs() {
diff --git a/rust/coverage_test.go b/rust/coverage_test.go
index 0f599d7..64077cf 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -55,6 +55,10 @@
 	libbarNoCov := ctx.ModuleForTests("libbar_nocov", "android_arm64_armv8-a_dylib").Rule("rustc")
 	fizzCov := ctx.ModuleForTests("fizz_cov", "android_arm64_armv8-a_cov").Rule("rustc")
 	buzzNoCov := ctx.ModuleForTests("buzzNoCov", "android_arm64_armv8-a").Rule("rustc")
+	libfooCovLink := ctx.ModuleForTests("libfoo_cov", "android_arm64_armv8-a_dylib_cov").Rule("rustLink")
+	libbarNoCovLink := ctx.ModuleForTests("libbar_nocov", "android_arm64_armv8-a_dylib").Rule("rustLink")
+	fizzCovLink := ctx.ModuleForTests("fizz_cov", "android_arm64_armv8-a_cov").Rule("rustLink")
+	buzzNoCovLink := ctx.ModuleForTests("buzzNoCov", "android_arm64_armv8-a").Rule("rustLink")
 
 	rustcCoverageFlags := []string{"-C instrument-coverage", " -g "}
 	for _, flag := range rustcCoverageFlags {
@@ -80,17 +84,17 @@
 		missingErrorStr := "missing rust linker flag '%s' for '%s' module with coverage enabled; rustcFlags: %#v"
 		containsErrorStr := "contains rust linker flag '%s' for '%s' module with coverage disabled; rustcFlags: %#v"
 
-		if !strings.Contains(fizzCov.Args["linkFlags"], flag) {
-			t.Fatalf(missingErrorStr, flag, "fizz_cov", fizzCov.Args["linkFlags"])
+		if !strings.Contains(fizzCovLink.Args["linkFlags"], flag) {
+			t.Fatalf(missingErrorStr, flag, "fizz_cov", fizzCovLink.Args["linkFlags"])
 		}
-		if !strings.Contains(libfooCov.Args["linkFlags"], flag) {
-			t.Fatalf(missingErrorStr, flag, "libfoo_cov dylib", libfooCov.Args["linkFlags"])
+		if !strings.Contains(libfooCovLink.Args["linkFlags"], flag) {
+			t.Fatalf(missingErrorStr, flag, "libfoo_cov dylib", libfooCovLink.Args["linkFlags"])
 		}
-		if strings.Contains(buzzNoCov.Args["linkFlags"], flag) {
-			t.Fatalf(containsErrorStr, flag, "buzzNoCov", buzzNoCov.Args["linkFlags"])
+		if strings.Contains(buzzNoCovLink.Args["linkFlags"], flag) {
+			t.Fatalf(containsErrorStr, flag, "buzzNoCov", buzzNoCovLink.Args["linkFlags"])
 		}
-		if strings.Contains(libbarNoCov.Args["linkFlags"], flag) {
-			t.Fatalf(containsErrorStr, flag, "libbar_cov", libbarNoCov.Args["linkFlags"])
+		if strings.Contains(libbarNoCovLink.Args["linkFlags"], flag) {
+			t.Fatalf(containsErrorStr, flag, "libbar_cov", libbarNoCovLink.Args["linkFlags"])
 		}
 	}
 
@@ -103,7 +107,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a_cov").Rule("rustc")
+	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a_cov").Rule("rustLink")
 	if !strings.Contains(fizz.Args["linkFlags"], "libprofile-clang-extras.a") {
 		t.Fatalf("missing expected coverage 'libprofile-clang-extras' dependency in linkFlags: %#v", fizz.Args["linkFlags"])
 	}
diff --git a/rust/library.go b/rust/library.go
index bc9c9aa..a3a5672 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -520,7 +520,7 @@
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
-	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects.Strings()...)
 
 	if library.dylib() {
 		// We need prefer-dynamic for now to avoid linking in the static stdlib. See:
@@ -543,6 +543,7 @@
 	if library.rlib() || library.dylib() {
 		library.flagExporter.exportLinkDirs(deps.linkDirs...)
 		library.flagExporter.exportLinkObjects(deps.linkObjects...)
+		library.flagExporter.exportLibDeps(deps.LibDeps...)
 	}
 
 	if library.static() || library.shared() {
diff --git a/rust/library_test.go b/rust/library_test.go
index e3e4d0f..d4b525f 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -148,7 +148,7 @@
 
 	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared")
 
-	libfooOutput := libfoo.Rule("rustc")
+	libfooOutput := libfoo.Rule("rustLink")
 	if !strings.Contains(libfooOutput.Args["linkFlags"], "-Wl,-soname=libfoo.so") {
 		t.Errorf("missing expected -Wl,-soname linker flag for libfoo shared lib, linkFlags: %#v",
 			libfooOutput.Args["linkFlags"])
diff --git a/rust/rust.go b/rust/rust.go
index f85babc..7b520cd 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -39,7 +39,6 @@
 		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
 		ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
 		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
-
 	})
 	android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
@@ -421,13 +420,12 @@
 }
 
 type PathDeps struct {
-	DyLibs        RustLibraries
-	RLibs         RustLibraries
-	SharedLibs    android.Paths
-	SharedLibDeps android.Paths
-	StaticLibs    android.Paths
-	ProcMacros    RustLibraries
-	AfdoProfiles  android.Paths
+	DyLibs          RustLibraries
+	RLibs           RustLibraries
+	LibDeps         android.Paths
+	WholeStaticLibs android.Paths
+	ProcMacros      RustLibraries
+	AfdoProfiles    android.Paths
 
 	// depFlags and depLinkFlags are rustc and linker (clang) flags.
 	depFlags     []string
@@ -436,7 +434,7 @@
 	// linkDirs are link paths passed via -L to rustc. linkObjects are objects passed directly to the linker.
 	// Both of these are exported and propagate to dependencies.
 	linkDirs    []string
-	linkObjects []string
+	linkObjects android.Paths
 
 	// Used by bindgen modules which call clang
 	depClangFlags         []string
@@ -499,7 +497,7 @@
 
 type exportedFlagsProducer interface {
 	exportLinkDirs(...string)
-	exportLinkObjects(...string)
+	exportLinkObjects(...android.Path)
 }
 
 type xref interface {
@@ -508,21 +506,27 @@
 
 type flagExporter struct {
 	linkDirs    []string
-	linkObjects []string
+	linkObjects android.Paths
+	libDeps     android.Paths
 }
 
 func (flagExporter *flagExporter) exportLinkDirs(dirs ...string) {
 	flagExporter.linkDirs = android.FirstUniqueStrings(append(flagExporter.linkDirs, dirs...))
 }
 
-func (flagExporter *flagExporter) exportLinkObjects(flags ...string) {
-	flagExporter.linkObjects = android.FirstUniqueStrings(append(flagExporter.linkObjects, flags...))
+func (flagExporter *flagExporter) exportLinkObjects(flags ...android.Path) {
+	flagExporter.linkObjects = android.FirstUniquePaths(append(flagExporter.linkObjects, flags...))
+}
+
+func (flagExporter *flagExporter) exportLibDeps(paths ...android.Path) {
+	flagExporter.libDeps = android.FirstUniquePaths(append(flagExporter.libDeps, paths...))
 }
 
 func (flagExporter *flagExporter) setProvider(ctx ModuleContext) {
 	ctx.SetProvider(FlagExporterInfoProvider, FlagExporterInfo{
 		LinkDirs:    flagExporter.linkDirs,
 		LinkObjects: flagExporter.linkObjects,
+		LibDeps:     flagExporter.libDeps,
 	})
 }
 
@@ -535,7 +539,8 @@
 type FlagExporterInfo struct {
 	Flags       []string
 	LinkDirs    []string // TODO: this should be android.Paths
-	LinkObjects []string // TODO: this should be android.Paths
+	LinkObjects android.Paths
+	LibDeps     android.Paths
 }
 
 var FlagExporterInfoProvider = blueprint.NewProvider(FlagExporterInfo{})
@@ -920,7 +925,7 @@
 
 	// Calculate rustc flags
 	if mod.afdo != nil {
-		flags, deps = mod.afdo.flags(ctx, flags, deps)
+		flags, deps = mod.afdo.flags(actx, flags, deps)
 	}
 	if mod.compiler != nil {
 		flags = mod.compiler.compilerFlags(ctx, flags)
@@ -1251,6 +1256,7 @@
 				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
 				depPaths.depFlags = append(depPaths.depFlags, exportedInfo.Flags...)
 				depPaths.linkObjects = append(depPaths.linkObjects, exportedInfo.LinkObjects...)
+				depPaths.LibDeps = append(depPaths.LibDeps, exportedInfo.LibDeps...)
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
@@ -1294,6 +1300,7 @@
 						depPaths.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", linkObject.Path().String(), "-Wl,--no-whole-archive"}...)
 					} else if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
 						depPaths.depFlags = append(depPaths.depFlags, "-lstatic="+libName)
+						depPaths.WholeStaticLibs = append(depPaths.WholeStaticLibs, linkObject.Path())
 					} else {
 						ctx.ModuleErrorf("'%q' cannot be listed as a whole_static_library in Rust modules unless the output is prefixed by 'lib'", depName, ctx.ModuleName())
 					}
@@ -1301,7 +1308,7 @@
 
 				// Add this to linkObjects to pass the library directly to the linker as well. This propagates
 				// to dependencies to avoid having to redeclare static libraries for dependents of the dylib variant.
-				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
+				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.AsPaths()...)
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
 
 				exportedInfo := ctx.OtherModuleProvider(dep, cc.FlagExporterInfoProvider).(cc.FlagExporterInfo)
@@ -1327,7 +1334,7 @@
 				linkPath = linkPathFromFilePath(linkObject.Path())
 
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
-				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
+				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.AsPaths()...)
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
@@ -1353,7 +1360,9 @@
 			// Make sure these dependencies are propagated
 			if lib, ok := mod.compiler.(exportedFlagsProducer); ok && exportDep {
 				lib.exportLinkDirs(linkPath)
-				lib.exportLinkObjects(linkObject.String())
+				if linkObject.Valid() {
+					lib.exportLinkObjects(linkObject.Path())
+				}
 			}
 		} else {
 			switch {
@@ -1385,19 +1394,16 @@
 		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: dep.CrateName()})
 	}
 
-	var staticLibDepFiles android.Paths
+	var libDepFiles android.Paths
 	for _, dep := range directStaticLibDeps {
-		staticLibDepFiles = append(staticLibDepFiles, dep.OutputFile().Path())
+		libDepFiles = append(libDepFiles, dep.OutputFile().Path())
 	}
 
-	var sharedLibFiles android.Paths
-	var sharedLibDepFiles android.Paths
 	for _, dep := range directSharedLibDeps {
-		sharedLibFiles = append(sharedLibFiles, dep.SharedLibrary)
 		if dep.TableOfContents.Valid() {
-			sharedLibDepFiles = append(sharedLibDepFiles, dep.TableOfContents.Path())
+			libDepFiles = append(libDepFiles, dep.TableOfContents.Path())
 		} else {
-			sharedLibDepFiles = append(sharedLibDepFiles, dep.SharedLibrary)
+			libDepFiles = append(libDepFiles, dep.SharedLibrary)
 		}
 	}
 
@@ -1413,15 +1419,13 @@
 
 	depPaths.RLibs = append(depPaths.RLibs, rlibDepFiles...)
 	depPaths.DyLibs = append(depPaths.DyLibs, dylibDepFiles...)
-	depPaths.SharedLibs = append(depPaths.SharedLibs, sharedLibFiles...)
-	depPaths.SharedLibDeps = append(depPaths.SharedLibDeps, sharedLibDepFiles...)
-	depPaths.StaticLibs = append(depPaths.StaticLibs, staticLibDepFiles...)
+	depPaths.LibDeps = append(depPaths.LibDeps, libDepFiles...)
 	depPaths.ProcMacros = append(depPaths.ProcMacros, procMacroDepFiles...)
 	depPaths.SrcDeps = append(depPaths.SrcDeps, srcProviderDepFiles...)
 
 	// Dedup exported flags from dependencies
 	depPaths.linkDirs = android.FirstUniqueStrings(depPaths.linkDirs)
-	depPaths.linkObjects = android.FirstUniqueStrings(depPaths.linkObjects)
+	depPaths.linkObjects = android.FirstUniquePaths(depPaths.linkObjects)
 	depPaths.depFlags = android.FirstUniqueStrings(depPaths.depFlags)
 	depPaths.depClangFlags = android.FirstUniqueStrings(depPaths.depClangFlags)
 	depPaths.depIncludePaths = android.FirstUniquePaths(depPaths.depIncludePaths)
@@ -1516,7 +1520,7 @@
 		for _, lib := range deps.Rustlibs {
 			if autoDep.depTag == rlibDepTag {
 				// Handle the rlib deptag case
-				addRlibDependency(actx, lib, mod, snapshotInfo, rlibDepVariations)
+				addRlibDependency(actx, lib, mod, &snapshotInfo, rlibDepVariations)
 			} else {
 				// autoDep.depTag is a dylib depTag. Not all rustlibs may be available as a dylib however.
 				// Check for the existence of the dylib deptag variant. Select it if available,
@@ -1527,7 +1531,7 @@
 					actx.AddVariationDependencies(autoDepVariations, autoDep.depTag, lib)
 				} else {
 					// If there's no dylib dependency available, try to add the rlib dependency instead.
-					addRlibDependency(actx, lib, mod, snapshotInfo, rlibDepVariations)
+					addRlibDependency(actx, lib, mod, &snapshotInfo, rlibDepVariations)
 				}
 			}
 		}
@@ -1613,11 +1617,13 @@
 
 	// 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...)
+
+	mod.afdo.addDep(ctx, actx)
 }
 
 // addRlibDependency will add an rlib dependency, rewriting to the snapshot library if available.
-func addRlibDependency(actx android.BottomUpMutatorContext, lib string, mod *Module, snapshotInfo *cc.SnapshotInfo, variations []blueprint.Variation) {
-	lib = cc.GetReplaceModuleName(lib, cc.GetSnapshot(mod, &snapshotInfo, actx).Rlibs)
+func addRlibDependency(actx android.BottomUpMutatorContext, lib string, mod *Module, snapshotInfo **cc.SnapshotInfo, variations []blueprint.Variation) {
+	lib = cc.GetReplaceModuleName(lib, cc.GetSnapshot(mod, snapshotInfo, actx).Rlibs)
 	actx.AddVariationDependencies(variations, rlibDepTag, lib)
 }
 
diff --git a/rust/rust_test.go b/rust/rust_test.go
index e8e5800..2a38b89 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -258,6 +258,7 @@
 	`)
 	module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
 	rustc := ctx.ModuleForTests("librlib", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	rustLink := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Rule("rustLink")
 
 	// Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up.
 	if !android.InList("libdylib", module.Properties.AndroidMkDylibs) {
@@ -284,16 +285,16 @@
 		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
 	}
 
-	if !strings.Contains(rustc.Args["linkFlags"], "cc_stubs_dep.so") {
-		t.Errorf("shared cc_library not being passed to rustc linkFlags %#v", rustc.Args["linkFlags"])
+	if !strings.Contains(rustLink.Args["linkFlags"], "cc_stubs_dep.so") {
+		t.Errorf("shared cc_library not being passed to rustc linkFlags %#v", rustLink.Args["linkFlags"])
 	}
 
-	if !android.SuffixInList(rustc.OrderOnly.Strings(), "cc_stubs_dep.so") {
-		t.Errorf("shared cc dep not being passed as order-only to rustc %#v", rustc.OrderOnly.Strings())
+	if !android.SuffixInList(rustLink.OrderOnly.Strings(), "cc_stubs_dep.so") {
+		t.Errorf("shared cc dep not being passed as order-only to rustc %#v", rustLink.OrderOnly.Strings())
 	}
 
-	if !android.SuffixInList(rustc.Implicits.Strings(), "cc_stubs_dep.so.toc") {
-		t.Errorf("shared cc dep TOC not being passed as implicit to rustc %#v", rustc.Implicits.Strings())
+	if !android.SuffixInList(rustLink.Implicits.Strings(), "cc_stubs_dep.so.toc") {
+		t.Errorf("shared cc dep TOC not being passed as implicit to rustc %#v", rustLink.Implicits.Strings())
 	}
 }
 
diff --git a/rust/sanitize_test.go b/rust/sanitize_test.go
index d6a14b2..43e95f4 100644
--- a/rust/sanitize_test.go
+++ b/rust/sanitize_test.go
@@ -35,7 +35,7 @@
 	note_sync := "note_memtag_heap_sync"
 
 	found := None
-	implicits := m.Rule("rustc").Implicits
+	implicits := m.Rule("rustLink").Implicits
 	for _, lib := range implicits {
 		if strings.Contains(lib.Rel(), note_async) {
 			found = Async
diff --git a/rust/vendor_snapshot_test.go b/rust/vendor_snapshot_test.go
index e1b3c86..2e7a330 100644
--- a/rust/vendor_snapshot_test.go
+++ b/rust/vendor_snapshot_test.go
@@ -941,7 +941,7 @@
 	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"]
+	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("rustLink").Args["linkFlags"]
 	for _, input := range [][]string{
 		[]string{sharedVariant, "libvndk.vndk.30.arm64"},
 		[]string{staticVariant, "libvendor.vendor_static.30.arm64"},
@@ -997,7 +997,7 @@
 		t.Errorf("Unexpected rust rlib name in AndroidMk: %q, expected: %q\n", rustVendorBinMkRlibName, expectedRustVendorSnapshotName)
 	}
 
-	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("rustc").Args["linkFlags"]
+	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("rustLink").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",
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 26fe432..9367ff0 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -189,6 +189,7 @@
     libs: [
         "linker_config_proto",
     ],
+    visibility: ["//system/linkerconfig"],
 }
 
 python_test_host {
diff --git a/scripts/mkcratersp.py b/scripts/mkcratersp.py
new file mode 100755
index 0000000..86b4aa3
--- /dev/null
+++ b/scripts/mkcratersp.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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.
+#
+
+"""
+This script is used as a replacement for the Rust linker. It converts a linker
+command line into a rspfile that can be used during the link phase.
+"""
+
+import os
+import shutil
+import subprocess
+import sys
+
+def create_archive(out, objects, archives):
+  mricmd = f'create {out}\n'
+  for o in objects:
+    mricmd += f'addmod {o}\n'
+  for a in archives:
+    mricmd += f'addlib {a}\n'
+  mricmd += 'save\nend\n'
+  subprocess.run([os.getenv('AR'), '-M'], encoding='utf-8', input=mricmd, check=True)
+
+objects = []
+archives = []
+linkdirs = []
+libs = []
+temp_archives = []
+version_script = None
+
+for i, arg in enumerate(sys.argv):
+  if arg == '-o':
+    out = sys.argv[i+1]
+  if arg == '-L':
+    linkdirs.append(sys.argv[i+1])
+  if arg.startswith('-l') or arg == '-shared':
+    libs.append(arg)
+  if arg.startswith('-Wl,--version-script='):
+    version_script = arg[21:]
+  if arg[0] == '-':
+    continue
+  if arg.endswith('.o') or arg.endswith('.rmeta'):
+    objects.append(arg)
+  if arg.endswith('.rlib'):
+    if arg.startswith(os.getenv('TMPDIR')):
+      temp_archives.append(arg)
+    else:
+      archives.append(arg)
+
+create_archive(f'{out}.whole.a', objects, [])
+create_archive(f'{out}.a', [], temp_archives)
+
+with open(out, 'w') as f:
+  print(f'-Wl,--whole-archive', file=f)
+  print(f'{out}.whole.a', file=f)
+  print(f'-Wl,--no-whole-archive', file=f)
+  print(f'{out}.a', file=f)
+  for a in archives:
+    print(a, file=f)
+  for linkdir in linkdirs:
+    print(f'-L{linkdir}', file=f)
+  for l in libs:
+    print(l, file=f)
+  if version_script:
+    shutil.copyfile(version_script, f'{out}.version_script')
+    print(f'-Wl,--version-script={out}.version_script', file=f)
diff --git a/starlark_fmt/format.go b/starlark_fmt/format.go
index 064fc21..a97f71b 100644
--- a/starlark_fmt/format.go
+++ b/starlark_fmt/format.go
@@ -35,7 +35,11 @@
 
 // PrintBool returns a Starlark compatible bool string.
 func PrintBool(item bool) string {
-	return strings.Title(fmt.Sprintf("%t", item))
+	if item {
+		return "True"
+	} else {
+		return "False"
+	}
 }
 
 // PrintsStringList returns a Starlark-compatible string of a list of Strings/Labels.
diff --git a/tests/lib.sh b/tests/lib.sh
index 26608b8..715eac1 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -94,9 +94,11 @@
   symlink_directory external/compiler-rt
   symlink_directory external/go-cmp
   symlink_directory external/golang-protobuf
+  symlink_directory external/licenseclassifier
   symlink_directory external/starlark-go
   symlink_directory external/python
   symlink_directory external/sqlite
+  symlink_directory external/spdx-tools
 
   touch "$MOCK_TOP/Android.bp"
 }
diff --git a/tests/sbom_test.sh b/tests/sbom_test.sh
index 6066d70..2f154cd 100755
--- a/tests/sbom_test.sh
+++ b/tests/sbom_test.sh
@@ -37,9 +37,14 @@
   out_dir=out
   droid_target=
 fi
+
+function run_soong {
+  TARGET_PRODUCT="aosp_cf_x86_64_phone" TARGET_BUILD_VARIANT=userdebug OUT_DIR=$out_dir \
+    build/soong/soong_ui.bash --make-mode "$@"
+}
+
 # m droid, build sbom later in case additional dependencies might be built and included in partition images.
-TARGET_PRODUCT="aosp_cf_x86_64_phone" TARGET_BUILD_VARIANT=userdebug OUT_DIR=$out_dir \
-  build/soong/soong_ui.bash --make-mode $droid_target dump.erofs
+run_soong $droid_target dump.erofs lz4
 
 product_out=$out_dir/target/product/vsoc_x86_64
 sbom_test=$product_out/sbom_test
@@ -47,11 +52,11 @@
 cp $product_out/*.img $sbom_test
 
 # m sbom
-TARGET_PRODUCT="aosp_cf_x86_64_phone" TARGET_BUILD_VARIANT=userdebug OUT_DIR=$out_dir \
-  build/soong/soong_ui.bash --make-mode sbom
+run_soong sbom
 
 # Generate installed file list from .img files in PRODUCT_OUT
 dump_erofs=$out_dir/host/linux-x86/bin/dump.erofs
+lz4=$out_dir/host/linux-x86/bin/lz4
 
 declare -A diff_excludes
 diff_excludes[odm]="-I /odm/lib/modules"
@@ -60,24 +65,12 @@
  -I /vendor/lib/modules \
  -I /vendor/odm"
 diff_excludes[system]=\
-"-I /acct/ \
- -I /adb_keys \
- -I /apex/ \
- -I /bin \
+"-I /bin \
  -I /bugreports \
  -I /cache \
- -I /config/ \
  -I /d \
- -I /data/ \
- -I /data_mirror/ \
- -I /debug_ramdisk/ \
- -I /dev/ \
  -I /etc \
  -I /init \
- -I /init.environ.rc \
- -I /linkerconfig/ \
- -I /metadata/ \
- -I /mnt/ \
  -I /odm/app \
  -I /odm/bin \
  -I /odm_dlkm/etc \
@@ -89,16 +82,7 @@
  -I /odm/overlay \
  -I /odm/priv-app \
  -I /odm/usr \
- -I /oem/ \
- -I /postinstall/ \
- -I /proc/ \
- -I /product/ \
  -I /sdcard \
- -I /second_stage_resources/ \
- -I /storage/ \
- -I /sys/ \
- -I /system_dlkm/ \
- -I /system_ext/ \
  -I /system/lib64/android.hardware.confirmationui@1.0.so \
  -I /system/lib64/android.hardware.confirmationui-V1-ndk.so \
  -I /system/lib64/android.hardware.keymaster@4.1.so \
@@ -114,22 +98,32 @@
  -I /system/lib64/libkm_compat.so \
  -I /system/lib64/vndk-29 \
  -I /system/lib64/vndk-sp-29 \
- -I /system/lib/modules \
  -I /system/lib/vndk-29 \
  -I /system/lib/vndk-sp-29 \
- -I /system/product \
- -I /system/system_ext \
  -I /system/usr/icu \
- -I /system/vendor \
- -I /vendor/ \
  -I /vendor_dlkm/etc"
 
+function diff_files {
+   file_list_file="$1"; shift
+   files_in_spdx_file="$1"; shift
+   partition_name="$1"; shift
+   exclude=
+   if [ -v 'diff_excludes[$partition_name]' ]; then
+     exclude=${diff_excludes[$partition_name]}
+   fi
+
+   diff "$file_list_file" "$files_in_spdx_file" $exclude
+   if [ $? != "0" ]; then
+     echo Found diffs in $f and SBOM.
+     exit 1
+   else
+     echo No diffs.
+   fi
+ }
+
 # Example output of dump.erofs is as below, and the data used in the test start
 # at line 11. Column 1 is inode id, column 2 is inode type and column 3 is name.
-# Each line is captured in variable "entry", sed is used to trim the leading
-# spaces and cut is used to get field 1 every time. Once a field is extracted,
-# "cut --complement" is used to remove the extracted field so next field can be
-# processed in the same way and to be processed field is always field 1.
+# Each line is captured in variable "entry", awk is used to get type and name.
 # Output of dump.erofs:
 #     File : /
 #     Size: 160  On-disk size: 160  directory
@@ -170,16 +164,13 @@
     all_dirs=$(echo "$all_dirs" | cut -d ' ' -f1 --complement -s)
     entries=$($dump_erofs --ls --path "$dir" $f | tail -n +11)
     while read -r entry; do
-      nid=$(echo $entry | sed 's/^\s*//' | cut -d ' ' -f1)
-      entry=$(echo $entry | sed 's/^\s*//' | cut -d ' ' -f1 --complement)
-      type=$(echo $entry | sed 's/^\s*//' | cut -d ' ' -f1)
-      entry=$(echo $entry | sed 's/^\s*//' | cut -d ' ' -f1 --complement)
-      name=$(echo $entry | sed 's/^\s*//' | cut -d ' ' -f1)
-      case $type in
+      inode_type=$(echo $entry | awk -F ' ' '{print $2}')
+      name=$(echo $entry | awk -F ' ' '{print $3}')
+      case $inode_type in
         "2")  # directory
           all_dirs=$(echo "$all_dirs $dir/$name" | sed 's/^\s*//')
           ;;
-        *)
+        "1"|"7")  # 1: file, 7: symlink
           (
           if [ "$partition_name" != "system" ]; then
             # system partition is mounted to /, not to prepend partition name.
@@ -193,18 +184,31 @@
   done
   sort -n -o "$file_list_file" "$file_list_file"
 
-  # Diff
+  grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' > "$files_in_spdx_file"
+  if [ "$partition_name" = "system" ]; then
+    # system partition is mounted to /, so include FileName starts with /root/ too.
+    grep "FileName: /root/" $product_out/sbom.spdx | sed 's/^FileName: \/root//' >> "$files_in_spdx_file"
+  fi
+  sort -n -o "$files_in_spdx_file" "$files_in_spdx_file"
+
   echo ============ Diffing files in $f and SBOM
+  diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name"
+done
+
+RAMDISK_IMAGES="$product_out/ramdisk.img"
+for f in $RAMDISK_IMAGES; do
+  partition_name=$(basename $f | cut -d. -f1)
+  file_list_file="${sbom_test}/sbom-${partition_name}-files.txt"
+  files_in_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-spdx.txt"
+  # lz4 decompress $f to stdout
+  # cpio list all entries like ls -l
+  # grep filter normal files and symlinks
+  # awk get entry names
+  # sed remove partition name from entry names
+  $lz4 -c -d $f | cpio -tv 2>/dev/null | grep '^[-l]' | awk -F ' ' '{print $9}' | sed "s:^:/$partition_name/:" | sort -n > "$file_list_file"
+
   grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' | sort -n > "$files_in_spdx_file"
-  exclude=
-  if [ -v 'diff_excludes[$partition_name]' ]; then
-    exclude=${diff_excludes[$partition_name]}
-  fi
-  diff "$file_list_file" "$files_in_spdx_file" $exclude
-  if [ $? != "0" ]; then
-    echo Found diffs in $f and SBOM.
-    exit 1
-  else
-    echo No diffs.
-  fi
+
+  echo ============ Diffing files in $f and SBOM
+  diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name"
 done
\ No newline at end of file