Support multiple sources for prebuilt_etc

Keep the Src attribute for compatibility. The new attribute (Srcs) is
mutually exclusive with Src.

Keep SourceFilePath and OutputFile for compatibility with other modules.
These can be removed in a follow up change.

Bug: 328313691
Test: presubmit
Test: m blueprint_tests
Test: prebuilts/build-tools/build-prebuilts.sh (on build-tools branch)
Change-Id: I5d5b2657715a7180a829c7ed0f501872d561b662
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 7642378..a42c576 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -71,10 +71,15 @@
 
 type prebuiltEtcProperties struct {
 	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
+	// Mutually exclusive with srcs.
 	Src *string `android:"path,arch_variant"`
 
+	// Source files of this prebuilt. Can reference a genrule type module with the ":module" syntax.
+	// Mutually exclusive with src. When used, filename_from_src is set to true.
+	Srcs []string `android:"path,arch_variant"`
+
 	// Optional name for the installed file. If unspecified, name of the module is used as the file
-	// name.
+	// name. Only available when using a single source (src).
 	Filename *string `android:"arch_variant"`
 
 	// When set to true, and filename property is not set, the name for the installed file
@@ -127,9 +132,9 @@
 	// Returns the sub install directory relative to BaseDir().
 	SubDir() string
 
-	// Returns an android.OutputPath to the intermeidate file, which is the renamed prebuilt source
+	// Returns an android.OutputPath to the intermediate file, which is the renamed prebuilt source
 	// file.
-	OutputFile() android.OutputPath
+	OutputFiles(tag string) (android.Paths, error)
 }
 
 type PrebuiltEtc struct {
@@ -142,8 +147,8 @@
 	properties       prebuiltEtcProperties
 	subdirProperties prebuiltSubdirProperties
 
-	sourceFilePath android.Path
-	outputFilePath android.OutputPath
+	sourceFilePaths android.Paths
+	outputFilePaths android.OutputPaths
 	// The base install location, e.g. "etc" for prebuilt_etc, "usr/share" for prebuilt_usr_share.
 	installDirBase               string
 	installDirBase64             string
@@ -246,6 +251,9 @@
 }
 
 func (p *PrebuiltEtc) SourceFilePath(ctx android.ModuleContext) android.Path {
+	if len(p.properties.Srcs) > 0 {
+		panic(fmt.Errorf("SourceFilePath not available on multi-source prebuilt %q", p.Name()))
+	}
 	return android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
 }
 
@@ -260,7 +268,10 @@
 }
 
 func (p *PrebuiltEtc) OutputFile() android.OutputPath {
-	return p.outputFilePath
+	if len(p.properties.Srcs) > 0 {
+		panic(fmt.Errorf("OutputFile not available on multi-source prebuilt %q", p.Name()))
+	}
+	return p.outputFilePaths[0]
 }
 
 var _ android.OutputFileProducer = (*PrebuiltEtc)(nil)
@@ -268,7 +279,7 @@
 func (p *PrebuiltEtc) OutputFiles(tag string) (android.Paths, error) {
 	switch tag {
 	case "":
-		return android.Paths{p.outputFilePath}, nil
+		return p.outputFilePaths.Paths(), nil
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 	}
@@ -301,50 +312,7 @@
 	return false
 }
 
-func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	filename := proptools.String(p.properties.Filename)
-	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
-	if p.properties.Src != nil {
-		p.sourceFilePath = android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
-
-		// Determine the output file basename.
-		// If Filename is set, use the name specified by the property.
-		// If Filename_from_src is set, use the source file name.
-		// Otherwise use the module name.
-		if filename != "" {
-			if filenameFromSrc {
-				ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
-				return
-			}
-		} else if filenameFromSrc {
-			filename = p.sourceFilePath.Base()
-		} else {
-			filename = ctx.ModuleName()
-		}
-	} else if ctx.Config().AllowMissingDependencies() {
-		// If no srcs was set and AllowMissingDependencies is enabled then
-		// mark the module as missing dependencies and set a fake source path
-		// and file name.
-		ctx.AddMissingDependencies([]string{"MISSING_PREBUILT_SRC_FILE"})
-		p.sourceFilePath = android.PathForModuleSrc(ctx)
-		if filename == "" {
-			filename = ctx.ModuleName()
-		}
-	} else {
-		ctx.PropertyErrorf("src", "missing prebuilt source file")
-		return
-	}
-
-	if strings.Contains(filename, "/") {
-		ctx.PropertyErrorf("filename", "filename cannot contain separator '/'")
-		return
-	}
-
-	// Check that `sub_dir` and `relative_install_path` are not set at the same time.
-	if p.subdirProperties.Sub_dir != nil && p.subdirProperties.Relative_install_path != nil {
-		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
-	}
-
+func (p *PrebuiltEtc) installBaseDir(ctx android.ModuleContext) string {
 	// If soc install dir was specified and SOC specific is set, set the installDirPath to the
 	// specified socInstallDirBase.
 	installBaseDir := p.installDirBase
@@ -357,47 +325,138 @@
 	if p.installAvoidMultilibConflict && !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
 		installBaseDir = filepath.Join(installBaseDir, ctx.Arch().ArchType.String())
 	}
+	return installBaseDir
+}
 
-	p.installDirPath = android.PathForModuleInstall(ctx, installBaseDir, p.SubDir())
+func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var installs []installProperties
 
-	// Call InstallFile even when uninstallable to make the module included in the package
-	ip := installProperties{
-		installable:    p.Installable(),
-		filename:       filename,
-		sourceFilePath: p.sourceFilePath,
-		symlinks:       p.properties.Symlinks,
+	if p.properties.Src != nil && len(p.properties.Srcs) > 0 {
+		ctx.PropertyErrorf("src", "src is set. Cannot set srcs")
 	}
-	p.addInstallRules(ctx, ip)
+
+	// Check that `sub_dir` and `relative_install_path` are not set at the same time.
+	if p.subdirProperties.Sub_dir != nil && p.subdirProperties.Relative_install_path != nil {
+		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
+	}
+	p.installDirPath = android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
+
+	filename := proptools.String(p.properties.Filename)
+	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
+	if p.properties.Src != nil {
+		p.sourceFilePaths = android.PathsForModuleSrc(ctx, []string{proptools.String(p.properties.Src)})
+		// If the source was not found, set a fake source path to
+		// support AllowMissingDependencies executions.
+		if len(p.sourceFilePaths) == 0 {
+			p.sourceFilePaths = android.Paths{android.PathForModuleSrc(ctx)}
+		}
+
+		// Determine the output file basename.
+		// If Filename is set, use the name specified by the property.
+		// If Filename_from_src is set, use the source file name.
+		// Otherwise use the module name.
+		if filename != "" {
+			if filenameFromSrc {
+				ctx.PropertyErrorf("filename_from_src", "filename is set. filename_from_src can't be true")
+				return
+			}
+		} else if filenameFromSrc {
+			filename = p.sourceFilePaths[0].Base()
+		} else {
+			filename = ctx.ModuleName()
+		}
+		if strings.Contains(filename, "/") {
+			ctx.PropertyErrorf("filename", "filename cannot contain separator '/'")
+			return
+		}
+		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+
+		ip := installProperties{
+			filename:       filename,
+			sourceFilePath: p.sourceFilePaths[0],
+			outputFilePath: p.outputFilePaths[0],
+			installDirPath: p.installDirPath,
+			symlinks:       p.properties.Symlinks,
+		}
+		installs = append(installs, ip)
+	} else if len(p.properties.Srcs) > 0 {
+		if filename != "" {
+			ctx.PropertyErrorf("filename", "filename cannot be set when using srcs")
+		}
+		if len(p.properties.Symlinks) > 0 {
+			ctx.PropertyErrorf("symlinks", "symlinks cannot be set when using srcs")
+		}
+		if p.properties.Filename_from_src != nil {
+			ctx.PropertyErrorf("filename_from_src", "filename_from_src is implicitly set to true when using srcs")
+		}
+		p.sourceFilePaths = android.PathsForModuleSrc(ctx, p.properties.Srcs)
+		for _, src := range p.sourceFilePaths {
+			filename := src.Base()
+			output := android.PathForModuleOut(ctx, filename).OutputPath
+			ip := installProperties{
+				filename:       filename,
+				sourceFilePath: src,
+				outputFilePath: output,
+				installDirPath: p.installDirPath,
+			}
+			p.outputFilePaths = append(p.outputFilePaths, output)
+			installs = append(installs, ip)
+		}
+	} else if ctx.Config().AllowMissingDependencies() {
+		// If no srcs was set and AllowMissingDependencies is enabled then
+		// mark the module as missing dependencies and set a fake source path
+		// and file name.
+		ctx.AddMissingDependencies([]string{"MISSING_PREBUILT_SRC_FILE"})
+		p.sourceFilePaths = android.Paths{android.PathForModuleSrc(ctx)}
+		if filename == "" {
+			filename = ctx.ModuleName()
+		}
+		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+		ip := installProperties{
+			filename:       filename,
+			sourceFilePath: p.sourceFilePaths[0],
+			outputFilePath: p.outputFilePaths[0],
+			installDirPath: p.installDirPath,
+		}
+		installs = append(installs, ip)
+	} else {
+		ctx.PropertyErrorf("src", "missing prebuilt source file")
+		return
+	}
+
+	// Call InstallFile even when uninstallable to make the module included in the package.
+	if !p.Installable() {
+		p.SkipInstall()
+	}
+	for _, ip := range installs {
+		ip.addInstallRules(ctx)
+	}
 	android.CollectDependencyAconfigFiles(ctx, &p.mergedAconfigFiles)
 }
 
 type installProperties struct {
-	installable    bool
 	filename       string
 	sourceFilePath android.Path
+	outputFilePath android.OutputPath
+	installDirPath android.InstallPath
 	symlinks       []string
 }
 
 // utility function to add install rules to the build graph.
 // Reduces code duplication between Soong and Mixed build analysis
-func (p *PrebuiltEtc) addInstallRules(ctx android.ModuleContext, ip installProperties) {
-	if !ip.installable {
-		p.SkipInstall()
-	}
-
+func (ip *installProperties) addInstallRules(ctx android.ModuleContext) {
 	// Copy the file from src to a location in out/ with the correct `filename`
 	// This ensures that outputFilePath has the correct name for others to
 	// use, as the source file may have a different name.
-	p.outputFilePath = android.PathForModuleOut(ctx, ip.filename).OutputPath
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
-		Output: p.outputFilePath,
+		Output: ip.outputFilePath,
 		Input:  ip.sourceFilePath,
 	})
 
-	installPath := ctx.InstallFile(p.installDirPath, ip.filename, p.outputFilePath)
+	installPath := ctx.InstallFile(ip.installDirPath, ip.filename, ip.outputFilePath)
 	for _, sl := range ip.symlinks {
-		ctx.InstallSymlink(p.installDirPath, sl, installPath)
+		ctx.InstallSymlink(ip.installDirPath, sl, installPath)
 	}
 }
 
@@ -421,15 +480,15 @@
 		class = "ETC"
 	}
 
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
+	return []android.AndroidMkEntries{{
 		Class:      class,
 		SubName:    nameSuffix,
-		OutputFile: android.OptionalPathForPath(p.outputFilePath),
+		OutputFile: android.OptionalPathForPath(p.outputFilePaths[0]),
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
 				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
-				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePath.Base())
+				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePaths[0].Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
 				}
@@ -700,7 +759,11 @@
 		targetArch := "arch-" + m.Target().Arch.ArchType.String()
 
 		snapshotLibOut := filepath.Join(snapshotArchDir, targetArch, "etc", m.BaseModuleName())
-		snapshotOutputs = append(snapshotOutputs, copyFile(ctx, m.OutputFile(), snapshotLibOut, s.Fake))
+		outputs, _ := m.OutputFiles("")
+		for _, output := range outputs {
+			cp := copyFile(ctx, output, snapshotLibOut, s.Fake)
+			snapshotOutputs = append(snapshotOutputs, cp)
+		}
 
 		prop := snapshot.SnapshotJsonFlags{}
 		propOut := snapshotLibOut + ".json"
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index df11709..1d9aa8e 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -96,7 +96,7 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	android.AssertStringEquals(t, "output file path", "foo.installed.conf", p.outputFilePath.Base())
+	android.AssertStringEquals(t, "output file path", "foo.installed.conf", p.outputFilePaths[0].Base())
 }
 
 func TestPrebuiltEtcGlob(t *testing.T) {
@@ -113,10 +113,24 @@
 	`)
 
 	p := result.Module("my_foo", "android_arm64_armv8-a").(*PrebuiltEtc)
-	android.AssertStringEquals(t, "my_foo output file path", "my_foo", p.outputFilePath.Base())
+	android.AssertStringEquals(t, "my_foo output file path", "my_foo", p.outputFilePaths[0].Base())
 
 	p = result.Module("my_bar", "android_arm64_armv8-a").(*PrebuiltEtc)
-	android.AssertStringEquals(t, "my_bar output file path", "bar.conf", p.outputFilePath.Base())
+	android.AssertStringEquals(t, "my_bar output file path", "bar.conf", p.outputFilePaths[0].Base())
+}
+
+func TestPrebuiltEtcMultipleSrcs(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["*.conf"],
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "bar.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "baz.conf", p.outputFilePaths[1].Base())
+	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[2].Base())
 }
 
 func TestPrebuiltEtcAndroidMk(t *testing.T) {