• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
168	No_full_install *bool
169
170	NamespaceExportedToMake bool
171
172	Visibility []string
173}
174
175// Split relative_install_path to a separate struct, because it is not supported for every
176// modules listed in [etcInstallPathToFactoryMap]
177type prebuiltSubdirProperties struct {
178	// If the base file name of the src and dst all match, dsts property does not need to be
179	// set, and only relative_install_path can be set.
180	Relative_install_path *string
181}
182
183// Split install_in_root to a separate struct as it is part of rootProperties instead of
184// properties
185type prebuiltInstallInRootProperties struct {
186	Install_in_root *bool
187}
188
189var (
190	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
191		"":                    etc.PrebuiltRootFactory,
192		"avb":                 etc.PrebuiltAvbFactory,
193		"bin":                 etc.PrebuiltBinaryFactory,
194		"bt_firmware":         etc.PrebuiltBtFirmwareFactory,
195		"cacerts":             etc.PrebuiltEtcCaCertsFactory,
196		"dsp":                 etc.PrebuiltDSPFactory,
197		"etc":                 etc.PrebuiltEtcFactory,
198		"etc/dsp":             etc.PrebuiltDSPFactory,
199		"etc/firmware":        etc.PrebuiltFirmwareFactory,
200		"firmware":            etc.PrebuiltFirmwareFactory,
201		"gpu":                 etc.PrebuiltGPUFactory,
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		"radio":               etc.PrebuiltRadioFactory,
214		"sbin":                etc.PrebuiltSbinFactory,
215		"system":              etc.PrebuiltSystemFactory,
216		"res":                 etc.PrebuiltResFactory,
217		"rfs":                 etc.PrebuiltRfsFactory,
218		"tee":                 etc.PrebuiltTeeFactory,
219		"tts":                 etc.PrebuiltVoicepackFactory,
220		"tvconfig":            etc.PrebuiltTvConfigFactory,
221		"tvservice":           etc.PrebuiltTvServiceFactory,
222		"usr/share":           etc.PrebuiltUserShareFactory,
223		"usr/hyphen-data":     etc.PrebuiltUserHyphenDataFactory,
224		"usr/keylayout":       etc.PrebuiltUserKeyLayoutFactory,
225		"usr/keychars":        etc.PrebuiltUserKeyCharsFactory,
226		"usr/srec":            etc.PrebuiltUserSrecFactory,
227		"usr/idc":             etc.PrebuiltUserIdcFactory,
228		"vendor":              etc.PrebuiltVendorFactory,
229		"vendor_dlkm":         etc.PrebuiltVendorDlkmFactory,
230		"vendor_overlay":      etc.PrebuiltVendorOverlayFactory,
231		"wallpaper":           etc.PrebuiltWallpaperFactory,
232		"wlc_upt":             etc.PrebuiltWlcUptFactory,
233	}
234)
235
236func generatedPrebuiltEtcModuleName(partition, srcDir, destDir string, count int) string {
237	// generated module name follows the pattern:
238	// <install partition>-<src file path>-<relative install path from partition root>-<number>
239	// Note that all path separators are replaced with "_" in the name
240	moduleName := partition
241	if !android.InList(srcDir, []string{"", "."}) {
242		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
243	}
244	if !android.InList(destDir, []string{"", "."}) {
245		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
246	}
247	moduleName += fmt.Sprintf("-%d", count)
248
249	return moduleName
250}
251
252func groupDestFilesBySrc(destFiles []srcBaseFileInstallBaseFileTuple) (ret map[string][]srcBaseFileInstallBaseFileTuple, maxLen int) {
253	ret = map[string][]srcBaseFileInstallBaseFileTuple{}
254	maxLen = 0
255	for _, tuple := range destFiles {
256		if _, ok := ret[tuple.srcBaseFile]; !ok {
257			ret[tuple.srcBaseFile] = []srcBaseFileInstallBaseFileTuple{}
258		}
259		ret[tuple.srcBaseFile] = append(ret[tuple.srcBaseFile], tuple)
260		maxLen = max(maxLen, len(ret[tuple.srcBaseFile]))
261	}
262	return ret, maxLen
263}
264
265func prebuiltEtcModuleProps(ctx android.LoadHookContext, moduleName, partition, destDir string) prebuiltModuleProperties {
266	moduleProps := prebuiltModuleProperties{}
267	moduleProps.Name = proptools.StringPtr(moduleName)
268
269	// Set partition specific properties
270	switch partition {
271	case "system_ext":
272		moduleProps.System_ext_specific = proptools.BoolPtr(true)
273	case "product":
274		moduleProps.Product_specific = proptools.BoolPtr(true)
275	case "vendor":
276		moduleProps.Soc_specific = proptools.BoolPtr(true)
277	case "recovery":
278		// To match the logic in modulePartition() in android/paths.go
279		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() && strings.HasPrefix(destDir, "first_stage_ramdisk") {
280			moduleProps.Ramdisk = proptools.BoolPtr(true)
281		} else {
282			moduleProps.Recovery = proptools.BoolPtr(true)
283		}
284	}
285
286	moduleProps.No_full_install = proptools.BoolPtr(true)
287	moduleProps.NamespaceExportedToMake = true
288	moduleProps.Visibility = []string{"//visibility:public"}
289
290	return moduleProps
291}
292
293func createPrebuiltEtcModulesInDirectory(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) (moduleNames []string) {
294	groupedDestFiles, maxLen := groupDestFilesBySrc(destFiles)
295
296	// Find out the most appropriate module type to generate
297	var etcInstallPathKey string
298	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
299		// Do not break when found but iterate until the end to find a module with more
300		// specific install path
301		if strings.HasPrefix(destDir, etcInstallPath) {
302			etcInstallPathKey = etcInstallPath
303		}
304	}
305	moduleFactory := etcInstallPathToFactoryList[etcInstallPathKey]
306	relDestDirFromInstallDirBase, _ := filepath.Rel(etcInstallPathKey, destDir)
307
308	for fileIndex := range maxLen {
309		srcTuple := []srcBaseFileInstallBaseFileTuple{}
310		for _, srcFile := range android.SortedKeys(groupedDestFiles) {
311			groupedDestFile := groupedDestFiles[srcFile]
312			if len(groupedDestFile) > fileIndex {
313				srcTuple = append(srcTuple, groupedDestFile[fileIndex])
314			}
315		}
316
317		moduleName := generatedPrebuiltEtcModuleName(partition, srcDir, destDir, fileIndex)
318		moduleProps := prebuiltEtcModuleProps(ctx, moduleName, partition, destDir)
319		modulePropsPtr := &moduleProps
320		propsList := []interface{}{modulePropsPtr}
321
322		allCopyFileNamesUnchanged := true
323		var srcBaseFiles, installBaseFiles []string
324		for _, tuple := range srcTuple {
325			if tuple.srcBaseFile != tuple.installBaseFile {
326				allCopyFileNamesUnchanged = false
327			}
328			srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
329			installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
330		}
331
332		// Recovery partition-installed modules are installed to `recovery/root/system` by
333		// default (See modulePartition() in android/paths.go). If the destination file
334		// directory is not `recovery/root/system/...`, it should set install_in_root to true
335		// to prevent being installed in `recovery/root/system`.
336		if partition == "recovery" && !strings.HasPrefix(destDir, "system") {
337			propsList = append(propsList, &prebuiltInstallInRootProperties{
338				Install_in_root: proptools.BoolPtr(true),
339			})
340			// Discard any previously picked module and force it to prebuilt_{root,any} as
341			// they are the only modules allowed to specify the `install_in_root` property.
342			etcInstallPathKey = ""
343			relDestDirFromInstallDirBase = destDir
344		}
345
346		// Set appropriate srcs, dsts, and releative_install_path based on
347		// the source and install file names
348		modulePropsPtr.Srcs = srcBaseFiles
349
350		// prebuilt_root should only be used in very limited cases in prebuilt_etc moddule gen, where:
351		// - all source file names are identical to the installed file names, and
352		// - all source files are installed in root, not the subdirectories of root
353		// prebuilt_root currently does not have a good way to specify the names of the multiple
354		// installed files, and prebuilt_root does not allow installing files at a subdirectory
355		// of the root.
356		// Use prebuilt_any instead of prebuilt_root if either of the conditions are not met as
357		// a fallback behavior.
358		if etcInstallPathKey == "" {
359			if !(allCopyFileNamesUnchanged && android.InList(relDestDirFromInstallDirBase, []string{"", "."})) {
360				moduleFactory = etc.PrebuiltAnyFactory
361			}
362		}
363
364		if allCopyFileNamesUnchanged {
365			// Specify relative_install_path if it is not installed in the base directory of the module.
366			// In case of prebuilt_{root,any} this is equivalent to the root of the partition.
367			if !android.InList(relDestDirFromInstallDirBase, []string{"", "."}) {
368				propsList = append(propsList, &prebuiltSubdirProperties{
369					Relative_install_path: proptools.StringPtr(relDestDirFromInstallDirBase),
370				})
371			}
372		} else {
373			dsts := proptools.NewConfigurable[[]string](nil, nil)
374			for _, installBaseFile := range installBaseFiles {
375				dsts.AppendSimpleValue([]string{filepath.Join(relDestDirFromInstallDirBase, installBaseFile)})
376			}
377			propsList = append(propsList, &etc.PrebuiltDstsProperties{
378				Dsts: dsts,
379			})
380		}
381
382		ctx.CreateModuleInDirectory(moduleFactory, srcDir, propsList...)
383		moduleNames = append(moduleNames, moduleName)
384	}
385
386	return moduleNames
387}
388
389func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
390	for _, destDir := range android.SortedKeys(destDirFilesMap) {
391		ret = append(ret, createPrebuiltEtcModulesInDirectory(ctx, partition, srcDir, destDir, destDirFilesMap[destDir])...)
392	}
393	return ret
394}
395
396// Creates prebuilt_* modules based on the install paths and returns the list of generated
397// module names
398func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
399	groupedSources := processProductCopyFiles(ctx)
400	for _, srcDir := range android.SortedKeys(groupedSources) {
401		groupedSource := groupedSources[srcDir]
402		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
403		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
404		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
405		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
406		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "recovery", srcDir, groupedSource.recovery)...)
407	}
408
409	return ret
410}
411
412func createAvbpubkeyModule(ctx android.LoadHookContext) bool {
413	avbKeyPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbKeyPath
414	if avbKeyPath == "" {
415		return false
416	}
417	ctx.CreateModuleInDirectory(
418		etc.AvbpubkeyModuleFactory,
419		".",
420		&struct {
421			Name             *string
422			Product_specific *bool
423			Private_key      *string
424			No_full_install  *bool
425			Visibility       []string
426		}{
427			Name:             proptools.StringPtr("system_other_avbpubkey"),
428			Product_specific: proptools.BoolPtr(true),
429			Private_key:      proptools.StringPtr(avbKeyPath),
430			No_full_install:  proptools.BoolPtr(true),
431			Visibility:       []string{"//visibility:public"},
432		},
433	)
434	return true
435}
436