Build / Gradle

Gradle Build – Part 5

Previously in this series we’ve looked at various aspects of the Gradle build system for Android and we’ll continue by looking at some simple techniques for simplifying our build configuration.

gradle_logoBack in part 2 of this series we looked at a trick for automatically getting minor updates to the Android Gradle plugin by using a wildcard in the version. However we did not update this when we created an Android library project in the previous article. While this shouldn’t cause us problems, using a different version of the build tool chain for different modules within the same project always carries risk. By specifying the Android Gradle plugin in each project means that we always have the inherent risk that we could update one and not the other. This becomes even more compounded as we add more modules to our project.

Fortunately we can resolve this quite easily by moving this declaration in to the build.gradle file of the parent project so that both the child projects inherit the same configuration setting. That way, if we change the version of the Android Gradle plugin that we wish to use, we only need do it in one place but all child projects will then pick up that change. So our previously empty build.gradle in the top level project now looks like this:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
    }
}

We can now remove the buildscript section from the build.gradle in both GadleLibrary and GradleTest projects.

If we compare the two build.gradle files in GradleLibrary and GradleTest projects we can see that the android section is also a duplication, so it would appear sensible to move this in to the parent configuration also. However if we do this we’ll get an error:

GradleTestProject > ./gradlew assemble --daemon

FAILURE: Build failed with an exception.

* Where:
Build file '/Users/markallison/Documents/Projects/mia/Styling Android/GradleTestProject/build.gradle' line: 11

* What went wrong:
A problem occurred evaluating root project 'GradleTestProject'.
> Could not find method android() for arguments [build_71g83e6ql1vljbhq5otjnq7hgg$_run_closure1@2cbe1bb4] on root project 'GradleTestProject'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 0.693 secs
GradleTestProject >

The reason for this is that the android plugin defines an extension object named android which enables the plugin to obtain configuration information from the build script. In our case this contains the SDK versions and build tool versions:

android {
    compileSdkVersion 18
    buildToolsVersion "18.0.0"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 18
    }
}

When we apply the plugin, the plugin registers this named extension object with Gradle, and only then can we add this configuration. If we move this to the parent build.gradle then Gradle tries to handle this extension object before the plugin has been applied in each child project and we therefore get an error.

One way that we can place defaults in the parent build.gradle, and then apply these defaults once the android extension object has been applied.

GradleTestProject/build.gradle:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
    }
}

ext.compileSdkVersion=18
ext.buildToolsVersion="18.0.0"
ext.minSdkVersion=7
ext.targetSdkVersion=18

Here we define four variables as extension properties – this is a mechanism for adding additional properties to a domain object, in this case the project object.

Now in GradleTestProject/GradleTest/build.gradle:

apply plugin: 'android'

dependencies {
    compile 'com.android.support:support-v4:18.0.0'
    compile project(':GradleLibrary')
}


android {
    compileSdkVersion rootProject.compileSdkVersion
    buildToolsVersion rootProject.buildToolsVersion

    defaultConfig {
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
    }
}

We apply the values which we obtain from the domain object of the root project. We also do the same for GradleTestProject/GradleLibrary/build.gradle and we can control the shared defaults from the parent project.

In Part 3 of this series we looked at the default source structure for our project, but what about if we have an existing project which does not conform to this structure? In the next article in this series we’ll have a look at how we can configure Gradle to build an existing project without having to move any of the source.

The source code for this article is available here.

© 2013 – 2014, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

2 Comments

  1. When you write it in such detail, it seems very good and capable but it is very complicated and hard to configure. Especially when you want to convert your existing projects with external libraries. I wish all of these to be automatic.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.