Merge "Support generating prebuilt_* modules from 1:n mapping of PRODUCT_COPY_FILES" into main
diff --git a/android/variable.go b/android/variable.go
index 89d90c2..2d43c6d 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -635,7 +635,7 @@
 	BoardInfoFiles      []string `json:",omitempty"`
 	BootLoaderBoardName string   `json:",omitempty"`
 
-	ProductCopyFiles map[string]string `json:",omitempty"`
+	ProductCopyFiles []string `json:",omitempty"`
 
 	BuildingSystemDlkmImage   bool     `json:",omitempty"`
 	SystemKernelModules       []string `json:",omitempty"`
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index b6ba6c4..e9fd513 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"slices"
+	"strings"
 	"sync"
 
 	"android/soong/android"
@@ -327,12 +328,21 @@
 
 var HighPriorityDeps = []string{}
 
+func isHighPriorityDep(depName string) bool {
+	for _, highPriorityDeps := range HighPriorityDeps {
+		if strings.HasPrefix(depName, highPriorityDeps) {
+			return true
+		}
+	}
+	return false
+}
+
 func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
 	depsStruct := packagingPropsStruct{}
 	for depName, depProps := range deps {
 		bitness := getBitness(depProps.Arch)
 		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
-		if android.InList(depName, HighPriorityDeps) {
+		if isHighPriorityDep(depName) {
 			depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
 		} else if android.InList("32", bitness) && android.InList("64", bitness) {
 			// If both 32 and 64 bit variants are enabled for this module
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
index 362ac31..cbcd4a1 100644
--- a/fsgen/prebuilt_etc_modules_gen.go
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -85,15 +85,25 @@
 	}
 }
 
-func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string]string {
+// Create a map of source files to the list of destination files from PRODUCT_COPY_FILES entries.
+// Note that the value of the map is a list of string, given that a single source file can be
+// copied to multiple files.
+// This function also checks the existence of the source files, and validates that there is no
+// multiple source files copying to the same dest file.
+func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string][]string {
 	seen := make(map[string]bool)
-	filtered := make(map[string]string)
+	filtered := make(map[string][]string)
 
-	for src, dest := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
+	for _, copyFilePair := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
+		srcDestList := strings.Split(copyFilePair, ":")
+		if len(srcDestList) < 2 {
+			ctx.ModuleErrorf("PRODUCT_COPY_FILES must follow the format \"src:dest\", got: %s", copyFilePair)
+		}
+		src, dest := srcDestList[0], srcDestList[1]
 		if _, ok := seen[dest]; !ok {
 			if optionalPath := android.ExistentPathForSource(ctx, src); optionalPath.Valid() {
 				seen[dest] = true
-				filtered[src] = dest
+				filtered[src] = append(filtered[src], dest)
 			}
 		}
 	}
@@ -121,12 +131,14 @@
 
 	groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
 	for _, src := range android.SortedKeys(productCopyFileMap) {
-		dest := productCopyFileMap[src]
+		destFiles := productCopyFileMap[src]
 		srcFileDir := filepath.Dir(src)
 		if _, ok := groupedSources[srcFileDir]; !ok {
 			groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
 		}
-		appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
+		for _, dest := range destFiles {
+			appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
+		}
 	}
 
 	return groupedSources
@@ -195,12 +207,9 @@
 	}
 )
 
-func createPrebuiltEtcModule(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) string {
-	moduleProps := &prebuiltModuleProperties{}
-	propsList := []interface{}{moduleProps}
-
+func generatedPrebuiltEtcModuleName(partition, srcDir, destDir string, count int) string {
 	// generated module name follows the pattern:
-	// <install partition>-<src file path>-<relative install path from partition root>-<install file extension>
+	// <install partition>-<src file path>-<relative install path from partition root>-<number>
 	// Note that all path separators are replaced with "_" in the name
 	moduleName := partition
 	if !android.InList(srcDir, []string{"", "."}) {
@@ -209,33 +218,27 @@
 	if !android.InList(destDir, []string{"", "."}) {
 		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
 	}
-	if len(destFiles) > 0 {
-		if ext := filepath.Ext(destFiles[0].srcBaseFile); ext != "" {
-			moduleName += fmt.Sprintf("-%s", strings.TrimPrefix(ext, "."))
-		}
-	}
-	moduleProps.Name = proptools.StringPtr(moduleName)
+	moduleName += fmt.Sprintf("-%d", count)
 
-	allCopyFileNamesUnchanged := true
-	var srcBaseFiles, installBaseFiles []string
+	return moduleName
+}
+
+func groupDestFilesBySrc(destFiles []srcBaseFileInstallBaseFileTuple) (ret map[string][]srcBaseFileInstallBaseFileTuple, maxLen int) {
+	ret = map[string][]srcBaseFileInstallBaseFileTuple{}
+	maxLen = 0
 	for _, tuple := range destFiles {
-		if tuple.srcBaseFile != tuple.installBaseFile {
-			allCopyFileNamesUnchanged = false
+		if _, ok := ret[tuple.srcBaseFile]; !ok {
+			ret[tuple.srcBaseFile] = []srcBaseFileInstallBaseFileTuple{}
 		}
-		srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
-		installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
+		ret[tuple.srcBaseFile] = append(ret[tuple.srcBaseFile], tuple)
+		maxLen = max(maxLen, len(ret[tuple.srcBaseFile]))
 	}
+	return ret, maxLen
+}
 
-	// Find out the most appropriate module type to generate
-	var etcInstallPathKey string
-	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
-		// Do not break when found but iterate until the end to find a module with more
-		// specific install path
-		if strings.HasPrefix(destDir, etcInstallPath) {
-			etcInstallPathKey = etcInstallPath
-		}
-	}
-	destDir, _ = filepath.Rel(etcInstallPathKey, destDir)
+func prebuiltEtcModuleProps(moduleName, partition string) prebuiltModuleProperties {
+	moduleProps := prebuiltModuleProperties{}
+	moduleProps.Name = proptools.StringPtr(moduleName)
 
 	// Set partition specific properties
 	switch partition {
@@ -247,39 +250,81 @@
 		moduleProps.Soc_specific = proptools.BoolPtr(true)
 	}
 
-	// Set appropriate srcs, dsts, and releative_install_path based on
-	// the source and install file names
-	if allCopyFileNamesUnchanged {
-		moduleProps.Srcs = srcBaseFiles
-
-		// Specify relative_install_path if it is not installed in the root directory of the
-		// partition
-		if !android.InList(destDir, []string{"", "."}) {
-			propsList = append(propsList, &prebuiltSubdirProperties{
-				Relative_install_path: proptools.StringPtr(destDir),
-			})
-		}
-	} else {
-		moduleProps.Srcs = srcBaseFiles
-		dsts := []string{}
-		for _, installBaseFile := range installBaseFiles {
-			dsts = append(dsts, filepath.Join(destDir, installBaseFile))
-		}
-		moduleProps.Dsts = dsts
-	}
-
 	moduleProps.No_full_install = proptools.BoolPtr(true)
 	moduleProps.NamespaceExportedToMake = true
 	moduleProps.Visibility = []string{"//visibility:public"}
 
-	ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
+	return moduleProps
+}
 
-	return moduleName
+func createPrebuiltEtcModulesInDirectory(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) (moduleNames []string) {
+	groupedDestFiles, maxLen := groupDestFilesBySrc(destFiles)
+
+	// Find out the most appropriate module type to generate
+	var etcInstallPathKey string
+	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
+		// Do not break when found but iterate until the end to find a module with more
+		// specific install path
+		if strings.HasPrefix(destDir, etcInstallPath) {
+			etcInstallPathKey = etcInstallPath
+		}
+	}
+	relDestDirFromInstallDirBase, _ := filepath.Rel(etcInstallPathKey, destDir)
+
+	for fileIndex := range maxLen {
+		srcTuple := []srcBaseFileInstallBaseFileTuple{}
+		for _, groupedDestFile := range groupedDestFiles {
+			if len(groupedDestFile) > fileIndex {
+				srcTuple = append(srcTuple, groupedDestFile[fileIndex])
+			}
+		}
+
+		moduleName := generatedPrebuiltEtcModuleName(partition, srcDir, destDir, fileIndex)
+		moduleProps := prebuiltEtcModuleProps(moduleName, partition)
+		modulePropsPtr := &moduleProps
+		propsList := []interface{}{modulePropsPtr}
+
+		allCopyFileNamesUnchanged := true
+		var srcBaseFiles, installBaseFiles []string
+		for _, tuple := range srcTuple {
+			if tuple.srcBaseFile != tuple.installBaseFile {
+				allCopyFileNamesUnchanged = false
+			}
+			srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
+			installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
+		}
+
+		// Set appropriate srcs, dsts, and releative_install_path based on
+		// the source and install file names
+		if allCopyFileNamesUnchanged {
+			modulePropsPtr.Srcs = srcBaseFiles
+
+			// Specify relative_install_path if it is not installed in the root directory of the
+			// partition
+			if !android.InList(relDestDirFromInstallDirBase, []string{"", "."}) {
+				propsList = append(propsList, &prebuiltSubdirProperties{
+					Relative_install_path: proptools.StringPtr(relDestDirFromInstallDirBase),
+				})
+			}
+		} else {
+			modulePropsPtr.Srcs = srcBaseFiles
+			dsts := []string{}
+			for _, installBaseFile := range installBaseFiles {
+				dsts = append(dsts, filepath.Join(relDestDirFromInstallDirBase, installBaseFile))
+			}
+			modulePropsPtr.Dsts = dsts
+		}
+
+		ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
+		moduleNames = append(moduleNames, moduleName)
+	}
+
+	return moduleNames
 }
 
 func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
 	for _, destDir := range android.SortedKeys(destDirFilesMap) {
-		ret = append(ret, createPrebuiltEtcModule(ctx, partition, srcDir, destDir, destDirFilesMap[destDir]))
+		ret = append(ret, createPrebuiltEtcModulesInDirectory(ctx, partition, srcDir, destDir, destDirFilesMap[destDir])...)
 	}
 	return ret
 }