mobile/android/app/build.gradle
author Vlad Baicu <vlad.baicu@softvision.ro>
Tue, 14 May 2019 12:28:51 +0000
changeset 532612 6ff9d6783ae0e67b522400818dbd02e1e42b74f6
parent 532505 d6e21ffd8ecceee564afa0c7c8b1bcf61fbd6aff
child 532697 db85e84612070c8cd9c75a7cc1b0a50bb5ed2d38
permissions -rw-r--r--
Bug 1534451 - Send Mobile Activation Telemetry ping. r=JanH Differential Revision: https://phabricator.services.mozilla.com/D29668

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}\"".tr(File.separator, "/")

        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'
                }

                if (!mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
                    exclude 'org/mozilla/gecko/util/WebAuthnUtils.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'
                    exclude 'org/mozilla/gecko/advertising/**'
                }
            }
            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"
        implementation "com.google.android.gms:play-services-ads-identifier:$google_play_services_version"
        implementation "org.mindrot:jbcrypt:0.4"
    }

    if (mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
        implementation "com.google.android.gms:play-services-fido:$google_play_services_fido_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
    // <manifest> 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 ->
    if (variant.name == mozconfig.substs.GRADLE_ANDROID_APP_VARIANT_NAME) {
        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.requested.name != 'play-services-fido') {
                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 <activity-alias android:name=".App">.
                    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"])
}