From 10944252b2237cf5950d1fea443603f9603d394e Mon Sep 17 00:00:00 2001 From: Jaewoong Jung Date: Mon, 15 Apr 2019 09:48:31 -0700 Subject: Add android_app_import. This is an initial version that handles the most basic cases. Bug: 128610294 Test: app_test.go + prebuilt webview.apk Change-Id: Ic525559aad5612987e50aa75b326b77b23acb716 --- java/androidmk.go | 26 ++++++++ java/app.go | 182 +++++++++++++++++++++++++++++++++++++++++++++------- java/app_builder.go | 9 ++- java/app_test.go | 84 ++++++++++++++++++++++++ java/dexpreopt.go | 13 ++-- java/java_test.go | 3 + 6 files changed, 286 insertions(+), 31 deletions(-) diff --git a/java/androidmk.go b/java/androidmk.go index b04f8050..78815838 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -581,6 +581,32 @@ func (dstubs *Droidstubs) AndroidMk() android.AndroidMkData { } } +func (app *AndroidAppImport) AndroidMk() android.AndroidMkData { + return android.AndroidMkData{ + Class: "APPS", + OutputFile: android.OptionalPathForPath(app.outputFile), + Include: "$(BUILD_SYSTEM)/soong_app_prebuilt.mk", + Extra: []android.AndroidMkExtraFunc{ + func(w io.Writer, outputFile android.Path) { + if Bool(app.properties.Privileged) { + fmt.Fprintln(w, "LOCAL_PRIVILEGED_MODULE := true") + } + if app.certificate != nil { + fmt.Fprintln(w, "LOCAL_CERTIFICATE :=", app.certificate.Pem.String()) + } else { + fmt.Fprintln(w, "LOCAL_CERTIFICATE := PRESIGNED") + } + if len(app.properties.Overrides) > 0 { + fmt.Fprintln(w, "LOCAL_OVERRIDES_PACKAGES :=", strings.Join(app.properties.Overrides, " ")) + } + if len(app.dexpreopter.builtInstalled) > 0 { + fmt.Fprintln(w, "LOCAL_SOONG_BUILT_INSTALLED :=", app.dexpreopter.builtInstalled) + } + }, + }, + } +} + func androidMkWriteTestData(data android.Paths, ret *android.AndroidMkData) { var testFiles []string for _, d := range data { diff --git a/java/app.go b/java/app.go index ece87802..cad90b03 100644 --- a/java/app.go +++ b/java/app.go @@ -35,6 +35,7 @@ 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_import", AndroidAppImportFactory) } // AndroidManifest.xml merging @@ -308,37 +309,38 @@ func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, ctx android.ModuleContext return jniJarFile } -func (a *AndroidApp) certificateBuildActions(certificateDeps []Certificate, ctx android.ModuleContext) []Certificate { - cert := a.getCertString(ctx) - certModule := android.SrcIsModule(cert) - if certModule != "" { - a.certificate = certificateDeps[0] - certificateDeps = certificateDeps[1:] - } else if cert != "" { - defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) - a.certificate = Certificate{ - defaultDir.Join(ctx, cert+".x509.pem"), - defaultDir.Join(ctx, cert+".pk8"), +// Reads and prepends a main cert from the default cert dir if it hasn't been set already, i.e. it +// isn't a cert module reference. Also checks and enforces system cert restriction if applicable. +func processMainCert(m android.ModuleBase, certPropValue string, certificates []Certificate, ctx android.ModuleContext) []Certificate { + if android.SrcIsModule(certPropValue) == "" { + var mainCert Certificate + if certPropValue != "" { + defaultDir := ctx.Config().DefaultAppCertificateDir(ctx) + mainCert = Certificate{ + defaultDir.Join(ctx, certPropValue+".x509.pem"), + defaultDir.Join(ctx, certPropValue+".pk8"), + } + } else { + pem, key := ctx.Config().DefaultAppCertificate(ctx) + mainCert = Certificate{pem, key} } - } else { - pem, key := ctx.Config().DefaultAppCertificate(ctx) - a.certificate = Certificate{pem, key} + certificates = append([]Certificate{mainCert}, certificates...) } - if !a.Module.Platform() { - certPath := a.certificate.Pem.String() + if !m.Platform() { + certPath := certificates[0].Pem.String() systemCertPath := ctx.Config().DefaultAppCertificateDir(ctx).String() if strings.HasPrefix(certPath, systemCertPath) { enforceSystemCert := ctx.Config().EnforceSystemCertificate() whitelist := ctx.Config().EnforceSystemCertificateWhitelist() - if enforceSystemCert && !inList(a.Module.Name(), whitelist) { + if enforceSystemCert && !inList(m.Name(), whitelist) { ctx.PropertyErrorf("certificate", "The module in product partition cannot be signed with certificate in system.") } } } - return append([]Certificate{a.certificate}, certificateDeps...) + return certificates } func (a *AndroidApp) noticeBuildActions(ctx android.ModuleContext, installDir android.OutputPath) android.OptionalPath { @@ -419,25 +421,26 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { dexJarFile := a.dexBuildActions(ctx) - jniLibs, certificateDeps := a.collectAppDeps(ctx) + jniLibs, certificateDeps := collectAppDeps(ctx) jniJarFile := a.jniBuildActions(jniLibs, ctx) if ctx.Failed() { return } - certificates := a.certificateBuildActions(certificateDeps, ctx) + certificates := processMainCert(a.ModuleBase, a.getCertString(ctx), certificateDeps, ctx) + a.certificate = certificates[0] // Build a final signed app package. // TODO(jungjw): Consider changing this to installApkName. packageFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".apk") - CreateAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates) + CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates) a.outputFile = packageFile for _, split := range a.aapt.splits { // Sign the split APKs packageFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"_"+split.suffix+".apk") - CreateAppPackage(ctx, packageFile, split.path, nil, nil, certificates) + CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates) a.extraOutputFiles = append(a.extraOutputFiles, packageFile) } @@ -453,7 +456,7 @@ func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) { } } -func (a *AndroidApp) collectAppDeps(ctx android.ModuleContext) ([]jniLib, []Certificate) { +func collectAppDeps(ctx android.ModuleContext) ([]jniLib, []Certificate) { var jniLibs []jniLib var certificates []Certificate @@ -475,7 +478,6 @@ func (a *AndroidApp) collectAppDeps(ctx android.ModuleContext) ([]jniLib, []Cert } } else { ctx.ModuleErrorf("jni_libs dependency %q must be a cc library", otherName) - } } else if tag == certificateTag { if dep, ok := module.(*AndroidAppCertificate); ok { @@ -683,3 +685,135 @@ func OverrideAndroidAppModuleFactory() android.Module { android.InitOverrideModule(m) return m } + +type AndroidAppImport struct { + android.ModuleBase + android.DefaultableModuleBase + prebuilt android.Prebuilt + + properties AndroidAppImportProperties + + outputFile android.Path + certificate *Certificate + + dexpreopter +} + +type AndroidAppImportProperties struct { + // A prebuilt apk to import + Apk string + + // The name of a certificate in the default certificate directory, blank to use the default + // product certificate, or an android_app_certificate module name in the form ":module". + Certificate *string + + // Set this flag to true if the prebuilt apk is already signed. The certificate property must not + // be set for presigned modules. + Presigned *bool + + // 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 + + // Names of modules to be overridden. Listed modules can only be other binaries + // (in Make or Soong). + // This does not completely prevent installation of the overridden binaries, but if both + // binaries would be installed by default (in PRODUCT_PACKAGES) the other binary will be removed + // from PRODUCT_PACKAGES. + Overrides []string +} + +func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) { + cert := android.SrcIsModule(String(a.properties.Certificate)) + if cert != "" { + ctx.AddDependency(ctx.Module(), certificateTag, cert) + } +} + +func (a *AndroidAppImport) uncompressEmbeddedJniLibs( + ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) { + rule := android.NewRuleBuilder() + rule.Command(). + Textf(`if (zipinfo %s 'lib/*.so' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, inputPath). + Tool(ctx.Config().HostToolPath(ctx, "zip2zip")). + FlagWithInput("-i ", inputPath). + FlagWithOutput("-o ", outputPath). + FlagWithArg("-0 ", "'lib/**/*.so'"). + Textf(`; else cp -f %s %s; fi`, inputPath, outputPath) + rule.Build(pctx, ctx, "uncompress-embedded-jni-libs", "Uncompress embedded JIN libs") +} + +func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) { + if String(a.properties.Certificate) == "" && !Bool(a.properties.Presigned) { + ctx.PropertyErrorf("certificate", "No certificate specified for prebuilt") + } + if String(a.properties.Certificate) != "" && Bool(a.properties.Presigned) { + ctx.PropertyErrorf("certificate", "Certificate can't be specified for presigned modules") + } + + _, certificates := collectAppDeps(ctx) + + // TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK + // TODO: LOCAL_DPI_VARIANTS + // TODO: LOCAL_PACKAGE_SPLITS + + srcApk := a.prebuilt.SingleSourcePath(ctx) + + // TODO: Install or embed JNI libraries + + // Uncompress JNI libraries in the apk + jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk") + a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath) + + // TODO: Uncompress dex if applicable + + installDir := android.PathForModuleInstall(ctx, "app", a.BaseModuleName()) + a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk") + a.dexpreopter.isInstallable = true + a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned) + dexOutput := a.dexpreopter.dexpreopt(ctx, jnisUncompressed) + + // Sign or align the package + // TODO: Handle EXTERNAL + if !Bool(a.properties.Presigned) { + certificates = processMainCert(a.ModuleBase, *a.properties.Certificate, certificates, ctx) + if len(certificates) != 1 { + ctx.ModuleErrorf("Unexpected number of certificates were extracted: %q", certificates) + } + a.certificate = &certificates[0] + signed := android.PathForModuleOut(ctx, "signed", ctx.ModuleName()+".apk") + SignAppPackage(ctx, signed, dexOutput, certificates) + a.outputFile = signed + } else { + alignedApk := android.PathForModuleOut(ctx, "zip-aligned", ctx.ModuleName()+".apk") + TransformZipAlign(ctx, alignedApk, dexOutput) + a.outputFile = alignedApk + } + + // TODO: Optionally compress the output apk. + + ctx.InstallFile(installDir, a.BaseModuleName()+".apk", a.outputFile) + + // TODO: androidmk converter jni libs +} + +func (a *AndroidAppImport) Prebuilt() *android.Prebuilt { + return &a.prebuilt +} + +func (a *AndroidAppImport) Name() string { + return a.prebuilt.Name(a.ModuleBase.Name()) +} + +// android_app_import imports a prebuilt apk with additional processing specified in the module. +func AndroidAppImportFactory() android.Module { + module := &AndroidAppImport{} + module.AddProperties(&module.properties) + module.AddProperties(&module.dexpreoptProperties) + + InitJavaModule(module, android.DeviceSupported) + android.InitSingleSourcePrebuiltModule(module, &module.properties.Apk) + + return module +} diff --git a/java/app_builder.go b/java/app_builder.go index 5bacb677..82a390f6 100644 --- a/java/app_builder.go +++ b/java/app_builder.go @@ -62,7 +62,7 @@ var combineApk = pctx.AndroidStaticRule("combineApk", CommandDeps: []string{"${config.MergeZipsCmd}"}, }) -func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, +func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.WritablePath, packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate) { unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk" @@ -83,6 +83,11 @@ func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath Output: unsignedApk, }) + SignAppPackage(ctx, outputFile, unsignedApk, certificates) +} + +func SignAppPackage(ctx android.ModuleContext, signedApk android.WritablePath, unsignedApk android.Path, certificates []Certificate) { + var certificateArgs []string var deps android.Paths for _, c := range certificates { @@ -93,7 +98,7 @@ func CreateAppPackage(ctx android.ModuleContext, outputFile android.WritablePath ctx.Build(pctx, android.BuildParams{ Rule: Signapk, Description: "signapk", - Output: outputFile, + Output: signedApk, Input: unsignedApk, Implicits: deps, Args: map[string]string{ diff --git a/java/app_test.go b/java/app_test.go index 5c19d9a6..66f993dc 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -1151,3 +1151,87 @@ func TestUncompressDex(t *testing.T) { }) } } + +func TestAndroidAppImport(t *testing.T) { + ctx := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + dex_preopt: { + enabled: true, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil { + t.Errorf("can't find dexpreopt outputs") + } + + // Check cert signing flag. + signedApk := variant.Output("signed/foo.apk") + signingFlag := signedApk.Args["certificates"] + expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8" + if expected != signingFlag { + t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag) + } +} + +func TestAndroidAppImport_NoDexPreopt(t *testing.T) { + ctx := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + certificate: "platform", + dex_preopt: { + enabled: false, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. They shouldn't exist. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule != nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule != nil { + t.Errorf("dexpreopt shouldn't have run.") + } +} + +func TestAndroidAppImport_Presigned(t *testing.T) { + ctx := testJava(t, ` + android_app_import { + name: "foo", + apk: "prebuilts/apk/app.apk", + presigned: true, + dex_preopt: { + enabled: true, + }, + } + `) + + variant := ctx.ModuleForTests("foo", "android_common") + + // Check dexpreopt outputs. + if variant.MaybeOutput("dexpreopt/oat/arm64/package.vdex").Rule == nil || + variant.MaybeOutput("dexpreopt/oat/arm64/package.odex").Rule == nil { + t.Errorf("can't find dexpreopt outputs") + } + // Make sure stripping wasn't done. + stripRule := variant.Output("dexpreopt/foo.apk") + if !strings.HasPrefix(stripRule.RuleParams.Command, "cp -f") { + t.Errorf("unexpected, non-skipping strip command: %q", stripRule.RuleParams.Command) + } + + // Make sure signing was skipped and aligning was done instead. + if variant.MaybeOutput("signed/foo.apk").Rule != nil { + t.Errorf("signing rule shouldn't be included.") + } + if variant.MaybeOutput("zip-aligned/foo.apk").Rule == nil { + t.Errorf("can't find aligning rule") + } +} diff --git a/java/dexpreopt.go b/java/dexpreopt.go index 9141f9ef..088c12df 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -22,11 +22,12 @@ import ( type dexpreopter struct { dexpreoptProperties DexpreoptProperties - installPath android.OutputPath - uncompressedDex bool - isSDKLibrary bool - isTest bool - isInstallable bool + installPath android.OutputPath + uncompressedDex bool + isSDKLibrary bool + isTest bool + isInstallable bool + isPresignedPrebuilt bool builtInstalled string } @@ -177,6 +178,8 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo NoCreateAppImage: !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true), ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false), + PresignedPrebuilt: d.isPresignedPrebuilt, + NoStripping: Bool(d.dexpreoptProperties.Dex_preopt.No_stripping), StripInputPath: dexJarFile, StripOutputPath: strippedDexJarFile.OutputPath, diff --git a/java/java_test.go b/java/java_test.go index 90849c2f..60ea37ce 100644 --- a/java/java_test.go +++ b/java/java_test.go @@ -63,6 +63,7 @@ func testContext(config android.Config, bp string, ctx := android.NewTestArchContext() ctx.RegisterModuleType("android_app", android.ModuleFactoryAdaptor(AndroidAppFactory)) ctx.RegisterModuleType("android_app_certificate", android.ModuleFactoryAdaptor(AndroidAppCertificateFactory)) + ctx.RegisterModuleType("android_app_import", android.ModuleFactoryAdaptor(AndroidAppImportFactory)) ctx.RegisterModuleType("android_library", android.ModuleFactoryAdaptor(AndroidLibraryFactory)) ctx.RegisterModuleType("android_test", android.ModuleFactoryAdaptor(AndroidTestFactory)) ctx.RegisterModuleType("android_test_helper_app", android.ModuleFactoryAdaptor(AndroidTestHelperAppFactory)) @@ -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/apk/app.apk": nil, + // For framework-res, which is an implicit dependency for framework "AndroidManifest.xml": nil, "build/make/target/product/security/testkey": nil, -- cgit v1.2.3