xref: /aosp_15_r20/build/soong/fsgen/prebuilt_etc_modules_gen.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package fsgen
16
17import (
18	"android/soong/android"
19	"android/soong/etc"
20	"fmt"
21	"path/filepath"
22	"strings"
23
24	"github.com/google/blueprint/proptools"
25)
26
27type srcBaseFileInstallBaseFileTuple struct {
28	srcBaseFile     string
29	installBaseFile string
30}
31
32// prebuilt src files grouped by the install partitions.
33// Each groups are a mapping of the relative install path to the name of the files
34type prebuiltSrcGroupByInstallPartition struct {
35	system     map[string][]srcBaseFileInstallBaseFileTuple
36	system_ext map[string][]srcBaseFileInstallBaseFileTuple
37	product    map[string][]srcBaseFileInstallBaseFileTuple
38	vendor     map[string][]srcBaseFileInstallBaseFileTuple
39	recovery   map[string][]srcBaseFileInstallBaseFileTuple
40}
41
42func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
43	return &prebuiltSrcGroupByInstallPartition{
44		system:     map[string][]srcBaseFileInstallBaseFileTuple{},
45		system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
46		product:    map[string][]srcBaseFileInstallBaseFileTuple{},
47		vendor:     map[string][]srcBaseFileInstallBaseFileTuple{},
48		recovery:   map[string][]srcBaseFileInstallBaseFileTuple{},
49	}
50}
51
52func isSubdirectory(parent, child string) bool {
53	rel, err := filepath.Rel(parent, child)
54	if err != nil {
55		return false
56	}
57	return !strings.HasPrefix(rel, "..")
58}
59
60func appendIfCorrectInstallPartition(partitionToInstallPathList []partitionToInstallPath, destPath, srcPath string, srcGroup *prebuiltSrcGroupByInstallPartition) {
61	for _, part := range partitionToInstallPathList {
62		partition := part.name
63		installPath := part.installPath
64
65		if isSubdirectory(installPath, destPath) {
66			relativeInstallPath, _ := filepath.Rel(installPath, destPath)
67			relativeInstallDir := filepath.Dir(relativeInstallPath)
68			var srcMap map[string][]srcBaseFileInstallBaseFileTuple
69			switch partition {
70			case "system":
71				srcMap = srcGroup.system
72			case "system_ext":
73				srcMap = srcGroup.system_ext
74			case "product":
75				srcMap = srcGroup.product
76			case "vendor":
77				srcMap = srcGroup.vendor
78			case "recovery":
79				srcMap = srcGroup.recovery
80			}
81			if srcMap != nil {
82				srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
83					srcBaseFile:     filepath.Base(srcPath),
84					installBaseFile: filepath.Base(destPath),
85				})
86			}
87			return
88		}
89	}
90}
91
92// Create a map of source files to the list of destination files from PRODUCT_COPY_FILES entries.
93// Note that the value of the map is a list of string, given that a single source file can be
94// copied to multiple files.
95// This function also checks the existence of the source files, and validates that there is no
96// multiple source files copying to the same dest file.
97func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string][]string {
98	seen := make(map[string]bool)
99	filtered := make(map[string][]string)
100
101	for _, copyFilePair := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
102		srcDestList := strings.Split(copyFilePair, ":")
103		if len(srcDestList) < 2 {
104			ctx.ModuleErrorf("PRODUCT_COPY_FILES must follow the format \"src:dest\", got: %s", copyFilePair)
105		}
106		src, dest := srcDestList[0], srcDestList[1]
107
108		// Some downstream branches use absolute path as entries in PRODUCT_COPY_FILES.
109		// Convert them to relative path from top and check if they do not escape the tree root.
110		relSrc := android.ToRelativeSourcePath(ctx, src)
111
112		if _, ok := seen[dest]; !ok {
113			if optionalPath := android.ExistentPathForSource(ctx, relSrc); optionalPath.Valid() {
114				seen[dest] = true
115				filtered[relSrc] = append(filtered[relSrc], dest)
116			}
117		}
118	}
119
120	return filtered
121}
122
123type partitionToInstallPath struct {
124	name        string
125	installPath string
126}
127
128func processProductCopyFiles(ctx android.LoadHookContext) map[string]*prebuiltSrcGroupByInstallPartition {
129	// Filter out duplicate dest entries and non existing src entries
130	productCopyFileMap := uniqueExistingProductCopyFileMap(ctx)
131
132	// System is intentionally added at the last to consider the scenarios where
133	// non-system partitions are installed as part of the system partition
134	partitionToInstallPathList := []partitionToInstallPath{
135		{name: "recovery", installPath: "recovery/root"},
136		{name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
137		{name: "product", installPath: ctx.DeviceConfig().ProductPath()},
138		{name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
139		{name: "system", installPath: "system"},
140	}
141
142	groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
143	for _, src := range android.SortedKeys(productCopyFileMap) {
144		destFiles := productCopyFileMap[src]
145		srcFileDir := filepath.Dir(src)
146		if _, ok := groupedSources[srcFileDir]; !ok {
147			groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
148		}
149		for _, dest := range destFiles {
150			appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
151		}
152	}
153
154	return groupedSources
155}
156
157type prebuiltModuleProperties struct {
158	Name *string
159
160	Soc_specific        *bool
161	Product_specific    *bool
162	System_ext_specific *bool
163	Recovery            *bool
164	Ramdisk             *bool
165
166	Srcs []string
167	Dsts []string
168
169	No_full_install *bool
170
171	NamespaceExportedToMake bool
172
173	Visibility []string
174}
175
176// Split relative_install_path to a separate struct, because it is not supported for every
177// modules listed in [etcInstallPathToFactoryMap]
178type prebuiltSubdirProperties struct {
179	// If the base file name of the src and dst all match, dsts property does not need to be
180	// set, and only relative_install_path can be set.
181	Relative_install_path *string
182}
183
184// Split install_in_root to a separate struct as it is part of rootProperties instead of
185// properties
186type prebuiltInstallInRootProperties struct {
187	Install_in_root *bool
188}
189
190var (
191	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
192		"":                    etc.PrebuiltRootFactory,
193		"avb":                 etc.PrebuiltAvbFactory,
194		"bin":                 etc.PrebuiltBinaryFactory,
195		"bt_firmware":         etc.PrebuiltBtFirmwareFactory,
196		"cacerts":             etc.PrebuiltEtcCaCertsFactory,
197		"dsp":                 etc.PrebuiltDSPFactory,
198		"etc":                 etc.PrebuiltEtcFactory,
199		"etc/dsp":             etc.PrebuiltDSPFactory,
200		"etc/firmware":        etc.PrebuiltFirmwareFactory,
201		"firmware":            etc.PrebuiltFirmwareFactory,
202		"first_stage_ramdisk": etc.PrebuiltFirstStageRamdiskFactory,
203		"fonts":               etc.PrebuiltFontFactory,
204		"framework":           etc.PrebuiltFrameworkFactory,
205		"lib":                 etc.PrebuiltRenderScriptBitcodeFactory,
206		"lib64":               etc.PrebuiltRenderScriptBitcodeFactory,
207		"lib/rfsa":            etc.PrebuiltRFSAFactory,
208		"media":               etc.PrebuiltMediaFactory,
209		"odm":                 etc.PrebuiltOdmFactory,
210		"optee":               etc.PrebuiltOpteeFactory,
211		"overlay":             etc.PrebuiltOverlayFactory,
212		"priv-app":            etc.PrebuiltPrivAppFactory,
213		"sbin":                etc.PrebuiltSbinFactory,
214		"system":              etc.PrebuiltSystemFactory,
215		"res":                 etc.PrebuiltResFactory,
216		"rfs":                 etc.PrebuiltRfsFactory,
217		"tts":                 etc.PrebuiltVoicepackFactory,
218		"tvconfig":            etc.PrebuiltTvConfigFactory,
219		"tvservice":           etc.PrebuiltTvServiceFactory,
220		"usr/share":           etc.PrebuiltUserShareFactory,
221		"usr/hyphen-data":     etc.PrebuiltUserHyphenDataFactory,
222		"usr/keylayout":       etc.PrebuiltUserKeyLayoutFactory,
223		"usr/keychars":        etc.PrebuiltUserKeyCharsFactory,
224		"usr/srec":            etc.PrebuiltUserSrecFactory,
225		"usr/idc":             etc.PrebuiltUserIdcFactory,
226		"vendor":              etc.PrebuiltVendorFactory,
227		"vendor_dlkm":         etc.PrebuiltVendorDlkmFactory,
228		"wallpaper":           etc.PrebuiltWallpaperFactory,
229		"wlc_upt":             etc.PrebuiltWlcUptFactory,
230	}
231)
232
233func generatedPrebuiltEtcModuleName(partition, srcDir, destDir string, count int) string {
234	// generated module name follows the pattern:
235	// <install partition>-<src file path>-<relative install path from partition root>-<number>
236	// Note that all path separators are replaced with "_" in the name
237	moduleName := partition
238	if !android.InList(srcDir, []string{"", "."}) {
239		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
240	}
241	if !android.InList(destDir, []string{"", "."}) {
242		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
243	}
244	moduleName += fmt.Sprintf("-%d", count)
245
246	return moduleName
247}
248
249func groupDestFilesBySrc(destFiles []srcBaseFileInstallBaseFileTuple) (ret map[string][]srcBaseFileInstallBaseFileTuple, maxLen int) {
250	ret = map[string][]srcBaseFileInstallBaseFileTuple{}
251	maxLen = 0
252	for _, tuple := range destFiles {
253		if _, ok := ret[tuple.srcBaseFile]; !ok {
254			ret[tuple.srcBaseFile] = []srcBaseFileInstallBaseFileTuple{}
255		}
256		ret[tuple.srcBaseFile] = append(ret[tuple.srcBaseFile], tuple)
257		maxLen = max(maxLen, len(ret[tuple.srcBaseFile]))
258	}
259	return ret, maxLen
260}
261
262func prebuiltEtcModuleProps(ctx android.LoadHookContext, moduleName, partition, destDir string) prebuiltModuleProperties {
263	moduleProps := prebuiltModuleProperties{}
264	moduleProps.Name = proptools.StringPtr(moduleName)
265
266	// Set partition specific properties
267	switch partition {
268	case "system_ext":
269		moduleProps.System_ext_specific = proptools.BoolPtr(true)
270	case "product":
271		moduleProps.Product_specific = proptools.BoolPtr(true)
272	case "vendor":
273		moduleProps.Soc_specific = proptools.BoolPtr(true)
274	case "recovery":
275		// To match the logic in modulePartition() in android/paths.go
276		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() && strings.HasPrefix(destDir, "first_stage_ramdisk") {
277			moduleProps.Ramdisk = proptools.BoolPtr(true)
278		} else {
279			moduleProps.Recovery = proptools.BoolPtr(true)
280		}
281	}
282
283	moduleProps.No_full_install = proptools.BoolPtr(true)
284	moduleProps.NamespaceExportedToMake = true
285	moduleProps.Visibility = []string{"//visibility:public"}
286
287	return moduleProps
288}
289
290func createPrebuiltEtcModulesInDirectory(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) (moduleNames []string) {
291	groupedDestFiles, maxLen := groupDestFilesBySrc(destFiles)
292
293	// Find out the most appropriate module type to generate
294	var etcInstallPathKey string
295	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
296		// Do not break when found but iterate until the end to find a module with more
297		// specific install path
298		if strings.HasPrefix(destDir, etcInstallPath) {
299			etcInstallPathKey = etcInstallPath
300		}
301	}
302	relDestDirFromInstallDirBase, _ := filepath.Rel(etcInstallPathKey, destDir)
303
304	for fileIndex := range maxLen {
305		srcTuple := []srcBaseFileInstallBaseFileTuple{}
306		for _, srcFile := range android.SortedKeys(groupedDestFiles) {
307			groupedDestFile := groupedDestFiles[srcFile]
308			if len(groupedDestFile) > fileIndex {
309				srcTuple = append(srcTuple, groupedDestFile[fileIndex])
310			}
311		}
312
313		moduleName := generatedPrebuiltEtcModuleName(partition, srcDir, destDir, fileIndex)
314		moduleProps := prebuiltEtcModuleProps(ctx, moduleName, partition, destDir)
315		modulePropsPtr := &moduleProps
316		propsList := []interface{}{modulePropsPtr}
317
318		allCopyFileNamesUnchanged := true
319		var srcBaseFiles, installBaseFiles []string
320		for _, tuple := range srcTuple {
321			if tuple.srcBaseFile != tuple.installBaseFile {
322				allCopyFileNamesUnchanged = false
323			}
324			srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
325			installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
326		}
327
328		// Recovery partition-installed modules are installed to `recovery/root/system` by
329		// default (See modulePartition() in android/paths.go). If the destination file
330		// directory is not `recovery/root/system/...`, it should set install_in_root to true
331		// to prevent being installed in `recovery/root/system`.
332		if partition == "recovery" && !strings.HasPrefix(destDir, "system") {
333			propsList = append(propsList, &prebuiltInstallInRootProperties{
334				Install_in_root: proptools.BoolPtr(true),
335			})
336		}
337
338		// Set appropriate srcs, dsts, and releative_install_path based on
339		// the source and install file names
340		if allCopyFileNamesUnchanged {
341			modulePropsPtr.Srcs = srcBaseFiles
342
343			// Specify relative_install_path if it is not installed in the root directory of the
344			// partition
345			if !android.InList(relDestDirFromInstallDirBase, []string{"", "."}) {
346				propsList = append(propsList, &prebuiltSubdirProperties{
347					Relative_install_path: proptools.StringPtr(relDestDirFromInstallDirBase),
348				})
349			}
350		} else {
351			modulePropsPtr.Srcs = srcBaseFiles
352			dsts := []string{}
353			for _, installBaseFile := range installBaseFiles {
354				dsts = append(dsts, filepath.Join(relDestDirFromInstallDirBase, installBaseFile))
355			}
356			modulePropsPtr.Dsts = dsts
357		}
358
359		ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
360		moduleNames = append(moduleNames, moduleName)
361	}
362
363	return moduleNames
364}
365
366func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
367	for _, destDir := range android.SortedKeys(destDirFilesMap) {
368		ret = append(ret, createPrebuiltEtcModulesInDirectory(ctx, partition, srcDir, destDir, destDirFilesMap[destDir])...)
369	}
370	return ret
371}
372
373// Creates prebuilt_* modules based on the install paths and returns the list of generated
374// module names
375func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
376	groupedSources := processProductCopyFiles(ctx)
377	for _, srcDir := range android.SortedKeys(groupedSources) {
378		groupedSource := groupedSources[srcDir]
379		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
380		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
381		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
382		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
383		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "recovery", srcDir, groupedSource.recovery)...)
384	}
385
386	return ret
387}
388
389func createAvbpubkeyModule(ctx android.LoadHookContext) bool {
390	avbKeyPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbKeyPath
391	if avbKeyPath == "" {
392		return false
393	}
394	ctx.CreateModuleInDirectory(
395		etc.AvbpubkeyModuleFactory,
396		".",
397		&struct {
398			Name             *string
399			Product_specific *bool
400			Private_key      *string
401			No_full_install  *bool
402			Visibility       []string
403		}{
404			Name:             proptools.StringPtr("system_other_avbpubkey"),
405			Product_specific: proptools.BoolPtr(true),
406			Private_key:      proptools.StringPtr(avbKeyPath),
407			No_full_install:  proptools.BoolPtr(true),
408			Visibility:       []string{"//visibility:public"},
409		},
410	)
411	return true
412}
413