Bug 1411654 - Part 1: Upgrade to Android-Gradle 3.0+ and build-tools;26.0.2. r=maliu draft
authorNick Alexander <nalexander@mozilla.com>
Thu, 26 Oct 2017 11:00:36 -0700
changeset 749694 a3308672fcefec1234b625fecfa85c89bf61e6d7
parent 749693 b47e85ea97f65ee2c25618bbc6facbe3dbfe35f2
child 749695 ab1dca42bbc41d13edfd05990dc329276ab26277
push id97470
push usernalexander@mozilla.com
push dateWed, 31 Jan 2018 21:14:34 +0000
reviewersmaliu
bugs1411654
milestone60.0a1
Bug 1411654 - Part 1: Upgrade to Android-Gradle 3.0+ and build-tools;26.0.2. r=maliu New Android-Gradle plugins pin the build-tools version, and we want to be consistent between Gradle and moz.build. MozReview-Commit-ID: ApWS4rHzPuH
build.gradle
gradle/wrapper/gradle-wrapper.properties
mobile/android/app/build.gradle
mobile/android/app/src/photon/res/values/styles.xml
mobile/android/base/AndroidManifest.xml.in
mobile/android/geckoview/build.gradle
mobile/android/geckoview_example/build.gradle
mobile/android/gradle.configure
mobile/android/gradle/with_gecko_binaries.gradle
mobile/android/thirdparty/build.gradle
old-configure.in
python/mozboot/mozboot/android-packages.txt
taskcluster/scripts/misc/android-gradle-dependencies/after.sh
--- a/build.gradle
+++ b/build.gradle
@@ -11,17 +11,16 @@ def tryInt = { string ->
 allprojects {
     // Expose the per-object-directory configuration to all projects.
     ext {
         mozconfig = gradle.mozconfig
         topsrcdir = gradle.mozconfig.topsrcdir
         topobjdir = gradle.mozconfig.topobjdir
 
         compileSdkVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_VERSION)
-        buildToolsVersion = tryInt(mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION)
         targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
         minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
         manifestPlaceholders = [
             ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
             ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
             MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
             MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
         ]
@@ -47,19 +46,18 @@ buildscript {
         }
         // For in tree plugins.
         maven {
             url "file://${gradle.mozconfig.topsrcdir}/mobile/android/gradle/m2repo"
         }
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.3'
-        // Provided in tree.
-        classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.3'
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
     }
 }
 
 if ('multi' == System.env.AB_CD) {
     // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale.  This
     // causes the
     //
     // |mach build| > |mach gradle| > |make gradle-targets| > AndroidManifest.xml > strings.xml > multi/brand.dtd
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
 #Fri Sep 16 15:41:50 PDT 2016
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
-distributionSha256Sum=ed7e9c8bb41bd10d4c9339c95b2f8b122f5bf13188bd90504a26e0f00b123b0d
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionSha256Sum=5c07b3bac2209fbc98fb1fdf6fd831f72429cdf8c503807404eae03d8c8099e5
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -6,17 +6,16 @@ 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'
@@ -69,16 +68,24 @@ android {
             // shrinkResources true
             minifyEnabled true
             proguardFile "${topsrcdir}/mobile/android/config/proguard/proguard.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 
+        }
+        release {
+            debuggable isDebuggable
+        }
     }
 
     // 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
@@ -234,82 +241,65 @@ android {
             // 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}"
+    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "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}"
+        implementation "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+        implementation "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "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-analytics:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.google.android.gms:play-services-analytics:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "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}"
+        implementation "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "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'
+    localImplementation 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
+    localOldImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
+    officialImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
+    officialImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
+    testImplementation '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).
-    //
-    // Official builds compile a hacked up app:Official..Debug, but need the
-    // release versions of the dependencies, in order to not have debugging
-    // information.  It's not yet possible to specify just officialDebug, so we
-    // hack around it here.
-    if (mozconfig.substs.MOZILLA_OFFICIAL) {
-        debugCompile project(path: ':geckoview', configuration: "release")
-    } else {
-        debugCompile project(path: ':geckoview', configuration: "debug")
-    }
-    releaseCompile project(path: ':geckoview', configuration: "release")
+    implementation project(path: ':geckoview')
+    implementation project(path: ':thirdparty')
 
-    if (mozconfig.substs.MOZILLA_OFFICIAL) {
-        debugCompile project(path: ':thirdparty', configuration: "release")
-    } else {
-        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'
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'org.robolectric:robolectric:3.1.2'
+    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.
-    androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.4'
+    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/']
@@ -376,17 +366,17 @@ android.applicationVariants.all { varian
     // :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)
     }
 }
 
 android.applicationVariants.all { variant ->
-    configureVariantWithJNIWrappers(variant, "Fennec")
+    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 " +
@@ -413,17 +403,17 @@ android.applicationVariants.all { varian
         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/reports/findbugs/findbugs-${variant.name}-output.html"
+            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"
@@ -437,17 +427,17 @@ android.applicationVariants.all { varian
         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/reports/findbugs/findbugs-${variant.name}-output.xml"
+            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
@@ -463,18 +453,17 @@ android.applicationVariants.all { varian
     // 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,
+            [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">.
--- a/mobile/android/app/src/photon/res/values/styles.xml
+++ b/mobile/android/app/src/photon/res/values/styles.xml
@@ -574,18 +574,18 @@
         <item name="android:layout_marginRight">5dip</item>
         <item name="android:layout_marginEnd">5dip</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:layout_centerVertical">true</item>
         <item name="android:background">@drawable/action_bar_button_inverse</item>
     </style>
 
     <style name="PopupAnimation">
-        <item name="@android:windowEnterAnimation">@anim/popup_show</item>
-        <item name="@android:windowExitAnimation">@anim/popup_hide</item>
+        <item name="android:windowEnterAnimation">@anim/popup_show</item>
+        <item name="android:windowExitAnimation">@anim/popup_hide</item>
     </style>
 
     <style name="ToastBase">
         <item name="android:background">@drawable/toast_background</item>
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_alignParentBottom">true</item>
         <item name="android:layout_centerHorizontal">true</item>
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -24,24 +24,29 @@
 
     <application android:label="@string/moz_app_displayname"
                  android:icon="@drawable/icon"
                  android:logo="@drawable/logo"
                  android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
                  android:hardwareAccelerated="true"
                  android:supportsRtl="true"
                  android:allowBackup="false"
+#ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+# Android-Gradle 3.0+ doesn't respect android:debuggable from AndroidManifest.xml.
+                 >
+#else
 # The preprocessor does not yet support arbitrary parentheses, so this cannot
 # be parenthesized thus to clarify that the logical AND operator has precedence:
 #   !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
 #if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
                  android:debuggable="true">
 #else
                  android:debuggable="false">
 #endif
+#endif
 
         <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
 
         <meta-data android:name="android.max_aspect" android:value="2.1"/>
 
 #ifdef MOZ_NATIVE_DEVICES
         <service android:name="org.mozilla.gecko.RemotePresentationService" android:exported="false"/>
 #endif
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -33,22 +33,18 @@ def computeVersionCode() {
         code += Integer.parseInt(parts[2])
     }
 
     return code;
 }
 
 android {
     compileSdkVersion project.ext.compileSdkVersion
-    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
-        defaultPublishConfig 'release'
-        publishNonDefault true
-
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
 
         versionCode computeVersionCode()
         versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
         consumerProguardFiles 'proguard-rules.txt'
 
@@ -149,18 +145,18 @@ android {
 
             assets {
             }
         }
     }
 }
 
 dependencies {
-    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 }
 
 apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
 
 android.libraryVariants.all { variant ->
     // Like 'debug', 'release', or 'withGeckoBinaries'.
     def buildType = variant.buildType.name
 
@@ -213,17 +209,17 @@ android.libraryVariants.all { variant ->
         classifier 'sources'
         description = "Generate Javadoc for build variant $name"
         destinationDir = new File(destinationDir, variant.baseName)
         from files(variant.javaCompile.source)
     }
 }
 
 android.libraryVariants.all { variant ->
-    configureVariantWithJNIWrappers(variant, "Generated")
+    configureLibraryVariantWithJNIWrappers(variant, "Generated")
 }
 
 apply plugin: 'maven'
  
 uploadArchives {
     repositories.mavenDeployer {
         pom.groupId = 'org.mozilla'
         pom.artifactId = "geckoview-${mozconfig.substs.MOZ_UPDATE_CHANNEL}-${mozconfig.substs.ANDROID_CPU_ARCH}"
--- a/mobile/android/geckoview_example/build.gradle
+++ b/mobile/android/geckoview_example/build.gradle
@@ -1,15 +1,14 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/geckoview_example"
 
 apply plugin: 'com.android.application'
 
 android {
     compileSdkVersion project.ext.compileSdkVersion
-    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
 
         applicationId "org.mozilla.geckoview_example"
         versionCode 1
@@ -31,30 +30,27 @@ android {
         }
         withoutGeckoBinaries { // Logical negation of withGeckoBinaries.
             initWith debug
         }
     }
 }
 
 dependencies {
-    testCompile 'junit:junit:4.12'
+    testImplementation 'junit:junit:4.12'
 
-    compile 'com.android.support:support-annotations:23.4.0'
+    implementation 'com.android.support:support-annotations:23.4.0'
 
-    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
-    androidTestCompile 'com.android.support.test:runner:0.5'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
+    androidTestImplementation 'com.android.support.test:runner:0.5'
     // Not defining this library again results in test-app assuming 23.1.1, and the following errors:
     // "Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (23.4.0) and test app (23.1.1) differ."
-    androidTestCompile 'com.android.support:support-annotations:23.4.0'
+    androidTestImplementation 'com.android.support:support-annotations:23.4.0'
 
-    debugCompile project(path: ':geckoview', configuration: "debug")
-    releaseCompile project(path: ':geckoview', configuration: "release")
-    withGeckoBinariesCompile project(path: ':geckoview', configuration: "withGeckoBinaries")
-    withoutGeckoBinariesCompile project(path: ':geckoview', configuration: "withoutGeckoBinaries")
+    implementation project(path: ':geckoview')
 }
 
 apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
 
 android.applicationVariants.all { variant ->
     // Like 'debug', 'release', or 'withoutGeckoBinaries'.
     def buildType = variant.buildType.name
 
--- a/mobile/android/gradle.configure
+++ b/mobile/android/gradle.configure
@@ -87,24 +87,49 @@ def gradle_android_app_tasks(build_confi
         'app:assemble{app.variant.name}'.format(app=build_config.app),
         'app:assemble{app.variant.name}AndroidTest'.format(app=build_config.app),
     ]
 
 set_config('GRADLE_ANDROID_APP_TASKS', gradle_android_app_tasks)
 
 
 @depends(gradle_android_build_config, check_build_environment)
+@imports(_from='itertools', _import='imap')
 def gradle_android_app_apks(build_config, build_env):
     '''Paths to APK files produced by |mach android assemble-app|.'''
-    flavor = '-'.join(build_config.app.variant.productFlavors)
-    buildType = build_config.app.variant.buildType
-    f = '{}/gradle/build/mobile/android/app/outputs/apk/app-{}-{}.apk'
-    g = '{}/gradle/build/mobile/android/app/outputs/apk/app-{}-{}-androidTest.apk'
-    return namespace(app_apk=f.format(build_env.topobjdir, flavor, buildType),
-                     app_androidTest_apk=g.format(build_env.topobjdir, flavor, buildType))
+    def capitalize(s):
+        # str.capitalize lower cases trailing letters.
+        if s:
+            return s[0].upper() + s[1:]
+        else:
+            return s
+
+    def uncapitalize(s):
+        if s:
+            return s[0].lower() + s[1:]
+        else:
+            return s
+
+    # Like 'officialPhoton'.
+    productFlavor = uncapitalize(''.join(imap(capitalize, build_config.app.variant.productFlavors)))
+    # Like 'official-photon'.
+    product_flavor = '-'.join(build_config.app.variant.productFlavors)
+
+    substs = {
+        'topobjdir': build_env.topobjdir,
+        'productFlavor': productFlavor,
+        'product_flavor': product_flavor,
+        'buildType': build_config.app.variant.buildType,
+    }
+
+    f = '{topobjdir}/gradle/build/mobile/android/app/outputs/apk/{productFlavor}/{buildType}/app-{product_flavor}-{buildType}.apk'
+    g = '{topobjdir}/gradle/build/mobile/android/app/outputs/apk/androidTest/{productFlavor}/{buildType}/app-{product_flavor}-{buildType}-androidTest.apk'
+
+    return namespace(app_apk=f.format(**substs),
+                     app_androidTest_apk=g.format(**substs))
 
 set_config('GRADLE_ANDROID_APP_APK', gradle_android_app_apks.app_apk)
 set_config('GRADLE_ANDROID_APP_ANDROIDTEST_APK', gradle_android_app_apks.app_androidTest_apk)
 
 
 @depends(gradle_android_build_config)
 def gradle_android_test_tasks(build_config):
     '''Gradle tasks run by |mach android test|.'''
@@ -141,18 +166,18 @@ def gradle_android_findbugs_tasks(build_
     return [
         'app:findbugsXml{app.variant.name}'.format(app=build_config.app),
         'app:findbugsHtml{app.variant.name}'.format(app=build_config.app),
     ]
 
 set_config('GRADLE_ANDROID_FINDBUGS_TASKS', gradle_android_findbugs_tasks)
 
 
-@dependable
-def gradle_android_archive_geckoview_tasks():
+@depends(gradle_android_build_config)
+def gradle_android_archive_geckoview_tasks(build_config):
     '''Gradle tasks run by |mach android archive-geckoview|.'''
     return [
         'geckoview:assembleWithGeckoBinaries',
         'geckoview_example:assembleWithGeckoBinaries',
         'geckoview_example:assembleWithGeckoBinariesAndroidTest',
         'geckoview:uploadArchives',
     ]
 
--- a/mobile/android/gradle/with_gecko_binaries.gradle
+++ b/mobile/android/gradle/with_gecko_binaries.gradle
@@ -1,9 +1,9 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // We run fairly hard into a fundamental limitation of the Android Gradle
 // plugin.  There are many bugs filed about this, but
 // https://code.google.com/p/android/issues/detail?id=216978#c6 is a reason one.
 // The issue is that we need fine-grained control over when to include Gecko's
@@ -121,68 +121,89 @@ ext.configureVariantWithGeckoBinaries = 
     generateAssetsTask.dependsOn syncAssetsFromDistDir
 
     def sourceSet = productFlavor ? "${productFlavor}${buildType.capitalize()}" : buildType
     android.sourceSets."${sourceSet}".assets.srcDir syncOmnijarFromDistDir.destinationDir
     android.sourceSets."${sourceSet}".assets.srcDir syncAssetsFromDistDir.destinationDir
     android.sourceSets."${sourceSet}".jniLibs.srcDir syncLibsFromDistDir.destinationDir
 }
 
-ext.configureVariantWithJNIWrappers = { variant, module ->
-    def jarTask
-    if (module == 'Generated') {
-        jarTask = tasks["package${variant.name.capitalize()}JarArtifact"]
-    } else {
-        jarTask = tasks["jar${variant.name.capitalize()}Classes"]
-    }
-
-    if (jarTask.outputs.files.size() != 1) {
-        throw new GradleException("Jar task output multiple files other than one single jar")
-    }
-
-    // At configuration time, the classpath of dependencies with internal_impl
-    // JAR files may not be correct.  This manifests in
+ext.configureLibraryVariantWithJNIWrappers = { variant, module ->
+    // Library variants have two essentially independent transform* tasks:
+    //
+    // - ...WithSyncLibJars... is used by assemble* and bundle*
+    // - ...WithPrepareIntermediateJars... is used by consuming applications.
     //
-    // 'Exception in thread "main" java.lang.NoClassDefFoundError: android/support/v4/app/ActivityCompatApi23$RequestPermissionsRequestCodeValidator'
-    //
-    // when running |mach gradle clean app:generateJNI...|.  We work around this
-    // by configuring the classpath at evaluation-time, not configuration-time.
-    //
-    // The specific dependency on the `prepareDependencies` task may not be
-    // necessary, but commits like
-    // https://github.com/evant/gradle-retrolambda/commit/15108c65ee43be499a1359d9d4f88b0851d46769
-    // suggest that it is.  It certainly doesn't hurt.
-    def prepareDependenciesTask = tasks.getByName("prepare${variant.name.capitalize()}Dependencies")
+    // It's not really possible to insert something immediately _after_
+    // ...WithPrepareIntermediateJars..., so we make the consuming moz.build
+    // system invoke this target directly, and force the
+    // ...WithPrepareIntermediateJars... dependency.  The real consumer of the
+    // JNI wrappers is the moz.build system, which always builds geckoview to
+    // consume from Fennec, so that dependency likely adds less to the build time.
+    def jarTask = tasks["transformClassesAndResourcesWithPrepareIntermediateJarsFor${variant.name.capitalize()}"]
+    def output = jarTask.outputs.files.find({ it.absolutePath.contains('/classes.jar') })
 
     def wrapperTask
     if (System.env.IS_LANGUAGE_REPACK == '1') {
         // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and don't
         // really have a build environment.
         wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}")
     } else {
         wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}", type: JavaExec) {
             classpath "${topobjdir}/build/annotationProcessors/annotationProcessors.jar"
-    
+        
             // Configure the classpath at evaluation-time, not at
             // configuration-time: see above comment.
             doFirst {
                 classpath variant.javaCompile.classpath
                 // Include android.jar.
                 classpath variant.javaCompile.options.bootClasspath
             }
     
             main = 'org.mozilla.gecko.annotationProcessors.AnnotationProcessor'
             args module
-            args jarTask.outputs.files.iterator().next()
-    
+            args output
+            
             workingDir "${topobjdir}/mobile/android/base"
-    
+            
             dependsOn jarTask
-            dependsOn prepareDependenciesTask
         }
     }
 
     if (module == 'Generated') {
         tasks["bundle${variant.name.capitalize()}"].dependsOn wrapperTask
     } else {
         tasks["assemble${variant.name.capitalize()}"].dependsOn wrapperTask
     }
 }
+
+ext.configureApplicationVariantWithJNIWrappers = { variant, module ->
+    def jarTask = tasks["bundleAppClasses${variant.name.capitalize()}"]
+    def output = jarTask.outputs.files.find({ it.absolutePath.contains('/classes.jar') })
+
+    def wrapperTask
+    if (System.env.IS_LANGUAGE_REPACK == '1') {
+        // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and don't
+        // really have a build environment.
+        wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}")
+    } else {
+        wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}", type: JavaExec) {
+            classpath "${topobjdir}/build/annotationProcessors/annotationProcessors.jar"
+        
+            // Configure the classpath at evaluation-time, not at
+            // configuration-time: see above comment.
+            doFirst {
+                classpath variant.javaCompile.classpath
+                // Include android.jar.
+                classpath variant.javaCompile.options.bootClasspath
+            }
+        
+            main = 'org.mozilla.gecko.annotationProcessors.AnnotationProcessor'
+            args module
+            args output
+        
+            workingDir "${topobjdir}/mobile/android/base"
+    
+            // This forces bundling, which isn't usually part of the assemble* process.
+            dependsOn jarTask
+        }
+    }
+}
--- a/mobile/android/thirdparty/build.gradle
+++ b/mobile/android/thirdparty/build.gradle
@@ -1,20 +1,16 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/thirdparty"
 
 apply plugin: 'com.android.library'
 
 android {
     compileSdkVersion project.ext.compileSdkVersion
-    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
-        defaultPublishConfig 'release'
-        publishNonDefault true
-
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
@@ -42,21 +38,21 @@ android {
                 // here is only the no-op library for mach-based builds.
                 exclude 'com/squareup/leakcanary/**'
             }
         }
     }
 }
 
 dependencies {
-    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     if (mozconfig.substs.MOZ_ANDROID_MMA) {
-        compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-        compile "com.android.support:support-annotations:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+        implementation "com.android.support:support-annotations:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        implementation "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
         // This is cosmetic.  See the excludes in the root project.
--- a/old-configure.in
+++ b/old-configure.in
@@ -2100,17 +2100,17 @@ AC_SUBST(MOZ_MULET)
 
 dnl ========================================================
 dnl Ensure Android SDK and build-tools versions depending on
 dnl mobile target.
 dnl ========================================================
 
 case "$MOZ_BUILD_APP" in
 mobile/android)
-    MOZ_ANDROID_SDK(23, 23, 25.0.3, 26.0.0 26.0.0-dev 25.3.2 25.3.1)
+    MOZ_ANDROID_SDK(23, 23, 26.0.2, 26.0.0 26.0.0-dev 25.3.2 25.3.1)
     ;;
 esac
 
 dnl ========================================================
 dnl =
 dnl = Toolkit Options
 dnl =
 dnl ========================================================
--- a/python/mozboot/mozboot/android-packages.txt
+++ b/python/mozboot/mozboot/android-packages.txt
@@ -1,6 +1,6 @@
 platform-tools
-build-tools;25.0.3
+build-tools;26.0.2
 platforms;android-23
 extras;android;m2repository
 extras;google;m2repository
 emulator
--- a/taskcluster/scripts/misc/android-gradle-dependencies/after.sh
+++ b/taskcluster/scripts/misc/android-gradle-dependencies/after.sh
@@ -1,16 +1,16 @@
 #!/bin/bash -vex
 
 set -x -e
 
 echo "running as" $(id)
 
 : WORKSPACE ${WORKSPACE:=/builds/worker/workspace}
-: GRADLE_VERSION ${GRADLE_VERSION:=3.4.1}
+: GRADLE_VERSION ${GRADLE_VERSION:=4.1}
 
 set -v
 
 # Package everything up.
 pushd $WORKSPACE
 mkdir -p android-gradle-dependencies /builds/worker/artifacts
 
 cp -R ${NEXUS_WORK}/storage/jcenter android-gradle-dependencies