Merge "Add 'dsts' property to prebuilt_etc" into main
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index fc6d1f7..fbe24d1 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -78,9 +78,16 @@
 	Src proptools.Configurable[string] `android:"path,arch_variant,replace_instead_of_append"`
 
 	// 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.
+	// Mutually exclusive with src. When used, filename_from_src is set to true unless dsts is also
+	// set. May use globs in filenames.
 	Srcs proptools.Configurable[[]string] `android:"path,arch_variant"`
 
+	// Destination files of this prebuilt. Requires srcs to be used and causes srcs not to implicitly
+	// set filename_from_src. This can be used to install each source file to a different directory
+	// and/or change filenames when files are installed. Must be exactly one entry per source file,
+	// which means care must be taken if srcs has globs.
+	Dsts proptools.Configurable[[]string] `android:"path,arch_variant"`
+
 	// Optional name for the installed file. If unspecified, name of the module is used as the file
 	// name. Only available when using a single source (src).
 	Filename *string `android:"arch_variant"`
@@ -166,7 +173,7 @@
 	// The base install location when soc_specific property is set to true, e.g. "firmware" for
 	// prebuilt_firmware.
 	socInstallDirBase      string
-	installDirPath         android.InstallPath
+	installDirPaths        []android.InstallPath
 	additionalDependencies *android.Paths
 
 	usedSrcsProperty bool
@@ -279,7 +286,10 @@
 }
 
 func (p *PrebuiltEtc) InstallDirPath() android.InstallPath {
-	return p.installDirPath
+	if len(p.installDirPaths) != 1 {
+		panic(fmt.Errorf("InstallDirPath not available on multi-source prebuilt %q", p.Name()))
+	}
+	return p.installDirPaths[0]
 }
 
 // This allows other derivative modules (e.g. prebuilt_etc_xml) to perform
@@ -338,12 +348,16 @@
 	if srcProperty.IsPresent() && len(srcsProperty) > 0 {
 		ctx.PropertyErrorf("src", "src is set. Cannot set srcs")
 	}
+	dstsProperty := p.properties.Dsts.GetOrDefault(ctx, nil)
+	if len(dstsProperty) > 0 && len(srcsProperty) == 0 {
+		ctx.PropertyErrorf("dsts", "dsts is set. Must use srcs")
+	}
 
 	// 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())
+	baseInstallDirPath := android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
 
 	filename := proptools.String(p.properties.Filename)
 	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
@@ -379,10 +393,11 @@
 			filename:       filename,
 			sourceFilePath: p.sourceFilePaths[0],
 			outputFilePath: p.outputFilePaths[0],
-			installDirPath: p.installDirPath,
+			installDirPath: baseInstallDirPath,
 			symlinks:       p.properties.Symlinks,
 		}
 		installs = append(installs, ip)
+		p.installDirPaths = append(p.installDirPaths, baseInstallDirPath)
 	} else if len(srcsProperty) > 0 {
 		p.usedSrcsProperty = true
 		if filename != "" {
@@ -392,20 +407,39 @@
 			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")
+			if len(dstsProperty) > 0 {
+				ctx.PropertyErrorf("filename_from_src", "dsts is set. Cannot set filename_from_src")
+			} else {
+				ctx.PropertyErrorf("filename_from_src", "filename_from_src is implicitly set to true when using srcs")
+			}
 		}
 		p.sourceFilePaths = android.PathsForModuleSrc(ctx, srcsProperty)
-		for _, src := range p.sourceFilePaths {
-			filename := src.Base()
+		if len(dstsProperty) > 0 && len(p.sourceFilePaths) != len(dstsProperty) {
+			ctx.PropertyErrorf("dsts", "Must have one entry in dsts per source file")
+		}
+		for i, src := range p.sourceFilePaths {
+			var filename string
+			var installDirPath android.InstallPath
+
+			if len(dstsProperty) > 0 {
+				var dstdir string
+
+				dstdir, filename = filepath.Split(dstsProperty[i])
+				installDirPath = baseInstallDirPath.Join(ctx, dstdir)
+			} else {
+				filename = src.Base()
+				installDirPath = baseInstallDirPath
+			}
 			output := android.PathForModuleOut(ctx, filename).OutputPath
 			ip := installProperties{
 				filename:       filename,
 				sourceFilePath: src,
 				outputFilePath: output,
-				installDirPath: p.installDirPath,
+				installDirPath: installDirPath,
 			}
 			p.outputFilePaths = append(p.outputFilePaths, output)
 			installs = append(installs, ip)
+			p.installDirPaths = append(p.installDirPaths, installDirPath)
 		}
 	} else if ctx.Config().AllowMissingDependencies() {
 		// If no srcs was set and AllowMissingDependencies is enabled then
@@ -421,9 +455,10 @@
 			filename:       filename,
 			sourceFilePath: p.sourceFilePaths[0],
 			outputFilePath: p.outputFilePaths[0],
-			installDirPath: p.installDirPath,
+			installDirPath: baseInstallDirPath,
 		}
 		installs = append(installs, ip)
+		p.installDirPaths = append(p.installDirPaths, baseInstallDirPath)
 	} else {
 		ctx.PropertyErrorf("src", "missing prebuilt source file")
 		return
@@ -493,7 +528,7 @@
 		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_MODULE_PATH", p.installDirPaths[0].String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePaths[0].Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index e739afe..75c6d12 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -119,6 +119,113 @@
 	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[2].Base())
 }
 
+func TestPrebuiltEtcDsts(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["foo.conf", "bar.conf"],
+			dsts: ["foodir/foo.conf", "bardir/extradir/different.name"],
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "different.name", p.outputFilePaths[1].Base())
+
+	expectedPaths := [...]string{
+		"out/soong/target/product/test_device/system/etc/foodir",
+		"out/soong/target/product/test_device/system/etc/bardir/extradir",
+	}
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[0], p.installDirPaths[0])
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[1], p.installDirPaths[1])
+}
+
+func TestPrebuiltEtcDstsPlusRelativeInstallPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["foo.conf", "bar.conf"],
+			dsts: ["foodir/foo.conf", "bardir/extradir/different.name"],
+			relative_install_path: "somewhere",
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "different.name", p.outputFilePaths[1].Base())
+
+	expectedPaths := [...]string{
+		"out/soong/target/product/test_device/system/etc/somewhere/foodir",
+		"out/soong/target/product/test_device/system/etc/somewhere/bardir/extradir",
+	}
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[0], p.installDirPaths[0])
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[1], p.installDirPaths[1])
+}
+
+func TestPrebuiltEtcDstsSrcGlob(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["*.conf"],
+			dsts: ["a.conf", "b.conf", "c.conf"],
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "a.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "b.conf", p.outputFilePaths[1].Base())
+	android.AssertStringEquals(t, "output file path", "c.conf", p.outputFilePaths[2].Base())
+}
+
+func TestPrebuiltEtcDstsSrcGlobDstsTooShort(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("Must have one entry in dsts per source file")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo",
+				srcs: ["*.conf"],
+				dsts: ["a.conf", "b.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcDstsSrcGlobDstsTooLong(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("Must have one entry in dsts per source file")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo",
+				srcs: ["*.conf"],
+				dsts: ["a.conf", "b.conf", "c.conf", "d.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcCannotDstsWithSrc(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("dsts is set. Must use srcs")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo.conf",
+				src: "foo.conf",
+				dsts: ["a.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcCannotDstsWithFilenameFromSrc(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("dsts is set. Cannot set filename_from_src")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo.conf",
+				srcs: ["foo.conf"],
+				dsts: ["a.conf"],
+				filename_from_src: true,
+			}
+		`)
+}
+
 func TestPrebuiltEtcAndroidMk(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
 		prebuilt_etc {
@@ -165,7 +272,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/etc/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
@@ -231,7 +338,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltRootInstallDirPathValidate(t *testing.T) {
@@ -256,7 +363,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/root/avb"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltAvdInstallDirPathValidate(t *testing.T) {
@@ -280,7 +387,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/usr/share/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltUserShareHostInstallDirPath(t *testing.T) {
@@ -295,7 +402,7 @@
 	buildOS := result.Config.BuildOS.String()
 	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
 	expected := filepath.Join("out/soong/host", result.Config.PrebuiltOS(), "usr", "share", "bar")
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserHyphenDataInstallDirPath(t *testing.T) {
@@ -309,7 +416,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/usr/hyphen-data/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserKeyLayoutInstallDirPath(t *testing.T) {
@@ -323,7 +430,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/usr/keylayout/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserKeyCharsInstallDirPath(t *testing.T) {
@@ -337,7 +444,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/usr/keychars/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserIdcInstallDirPath(t *testing.T) {
@@ -351,7 +458,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/usr/idc/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltFontInstallDirPath(t *testing.T) {
@@ -364,7 +471,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/fonts"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltOverlayInstallDirPath(t *testing.T) {
@@ -377,7 +484,7 @@
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
 	expected := "out/soong/target/product/test_device/system/overlay"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltFirmwareDirPath(t *testing.T) {
@@ -409,7 +516,7 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }
@@ -443,7 +550,7 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }
@@ -477,7 +584,7 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }