Trying Android Gradle Plugin 2.5

Last updated 02/23/17

Trying this plugin with a large Android test project

You can try building a mock Android project with a large number of dependencies to compare the effects of the new plugin. The following steps set up the mock project to first run with version 2.3 of the plugin, before building it with 2.5. To get started, open the command line from your desired target directory and run the following commands:


> git clone https://github.com/jmslau/perf-android-large.git

> cd perf-android-large

> git checkout android-2.3

> export ANDROID_HOME=<path to your Android SDK>

> ./gradlew help


The ./gradlew help command runs through the Gradle configuration phase. To run the project using version 2.5 of the plugin, proceed as follows:


> git checkout android-2.5

> ./gradlew help


You should see faster configuration times. You can also  trigger other build tasks to compare build performance between the two versions.

Trying this plugin with your project

If you want to compare the benefits of the 2.5 plugin on your own project, read the sections below to learn how to apply the plugin and specific nightly version of Gradle, and adapt your project to some breaking changes.

Update Gradle version

Because many of the changes are built on very new Gradle APIs that are going through changes, the current plugin requires a very specific nightly build of Gradle.

The current required version is 3.5-20170213202653+0000


To use the nightly build, you need to update the URL in gradle-wrapper.properties as follows:


distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-3.5-20170213202653+0000-all.zip

# Keep the stable version of the plugin commented out, so you can switch back easily.

# distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip


Although there is no matching versions of Android Studio yet, Android Studio 2.3 is compatible with this version of the plugin.

Apply the alpha plugin

To apply the plugin to a sample project, change the plugin version in your project-level build.gradle file to the following:


dependencies {

   classpath 'com.android.tools.build:gradle:2.5.0-alpha-preview-01'

   ...

}


Because the new plugin introduces changes that may break your current build, if you want to use this plugin with a project you are currently developing, we recommend that you conditionally apply the plugin (and configurations specific to the plugin) using a command line argument. For example, add something like the following to your project-level build.gradle file:


dependencies {

  // Looks for the following command-line argument.

  if(project.hasProperty('useAlpha')){

      classpath 'com.android.tools.build:gradle:2.5.0-alpha-preview-01'

  } else {

      classpath 'com.android.tools.build:gradle:2.3.0-rc1'

  }

}


To build your project using Android Gradle plugin 2.5.0, simply pass ‘useAlpha’ as an argument as follows:


./gradlew assembleDebug -PuseAlpha


Note: If you want to switch to using the stable version of  the plugin, make sure to also update gradle-wrapper.properties to use a stable version of Gradle. Alternatively, you can simply bypass the wrapper and use a local installation of stable Gradle by running with gradle, instead of ./gradlew.

Adapt to Breaking Changes

The following sections describe changes to the build model in version 2.5.0. Code samples in the sections below also show you how to update your build configuration to work with the new plugin and only apply those configurations when you pass the command line argument from the previous section.

New dependency configurations

Gradle 3.4 introduced a new java-library plugin that includes new configurations allowing control over publication to compile and runtime classpaths (for inter-module dependencies). To be consistent with Gradle’s library plugin, Android plugin 2.5.0 is moving to these dependency configurations.


  • api: the dependency is available to the library module at compile time. The dependency is also available at compile time and runtime for the consumers of this library module. This configuration behaves just like compile (which is now deprecated).

  • implementation: the dependency is available to the library module at compile time, and it’s available only at runtime for the consumers of this library module. For large multi-project builds, using implementation instead of api/compile can give significant build time improvements because it reduces the amount of projects that the build system needs to recompile.

  • compileOnly: the dependency is available to the library module only at compile time. The dependency is not available to the consumers of this library module, neither for compilation or runtime. This configuration behaves just like provided (which is now deprecated).

  • runtimeOnly: the dependency is available to the library module, and its consumers, only at runtime. This configuration behaves just like apk (which is now deprecated).


The charts here explain the relationship between the different configurations.


Note: compile, provided, and apk are currently still available. However, they will disappear in the next major release of the Android plugin.


Just like the current stable versions of the Android plugin, the above configurations are available for flavor- or build type-specific dependencies. For example, you can use api to make the dependency available to all variants, or you can use redApi to make it available to just the ‘red’ variant(s) of the module.

Resolving Dependencies

The configuration that is used to resolve all the dependencies of a variant is composed of the above configurations, for the common code/config (api, implementation, compileOnly) as well as the flavor- and build type-specific components (redApi, debugImplementation, …)


These configurations have been renamed:

  • _<variant>Compile<variant>CompileClasspath

  • _<variant>Apk<variant>RuntimeClasspath


These configurations are the ones that gets resolved, and Gradle now allows plugins to mark other configurations as non resolvable. The android plugin now does so. Plugins attempting to resolve other configurations (debugCompile for instance) will fail. The result of this resolution would not be accurate or reflect what goes in the variant in all cases anyway.


Plugins or build files that set a resolution strategy on the the resolved configuration will need to be adapted to use the new name. Due to delayed dependency resolution it is now possible to set the resolution strategy while using the variant API. We added getters to access the Configuration objects of a variant.


The following sample shows how to use the new API and how to dynamically use the old or new ways:


// Previously, you had to apply a custom resolution strategy during the

// configuration phase, rather than in the execution phase. That’s

// because, by the time the variant was created and the Variant API was

// called, the dependencies were already resolved. This no longer works, and

// you should use this method only while using an older version of the plugin.

configurations {

   if (!project.hasProperty('useAlpha') {

       _debugCompile

       _debugApk

   }

}

...


if (!project.hasProperty('useAlpha')) {

   configurations._debugCompile.resolutionStrategy {

     ...

   }

}


// Because the new build model delays dependency resolution, you should

// query and modify the resolution strategy using the Variant API.

android {

   applicationVariants.all { variant ->

       if (project.hasProperty('useAlpha')) {

           variant.getCompileClasspath().resolutionStrategy {

               ...

           }

           variant.runtimeClasspath.resolutionStrategy {

               ...

           }

           variant.getAnnotationProcessorClasspath().resolutionStrategy {

               ...

           }

       }

   }

}


Note: The preview version of the new plugin looks for configurations using the old name and  will fail if it finds them. Otherwise it would silently ignore the custom resolution strategy. We may change this based on feedback, but we would like to find a way to ease migration.

Publishing Dependencies

The following configurations hold the transitive dependencies of a library for consumption by its consumers:

  • <variant>ApiElements

  • <variant>RuntimeElements

There used to be a single configuration per variant called: <variant>. Since a library can now control what consumers see for compilation, using the implementation and api configurations described in the previous section, there are now two configurations, one for compilation of the consumer(s), and one for runtime.


The charts here explain the relationship between the different configurations.

Variant Aware Dependency Management

Plugin 2.5.0 includes a new dependency mechanism that automatically matches variants when consuming a library. This means an app’s debug variant automatically consumes a library’s debug variant. This also works when using flavors—an app’s redDebug variant will consume a library’s redDebug variant.

The default matching strategy requires an exact match of build types and flavors for both the producer and consumers. However, there are some exceptions:


  • If a library does not have a flavor dimension that the app does, there is no issue. By default, the app’s redDebug variant consumes the library’s debug variant.

  • If a library has a flavor dimension that does not exist in the app (or the consumer), then something must be done on the app side to indicate which version to use.


For flavor matching to work between consumer and producer(s), the plugin now requires all flavors to belong to a named flavor dimension—even if you intend to use only a single dimension. This allows a project to properly handle cases where it consumes different producers using different concepts (e.g. color vs shape).


Adding two flavors to a dimension looks something like this:


// Specifies a flavor dimension.

flavorDimensions "color"


productFlavors {

    red {

     // Assigns this product flavor to the "color" flavor dimension.

     // This step is optional if you are using only one dimension.

     dimension "color"

     ...

   }


   blue {

     dimension "color"

     ...

   }

}


On the consumer side, for each flavor dimension that is missing (compared to the producers), you need to specify the flavor you want to consume from that dimension. You can do this using the new flavorSelection property:


android {

   // Chooses the ‘red’ flavor from libraries that specify a ‘color’ dimension.

   flavorSelection ‘color’, ‘red’

   // Chooses the ‘square’ flavor from libraries that specify a ‘shape’ dimension.

   flavorSelection ‘shape’, ‘square’

}


Note: You can not currently set this per-variant on the consumer side, though we intend to change this in the future. Additionally,  if two libraries each configure a ‘color’ dimension, you can not specify the ‘red’ flavor for the first library and the ‘blue’ flavor for the second library.


Note: You cannot easily use the old mechanism for manual variant dependency anymore, even though the Gradle API for it is still present. The configuration provided to the project() DSL now needs to match the consumer in build type and flavors (and other attributes). For instance, it is not possible to make a debug variant consume a release variant through this mechanism because the producer and consumer would not match. (In this case, the name debug refers to the published configuration object mentioned above in the Publishing Dependencies section.) Now that we publish two configurations, one for compiling and one for runtime, this old way of selecting one configuration really doesn’t work anymore.


In order to have your build file support both cases, you can check for ‘useAlpha’ again:


dependencies {

   if (project.hasProperty('useAlpha') {

       compile project(‘:foo’)

   } else {

       debugCompile project(path ‘:foo’, configuration: ‘debug’)

       releaseCompile project(path ‘:foo’, configuration: ‘release’)

   }

}


While the default behavior for matching is exact match (“red” must match “red”), Gradle has a new DSL to offer some flexibility around attribute matching. You could for instance setup some rules saying that if a consumer asks for red, then red or magenta are compatible.


The API is here, but it is not trivial to set this up to configure the built-in attributes of the Gradle plugin. We are planning on making this easier in future updates.

Wear App dependencies

Typically, variant dependencies are a combinations of all the component graphs. For example, the blueDebug variant is made up of compile, blueCompile, and debugCompile. All three dependencies are put together and resolved as a single graph.


In previous versions of the plugin, this did not apply to the dependencies used to a embed wear application into the main app. All the <component>WearApp graphs were resolved separately and the highest priority one won. For example, you could do something like the following, and the blue variant(s) would use :wear2 and all other ones would use :wear1.


dependencies {

   wearApp     project(‘:wear1’)

   blueWearApp project(‘:wear2’)

}


To support variant aware dependency management for embedded wear apps, plugin 2.5.0 now combines all the graphs together before resolving them, similarly to how other dependencies are handled. So the example above would have to be rewritten as follows:


dependencies {

   redWearApp project(‘:wear1’)

   greenWearApp project(‘:wear1’)

   blueWearApp project(‘:wear2’)

}


One benefit though is that if your wearable configures the same flavors as your main app, you don’t need to use the <flavor>WearApp configuration, you can simply specify the wearApp configuration, and each variant of the main app will consume the matching variant on the wearable side:


dependencies {

   // You can’t mix the following with configurations from the previous sample.

   wearApp  project(‘:wearable’)

}

Separate Test Module

Separate test modules are now variant aware (see the section above) when using plugin 2.5.0. This means that specifying targetVariant is no longer necessary.


Each variant in the test module will attempt to test a matching variant in the target project. By default, test modules contain only a debug variant, but you can create new build types and new flavors to create new variants to match the tested app project.  A connectedCheck task is created for each variant.


To make the Test project test a different build type only, and not the debug one, use VariantFilter to disable the debug variant in the test project.


android {

   variantFilter { variant ->

       if (variant.buildType.name.equals(‘debug’) {

           variant.setIgnore(true);

       }

   }

}


If the test project is meant to test a single specific variant of an app that contains flavor(s), it is possible to use one or more flavorSelection properties to have the test project target a specific flavor (or set of flavors) without having to declare them itself.

Local Jars In Libraries

Previously library modules would handle dependencies on local JARs (that is, JAR files somewhere on disk rather than via maven coordinates) in a non-standard way, and would package them inside their AAR. Even in a multi-project build, consumers of the AAR would see these JAR files through the packaged version. Part of this was due to limitations in the Gradle APIs that the Android plugin used to in order to resolve dependencies.


Now that the plugin is using different APIs, consuming projects can see the local JARs as regular transitive dependencies, similar to maven coordinate based dependencies.


To adapt to the new Gradle APIs, the plugin had to change a few aspects to how it handles local JAR files.


Inter-project Publishing


  • Library modules no longer process local JARs. This is to speed up incremental builds that are caused by changes to a library module’s code.


  • Transforms on library modules now can affect only the PROJECT scope. Applying transforms using PROJECT_LOCAL_DEPS will fail. This scope is now deprecated—even on application modules where local JARs are now part of the EXTERNAL stream (the PROJECT_LOCAL_DEPS and SUB_PROJECT_LOCAL_DEPS streams are now always empty).


  • Enabling ProGuard on library modules no longer affects the library’s code. You should instead run ProGuard on the final application module.


  • Previously, Java resource conflicts between a library module and its local JAR dependencies had to be resolved in the library module. Because local JARs are no longer processed by the library module, the conflict must be resolved in the application that consumes the library.


Publishing to Maven repo


  • Nothing has changed with respect to publishing to a Maven repo. The local JARs are bundled and their non-class resources are merged in the main JAR file of the AAR. If the module enables ProGuard then all the JARs are merged into the main JAR file.

API change in variant Output

The Variant API regarding the different outputs of a variant is currently broken. We are making large changes internally to do less things during configuration time, and in some instances, this makes the plugin unable to know all of its outputs up front. We are working on cleaning this up, having some backward compatibility support and new dynamic APIs through call back to offer the same features, but this is not ready yet.

Incremental dexing

A new incremental dexing pipeline has been implemented. Now the Gradle plugin dex only the class files that have changed. It is enabled by default, but you can disable it by doing one of the following:

  • Passing -Pandroid.useDexArchive=false when running builds from command line e.g. ./gradlew :app:assembleDebug -Pandroid.useDexArchive=false.

  • Adding android.useDexArchive=false to the gradle.properties file in your root project.

The new pipeline should bring performance improvements for all non-minified variants. Support for the incremental dexing of minified variants will be added in the future.
Comments