Exploring Android Studio: The Gradle Build System

Larry

Larry Schiefer

Welcome to Part 10 of this review of the Pluralsight course Exploring Android Studio by Larry Schiefer.

Larry Schiefer is the CTO and co-founder of HiQES, a mobile, embedded, and application development service company. Larry has over 16 years of experience leading teams, and designing and developing high performance and robust software, and is a Google GDE.

The Gradle Build System

Gradle is a flexible and highly extensible build system. It can be used for multiple languages and target platforms.

In the years before Gradle, Android apps were built either with Ant or a combination of Ant and GNU Make.

Gradle is much more powerful and uses Groovy for its scripting language providing a declarative environment to write scripts. This gives us much more power than writing XML-based rules and targets.

It has built-in support for Maven and Ivy repositories, which can be used for dependencies, as well as building code.

Gradle and Groovy Background

Larry provides us with a history of the Gradle releases:

  • v0.7 July 2009
  • v0.8 Sept 2009
  • v0.9 Dec 2010
  • v1.1 June 2012
  • Further point releases roughly every 1 to 3 months

The development goals of Gradle were:

  • Ease of code and resource re-use
  • Ease of app variants
  • Ease of configuration and customization
  • Good IDE integration

A fundamental feature is it is declarative meaning we don’t need to define everything we wish to build and how to build it.

With Gradle we build by convention over configuration.

Groovy

Groovy is a dynamic programming language, sharing many Java features. All code is compiled into JVM bytecode meaning it can interoperate with Java code.

As an example, Larry shows the code that creates the area of a circle, and shows how we can reimplement it using a closure.

This is a lot like Java 8 lambdas, or LINQ expressions from .NET.

Gradle Concepts

The only things we need to define are the project type, any dependencies and any customizations.

Gradle uses a Directed Acyclic Graph to determine what order tasks run in.

There are three phases:

  1. Initialization: what projects to build
  2. Configuration: what tasks to perform for the projects
  3. Execution: perform the tasks to build

Larry explains each of these into detail.

Normally our settings.gradle file specifies project paths:

include 'project1', 'project2', 'project3:subproj1'

Gradle uses the term projects or subprojects to describe the components which make up our top level project build. Android Studio on the other hand uses the term module. Just remember that these mean the same thing.

We can use includeFlat to specify directories beneath our root directory.

Larry also explains how to override the include directive’s assumption that subproject and path are the same.

Gradle Configuration Phase Demo

We begin inside Android Studio looking at a build.gradle file. Although it is optional for gradle, Android Studio always creates one for our projects.

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

allprojects {
    repositories {
        mavenCentral()
    }
}

Larry explains the purpose of both the buildscript closure and the allprojects closure here.

Configuration injection is used to modify the configuration of all projects. Larry describes it as a subtle but powerful feature.

To learn more about configuration injection see the Cross project configuration of the official gradle docs.

We also see build.gradle files in the app and util_lib directories. The one in the app folder has a lot of Android specific items. The next module will explain these Android DSL items.

From the Android terminal we can type gradlew to run the gradle wrapper.

We see a warning the dynamic properties are being deprecated. We also see our print line execute and the property contents are printed.

Larry says this is one of the more confusing things about the gradle build process. Our build script gets executed in the configuration phase.

The execution phase of gradle only handles the execution of tasks.

The next lesson aims to crystalize our understanding of this.

Gradle Execution Phase

By the end of the configuration phase, Gradle has a DAG of tasks needing to be executed.

A gradle task is one or more actions to be performed.

New tasks can be created and existing ones can be modified.

Larry shows a simple example of a gradle task:

task executionTest << {
    println "Execution phase test"
}

He demonstrates running this task using the gradle wrapper:

gradlew –info executionTest

He also explains why we need the left shift << syntax.

An example of modifying an existing tasks is adding a print at the end of the assembleDebug task. We can use the left shift syntax to do this for us:

assembleDebug << {
    println "Action added to assembleDebug"
}

Miscellaneous Advanced Features

Larry says the gradle documentation and the groovy documentation are extensive, well written and invaluable resources.

If you want to dive deeper with Pluralsight, Jeremy Jarell has a course called Groovy Fundamentals and Kevin Jones has produced Gradle Fundamentals.

However Larry has more valuable advice here.

To begin with, we can use doFirst to inject an action at the start.

Gradle also comes with pre-defined task templates, and Larry says these helpers are great starting points. An example tasks template is the Copy task.

Larry recommends looking for an existing task type before defining your own task from the ground up.

Task Descriptions

He discusses Task descriptions. These use the description property:

description “My task description”

We see a brief description added for our timeDebugBuild task and the effect that this has.

Handling Errors

Also covered in this lesson is how we can stop the build if we encounter an error, and how to report an error for our task without killing the build entirely. We do this using exceptions such as these:

throw new TaskExecutionException()
throw new StopActionException()
throw new StopExecutionException()

Callback Notifications

We can add special callback closures in our build.gradle files.

The afterEvaluate property handles cases where certain features should be enabled. Larry says this is particularly useful for multi-project builds.

We can also use the tasks property to modify or update properties when tasks are added.

Larry demonstrates in Android Studio, making it print every time a task is added to our build.

The Gradle Wrapper

This is what we use to build via the command line. It is part of Gradle, not Android Studio, and wraps around the Gradle executable when performing the build.

It also ensures the Gradle executable being used is the correct version. Larry says this is particularly useful for continuous integration or build servers.

We see a demonstration of the wrapper on a demo project with a simple task definition:

task wrapper(type: Wrapper) {
gradleVersion = ‘1.10’
}

Finally, Larry says the wrapper pulls down the correct version of the Gradle executable over the network and this may not work if you are behind a corporate firewall.

We see the Java JVM properties for setting your proxy settings if this is the case.

Gradle Plugins

Gradle is a very flexible system for builds and scripting operations. In this lesson we see how it can build something for us.

Plugins are an extension to gradle, adding new tasks domain specific objects and more.

Gradle comes with plugins for Java, Scala, Groovy and creating package distributions.

Gradle supports Domain Specific Languages (DSLs) and each plugin can add it’s own domain.

Larry introduces the Java plugin SourceSet. This defines a source set as a group of files to be compiled and executed together.

So how do we build Java code? Take a look at this project on Github.

The DemoJava code is very simple and just prints “Java build test” to the console.

To build our project:

>gradlew build

We can then test with

>java -classpath build/libs/demojava.jar com.pluralsight.demojava.DemoJava

and see “Java build test” is output.

Dependencies with Maven

Maven is a build automation tool primarily used for Java projects. It also supports other languages including C#, Ruby and Scala.

The build is done by convention and set by XML files.

In this lesson we only look at how Maven can be used by Gradle and Android Studio.

We load up the SDK manager and scroll down to the extras section.

The “Android Support Repository” is listed there and it is a Maven repository.

We see an example build.grade file with a dependencies section at the bottom:

dependencies {
    compile 'com.android.support:support-v4:+'
    compile filetree(dir: 'libs', include: ['*.jar'])
    compile project(':util_lib')
}

Larry explains the first dependency is compiled, which means the compilation order must be defined. It is a shorthand form of this:

compile group:'com.android.support', name:'support-v4', version:'+'

Gradle searches a repository for an external dependency, and the repository could either be a Maven or an Ivy one depending on your configuration.

In the Gradle Configuration Phase Demo near the beginning of this module, we saw the allprojects property set to use the mavenCentral repository.

Larry says there are two ways to get the dependency information and Android Studio’s UI makes it easy to add dependencies.

He demonstrates how to view the library dependencies and we see the Maven Project Object Model (POM) file.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s