• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2019 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 apex
16
17import (
18	"encoding/json"
19	"fmt"
20	"path/filepath"
21	"runtime"
22	"sort"
23	"strconv"
24	"strings"
25
26	"android/soong/android"
27	"android/soong/java"
28
29	"github.com/google/blueprint"
30	"github.com/google/blueprint/proptools"
31)
32
33var (
34	pctx = android.NewPackageContext("android/apex")
35)
36
37func init() {
38	pctx.Import("android/soong/android")
39	pctx.Import("android/soong/cc/config")
40	pctx.Import("android/soong/java")
41	pctx.HostBinToolVariable("apexer", "apexer")
42	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
43	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
44	hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
45		pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
46			if !ctx.Config().FrameworksBaseDirExists(ctx) {
47				return filepath.Join(prebuiltDir, runtime.GOOS, "bin", tool)
48			} else {
49				return ctx.Config().HostToolPath(ctx, tool).String()
50			}
51		})
52	}
53	hostBinToolVariableWithPrebuilt("aapt2", "prebuilts/sdk/tools", "aapt2")
54	pctx.HostBinToolVariable("avbtool", "avbtool")
55	pctx.HostBinToolVariable("e2fsdroid", "e2fsdroid")
56	pctx.HostBinToolVariable("merge_zips", "merge_zips")
57	pctx.HostBinToolVariable("mke2fs", "mke2fs")
58	pctx.HostBinToolVariable("resize2fs", "resize2fs")
59	pctx.HostBinToolVariable("sefcontext_compile", "sefcontext_compile")
60	pctx.HostBinToolVariable("soong_zip", "soong_zip")
61	pctx.HostBinToolVariable("zip2zip", "zip2zip")
62	pctx.HostBinToolVariable("zipalign", "zipalign")
63	pctx.HostBinToolVariable("jsonmodify", "jsonmodify")
64	pctx.HostBinToolVariable("conv_apex_manifest", "conv_apex_manifest")
65	pctx.HostBinToolVariable("extract_apks", "extract_apks")
66	pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
67	pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
68	pctx.HostBinToolVariable("make_erofs", "make_erofs")
69	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
70	pctx.HostBinToolVariable("dexdeps", "dexdeps")
71	pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh")
72}
73
74var (
75	apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{
76		Command: `rm -f $out && ${jsonmodify} $in ` +
77			`-a provideNativeLibs ${provideNativeLibs} ` +
78			`-a requireNativeLibs ${requireNativeLibs} ` +
79			`${opt} ` +
80			`-o $out`,
81		CommandDeps: []string{"${jsonmodify}"},
82		Description: "prepare ${out}",
83	}, "provideNativeLibs", "requireNativeLibs", "opt")
84
85	stripApexManifestRule = pctx.StaticRule("stripApexManifestRule", blueprint.RuleParams{
86		Command:     `rm -f $out && ${conv_apex_manifest} strip $in -o $out`,
87		CommandDeps: []string{"${conv_apex_manifest}"},
88		Description: "strip ${in}=>${out}",
89	})
90
91	pbApexManifestRule = pctx.StaticRule("pbApexManifestRule", blueprint.RuleParams{
92		Command:     `rm -f $out && ${conv_apex_manifest} proto $in -o $out`,
93		CommandDeps: []string{"${conv_apex_manifest}"},
94		Description: "convert ${in}=>${out}",
95	})
96
97	// TODO(b/113233103): make sure that file_contexts is as expected, i.e., validate
98	// against the binary policy using sefcontext_compiler -p <policy>.
99
100	// TODO(b/114327326): automate the generation of file_contexts
101	apexRule = pctx.StaticRule("apexRule", blueprint.RuleParams{
102		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
103			`(. ${out}.copy_commands) && ` +
104			`APEXER_TOOL_PATH=${tool_path} ` +
105			`${apexer} --force --manifest ${manifest} ` +
106			`--file_contexts ${file_contexts} ` +
107			`--canned_fs_config ${canned_fs_config} ` +
108			`--include_build_info ` +
109			`--payload_type image ` +
110			`--key ${key} ${opt_flags} ${image_dir} ${out} `,
111		CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}",
112			"${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}",
113			"${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"},
114		Rspfile:        "${out}.copy_commands",
115		RspfileContent: "${copy_commands}",
116		Description:    "APEX ${image_dir} => ${out}",
117	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key", "opt_flags", "manifest", "payload_fs_type")
118
119	zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{
120		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
121			`(. ${out}.copy_commands) && ` +
122			`APEXER_TOOL_PATH=${tool_path} ` +
123			`${apexer} --force --manifest ${manifest} ` +
124			`--payload_type zip ` +
125			`${image_dir} ${out} `,
126		CommandDeps:    []string{"${apexer}", "${merge_zips}", "${soong_zip}", "${zipalign}", "${aapt2}"},
127		Rspfile:        "${out}.copy_commands",
128		RspfileContent: "${copy_commands}",
129		Description:    "ZipAPEX ${image_dir} => ${out}",
130	}, "tool_path", "image_dir", "copy_commands", "manifest")
131
132	apexProtoConvertRule = pctx.AndroidStaticRule("apexProtoConvertRule",
133		blueprint.RuleParams{
134			Command:     `${aapt2} convert --output-format proto $in -o $out`,
135			CommandDeps: []string{"${aapt2}"},
136		})
137
138	apexBundleRule = pctx.StaticRule("apexBundleRule", blueprint.RuleParams{
139		Command: `${zip2zip} -i $in -o $out.base ` +
140			`apex_payload.img:apex/${abi}.img ` +
141			`apex_build_info.pb:apex/${abi}.build_info.pb ` +
142			`apex_manifest.json:root/apex_manifest.json ` +
143			`apex_manifest.pb:root/apex_manifest.pb ` +
144			`AndroidManifest.xml:manifest/AndroidManifest.xml ` +
145			`assets/NOTICE.html.gz:assets/NOTICE.html.gz &&` +
146			`${soong_zip} -o $out.config -C $$(dirname ${config}) -f ${config} && ` +
147			`${merge_zips} $out $out.base $out.config`,
148		CommandDeps: []string{"${zip2zip}", "${soong_zip}", "${merge_zips}"},
149		Description: "app bundle",
150	}, "abi", "config")
151
152	emitApexContentRule = pctx.StaticRule("emitApexContentRule", blueprint.RuleParams{
153		Command:        `rm -f ${out} && touch ${out} && (. ${out}.emit_commands)`,
154		Rspfile:        "${out}.emit_commands",
155		RspfileContent: "${emit_commands}",
156		Description:    "Emit APEX image content",
157	}, "emit_commands")
158
159	diffApexContentRule = pctx.StaticRule("diffApexContentRule", blueprint.RuleParams{
160		Command: `diff --unchanged-group-format='' \` +
161			`--changed-group-format='%<' \` +
162			`${image_content_file} ${allowed_files_file} || (` +
163			`echo -e "New unexpected files were added to ${apex_module_name}." ` +
164			` "To fix the build run following command:" && ` +
165			`echo "system/apex/tools/update_allowed_list.sh ${allowed_files_file} ${image_content_file}" && ` +
166			`exit 1); touch ${out}`,
167		Description: "Diff ${image_content_file} and ${allowed_files_file}",
168	}, "image_content_file", "allowed_files_file", "apex_module_name")
169
170	generateAPIsUsedbyApexRule = pctx.StaticRule("generateAPIsUsedbyApexRule", blueprint.RuleParams{
171		Command:     "$genNdkUsedbyApexPath ${image_dir} ${readelf} ${out}",
172		CommandDeps: []string{"${genNdkUsedbyApexPath}"},
173		Description: "Generate symbol list used by Apex",
174	}, "image_dir", "readelf")
175
176	// Don't add more rules here. Consider using android.NewRuleBuilder instead.
177)
178
179// buildManifest creates buile rules to modify the input apex_manifest.json to add information
180// gathered by the build system such as provided/required native libraries. Two output files having
181// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and
182// a.manifest.PbOut is protobuf format for R+ devices.
183// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle
184func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) {
185	src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
186
187	// Put dependency({provide|require}NativeLibs) in apex_manifest.json
188	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
189	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
190
191	// APEX name can be overridden
192	optCommands := []string{}
193	if a.properties.Apex_name != nil {
194		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
195	}
196
197	// Collect jniLibs. Notice that a.filesInfo is already sorted
198	var jniLibs []string
199	for _, fi := range a.filesInfo {
200		if fi.isJniLib && !android.InList(fi.stem(), jniLibs) {
201			jniLibs = append(jniLibs, fi.stem())
202		}
203	}
204	if len(jniLibs) > 0 {
205		optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
206	}
207
208	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
209	ctx.Build(pctx, android.BuildParams{
210		Rule:   apexManifestRule,
211		Input:  src,
212		Output: manifestJsonFullOut,
213		Args: map[string]string{
214			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
215			"requireNativeLibs": strings.Join(requireNativeLibs, " "),
216			"opt":               strings.Join(optCommands, " "),
217		},
218	})
219
220	// b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare
221	// stripped-down version so that APEX modules built from R+ can be installed to Q
222	minSdkVersion := a.minSdkVersion(ctx)
223	if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
224		a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json")
225		ctx.Build(pctx, android.BuildParams{
226			Rule:   stripApexManifestRule,
227			Input:  manifestJsonFullOut,
228			Output: a.manifestJsonOut,
229		})
230	}
231
232	// From R+, protobuf binary format (.pb) is the standard format for apex_manifest
233	a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb")
234	ctx.Build(pctx, android.BuildParams{
235		Rule:   pbApexManifestRule,
236		Input:  manifestJsonFullOut,
237		Output: a.manifestPbOut,
238	})
239}
240
241// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts
242// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
243// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
244// labeled as system_file.
245func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
246	var fileContexts android.Path
247	var fileContextsDir string
248	if a.properties.File_contexts == nil {
249		fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
250	} else {
251		if m, t := android.SrcIsModuleWithTag(*a.properties.File_contexts); m != "" {
252			otherModule := android.GetModuleFromPathDep(ctx, m, t)
253			fileContextsDir = ctx.OtherModuleDir(otherModule)
254		}
255		fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
256	}
257	if fileContextsDir == "" {
258		fileContextsDir = filepath.Dir(fileContexts.String())
259	}
260	fileContextsDir += string(filepath.Separator)
261
262	if a.Platform() {
263		if !strings.HasPrefix(fileContextsDir, "system/sepolicy/") {
264			ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but found in  %q", fileContextsDir)
265		}
266	}
267	if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
268		ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String())
269	}
270
271	output := android.PathForModuleOut(ctx, "file_contexts")
272	rule := android.NewRuleBuilder(pctx, ctx)
273
274	switch a.properties.ApexType {
275	case imageApex:
276		// remove old file
277		rule.Command().Text("rm").FlagWithOutput("-f ", output)
278		// copy file_contexts
279		rule.Command().Text("cat").Input(fileContexts).Text(">>").Output(output)
280		// new line
281		rule.Command().Text("echo").Text(">>").Output(output)
282		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
283		rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
284		rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
285	case flattenedApex:
286		// For flattened apexes, install path should be prepended.
287		// File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS
288		// so that it can be merged into file_contexts.bin
289		apexPath := android.InstallPathToOnDevicePath(ctx, a.installDir.Join(ctx, a.Name()))
290		apexPath = strings.ReplaceAll(apexPath, ".", `\\.`)
291		// remove old file
292		rule.Command().Text("rm").FlagWithOutput("-f ", output)
293		// copy file_contexts
294		rule.Command().Text("awk").Text(`'/object_r/{printf("` + apexPath + `%s\n", $0)}'`).Input(fileContexts).Text(">").Output(output)
295		// new line
296		rule.Command().Text("echo").Text(">>").Output(output)
297		// force-label /apex_manifest.pb and / as system_file so that apexd can read them
298		rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output)
299		rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output)
300	default:
301		panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
302	}
303
304	rule.Build("file_contexts."+a.Name(), "Generate file_contexts")
305	return output.OutputPath
306}
307
308// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
309// files included in this APEX is shown. The text file is dist'ed so that people can see what's
310// included in the APEX without actually downloading and extracting it.
311func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
312	output := android.PathForModuleOut(ctx, "installed-files.txt")
313	rule := android.NewRuleBuilder(pctx, ctx)
314	rule.Command().
315		Implicit(builtApex).
316		Text("(cd " + imageDir.String() + " ; ").
317		Text("find . \\( -type f -o -type l \\) -printf \"%s %p\\n\") ").
318		Text(" | sort -nr > ").
319		Output(output)
320	rule.Build("installed-files."+a.Name(), "Installed files")
321	return output.OutputPath
322}
323
324// buildBundleConfig creates a build rule for the bundle config file that will control the bundle
325// creation process.
326func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
327	output := android.PathForModuleOut(ctx, "bundle_config.json")
328
329	type ApkConfig struct {
330		Package_name string `json:"package_name"`
331		Apk_path     string `json:"path"`
332	}
333	config := struct {
334		Compression struct {
335			Uncompressed_glob []string `json:"uncompressed_glob"`
336		} `json:"compression"`
337		Apex_config struct {
338			Apex_embedded_apk_config []ApkConfig `json:"apex_embedded_apk_config,omitempty"`
339		} `json:"apex_config,omitempty"`
340	}{}
341
342	config.Compression.Uncompressed_glob = []string{
343		"apex_payload.img",
344		"apex_manifest.*",
345	}
346
347	// Collect the manifest names and paths of android apps if their manifest names are
348	// overridden.
349	for _, fi := range a.filesInfo {
350		if fi.class != app && fi.class != appSet {
351			continue
352		}
353		packageName := fi.overriddenPackageName
354		if packageName != "" {
355			config.Apex_config.Apex_embedded_apk_config = append(
356				config.Apex_config.Apex_embedded_apk_config,
357				ApkConfig{
358					Package_name: packageName,
359					Apk_path:     fi.path(),
360				})
361		}
362	}
363
364	j, err := json.Marshal(config)
365	if err != nil {
366		panic(fmt.Errorf("error while marshalling to %q: %#v", output, err))
367	}
368
369	android.WriteFileRule(ctx, output, string(j))
370
371	return output.OutputPath
372}
373
374func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path {
375	return java.ManifestFixer(ctx, androidManifestFile, java.ManifestFixerParams{
376		TestOnly: true,
377	})
378}
379
380// buildUnflattendApex creates build rules to build an APEX using apexer.
381func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
382	apexType := a.properties.ApexType
383	suffix := apexType.suffix()
384	apexName := proptools.StringDefault(a.properties.Apex_name, a.BaseModuleName())
385
386	////////////////////////////////////////////////////////////////////////////////////////////
387	// Step 1: copy built files to appropriate directories under the image directory
388
389	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
390
391	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
392
393	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
394	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
395	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
396	// for the overriding VNDK APEXes.
397	if a.vndkApex && len(a.overridableProperties.Overrides) > 0 {
398		installSymbolFiles = false
399	}
400
401	// Avoid creating duplicate build rules for multi-installed APEXes.
402	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
403		installSymbolFiles = false
404
405	}
406	// set of dependency module:location mappings
407	installMapSet := make(map[string]bool)
408
409	// TODO(jiyong): use the RuleBuilder
410	var copyCommands []string
411	var implicitInputs []android.Path
412	pathWhenActivated := android.PathForModuleInPartitionInstall(ctx, "apex", apexName)
413	for _, fi := range a.filesInfo {
414		destPath := imageDir.Join(ctx, fi.path()).String()
415		// Prepare the destination path
416		destPathDir := filepath.Dir(destPath)
417		if fi.class == appSet {
418			copyCommands = append(copyCommands, "rm -rf "+destPathDir)
419		}
420		copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
421
422		installMapPath := fi.builtFile
423
424		// Copy the built file to the directory. But if the symlink optimization is turned
425		// on, place a symlink to the corresponding file in /system partition instead.
426		if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
427			// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
428			pathOnDevice := filepath.Join("/system", fi.path())
429			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
430		} else {
431			var installedPath android.InstallPath
432			if fi.class == appSet {
433				copyCommands = append(copyCommands,
434					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir,
435						fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String()))
436				if installSymbolFiles {
437					installedPath = ctx.InstallFileWithExtraFilesZip(pathWhenActivated.Join(ctx, fi.installDir),
438						fi.stem(), fi.builtFile, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs())
439				}
440			} else {
441				copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath)
442				if installSymbolFiles {
443					installedPath = ctx.InstallFile(pathWhenActivated.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
444				}
445			}
446			implicitInputs = append(implicitInputs, fi.builtFile)
447			if installSymbolFiles {
448				implicitInputs = append(implicitInputs, installedPath)
449			}
450
451			// Create additional symlinks pointing the file inside the APEX (if any). Note that
452			// this is independent from the symlink optimization.
453			for _, symlinkPath := range fi.symlinkPaths() {
454				symlinkDest := imageDir.Join(ctx, symlinkPath).String()
455				copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
456				if installSymbolFiles {
457					installedSymlink := ctx.InstallSymlink(pathWhenActivated.Join(ctx, filepath.Dir(symlinkPath)), filepath.Base(symlinkPath), installedPath)
458					implicitInputs = append(implicitInputs, installedSymlink)
459				}
460			}
461
462			installMapPath = installedPath
463		}
464
465		// Copy the test files (if any)
466		for _, d := range fi.dataPaths {
467			// TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible
468			relPath := d.SrcPath.Rel()
469			dataPath := d.SrcPath.String()
470			if !strings.HasSuffix(dataPath, relPath) {
471				panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath))
472			}
473
474			dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
475
476			copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
477			implicitInputs = append(implicitInputs, d.SrcPath)
478		}
479
480		installMapSet[installMapPath.String()+":"+fi.installDir+"/"+fi.builtFile.Base()] = true
481	}
482	implicitInputs = append(implicitInputs, a.manifestPbOut)
483	if installSymbolFiles {
484		installedManifest := ctx.InstallFile(pathWhenActivated, "apex_manifest.pb", a.manifestPbOut)
485		installedKey := ctx.InstallFile(pathWhenActivated, "apex_pubkey", a.publicKeyFile)
486		implicitInputs = append(implicitInputs, installedManifest, installedKey)
487	}
488
489	if len(installMapSet) > 0 {
490		var installs []string
491		installs = append(installs, android.SortedStringKeys(installMapSet)...)
492		a.SetLicenseInstallMap(installs)
493	}
494
495	////////////////////////////////////////////////////////////////////////////////////////////
496	// Step 1.a: Write the list of files in this APEX to a txt file and compare it against
497	// the allowed list given via the allowed_files property. Build fails when the two lists
498	// differ.
499	//
500	// TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems
501	// to be using this at this moment. Furthermore, this looks very similar to what
502	// buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't
503	// hurt readability.
504	// TODO(jiyong): use RuleBuilder
505	if a.overridableProperties.Allowed_files != nil {
506		// Build content.txt
507		var emitCommands []string
508		imageContentFile := android.PathForModuleOut(ctx, "content.txt")
509		emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
510		minSdkVersion := a.minSdkVersion(ctx)
511		if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
512			emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
513		}
514		for _, fi := range a.filesInfo {
515			emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
516		}
517		emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
518		ctx.Build(pctx, android.BuildParams{
519			Rule:        emitApexContentRule,
520			Implicits:   implicitInputs,
521			Output:      imageContentFile,
522			Description: "emit apex image content",
523			Args: map[string]string{
524				"emit_commands": strings.Join(emitCommands, " && "),
525			},
526		})
527		implicitInputs = append(implicitInputs, imageContentFile)
528
529		// Compare content.txt against allowed_files.
530		allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
531		phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
532		ctx.Build(pctx, android.BuildParams{
533			Rule:        diffApexContentRule,
534			Implicits:   implicitInputs,
535			Output:      phonyOutput,
536			Description: "diff apex image content",
537			Args: map[string]string{
538				"allowed_files_file": allowedFilesFile.String(),
539				"image_content_file": imageContentFile.String(),
540				"apex_module_name":   a.Name(),
541			},
542		})
543		implicitInputs = append(implicitInputs, phonyOutput)
544	}
545
546	unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
547	outHostBinDir := ctx.Config().HostToolPath(ctx, "").String()
548	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
549
550	// Figure out if we need to compress the apex.
551	compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.overridableProperties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps()
552	if apexType == imageApex {
553
554		////////////////////////////////////////////////////////////////////////////////////
555		// Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
556		// in this APEX. The file will be used by apexer in later steps.
557		cannedFsConfig := a.buildCannedFsConfig(ctx)
558		implicitInputs = append(implicitInputs, cannedFsConfig)
559
560		////////////////////////////////////////////////////////////////////////////////////
561		// Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX.
562		// TODO(jiyong): use the RuleBuilder
563		optFlags := []string{}
564
565		fileContexts := a.buildFileContexts(ctx)
566		implicitInputs = append(implicitInputs, fileContexts)
567
568		implicitInputs = append(implicitInputs, a.privateKeyFile, a.publicKeyFile)
569		optFlags = append(optFlags, "--pubkey "+a.publicKeyFile.String())
570
571		manifestPackageName := a.getOverrideManifestPackageName(ctx)
572		if manifestPackageName != "" {
573			optFlags = append(optFlags, "--override_apk_package_name "+manifestPackageName)
574		}
575
576		if a.properties.AndroidManifest != nil {
577			androidManifestFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.AndroidManifest))
578
579			if a.testApex {
580				androidManifestFile = markManifestTestOnly(ctx, androidManifestFile)
581			}
582
583			implicitInputs = append(implicitInputs, androidManifestFile)
584			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
585		} else if a.testApex {
586			optFlags = append(optFlags, "--test_only")
587		}
588
589		// Determine target/min sdk version from the context
590		// TODO(jiyong): make this as a function
591		moduleMinSdkVersion := a.minSdkVersion(ctx)
592		minSdkVersion := moduleMinSdkVersion.String()
593
594		// bundletool doesn't understand what "current" is. We need to transform it to
595		// codename
596		if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
597			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
598
599			if java.UseApiFingerprint(ctx) {
600				minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
601				implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
602			}
603		}
604		// apex module doesn't have a concept of target_sdk_version, hence for the time
605		// being targetSdkVersion == default targetSdkVersion of the branch.
606		targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
607
608		if java.UseApiFingerprint(ctx) {
609			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
610			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
611		}
612		optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
613		optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
614
615		if a.overridableProperties.Logging_parent != "" {
616			optFlags = append(optFlags, "--logging_parent ", a.overridableProperties.Logging_parent)
617		}
618
619		// Create a NOTICE file, and embed it as an asset file in the APEX.
620		a.htmlGzNotice = android.PathForModuleOut(ctx, "NOTICE.html.gz")
621		android.BuildNoticeHtmlOutputFromLicenseMetadata(
622			ctx, a.htmlGzNotice, "", "",
623			[]string{
624				android.PathForModuleInstall(ctx).String() + "/",
625				android.PathForModuleInPartitionInstall(ctx, "apex").String() + "/",
626			})
627		noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz")
628		builder := android.NewRuleBuilder(pctx, ctx)
629		builder.Command().Text("cp").
630			Input(a.htmlGzNotice).
631			Output(noticeAssetPath)
632		builder.Build("notice_dir", "Building notice dir")
633		implicitInputs = append(implicitInputs, noticeAssetPath)
634		optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String()))
635
636		if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled {
637			// Apexes which are supposed to be installed in builtin dirs(/system, etc)
638			// don't need hashtree for activation. Therefore, by removing hashtree from
639			// apex bundle (filesystem image in it, to be specific), we can save storage.
640			optFlags = append(optFlags, "--no_hashtree")
641		}
642
643		if a.testOnlyShouldSkipPayloadSign() {
644			optFlags = append(optFlags, "--unsigned_payload")
645		}
646
647		if a.properties.Apex_name != nil {
648			// If apex_name is set, apexer can skip checking if key name matches with
649			// apex name.  Note that apex_manifest is also mended.
650			optFlags = append(optFlags, "--do_not_check_keyname")
651		}
652
653		if moduleMinSdkVersion == android.SdkVersion_Android10 {
654			implicitInputs = append(implicitInputs, a.manifestJsonOut)
655			optFlags = append(optFlags, "--manifest_json "+a.manifestJsonOut.String())
656		}
657
658		optFlags = append(optFlags, "--payload_fs_type "+a.payloadFsType.string())
659
660		ctx.Build(pctx, android.BuildParams{
661			Rule:        apexRule,
662			Implicits:   implicitInputs,
663			Output:      unsignedOutputFile,
664			Description: "apex (" + apexType.name() + ")",
665			Args: map[string]string{
666				"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
667				"image_dir":        imageDir.String(),
668				"copy_commands":    strings.Join(copyCommands, " && "),
669				"manifest":         a.manifestPbOut.String(),
670				"file_contexts":    fileContexts.String(),
671				"canned_fs_config": cannedFsConfig.String(),
672				"key":              a.privateKeyFile.String(),
673				"opt_flags":        strings.Join(optFlags, " "),
674			},
675		})
676
677		// TODO(jiyong): make the two rules below as separate functions
678		apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix)
679		bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip")
680		a.bundleModuleFile = bundleModuleFile
681
682		ctx.Build(pctx, android.BuildParams{
683			Rule:        apexProtoConvertRule,
684			Input:       unsignedOutputFile,
685			Output:      apexProtoFile,
686			Description: "apex proto convert",
687		})
688
689		implicitInputs = append(implicitInputs, unsignedOutputFile)
690
691		// Run coverage analysis
692		apisUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.txt")
693		ctx.Build(pctx, android.BuildParams{
694			Rule:        generateAPIsUsedbyApexRule,
695			Implicits:   implicitInputs,
696			Description: "coverage",
697			Output:      apisUsedbyOutputFile,
698			Args: map[string]string{
699				"image_dir": imageDir.String(),
700				"readelf":   "${config.ClangBin}/llvm-readelf",
701			},
702		})
703		a.nativeApisUsedByModuleFile = apisUsedbyOutputFile
704
705		var nativeLibNames []string
706		for _, f := range a.filesInfo {
707			if f.class == nativeSharedLib {
708				nativeLibNames = append(nativeLibNames, f.stem())
709			}
710		}
711		apisBackedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_backing.txt")
712		rule := android.NewRuleBuilder(pctx, ctx)
713		rule.Command().
714			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")).
715			Output(apisBackedbyOutputFile).
716			Flags(nativeLibNames)
717		rule.Build("ndk_backedby_list", "Generate API libraries backed by Apex")
718		a.nativeApisBackedByModuleFile = apisBackedbyOutputFile
719
720		var javaLibOrApkPath []android.Path
721		for _, f := range a.filesInfo {
722			if f.class == javaSharedLib || f.class == app {
723				javaLibOrApkPath = append(javaLibOrApkPath, f.builtFile)
724			}
725		}
726		javaApiUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.xml")
727		javaUsedByRule := android.NewRuleBuilder(pctx, ctx)
728		javaUsedByRule.Command().
729			Tool(android.PathForSource(ctx, "build/soong/scripts/gen_java_usedby_apex.sh")).
730			BuiltTool("dexdeps").
731			Output(javaApiUsedbyOutputFile).
732			Inputs(javaLibOrApkPath)
733		javaUsedByRule.Build("java_usedby_list", "Generate Java APIs used by Apex")
734		a.javaApisUsedByModuleFile = javaApiUsedbyOutputFile
735
736		bundleConfig := a.buildBundleConfig(ctx)
737
738		var abis []string
739		for _, target := range ctx.MultiTargets() {
740			if len(target.Arch.Abi) > 0 {
741				abis = append(abis, target.Arch.Abi[0])
742			}
743		}
744
745		abis = android.FirstUniqueStrings(abis)
746
747		ctx.Build(pctx, android.BuildParams{
748			Rule:        apexBundleRule,
749			Input:       apexProtoFile,
750			Implicit:    bundleConfig,
751			Output:      a.bundleModuleFile,
752			Description: "apex bundle module",
753			Args: map[string]string{
754				"abi":    strings.Join(abis, "."),
755				"config": bundleConfig.String(),
756			},
757		})
758	} else { // zipApex
759		ctx.Build(pctx, android.BuildParams{
760			Rule:        zipApexRule,
761			Implicits:   implicitInputs,
762			Output:      unsignedOutputFile,
763			Description: "apex (" + apexType.name() + ")",
764			Args: map[string]string{
765				"tool_path":     outHostBinDir + ":" + prebuiltSdkToolsBinDir,
766				"image_dir":     imageDir.String(),
767				"copy_commands": strings.Join(copyCommands, " && "),
768				"manifest":      a.manifestPbOut.String(),
769			},
770		})
771	}
772
773	////////////////////////////////////////////////////////////////////////////////////
774	// Step 4: Sign the APEX using signapk
775	signedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix)
776
777	pem, key := a.getCertificateAndPrivateKey(ctx)
778	rule := java.Signapk
779	args := map[string]string{
780		"certificates": pem.String() + " " + key.String(),
781		"flags":        "-a 4096 --align-file-size", //alignment
782	}
783	implicits := android.Paths{pem, key}
784	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
785		rule = java.SignapkRE
786		args["implicits"] = strings.Join(implicits.Strings(), ",")
787		args["outCommaList"] = signedOutputFile.String()
788	}
789	ctx.Build(pctx, android.BuildParams{
790		Rule:        rule,
791		Description: "signapk",
792		Output:      signedOutputFile,
793		Input:       unsignedOutputFile,
794		Implicits:   implicits,
795		Args:        args,
796	})
797	if suffix == imageApexSuffix {
798		a.outputApexFile = signedOutputFile
799	}
800	a.outputFile = signedOutputFile
801
802	if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() {
803		ctx.PropertyErrorf("test_only_force_compression", "not available")
804		return
805	}
806
807	if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) {
808		a.isCompressed = true
809		unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix+".unsigned")
810
811		compressRule := android.NewRuleBuilder(pctx, ctx)
812		compressRule.Command().
813			Text("rm").
814			FlagWithOutput("-f ", unsignedCompressedOutputFile)
815		compressRule.Command().
816			BuiltTool("apex_compression_tool").
817			Flag("compress").
818			FlagWithArg("--apex_compression_tool ", outHostBinDir+":"+prebuiltSdkToolsBinDir).
819			FlagWithInput("--input ", signedOutputFile).
820			FlagWithOutput("--output ", unsignedCompressedOutputFile)
821		compressRule.Build("compressRule", "Generate unsigned compressed APEX file")
822
823		signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix)
824		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
825			args["outCommaList"] = signedCompressedOutputFile.String()
826		}
827		ctx.Build(pctx, android.BuildParams{
828			Rule:        rule,
829			Description: "sign compressedApex",
830			Output:      signedCompressedOutputFile,
831			Input:       unsignedCompressedOutputFile,
832			Implicits:   implicits,
833			Args:        args,
834		})
835		a.outputFile = signedCompressedOutputFile
836	}
837
838	installSuffix := suffix
839	if a.isCompressed {
840		installSuffix = imageCapexSuffix
841	}
842
843	if !a.installable() {
844		a.SkipInstall()
845	}
846
847	// Install to $OUT/soong/{target,host}/.../apex.
848	a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile,
849		a.compatSymlinks.Paths()...)
850
851	// installed-files.txt is dist'ed
852	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
853}
854
855// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
856// single output file. It is a phony target for all the files under /system/apex/<name> directory.
857// This function creates the installation rules for the files.
858func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
859	bundleName := a.Name()
860	installedSymlinks := append(android.InstallPaths(nil), a.compatSymlinks...)
861	if a.installable() {
862		for _, fi := range a.filesInfo {
863			dir := filepath.Join("apex", bundleName, fi.installDir)
864			installDir := android.PathForModuleInstall(ctx, dir)
865			if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
866				// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
867				pathOnDevice := filepath.Join("/system", fi.path())
868				installedSymlinks = append(installedSymlinks,
869					ctx.InstallAbsoluteSymlink(installDir, fi.stem(), pathOnDevice))
870			} else {
871				target := ctx.InstallFile(installDir, fi.stem(), fi.builtFile)
872				for _, sym := range fi.symlinks {
873					installedSymlinks = append(installedSymlinks,
874						ctx.InstallSymlink(installDir, sym, target))
875				}
876			}
877		}
878
879		// Create install rules for the files added in GenerateAndroidBuildActions after
880		// buildFlattenedApex is called.  Add the links to system libs (if any) as dependencies
881		// of the apex_manifest.pb file since it is always present.
882		dir := filepath.Join("apex", bundleName)
883		installDir := android.PathForModuleInstall(ctx, dir)
884		ctx.InstallFile(installDir, "apex_manifest.pb", a.manifestPbOut, installedSymlinks.Paths()...)
885		ctx.InstallFile(installDir, "apex_pubkey", a.publicKeyFile)
886	}
887
888	a.fileContexts = a.buildFileContexts(ctx)
889
890	a.outputFile = android.PathForModuleInstall(ctx, "apex", bundleName)
891}
892
893// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
894// the zip container of this APEX. See the description of the 'certificate' property for how
895// the cert and the private key are found.
896func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) {
897	if a.containerCertificateFile != nil {
898		return a.containerCertificateFile, a.containerPrivateKeyFile
899	}
900
901	cert := String(a.overridableProperties.Certificate)
902	if cert == "" {
903		return ctx.Config().DefaultAppCertificate(ctx)
904	}
905
906	defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
907	pem = defaultDir.Join(ctx, cert+".x509.pem")
908	key = defaultDir.Join(ctx, cert+".pk8")
909	return pem, key
910}
911
912func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string {
913	// For VNDK APEXes, check "com.android.vndk" in PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
914	// to see if it should be overridden because their <apex name> is dynamically generated
915	// according to its VNDK version.
916	if a.vndkApex {
917		overrideName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(vndkApexName)
918		if overridden {
919			return strings.Replace(*a.properties.Apex_name, vndkApexName, overrideName, 1)
920		}
921		return ""
922	}
923	if a.overridableProperties.Package_name != "" {
924		return a.overridableProperties.Package_name
925	}
926	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
927	if overridden {
928		return manifestPackageName
929	}
930	return ""
931}
932
933func (a *apexBundle) buildApexDependencyInfo(ctx android.ModuleContext) {
934	if !a.primaryApexType {
935		return
936	}
937
938	if a.properties.IsCoverageVariant {
939		// Otherwise, we will have duplicated rules for coverage and
940		// non-coverage variants of the same APEX
941		return
942	}
943
944	if ctx.Host() {
945		// No need to generate dependency info for host variant
946		return
947	}
948
949	depInfos := android.DepNameToDepInfoMap{}
950	a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
951		if from.Name() == to.Name() {
952			// This can happen for cc.reuseObjTag. We are not interested in tracking this.
953			// As soon as the dependency graph crosses the APEX boundary, don't go further.
954			return !externalDep
955		}
956
957		// Skip dependencies that are only available to APEXes; they are developed with updatability
958		// in mind and don't need manual approval.
959		if to.(android.ApexModule).NotAvailableForPlatform() {
960			return !externalDep
961		}
962
963		depTag := ctx.OtherModuleDependencyTag(to)
964		// Check to see if dependency been marked to skip the dependency check
965		if skipDepCheck, ok := depTag.(android.SkipApexAllowedDependenciesCheck); ok && skipDepCheck.SkipApexAllowedDependenciesCheck() {
966			return !externalDep
967		}
968
969		if info, exists := depInfos[to.Name()]; exists {
970			if !android.InList(from.Name(), info.From) {
971				info.From = append(info.From, from.Name())
972			}
973			info.IsExternal = info.IsExternal && externalDep
974			depInfos[to.Name()] = info
975		} else {
976			toMinSdkVersion := "(no version)"
977			if m, ok := to.(interface {
978				MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec
979			}); ok {
980				if v := m.MinSdkVersion(ctx); !v.ApiLevel.IsNone() {
981					toMinSdkVersion = v.ApiLevel.String()
982				}
983			} else if m, ok := to.(interface{ MinSdkVersion() string }); ok {
984				// TODO(b/175678607) eliminate the use of MinSdkVersion returning
985				// string
986				if v := m.MinSdkVersion(); v != "" {
987					toMinSdkVersion = v
988				}
989			}
990			depInfos[to.Name()] = android.ApexModuleDepInfo{
991				To:            to.Name(),
992				From:          []string{from.Name()},
993				IsExternal:    externalDep,
994				MinSdkVersion: toMinSdkVersion,
995			}
996		}
997
998		// As soon as the dependency graph crosses the APEX boundary, don't go further.
999		return !externalDep
1000	})
1001
1002	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(ctx).Raw, depInfos)
1003
1004	ctx.Build(pctx, android.BuildParams{
1005		Rule:   android.Phony,
1006		Output: android.PathForPhony(ctx, a.Name()+"-deps-info"),
1007		Inputs: []android.Path{
1008			a.ApexBundleDepsInfo.FullListPath(),
1009			a.ApexBundleDepsInfo.FlatListPath(),
1010		},
1011	})
1012}
1013
1014func (a *apexBundle) buildLintReports(ctx android.ModuleContext) {
1015	depSetsBuilder := java.NewLintDepSetBuilder()
1016	for _, fi := range a.filesInfo {
1017		depSetsBuilder.Transitive(fi.lintDepSets)
1018	}
1019
1020	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
1021}
1022
1023func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
1024	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
1025	var executablePaths []string // this also includes dirs
1026	var appSetDirs []string
1027	appSetFiles := make(map[string]android.Path)
1028	for _, f := range a.filesInfo {
1029		pathInApex := f.path()
1030		if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") {
1031			executablePaths = append(executablePaths, pathInApex)
1032			for _, d := range f.dataPaths {
1033				readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel()))
1034			}
1035			for _, s := range f.symlinks {
1036				executablePaths = append(executablePaths, filepath.Join(f.installDir, s))
1037			}
1038		} else if f.class == appSet {
1039			appSetDirs = append(appSetDirs, f.installDir)
1040			appSetFiles[f.installDir] = f.builtFile
1041		} else {
1042			readOnlyPaths = append(readOnlyPaths, pathInApex)
1043		}
1044		dir := f.installDir
1045		for !android.InList(dir, executablePaths) && dir != "" {
1046			executablePaths = append(executablePaths, dir)
1047			dir, _ = filepath.Split(dir) // move up to the parent
1048			if len(dir) > 0 {
1049				// remove trailing slash
1050				dir = dir[:len(dir)-1]
1051			}
1052		}
1053	}
1054	sort.Strings(readOnlyPaths)
1055	sort.Strings(executablePaths)
1056	sort.Strings(appSetDirs)
1057
1058	cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config")
1059	builder := android.NewRuleBuilder(pctx, ctx)
1060	cmd := builder.Command()
1061	cmd.Text("(")
1062	cmd.Text("echo '/ 1000 1000 0755';")
1063	for _, p := range readOnlyPaths {
1064		cmd.Textf("echo '/%s 1000 1000 0644';", p)
1065	}
1066	for _, p := range executablePaths {
1067		cmd.Textf("echo '/%s 0 2000 0755';", p)
1068	}
1069	for _, dir := range appSetDirs {
1070		cmd.Textf("echo '/%s 0 2000 0755';", dir)
1071		file := appSetFiles[dir]
1072		cmd.Text("zipinfo -1").Input(file).Textf(`| sed "s:\(.*\):/%s/\1 1000 1000 0644:";`, dir)
1073	}
1074	// Custom fs_config is "appended" to the last so that entries from the file are preferred
1075	// over default ones set above.
1076	if a.properties.Canned_fs_config != nil {
1077		cmd.Text("cat").Input(android.PathForModuleSrc(ctx, *a.properties.Canned_fs_config))
1078	}
1079	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
1080	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
1081
1082	return cannedFsConfig.OutputPath
1083}
1084