Add linker.config.pb support to android_filesystem

As part of the mk->bp conversion, all modules and partitions will
eventually be built with Soong. vendor.img (built with kati) uses some
rules in build/make/core to install a /etc/linker.config.pb file. This
CL adds this logic to `android_filesystem`. This soong module will
eventually be used to build vendor.img

There are two main inputs to linker.config.pb generation for vendor.
1. PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS, a list of json files
2. List of stub libraries installed in vendor

(1) will be passed to `android_filesystem` as `Linker_config_srcs`.

(2) has a subtle difference between kati and soong implementation. Kati
uses `SOONG_STUB_VENDOR_LIBRARIES` to determine the list of all vendor
stub libraries in the tree, and then uses `--system $TARGET_OUT/vendor`
to filter in the libraries which are actually installed. For the Soong
implementation, this will be replaced with ctx.VisitDirectDeps, followed
by child.HasStubVariants

Test: go test ./filesystem
Bug: 375686533

Change-Id: I6f9130d2aa866dcac9272b71939e40ed50a952ac
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 97421c8..d178710 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -25,6 +25,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/linkerconfig"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -146,6 +147,10 @@
 
 	Erofs ErofsProperties
 
+	// List of files (in .json format) that will be converted to a linker config file (in .pb format).
+	// The linker config file be installed in the filesystem at /etc/linker.config.pb
+	Linker_config_srcs []string `android:"path"`
+
 	// Determines if the module is auto-generated from Soong or not. If the module is
 	// auto-generated, its deps are exempted from visibility enforcement.
 	Is_auto_generated *bool
@@ -428,6 +433,7 @@
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
+	f.buildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	// run host_init_verifier
@@ -591,6 +597,7 @@
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
+	f.buildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
@@ -682,6 +689,32 @@
 	f.appendToEntry(ctx, eventLogtagsPath)
 }
 
+func (f *filesystem) buildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
+	getCStubLibs := func() []android.Module {
+		// Determine the list of C stub libraries that are part of this filesystem.
+		// These will be added to `provideLibs`.
+		// The current implementation assumes that stub libraries are listed explicitly in `deps`
+		// (direct deps). If this is not true, ctx.VisitDeps will need to be replaced by ctx.WalkDeps.
+		ret := []android.Module{}
+		ctx.VisitDirectDeps(func(child android.Module) {
+			if c, ok := child.(*cc.Module); ok && c.HasStubsVariants() {
+				ret = append(ret, c)
+			}
+		})
+		return ret
+	}
+
+	if len(f.properties.Linker_config_srcs) == 0 {
+		return
+	}
+
+	// cp to the final output
+	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
+	linkerconfig.BuildLinkerConfig(ctx, builder, android.PathsForModuleSrc(ctx, f.properties.Linker_config_srcs), getCStubLibs(), nil, output)
+
+	f.appendToEntry(ctx, output)
+}
+
 type partition interface {
 	PartitionType() string
 }
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 7300061..cb27f64 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -664,3 +664,24 @@
 	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
 	android.AssertDeepEquals(t, "Shared library dep of overridden binary should not be installed", fileList, "bin/binfoo1\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo2.so\nlib64/libm.so\n")
 }
+
+func TestInstallLinkerConfigFile(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+android_filesystem {
+    name: "myfilesystem",
+    deps: ["libfoo_has_no_stubs", "libfoo_has_stubs"],
+    linker_config_srcs: ["linker.config.json"]
+}
+cc_library {
+    name: "libfoo_has_no_stubs",
+}
+cc_library {
+    name: "libfoo_has_stubs",
+    stubs: {symbol_file: "libfoo.map.txt"},
+}
+	`)
+
+	linkerConfigCmd := result.ModuleForTests("myfilesystem", "android_common").Rule("build_filesystem_image").RuleParams.Command
+	android.AssertStringDoesContain(t, "", linkerConfigCmd, "conv_linker_config proto --force -s linker.config.json")
+	android.AssertStringDoesContain(t, "", linkerConfigCmd, "--key provideLibs --value libfoo_has_stubs.so")
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 7dbf986..6200df4 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -94,7 +94,7 @@
 	})
 
 	builder := android.NewRuleBuilder(pctx, ctx)
-	linkerconfig.BuildLinkerConfig(ctx, builder, input, provideModules, requireModules, output)
+	linkerconfig.BuildLinkerConfig(ctx, builder, android.Paths{input}, provideModules, requireModules, output)
 	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
 	return output
 }