// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package apex import ( "encoding/json" "fmt" "path" "path/filepath" "runtime" "sort" "strconv" "strings" "android/soong/android" "android/soong/java" "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) var ( pctx = android.NewPackageContext("android/apex") ) func init() { pctx.Import("android/soong/android") pctx.Import("android/soong/cc/config") pctx.Import("android/soong/java") pctx.HostBinToolVariable("apexer", "apexer") pctx.HostBinToolVariable("apexer_with_DCLA_preprocessing", "apexer_with_DCLA_preprocessing") pctx.HostBinToolVariable("apexer_with_trim_preprocessing", "apexer_with_trim_preprocessing") // ART minimal builds (using the master-art manifest) do not have the "frameworks/base" // 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) { return filepath.Join(prebuiltDir, runtime.GOOS, "bin", tool) } else { return ctx.Config().HostToolPath(ctx, tool).String() } }) } hostBinToolVariableWithPrebuilt("aapt2", "prebuilts/sdk/tools", "aapt2") pctx.HostBinToolVariable("avbtool", "avbtool") pctx.HostBinToolVariable("e2fsdroid", "e2fsdroid") pctx.HostBinToolVariable("merge_zips", "merge_zips") pctx.HostBinToolVariable("mke2fs", "mke2fs") pctx.HostBinToolVariable("resize2fs", "resize2fs") pctx.HostBinToolVariable("sefcontext_compile", "sefcontext_compile") pctx.HostBinToolVariable("soong_zip", "soong_zip") pctx.HostBinToolVariable("zip2zip", "zip2zip") pctx.HostBinToolVariable("zipalign", "zipalign") 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("make_erofs", "make_erofs") pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool") pctx.HostBinToolVariable("dexdeps", "dexdeps") pctx.HostBinToolVariable("apex_sepolicy_tests", "apex_sepolicy_tests") pctx.HostBinToolVariable("deapexer", "deapexer") pctx.HostBinToolVariable("debugfs_static", "debugfs_static") pctx.SourcePathVariable("genNdkUsedbyApexPath", "build/soong/scripts/gen_ndk_usedby_apex.sh") pctx.HostBinToolVariable("conv_linker_config", "conv_linker_config") pctx.HostBinToolVariable("assemble_vintf", "assemble_vintf") } var ( apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{ Command: `rm -f $out && ${jsonmodify} $in ` + `-a provideNativeLibs ${provideNativeLibs} ` + `-a requireNativeLibs ${requireNativeLibs} ` + `-se version 0 ${default_version} ` + `${opt} ` + `-o $out`, CommandDeps: []string{"${jsonmodify}"}, Description: "prepare ${out}", }, "provideNativeLibs", "requireNativeLibs", "default_version", "opt") stripApexManifestRule = pctx.StaticRule("stripApexManifestRule", blueprint.RuleParams{ Command: `rm -f $out && ${conv_apex_manifest} strip $in -o $out`, CommandDeps: []string{"${conv_apex_manifest}"}, Description: "strip ${in}=>${out}", }) pbApexManifestRule = pctx.StaticRule("pbApexManifestRule", blueprint.RuleParams{ Command: `rm -f $out && ${conv_apex_manifest} proto $in -o $out`, CommandDeps: []string{"${conv_apex_manifest}"}, Description: "convert ${in}=>${out}", }) // TODO(b/113233103): make sure that file_contexts is as expected, i.e., validate // against the binary policy using sefcontext_compiler -p . // TODO(b/114327326): automate the generation of file_contexts apexRule = pctx.StaticRule("apexRule", blueprint.RuleParams{ Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` + `(. ${out}.copy_commands) && ` + `APEXER_TOOL_PATH=${tool_path} ` + `${apexer} --force --manifest ${manifest} ` + `--file_contexts ${file_contexts} ` + `--canned_fs_config ${canned_fs_config} ` + `--include_build_info ` + `--payload_type image ` + `--key ${key} ${opt_flags} ${image_dir} ${out} `, CommandDeps: []string{"${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}", "${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") DCLAApexRule = pctx.StaticRule("DCLAApexRule", blueprint.RuleParams{ Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` + `(. ${out}.copy_commands) && ` + `APEXER_TOOL_PATH=${tool_path} ` + `${apexer_with_DCLA_preprocessing} ` + `--apexer ${apexer} ` + `--canned_fs_config ${canned_fs_config} ` + `${image_dir} ` + `${out} ` + `-- ` + `--include_build_info ` + `--force ` + `--payload_type image ` + `--key ${key} ` + `--file_contexts ${file_contexts} ` + `--manifest ${manifest} ` + `${opt_flags} `, CommandDeps: []string{"${apexer_with_DCLA_preprocessing}", "${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}", "${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", "is_DCLA") TrimmedApexRule = pctx.StaticRule("TrimmedApexRule", blueprint.RuleParams{ Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` + `(. ${out}.copy_commands) && ` + `APEXER_TOOL_PATH=${tool_path} ` + `${apexer_with_trim_preprocessing} ` + `--apexer ${apexer} ` + `--canned_fs_config ${canned_fs_config} ` + `--manifest ${manifest} ` + `--libs_to_trim ${libs_to_trim} ` + `${image_dir} ` + `${out} ` + `-- ` + `--include_build_info ` + `--force ` + `--payload_type image ` + `--key ${key} ` + `--file_contexts ${file_contexts} ` + `${opt_flags} `, CommandDeps: []string{"${apexer_with_trim_preprocessing}", "${apexer}", "${avbtool}", "${e2fsdroid}", "${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}", "${sload_f2fs}", "${make_erofs}", "${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", "libs_to_trim") apexProtoConvertRule = pctx.AndroidStaticRule("apexProtoConvertRule", blueprint.RuleParams{ Command: `${aapt2} convert --output-format proto $in -o $out`, CommandDeps: []string{"${aapt2}"}, }) apexBundleRule = pctx.StaticRule("apexBundleRule", blueprint.RuleParams{ Command: `${zip2zip} -i $in -o $out.base ` + `apex_payload.img:apex/${abi}.img ` + `apex_build_info.pb:apex/${abi}.build_info.pb ` + `apex_manifest.json:root/apex_manifest.json ` + `apex_manifest.pb:root/apex_manifest.pb ` + `AndroidManifest.xml:manifest/AndroidManifest.xml ` + `assets/NOTICE.html.gz:assets/NOTICE.html.gz &&` + `${soong_zip} -o $out.config -C $$(dirname ${config}) -f ${config} && ` + `${merge_zips} $out $out.base $out.config`, CommandDeps: []string{"${zip2zip}", "${soong_zip}", "${merge_zips}"}, Description: "app bundle", }, "abi", "config") diffApexContentRule = pctx.StaticRule("diffApexContentRule", blueprint.RuleParams{ Command: `diff --unchanged-group-format='' \` + `--changed-group-format='%<' \` + `${image_content_file} ${allowed_files_file} || (` + `echo -e "New unexpected files were added to ${apex_module_name}." ` + ` "To fix the build run following command:" && ` + `echo "system/apex/tools/update_allowed_list.sh ${allowed_files_file} ${image_content_file}" && ` + `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") apexSepolicyTestsRule = pctx.StaticRule("apexSepolicyTestsRule", blueprint.RuleParams{ Command: `${deapexer} --debugfs_path ${debugfs_static} list -Z ${in} > ${out}.fc` + ` && ${apex_sepolicy_tests} -f ${out}.fc && touch ${out}`, CommandDeps: []string{"${apex_sepolicy_tests}", "${deapexer}", "${debugfs_static}"}, Description: "run apex_sepolicy_tests", }) apexLinkerconfigValidationRule = pctx.StaticRule("apexLinkerconfigValidationRule", blueprint.RuleParams{ Command: `${conv_linker_config} validate --type apex ${image_dir} && touch ${out}`, CommandDeps: []string{"${conv_linker_config}"}, Description: "run apex_linkerconfig_validation", }, "image_dir") assembleVintfRule = pctx.StaticRule("assembleVintfRule", blueprint.RuleParams{ Command: `rm -f $out && VINTF_IGNORE_TARGET_FCM_VERSION=true ${assemble_vintf} -i $in -o $out`, CommandDeps: []string{"${assemble_vintf}"}, Description: "run assemble_vintf", }) ) // 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) { src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json")) // Put dependency({provide|require}NativeLibs) in apex_manifest.json provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs) requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs)) // VNDK APEX name is determined at runtime, so update "name" in apex_manifest optCommands := []string{} if a.vndkApex { apexName := vndkApexNamePrefix + a.vndkVersion(ctx.DeviceConfig()) optCommands = append(optCommands, "-v name "+apexName) } // 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, " ")) } if android.InList(":vndk", requireNativeLibs) { if _, vndkVersion := a.getImageVariationPair(ctx.DeviceConfig()); vndkVersion != "" { optCommands = append(optCommands, "-v vndkVersion "+vndkVersion) } } manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json") defaultVersion := android.DefaultUpdatableModuleVersion if a.properties.Variant_version != nil { defaultVersionInt, err := strconv.Atoi(defaultVersion) if err != nil { ctx.ModuleErrorf("expected DefaultUpdatableModuleVersion to be an int, but got %s", defaultVersion) } if defaultVersionInt%10 != 0 { ctx.ModuleErrorf("expected DefaultUpdatableModuleVersion to end in a zero, but got %s", defaultVersion) } variantVersion := []rune(*a.properties.Variant_version) if len(variantVersion) != 1 || variantVersion[0] < '0' || variantVersion[0] > '9' { ctx.PropertyErrorf("variant_version", "expected an integer between 0-9; got %s", *a.properties.Variant_version) } defaultVersionRunes := []rune(defaultVersion) defaultVersionRunes[len(defaultVersion)-1] = []rune(variantVersion)[0] defaultVersion = string(defaultVersionRunes) } if override := ctx.Config().Getenv("OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION"); override != "" { defaultVersion = override } ctx.Build(pctx, android.BuildParams{ Rule: apexManifestRule, Input: src, Output: manifestJsonFullOut, Args: map[string]string{ "provideNativeLibs": strings.Join(provideNativeLibs, " "), "requireNativeLibs": strings.Join(requireNativeLibs, " "), "default_version": defaultVersion, "opt": strings.Join(optCommands, " "), }, }) // 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, Input: manifestJsonFullOut, Output: a.manifestJsonOut, }) } // 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, Input: manifestJsonFullOut, Output: a.manifestPbOut, }) } // 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/-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 or vendor_apex_metadata_file. func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath { var fileContexts android.Path var fileContextsDir string if a.properties.File_contexts == nil { fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts") } else { if m, t := android.SrcIsModuleWithTag(*a.properties.File_contexts); m != "" { otherModule := android.GetModuleFromPathDep(ctx, m, t) fileContextsDir = ctx.OtherModuleDir(otherModule) } fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts) } if fileContextsDir == "" { fileContextsDir = filepath.Dir(fileContexts.String()) } fileContextsDir += string(filepath.Separator) if a.Platform() { if !strings.HasPrefix(fileContextsDir, "system/sepolicy/") { ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but found in %q", fileContextsDir) } } if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() { ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String()) } useFileContextsAsIs := proptools.Bool(a.properties.Use_file_contexts_as_is) output := android.PathForModuleOut(ctx, "file_contexts") rule := android.NewRuleBuilder(pctx, ctx) forceLabel := "u:object_r:system_file:s0" if a.SocSpecific() && !a.vndkApex { // APEX on /vendor should label ./ and ./apex_manifest.pb as vendor_apex_metadata_file. // The reason why we skip VNDK APEX is that aosp_{pixel device} targets install VNDK APEX on /vendor // even though VNDK APEX is supposed to be installed on /system. (See com.android.vndk.current.on_vendor) forceLabel = "u:object_r:vendor_apex_metadata_file:s0" } // 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) if !useFileContextsAsIs { // force-label /apex_manifest.pb and / rule.Command().Text("echo").Text("/apex_manifest\\\\.pb").Text(forceLabel).Text(">>").Output(output) rule.Command().Text("echo").Text("/").Text(forceLabel).Text(">>").Output(output) } rule.Build("file_contexts."+a.Name(), "Generate file_contexts") return output.OutputPath } // 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(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("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") type ApkConfig struct { Package_name string `json:"package_name"` Apk_path string `json:"path"` } config := struct { Compression struct { Uncompressed_glob []string `json:"uncompressed_glob"` } `json:"compression"` Apex_config struct { Apex_embedded_apk_config []ApkConfig `json:"apex_embedded_apk_config,omitempty"` } `json:"apex_config,omitempty"` }{} config.Compression.Uncompressed_glob = []string{ "apex_payload.img", "apex_manifest.*", } // 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 } packageName := fi.overriddenPackageName if packageName != "" { config.Apex_config.Apex_embedded_apk_config = append( config.Apex_config.Apex_embedded_apk_config, ApkConfig{ Package_name: packageName, Apk_path: fi.path(), }) } } j, err := json.Marshal(config) if err != nil { panic(fmt.Errorf("error while marshalling to %q: %#v", output, err)) } android.WriteFileRule(ctx, output, string(j)) return output.OutputPath } func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path { return java.ManifestFixer(ctx, androidManifestFile, java.ManifestFixerParams{ TestOnly: true, }) } func isVintfFragment(fi apexFile) bool { isVintfFragment, _ := path.Match("etc/vintf/*.xml", fi.path()) return isVintfFragment } func runAssembleVintf(ctx android.ModuleContext, vintfFragment android.Path) android.Path { processed := android.PathForModuleOut(ctx, "vintf", vintfFragment.Base()) ctx.Build(pctx, android.BuildParams{ Rule: assembleVintfRule, Input: vintfFragment, Output: processed, Description: "run assemble_vintf for VINTF in APEX", }) return processed } // buildApex creates build rules to build an APEX using apexer. func (a *apexBundle) buildApex(ctx android.ModuleContext) { suffix := imageApexSuffix apexName := a.BaseModuleName() //////////////////////////////////////////////////////////////////////////////////////////// // Step 1: copy built files to appropriate directories under the image directory imageDir := android.PathForModuleOut(ctx, "image"+suffix) installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable() // set of dependency module:location mappings installMapSet := make(map[string]bool) // TODO(jiyong): use the RuleBuilder var copyCommands []string var implicitInputs []android.Path apexDir := android.PathForModuleInPartitionInstall(ctx, "apex", apexName) for _, fi := range a.filesInfo { 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) installMapPath := fi.builtFile // 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() { pathOnDevice := filepath.Join("/", fi.partition, fi.path()) copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath) } else { // Copy the file into APEX if !a.testApex && isVintfFragment(fi) { // copy the output of assemble_vintf instead of the original vintfFragment := runAssembleVintf(ctx, fi.builtFile) copyCommands = append(copyCommands, "cp -f "+vintfFragment.String()+" "+destPath) implicitInputs = append(implicitInputs, vintfFragment) } else { copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath) implicitInputs = append(implicitInputs, fi.builtFile) } var installedPath android.InstallPath if fi.class == appSet { // In case of AppSet, we need to copy additional APKs as well. They // are zipped. So we need to unzip them. copyCommands = append(copyCommands, fmt.Sprintf("unzip -qDD -d %s %s", destPathDir, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String())) if installSymbolFiles { installedPath = ctx.InstallFileWithExtraFilesZip(apexDir.Join(ctx, fi.installDir), fi.stem(), fi.builtFile, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs()) } } else { if installSymbolFiles { installedPath = ctx.InstallFile(apexDir.Join(ctx, fi.installDir), fi.stem(), fi.builtFile) } } // 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) if installSymbolFiles { ctx.InstallSymlink(apexDir.Join(ctx, filepath.Dir(symlinkPath)), filepath.Base(symlinkPath), installedPath) } } installMapPath = installedPath } // 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) } installMapSet[installMapPath.String()+":"+fi.installDir+"/"+fi.builtFile.Base()] = true } implicitInputs = append(implicitInputs, a.manifestPbOut) if len(installMapSet) > 0 { var installs []string installs = append(installs, android.SortedKeys(installMapSet)...) a.SetLicenseInstallMap(installs) } //////////////////////////////////////////////////////////////////////////////////////////// // 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. if a.overridableProperties.Allowed_files != nil { // Build content.txt var contentLines []string imageContentFile := android.PathForModuleOut(ctx, "content.txt") contentLines = append(contentLines, "./apex_manifest.pb") minSdkVersion := a.minSdkVersion(ctx) if minSdkVersion.EqualTo(android.SdkVersion_Android10) { contentLines = append(contentLines, "./apex_manifest.json") } for _, fi := range a.filesInfo { contentLines = append(contentLines, "./"+fi.path()) } sort.Strings(contentLines) android.WriteFileRule(ctx, imageContentFile, strings.Join(contentLines, "\n")) implicitInputs = append(implicitInputs, imageContentFile) // 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, Implicits: implicitInputs, Output: phonyOutput, Description: "diff apex image content", Args: map[string]string{ "allowed_files_file": allowedFilesFile.String(), "image_content_file": imageContentFile.String(), "apex_module_name": a.Name(), }, }) implicitInputs = append(implicitInputs, phonyOutput) } unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned") outHostBinDir := ctx.Config().HostToolPath(ctx, "").String() prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin") //////////////////////////////////////////////////////////////////////////////////// // 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. cannedFsConfig := a.buildCannedFsConfig(ctx) 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{} 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 != "" { optFlags = append(optFlags, "--override_apk_package_name "+manifestPackageName) } if a.properties.AndroidManifest != nil { androidManifestFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.AndroidManifest)) if a.testApex { androidManifestFile = markManifestTestOnly(ctx, androidManifestFile) } implicitInputs = append(implicitInputs, androidManifestFile) optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String()) } else if a.testApex { optFlags = append(optFlags, "--test_only") } // Determine target/min sdk version from the context // TODO(jiyong): make this as a function moduleMinSdkVersion := a.minSdkVersion(ctx) minSdkVersion := moduleMinSdkVersion.String() // 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() if java.UseApiFingerprint(ctx) { minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String()) implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx)) } } // 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()) implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx)) } optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion) optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion) if a.overridableProperties.Logging_parent != "" { optFlags = append(optFlags, "--logging_parent ", a.overridableProperties.Logging_parent) } // Create a NOTICE file, and embed it as an asset file in the APEX. htmlGzNotice := android.PathForModuleOut(ctx, "NOTICE.html.gz") android.BuildNoticeHtmlOutputFromLicenseMetadata( ctx, htmlGzNotice, "", "", []string{ android.PathForModuleInstall(ctx).String() + "/", android.PathForModuleInPartitionInstall(ctx, "apex").String() + "/", }) noticeAssetPath := android.PathForModuleOut(ctx, "NOTICE", "NOTICE.html.gz") builder := android.NewRuleBuilder(pctx, ctx) builder.Command().Text("cp"). Input(htmlGzNotice). Output(noticeAssetPath) builder.Build("notice_dir", "Building notice dir") implicitInputs = append(implicitInputs, noticeAssetPath) optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String())) // 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. needHashTree := moduleMinSdkVersion.LessThanOrEqualTo(android.SdkVersion_Android10) || a.shouldGenerateHashtree() if ctx.Config().ApexCompressionEnabled() && a.isCompressable() { needHashTree = true } if !needHashTree { optFlags = append(optFlags, "--no_hashtree") } if a.testOnlyShouldSkipPayloadSign() { optFlags = append(optFlags, "--unsigned_payload") } 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()) if a.dynamic_common_lib_apex() { ctx.Build(pctx, android.BuildParams{ Rule: DCLAApexRule, Implicits: implicitInputs, Output: unsignedOutputFile, Description: "apex", Args: map[string]string{ "tool_path": outHostBinDir + ":" + prebuiltSdkToolsBinDir, "image_dir": imageDir.String(), "copy_commands": strings.Join(copyCommands, " && "), "manifest": a.manifestPbOut.String(), "file_contexts": fileContexts.String(), "canned_fs_config": cannedFsConfig.String(), "key": a.privateKeyFile.String(), "opt_flags": strings.Join(optFlags, " "), }, }) } else if ctx.Config().ApexTrimEnabled() && len(a.libs_to_trim(ctx)) > 0 { ctx.Build(pctx, android.BuildParams{ Rule: TrimmedApexRule, Implicits: implicitInputs, Output: unsignedOutputFile, Description: "apex", Args: map[string]string{ "tool_path": outHostBinDir + ":" + prebuiltSdkToolsBinDir, "image_dir": imageDir.String(), "copy_commands": strings.Join(copyCommands, " && "), "manifest": a.manifestPbOut.String(), "file_contexts": fileContexts.String(), "canned_fs_config": cannedFsConfig.String(), "key": a.privateKeyFile.String(), "opt_flags": strings.Join(optFlags, " "), "libs_to_trim": strings.Join(a.libs_to_trim(ctx), ","), }, }) } else { ctx.Build(pctx, android.BuildParams{ Rule: apexRule, Implicits: implicitInputs, Output: unsignedOutputFile, Description: "apex", Args: map[string]string{ "tool_path": outHostBinDir + ":" + prebuiltSdkToolsBinDir, "image_dir": imageDir.String(), "copy_commands": strings.Join(copyCommands, " && "), "manifest": a.manifestPbOut.String(), "file_contexts": fileContexts.String(), "canned_fs_config": cannedFsConfig.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 ctx.Build(pctx, android.BuildParams{ Rule: apexProtoConvertRule, Input: unsignedOutputFile, Output: apexProtoFile, 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.nativeApisUsedByModuleFile = apisUsedbyOutputFile var nativeLibNames []string for _, f := range a.filesInfo { if f.class == nativeSharedLib { nativeLibNames = append(nativeLibNames, f.stem()) } } apisBackedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_backing.txt") rb := android.NewRuleBuilder(pctx, ctx) rb.Command(). Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")). Output(apisBackedbyOutputFile). Flags(nativeLibNames) rb.Build("ndk_backedby_list", "Generate API libraries backed by Apex") a.nativeApisBackedByModuleFile = apisBackedbyOutputFile var javaLibOrApkPath []android.Path for _, f := range a.filesInfo { if f.class == javaSharedLib || f.class == app { javaLibOrApkPath = append(javaLibOrApkPath, f.builtFile) } } javaApiUsedbyOutputFile := android.PathForModuleOut(ctx, a.Name()+"_using.xml") javaUsedByRule := android.NewRuleBuilder(pctx, ctx) javaUsedByRule.Command(). Tool(android.PathForSource(ctx, "build/soong/scripts/gen_java_usedby_apex.sh")). BuiltTool("dexdeps"). Output(javaApiUsedbyOutputFile). Inputs(javaLibOrApkPath) javaUsedByRule.Build("java_usedby_list", "Generate Java APIs used by Apex") a.javaApisUsedByModuleFile = javaApiUsedbyOutputFile 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, Implicit: bundleConfig, Output: a.bundleModuleFile, Description: "apex bundle module", Args: map[string]string{ "abi": strings.Join(abis, "."), "config": bundleConfig.String(), }, }) //////////////////////////////////////////////////////////////////////////////////// // 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": pem.String() + " " + key.String(), "flags": "-a 4096 --align-file-size", //alignment } 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"] = signedOutputFile.String() } var validations android.Paths validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile.OutputPath, imageDir.OutputPath)) // TODO(b/279688635) deapexer supports [ext4] if suffix == imageApexSuffix && ext4 == a.payloadFsType { validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile.OutputPath)) } ctx.Build(pctx, android.BuildParams{ Rule: rule, Description: "signapk", Output: signedOutputFile, Input: unsignedOutputFile, Implicits: implicits, Args: args, Validations: validations, }) if suffix == imageApexSuffix { a.outputApexFile = signedOutputFile } a.outputFile = signedOutputFile if ctx.ModuleDir() != "system/apex/apexd/apexd_testdata" && a.testOnlyShouldForceCompression() { ctx.PropertyErrorf("test_only_force_compression", "not available") return } installSuffix := suffix a.setCompression(ctx) if a.isCompressed { unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix+".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()+imageCapexSuffix) 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 installSuffix = imageCapexSuffix } if !a.installable() { a.SkipInstall() } // Install to $OUT/soong/{target,host}/.../apex. a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile, a.compatSymlinks.Paths()...) // installed-files.txt is dist'ed a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir) } // 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 } 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 { // For VNDK APEXes, check "com.android.vndk" in PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES // to see if it should be overridden because their is dynamically generated // according to its VNDK version. if a.vndkApex { overrideName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(vndkApexName) if overridden { return overrideName + ".v" + a.vndkVersion(ctx.DeviceConfig()) } return "" } if a.overridableProperties.Package_name != "" { return a.overridableProperties.Package_name } manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName()) if overridden { return manifestPackageName } return "" } func (a *apexBundle) buildApexDependencyInfo(ctx android.ModuleContext) { if a.properties.IsCoverageVariant { // Otherwise, we will have duplicated rules for coverage and // non-coverage variants of the same APEX return } depInfos := android.DepNameToDepInfoMap{} 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()) } info.IsExternal = info.IsExternal && externalDep depInfos[to.Name()] = info } else { toMinSdkVersion := "(no version)" if m, ok := to.(interface { MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel }); ok { if v := m.MinSdkVersion(ctx); !v.IsNone() { toMinSdkVersion = v.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()}, IsExternal: externalDep, MinSdkVersion: toMinSdkVersion, } } // As soon as the dependency graph crosses the APEX boundary, don't go further. return !externalDep }) a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(ctx).String(), depInfos) ctx.Build(pctx, android.BuildParams{ Rule: android.Phony, Output: android.PathForPhony(ctx, a.Name()+"-deps-info"), Inputs: []android.Path{ a.ApexBundleDepsInfo.FullListPath(), a.ApexBundleDepsInfo.FlatListPath(), }, }) } func (a *apexBundle) buildLintReports(ctx android.ModuleContext) { depSetsBuilder := java.NewLintDepSetBuilder() for _, fi := range a.filesInfo { depSetsBuilder.Transitive(fi.lintDepSets) } a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build()) } func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath { var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"} var executablePaths []string // this also includes dirs var appSetDirs []string appSetFiles := make(map[string]android.Path) for _, f := range a.filesInfo { 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)) } } else if f.class == appSet { // base APK readOnlyPaths = append(readOnlyPaths, pathInApex) // Additional APKs appSetDirs = append(appSetDirs, f.installDir) appSetFiles[f.installDir] = f.module.(*java.AndroidAppSet).PackedAdditionalOutputs() } else { readOnlyPaths = append(readOnlyPaths, pathInApex) } dir := f.installDir for !android.InList(dir, executablePaths) && dir != "" { executablePaths = append(executablePaths, dir) dir, _ = filepath.Split(dir) // move up to the parent if len(dir) > 0 { // remove trailing slash dir = dir[:len(dir)-1] } } } sort.Strings(readOnlyPaths) sort.Strings(executablePaths) sort.Strings(appSetDirs) cannedFsConfig := android.PathForModuleOut(ctx, "canned_fs_config") builder := android.NewRuleBuilder(pctx, ctx) cmd := builder.Command() cmd.Text("(") cmd.Text("echo '/ 1000 1000 0755';") for _, p := range readOnlyPaths { cmd.Textf("echo '/%s 1000 1000 0644';", p) } for _, p := range executablePaths { cmd.Textf("echo '/%s 0 2000 0755';", p) } for _, dir := range appSetDirs { cmd.Textf("echo '/%s 0 2000 0755';", dir) file := appSetFiles[dir] cmd.Text("zipinfo -1").Input(file).Textf(`| sed "s:\(.*\):/%s/\1 1000 1000 0644:";`, dir) } // Custom fs_config is "appended" to the last so that entries from the file are preferred // over default ones set above. if a.properties.Canned_fs_config != nil { cmd.Text("cat").Input(android.PathForModuleSrc(ctx, *a.properties.Canned_fs_config)) } cmd.Text(")").FlagWithOutput("> ", cannedFsConfig) builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName())) return cannedFsConfig.OutputPath } func runApexLinkerconfigValidation(ctx android.ModuleContext, apexFile android.OutputPath, imageDir android.OutputPath) android.Path { timestamp := android.PathForModuleOut(ctx, "apex_linkerconfig_validation.timestamp") ctx.Build(pctx, android.BuildParams{ Rule: apexLinkerconfigValidationRule, Input: apexFile, Output: timestamp, Args: map[string]string{ "image_dir": imageDir.String(), }, }) return timestamp } // Runs apex_sepolicy_tests // // $ deapexer list -Z {apex_file} > {file_contexts} // $ apex_sepolicy_tests -f {file_contexts} func runApexSepolicyTests(ctx android.ModuleContext, apexFile android.OutputPath) android.Path { timestamp := android.PathForModuleOut(ctx, "sepolicy_tests.timestamp") ctx.Build(pctx, android.BuildParams{ Rule: apexSepolicyTestsRule, Input: apexFile, Output: timestamp, }) return timestamp }