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