diff options
Diffstat (limited to 'apex/builder.go')
-rw-r--r-- | apex/builder.go | 525 |
1 files changed, 376 insertions, 149 deletions
diff --git a/apex/builder.go b/apex/builder.go index 8573dc0d7..d2e6ad8d5 100644 --- a/apex/builder.go +++ b/apex/builder.go @@ -17,6 +17,7 @@ package apex import ( "encoding/json" "fmt" + "path" "path/filepath" "runtime" "sort" @@ -36,10 +37,11 @@ var ( func init() { pctx.Import("android/soong/android") + pctx.Import("android/soong/cc/config") pctx.Import("android/soong/java") pctx.HostBinToolVariable("apexer", "apexer") // ART minimal builds (using the master-art manifest) do not have the "frameworks/base" - // projects, and hence cannot built 'aapt2'. Use the SDK prebuilt instead. + // projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead. hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) { pctx.VariableFunc(name, func(ctx android.PackageVarContext) string { if !ctx.Config().FrameworksBaseDirExists(ctx) { @@ -62,6 +64,10 @@ func init() { pctx.HostBinToolVariable("jsonmodify", "jsonmodify") pctx.HostBinToolVariable("conv_apex_manifest", "conv_apex_manifest") pctx.HostBinToolVariable("extract_apks", "extract_apks") + pctx.HostBinToolVariable("make_f2fs", "make_f2fs") + pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs") + pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool") + pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh") } var ( @@ -115,12 +121,12 @@ var ( `--payload_type image ` + `--key ${key} ${opt_flags} ${image_dir} ${out} `, CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}", - "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", + "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${soong_zip}", "${zipalign}", "${aapt2}", "prebuilts/sdk/current/public/android.jar"}, Rspfile: "${out}.copy_commands", RspfileContent: "${copy_commands}", Description: "APEX ${image_dir} => ${out}", - }, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key", "opt_flags", "manifest") + }, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key", "opt_flags", "manifest", "payload_fs_type") zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{ Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` + @@ -172,26 +178,49 @@ var ( `exit 1); touch ${out}`, Description: "Diff ${image_content_file} and ${allowed_files_file}", }, "image_content_file", "allowed_files_file", "apex_module_name") + + generateAPIsUsedbyApexRule = pctx.StaticRule("generateAPIsUsedbyApexRule", blueprint.RuleParams{ + Command: "$genNdkUsedbyApexPath ${image_dir} ${readelf} ${out}", + CommandDeps: []string{"${genNdkUsedbyApexPath}"}, + Description: "Generate symbol list used by Apex", + }, "image_dir", "readelf") + + // Don't add more rules here. Consider using android.NewRuleBuilder instead. ) +// buildManifest creates buile rules to modify the input apex_manifest.json to add information +// gathered by the build system such as provided/required native libraries. Two output files having +// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and +// a.manifest.PbOut is protobuf format for R+ devices. +// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) { - manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json")) - - manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json") + src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json")) - // put dependency({provide|require}NativeLibs) in apex_manifest.json + // Put dependency({provide|require}NativeLibs) in apex_manifest.json provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs) requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs)) - // apex name can be overridden + // APEX name can be overridden optCommands := []string{} if a.properties.Apex_name != nil { optCommands = append(optCommands, "-v name "+*a.properties.Apex_name) } + // Collect jniLibs. Notice that a.filesInfo is already sorted + var jniLibs []string + for _, fi := range a.filesInfo { + if fi.isJniLib && !android.InList(fi.stem(), jniLibs) { + jniLibs = append(jniLibs, fi.stem()) + } + } + if len(jniLibs) > 0 { + optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " ")) + } + + manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json") ctx.Build(pctx, android.BuildParams{ Rule: apexManifestRule, - Input: manifestSrc, + Input: src, Output: manifestJsonFullOut, Args: map[string]string{ "provideNativeLibs": strings.Join(provideNativeLibs, " "), @@ -200,9 +229,10 @@ func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, }, }) - if a.minSdkVersion(ctx) == android.SdkVersion_Android10 { - // b/143654022 Q apexd can't understand newly added keys in apex_manifest.json - // prepare stripped-down version so that APEX modules built from R+ can be installed to Q + // b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare + // stripped-down version so that APEX modules built from R+ can be installed to Q + minSdkVersion := a.minSdkVersion(ctx) + if minSdkVersion.EqualTo(android.SdkVersion_Android10) { a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json") ctx.Build(pctx, android.BuildParams{ Rule: stripApexManifestRule, @@ -211,7 +241,7 @@ func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, }) } - // from R+, protobuf binary format (.pb) is the standard format for apex_manifest + // From R+, protobuf binary format (.pb) is the standard format for apex_manifest a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb") ctx.Build(pctx, android.BuildParams{ Rule: pbApexManifestRule, @@ -220,23 +250,82 @@ func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, }) } +// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts +// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from +// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly +// labeled as system_file. +func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath { + var fileContexts android.Path + if a.properties.File_contexts == nil { + fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts") + } else { + fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts) + } + if a.Platform() { + if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched { + ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts) + } + } + if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() { + ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String()) + } + + output := android.PathForModuleOut(ctx, "file_contexts") + rule := android.NewRuleBuilder(pctx, ctx) + + switch a.properties.ApexType { + case imageApex: + // remove old file + rule.Command().Text("rm").FlagWithOutput("-f ", output) + // copy file_contexts + rule.Command().Text("cat").Input(fileContexts).Text(">>").Output(output) + // new line + rule.Command().Text("echo").Text(">>").Output(output) + // force-label /apex_manifest.pb and / as system_file so that apexd can read them + rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output) + rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output) + case flattenedApex: + // For flattened apexes, install path should be prepended. + // File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS + // so that it can be merged into file_contexts.bin + apexPath := android.InstallPathToOnDevicePath(ctx, a.installDir.Join(ctx, a.Name())) + apexPath = strings.ReplaceAll(apexPath, ".", `\\.`) + // remove old file + rule.Command().Text("rm").FlagWithOutput("-f ", output) + // copy file_contexts + rule.Command().Text("awk").Text(`'/object_r/{printf("` + apexPath + `%s\n", $0)}'`).Input(fileContexts).Text(">").Output(output) + // new line + rule.Command().Text("echo").Text(">>").Output(output) + // force-label /apex_manifest.pb and / as system_file so that apexd can read them + rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output) + rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output) + default: + panic(fmt.Errorf("unsupported type %v", a.properties.ApexType)) + } + + rule.Build("file_contexts."+a.Name(), "Generate file_contexts") + return output.OutputPath +} + +// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that +// contributes to this APEX. The notice files are merged into a big notice file. func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs { var noticeFiles android.Paths - a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool { + a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool { if externalDep { // As soon as the dependency graph crosses the APEX boundary, don't go further. return false } - - notice := to.NoticeFile() - if notice.Valid() { - noticeFiles = append(noticeFiles, notice.Path()) - } - + noticeFiles = append(noticeFiles, to.NoticeFiles()...) return true }) + // TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this. + for _, fi := range a.filesInfo { + noticeFiles = append(noticeFiles, fi.noticeFiles...) + } + if len(noticeFiles) == 0 { return android.NoticeOutputs{} } @@ -244,19 +333,24 @@ func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName st return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles)) } +// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of +// files included in this APEX is shown. The text file is dist'ed so that people can see what's +// included in the APEX without actually downloading and extracting it. func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath { output := android.PathForModuleOut(ctx, "installed-files.txt") - rule := android.NewRuleBuilder() + rule := android.NewRuleBuilder(pctx, ctx) rule.Command(). Implicit(builtApex). Text("(cd " + imageDir.String() + " ; "). Text("find . \\( -type f -o -type l \\) -printf \"%s %p\\n\") "). Text(" | sort -nr > "). Output(output) - rule.Build(pctx, ctx, "installed-files."+a.Name(), "Installed files") + rule.Build("installed-files."+a.Name(), "Installed files") return output.OutputPath } +// buildBundleConfig creates a build rule for the bundle config file that will control the bundle +// creation process. func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath { output := android.PathForModuleOut(ctx, "bundle_config.json") @@ -278,8 +372,8 @@ func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Output "apex_manifest.*", } - // collect the manifest names and paths of android apps - // if their manifest names are overridden + // Collect the manifest names and paths of android apps if their manifest names are + // overridden. for _, fi := range a.filesInfo { if fi.class != app && fi.class != appSet { continue @@ -290,7 +384,7 @@ func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Output config.Apex_config.Apex_embedded_apk_config, ApkConfig{ Package_name: packageName, - Apk_path: fi.Path(), + Apk_path: fi.path(), }) } } @@ -300,45 +394,39 @@ func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Output panic(fmt.Errorf("error while marshalling to %q: %#v", output, err)) } - ctx.Build(pctx, android.BuildParams{ - Rule: android.WriteFile, - Output: output, - Description: "Bundle Config " + output.String(), - Args: map[string]string{ - "content": string(j), - }, - }) + android.WriteFileRule(ctx, output, string(j)) return output.OutputPath } +// buildUnflattendApex creates build rules to build an APEX using apexer. func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { - var abis []string - for _, target := range ctx.MultiTargets() { - if len(target.Arch.Abi) > 0 { - abis = append(abis, target.Arch.Abi[0]) - } - } - - abis = android.FirstUniqueStrings(abis) - apexType := a.properties.ApexType suffix := apexType.suffix() - var implicitInputs []android.Path - unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned") - // TODO(jiyong): construct the copy rules using RuleBuilder + //////////////////////////////////////////////////////////////////////////////////////////// + // Step 1: copy built files to appropriate directories under the image directory + + imageDir := android.PathForModuleOut(ctx, "image"+suffix) + + // TODO(jiyong): use the RuleBuilder var copyCommands []string + var implicitInputs []android.Path for _, fi := range a.filesInfo { - destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.Path()).String() + destPath := imageDir.Join(ctx, fi.path()).String() + + // Prepare the destination path destPathDir := filepath.Dir(destPath) if fi.class == appSet { copyCommands = append(copyCommands, "rm -rf "+destPathDir) } copyCommands = append(copyCommands, "mkdir -p "+destPathDir) - if a.linkToSystemLib && fi.transitiveDep && fi.AvailableToPlatform() { + + // Copy the built file to the directory. But if the symlink optimization is turned + // on, place a symlink to the corresponding file in /system partition instead. + if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() { // TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here - pathOnDevice := filepath.Join("/system", fi.Path()) + pathOnDevice := filepath.Join("/system", fi.path()) copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath) } else { if fi.class == appSet { @@ -349,27 +437,54 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { } implicitInputs = append(implicitInputs, fi.builtFile) } - // create additional symlinks pointing the file inside the APEX - for _, symlinkPath := range fi.SymlinkPaths() { - symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String() + + // Create additional symlinks pointing the file inside the APEX (if any). Note that + // this is independent from the symlink optimization. + for _, symlinkPath := range fi.symlinkPaths() { + symlinkDest := imageDir.Join(ctx, symlinkPath).String() copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest) } - } - // TODO(jiyong): use RuleBuilder - var emitCommands []string - imageContentFile := android.PathForModuleOut(ctx, "content.txt") - emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String()) - if a.minSdkVersion(ctx) == android.SdkVersion_Android10 { - emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String()) - } - for _, fi := range a.filesInfo { - emitCommands = append(emitCommands, "echo './"+fi.Path()+"' >> "+imageContentFile.String()) + // Copy the test files (if any) + for _, d := range fi.dataPaths { + // TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible + relPath := d.SrcPath.Rel() + dataPath := d.SrcPath.String() + if !strings.HasSuffix(dataPath, relPath) { + panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath)) + } + + dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String() + + copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest) + implicitInputs = append(implicitInputs, d.SrcPath) + } } - emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String()) implicitInputs = append(implicitInputs, a.manifestPbOut) + //////////////////////////////////////////////////////////////////////////////////////////// + // Step 1.a: Write the list of files in this APEX to a txt file and compare it against + // the allowed list given via the allowed_files property. Build fails when the two lists + // differ. + // + // TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems + // to be using this at this moment. Furthermore, this looks very similar to what + // buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't + // hurt readability. + // TODO(jiyong): use RuleBuilder if a.overridableProperties.Allowed_files != nil { + // Build content.txt + var emitCommands []string + imageContentFile := android.PathForModuleOut(ctx, "content.txt") + emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String()) + minSdkVersion := a.minSdkVersion(ctx) + if minSdkVersion.EqualTo(android.SdkVersion_Android10) { + emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String()) + } + for _, fi := range a.filesInfo { + emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String()) + } + emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String()) ctx.Build(pctx, android.BuildParams{ Rule: emitApexContentRule, Implicits: implicitInputs, @@ -380,8 +495,9 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { }, }) implicitInputs = append(implicitInputs, imageContentFile) - allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files)) + // Compare content.txt against allowed_files. + allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files)) phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output") ctx.Build(pctx, android.BuildParams{ Rule: diffApexContentRule, @@ -394,24 +510,32 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { "apex_module_name": a.Name(), }, }) - implicitInputs = append(implicitInputs, phonyOutput) } + unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned") outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String() prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin") - imageDir := android.PathForModuleOut(ctx, "image"+suffix) + // Figure out if need to compress apex. + compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.properties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps() if apexType == imageApex { - // files and dirs that will be created in APEX + //////////////////////////////////////////////////////////////////////////////////// + // Step 2: create canned_fs_config which encodes filemode,uid,gid of each files + // in this APEX. The file will be used by apexer in later steps. + // TODO(jiyong): make this as a function + // TODO(jiyong): use the RuleBuilder var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"} var executablePaths []string // this also includes dirs var extractedAppSetPaths android.Paths var extractedAppSetDirs []string for _, f := range a.filesInfo { - pathInApex := f.Path() + pathInApex := f.path() if f.installDir == "bin" || strings.HasPrefix(f.installDir, "bin/") { executablePaths = append(executablePaths, pathInApex) + for _, d := range f.dataPaths { + readOnlyPaths = append(readOnlyPaths, filepath.Join(f.installDir, d.RelativeInstallPath, d.SrcPath.Rel())) + } for _, s := range f.symlinks { executablePaths = append(executablePaths, filepath.Join(f.installDir, s)) } @@ -445,12 +569,18 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { "apk_paths": strings.Join(extractedAppSetDirs, " "), }, }) + implicitInputs = append(implicitInputs, cannedFsConfig) + //////////////////////////////////////////////////////////////////////////////////// + // Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX. + // TODO(jiyong): use the RuleBuilder optFlags := []string{} - // Additional implicit inputs. - implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file) - optFlags = append(optFlags, "--pubkey "+a.public_key_file.String()) + fileContexts := a.buildFileContexts(ctx) + implicitInputs = append(implicitInputs, fileContexts) + + implicitInputs = append(implicitInputs, a.privateKeyFile, a.publicKeyFile) + optFlags = append(optFlags, "--pubkey "+a.publicKeyFile.String()) manifestPackageName := a.getOverrideManifestPackageName(ctx) if manifestPackageName != "" { @@ -463,13 +593,19 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String()) } - targetSdkVersion := ctx.Config().DefaultAppTargetSdk() - // TODO(b/157078772): propagate min_sdk_version to apexer. - minSdkVersion := ctx.Config().DefaultAppTargetSdk() + // Determine target/min sdk version from the context + // TODO(jiyong): make this as a function + moduleMinSdkVersion := a.minSdkVersion(ctx) + minSdkVersion := moduleMinSdkVersion.String() - if a.minSdkVersion(ctx) == android.SdkVersion_Android10 { - minSdkVersion = strconv.Itoa(a.minSdkVersion(ctx)) + // bundletool doesn't understand what "current" is. We need to transform it to + // codename + if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() { + minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String() } + // apex module doesn't have a concept of target_sdk_version, hence for the time + // being targetSdkVersion == default targetSdkVersion of the branch. + targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt()) if java.UseApiFingerprint(ctx) { targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String()) @@ -493,11 +629,7 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { optFlags = append(optFlags, "--assets_dir "+filepath.Dir(a.mergedNotices.HtmlGzOutput.String())) } - if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && ctx.ModuleDir() != "system/apex/shim/build" && a.testOnlyShouldSkipHashtreeGeneration() { - ctx.PropertyErrorf("test_only_no_hashtree", "not available") - return - } - if a.minSdkVersion(ctx) > android.SdkVersion_Android10 || a.testOnlyShouldSkipHashtreeGeneration() { + if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled { // Apexes which are supposed to be installed in builtin dirs(/system, etc) // don't need hashtree for activation. Therefore, by removing hashtree from // apex bundle (filesystem image in it, to be specific), we can save storage. @@ -509,16 +641,18 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { } if a.properties.Apex_name != nil { - // If apex_name is set, apexer can skip checking if key name matches with apex name. - // Note that apex_manifest is also mended. + // If apex_name is set, apexer can skip checking if key name matches with + // apex name. Note that apex_manifest is also mended. optFlags = append(optFlags, "--do_not_check_keyname") } - if a.minSdkVersion(ctx) == android.SdkVersion_Android10 { + if moduleMinSdkVersion == android.SdkVersion_Android10 { implicitInputs = append(implicitInputs, a.manifestJsonOut) optFlags = append(optFlags, "--manifest_json "+a.manifestJsonOut.String()) } + optFlags = append(optFlags, "--payload_fs_type "+a.payloadFsType.string()) + ctx.Build(pctx, android.BuildParams{ Rule: apexRule, Implicits: implicitInputs, @@ -529,13 +663,14 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { "image_dir": imageDir.String(), "copy_commands": strings.Join(copyCommands, " && "), "manifest": a.manifestPbOut.String(), - "file_contexts": a.fileContexts.String(), + "file_contexts": fileContexts.String(), "canned_fs_config": cannedFsConfig.String(), - "key": a.private_key_file.String(), + "key": a.privateKeyFile.String(), "opt_flags": strings.Join(optFlags, " "), }, }) + // TODO(jiyong): make the two rules below as separate functions apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix) bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip") a.bundleModuleFile = bundleModuleFile @@ -547,8 +682,50 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { Description: "apex proto convert", }) + implicitInputs = append(implicitInputs, unsignedOutputFile) + + // Run coverage analysis + apisUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.txt") + ctx.Build(pctx, android.BuildParams{ + Rule: generateAPIsUsedbyApexRule, + Implicits: implicitInputs, + Description: "coverage", + Output: apisUsedbyOutputFile, + Args: map[string]string{ + "image_dir": imageDir.String(), + "readelf": "${config.ClangBin}/llvm-readelf", + }, + }) + a.apisUsedByModuleFile = apisUsedbyOutputFile + + var libNames []string + for _, f := range a.filesInfo { + if f.class == nativeSharedLib { + libNames = append(libNames, f.stem()) + } + } + apisBackedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_backing.txt") + ndkLibraryList := android.PathForSource(ctx, "system/core/rootdir/etc/public.libraries.android.txt") + rule := android.NewRuleBuilder(pctx, ctx) + rule.Command(). + Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")). + Output(apisBackedbyOutputFile). + Input(ndkLibraryList). + Flags(libNames) + rule.Build("ndk_backedby_list", "Generate API libraries backed by Apex") + a.apisBackedByModuleFile = apisBackedbyOutputFile + bundleConfig := a.buildBundleConfig(ctx) + var abis []string + for _, target := range ctx.MultiTargets() { + if len(target.Arch.Abi) > 0 { + abis = append(abis, target.Arch.Abi[0]) + } + } + + abis = android.FirstUniqueStrings(abis) + ctx.Build(pctx, android.BuildParams{ Rule: apexBundleRule, Input: apexProtoFile, @@ -560,7 +737,7 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { "config": bundleConfig.String(), }, }) - } else { + } else { // zipApex ctx.Build(pctx, android.BuildParams{ Rule: zipApexRule, Implicits: implicitInputs, @@ -575,99 +752,130 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { }) } - a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix) + //////////////////////////////////////////////////////////////////////////////////// + // Step 4: Sign the APEX using signapk + signedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix) + + pem, key := a.getCertificateAndPrivateKey(ctx) rule := java.Signapk args := map[string]string{ - "certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(), + "certificates": pem.String() + " " + key.String(), "flags": "-a 4096", //alignment } - implicits := android.Paths{ - a.container_certificate_file, - a.container_private_key_file, - } - if ctx.Config().IsEnvTrue("RBE_SIGNAPK") { + implicits := android.Paths{pem, key} + if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") { rule = java.SignapkRE args["implicits"] = strings.Join(implicits.Strings(), ",") - args["outCommaList"] = a.outputFile.String() + args["outCommaList"] = signedOutputFile.String() } ctx.Build(pctx, android.BuildParams{ Rule: rule, Description: "signapk", - Output: a.outputFile, + Output: signedOutputFile, Input: unsignedOutputFile, Implicits: implicits, Args: args, }) + a.outputFile = signedOutputFile + + if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() { + ctx.PropertyErrorf("test_only_force_compression", "not available") + return + } + + if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) { + a.isCompressed = true + unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex.unsigned") + + compressRule := android.NewRuleBuilder(pctx, ctx) + compressRule.Command(). + Text("rm"). + FlagWithOutput("-f ", unsignedCompressedOutputFile) + compressRule.Command(). + BuiltTool("apex_compression_tool"). + Flag("compress"). + FlagWithArg("--apex_compression_tool ", outHostBinDir+":"+prebuiltSdkToolsBinDir). + FlagWithInput("--input ", signedOutputFile). + FlagWithOutput("--output ", unsignedCompressedOutputFile) + compressRule.Build("compressRule", "Generate unsigned compressed APEX file") + + signedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+".capex") + if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") { + args["outCommaList"] = signedCompressedOutputFile.String() + } + ctx.Build(pctx, android.BuildParams{ + Rule: rule, + Description: "sign compressedApex", + Output: signedCompressedOutputFile, + Input: unsignedCompressedOutputFile, + Implicits: implicits, + Args: args, + }) + a.outputFile = signedCompressedOutputFile + } // Install to $OUT/soong/{target,host}/.../apex if a.installable() { ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile) } - a.buildFilesInfo(ctx) // installed-files.txt is dist'ed a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir) } -func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) { - // Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it - // reply true to `InstallBypassMake()` (thus making the call - // `android.PathForModuleInstall` below use `android.pathForInstallInMakeDir` - // instead of `android.PathForOutput`) to return the correct path to the flattened - // APEX (as its contents is installed by Make, not Soong). - factx := flattenedApexContext{ctx} - apexBundleName := a.Name() - a.outputFile = android.PathForModuleInstall(&factx, "apex", apexBundleName) +// Context "decorator", overriding the InstallBypassMake method to always reply `true`. +type flattenedApexContext struct { + android.ModuleContext +} - if a.installable() { - installPath := android.PathForModuleInstall(ctx, "apex", apexBundleName) - devicePath := android.InstallPathToOnDevicePath(ctx, installPath) - addFlattenedFileContextsInfos(ctx, apexBundleName+":"+devicePath+":"+a.fileContexts.String()) - } - a.buildFilesInfo(ctx) +func (c *flattenedApexContext) InstallBypassMake() bool { + return true } -func (a *apexBundle) setCertificateAndPrivateKey(ctx android.ModuleContext) { - if a.container_certificate_file == nil { - cert := String(a.properties.Certificate) - if cert == "" { - pem, key := ctx.Config().DefaultAppCertificate(ctx) - a.container_certificate_file = pem - a.container_private_key_file = key - } else { - defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) - a.container_certificate_file = defaultDir.Join(ctx, cert+".x509.pem") - a.container_private_key_file = defaultDir.Join(ctx, cert+".pk8") +// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a +// single output file. It is a phony target for all the files under /system/apex/<name> directory. +// This function creates the installation rules for the files. +func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) { + bundleName := a.Name() + if a.installable() { + for _, fi := range a.filesInfo { + dir := filepath.Join("apex", bundleName, fi.installDir) + target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile) + for _, sym := range fi.symlinks { + ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target) + } } } + + a.fileContexts = a.buildFileContexts(ctx) + + // Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true + // to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use + // `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the + // correct path to the flattened APEX (as its contents is installed by Make, not Soong). + // TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use + // it and it actually points to a path that can never be built. Remove this. + factx := flattenedApexContext{ctx} + a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName) } -func (a *apexBundle) buildFilesInfo(ctx android.ModuleContext) { - if a.installable() { - // For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along - // with other ordinary files. - a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil)) +// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign +// the zip container of this APEX. See the description of the 'certificate' property for how +// the cert and the private key are found. +func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) { + if a.containerCertificateFile != nil { + return a.containerCertificateFile, a.containerPrivateKeyFile + } - // rename to apex_pubkey - copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey") - ctx.Build(pctx, android.BuildParams{ - Rule: android.Cp, - Input: a.public_key_file, - Output: copiedPubkey, - }) - a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil)) - - if a.properties.ApexType == flattenedApex { - apexBundleName := a.Name() - for _, fi := range a.filesInfo { - dir := filepath.Join("apex", apexBundleName, fi.installDir) - target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.Stem(), fi.builtFile) - for _, sym := range fi.symlinks { - ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target) - } - } - } + cert := String(a.overridableProperties.Certificate) + if cert == "" { + return ctx.Config().DefaultAppCertificate(ctx) } + + defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) + pem = defaultDir.Join(ctx, cert+".x509.pem") + key = defaultDir.Join(ctx, cert+".pk8") + return pem, key } func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string { @@ -708,13 +916,25 @@ func (a *apexBundle) buildApexDependencyInfo(ctx android.ModuleContext) { } depInfos := android.DepNameToDepInfoMap{} - a.walkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool { + a.WalkPayloadDeps(ctx, func(ctx android.ModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool { if from.Name() == to.Name() { // This can happen for cc.reuseObjTag. We are not interested in tracking this. // As soon as the dependency graph crosses the APEX boundary, don't go further. return !externalDep } + // Skip dependencies that are only available to APEXes; they are developed with updatability + // in mind and don't need manual approval. + if to.(android.ApexModule).NotAvailableForPlatform() { + return !externalDep + } + + depTag := ctx.OtherModuleDependencyTag(to) + // Check to see if dependency been marked to skip the dependency check + if skipDepCheck, ok := depTag.(android.SkipApexAllowedDependenciesCheck); ok && skipDepCheck.SkipApexAllowedDependenciesCheck() { + return !externalDep + } + if info, exists := depInfos[to.Name()]; exists { if !android.InList(from.Name(), info.From) { info.From = append(info.From, from.Name()) @@ -723,12 +943,19 @@ func (a *apexBundle) buildApexDependencyInfo(ctx android.ModuleContext) { depInfos[to.Name()] = info } else { toMinSdkVersion := "(no version)" - if m, ok := to.(interface{ MinSdkVersion() string }); ok { + if m, ok := to.(interface { + MinSdkVersion(ctx android.EarlyModuleContext) android.SdkSpec + }); ok { + if v := m.MinSdkVersion(ctx); !v.ApiLevel.IsNone() { + toMinSdkVersion = v.ApiLevel.String() + } + } else if m, ok := to.(interface{ MinSdkVersion() string }); ok { + // TODO(b/175678607) eliminate the use of MinSdkVersion returning + // string if v := m.MinSdkVersion(); v != "" { toMinSdkVersion = v } } - depInfos[to.Name()] = android.ApexModuleDepInfo{ To: to.Name(), From: []string{from.Name()}, |