rust: Add stub support for rust_ffi modules

This adds stubs support for rust_ffi and rust_ffi_shared modules. Usage
should match current cc usage. The stubs generator leveraged is the cc
stubs generator.

Bug: 203478530
Test: m blueprint_tests
Change-Id: I043b9714a357cd5fe17c183ccdf86900f5172e0e
diff --git a/apex/apex_test.go b/apex/apex_test.go
index e1a9582..6796fca 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -897,7 +897,11 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib3"],
+			native_shared_libs: [
+				"mylib",
+				"mylib3",
+				"libmylib3_rs",
+			],
 			binaries: ["foo.rust"],
 			updatable: false,
 		}
@@ -911,7 +915,14 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3#impl", "my_prebuilt_platform_lib", "my_prebuilt_platform_stub_only_lib"],
+			shared_libs: [
+				"mylib2",
+				"mylib3#impl",
+				"libmylib2_rs",
+				"libmylib3_rs#impl",
+				"my_prebuilt_platform_lib",
+				"my_prebuilt_platform_stub_only_lib",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -929,6 +940,16 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libmylib2_rs",
+			crate_name: "mylib2",
+			srcs: ["mylib.rs"],
+			stubs: {
+				symbol_file: "mylib2.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
 		cc_library {
 			name: "mylib3",
 			srcs: ["mylib.cpp"],
@@ -942,6 +963,18 @@
 			apex_available: [ "myapex" ],
 		}
 
+		rust_ffi {
+			name: "libmylib3_rs",
+			crate_name: "mylib3",
+			srcs: ["mylib.rs"],
+			shared_libs: ["mylib4.from_rust"],
+			stubs: {
+				symbol_file: "mylib3.map.txt",
+				versions: ["10", "11", "12"],
+			},
+			apex_available: [ "myapex" ],
+		}
+
 		cc_library {
 			name: "mylib4",
 			srcs: ["mylib.cpp"],
@@ -950,6 +983,14 @@
 			apex_available: [ "myapex" ],
 		}
 
+		cc_library {
+			name: "mylib4.from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+		}
+
 		cc_prebuilt_library_shared {
 			name: "my_prebuilt_platform_lib",
 			stubs: {
@@ -971,7 +1012,10 @@
 		rust_binary {
 			name: "foo.rust",
 			srcs: ["foo.rs"],
-			shared_libs: ["libfoo.shared_from_rust"],
+			shared_libs: [
+				"libfoo.shared_from_rust",
+				"libfoo_rs.shared_from_rust",
+			],
 			prefer_rlib: true,
 			apex_available: ["myapex"],
 		}
@@ -986,6 +1030,15 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rs.shared_from_rust",
+			crate_name: "foo_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
@@ -996,21 +1049,27 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib2_rs.so")
 
 	// Ensure that direct stubs dep is included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib3_rs.so")
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with the latest version of stubs for mylib2
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rs/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rs.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rs/android_arm64_armv8-a_shared/unstripped/libmylib2_rs.so")
 
 	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex10000/mylib3.so")
+	ensureContains(t, mylibLdFlags, "libmylib3_rs/android_arm64_armv8-a_shared_apex10000/unstripped/libmylib3_rs.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12/mylib3.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib3_rs/android_arm64_armv8-a_shared_12/unstripped/mylib3.so")
 
 	// Comment out this test. Now it fails after the optimization of sharing "cflags" in cc/cc.go
 	// is replaced by sharing of "cFlags" in cc/builder.go.
@@ -1026,27 +1085,35 @@
 
 	// Ensure that genstub for platform-provided lib is invoked with --systemapi
 	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests("libmylib2_rs", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
 	// Ensure that genstub for apex-provided lib is invoked with --apex
 	ensureContains(t, ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
+	ensureContains(t, ctx.ModuleForTests("libmylib3_rs", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
+		"lib64/libmylib3_rs.so",
 		"lib64/mylib4.so",
+		"lib64/mylib4.from_rust.so",
 		"bin/foo.rust",
-		"lib64/libc++.so", // by the implicit dependency from foo.rust
-		"lib64/liblog.so", // by the implicit dependency from foo.rust
+
+		"lib64/libstd.dylib.so", // implicit rust ffi dep
 	})
 
 	// Ensure that stub dependency from a rust module is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rs.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"]
 	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
+	ensureContains(t, rustDeps, "libfoo_rs.shared_from_rust/android_arm64_armv8-a_shared_current/unstripped/libfoo_rs.shared_from_rust.so")
 	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
+	ensureNotContains(t, rustDeps, "libfoo_rs.shared_from_rust/android_arm64_armv8-a_shared/unstripped/libfoo_rs.shared_from_rust.so")
 
 	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
 	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.shared_from_rust.so")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo_rs.shared_from_rust.so")
 
 	// Ensure that mylib is linking with the latest version of stubs for my_prebuilt_platform_lib
 	ensureContains(t, mylibLdFlags, "my_prebuilt_platform_lib/android_arm64_armv8-a_shared_current/my_prebuilt_platform_lib.so")
@@ -1111,7 +1178,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2"],
+			shared_libs: [
+				"mylib2",
+				"libmylib2_rust"
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1128,10 +1198,22 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libmylib2_rust",
+			crate_name: "mylib2_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
 		rust_binary {
 			name: "foo.rust",
 			srcs: ["foo.rs"],
-			shared_libs: ["libfoo.shared_from_rust"],
+			shared_libs: [
+				"libfoo.shared_from_rust",
+				"libmylib_rust.shared_from_rust"
+			],
 			prefer_rlib: true,
 			apex_available: ["myapex"],
 		}
@@ -1145,6 +1227,15 @@
 				versions: ["10", "11", "12"],
 			},
 		}
+		rust_ffi {
+			name: "libmylib_rust.shared_from_rust",
+			crate_name: "mylib_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
@@ -1152,6 +1243,8 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib_rust.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib_rust.shared_from_rust.so")
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
 
 	// Ensure that we are using non-stub variants of mylib2 and libfoo.shared_from_rust (because
@@ -1159,9 +1252,13 @@
 	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")
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rust.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared/unstripped/libmylib2_rust.so")
 	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").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")
+	ensureNotContains(t, rustDeps, "libmylib_rust.shared_from_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib_rust.shared_from_rust.so")
+	ensureContains(t, rustDeps, "libmylib_rust.shared_from_rust/android_arm64_armv8-a_shared/unstripped/libmylib_rust.shared_from_rust.so")
 }
 
 func TestApexWithStubsWithMinSdkVersion(t *testing.T) {
@@ -1170,7 +1267,11 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib3"],
+			native_shared_libs: [
+				"mylib",
+				"mylib3",
+				"libmylib3_rust",
+			],
 			min_sdk_version: "29",
 		}
 
@@ -1183,7 +1284,12 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3#impl"],
+			shared_libs: [
+				"mylib2",
+				"mylib3#impl",
+				"libmylib2_rust",
+				"libmylib3_rust#impl",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1203,6 +1309,17 @@
 			min_sdk_version: "28",
 		}
 
+		rust_ffi {
+			name: "libmylib2_rust",
+			crate_name: "mylib2_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				symbol_file: "mylib2.map.txt",
+				versions: ["28", "29", "30", "current"],
+			},
+			min_sdk_version: "28",
+		}
+
 		cc_library {
 			name: "mylib3",
 			srcs: ["mylib.cpp"],
@@ -1217,6 +1334,19 @@
 			min_sdk_version: "28",
 		}
 
+		rust_ffi {
+			name: "libmylib3_rust",
+			crate_name: "mylib3_rust",
+			srcs: ["mylib.rs"],
+			shared_libs: ["libmylib4.from_rust"],
+			stubs: {
+				symbol_file: "mylib3.map.txt",
+				versions: ["28", "29", "30", "current"],
+			},
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
+
 		cc_library {
 			name: "mylib4",
 			srcs: ["mylib.cpp"],
@@ -1225,6 +1355,14 @@
 			apex_available: [ "myapex" ],
 			min_sdk_version: "28",
 		}
+
+		rust_ffi {
+			name: "libmylib4.from_rust",
+			crate_name: "mylib4",
+			srcs: ["mylib.rs"],
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
@@ -1232,9 +1370,12 @@
 
 	// Ensure that direct non-stubs dep is always included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib3_rust.so")
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib2_rust.so")
 
 	// Ensure that direct stubs dep is included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
@@ -1243,13 +1384,17 @@
 
 	// Ensure that mylib is linking with the latest version of stub for mylib2
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rust.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared/unstripped/libmylib2_rust.so")
 
 	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex29/mylib3.so")
+	ensureContains(t, mylibLdFlags, "libmylib3_rust/android_arm64_armv8-a_shared_apex29/unstripped/libmylib3_rust.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_29/mylib3.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib3_rust/android_arm64_armv8-a_shared_29/unstripped/libmylib3_rust.so")
 
 	// Ensure that stubs libs are built without -include flags
 	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("cc").Args["cFlags"]
@@ -1257,11 +1402,16 @@
 
 	// Ensure that genstub is invoked with --systemapi
 	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests("libmylib2_rust", "android_arm64_armv8-a_shared_29").Rule("cc.genStubSrc").Args["flags"], "--systemapi")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
+		"lib64/libmylib3_rust.so",
 		"lib64/mylib4.so",
+		"lib64/libmylib4.from_rust.so",
+
+		"lib64/libstd.dylib.so", // by the implicit dependency from foo.rust
 	})
 }
 
@@ -1286,7 +1436,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libstub"],
+			shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 			apex_available: ["myapex"],
 			min_sdk_version: "Z",
 		}
@@ -1300,7 +1453,10 @@
 		apex {
 			name: "otherapex",
 			key: "myapex.key",
-			native_shared_libs: ["libstub"],
+			native_shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 			min_sdk_version: "29",
 		}
 
@@ -1314,11 +1470,25 @@
 			min_sdk_version: "29",
 		}
 
+		rust_ffi {
+			name: "libstub_rust",
+			crate_name: "stub_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["29", "Z", "current"],
+			},
+			apex_available: ["otherapex"],
+			min_sdk_version: "29",
+		}
+
 		// platform module depending on libstub from otherapex should use the latest stub("current")
 		cc_library {
 			name: "libplatform",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libstub"],
+			shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 		}
 	`,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -1331,14 +1501,20 @@
 	// Ensure that mylib from myapex is built against the latest stub (current)
 	mylibCflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=10000 ")
+	// rust stubs do not emit -D__LIBFOO_API__ flags as this is deprecated behavior for cc stubs
+
 	mylibLdflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 	ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
+	ensureContains(t, mylibLdflags, "libstub_rust/android_arm64_armv8-a_shared_current/unstripped/libstub_rust.so ")
 
 	// Ensure that libplatform is built against latest stub ("current") of mylib3 from the apex
 	libplatformCflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureContains(t, libplatformCflags, "-D__LIBSTUB_API__=10000 ") // "current" maps to 10000
+	// rust stubs do not emit -D__LIBFOO_API__ flags as this is deprecated behavior for cc stubs
+
 	libplatformLdflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
 	ensureContains(t, libplatformLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
+	ensureContains(t, libplatformLdflags, "libstub_rust/android_arm64_armv8-a_shared_current/unstripped/libstub_rust.so ")
 }
 
 func TestApexWithExplicitStubsDependency(t *testing.T) {
@@ -1360,7 +1536,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libfoo#10"],
+			shared_libs: [
+				"libfoo#10",
+				"libfoo_rust#10"
+			],
 			static_libs: ["libbaz"],
 			system_shared_libs: [],
 			stl: "none",
@@ -1378,6 +1557,16 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rust",
+			crate_name: "foo_rust",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libbar.from_rust"],
+			stubs: {
+				versions: ["10", "20", "30"],
+			},
+		}
+
 		cc_library {
 			name: "libbar",
 			srcs: ["mylib.cpp"],
@@ -1385,6 +1574,13 @@
 			stl: "none",
 		}
 
+		cc_library {
+			name: "libbar.from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+
 		cc_library_static {
 			name: "libbaz",
 			srcs: ["mylib.cpp"],
@@ -1403,21 +1599,27 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rust.so")
 
 	// Ensure that dependency of stubs is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libbar.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libbar.from_rust.so")
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with version 10 of libfoo
 	ensureContains(t, mylibLdFlags, "libfoo/android_arm64_armv8-a_shared_10/libfoo.so")
+	ensureContains(t, mylibLdFlags, "libfoo_rust/android_arm64_armv8-a_shared_10/unstripped/libfoo_rust.so")
 	// ... and not linking to the non-stub (impl) variant of libfoo
 	ensureNotContains(t, mylibLdFlags, "libfoo/android_arm64_armv8-a_shared/libfoo.so")
+	ensureNotContains(t, mylibLdFlags, "libfoo_rust/android_arm64_armv8-a_shared/unstripped/libfoo_rust.so")
 
 	libFooStubsLdFlags := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_10").Rule("ld").Args["libFlags"]
+	libFooRustStubsLdFlags := ctx.ModuleForTests("libfoo_rust", "android_arm64_armv8-a_shared_10").Rule("ld").Args["libFlags"]
 
 	// Ensure that libfoo stubs is not linking to libbar (since it is a stubs)
 	ensureNotContains(t, libFooStubsLdFlags, "libbar.so")
+	ensureNotContains(t, libFooRustStubsLdFlags, "libbar.from_rust.so")
 
 	fullDepsInfo := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
 		ctx.ModuleForTests("myapex2", "android_common_myapex2").Output("depsinfo/fulllist.txt")), "\n")
@@ -1457,7 +1659,11 @@
 			srcs: ["mylib.cpp"],
 			static_libs: ["libstatic"],
 			shared_libs: ["libshared"],
-			runtime_libs: ["libfoo", "libbar"],
+			runtime_libs: [
+				"libfoo",
+				"libbar",
+				"libfoo_rs",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1473,6 +1679,15 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rs",
+			crate_name: "foo_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["10", "20", "30"],
+			},
+		}
+
 		cc_library {
 			name: "libbar",
 			srcs: ["mylib.cpp"],
@@ -1524,6 +1739,7 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rs.so")
 
 	// Ensure that runtime_libs dep in included
 	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
@@ -1535,6 +1751,7 @@
 	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
 	ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.so")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo_rs.so")
 }
 
 var prepareForTestOfRuntimeApexWithHwasan = android.GroupFixturePreparers(
@@ -1766,7 +1983,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib_shared", "libdl", "libm"],
+			native_shared_libs: ["mylib", "mylib_shared", "libdl", "libm", "libmylib_rs"],
 			updatable: false,
 		}
 
@@ -1785,6 +2002,14 @@
 			apex_available: [ "myapex" ],
 		}
 
+		rust_ffi {
+			name: "libmylib_rs",
+			crate_name: "mylib_rs",
+			shared_libs: ["libvers#27", "libm#impl"],
+			srcs: ["mylib.rs"],
+			apex_available: [ "myapex" ],
+		}
+
 		cc_library_shared {
 			name: "mylib_shared",
 			srcs: ["mylib.cpp"],
@@ -1799,20 +2024,38 @@
 			stl: "none",
 			bootstrap: true,
 		}
+
+		rust_ffi {
+			name: "libbootstrap_rs",
+			srcs: ["mylib.cpp"],
+			crate_name: "bootstrap_rs",
+			bootstrap: true,
+		}
+
+		cc_library {
+			name: "libvers",
+			srcs: ["mylib.cpp"],
+			stl: "none",
+			stubs: { versions: ["27","30"] },
+		}
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	// Ensure that mylib, libm, libdl are included.
+	// Ensure that mylib, libmylib_rs, libm, libdl, libstd.dylib.so (from Rust) are included.
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib_rs.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/bionic/libm.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/bionic/libdl.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libstd.dylib.so")
 
-	// Ensure that libc is not included (since it has stubs and not listed in native_shared_libs)
+	// Ensure that libc and liblog (from Rust) is not included (since it has stubs and not listed in native_shared_libs)
 	ensureNotContains(t, copyCmds, "image.apex/lib64/bionic/libc.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/liblog.so")
 
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	mylibRsFlags := ctx.ModuleForTests("libmylib_rs", "android_arm64_armv8-a_shared_apex10000").Rule("rustc").Args["linkFlags"]
 	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	mylibSharedCFlags := ctx.ModuleForTests("mylib_shared", "android_arm64_armv8-a_shared_apex10000").Rule("cc").Args["cFlags"]
 
@@ -1846,11 +2089,42 @@
 	ensureContains(t, mylibCFlags, "__LIBDL_API__=27")
 	ensureContains(t, mylibSharedCFlags, "__LIBDL_API__=27")
 
+	// Rust checks
+	// For dependency to libc, liblog
+	// Ensure that libmylib_rs is linking with the latest versions of stubs
+	ensureContains(t, mylibRsFlags, "libc/android_arm64_armv8-a_shared_current/libc.so")
+	ensureContains(t, mylibRsFlags, "liblog/android_arm64_armv8-a_shared_current/liblog.so")
+	// ... and not linking to the non-stub (impl) variants
+	ensureNotContains(t, mylibRsFlags, "libc/android_arm64_armv8-a_shared/libc.so")
+	ensureNotContains(t, mylibRsFlags, "liblog/android_arm64_armv8-a_shared/liblog.so")
+
+	// For libm dependency (explicit)
+	// Ensure that mylib is linking with the non-stub (impl) variant
+	ensureContains(t, mylibRsFlags, "libm/android_arm64_armv8-a_shared_apex10000/libm.so")
+	// ... and not linking to the stub variant
+	ensureNotContains(t, mylibRsFlags, "libm/android_arm64_armv8-a_shared_29/libm.so")
+
+	// For dependency to libvers
+	// (We do not use libdl#27 as Rust links the system libs implicitly and does
+	// not currently have a system_shared_libs equivalent to prevent this)
+	// Ensure that mylib is linking with the specified version of stubs
+	ensureContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_27/libvers.so")
+	// ... and not linking to the other versions of stubs
+	ensureNotContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_30/libvers.so")
+	// ... and not linking to the non-stub (impl) variant
+	ensureNotContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_apex10000/libvers.so")
+
 	// Ensure that libBootstrap is depending on the platform variant of bionic libs
 	libFlags := ctx.ModuleForTests("libBootstrap", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
 	ensureContains(t, libFlags, "libc/android_arm64_armv8-a_shared/libc.so")
 	ensureContains(t, libFlags, "libm/android_arm64_armv8-a_shared/libm.so")
 	ensureContains(t, libFlags, "libdl/android_arm64_armv8-a_shared/libdl.so")
+
+	// Ensure that libbootstrap_rs is depending on the platform variant of bionic libs
+	libRsFlags := ctx.ModuleForTests("libbootstrap_rs", "android_arm64_armv8-a_shared").Rule("rustc").Args["linkFlags"]
+	ensureContains(t, libRsFlags, "libc/android_arm64_armv8-a_shared/libc.so")
+	ensureContains(t, libRsFlags, "libm/android_arm64_armv8-a_shared/libm.so")
+	ensureContains(t, libRsFlags, "libdl/android_arm64_armv8-a_shared/libdl.so")
 }
 
 func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) {
@@ -1900,7 +2174,7 @@
 
 		cc_library {
 			name: "liba",
-			shared_libs: ["libz"],
+			shared_libs: ["libz", "libz_rs"],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [
@@ -1918,6 +2192,15 @@
 				versions: ["28", "30"],
 			},
 		}
+
+		rust_ffi {
+			name: "libz_rs",
+			crate_name: "z_rs",
+			srcs: ["foo.rs"],
+			stubs: {
+				versions: ["28", "30"],
+			},
+		}
 	`)
 
 	expectLink := func(from, from_variant, to, to_variant string) {
@@ -1930,16 +2213,25 @@
 	}
 	// platform liba is linked to non-stub version
 	expectLink("liba", "shared", "libz", "shared")
+	expectLink("liba", "shared", "unstripped/libz_rs", "shared")
 	// liba in myapex is linked to current
 	expectLink("liba", "shared_apex29", "libz", "shared_current")
 	expectNoLink("liba", "shared_apex29", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex29", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex29", "libz", "shared")
+	expectLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_current")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_30")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_28")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared")
 	// liba in otherapex is linked to current
 	expectLink("liba", "shared_apex30", "libz", "shared_current")
 	expectNoLink("liba", "shared_apex30", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex30", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex30", "libz", "shared")
+	expectLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_current")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_30")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_28")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared")
 }
 
 func TestApexMinSdkVersion_SupportsCodeNames(t *testing.T) {
@@ -2159,7 +2451,7 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libbar"],
+			shared_libs: ["libbar", "libbar_rs"],
 			min_sdk_version: "29",
 			apex_available: ["myapex"],
 		}
@@ -2169,6 +2461,13 @@
 			stubs: { versions: ["29", "30"] },
 		}
 
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
 		cc_library {
 			name: "yourlib",
 			srcs: ["mylib.cpp"],
@@ -2191,6 +2490,8 @@
 		myapex.Output("depsinfo/flatlist.txt")), "\n")
 	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
 		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar_rs(minSdkVersion:(no version)) (external)")
 	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
 		flatlist, "mylib:(minSdkVersion:29)")
 	android.AssertStringListContains(t, "track platform-available lib",
@@ -2227,7 +2528,7 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libbar"],
+			shared_libs: ["libbar", "libbar_rs",],
 			min_sdk_version: "29",
 			apex_available: ["myapex"],
 		}
@@ -2237,6 +2538,13 @@
 			stubs: { versions: ["29", "30"] },
 		}
 
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
 		cc_library {
 			name: "yourlib",
 			srcs: ["mylib.cpp"],
@@ -2264,6 +2572,8 @@
 		myapex.Output("depsinfo/flatlist.txt")), "\n")
 	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
 		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar_rs(minSdkVersion:(no version)) (external)")
 	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
 		flatlist, "mylib:(minSdkVersion:29)")
 	android.AssertStringListContains(t, "track platform-available lib",
@@ -2300,7 +2610,7 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libbar"],
+			shared_libs: ["libbar", "libbar_rs"],
 			min_sdk_version: "29",
 			apex_available: ["myapex"],
 		}
@@ -2310,6 +2620,13 @@
 			stubs: { versions: ["29", "30"] },
 		}
 
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
 		cc_library {
 			name: "yourlib",
 			srcs: ["mylib.cpp"],
@@ -2338,6 +2655,8 @@
 		myapex.Output("depsinfo/flatlist.txt")), "\n")
 	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
 		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar_rs(minSdkVersion:(no version)) (external)")
 	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
 		flatlist, "mylib:(minSdkVersion:29)")
 	android.AssertStringListContains(t, "track platform-available lib",
@@ -2372,7 +2691,7 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["libx"],
+			native_shared_libs: ["libx", "libx_rs"],
 			updatable: false,
 		}
 
@@ -2392,9 +2711,19 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libx_rs",
+			crate_name: "x_rs",
+			srcs: ["x.rs"],
+			apex_available: [ "myapex" ],
+			stubs: {
+				versions: ["1", "2"],
+			},
+		}
+
 		cc_library {
 			name: "libz",
-			shared_libs: ["libx"],
+			shared_libs: ["libx", "libx_rs",],
 			system_shared_libs: [],
 			stl: "none",
 		}
@@ -2412,6 +2741,8 @@
 	}
 	expectLink("libz", "shared", "libx", "shared_current")
 	expectNoLink("libz", "shared", "libx", "shared_2")
+	expectLink("libz", "shared", "unstripped/libx_rs", "shared_current")
+	expectNoLink("libz", "shared", "unstripped/libx_rs", "shared_2")
 	expectNoLink("libz", "shared", "libz", "shared_1")
 	expectNoLink("libz", "shared", "libz", "shared")
 }
@@ -2440,11 +2771,18 @@
 
 		cc_library {
 			name: "libx",
-			shared_libs: ["libbar"],
+			shared_libs: ["libbar", "libbar_rs"],
 			apex_available: [ "myapex" ],
 			min_sdk_version: "29",
 		}
 
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
 		cc_library {
 			name: "libbar",
 			stubs: {
@@ -2460,6 +2798,7 @@
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("libx", "shared_hwasan_apex29", "libbar", "shared_current")
+	expectLink("libx", "shared_hwasan_apex29", "unstripped/libbar_rs", "shared_current")
 }
 
 func TestQTargetApexUsesStaticUnwinder(t *testing.T) {
@@ -3404,10 +3743,20 @@
 			apex_available: ["myapex"],
 		}
 
+		rust_ffi {
+			name: "libmylib_rs",
+			crate_name: "mylib_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+			apex_available: ["myapex"],
+		}
+
 		cc_binary {
 			name: "not_in_apex",
 			srcs: ["mylib.cpp"],
-			static_libs: ["mylib"],
+			static_libs: ["mylib", "libmylib_rs"],
 			static_executable: true,
 			system_shared_libs: [],
 			stl: "none",
@@ -3418,6 +3767,7 @@
 
 	// Ensure that not_in_apex is linking with the static variant of mylib
 	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_static/mylib.a")
+	ensureContains(t, ldFlags, "generated_rust_staticlib/librustlibs.a")
 }
 
 func TestKeys(t *testing.T) {
@@ -8217,8 +8567,6 @@
 		"lib64/mylib2.so",
 		"lib64/mylib3.so",
 		"lib64/libfoo.rust.so",
-		"lib64/libc++.so", // auto-added to libfoo.rust by Soong
-		"lib64/liblog.so", // auto-added to libfoo.rust by Soong
 	})
 
 	// b/220397949
@@ -10676,8 +11024,8 @@
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
-	if len(copyCmds) != 38 {
-		t.Fatalf("Expected 38 commands, got %d in:\n%s", len(copyCmds), s)
+	if len(copyCmds) != 34 {
+		t.Fatalf("Expected 34 commands, got %d in:\n%s", len(copyCmds), s)
 	}
 
 	ensureListContainsMatch(t, copyCmds, "^cp -f .*/aconfig_flags.pb .*/image.apex/etc/aconfig_flags.pb")
diff --git a/cc/builder.go b/cc/builder.go
index 56b7139..5325116 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -851,6 +851,25 @@
 	return strings.Join(lines, "\n")
 }
 
+func BuildRustStubs(ctx android.ModuleContext, outputFile android.ModuleOutPath,
+	crtBegin, crtEnd android.Paths, stubObjs Objects, ccFlags Flags) {
+
+	// Instantiate paths
+	sharedLibs := android.Paths{}
+	staticLibs := android.Paths{}
+	lateStaticLibs := android.Paths{}
+	wholeStaticLibs := android.Paths{}
+	deps := android.Paths{}
+	implicitOutputs := android.WritablePaths{}
+	validations := android.Paths{}
+	groupLate := false
+
+	builderFlags := flagsToBuilderFlags(ccFlags)
+	transformObjToDynamicBinary(ctx, stubObjs.objFiles, sharedLibs, staticLibs,
+		lateStaticLibs, wholeStaticLibs, deps, crtBegin, crtEnd,
+		groupLate, builderFlags, outputFile, implicitOutputs, validations)
+}
+
 // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
 // and shared libraries, to a shared library (.so) or dynamic executable
 func transformObjToDynamicBinary(ctx android.ModuleContext,
diff --git a/cc/cc.go b/cc/cc.go
index 0db5c40..57b5e3c 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -160,6 +160,8 @@
 	Installable         *bool
 	// RelativeInstallPath returns the relative install path for this module.
 	RelativeInstallPath string
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+	RustApexExclude bool
 }
 
 var LinkableInfoProvider = blueprint.NewProvider[*LinkableInfo]()
@@ -943,6 +945,11 @@
 	return depTag == runtimeDepTag
 }
 
+func ExcludeInApexDepTag(depTag blueprint.DependencyTag) bool {
+	ccLibDepTag, ok := depTag.(libraryDependencyTag)
+	return ok && ccLibDepTag.excludeInApex
+}
+
 // Module contains the properties and members used by all C/C++ module types, and implements
 // the blueprint.Module interface.  It delegates to compiler, linker, and installer interfaces
 // to construct the output file.  Behavior can be customized with a Customizer, or "decorator",
@@ -1529,6 +1536,10 @@
 	return false
 }
 
+func (c *Module) RustApexExclude() bool {
+	return false
+}
+
 func (c *Module) IsStubsImplementationRequired() bool {
 	if lib := c.library; lib != nil {
 		return lib.IsStubsImplementationRequired()
@@ -2360,6 +2371,8 @@
 		OnlyInRecovery:       mod.OnlyInRecovery(),
 		Installable:          mod.Installable(),
 		RelativeInstallPath:  mod.RelativeInstallPath(),
+		// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+		RustApexExclude: mod.RustApexExclude(),
 	}
 }
 
@@ -3354,9 +3367,12 @@
 				depFile = sharedLibraryInfo.TableOfContents
 
 				if !sharedLibraryInfo.IsStubs {
-					depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
-					if info, ok := android.OtherModuleProvider(ctx, dep, ImplementationDepInfoProvider); ok {
-						depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+					// TODO(b/362509506): remove this additional check once all apex_exclude uses are switched to stubs.
+					if !linkableInfo.RustApexExclude {
+						depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+						if info, ok := android.OtherModuleProvider(ctx, dep, ImplementationDepInfoProvider); ok {
+							depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+						}
 					}
 				}
 
diff --git a/cc/compiler.go b/cc/compiler.go
index 1899f59..3730bbe 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -360,7 +360,7 @@
 	}
 }
 
-func addTargetFlags(ctx android.ModuleContext, flags Flags, tc config.Toolchain, version string, bpf bool) Flags {
+func AddTargetFlags(ctx android.ModuleContext, flags Flags, tc config.Toolchain, version string, bpf bool) Flags {
 	target := "-target " + tc.ClangTriple()
 	if ctx.Os().Class == android.Device {
 		if version == "" || version == "current" {
@@ -536,7 +536,7 @@
 	flags.Local.ConlyFlags = config.ClangFilterUnknownCflags(flags.Local.ConlyFlags)
 	flags.Local.LdFlags = config.ClangFilterUnknownCflags(flags.Local.LdFlags)
 
-	flags = addTargetFlags(ctx, flags, tc, ctx.minSdkVersion(), Bool(compiler.Properties.Bpf_target))
+	flags = AddTargetFlags(ctx, flags, tc, ctx.minSdkVersion(), Bool(compiler.Properties.Bpf_target))
 
 	hod := "Host"
 	if ctx.Os().Class == android.Device {
diff --git a/cc/coverage.go b/cc/coverage.go
index 4e058bd..c8ff82b 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -354,7 +354,7 @@
 	}
 }
 
-func parseSymbolFileForAPICoverage(ctx android.ModuleContext, symbolFile string) android.ModuleOutPath {
+func ParseSymbolFileForAPICoverage(ctx android.ModuleContext, symbolFile string) android.ModuleOutPath {
 	apiLevelsJson := android.GetApiLevelsJson(ctx)
 	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
 	outputFile := ctx.Module().(LinkableInterface).BaseModuleName() + ".xml"
diff --git a/cc/library.go b/cc/library.go
index 7b225ec..950ea91 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -460,7 +460,7 @@
 	return props
 }
 
-func commonLibraryLinkerFlags(ctx android.ModuleContext, flags Flags,
+func CommonLibraryLinkerFlags(ctx android.ModuleContext, flags Flags,
 	toolchain config.Toolchain, libName string) Flags {
 
 	mod, ok := ctx.Module().(LinkableInterface)
@@ -511,7 +511,7 @@
 // shared library).
 func (library *libraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = library.baseLinker.linkerFlags(ctx, flags)
-	flags = commonLibraryLinkerFlags(ctx, flags, ctx.toolchain(), library.getLibName(ctx))
+	flags = CommonLibraryLinkerFlags(ctx, flags, ctx.toolchain(), library.getLibName(ctx))
 	if library.static() {
 		flags.Local.CFlags = append(flags.Local.CFlags, library.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)...)
 	} else if library.shared() {
@@ -558,7 +558,7 @@
 		flags.Local.CommonFlags = removeInclude(flags.Local.CommonFlags)
 		flags.Local.CFlags = removeInclude(flags.Local.CFlags)
 
-		flags = addStubLibraryCompilerFlags(flags)
+		flags = AddStubLibraryCompilerFlags(flags)
 	}
 	return flags
 }
@@ -735,7 +735,7 @@
 		nativeAbiResult.VersionScript)
 	// Parse symbol file to get API list for coverage
 	if library.StubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
-		library.apiListCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
+		library.apiListCoverageXmlPath = ParseSymbolFileForAPICoverage(ctx, symbolFile)
 	}
 
 	return objs
@@ -2360,10 +2360,6 @@
 
 // setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
 func setStubsVersions(mctx android.BaseModuleContext, module VersionedLinkableInterface) {
-	// TODO(ivanlozano) remove this when Rust supports stubs
-	if module.RustLibraryInterface() {
-		return
-	}
 	if !module.BuildSharedVariant() || !canBeVersionVariant(module) {
 		return
 	}
@@ -2385,10 +2381,6 @@
 		return []string{""}
 	}
 	if m, ok := ctx.Module().(VersionedLinkableInterface); ok {
-		// TODO(ivanlozano) remove this when Rust supports stubs
-		if m.RustLibraryInterface() {
-			return []string{""}
-		}
 		if m.CcLibraryInterface() && canBeVersionVariant(m) {
 			setStubsVersions(ctx, m)
 			return append(slices.Clone(m.VersionedInterface().AllStubsVersions()), "")
@@ -2439,11 +2431,6 @@
 
 	m, ok := ctx.Module().(VersionedLinkableInterface)
 	if library := moduleVersionedInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
-		// TODO(ivanlozano) remove this when Rust supports stubs
-		if m.RustLibraryInterface() {
-			return
-		}
-
 		isLLNDK := m.IsLlndk()
 		isVendorPublicLibrary := m.IsVendorPublicLibrary()
 
diff --git a/cc/linkable.go b/cc/linkable.go
index d0fda64..337b459 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -84,6 +84,10 @@
 	SetMinSdkVersion(version string)
 	ApexSdkVersion() android.ApiLevel
 	ImplementationModuleNameForMake(ctx android.BaseModuleContext) string
+
+	// RustApexExclude returns ApexExclude() for Rust modules; always returns false for all non-Rust modules.
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+	RustApexExclude() bool
 }
 
 // LinkableInterface is an interface for a type of module that is linkable in a C++ library.
diff --git a/cc/linker.go b/cc/linker.go
index 20c2f4a..f854937 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -471,7 +471,7 @@
 
 // ModuleContext extends BaseModuleContext
 // BaseModuleContext should know if LLD is used?
-func commonLinkerFlags(ctx android.ModuleContext, flags Flags, useClangLld bool,
+func CommonLinkerFlags(ctx android.ModuleContext, flags Flags, useClangLld bool,
 	toolchain config.Toolchain, allow_undefined_symbols bool) Flags {
 	hod := "Host"
 	if ctx.Os().Class == android.Device {
@@ -480,7 +480,7 @@
 
 	mod, ok := ctx.Module().(LinkableInterface)
 	if !ok {
-		ctx.ModuleErrorf("trying to add commonLinkerFlags to non-LinkableInterface module.")
+		ctx.ModuleErrorf("trying to add CommonLinkerFlags to non-LinkableInterface module.")
 		return flags
 	}
 	if useClangLld {
@@ -531,7 +531,7 @@
 	toolchain := ctx.toolchain()
 	allow_undefined_symbols := Bool(linker.Properties.Allow_undefined_symbols)
 
-	flags = commonLinkerFlags(ctx, flags, linker.useClangLld(ctx), toolchain,
+	flags = CommonLinkerFlags(ctx, flags, linker.useClangLld(ctx), toolchain,
 		allow_undefined_symbols)
 
 	if !toolchain.Bionic() && ctx.Os() != android.LinuxMusl {
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 4321beb..1f0fc07 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -236,7 +236,7 @@
 	pctx.StaticVariable("StubLibraryCompilerFlags", strings.Join(stubLibraryCompilerFlags, " "))
 }
 
-func addStubLibraryCompilerFlags(flags Flags) Flags {
+func AddStubLibraryCompilerFlags(flags Flags) Flags {
 	flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...)
 	// All symbols in the stubs library should be visible.
 	if inList("-fvisibility=hidden", flags.Local.CFlags) {
@@ -247,7 +247,7 @@
 
 func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
 	flags = stub.baseCompiler.compilerFlags(ctx, flags, deps)
-	return addStubLibraryCompilerFlags(flags)
+	return AddStubLibraryCompilerFlags(flags)
 }
 
 type NdkApiOutputs struct {
@@ -510,7 +510,7 @@
 		}
 	}
 	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
-		c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
+		c.parsedCoverageXmlPath = ParseSymbolFileForAPICoverage(ctx, symbolFile)
 	}
 	return objs
 }
diff --git a/cc/stub_library.go b/cc/stub_library.go
index 2291153..9d7b5bc 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -59,10 +59,6 @@
 	vendorStubLibraryMap := make(map[string]bool)
 	ctx.VisitAllModules(func(module android.Module) {
 		if m, ok := module.(VersionedLinkableInterface); ok {
-			// TODO(ivanlozano) remove this when Rust supports stubs
-			if m.RustLibraryInterface() {
-				return
-			}
 			if IsStubTarget(android.OtherModuleProviderOrDefault(ctx, m, LinkableInfoProvider)) {
 				if name := getInstalledFileName(ctx, m); name != "" {
 					stubLibraryMap[name] = true
diff --git a/rust/library.go b/rust/library.go
index 9912c94..49169ac 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -25,6 +25,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	cc_config "android/soong/cc/config"
 )
 
 var (
@@ -79,9 +80,13 @@
 	// Whether this library is part of the Rust toolchain sysroot.
 	Sysroot *bool
 
-	// Exclude this rust_ffi target from being included in APEXes.
-	// TODO(b/362509506): remove this once stubs are properly supported by rust_ffi targets.
+	// Deprecated - exclude this rust_ffi target from being included in APEXes.
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
 	Apex_exclude *bool
+
+	// Generate stubs to make this library accessible to APEXes.
+	// Can only be set for modules producing shared libraries.
+	Stubs cc.StubsProperties `android:"arch_variant"`
 }
 
 type LibraryMutatedProperties struct {
@@ -109,6 +114,15 @@
 
 	// Whether this library variant should be link libstd via rlibs
 	VariantIsStaticStd bool `blueprint:"mutated"`
+
+	// This variant is a stubs lib
+	BuildStubs bool `blueprint:"mutated"`
+	// This variant is the latest version
+	IsLatestVersion bool `blueprint:"mutated"`
+	// Version of the stubs lib
+	StubsVersion string `blueprint:"mutated"`
+	// List of all stubs versions associated with an implementation lib
+	AllStubsVersions []string `blueprint:"mutated"`
 }
 
 type libraryDecorator struct {
@@ -123,9 +137,28 @@
 
 	// table-of-contents file for cdylib crates to optimize out relinking when possible
 	tocFile android.OptionalPath
+
+	// Path to the file containing the APIs exported by this library
+	stubsSymbolFilePath    android.Path
+	apiListCoverageXmlPath android.ModuleOutPath
+	versionScriptPath      android.OptionalPath
+}
+
+func (library *libraryDecorator) stubs() bool {
+	return library.MutatedProperties.BuildStubs
+}
+
+func (library *libraryDecorator) setAPIListCoverageXMLPath(xml android.ModuleOutPath) {
+	library.apiListCoverageXmlPath = xml
+}
+
+func (library *libraryDecorator) libraryProperties() LibraryCompilerProperties {
+	return library.Properties
 }
 
 type libraryInterface interface {
+	cc.VersionedInterface
+
 	rlib() bool
 	dylib() bool
 	static() bool
@@ -161,6 +194,11 @@
 	BuildOnlyShared()
 
 	toc() android.OptionalPath
+
+	IsStubsImplementationRequired() bool
+	setAPIListCoverageXMLPath(out android.ModuleOutPath)
+
+	libraryProperties() LibraryCompilerProperties
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
@@ -276,58 +314,63 @@
 
 var _ compiler = (*libraryDecorator)(nil)
 var _ libraryInterface = (*libraryDecorator)(nil)
+var _ cc.VersionedInterface = (*libraryDecorator)(nil)
 var _ exportedFlagsProducer = (*libraryDecorator)(nil)
 var _ cc.VersionedInterface = (*libraryDecorator)(nil)
 
 func (library *libraryDecorator) HasLLNDKStubs() bool {
-	// Rust does not support LLNDK yet.
+	// Rust LLNDK is currently unsupported
 	return false
 }
 
 func (library *libraryDecorator) HasVendorPublicLibrary() bool {
-	// Rust does not support vendor public library.
+	// Rust does not support vendor_public_library yet.
 	return false
 }
 
 func (library *libraryDecorator) HasLLNDKHeaders() bool {
-	// Rust does not support LLNDK yet.
+	// Rust LLNDK is currently unsupported
 	return false
 }
 
 func (library *libraryDecorator) HasStubsVariants() bool {
-	return false
+	// Just having stubs.symbol_file is enough to create a stub variant. In that case
+	// the stub for the future API level is created.
+	return library.Properties.Stubs.Symbol_file != nil ||
+		len(library.Properties.Stubs.Versions) > 0
 }
 
 func (library *libraryDecorator) IsStubsImplementationRequired() bool {
-	return false
+	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
 }
 
 func (library *libraryDecorator) GetAPIListCoverageXMLPath() android.ModuleOutPath {
-	panic(fmt.Errorf("GetAPIListCoverageXMLPath called on unsupported Rust module"))
+	return library.apiListCoverageXmlPath
 }
 
 func (library *libraryDecorator) AllStubsVersions() []string {
-	panic(fmt.Errorf("AllStubsVersions called on unsupported Rust module"))
+	return library.MutatedProperties.AllStubsVersions
 }
 
 func (library *libraryDecorator) SetAllStubsVersions(versions []string) {
-	panic(fmt.Errorf("ApexSdkVersion called on unsupported Rust module"))
+	library.MutatedProperties.AllStubsVersions = versions
 }
 
 func (library *libraryDecorator) SetStubsVersion(version string) {
-	panic(fmt.Errorf("SetStubsVersion called on unsupported Rust module"))
+	library.MutatedProperties.StubsVersion = version
 }
 
 func (library *libraryDecorator) SetBuildStubs(isLatest bool) {
-	panic(fmt.Errorf("SetBuildStubs called on unsupported Rust module"))
+	library.MutatedProperties.BuildStubs = true
+	library.MutatedProperties.IsLatestVersion = isLatest
 }
 
 func (library *libraryDecorator) BuildStubs() bool {
-	return false
+	return library.MutatedProperties.BuildStubs
 }
 
 func (library *libraryDecorator) ImplementationModuleName(name string) string {
-	panic(fmt.Errorf("ImplementationModuleName called on unsupported Rust module"))
+	return name
 }
 
 func (library *libraryDecorator) IsLLNDKMovedToApex() bool {
@@ -335,12 +378,20 @@
 	return false
 }
 
-func (library *libraryDecorator) StubsVersions(ctx android.BaseModuleContext) []string {
-	panic(fmt.Errorf("StubsVersions called on unsupported Rust module"))
+func (library *libraryDecorator) StubsVersion() string {
+	return library.MutatedProperties.StubsVersion
 }
 
-func (library *libraryDecorator) StubsVersion() string {
-	panic(fmt.Errorf("StubsVersions called on unsupported Rust module"))
+// stubsVersions implements cc.VersionedInterface.
+func (library *libraryDecorator) StubsVersions(ctx android.BaseModuleContext) []string {
+	if !library.HasStubsVariants() {
+		return nil
+	}
+
+	// Future API level is implicitly added if there isn't
+	versions := cc.AddCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	cc.NormalizeVersions(ctx, versions)
+	return versions
 }
 
 // rust_library produces all Rust variants (rust_library_dylib and
@@ -420,6 +471,18 @@
 	return module.Init()
 }
 
+func CheckRustLibraryProperties(mctx android.DefaultableHookContext) {
+	lib := mctx.Module().(*Module).compiler.(libraryInterface)
+	if !lib.buildShared() {
+		if lib.libraryProperties().Stubs.Symbol_file != nil ||
+			lib.libraryProperties().Stubs.Implementation_installable != nil ||
+			len(lib.libraryProperties().Stubs.Versions) > 0 {
+
+			mctx.PropertyErrorf("stubs", "stubs properties can only be set for rust_ffi or rust_ffi_shared modules")
+		}
+	}
+}
+
 func (library *libraryDecorator) BuildOnlyFFI() {
 	library.MutatedProperties.BuildDylib = false
 	// we build rlibs for later static ffi linkage.
@@ -479,6 +542,7 @@
 
 	module.compiler = library
 
+	module.SetDefaultableHook(CheckRustLibraryProperties)
 	return module, library
 }
 
@@ -641,7 +705,11 @@
 	}
 
 	// Call the appropriate builder for this library type
-	if library.rlib() {
+	if library.stubs() {
+		ccFlags := library.getApiStubsCcFlags(ctx)
+		stubObjs := library.compileModuleLibApiStubs(ctx, ccFlags)
+		cc.BuildRustStubs(ctx, outputFile, deps.CrtBegin, deps.CrtEnd, stubObjs, ccFlags)
+	} else if library.rlib() {
 		ret.kytheFile = TransformSrctoRlib(ctx, crateRootPath, deps, flags, outputFile).kytheFile
 	} else if library.dylib() {
 		ret.kytheFile = TransformSrctoDylib(ctx, crateRootPath, deps, flags, outputFile).kytheFile
@@ -657,13 +725,13 @@
 	}
 
 	// Since we have FFI rlibs, we need to collect their includes as well
-	if library.static() || library.shared() || library.rlib() {
+	if library.static() || library.shared() || library.rlib() || library.stubs() {
 		android.SetProvider(ctx, cc.FlagExporterInfoProvider, cc.FlagExporterInfo{
 			IncludeDirs: android.FirstUniquePaths(library.includeDirs),
 		})
 	}
 
-	if library.shared() {
+	if library.shared() || library.stubs() {
 		// Optimize out relinking against shared libraries whose interface hasn't changed by
 		// depending on a table of contents file instead of the library itself.
 		tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.SharedLibSuffix()[1:]+".toc")
@@ -674,9 +742,7 @@
 			TableOfContents: android.OptionalPathForPath(tocFile),
 			SharedLibrary:   outputFile,
 			Target:          ctx.Target(),
-			// TODO: when rust supports stubs uses the stubs state rather than inferring it from
-			//  apex_exclude.
-			IsStubs: Bool(library.Properties.Apex_exclude),
+			IsStubs:         library.BuildStubs(),
 		})
 	}
 
@@ -688,6 +754,7 @@
 			TransitiveStaticLibrariesForOrdering: depSet,
 		})
 	}
+	cc.AddStubDependencyProviders(ctx)
 
 	library.flagExporter.setProvider(ctx)
 
@@ -707,6 +774,53 @@
 	}
 }
 
+func (library *libraryDecorator) getApiStubsCcFlags(ctx ModuleContext) cc.Flags {
+	ccFlags := cc.Flags{}
+	toolchain := cc_config.FindToolchain(ctx.Os(), ctx.Arch())
+
+	platformSdkVersion := ""
+	if ctx.Device() {
+		platformSdkVersion = ctx.Config().PlatformSdkVersion().String()
+	}
+	minSdkVersion := cc.MinSdkVersion(ctx.RustModule(), cc.CtxIsForPlatform(ctx), ctx.Device(), platformSdkVersion)
+
+	// Collect common CC compilation flags
+	ccFlags = cc.CommonLinkerFlags(ctx, ccFlags, true, toolchain, false)
+	ccFlags = cc.CommonLibraryLinkerFlags(ctx, ccFlags, toolchain, library.getStem(ctx))
+	ccFlags = cc.AddStubLibraryCompilerFlags(ccFlags)
+	ccFlags = cc.AddTargetFlags(ctx, ccFlags, toolchain, minSdkVersion, false)
+
+	return ccFlags
+}
+
+func (library *libraryDecorator) compileModuleLibApiStubs(ctx ModuleContext, ccFlags cc.Flags) cc.Objects {
+	mod := ctx.RustModule()
+
+	symbolFile := String(library.Properties.Stubs.Symbol_file)
+	library.stubsSymbolFilePath = android.PathForModuleSrc(ctx, symbolFile)
+
+	apiParams := cc.ApiStubsParams{
+		NotInPlatform:  mod.NotInPlatform(),
+		IsNdk:          mod.IsNdk(ctx.Config()),
+		BaseModuleName: mod.BaseModuleName(),
+		ModuleName:     ctx.ModuleName(),
+	}
+	flag := cc.GetApiStubsFlags(apiParams)
+
+	nativeAbiResult := cc.ParseNativeAbiDefinition(ctx, symbolFile,
+		android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), flag)
+	objs := cc.CompileStubLibrary(ctx, ccFlags, nativeAbiResult.StubSrc, mod.getSharedFlags())
+
+	library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.VersionScript)
+
+	// Parse symbol file to get API list for coverage
+	if library.StubsVersion() == "current" && ctx.PrimaryArch() && !mod.InRecovery() && !mod.InProduct() && !mod.InVendor() {
+		library.apiListCoverageXmlPath = cc.ParseSymbolFileForAPICoverage(ctx, symbolFile)
+	}
+
+	return objs
+}
+
 func (library *libraryDecorator) rustdoc(ctx ModuleContext, flags Flags,
 	deps PathDeps) android.OptionalPath {
 	// rustdoc has builtin support for documenting config specific information
diff --git a/rust/library_test.go b/rust/library_test.go
index e5fd5e0..1198fcc 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -482,3 +482,289 @@
 			extra_exported_symbols: "libbar.map.txt",
 		}`)
 }
+
+func TestStubsVersions(t *testing.T) {
+	t.Parallel()
+	bp := `
+		rust_ffi {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.rs"],
+			stubs: {
+				versions: ["29", "R", "current"],
+			},
+		}
+	`
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		})).RunTestWithBp(t, bp)
+
+	variants := ctx.ModuleVariantsForTests("libfoo")
+	for _, expectedVer := range []string{"29", "R", "current"} {
+		expectedVariant := "android_arm_armv7-a-neon_shared_" + expectedVer
+		if !android.InList(expectedVariant, variants) {
+			t.Errorf("missing expected variant: %q", expectedVariant)
+		}
+	}
+}
+
+func TestStubsVersions_NotSorted(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+		stubs: {
+				versions: ["29", "current", "R"],
+			},
+		}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		}))
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`"libfoo" .*: versions: not sorted`)).RunTestWithBp(t, bp)
+}
+
+func TestStubsVersions_ParseError(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+			stubs: {
+				versions: ["29", "current", "X"],
+			},
+		}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		}))
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`"libfoo" .*: versions: "X" could not be parsed as an integer and is not a recognized codename`)).RunTestWithBp(t, bp)
+}
+
+func TestVersionedStubs(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libFoo",
+		crate_name: "Foo",
+		srcs: ["foo.rs"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
+	cc_library_shared {
+		name: "libBar",
+		srcs: ["bar.c"],
+		shared_libs: ["libFoo#1"],
+	}
+
+	rust_library {
+		name: "libbar_rs",
+		crate_name: "bar_rs",
+		srcs: ["bar.rs"],
+		shared_libs: ["libFoo#1"],
+	}
+	rust_ffi {
+		name: "libbar_ffi_rs",
+		crate_name: "bar_ffi_rs",
+		srcs: ["bar.rs"],
+		shared_libs: ["libFoo#1"],
+	}
+	`
+
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture()).RunTestWithBp(t, bp)
+
+	variants := ctx.ModuleVariantsForTests("libFoo")
+	expectedVariants := []string{
+		"android_arm64_armv8-a_shared",
+		"android_arm64_armv8-a_shared_1",
+		"android_arm64_armv8-a_shared_2",
+		"android_arm64_armv8-a_shared_3",
+		"android_arm64_armv8-a_shared_current",
+		"android_arm_armv7-a-neon_shared",
+		"android_arm_armv7-a-neon_shared_1",
+		"android_arm_armv7-a-neon_shared_2",
+		"android_arm_armv7-a-neon_shared_3",
+		"android_arm_armv7-a-neon_shared_current",
+	}
+	variantsMismatch := false
+	if len(variants) != len(expectedVariants) {
+		variantsMismatch = true
+	} else {
+		for _, v := range expectedVariants {
+			if !android.InList(v, variants) {
+				variantsMismatch = false
+			}
+		}
+	}
+	if variantsMismatch {
+		t.Errorf("variants of libFoo expected:\n")
+		for _, v := range expectedVariants {
+			t.Errorf("%q\n", v)
+		}
+		t.Errorf(", but got:\n")
+		for _, v := range variants {
+			t.Errorf("%q\n", v)
+		}
+	}
+
+	libBarLinkRule := ctx.ModuleForTests("libBar", "android_arm64_armv8-a_shared").Rule("ld")
+	libBarFlags := libBarLinkRule.Args["libFlags"]
+
+	libBarRsRustcRule := ctx.ModuleForTests("libbar_rs", "android_arm64_armv8-a_dylib").Rule("rustc")
+	libBarRsFlags := libBarRsRustcRule.Args["linkFlags"]
+
+	libBarFfiRsRustcRule := ctx.ModuleForTests("libbar_ffi_rs", "android_arm64_armv8-a_shared").Rule("rustc")
+	libBarFfiRsFlags := libBarFfiRsRustcRule.Args["linkFlags"]
+
+	libFoo1StubPath := "libFoo/android_arm64_armv8-a_shared_1/unstripped/libFoo.so"
+	if !strings.Contains(libBarFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarFlags)
+	}
+	if !strings.Contains(libBarRsFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarRsFlags)
+	}
+	if !strings.Contains(libBarFfiRsFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarFfiRsFlags)
+	}
+}
+
+func TestCheckConflictingExplicitVersions(t *testing.T) {
+	t.Parallel()
+	bp := `
+	cc_library_shared {
+		name: "libbar",
+		srcs: ["bar.c"],
+		shared_libs: ["libfoo", "libfoo#impl"],
+	}
+
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+		stubs: {
+			versions: ["29", "current"],
+		},
+	}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture())
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`duplicate shared libraries with different explicit versions`)).RunTestWithBp(t, bp)
+}
+
+func TestAddnoOverride64GlobalCflags(t *testing.T) {
+	t.Parallel()
+	bp := `
+		cc_library_shared {
+			name: "libclient",
+			srcs: ["foo.c"],
+			shared_libs: ["libfoo#1"],
+		}
+
+		rust_ffi_shared {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
+		cc_library_shared {
+			name: "libbar",
+			export_include_dirs: ["include/libbar"],
+			srcs: ["foo.c"],
+		}`
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture()).RunTestWithBp(t, bp)
+
+	cFlags := ctx.ModuleForTests("libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+
+	if !strings.Contains(cFlags, "${config.NoOverride64GlobalCflags}") {
+		t.Errorf("expected %q in cflags, got %q", "${config.NoOverride64GlobalCflags}", cFlags)
+	}
+}
+
+// Make sure the stubs properties can only be used in modules producing shared libs
+func TestRustStubsFFIOnly(t *testing.T) {
+	testRustError(t, "stubs properties", `
+		rust_library {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+			},
+		}
+	`)
+
+	testRustError(t, "stubs properties", `
+		rust_library {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`)
+
+	testRustError(t, "stubs properties", `
+		rust_ffi_static {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+			},
+		}
+	`)
+	testRustError(t, "stubs properties", `
+		rust_ffi_static {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`)
+}
+
+// TODO: When rust_ffi libraries support export_*_lib_headers,
+// add a test similar to cc.TestStubsLibReexportsHeaders
diff --git a/rust/rust.go b/rust/rust.go
index 2332b52..557bdc3 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -162,9 +162,36 @@
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
-	// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
+	// The API level that this module is built against. The APIs of this API level will be
+	// visible at build time, but use of any APIs newer than min_sdk_version will render the
+	// module unloadable on older devices.  In the future it will be possible to weakly-link new
+	// APIs, making the behavior match Java: such modules will load on older devices, but
+	// calling new APIs on devices that do not support them will result in a crash.
+	//
+	// This property has the same behavior as sdk_version does for Java modules. For those
+	// familiar with Android Gradle, the property behaves similarly to how compileSdkVersion
+	// does for Java code.
+	//
+	// In addition, setting this property causes two variants to be built, one for the platform
+	// and one for apps.
+	Sdk_version *string
+
+	// Minimum OS API level supported by this C or C++ module. This property becomes the value
+	// of the __ANDROID_API__ macro. When the C or C++ module is included in an APEX or an APK,
+	// this property is also used to ensure that the min_sdk_version of the containing module is
+	// not older (i.e. less) than this module's min_sdk_version. When not set, this property
+	// defaults to the value of sdk_version.  When this is set to "apex_inherit", this tracks
+	// min_sdk_version of the containing APEX. When the module
+	// is not built for an APEX, "apex_inherit" defaults to sdk_version.
 	Min_sdk_version *string
 
+	// Variant is an SDK variant created by sdkMutator
+	IsSdkVariant bool `blueprint:"mutated"`
+
+	// Set by factories of module types that can only be referenced from variants compiled against
+	// the SDK.
+	AlwaysSdk bool `blueprint:"mutated"`
+
 	HideFromMake   bool `blueprint:"mutated"`
 	PreventInstall bool `blueprint:"mutated"`
 
@@ -209,6 +236,9 @@
 	apexSdkVersion android.ApiLevel
 
 	transitiveAndroidMkSharedLibs depset.DepSet[string]
+
+	// Shared flags among stubs build rules of this module
+	sharedFlags cc.SharedFlags
 }
 
 func (mod *Module) Header() bool {
@@ -374,7 +404,8 @@
 }
 
 func (mod *Module) IsVendorPublicLibrary() bool {
-	return mod.VendorProperties.IsVendorPublicLibrary
+	// Rust modules do not currently support vendor_public_library
+	return false
 }
 
 func (mod *Module) SdkAndPlatformVariantVisibleToMake() bool {
@@ -383,10 +414,12 @@
 }
 
 func (c *Module) IsVndkPrivate() bool {
+	// Rust modules do not currently support VNDK variants
 	return false
 }
 
 func (c *Module) IsLlndk() bool {
+	// Rust modules do not currently support LLNDK variants
 	return false
 }
 
@@ -395,31 +428,34 @@
 }
 
 func (m *Module) NeedsLlndkVariants() bool {
+	// Rust modules do not currently support LLNDK variants
 	return false
 }
 
 func (m *Module) NeedsVendorPublicLibraryVariants() bool {
+	// Rust modules do not currently support vendor_public_library
 	return false
 }
 
 func (mod *Module) HasLlndkStubs() bool {
+	// Rust modules do not currently support LLNDK stubs
 	return false
 }
 
 func (mod *Module) SdkVersion() string {
-	return ""
+	return String(mod.Properties.Sdk_version)
 }
 
 func (mod *Module) AlwaysSdk() bool {
-	return false
+	return mod.Properties.AlwaysSdk
 }
 
 func (mod *Module) IsSdkVariant() bool {
-	return false
+	return mod.Properties.IsSdkVariant
 }
 
 func (mod *Module) SplitPerApiLevel() bool {
-	return false
+	return cc.CanUseSdk(mod) && mod.IsCrt()
 }
 
 func (mod *Module) XrefRustFiles() android.Paths {
@@ -743,19 +779,42 @@
 }
 
 func (mod *Module) IsStubs() bool {
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		return lib.BuildStubs()
+	}
 	return false
 }
 
 func (mod *Module) HasStubsVariants() bool {
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		return lib.HasStubsVariants()
+	}
 	return false
 }
 
 func (mod *Module) ApexSdkVersion() android.ApiLevel {
-	panic(fmt.Errorf("ApexSdkVersion called on unsupported Rust module: %q", mod.BaseModuleName()))
+	return mod.apexSdkVersion
+}
+
+func (mod *Module) RustApexExclude() bool {
+	return mod.ApexExclude()
+}
+
+func (mod *Module) getSharedFlags() *cc.SharedFlags {
+	shared := &mod.sharedFlags
+	if shared.FlagsMap == nil {
+		shared.NumSharedFlags = 0
+		shared.FlagsMap = make(map[string]string)
+	}
+	return shared
 }
 
 func (mod *Module) ImplementationModuleNameForMake(ctx android.BaseModuleContext) string {
-	return mod.Name()
+	name := mod.BaseModuleName()
+	if versioned, ok := mod.compiler.(cc.VersionedInterface); ok {
+		name = versioned.ImplementationModuleName(name)
+	}
+	return name
 }
 
 func (mod *Module) Multilib() string {
@@ -903,7 +962,7 @@
 }
 
 func (mod *Module) SetSdkVersion(s string) {
-	panic(fmt.Errorf("SetSdkVersion called on unsupported Rust module: %q", mod.BaseModuleName()))
+	mod.Properties.Sdk_version = StringPtr(s)
 }
 
 func (mod *Module) SetMinSdkVersion(s string) {
@@ -1066,6 +1125,10 @@
 	linkableInfo.Shared = mod.Shared()
 	linkableInfo.CrateName = mod.CrateName()
 	linkableInfo.ExportedCrateLinkDirs = mod.ExportedCrateLinkDirs()
+	if lib, ok := mod.compiler.(cc.VersionedInterface); ok {
+		linkableInfo.StubsVersion = lib.StubsVersion()
+	}
+
 	android.SetProvider(ctx, cc.LinkableInfoProvider, linkableInfo)
 
 	rustInfo := &RustInfo{
@@ -1108,10 +1171,9 @@
 	// does compilation and linking in one step. If this changes in the future,
 	// move this as appropriate.
 	ccInfo.LinkerInfo = &cc.LinkerInfo{
-		WholeStaticLibs:      mod.compiler.baseCompilerProps().Whole_static_libs,
-		StaticLibs:           mod.compiler.baseCompilerProps().Static_libs,
-		SharedLibs:           mod.compiler.baseCompilerProps().Shared_libs,
-		UnstrippedOutputFile: mod.UnstrippedOutputFile(),
+		WholeStaticLibs: mod.compiler.baseCompilerProps().Whole_static_libs,
+		StaticLibs:      mod.compiler.baseCompilerProps().Static_libs,
+		SharedLibs:      mod.compiler.baseCompilerProps().Shared_libs,
 	}
 
 	android.SetProvider(ctx, cc.CcInfoProvider, ccInfo)
@@ -1277,6 +1339,21 @@
 	if mod.sanitize != nil {
 		mod.sanitize.begin(ctx)
 	}
+
+	if mod.UseSdk() && mod.IsSdkVariant() {
+		sdkVersion := ""
+		if ctx.Device() {
+			sdkVersion = mod.SdkVersion()
+		}
+		version, err := cc.NativeApiLevelFromUser(ctx, sdkVersion)
+		if err != nil {
+			ctx.PropertyErrorf("sdk_version", err.Error())
+			mod.Properties.Sdk_version = nil
+		} else {
+			mod.Properties.Sdk_version = StringPtr(version.String())
+		}
+	}
+
 }
 
 func (mod *Module) Prebuilt() *android.Prebuilt {
@@ -1552,9 +1629,12 @@
 				sharedLibraryInfo, exportedInfo := cc.ChooseStubOrImpl(ctx, dep)
 
 				if !sharedLibraryInfo.IsStubs {
-					depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
-					if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
-						depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+					// TODO(b/362509506): remove this additional check once all apex_exclude uses are switched to stubs.
+					if !linkableInfo.RustApexExclude {
+						depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+						if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+							depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+						}
 					}
 				}
 
@@ -1922,7 +2002,7 @@
 var _ android.ApexModule = (*Module)(nil)
 
 // If a module is marked for exclusion from apexes, don't provide apex variants.
-// TODO(b/362509506): remove this once stubs are properly supported by rust_ffi targets.
+// TODO(b/362509506): remove this once all apex_exclude usages are removed.
 func (m *Module) CanHaveApexVariants() bool {
 	if m.ApexExclude() {
 		return false
@@ -1959,6 +2039,13 @@
 }
 
 // Implements android.ApexModule
+func (mod *Module) AlwaysRequiresPlatformApexVariant() bool {
+	// stub libraries and native bridge libraries are always available to platform
+	// TODO(b/362509506): remove the ApexExclude() check once all apex_exclude uses are switched to stubs.
+	return mod.IsStubs() || mod.Target().NativeBridge == android.NativeBridgeEnabled || mod.ApexExclude()
+}
+
+// Implements android.ApexModule
 func (mod *Module) OutgoingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
 	if depTag == procMacroDepTag || depTag == customBindgenDepTag {
 		return false
@@ -1971,18 +2058,55 @@
 		return false
 	}
 
+	if depTag == cc.StubImplDepTag {
+		// We don't track from an implementation library to its stubs.
+		return false
+	}
+
+	if cc.ExcludeInApexDepTag(depTag) {
+		return false
+	}
+
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if mod.ApexExclude() {
+		return false
+	}
+
 	return true
 }
 
 func (mod *Module) IncomingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
-	return !mod.ApexExclude()
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if mod.ApexExclude() {
+		return false
+	}
+
+	if mod.HasStubsVariants() {
+		if cc.IsSharedDepTag(depTag) {
+			// dynamic dep to a stubs lib crosses APEX boundary
+			return false
+		}
+		if cc.IsRuntimeDepTag(depTag) {
+			// runtime dep to a stubs lib also crosses APEX boundary
+			return false
+		}
+		if cc.IsHeaderDepTag(depTag) {
+			return false
+		}
+	}
+	return true
 }
 
 // Overrides ApexModule.IsInstallabeToApex()
 func (mod *Module) IsInstallableToApex() bool {
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if mod.ApexExclude() {
+		return false
+	}
+
 	if mod.compiler != nil {
-		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.shared() || lib.dylib()) {
-			return true
+		if lib, ok := mod.compiler.(libraryInterface); ok {
+			return (lib.shared() || lib.dylib()) && !lib.BuildStubs()
 		}
 		if _, ok := mod.compiler.(*binaryDecorator); ok {
 			return true
diff --git a/rust/testing.go b/rust/testing.go
index 0ce1b66..2082b52 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -80,7 +80,6 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
-			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
 			host_supported: true,
@@ -88,6 +87,13 @@
 			llndk: {
 				symbol_file: "liblog.map.txt",
 			},
+			stubs: {
+				symbol_file: "liblog.map.txt",
+				versions: [
+					"29",
+					"30",
+				],
+			},
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",