diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | android/module.go | 13 | ||||
| -rw-r--r-- | android/neverallow.go | 23 | ||||
| -rw-r--r-- | android/neverallow_test.go | 11 | ||||
| -rw-r--r-- | dexpreopt/config.go | 2 | ||||
| -rw-r--r-- | dexpreopt/dexpreopt.go | 3 | ||||
| -rw-r--r-- | java/androidmk.go | 26 | ||||
| -rw-r--r-- | java/app.go | 182 | ||||
| -rw-r--r-- | java/app_builder.go | 9 | ||||
| -rw-r--r-- | java/app_test.go | 84 | ||||
| -rw-r--r-- | java/dexpreopt.go | 13 | ||||
| -rw-r--r-- | java/java.go | 4 | ||||
| -rw-r--r-- | java/java_test.go | 3 | ||||
| -rw-r--r-- | java/robolectric.go | 110 |
14 files changed, 420 insertions, 64 deletions
diff --git a/Android.bp b/Android.bp index 1fdd44ecb..25e2796c7 100644 --- a/Android.bp +++ b/Android.bp @@ -276,6 +276,7 @@ bootstrap_go_package { "java/plugin.go", "java/prebuilt_apis.go", "java/proto.go", + "java/robolectric.go", "java/sdk.go", "java/sdk_library.go", "java/support_libraries.go", diff --git a/android/module.go b/android/module.go index 674341255..e3c37bb2e 100644 --- a/android/module.go +++ b/android/module.go @@ -143,6 +143,7 @@ type ModuleContext interface { OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag + GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) @@ -1093,6 +1094,18 @@ func (a *androidModuleContext) getDirectDepInternal(name string, tag blueprint.D } } +func (a *androidModuleContext) GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module { + var deps []Module + a.VisitDirectDepsBlueprint(func(m blueprint.Module) { + if aModule, _ := m.(Module); aModule != nil { + if a.ModuleContext.OtherModuleDependencyTag(aModule) == tag { + deps = append(deps, aModule) + } + } + }) + return deps +} + func (a *androidModuleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module { m, _ := a.getDirectDepInternal(name, tag) return m diff --git a/android/neverallow.go b/android/neverallow.go index fba43b3a1..93144830e 100644 --- a/android/neverallow.go +++ b/android/neverallow.go @@ -97,32 +97,15 @@ func createLibcoreRules() []*rule { "external/wycheproof", } - var coreModules = []string{ - "core-all", - "core-oj", - "core-libart", - "okhttp", - "bouncycastle", - "conscrypt", - "apache-xml", - } - - // Core library constraints. Prevent targets adding dependencies on core - // library internals, which could lead to compatibility issues with the ART - // mainline module. They should use core.platform.api.stubs instead. + // Core library constraints. The no_standard_libs can only be used in core + // library projects. Access to core library targets is restricted using + // visibility rules. rules := []*rule{ neverallow(). notIn(append(coreLibraryProjects, "development")...). with("no_standard_libs", "true"), } - for _, m := range coreModules { - r := neverallow(). - notIn(coreLibraryProjects...). - with("libs", m). - because("Only core libraries projects can depend on " + m) - rules = append(rules, r) - } return rules } diff --git a/android/neverallow_test.go b/android/neverallow_test.go index d55ca575c..00c51eaab 100644 --- a/android/neverallow_test.go +++ b/android/neverallow_test.go @@ -138,17 +138,6 @@ var neverallowTests = []struct { expectedError: "", }, { - name: "dependency on core-libart", - fs: map[string][]byte{ - "Blueprints": []byte(` - java_library { - name: "needs_core_libart", - libs: ["core-libart"], - }`), - }, - expectedError: "Only core libraries projects can depend on core-libart", - }, - { name: "java_device_for_host", fs: map[string][]byte{ "Blueprints": []byte(` diff --git a/dexpreopt/config.go b/dexpreopt/config.go index f1fa0ff34..3b77042ba 100644 --- a/dexpreopt/config.go +++ b/dexpreopt/config.go @@ -59,6 +59,7 @@ type GlobalConfig struct { NeverAllowStripping bool // whether stripping should not be done - used as build time check to make sure dex files are always available NoDebugInfo bool // don't generate debug info by default + DontResolveStartupStrings bool // don't resolve string literals loaded during application startup. AlwaysSystemServerDebugInfo bool // always generate mini debug info for system server modules (overrides NoDebugInfo=true) NeverSystemServerDebugInfo bool // never generate mini debug info for system server modules (overrides NoDebugInfo=false) AlwaysOtherDebugInfo bool // always generate mini debug info for non-system server modules (overrides NoDebugInfo=true) @@ -301,6 +302,7 @@ func GlobalConfigForTests(ctx android.PathContext) GlobalConfig { GenerateDMFiles: false, NeverAllowStripping: false, NoDebugInfo: false, + DontResolveStartupStrings: false, AlwaysSystemServerDebugInfo: false, NeverSystemServerDebugInfo: false, AlwaysOtherDebugInfo: false, diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go index 01ee15ed3..5b658d989 100644 --- a/dexpreopt/dexpreopt.go +++ b/dexpreopt/dexpreopt.go @@ -461,6 +461,9 @@ func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module Modul appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art") cmd.FlagWithOutput("--app-image-file=", appImagePath). FlagWithArg("--image-format=", "lz4") + if !global.DontResolveStartupStrings { + cmd.FlagWithArg("--resolve-startup-const-strings=", "true") + } rule.Install(appImagePath, appImageInstallPath) } diff --git a/java/androidmk.go b/java/androidmk.go index aa1a81b2a..5491b3eba 100644 --- a/java/androidmk.go +++ b/java/androidmk.go @@ -590,6 +590,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 da8024fd2..96fd22d48 100644 --- a/java/app.go +++ b/java/app.go @@ -34,6 +34,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 @@ -302,37 +303,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) generateAndroidBuildActions(ctx android.ModuleContext) { @@ -346,25 +348,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) } @@ -390,7 +393,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 @@ -412,7 +415,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 { @@ -620,3 +622,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 5bacb6776..82a390f64 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 a084c9cc0..e4c6afe29 100644 --- a/java/app_test.go +++ b/java/app_test.go @@ -968,3 +968,87 @@ func TestOverrideAndroidApp(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 b502d07c5..a7938c82c 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.go b/java/java.go index 6168b38d4..47dd95724 100644 --- a/java/java.go +++ b/java/java.go @@ -1253,9 +1253,9 @@ func (j *Module) compile(ctx android.ModuleContext, extraSrcJars ...android.Path // merge implementation jar with resources if necessary implementationAndResourcesJar := outputFile if j.resourceJar != nil { - jars := android.Paths{implementationAndResourcesJar, j.resourceJar} + jars := android.Paths{j.resourceJar, implementationAndResourcesJar} combinedJar := android.PathForModuleOut(ctx, "withres", jarName) - TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{}, + TransformJarsToJar(ctx, combinedJar, "for resources", jars, manifest, false, nil, nil) implementationAndResourcesJar = combinedJar } diff --git a/java/java_test.go b/java/java_test.go index 3fab43d6d..5335d7897 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)) @@ -163,6 +164,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, diff --git a/java/robolectric.go b/java/robolectric.go new file mode 100644 index 000000000..26f1e9d1d --- /dev/null +++ b/java/robolectric.go @@ -0,0 +1,110 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// 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 java + +import ( + "fmt" + "io" + "strings" + + "android/soong/android" +) + +func init() { + android.RegisterModuleType("android_robolectric_test", RobolectricTestFactory) +} + +var robolectricDefaultLibs = []string{ + "robolectric_android-all-stub", + "Robolectric_all-target", + "mockito-robolectric-prebuilt", + "truth-prebuilt", +} + +type robolectricProperties struct { + // The name of the android_app module that the tests will run against. + Instrumentation_for *string + + Test_options struct { + // Timeout in seconds when running the tests. + Timeout *string + } +} + +type robolectricTest struct { + Library + + robolectricProperties robolectricProperties + + libs []string +} + +func (r *robolectricTest) DepsMutator(ctx android.BottomUpMutatorContext) { + r.Library.DepsMutator(ctx) + + if r.robolectricProperties.Instrumentation_for != nil { + ctx.AddVariationDependencies(nil, instrumentationForTag, String(r.robolectricProperties.Instrumentation_for)) + } else { + ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module") + } + + ctx.AddVariationDependencies(nil, libTag, robolectricDefaultLibs...) +} + +func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) { + r.Library.GenerateAndroidBuildActions(ctx) + + for _, dep := range ctx.GetDirectDepsWithTag(libTag) { + r.libs = append(r.libs, ctx.OtherModuleName(dep)) + } +} + +func (r *robolectricTest) AndroidMk() android.AndroidMkData { + data := r.Library.AndroidMk() + + data.Custom = func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) { + android.WriteAndroidMkData(w, data) + + fmt.Fprintln(w, "") + fmt.Fprintln(w, "include $(CLEAR_VARS)") + fmt.Fprintln(w, "LOCAL_MODULE := Run"+name) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", name) + fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " ")) + fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for)) + if t := r.robolectricProperties.Test_options.Timeout; t != nil { + fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t) + } + fmt.Fprintln(w, "-include external/robolectric-shadows/run_robotests.mk") + } + + return data +} + +// An android_robolectric_test module compiles tests against the Robolectric framework that can run on the local host +// instead of on a device. It also generates a rule with the name of the module prefixed with "Run" that can be +// used to run the tests. Running the tests with build rule will eventually be deprecated and replaced with atest. +func RobolectricTestFactory() android.Module { + module := &robolectricTest{} + + module.AddProperties( + &module.Module.properties, + &module.Module.protoProperties, + &module.robolectricProperties) + + module.Module.dexpreopter.isTest = true + + InitJavaModule(module, android.DeviceSupported) + return module +} |