buildDir "${topobjdir}/gradle/build/mobile/android/app" apply plugin: 'com.android.application' apply plugin: 'checkstyle' apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'findbugs' dexcount { format = "tree" } android { compileSdkVersion project.ext.compileSdkVersion buildToolsVersion project.ext.buildToolsVersion 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 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } dexOptions { javaMaxHeapSize "2g" 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" } release configureMinifyClosure if (mozconfig.substs.MOZILLA_OFFICIAL) { debug configureMinifyClosure } } // The "audience" flavour dimension distinguishes between _local_ builds (intended for // development) and _official_ builds (intended for testing in automation and to ship in one of // the Fennec distribution channels). // // The "skin" flavor dimension distinguishes between different user interfaces. We sometimes // want to develop significant new user interface pieces in-tree that don't ship (even in the // Nightly channel) while under development. A new "skin" flavour allows us to develop such // pieces in Gradle without changing the mainline configuration. flavorDimensions "audience", "skin" productFlavors { // For API 21+ - with pre-dexing, this will be faster for local development. local { dimension "audience" // For pre-dexing, setting `minSdkVersion 21` allows the Android gradle plugin to // pre-DEX each module and produce an APK that can be tested on // Android Lollipop without time consuming DEX merging processes. minSdkVersion 21 dexOptions { preDexLibraries true } } // For API < 21 - does not support pre-dexing because local development // is slow in that case. localOld { dimension "audience" } // Automation builds. We use "official" rather than "automation" to drive these builds down // the list of configurations that Android Studio offers, thereby making it _not_ the // default. This avoids a common issue with "omni.ja" not being packed into the default APK // built and deployed by Android Studio. official { dimension "audience" } // Since Firefox 57, the mobile user interface has followed the Photon design. // Before Firefox 57, the user interface followed the Australis design. photon { dimension "skin" } } sourceSets { main { manifest.srcFile "${project.buildDir}/generated/source/preprocessed_manifest/AndroidManifest.xml" aidl { srcDir "${topsrcdir}/mobile/android/base/aidl" } java { srcDir "${topsrcdir}/mobile/android/base/java" srcDir "${topsrcdir}/mobile/android/search/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/CrashReporter.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' } 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' } if (!mozconfig.substs.MOZ_ANDROID_GCM) { exclude 'org/mozilla/gecko/gcm/**/*.java' exclude 'org/mozilla/gecko/push/**/*.java' } srcDir "${project.buildDir}/generated/source/preprocessed_code" // See syncPreprocessedCode. } res { srcDir "${topsrcdir}/${mozconfig.substs.MOZ_BRANDING_DIRECTORY}/res" srcDir "${project.buildDir}/generated/source/preprocessed_resources" // See syncPreprocessedResources. 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 && !mozconfig.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) { // If we are packaging the bouncer, it will have the distribution, so don't put // it in the main APK as well. srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets" } srcDir "${topsrcdir}/mobile/android/app/assets" } } test { java { srcDir "${topsrcdir}/mobile/android/tests/background/junit4/src" if (!mozconfig.substs.MOZ_ANDROID_GCM) { exclude 'org/mozilla/gecko/gcm/**/*.java' exclude 'org/mozilla/gecko/push/**/*.java' } } resources { srcDir "${topsrcdir}/mobile/android/tests/background/junit4/resources" } } androidTest { java { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/src" srcDir "${topsrcdir}/mobile/android/tests/background/junit3/src" srcDir "${topsrcdir}/mobile/android/tests/browser/junit3/src" srcDir "${topsrcdir}/mobile/android/tests/javaddons/src" } res { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/res" } assets { srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets" } } } testOptions { 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 { compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" if (mozconfig.substs.MOZ_NATIVE_DEVICES) { compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}" compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } if (mozconfig.substs.MOZ_INSTALL_TRACKING) { compile "com.google.android.gms:play-services-ads:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } if (mozconfig.substs.MOZ_ANDROID_GCM) { compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" compile "com.google.android.gms:play-services-measurement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}" } // Include LeakCanary in most gradle based builds. LeakCanary adds about 5k methods, so we disable // it for the (non-proguarded, non-predex) localOld builds to allow space for other libraries. // Gradle based tests include the no-op version. Mach based builds only include the no-op version // of this library. // It doesn't seem like there is a non-trivial way to be conditional on 'localOld', so instead we explicitly // define a version of leakcanary for every flavor: localCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1' localOldCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' // With a simple "compile", Gradle will always build these libraries in their default configuration // (i.e. release), so we need to explicitly forward our own build configuration here (bug 1385695). debugCompile project(path: ':geckoview', configuration: "debug") releaseCompile project(path: ':geckoview', configuration: "release") debugCompile project(path: ':thirdparty', configuration: "debug") releaseCompile project(path: ':thirdparty', configuration: "release") testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.1.2' testCompile 'org.simpleframework:simple-http:6.0.1' testCompile 'org.mockito:mockito-core:1.10.19' // Including the Robotium JAR directly can cause issues with dexing. androidTestCompile '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() } task syncPreprocessedCode(type: Sync, dependsOn: rootProject.generateCodeAndResources) { into("${project.buildDir}/generated/source/preprocessed_code") from("${topobjdir}/mobile/android/base/generated/preprocessed") { // All other preprocessed code is included in the geckoview project. include '**/AdjustConstants.java' include '**/MmaConstants.java' } } // 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)))) } } task syncPreprocessedResources(type: Sync, dependsOn: rootProject.generateCodeAndResources) { into("${project.buildDir}/generated/source/preprocessed_resources") from("${topobjdir}/mobile/android/base/res") filesMatching('**/strings.xml') { filter(ExpandXMLEntitiesFilter) } } // 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. task rewriteManifestPackage(type: Copy, dependsOn: rootProject.generateCodeAndResources) { into("${project.buildDir}/generated/source/preprocessed_manifest") from("${topobjdir}/mobile/android/base/AndroidManifest.xml") filter { it.replaceFirst(/package=".*?"/, 'package="org.mozilla.gecko"') } } apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle" android.applicationVariants.all { variant -> variant.preBuild.dependsOn rewriteManifestPackage variant.preBuild.dependsOn syncPreprocessedCode variant.preBuild.dependsOn syncPreprocessedResources // Official automation builds don't include Gecko binaries, since those binaries are not // produced until after build time (at package time). official Therefore, automation builds // include the Gecko binaries into the APK at package time. The "withGeckoBinaries" variant of // the :geckoview project also does this. (It does what it says on the tin!) For notes on this // approach, see mobile/android/gradle/with_gecko_binaries.gradle. // Like 'local' or 'localOld'. def audienceDimension = variant.productFlavors[0].name // :app uses :geckoview:release and handles it's own Gecko binary inclusion, // even though this would be most naturally done in the :geckoview project. if (!audienceDimension.equals('official')) { configureVariantWithGeckoBinaries(variant) } } apply plugin: 'spoon' spoon { // For now, let's be verbose. debug = true // It's not helpful to pass when we don't have a device connected. failIfNoDeviceConnected = true def spoonPackageName if (gradle.startParameter.taskNames.contains('runBrowserTests')) { spoonPackageName = 'org.mozilla.tests.browser.junit3' } if (gradle.startParameter.taskNames.contains('runBackgroundTests')) { spoonPackageName = 'org.mozilla.gecko.background' } if (project.hasProperty('spoonPackageName')) { // Command line overrides everything. spoonPackageName = project.spoonPackageName } if (spoonPackageName) { instrumentationArgs = ['-e', "package=${spoonPackageName}".toString()] } } // // See discussion at https://github.com/stanfy/spoon-gradle-plugin/issues/9. // afterEvaluate { // tasks["spoonLocal${android.testBuildType.capitalize()}AndroidTest"].outputs.upToDateWhen { false } // // This is an awkward way to define different sets of instrumentation tests. // // The task name itself is fished at runtime and the package name configured // // in the spoon configuration. // task runBrowserTests { // dependsOn tasks["spoonLocalOldDebugAndroidTest"] // } // task runBackgroundTests { // dependsOn tasks["spoonLocalOldDebugAndroidTest"] // } // } // Bug 1299015: Complain to treeherder if checkstyle, lint, or unittest fails. It's not obvious // how to listen to individual errors in most cases, so we just link to the reports for now. def makeTaskExecutionListener(artifactRootUrl) { return new TaskExecutionListener() { void beforeExecute(Task task) { // Do nothing. } void afterExecute(Task task, TaskState state) { if (!state.failure) { return } // Link to the failing report. The task path and the report path // depend on the android-lint task in // taskcluster/ci/android-stuff/kind.yml. It's not possible to link // directly, so for now consumers will need to copy-paste the URL. switch (task.path) { case ':app:checkstyle': def url = "${artifactRootUrl}/public/android/checkstyle/checkstyle.xml" println "TEST-UNEXPECTED-FAIL | android-checkstyle | Checkstyle rule violations were found. See the report at: $url" break case ':app:lintOfficialPhotonDebug': def url = "${artifactRootUrl}/public/android/lint/lint-results-officialPhotonDebug.html" println "TEST-UNEXPECTED-FAIL | android-lint | Lint found errors in the project; aborting build. See the report at: $url" break case ':app:testOfficialPhotonDebugUnitTest': def url = "${artifactRootUrl}/public/android/unittest/officialPhotonDebug/index.html" println "TEST-UNEXPECTED-FAIL | android-test | There were failing tests. See the reports at: $url" break case ':app:findbugsHtmlOfficialPhotonDebug': def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialPhotonDebug-output.html" println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url" break case ':app:findbugsXmlOfficialPhotonDebug': def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialPhotonDebug-output.xml" println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url" break } } } } // TASK_ID and RUN_ID are provided by docker-worker; see // https://docs.taskcluster.net/manual/execution/workers/docker-worker. if (System.env.TASK_ID && System.env.RUN_ID) { def artifactRootUrl = "https://queue.taskcluster.net/v1/task/${System.env.TASK_ID}/runs/${System.env.RUN_ID}/artifacts" gradle.addListener(makeTaskExecutionListener(artifactRootUrl)) } 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 "assemble${variant.name.capitalize()}" reports { html.enabled = true // HTML reports for humans. html.destination = "$project.buildDir/outputs/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 "assemble${variant.name.capitalize()}" reports { xml.enabled = true // XML reports for machines. xml.destination = "$project.buildDir/outputs/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 android.applicationVariants.all { variant -> // Like 'local', 'localOld', or 'official'. def audienceDimension = variant.productFlavors[0].name if (!audienceDimension.equals('official')) { return } variant.outputs.each { output -> output.processManifest.doLast { [output.processManifest.manifestOutputFile, output.processManifest.instantRunManifestOutputFile, ].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' } // Second, cull all manifest entries provided by com.google.android.gms.measurement. xml.depthFirst() .findAll { it.'@android:name'.text().contains('AppMeasurement') } .each { it.replaceNode {} } manifestOutFile.write(XmlUtil.serialize(xml), 'UTF-8') } }) } } }