# Task Dependencies: Gradle

Last modified: Dec 21, 2019

Abstract

Gradle is a build manager based upon an Ant-like task dependency graph expressed in a more human-friendly notation, with a Maven-like ability to express standard project layouts and build conventions.

In this lesson we look at how Gradle combines some of the better features of Ant and Maven, while providing a more convenient notation than either.

We will look at how Gradle could be applied to some of our sample projects.

# 1 Gradle Overview

• Gradle devised by GradleWare, founded by Hans Dockter, released in 2012

• Has become the standard build tool for Android

• Tries to strike a middle ground between Ant and Maven

## 1.1 What to keep and leave from Ant

Keep:

• Portability
• Build commands are described in a platform-independent manner.
• Flexibility
• Almost any sequence of processing steps can be described.
• Allows for unconventional build sequences
• Allows for unconventional build targets

Leave

• XML as a build language.

“XML was an easy choice for a build tool a decade ago, when it was a new technology, developers were enthusiastic about it, and no one yet knew the pain of reading it in large quantities. It seemed to be human-readable, and it was very easy to write code to parse it. However, a decade of experience has shown that large and complex XML files are only easy for machines to read, not for humans. Also, XML’s strictly hierarchical structure limits the expressiveness of the format. It’s easy to show nesting relationships in XML, but it’s hard to express program flow and data access the way most common programming language idioms express them. Ultimately, XML is the wrong format for a build file.”

Tim Berglund, Learning and Testing with Gradle

• Inability to express simple control flow
• e.g., loop through all files in a directory and do task

## 1.2 What to keep and leave from Maven

Keep:

• Dependency management

• Gradle will work with Maven & Ivy repositories.
• Early versions of Gradle actually used Ivy, though eventually it gained its own dependency manager.
• Standard directory layouts and build conventions for common project types.

• If your project has nothing unusual about its build, you can sit back and use the defaults.

Leave

• XML as a build language.

• Inflexibility

• If your project has anything unusual about its build, changing the defaults in Maven is frustrating.
• Inability to express simple control flow

## 1.3 What does Gradle offer?

• Groovy is a scripting language that runs in a Java JVM
• Syntax is Java-based
• Interfacing with Java code is easy
• Gradle adds build-specific functions as a Groovy library
• Gradle is built on top of the Ant libraries.

• All conventional Ant tasks are available.
• Ant build files can be incorporated as functions of a Gradle build.

Hans Dockter describes Gradle, compares it to Ant and Maven, and shows lots of examples (in Eclipse).

## 1.4 The Gradle Wrapper

Suppose that you are building your project with Gradle.

• Other people may try to check out a copy of your code, including your Gradle build file.
• But maybe they won’t have Gradle on their machine.

If you set up your project with the Gradle Wrapper, you get a simple script named gradlew.

• You invoke your build via ‘gradlew’ instead of ‘gradle’
• gradlew checks to see if the system on which it is running has Gradle installed.
• If not, it downloads a copy and installs it under $USER_HOME/.gradle • It then invokes gradle (old installation or new), passing any parameters you supplied to the gradlew command. This works nicely for projects that distribute their source code via any of the version control systems that we will be discussing later. # 2 Running Gradle • gradle looks for its instructions in a file named, by default, build.gradle • The gradle command can name any task (or list of tasks) as targets to be built, e.g., gradle setup compile • If no target is given, gradle can use a default task if one has been declared in the build file. gradle Options Some useful options: -m, –dry-run List steps that would be run without actually doing anything. -t, –tasks List tasks that can be used as targets. -b filename, –build-file filename Use filename instead of the default build.xml. Also -file or -buildfile -Dproperty=value Sets a property (similar to make’s variables) -q, –quiet Suppress most output except for error messages. gradle Tasks Some built-in tasks that you can use as targets: tasks list all available tasks help By itself, explains how to get more help on using Gradle. With --task, ask for a description of a specific task. init Used to set up a new project using Gradle default properties. wrapper Adds the Gradle wrapper to the project. • Usually accompanied by an option --gradle-version versionNumber to generate a wrapper for a specific version of gradle. # 3 Build Files The gradle build file is a Groovy script, with Java-like syntax. Many of the more common Java API packages are imported automatically. task upper << { String myName = "Steven Zeil"; println myName; println myName.toUpperCase(); } If this is placed in build.gradle, then we can run it:$ gradle upper
:upper
Steven Zeil
STEVEN ZEIL

BUILD SUCCESSFUL

Total time: 1.747 secs
$## 3.1 Gradle Tasks The basic elements of a Gradle build file are tasks. • Gradle tasks can correspond Ant targets. A Gradle task can perform multiple actions. task upper { doLast { String myName = "Steven Zeil"; println myName; println myName.toUpperCase(); } } • Gradle tasks can correspond individual Ant tasks. A Gradle task can perform multiple actions. task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) } In Ant, we would have used a <copy> task within a larger Ant target for this purpose. ## 3.2 Anatomy of a Task ### 3.2.1 The phases of a Gradle run Before looking at the components of a task, we need to understand a bit about how Gradle works. A Gradle run occurs in four specific phases. 1. Initialization takes care of things that affect how Gradle itself will run. The most visible activity during this phase is loading Gradle plugins that add new behaviors. 2. Configuration involves collecting the build’s tasks, setting the properties of those tasks, and then deciding which tasks need to be executed and the order in which that will be done. 3. Execution is when the tasks that need to be executed get run. 4. Finalization covers any needed cleanup before the Gradle run ends. Software developers will be mainly concerned with the middle two phases. For example, if we need to compile some Java source code for a project, then we would want to configure that task by indicating the source code directory (or directories) and the destination for the generated code. With that information, Gradle can look to see if the .class files already exist from a prior run and whether the source code has been modified since then. Gradle can then decide whether or not the compilation task actually needs to be run this time. This decision is remembered during the execution phase when the task will or will not be run, accordingly. ### 3.2.2 Declaring tasks A Gradle task can be declared as easily as: task myTask Some tasks may need parameters: task copyResources (type: copy) ### 3.2.3 Configuring tasks You can add code to be run at configuration time by putting it in { } brackets just after the task name: task copyResources (type: Copy) copyResources { description = 'Copy resources into a directory from which they will be added to the Jar' from(file('src/main/resources')) into(file('target/classes')) } You can combine a configuration with the task declaration: task copyResources (type: Copy) { description = 'Copy resources into a directory from which they will be added to the Jar' from(file('src/main/resources')) into(file('target/classes')) } ### 3.2.4 Executable Behavior • Task types often have pre-defined behaviors. • For example, the Copy type copies files at execution time task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) } The from and into calls are performed at configuration time. The Copytype actually copies the files at execution time. • gradle will check to see, at configuration time, if the files already exist at the destination and appear to be no olderthan the ones at the source. If so, the copyResources task will be skipped at execution time. ### 3.2.5 Adding Executable Behavior • You can add code to be run at execution time by attaching it, within { }, using the doLast operation. task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) doLast { println 'Copy has been done.' } } or you can use that operation to add the code to an already-declared task object. task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) } ⋮ copyResources.doLast { println 'Copy has been done.' } ## 3.3 Task Dependencies task setupFormat task setupGraphics task setupSourceCode task generatePDFs (dependsOn: 'setup') generatePDFs.doLast { ➀ println 'in task generatePDFs' } task setup (dependsOn: ['setupFormat', 'setupGraphics', 'setupSourceCode']) setup.doLast { ➁ println 'in task setup' } task deploy (dependsOn: generatePDFs) deploy.doLast { println 'in task deploy' } • : This line shows a dependency. Note that the task on which we are depending has not been declared yet. • : The [a, b, ...] notation introduces a Groovy array value. The dependsOn parameter expects an array. But note that we did not use an array at . like most scripting languages, Groovy has lots of little shortcuts designed to make like easier for programmers. At , we got away due to a shortcut that allows a single element to be passed as an array of length 1. Running this gives:$ gradle deploy
:setupFormat UP-TO-DATE
:setupGraphics UP-TO-DATE
:setupSourceCode UP-TO-DATE
:setup
in task setup
:generatePDFs
in task generatePDFs
:deploy
in task deploy

### 3.3.1 Appending to Tasks

doLast (and doFirst) add actions to a task.

If we add the following to the previous script:

setup.doLast {
println 'still in task setup'
}
}
}
}
}

### 3.4.2 Using Ant within Tasks

The ant tasks library is included within gradle. So any useful ant task can be called:

task compile {
doLast {
// Compile src/.../*.java into bin/
ant.mkdir (dir: 'bin')
ant.javac (srcdir: 'src/main/java', destdir: 'bin',
debug: 'true', includeantruntime: 'false')
ant.javac (srcdir: 'src/test/java', destdir: 'bin',
debug: 'true', includeantruntime: 'false',
classpath: testCompilePath)
println 'compiled'
}
}

However, we probably would not use any of these:

• Most Gradle users would use Java constructs for file manipulations, e.g., instead of

ant.mkdir(dir: 'bin')

they would write

file('bin').mkdir();

• Gradle provides a Java plugin to handle compilation more easily.

• Moreover, if we already had the Ant build file simpleBuild,xml, we could actually simply import it into a Gradle build:

ant.importBuild 'simpleBuild.xml'

task build (dependsOn: 'deploy') .doLast {  // "deploy" target from the Ant build file
println 'Done'
}

## 3.5 Doing Maven-Like Things with Gradle

Like Maven, Gradle can be used to quickly create new projects with a standard directory/file layout and a standard sequence of build tasks. E.g.,

gradle init --type java-library

sets up a project with src/main/java and src/test/java directories.

# 4 Case Studies

## 4.1 Simple Java Build

Using the Java plugin, a basic build with unit tests is easy:

settings.gradle

// A simple Java project

This file needs to exist, but can be empty.

build.gradle

plugins {
id 'java'  ➀
}

repositories { ➁
jcenter()
}

This is all you need to compile Java code stores in src/main/java.

If you have unit tests, we need to add a little more info.

build.gradle

plugins {
id 'java'
}

repositories {
jcenter()
}

dependencies {
testImplementation("junit:junit:4.12")   ➀
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") ➁
}

test {  ➂
useJUnit()
}

The two sections at the end establish that

• When compiling our unit tests, we need to have the JUnit library, version 4.12, available.
• When running our unit tests, we need the library junit-vintage-engine, version 5.5.2, available.
• The code in the src/test directory contains our JUnit tests.

You can find this entire project, with the Gradle files, here.

## 4.2 Java Build with Code Generation

This project adds a stage before compilation to generate some of the source code that then needs to be compiled.

The settings.gradle file is unchanged.

Here is the build.gradle file:

build.gradle

plugins {
id 'java'
id 'org.xbib.gradle.plugin.jflex' version '1.2.1'  ➀
}

repositories {
jcenter()
}

dependencies {
testImplementation("junit:junit:4.12")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") /
}

test {
useJUnit()
}
• Here we apply the jflex plugin,
• which knows to run jflex on code located in src/main/jflex to generate Java source code
• and to compile that generated Java source code along with the rest of the project.

## 4.3 C++ Multi-project Build

A multi-project build in gradle is kept in a directory tree.

• The top-most directory is the master project.

• It contains the settings.gradle file.

• Each subproject directory contains its own build.gradle file.

### 4.3.1 The master project

Any multi-project build in Gradle uses the settings.gradle file (usually in the common root containing the subproject directories) to identify the subprojects:

rootProject.name = 'manhattan'

include "application", "geomlib"

In this case, we have two subprojects. One provides the executable, the other a library of ADTs, with unit tests, constituting the bulk of the code design.

There is no need for build.gradle file at this level, although it would be legal to provide one if you have project-level steps to be carried out.

### 4.3.2 The geomlib subproject

Of the two subprojects, the geomlib subproject is probably most interesting because it does the most. It compiles code into a library, then compiles and runs unit tests on that library code. By contrast, the application subproject only compiles code.

Here is the build.gradle file for the lib subproject:

build.gradle.lib.listing
plugins {
id 'cpp-library'
id 'cpp-unit-test'
}

unitTest {
binaries.whenElementKnown(CppTestExecutable) { binary ->
if (binary.targetMachine.operatingSystemFamily.linux) {
binary.linkTask.get().linkerArgs.add('-pthread')
}
}
}
• This listing starts off with two plugins, one for C++ compilation (into a reusable library) and the other for unit testing.

• The unit test plugin can work with a variety of frameworks. Eventually, you should be able to load these frameworks as dependencies, much as you do in Java projects.

In this case, I am using CppUnitLite because it can be easily dropped into the directory with the unit tests.

• Like most unit test frameworks, this one runs the tests in a separate thread (process). The unittest configuration here adds the required pthread library when compiling under Linux.

### 4.3.3 The application subproject

The application subproject is simpler, because it only has one job to do – create an executable. Here it is:

build.gradle.exe.listing
plugins {
id 'cpp-application'
}

dependencies {
implementation  project(':geomlib')
}
• Very simple: we invoke the cpp-application plugin and
• Indicate that this depends on the :geomlib subproject

This dependency guarantees that the geomlib library will be constructed before this application is built and that it will be automatically included into the application compilation and link steps.

You can find this entire project, with the Gradle files, here.