From 8539023170a7574df4d8b6a6688f9e95232b00f7 Mon Sep 17 00:00:00 2001 From: Sasha Smundak Date: Thu, 23 Apr 2020 09:49:59 -0700 Subject: [PATCH] Implement android_app_set module Bug: 152319766 Test: manual and builtin Change-Id: Id0877476f9ae23311d92c0b59a9c568140ab4119 Merged-In: Id0877476f9ae23311d92c0b59a9c568140ab4119 --- android/config.go | 8 +++ java/androidmk.go | 17 ++++++ java/app.go | 122 ++++++++++++++++++++++++++++++++++++++++++ java/app_test.go | 84 +++++++++++++++++++++++++++++ java/builder.go | 12 +++++ java/config/config.go | 2 +- java/java_test.go | 24 +++++++++ 7 files changed, 268 insertions(+), 1 deletion(-) diff --git a/android/config.go b/android/config.go index d1db87b24..3ea361e8a 100644 --- a/android/config.go +++ b/android/config.go @@ -1053,3 +1053,11 @@ func (c *config) ProductHiddenAPIStubsTest() []string { func (c *deviceConfig) TargetFSConfigGen() []string { return c.config.productVariables.TargetFSConfigGen } + +func (c *deviceConfig) DeviceArch() string { + return String(c.config.productVariables.DeviceArch) +} + +func (c *deviceConfig) DeviceSecondaryArch() string { + return String(c.config.productVariables.DeviceSecondaryArch) +} diff --git a/java/androidmk.go b/java/androidmk.go index 43b9f4883..ed8295549 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -595,3 +595,20 @@ func androidMkWriteTestData(data android.Paths, ret *android.AndroidMkData) { }) } } + +func (apkSet *AndroidAppSet) AndroidMk() android.AndroidMkData { + return android.AndroidMkData{ + Class: "APPS", + OutputFile: android.OptionalPathForPath(apkSet.packedOutput), + Include: "$(BUILD_SYSTEM)/soong_android_app_set.mk", + Extra: []android.AndroidMkExtraFunc{ + func(w io.Writer, outputFile android.Path) { + if apkSet.Privileged() { + fmt.Fprintln(w, "LOCAL_PRIVILEGED_MODULE := true") + } + fmt.Fprintln(w, "LOCAL_APK_SET_MASTER_FILE := ", apkSet.masterFile) + fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES :=", strings.Join(apkSet.properties.Overrides, " ")) + }, + }, + } +} diff --git a/java/app.go b/java/app.go index ad672ead4..7323ffc76 100644 --- a/java/app.go +++ b/java/app.go @@ -19,6 +19,7 @@ package java import ( "path/filepath" "sort" + "strconv" "strings" "github.com/google/blueprint" @@ -35,6 +36,127 @@ func init() { android.RegisterModuleType("android_test_helper_app", AndroidTestHelperAppFactory) android.RegisterModuleType("android_app_certificate", AndroidAppCertificateFactory) android.RegisterModuleType("override_android_app", OverrideAndroidAppModuleFactory) + android.RegisterModuleType("android_app_set", AndroidApkSetFactory) +} + +type AndroidAppSetProperties struct { + // APK Set path + Set string + + // Specifies that this app should be installed to the priv-app directory, + // where the system will grant it additional privileges not available to + // normal apps. + Privileged *bool + + // APKs in this set use prerelease SDK version + Prerelease *bool + + // Names of modules to be overridden. Listed modules can only be other apps + // (in Make or Soong). + Overrides []string +} + +type AndroidAppSet struct { + android.ModuleBase + android.DefaultableModuleBase + prebuilt android.Prebuilt + + properties AndroidAppSetProperties + packedOutput android.WritablePath + masterFile string +} + +func (as *AndroidAppSet) Name() string { + return as.prebuilt.Name(as.ModuleBase.Name()) +} + +func (as *AndroidAppSet) IsInstallable() bool { + return true +} + +func (as *AndroidAppSet) Prebuilt() *android.Prebuilt { + return &as.prebuilt +} + +func (as *AndroidAppSet) Privileged() bool { + return Bool(as.properties.Privileged) +} + +var targetCpuAbi = map[string]string{ + "arm": "ARMEABI_V7A", + "arm64": "ARM64_V8A", + "x86": "X86", + "x86_64": "X86_64", +} + +func supportedAbis(ctx android.ModuleContext) []string { + abiName := func(archVar string, deviceArch string) string { + if abi, found := targetCpuAbi[deviceArch]; found { + return abi + } + ctx.ModuleErrorf("Invalid %s: %s", archVar, deviceArch) + return "BAD_ABI" + } + + result := []string{abiName("TARGET_ARCH", ctx.DeviceConfig().DeviceArch())} + if s := ctx.DeviceConfig().DeviceSecondaryArch(); s != "" { + result = append(result, abiName("TARGET_2ND_ARCH", s)) + } + return result +} + +func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) { + as.packedOutput = android.PathForModuleOut(ctx, "extracted.zip") + // We are assuming here that the master file in the APK + // set has `.apk` suffix. If it doesn't the build will fail. + // APK sets containing APEX files are handled elsewhere. + as.masterFile = ctx.ModuleName() + ".apk" + screenDensities := "all" + if dpis := ctx.Config().ProductAAPTPrebuiltDPI(); len(dpis) > 0 { + screenDensities = strings.ToUpper(strings.Join(dpis, ",")) + } + // TODO(asmundak): handle locales. + // TODO(asmundak): do we support device features + ctx.Build(pctx, + android.BuildParams{ + Rule: extractMatchingApks, + Description: "Extract APKs from APK set", + Output: as.packedOutput, + Inputs: android.Paths{as.prebuilt.SingleSourcePath(ctx)}, + Args: map[string]string{ + "abis": strings.Join(supportedAbis(ctx), ","), + "allow-prereleased": strconv.FormatBool(proptools.Bool(as.properties.Prerelease)), + "screen-densities": screenDensities, + "sdk-version": ctx.Config().PlatformSdkVersion(), + "stem": ctx.ModuleName(), + }, + }) + // TODO(asmundak): add this (it's wrong now, will cause copying extracted.zip) + /* + var installDir android.InstallPath + if Bool(as.properties.Privileged) { + installDir = android.PathForModuleInstall(ctx, "priv-app", as.BaseModuleName()) + } else if ctx.InstallInTestcases() { + installDir = android.PathForModuleInstall(ctx, as.BaseModuleName(), ctx.DeviceConfig().DeviceArch()) + } else { + installDir = android.PathForModuleInstall(ctx, "app", as.BaseModuleName()) + } + ctx.InstallFile(installDir, as.masterFile", as.packedOutput) + */ +} + +// android_app_set extracts a set of APKs based on the target device +// configuration and installs this set as "split APKs". +// The set will always contain `base-master.apk` and every APK built +// to the target device. All density-specific APK will be included, too, +// unless PRODUCT_APPT_PREBUILT_DPI is defined (should contain comma-sepearated +// list of density names (LDPI, MDPI, HDPI, etc.) +func AndroidApkSetFactory() android.Module { + module := &AndroidAppSet{} + module.AddProperties(&module.properties) + InitJavaModule(module, android.DeviceSupported) + android.InitSingleSourcePrebuiltModule(module, &module.properties.Set) + return module } // AndroidManifest.xml merging diff --git a/java/app_test.go b/java/app_test.go index 2bd44ad4a..4f174d8a3 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -134,6 +134,90 @@ func TestAppSplits(t *testing.T) { } } +func TestAndroidAppSet(t *testing.T) { + config := testConfig(nil) + config.TestProductVariables.DeviceArch = proptools.StringPtr("arm64") + ctx := testAppContext(config, ` + android_app_set { + name: "foo", + set: "prebuilts/apks/app.apks", + prerelease: true, + }`, nil) + run(t, ctx, config) + module := ctx.ModuleForTests("foo", "android_common") + const packedSplitApks = "extracted.zip" + params := module.Output(packedSplitApks) + if params.Rule == nil { + t.Errorf("expected output %s is missing", packedSplitApks) + } + if s := params.Args["allow-prereleased"]; s != "true" { + t.Errorf("wrong allow-prereleased value: '%s', expected 'true'", s) + } +} + +func TestAndroidAppSet_Variants(t *testing.T) { + bp := ` + android_app_set { + name: "foo", + set: "prebuilts/apks/app.apks", + }` + testCases := []struct { + name string + deviceArch *string + deviceSecondaryArch *string + aaptPrebuiltDPI []string + sdkVersion int + expected map[string]string + }{ + { + name: "One", + deviceArch: proptools.StringPtr("x86"), + aaptPrebuiltDPI: []string{"ldpi", "xxhdpi"}, + sdkVersion: 29, + expected: map[string]string{ + "abis": "X86", + "allow-prereleased": "false", + "screen-densities": "LDPI,XXHDPI", + "sdk-version": "29", + "stem": "foo", + }, + }, + { + name: "Two", + deviceArch: proptools.StringPtr("x86_64"), + deviceSecondaryArch: proptools.StringPtr("x86"), + aaptPrebuiltDPI: nil, + sdkVersion: 30, + expected: map[string]string{ + "abis": "X86_64,X86", + "allow-prereleased": "false", + "screen-densities": "all", + "sdk-version": "30", + "stem": "foo", + }, + }, + } + + for _, test := range testCases { + config := testConfig(nil) + config.TestProductVariables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI + config.TestProductVariables.Platform_sdk_version = &test.sdkVersion + config.TestProductVariables.DeviceArch = test.deviceArch + config.TestProductVariables.DeviceSecondaryArch = test.deviceSecondaryArch + ctx := testAppContext(config, bp, nil) + run(t, ctx, config) + module := ctx.ModuleForTests("foo", "android_common") + const packedSplitApks = "extracted.zip" + params := module.Output(packedSplitApks) + for k, v := range test.expected { + if actual := params.Args[k]; actual != v { + t.Errorf("%s: bad build arg value for '%s': '%s', expected '%s'", + test.name, k, actual, v) + } + } + } +} + func TestResourceDirs(t *testing.T) { testCases := []struct { name string diff --git a/java/builder.go b/java/builder.go index d257d1da1..b552b2c8e 100644 --- a/java/builder.go +++ b/java/builder.go @@ -61,6 +61,18 @@ var ( "javacFlags", "bootClasspath", "classpath", "processorpath", "processor", "srcJars", "srcJarDir", "outDir", "annoDir", "javaVersion") + extractMatchingApks = pctx.StaticRule( + "extractMatchingApks", + blueprint.RuleParams{ + Command: `rm -rf "$out" && ` + + `${config.ExtractApksCmd} -o "${out}" -allow-prereleased=${allow-prereleased} ` + + `-sdk-version=${sdk-version} -abis=${abis} ` + + `--screen-densities=${screen-densities} --stem=${stem} ` + + `${in}`, + CommandDeps: []string{"${config.ExtractApksCmd}"}, + }, + "abis", "allow-prereleased", "screen-densities", "sdk-version", "stem") + turbine = pctx.AndroidStaticRule("turbine", blueprint.RuleParams{ Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + diff --git a/java/config/config.go b/java/config/config.go index 7f968bcc6..f6688a281 100644 --- a/java/config/config.go +++ b/java/config/config.go @@ -102,7 +102,7 @@ func init() { pctx.HostBinToolVariable("D8Cmd", "d8") pctx.HostBinToolVariable("R8Cmd", "r8-compat-proguard") pctx.HostBinToolVariable("HiddenAPICmd", "hiddenapi") - + pctx.HostBinToolVariable("ExtractApksCmd", "extract_apks") pctx.VariableFunc("TurbineJar", func(ctx android.PackageVarContext) string { turbine := "turbine.jar" if ctx.Config().UnbundledBuild() { diff --git a/java/java_test.go b/java/java_test.go index 31b23e764..50b2f69d9 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -88,6 +88,7 @@ func testContext(config android.Config, bp string, ctx.RegisterModuleType("java_sdk_library", android.ModuleFactoryAdaptor(SdkLibraryFactory)) ctx.RegisterModuleType("override_android_app", android.ModuleFactoryAdaptor(OverrideAndroidAppModuleFactory)) ctx.RegisterModuleType("prebuilt_apis", android.ModuleFactoryAdaptor(PrebuiltApisFactory)) + ctx.RegisterModuleType("android_app_set", android.ModuleFactoryAdaptor(AndroidApkSetFactory)) ctx.PreArchMutators(android.RegisterPrebuiltsPreArchMutators) ctx.PreArchMutators(android.RegisterPrebuiltsPostDepsMutators) ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) @@ -167,6 +168,8 @@ func testContext(config android.Config, bp string, "prebuilts/sdk/tools/core-lambda-stubs.jar": nil, "prebuilts/sdk/Android.bp": []byte(`prebuilt_apis { name: "sdk", api_dirs: ["14", "28", "current"],}`), + "prebuilts/apks/app.apks": nil, + // For framework-res, which is an implicit dependency for framework "AndroidManifest.xml": nil, "build/target/product/security/testkey": nil, @@ -212,6 +215,27 @@ func run(t *testing.T, ctx *android.TestContext, config android.Config) { android.FailIfErrored(t, errs) } +func testJavaError(t *testing.T, pattern string, bp string) (*android.TestContext, android.Config) { + t.Helper() + config := testConfig(nil) + ctx := testContext(config, bp, nil) + + _, errs := ctx.ParseBlueprintsFiles("Android.bp") + if len(errs) > 0 { + android.FailIfNoMatchingErrors(t, pattern, errs) + return ctx, config + } + _, errs = ctx.PrepareBuildActions(config) + if len(errs) > 0 { + android.FailIfNoMatchingErrors(t, pattern, errs) + return ctx, config + } + + t.Fatalf("missing expected error %q (0 errors are returned)", pattern) + + return ctx, config +} + func testJava(t *testing.T, bp string) *android.TestContext { t.Helper() config := testConfig(nil)