| // 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/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") |
| } |
| |
| 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 <policy>. |
| |
| // 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") |
| |
| zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{ |
| Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` + |
| `(. ${out}.copy_commands) && ` + |
| `APEXER_TOOL_PATH=${tool_path} ` + |
| `${apexer} --force --manifest ${manifest} ` + |
| `--payload_type zip ` + |
| `${image_dir} ${out} `, |
| CommandDeps: []string{"${apexer}", "${merge_zips}", "${soong_zip}", "${zipalign}", "${aapt2}"}, |
| Rspfile: "${out}.copy_commands", |
| RspfileContent: "${copy_commands}", |
| Description: "ZipAPEX ${image_dir} => ${out}", |
| }, "tool_path", "image_dir", "copy_commands", "manifest") |
| |
| 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", |
| }) |
| ) |
| |
| // 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/<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 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" |
| } |
| 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) |
| 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) |
| } |
| 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) |
| if !useFileContextsAsIs { |
| // force-label /apex_manifest.pb and / |
| rule.Command().Text("echo").Text(apexPath + "/apex_manifest\\\\.pb").Text(forceLabel).Text(">>").Output(output) |
| rule.Command().Text("echo").Text(apexPath + "/").Text(forceLabel).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 |
| } |
| |
| // 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, |
| }) |
| } |
| |
| // buildUnflattendApex creates build rules to build an APEX using apexer. |
| func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { |
| apexType := a.properties.ApexType |
| suffix := apexType.suffix() |
| 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 |
| copyCommands = append(copyCommands, "cp -f "+fi.builtFile.String()+" "+destPath) |
| |
| 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) |
| } |
| } |
| implicitInputs = append(implicitInputs, 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") |
| |
| if apexType == imageApex { |
| |
| //////////////////////////////////////////////////////////////////////////////////// |
| // 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 (" + apexType.name() + ")", |
| 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 (" + apexType.name() + ")", |
| 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 (" + apexType.name() + ")", |
| 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") |
| rule := android.NewRuleBuilder(pctx, ctx) |
| rule.Command(). |
| Tool(android.PathForSource(ctx, "build/soong/scripts/gen_ndk_backedby_apex.sh")). |
| Output(apisBackedbyOutputFile). |
| Flags(nativeLibNames) |
| rule.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(), |
| }, |
| }) |
| } else { // zipApex |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: zipApexRule, |
| Implicits: implicitInputs, |
| Output: unsignedOutputFile, |
| Description: "apex (" + apexType.name() + ")", |
| Args: map[string]string{ |
| "tool_path": outHostBinDir + ":" + prebuiltSdkToolsBinDir, |
| "image_dir": imageDir.String(), |
| "copy_commands": strings.Join(copyCommands, " && "), |
| "manifest": a.manifestPbOut.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 |
| // 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) |
| } |
| |
| // 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() |
| installedSymlinks := append(android.InstallPaths(nil), a.compatSymlinks...) |
| if a.installable() { |
| for _, fi := range a.filesInfo { |
| dir := filepath.Join("apex", bundleName, fi.installDir) |
| installDir := android.PathForModuleInstall(ctx, dir) |
| if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() { |
| pathOnDevice := filepath.Join("/", fi.partition, fi.path()) |
| installedSymlinks = append(installedSymlinks, |
| ctx.InstallAbsoluteSymlink(installDir, fi.stem(), pathOnDevice)) |
| } else { |
| if fi.class == appSet { |
| as := fi.module.(*java.AndroidAppSet) |
| ctx.InstallFileWithExtraFilesZip(installDir, as.BaseModuleName()+".apk", |
| as.OutputFile(), as.PackedAdditionalOutputs()) |
| } else { |
| target := ctx.InstallFile(installDir, fi.stem(), fi.builtFile) |
| for _, sym := range fi.symlinks { |
| installedSymlinks = append(installedSymlinks, |
| ctx.InstallSymlink(installDir, sym, target)) |
| } |
| } |
| } |
| } |
| |
| // Create install rules for the files added in GenerateAndroidBuildActions after |
| // buildFlattenedApex is called. Add the links to system libs (if any) as dependencies |
| // of the apex_manifest.pb file since it is always present. |
| dir := filepath.Join("apex", bundleName) |
| installDir := android.PathForModuleInstall(ctx, dir) |
| ctx.InstallFile(installDir, "apex_manifest.pb", a.manifestPbOut, installedSymlinks.Paths()...) |
| ctx.InstallFile(installDir, "apex_pubkey", a.publicKeyFile) |
| } |
| |
| a.fileContexts = a.buildFileContexts(ctx) |
| |
| a.outputFile = android.PathForModuleInstall(ctx, "apex", bundleName) |
| } |
| |
| // 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 <apex name> 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.primaryApexType { |
| return |
| } |
| |
| if a.properties.IsCoverageVariant { |
| // Otherwise, we will have duplicated rules for coverage and |
| // non-coverage variants of the same APEX |
| return |
| } |
| |
| if ctx.Host() { |
| // No need to generate dependency info for host variant |
| 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 |
| } |
| |
| // 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 |
| } |