Merge "rust: Propagate static libs correctly" into main
diff --git a/rust/binary.go b/rust/binary.go
index 3c7a482..5a03d91 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -139,7 +139,10 @@
 
 	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.rustLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.sharedLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.staticLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.wholeStaticLibObjects...)
 
 	if binary.stripper.NeedsStrip(ctx) {
 		strippedOutputFile := outputFile
diff --git a/rust/library.go b/rust/library.go
index 3686bf9..94f5730 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -674,7 +674,10 @@
 
 	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.rustLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.sharedLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.staticLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.wholeStaticLibObjects...)
 
 	if String(library.Properties.Version_script) != "" {
 		if String(library.Properties.Extra_exported_symbols) != "" {
@@ -722,9 +725,17 @@
 		ret.kytheFile = TransformSrctoShared(ctx, crateRootPath, deps, flags, outputFile).kytheFile
 	}
 
+	// rlibs and dylibs propagate their shared, whole static, and rustlib dependencies
 	if library.rlib() || library.dylib() {
 		library.flagExporter.exportLinkDirs(deps.linkDirs...)
-		library.flagExporter.exportLinkObjects(deps.linkObjects...)
+		library.flagExporter.exportRustLibs(deps.rustLibObjects...)
+		library.flagExporter.exportSharedLibs(deps.sharedLibObjects...)
+		library.flagExporter.exportWholeStaticLibs(deps.wholeStaticLibObjects...)
+	}
+
+	// rlibs also propagate their staticlibs dependencies
+	if library.rlib() {
+		library.flagExporter.exportStaticLibs(deps.staticLibObjects...)
 	}
 
 	// Since we have FFI rlibs, we need to collect their includes as well
@@ -759,6 +770,7 @@
 	}
 	cc.AddStubDependencyProviders(ctx)
 
+	// Set our flagexporter provider to export relevant Rust flags
 	library.flagExporter.setProvider(ctx)
 
 	return ret
diff --git a/rust/rust.go b/rust/rust.go
index 5cc8c07..f4fda22 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -497,8 +497,11 @@
 
 	// 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
+	linkDirs              []string
+	rustLibObjects        []string
+	staticLibObjects      []string
+	wholeStaticLibObjects []string
+	sharedLibObjects      []string
 
 	// exportedLinkDirs are exported linkDirs for direct rlib dependencies to
 	// cc_library_static dependants of rlibs.
@@ -531,7 +534,10 @@
 
 type exportedFlagsProducer interface {
 	exportLinkDirs(...string)
-	exportLinkObjects(...string)
+	exportRustLibs(...string)
+	exportStaticLibs(...string)
+	exportWholeStaticLibs(...string)
+	exportSharedLibs(...string)
 }
 
 type xref interface {
@@ -539,23 +545,41 @@
 }
 
 type flagExporter struct {
-	linkDirs    []string
-	ccLinkDirs  []string
-	linkObjects []string
+	linkDirs              []string
+	ccLinkDirs            []string
+	rustLibPaths          []string
+	staticLibObjects      []string
+	sharedLibObjects      []string
+	wholeStaticLibObjects []string
 }
 
 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) exportRustLibs(flags ...string) {
+	flagExporter.rustLibPaths = android.FirstUniqueStrings(append(flagExporter.rustLibPaths, flags...))
+}
+
+func (flagExporter *flagExporter) exportStaticLibs(flags ...string) {
+	flagExporter.staticLibObjects = android.FirstUniqueStrings(append(flagExporter.staticLibObjects, flags...))
+}
+
+func (flagExporter *flagExporter) exportSharedLibs(flags ...string) {
+	flagExporter.sharedLibObjects = android.FirstUniqueStrings(append(flagExporter.sharedLibObjects, flags...))
+}
+
+func (flagExporter *flagExporter) exportWholeStaticLibs(flags ...string) {
+	flagExporter.wholeStaticLibObjects = android.FirstUniqueStrings(append(flagExporter.wholeStaticLibObjects, flags...))
 }
 
 func (flagExporter *flagExporter) setProvider(ctx ModuleContext) {
 	android.SetProvider(ctx, FlagExporterInfoProvider, FlagExporterInfo{
-		LinkDirs:    flagExporter.linkDirs,
-		LinkObjects: flagExporter.linkObjects,
+		LinkDirs:              flagExporter.linkDirs,
+		RustLibObjects:        flagExporter.rustLibPaths,
+		StaticLibObjects:      flagExporter.staticLibObjects,
+		WholeStaticLibObjects: flagExporter.wholeStaticLibObjects,
+		SharedLibPaths:        flagExporter.sharedLibObjects,
 	})
 }
 
@@ -566,9 +590,12 @@
 }
 
 type FlagExporterInfo struct {
-	Flags       []string
-	LinkDirs    []string // TODO: this should be android.Paths
-	LinkObjects []string // TODO: this should be android.Paths
+	Flags                 []string
+	LinkDirs              []string
+	RustLibObjects        []string
+	StaticLibObjects      []string
+	WholeStaticLibObjects []string
+	SharedLibPaths        []string
 }
 
 var FlagExporterInfoProvider = blueprint.NewProvider[FlagExporterInfo]()
@@ -1547,10 +1574,14 @@
 			}
 
 			exportedInfo, _ := android.OtherModuleProvider(ctx, dep, FlagExporterInfoProvider)
-			//Append the dependencies exportedDirs, except for proc-macros which target a different arch/OS
+
+			//Append the dependencies exported objects, except for proc-macros which target a different arch/OS
 			if depTag != procMacroDepTag {
 				depPaths.depFlags = append(depPaths.depFlags, exportedInfo.Flags...)
-				depPaths.linkObjects = append(depPaths.linkObjects, exportedInfo.LinkObjects...)
+				depPaths.rustLibObjects = append(depPaths.rustLibObjects, exportedInfo.RustLibObjects...)
+				depPaths.sharedLibObjects = append(depPaths.sharedLibObjects, exportedInfo.SharedLibPaths...)
+				depPaths.staticLibObjects = append(depPaths.staticLibObjects, exportedInfo.StaticLibObjects...)
+				depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, exportedInfo.WholeStaticLibObjects...)
 				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
 			}
 
@@ -1583,8 +1614,8 @@
 					return
 				}
 			}
-			linkObject := linkableInfo.OutputFile
-			if !linkObject.Valid() {
+			ccLibPath := linkableInfo.OutputFile
+			if !ccLibPath.Valid() {
 				if !ctx.Config().AllowMissingDependencies() {
 					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName())
 				} else {
@@ -1593,7 +1624,7 @@
 				return
 			}
 
-			linkPath := linkPathFromFilePath(linkObject.Path())
+			linkPath := linkPathFromFilePath(ccLibPath.Path())
 
 			exportDep := false
 			switch {
@@ -1602,20 +1633,25 @@
 					// rustc will bundle static libraries when they're passed with "-lstatic=<lib>". This will fail
 					// if the library is not prefixed by "lib".
 					if mod.Binary() {
-						// Binaries may sometimes need to link whole static libraries that don't start with 'lib'.
 						// Since binaries don't need to 'rebundle' these like libraries and only use these for the
 						// final linkage, pass the args directly to the linker to handle these cases.
-						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.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", ccLibPath.Path().String(), "-Wl,--no-whole-archive"}...)
+					} else if libName, ok := libNameFromFilePath(ccLibPath.Path()); ok {
+						depPaths.depFlags = append(depPaths.depFlags, "-lstatic:+whole-archive="+libName)
+						depPaths.depLinkFlags = append(depPaths.depLinkFlags, ccLibPath.Path().String())
 					} 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())
 					}
 				}
 
-				// 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())
+				if cc.IsWholeStaticLib(depTag) {
+					// Add whole staticlibs to wholeStaticLibObjects to propagate to Rust all dependents.
+					depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, ccLibPath.String())
+				} else {
+					// Otherwise add to staticLibObjects, which only propagate through rlibs to their dependents.
+					depPaths.staticLibObjects = append(depPaths.staticLibObjects, ccLibPath.String())
+
+				}
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
 
 				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
@@ -1647,8 +1683,8 @@
 
 				// Re-get linkObject as ChooseStubOrImpl actually tells us which
 				// object (either from stub or non-stub) to use.
-				linkObject = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
-				if !linkObject.Valid() {
+				ccLibPath = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
+				if !ccLibPath.Valid() {
 					if !ctx.Config().AllowMissingDependencies() {
 						ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName())
 					} else {
@@ -1656,10 +1692,10 @@
 					}
 					return
 				}
-				linkPath = linkPathFromFilePath(linkObject.Path())
+				linkPath = linkPathFromFilePath(ccLibPath.Path())
 
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
-				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
+				depPaths.sharedLibObjects = append(depPaths.sharedLibObjects, ccLibPath.String())
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
@@ -1678,15 +1714,15 @@
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 				mod.Properties.AndroidMkHeaderLibs = append(mod.Properties.AndroidMkHeaderLibs, makeLibName)
 			case depTag == cc.CrtBeginDepTag:
-				depPaths.CrtBegin = append(depPaths.CrtBegin, linkObject.Path())
+				depPaths.CrtBegin = append(depPaths.CrtBegin, ccLibPath.Path())
 			case depTag == cc.CrtEndDepTag:
-				depPaths.CrtEnd = append(depPaths.CrtEnd, linkObject.Path())
+				depPaths.CrtEnd = append(depPaths.CrtEnd, ccLibPath.Path())
 			}
 
-			// Make sure these dependencies are propagated
+			// Make sure shared dependencies are propagated
 			if lib, ok := mod.compiler.(exportedFlagsProducer); ok && exportDep {
 				lib.exportLinkDirs(linkPath)
-				lib.exportLinkObjects(linkObject.String())
+				lib.exportSharedLibs(ccLibPath.String())
 			}
 		} else {
 			switch {
@@ -1769,7 +1805,10 @@
 
 	// Dedup exported flags from dependencies
 	depPaths.linkDirs = android.FirstUniqueStrings(depPaths.linkDirs)
-	depPaths.linkObjects = android.FirstUniqueStrings(depPaths.linkObjects)
+	depPaths.rustLibObjects = android.FirstUniqueStrings(depPaths.rustLibObjects)
+	depPaths.staticLibObjects = android.FirstUniqueStrings(depPaths.staticLibObjects)
+	depPaths.wholeStaticLibObjects = android.FirstUniqueStrings(depPaths.wholeStaticLibObjects)
+	depPaths.sharedLibObjects = android.FirstUniqueStrings(depPaths.sharedLibObjects)
 	depPaths.depFlags = android.FirstUniqueStrings(depPaths.depFlags)
 	depPaths.depClangFlags = android.FirstUniqueStrings(depPaths.depClangFlags)
 	depPaths.depIncludePaths = android.FirstUniquePaths(depPaths.depIncludePaths)
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 9f65dec..858c4db 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -204,7 +204,7 @@
 		t.Errorf("Static library dependency not detected (dependency missing from AndroidMkStaticLibs)")
 	}
 
-	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=wholestatic") {
+	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic:+whole-archive=wholestatic") {
 		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
 	}
 
@@ -575,3 +575,176 @@
 		}
 	`)
 }
+
+func TestRustLinkPropagation(t *testing.T) {
+	// Test static and whole static propagation behavior
+	//
+	//  Whole static libs propagate through rlibs and through dylibs to
+	//  dependencies further down. rustc does not re-export whole-archived
+	//  static libs for dylibs, so this simulates re-exporting those symbols.
+	//
+	//  Static libs only propagate through rlibs to some final dylib. We propagate
+	//  normal static libs because we allow rustlib dependencies to represent
+	//  either rlibs or dylibs. Not propagating static libs through rlibs would
+	//  mean we'd need to always redeclare static libs throughout a dependency tree
+	//  We don't propagate past dylibs because they represent a final link.
+
+	ctx := testRust(t, `
+	rust_library_rlib {
+		name: "librlib1",
+		crate_name: "rlib1",
+		srcs: ["src/lib.rs"],
+		static_libs: ["libcc_static_rlib1"],
+		whole_static_libs: ["libcc_whole_static_rlib1"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib1",
+		crate_name: "dylib1",
+		static_libs: ["libcc_static_dylib1"],
+		srcs: ["src/lib.rs"],
+		whole_static_libs: ["libcc_whole_static_dylib1"],
+	}
+
+	rust_library_rlib {
+		name: "librlib2",
+		crate_name: "rlib2",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib1"],
+		static_libs: ["libcc_static_rlib2"],
+		whole_static_libs: ["libcc_whole_static_rlib2"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib2",
+		crate_name: "dylib2",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib1"],
+		rustlibs: ["libdylib1"],
+		static_libs: ["libcc_static_dylib2"],
+		whole_static_libs: ["libcc_whole_static_dylib2"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_rlib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_rlib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_dylib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_dylib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_rlib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_rlib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_dylib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_dylib2",
+		srcs:["foo.c"],
+	}
+
+	rust_library_rlib {
+		name: "librlib3",
+		crate_name: "rlib3",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib2"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib3",
+		crate_name: "dylib3",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib2"],
+		rustlibs: ["libdylib2"],
+	}
+	`)
+
+	librlib3 := ctx.ModuleForTests("librlib3", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
+	libdylib3 := ctx.ModuleForTests("libdylib3", "android_arm64_armv8-a_dylib").Rule("rustc")
+
+	// Test static lib propagation from:
+	// rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_static_rlib2.a") {
+		t.Errorf("direct dependency static lib not propagating from rlib to rlib; linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_static_rlib1.a") {
+		t.Errorf("indirect dependency static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_rlib1.a") {
+		t.Errorf("indirect dependency static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_rlib2.a") {
+		t.Errorf("direct dependency static lib not propagating from rlib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib (negative case, should not propagate)
+	if strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_dylib2.a") {
+		t.Errorf("direct dependency static lib propagating from dylib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib -> dylib (negative case, should not propagate)
+	if strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_dylib1.a") {
+		t.Errorf("indirect dependency static lib propagating from dylib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+
+	// Test whole static lib propagation from:
+	// rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_whole_static_rlib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_whole_static_rlib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_rlib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_rlib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_dylib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from dylib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_dylib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from dylib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+}