buildDir "${topobjdir}/gradle/build/mobile/android/app" apply plugin: 'com.android.application' apply plugin: 'checkstyle' apply plugin: 'findbugs' if (mozconfig.substs.MOZILLA_OFFICIAL) { apply plugin: 'com.getkeepsafe.dexcount' } apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle" if (mozconfig.substs.MOZILLA_OFFICIAL) { dexcount { format = "tree" } } android { compileSdkVersion project.ext.compileSdkVersion useLibrary 'android.test.runner' useLibrary 'android.test.base' useLibrary 'android.test.mock' defaultConfig { targetSdkVersion project.ext.targetSdkVersion minSdkVersion project.ext.minSdkVersion manifestPlaceholders = project.ext.manifestPlaceholders applicationId mozconfig.substs.ANDROID_PACKAGE_NAME testApplicationId 'org.mozilla.roboexample.test' testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner' // Used by Robolectric based tests; see TestRunner. buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\"" vectorDrawables.useSupportLibrary = true multiDexEnabled true } aaptOptions { // The omnijar is already a compressed file itself and Gecko expects it to be // STORED within the APK rather than DEFLATED. noCompress 'ja' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } dexOptions { javaMaxHeapSize "4g" jumboMode = true } lintOptions { abortOnError true } buildTypes { // We have the following difficult situation. Minification (Proguard) is only available per // Android-Gradle `buildType`. Instrumentation (Robocop) is only available for exactly one // `buildType` (see Android-Gradle `testBuildType`, which defaults to "debug"). Local // developers expect to build and run tests against the "debug" build type. Automation // needs to produce an instrumentation (Robocop) APK against a Fennec APK that will ship. // (This is very unusual; usually, instrumentation tests do _not_ run against a shipping // APK.) // // Given these constraints, we should not change `testBuildType` to avoid confusing local // developers. Also, we should not Proguard any "debug" builds, because we don't want local // developers to incur the cost of Proguard. However, we still need to find a way to // Proguard a shipping APK and produce an instrumentation (Robocop APK) against it. To // achieve this, we make "debug" builds Proguard in automation alone. This does have the // unfortunate side effect of Proguarding the instrumentation (Robocop) APK, but nothing // uses runtime inspection or class-loading with that APK, so it shouldn't be a problem. def configureMinifyClosure = { // Bug 1229269: we can't yet shrinkResources effectively. Be sure // to use -stripped.ap_ after enabling this. // shrinkResources true minifyEnabled true proguardFile "${topsrcdir}/mobile/android/config/proguard/proguard.cfg" testProguardFile "${topsrcdir}/mobile/android/config/proguard/proguard-robocop.cfg" } release configureMinifyClosure if (mozconfig.substs.MOZILLA_OFFICIAL) { debug configureMinifyClosure } def isDebuggable = (!mozconfig.substs.MOZILLA_OFFICIAL) || (mozconfig.substs.NIGHTLY_BUILD && mozconfig.substs.MOZ_DEBUG) debug { debuggable isDebuggable multiDexKeepProguard file("${topsrcdir}/mobile/android/config/proguard/debug-robocop-keeps.cfg") } release { debuggable isDebuggable } } project.configureProductFlavors.delegate = it project.configureProductFlavors() flavorDimensions "geckoBinaries" sourceSets { main { aidl { srcDir "${topsrcdir}/mobile/android/base/aidl" } java { srcDir "${topsrcdir}/mobile/android/base/java" srcDir "${topsrcdir}/mobile/android/services/src/main/java" if (mozconfig.substs.MOZ_ANDROID_MLS_STUMBLER) { srcDir "${topsrcdir}/mobile/android/stumbler/java" } if (!mozconfig.substs.MOZ_CRASHREPORTER) { exclude 'org/mozilla/gecko/CrashReporterActivity.java' exclude 'org/mozilla/gecko/CrashHandlerService.java' } if (!mozconfig.substs.MOZ_NATIVE_DEVICES) { exclude 'org/mozilla/gecko/ChromeCastDisplay.java' exclude 'org/mozilla/gecko/ChromeCastPlayer.java' exclude 'org/mozilla/gecko/GeckoMediaPlayer.java' exclude 'org/mozilla/gecko/GeckoPresentationDisplay.java' exclude 'org/mozilla/gecko/MediaPlayerManager.java' exclude 'org/mozilla/gecko/RemotePresentationService.java' exclude 'org/mozilla/gecko/VirtualPresentation.java' } if (mozconfig.substs.MOZ_INSTALL_TRACKING) { exclude 'org/mozilla/gecko/adjust/StubAdjustHelper.java' } else { exclude 'org/mozilla/gecko/adjust/AdjustHelper.java' } if (mozconfig.substs.MOZ_ANDROID_MMA) { exclude 'org/mozilla/gecko/mma/MmaStubImp.java' } else { exclude 'org/mozilla/gecko/mma/MmaLeanplumImp.java' exclude 'org/mozilla/gecko/mma/LeanplumVariables.java' } if (!mozconfig.substs.MOZ_ANDROID_GCM) { exclude 'org/mozilla/gecko/gcm/**/*.java' exclude 'org/mozilla/gecko/push/**/*.java' } } res { srcDir "${topsrcdir}/${mozconfig.substs.MOZ_BRANDING_DIRECTORY}/res" srcDir "${topsrcdir}/mobile/android/services/src/main/res" if (mozconfig.substs.MOZ_CRASHREPORTER) { srcDir "${topsrcdir}/mobile/android/base/crashreporter/res" } } assets { if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY) { srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets" } } } test { java { // Bug 1229149 tracks pushing this into a :services Gradle project. srcDir "${topsrcdir}/mobile/android/services/src/test/java" if (!mozconfig.substs.MOZ_ANDROID_GCM) { exclude 'org/mozilla/gecko/gcm/**/*.java' exclude 'org/mozilla/gecko/push/**/*.java' } } resources { // Bug 1229149 tracks pushing this into a :services Gradle project. srcDir "${topsrcdir}/mobile/android/services/src/test/resources" } } androidTest { java { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/src" // Bug 1229149 tracks pushing this into a :services Gradle project. srcDir "${topsrcdir}/mobile/android/services/src/androidTest/java" srcDir "${topsrcdir}/mobile/android/tests/browser/junit3/src" } res { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/res" } assets { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets" } } } testOptions { // For Robolectric: see https://github.com/robolectric/robolectric/issues/3333#issuecomment-324300418. unitTests.includeAndroidResources true unitTests.all { // We'd like to use (Runtime.runtime.availableProcessors()/2), but // we have tests that start test servers and the bound ports // collide. We'll fix this soon to have much faster test cycles. maxParallelForks 1 } } } dependencies { implementation "com.android.support:support-v4:$support_library_version" implementation "com.android.support:appcompat-v7:$support_library_version" implementation "com.android.support:cardview-v7:$support_library_version" implementation "com.android.support:recyclerview-v7:$support_library_version" implementation "com.android.support:design:$support_library_version" implementation "com.android.support:customtabs:$support_library_version" implementation "com.android.support:palette-v7:$support_library_version" // We always include support for multidexing even when we don't enable it at runtime. // We could conditionally include support, but we'd need // to generate the `Application` class or fork the file on disk. implementation "com.android.support:multidex:1.0.3" if (mozconfig.substs.MOZ_NATIVE_DEVICES) { implementation "com.android.support:mediarouter-v7:$support_library_version" implementation "com.google.android.gms:play-services-basement:$google_play_services_version" implementation "com.google.android.gms:play-services-base:$google_play_services_version" implementation "com.google.android.gms:play-services-cast:$google_play_services_cast_version" } if (mozconfig.substs.MOZ_INSTALL_TRACKING) { implementation "com.google.android.gms:play-services-ads-identifier:$google_play_services_version" implementation "com.google.android.gms:play-services-basement:$google_play_services_version" } if (mozconfig.substs.MOZ_ANDROID_GCM) { implementation "com.google.android.gms:play-services-basement:$google_play_services_version" implementation "com.google.android.gms:play-services-base:$google_play_services_version" implementation "com.google.android.gms:play-services-gcm:$google_play_services_version" } // Include LeakCanary in local builds, but not in official builds. if (mozconfig.substs.MOZILLA_OFFICIAL) { implementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' } else { implementation 'com.squareup.leakcanary:leakcanary-android:1.4-beta1' } testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' implementation project(path: ':geckoview') implementation project(path: ':thirdparty') testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:3.8' testImplementation 'org.simpleframework:simple-http:6.0.1' testImplementation 'org.mockito:mockito-core:1.10.19' // Including the Robotium JAR directly can cause issues with dexing. androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.5.4' } // TODO: (bug 1261486): This impl is not robust - // we just wanted to land something. task checkstyle(type: Checkstyle) { configFile file("checkstyle.xml") // TODO: should use sourceSets from project instead of hard-coded str. source = ['../base/java/','../geckoview/src/main/java/'] // TODO: This ignores our pre-processed resources. include '**/*.java' // TODO: classpath should probably be something. classpath = files() } // The localization system uses the moz.build preprocessor to interpolate a .dtd // file of XML entity definitions into an XML file of elements referencing those // entities. (Each locale produces its own .dtd file, backstopped by the en-US // .dtd file in tree.) Android Studio (and IntelliJ) don't handle these inline // entities smoothly. This filter merely expands the entities in place, making // them appear properly throughout the IDE. Be aware that this assumes that the // JVM's file.encoding is utf-8. See comments in // mobile/android/mach_commands.py. class ExpandXMLEntitiesFilter extends FilterReader { ExpandXMLEntitiesFilter(Reader input) { // Extremely inefficient, but whatever. super(new StringReader(groovy.xml.XmlUtil.serialize(new XmlParser(false, false, true).parse(input)))) } } apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle" android.applicationVariants.all { variant -> def syncPreprocessedJava = task("syncPreprocessedJavaFor${variant.name.capitalize()}", type: Sync) { into("${project.buildDir}/moz.build/src/${variant.name}/java") from("${topobjdir}/mobile/android/base/generated/preprocessed") exclude('**/*.mkdir.done') } // This is an Android-Gradle plugin 3+-ism. Culted from reading the source, // searching for "registerJavaGeneratingTask", and finding // https://github.com/GoogleCloudPlatform/endpoints-framework-gradle-plugin/commit/2f2b91476fb1c6647791e2c6fe531a47615a1e85. // The added directory doesn't appear in the paths listed by the // `sourceSets` task, for reasons unknown. variant.registerJavaGeneratingTask(syncPreprocessedJava, syncPreprocessedJava.destinationDir) def syncPreprocessedRes = task("syncPreprocessedResFor${variant.name.capitalize()}", type: Sync) { into("${project.buildDir}/moz.build/src/${variant.name}/res") from("${topobjdir}/mobile/android/base/res") filesMatching('**/strings.xml') { filter(ExpandXMLEntitiesFilter) } exclude('**/*.mkdir.done') // Bug 1478411: We want to turn XML entity expansion errors into the "error:" strings that // Tree Herder parses. It's surprisingly difficult to do useful things with exceptions in a // filter, but it appears that something deep in the XML parser prints errors to stderr, so // we amplify those error outputs to achieve the goal. def listener = { def matches = (it =~ /\[Fatal Error\] +(.*)/) if (matches) { def (_, message) = matches[0] logger.lifecycle "error: ${topobjdir}/mobile/android/base/res/values/strings.xml${message}" } } as StandardOutputListener doFirst { logging.addStandardErrorListener(listener) } doLast { logging.removeStandardErrorListener(listener) } } // This is an Android-Gradle plugin 3+-ism. Determined by reading the // source. The added directory doesn't appear in the paths listed by the // `sourceSets` task, for reasons unknown. variant.registerGeneratedResFolders(project.files(syncPreprocessedRes.destinationDir).builtBy(syncPreprocessedRes)) // It's not easy -- see the backout in Bug 1242213 -- to change the // package for Fennec. Gradle has grown a mechanism to achieve // what we want for Fennec, however, with applicationId. To use the same // manifest as moz.build, we replace the package with org.mozilla.gecko (the // eventual package) here. def rewriteManifestPackage = task("rewriteManifestPackageFor${variant.name.capitalize()}", type: Copy, dependsOn: rootProject.machBuildGeneratedAndroidCodeAndResources) { into("${project.buildDir}/moz.build/src/${variant.name}") from("${topobjdir}/mobile/android/base/AndroidManifest.xml") filter { it.replaceFirst(/package=".*?"/, 'package="org.mozilla.gecko"') } exclude('**/*.mkdir.done') } // Every configuration needs the stub manifest at // src/main/AndroidManifest.xml and the generated manifest. We can't use // the main sourceSet without losing the stub, so we cover all the // configurations here. android.sourceSets."${variant.name}".manifest.srcFile "${rewriteManifestPackage.destinationDir}/AndroidManifest.xml" variant.preBuild.dependsOn rewriteManifestPackage // Local (read, not 'official') builds want to reflect developer changes to // AndroidManifest.xml.in, strings.xml, and preprocessed Java code. To do // this, the Gradle build calls out to the moz.build system, which can be // re-entrant. Official builds are driven by the moz.build system and // should never be re-entrant in this way. if (!mozconfig.substs.MOZILLA_OFFICIAL) { syncPreprocessedJava.dependsOn rootProject.machBuildGeneratedAndroidCodeAndResources syncPreprocessedRes.dependsOn rootProject.machBuildGeneratedAndroidCodeAndResources rewriteManifestPackage.dependsOn rootProject.machBuildGeneratedAndroidCodeAndResources } // When driven from moz.build via |mach build|, Gradle does not require or // use Gecko binaries. It's only |mach package| that packs the Gecko // binaries into the resulting APK. The "withoutGeckoBinaries" variants // handle this. When driven from Android Studio or Gradle, the // "withGeckoBinaries" variants handle packing the Gecko binaries into the // resulting APK (for on-device deployment). They also update the Omnijars // as necessary, smoothing out the edit-compile-test development cycle. // They do what they say on the tin! if ((variant.productFlavors*.name).contains('withGeckoBinaries')) { configureVariantWithGeckoBinaries(variant) } } android.applicationVariants.all { variant -> configureApplicationVariantWithJNIWrappers(variant, "Fennec") } if (gradle.startParameter.taskNames.any { it.endsWith('UnitTest') }) { // Approach cribbed from https://github.com/rwinch/jce-checker. int maxKeyLen = javax.crypto.Cipher.getMaxAllowedKeyLength("AES") if (maxKeyLen <= 128) { throw new GradleException( "Android unit tests require " + "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy, see " + "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html") } } // Bug 1320035: Gradle configuration for running findbugs. Findbugs only allows // to generate one report per invocation: https://stackoverflow.com/a/42720235. // Run two tasks, accepting the cost of duplicate work. android.applicationVariants.all { variant -> task("findbugsHtml${variant.name.capitalize()}", type: FindBugs) { // TODO: figure out how to share the shared configuration. description "Analyze ${variant.name} code with findbugs (HTML report)" group "Verification" ignoreFailures = false // We want builds to fail when running this task and issues are found effort = "max" // Using more memory and time to find issues is acceptable in automation reportLevel = "high" // For now we only care about high priority bugs. After we have fixed // the issues with medium/low priority we can lower the report level here. classes = files("$project.buildDir/intermediates/classes") source = variant.javaCompile.source classpath = variant.javaCompile.classpath excludeFilter = file("findbugs-exclude.xml") dependsOn "bundleAppClasses${variant.name.capitalize()}" reports { html.enabled = true // HTML reports for humans. html.destination = file("$project.buildDir/reports/findbugs/findbugs-${variant.name}-output.html") xml.enabled = false } } task("findbugsXml${variant.name.capitalize()}", type: FindBugs) { // TODO: figure out how to share the shared configuration. description "Analyze ${variant.name} code with findbugs (XML report)" group "Verification" ignoreFailures = false // We want builds to fail when running this task and issues are found effort = "max" // Using more memory and time to find issues is acceptable in automation reportLevel = "high" // For now we only care about high priority bugs. After we have fixed // the issues with medium/low priority we can lower the report level here. classes = files("$project.buildDir/intermediates/classes") source = variant.javaCompile.source classpath = variant.javaCompile.classpath excludeFilter = file("findbugs-exclude.xml") dependsOn "bundleAppClasses${variant.name.capitalize()}" reports { xml.enabled = true // XML reports for machines. xml.destination = file("$project.buildDir/reports/findbugs/findbugs-${variant.name}-output.xml") html.enabled = false } } } // Bug 1353055 - Strip 'vars' debugging information to agree with moz.build. apply from: "${topsrcdir}/mobile/android/gradle/debug_level.gradle" android.applicationVariants.all configureVariantDebugLevel // Bug 1320310 - Hack up the manifest produced by Gradle to match that produced // by moz.build. Per https://bugzilla.mozilla.org/show_bug.cgi?id=1320310#c14, // this breaks launching in Android Studio; therefore, we only do this for // official automation builds and not for local developer builds. import groovy.xml.XmlUtil // Workaround for fixing sub-dependencies upon gradle error: // All gms/firebase (except play-services-cast since it has sub-dependencies in 15.0.0) // libraries must use the exact same version specification (mixing versions can // lead to runtime crashes). Found versions 15.0.1, 15.0.0. Examples include // com.google.android.gms:play-services-base:15.0.1 and com.google.android.gms:play-services-basement:15.0.0 configurations.all { resolutionStrategy { eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'com.google.android.gms' && details.requested.name != 'play-services-cast') { details.useVersion "$google_play_services_version" } } } } android.applicationVariants.all { variant -> if (!mozconfig.substs.MOZILLA_OFFICIAL) { return } variant.outputs.each { output -> output.processManifest.doLast { [file("${manifestOutputDirectory}/AndroidManifest.xml"), ].each({ File manifestOutFile -> if (manifestOutFile.exists()) { def contents = manifestOutFile.getText('UTF-8') // A non-validating, non-namespace aware XML processor. def xml = new XmlSlurper(false, false).parseText(contents) // First, reinstate our . xml.depthFirst() .findAll { it.name() == 'activity-alias' && it.'@android:name' == 'org.mozilla.gecko.App' } .each { it.'@android:name' = '.App' } manifestOutFile.write(XmlUtil.serialize(xml), 'UTF-8') } }) } } } // Bug 1415298: make Robolectric find assets. Fix adapted from // https://github.com/robolectric/robolectric/issues/2647. android.applicationVariants.all { variant -> def productFlavor = "" variant.productFlavors.each { productFlavor += "${it.name.capitalize()}" } def buildType = "${variant.buildType.name.capitalize()}" tasks["compile${productFlavor}${buildType}UnitTestSources"].dependsOn(tasks["merge${productFlavor}${buildType}Assets"]) } apply from: "${topsrcdir}/mobile/android/gradle/jacoco_dependencies.gradle" if (project.hasProperty('enable_code_coverage')) { apply from: "${topsrcdir}/mobile/android/gradle/jacoco_for_junit.gradle" } // Set up code coverage for tests on emulators. if (mozconfig.substs.MOZ_JAVA_CODE_COVERAGE) { android { jacoco { version = "$jacoco_version" } buildTypes { debug { testCoverageEnabled true } } } dependencies { // This is required both in the instrumented application classes and the test classes, // so `api` has to be used instead of `androidTestImplementation`. api "org.jacoco:org.jacoco.agent:$jacoco_version:runtime" } // Generate tasks to archive compiled classfiles for later use with JaCoCo report generation. // One of these tasks is used by `mach android archive-coverage-artifacts`. android.applicationVariants.all { variant -> def name = variant.name def compileTask = tasks.getByName("compile${name.capitalize()}JavaWithJavac") task "archiveClassfiles${name.capitalize()}"(type: Zip, dependsOn: compileTask) { description = "Archive compiled classfiles for $name in order to export them as code coverage artifacts." def fileFilter = ['**/androidTest/**', '**/test/**', '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] from fileTree(dir: compileTask.destinationDir, excludes: fileFilter) destinationDir = file("${buildDir}/coverage") // Note: This task assumes only one variant of archiveClassfiles* will be used. // Running multiple variants of this task will overwrite the output archive. archiveName = 'target.app_classfiles.zip' } } }